ETH Price: $2,793.46 (-0.49%)

Transaction Decoder

Block:
23863940 at Nov-23-2025 08:22:11 PM +UTC
Transaction Fee:
0.000214285562446808 ETH $0.60
Gas Used:
575,906 Gas / 0.372084268 Gwei

Emitted Events:

323 Morpho.FlashLoan( caller=[Receiver] 0x48b282b1632bf56f3393d3de0d239f35ff11d2e5, token=TetherToken, assets=126415814746 )
324 TetherToken.Transfer( from=Morpho, to=[Receiver] 0x48b282b1632bf56f3393d3de0d239f35ff11d2e5, value=126415814746 )
325 TetherToken.Transfer( from=[Receiver] 0x48b282b1632bf56f3393d3de0d239f35ff11d2e5, to=FluidLiquidityProxy, value=126415814746 )
326 FluidLiquidityProxy.0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15( 0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15, 0x000000000000000000000000ea734b615888c669667038d11950f44b177f15c0, 0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7, 0000000000000000000000000000000000000000000000000000000000000000, ffffffffffffffffffffffffffffffffffffffffffffffffffffffe2910b1fd2, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000009a655f754a9700000000000000000000de04b8afd04100, 00000000000000087315cc17500000081cbd4721e1a48da70c01e71543e80215 )
327 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e497, 0x00000000000000000000000048b282b1632bf56f3393d3de0d239f35ff11d2e5, 0000000000000000000000000000000000000000000000000000001d6d6cbd6d )
328 FluidLiquidityProxy.0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15( 0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15, 0x000000000000000000000000ea734b615888c669667038d11950f44b177f15c0, 0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000001d6d6cbd6d, 0000000000000000000000000000000000000000000000000000000000000000, 00000000000000000000000048b282b1632bf56f3393d3de0d239f35ff11d2e5, 000000000000000000b4105a598c6000000000000000000001032c65d4f99000, 00000000000000088390e906000000082eafb88b81a48da70c01e70d43e80212 )
329 FluidDexT1.Swap( swap0to1=False, amountIn=126415814746, amountOut=126389894509, to=[Receiver] 0x48b282b1632bf56f3393d3de0d239f35ff11d2e5 )
330 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00000000000000000000000048b282b1632bf56f3393d3de0d239f35ff11d2e5, 0x000000000000000000000000d0a61f2963622e992e6534bde4d52fd0a89f39e0, 0000000000000000000000000000000000000000000000000000001d6d6cbd6e )
331 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000d0a61f2963622e992e6534bde4d52fd0a89f39e0, 0x000000000000000000000000a188eec8f81263234da3622a406892f3d630f98c, 0000000000000000000000000000000000000000000000000000001d6d6cbd6e )
332 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000a188eec8f81263234da3622a406892f3d630f98c, 0x00000000000000000000000037305b1cd40574e4c5ce33f8e8306be057fd7341, 0000000000000000000000000000000000000000000000000000001d6d6cbd6e )
333 Dai.Transfer( src=DssLitePsm, dst=UsdsPsmWrapper, wad=126389894510000000000000 )
334 DssLitePsm.SellGem( owner=UsdsPsmWrapper, value=126389894510, fee=0 )
335 Vat.0xbb35783b00000000000000000000000000000000000000000000000000000000( 0xbb35783b00000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000009759a6ac90977b93b58547b4a71c78317f391a28, 0x000000000000000000000000a188eec8f81263234da3622a406892f3d630f98c, 0x0000000000000000000000567abee1f5d31a9b173f9267e3cc67ab0000000000, 0000000000000000000000000000000000000000000000000000000000000020, 00000000000000000000000000000000000000000000000000000000000000e0, bb35783b0000000000000000000000009759a6ac90977b93b58547b4a71c7831, 7f391a28000000000000000000000000a188eec8f81263234da3622a406892f3, d630f98c0000000000000000000000567abee1f5d31a9b173f9267e3cc67ab00, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000 )
336 Dai.Transfer( src=UsdsPsmWrapper, dst=0x0000000000000000000000000000000000000000, wad=126389894510000000000000 )
337 DaiJoin.0x3b4da69f00000000000000000000000000000000000000000000000000000000( 0x3b4da69f00000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000a188eec8f81263234da3622a406892f3d630f98c, 0x000000000000000000000000a188eec8f81263234da3622a406892f3d630f98c, 0x000000000000000000000000000000000000000000001ac39c23167bfbbce000, 0000000000000000000000000000000000000000000000000000000000000020, 00000000000000000000000000000000000000000000000000000000000000e0, 3b4da69f000000000000000000000000a188eec8f81263234da3622a406892f3, d630f98c000000000000000000000000000000000000000000001ac39c23167b, fbbce00000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000 )
338 Vat.0xbb35783b00000000000000000000000000000000000000000000000000000000( 0xbb35783b00000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000a188eec8f81263234da3622a406892f3d630f98c, 0x0000000000000000000000003c0f895007ca717aa01c8693e59df1e8c3777feb, 0x0000000000000000000000567abee1f5d31a9b173f9267e3cc67ab0000000000, 0000000000000000000000000000000000000000000000000000000000000020, 00000000000000000000000000000000000000000000000000000000000000e0, bb35783b000000000000000000000000a188eec8f81263234da3622a406892f3, d630f98c0000000000000000000000003c0f895007ca717aa01c8693e59df1e8, c3777feb0000000000000000000000567abee1f5d31a9b173f9267e3cc67ab00, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000 )
339 ERC1967Proxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000d0a61f2963622e992e6534bde4d52fd0a89f39e0, 000000000000000000000000000000000000000000001ac39c23167bfbbce000 )
340 UsdsJoin.Exit( caller=UsdsPsmWrapper, usr=PSMVariant1Actions, wad=126389894510000000000000 )
341 Vat.0xf24e23eb00000000000000000000000000000000000000000000000000000000( 0xf24e23eb00000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000a950524441892a31ebddf91d3ceefa04bf454466, 0x000000000000000000000000a3931d71877c0e7a3148cb7eb4463524fec27fbd, 0x00000000000000000000000070bc42ea6a8e91ae8b916954a76a835790000000, 0000000000000000000000000000000000000000000000000000000000000020, 00000000000000000000000000000000000000000000000000000000000000e0, f24e23eb000000000000000000000000a950524441892a31ebddf91d3ceefa04, bf454466000000000000000000000000a3931d71877c0e7a3148cb7eb4463524, fec27fbd00000000000000000000000070bc42ea6a8e91ae8b916954a76a8357, 9000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000 )
342 Vat.0xbb35783b00000000000000000000000000000000000000000000000000000000( 0xbb35783b00000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000a3931d71877c0e7a3148cb7eb4463524fec27fbd, 0x0000000000000000000000003c0f895007ca717aa01c8693e59df1e8c3777feb, 0x00000000000000000000000070bc42ea6a8e91ae8b916954a76a835790000000, 0000000000000000000000000000000000000000000000000000000000000020, 00000000000000000000000000000000000000000000000000000000000000e0, bb35783b000000000000000000000000a3931d71877c0e7a3148cb7eb4463524, fec27fbd0000000000000000000000003c0f895007ca717aa01c8693e59df1e8, c3777feb00000000000000000000000070bc42ea6a8e91ae8b916954a76a8357, 9000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000 )
343 ERC1967Proxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000a3931d71877c0e7a3148cb7eb4463524fec27fbd, 000000000000000000000000000000000000000000000022e3d17ac4b77da91a )
344 UsdsJoin.Exit( caller=ERC1967Proxy, usr=ERC1967Proxy, wad=643605335608177568026 )
345 ERC1967Proxy.0xad1e8a53178522eb68a9d94d862bf30c841f709d2115f743eb6b34528751c79f( 0xad1e8a53178522eb68a9d94d862bf30c841f709d2115f743eb6b34528751c79f, 0000000000000000000000000000000000000000037aa29d12c0ff3e1c2d7c0e, 000000000000000000000000000000000000000000000022e3d17ac4b77da91a )
346 ERC1967Proxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000d0a61f2963622e992e6534bde4d52fd0a89f39e0, 0x000000000000000000000000a3931d71877c0e7a3148cb7eb4463524fec27fbd, 000000000000000000000000000000000000000000001ac39c23167bfbbce000 )
347 ERC1967Proxy.0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7( 0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7, 0x000000000000000000000000d0a61f2963622e992e6534bde4d52fd0a89f39e0, 0x00000000000000000000000048b282b1632bf56f3393d3de0d239f35ff11d2e5, 000000000000000000000000000000000000000000001ac39c23167bfbbce000, 0000000000000000000000000000000000000000000018db74ffdc0abdb23dc2 )
348 ERC1967Proxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x00000000000000000000000048b282b1632bf56f3393d3de0d239f35ff11d2e5, 0000000000000000000000000000000000000000000018db74ffdc0abdb23dc2 )
349 ERC1967Proxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00000000000000000000000048b282b1632bf56f3393d3de0d239f35ff11d2e5, 0x00000000000000000000000000836fe54625be242bcfa286207795405ca4fd10, 0000000000000000000000000000000000000000000018db74ffdc0abdb23dc2 )
350 TetherToken.Transfer( from=CurveStableSwapNG, to=[Receiver] 0x48b282b1632bf56f3393d3de0d239f35ff11d2e5, value=126416703951 )
351 CurveStableSwapNG.TokenExchange( buyer=[Receiver] 0x48b282b1632bf56f3393d3de0d239f35ff11d2e5, sold_id=0, tokens_sold=117385063239980028149186, bought_id=1, tokens_bought=126416703951 )
352 TetherToken.Transfer( from=[Receiver] 0x48b282b1632bf56f3393d3de0d239f35ff11d2e5, to=Morpho, value=126415814746 )
353 TetherToken.Transfer( from=[Receiver] 0x48b282b1632bf56f3393d3de0d239f35ff11d2e5, to=0xEA7A2e09655c4028C18D9bF4249088B1Fd86483B, value=889204 )

Account State Difference:

  Address   Before After State Difference Code
0x00836Fe5...05ca4fD10
0x35D1b3F3...259A0492B
(Sky: MCD Vat)
(Titan Builder)
19.908444611298346458 Eth19.90860103109521009 Eth0.000156419796863632
0x52Aa8994...360F4e497
(Fluid: Liquidity)
0x6B175474...495271d0F
0xA0b86991...E3606eB48
0xa3931d71...4FEc27fbD
0xdAC17F95...13D831ec7
0xdC035D45...1e407384F
0xea734B61...b177F15C0
0xFfD514F5...B2B9a46aF
0.240864989417828175 Eth
Nonce: 699
0.240650703855381367 Eth
Nonce: 700
0.000214285562446808

Execution Trace

0x48b282b1632bf56f3393d3de0d239f35ff11d2e5.00000000( )
  • 0x5597f24d81b28f50d8c3a6c93175e990f67b9469.00dac17f( )
    • Morpho.flashLoan( token=0xdAC17F958D2ee523a2206206994597C13D831ec7, assets=126415814746, data=0x032C240D4FFBD7F070E2ABDCD4EC74612A12A8BAE7013CEA734B615888C669667038D11950F44B177F15C001000001DAC17F958D2EE523A2206206994597C13D831EC70000051D6EF8405A2668DFAA01600120A02264E8E722846D2B1AB1377B23C855FA7A2D250153D0A61F2963622E992E6534BDE4D52FD0A89F39E001A0B86991C6218B36C1D19D4A2E9EB0CE3606EB4800018FBA2CEE0140012000000000000000000000000048B282B1632BF56F3393D3DE0D239F35FF11D2E5C768A2A9D660E69FEF58C0D0474A36C3DD200F4A012E00836FE54625BE242BCFA286207795405CA4FD100000A3931D71877C0E7A3148CB7EB4463524FEC27FBD00010100 )
      • TetherToken.transfer( _to=0x48b282b1632bf56f3393D3dE0D239f35Ff11D2e5, _value=126415814746 )
      • 0x48b282b1632bf56f3393d3de0d239f35ff11d2e5.31f57072( )
        • 0x2c240d4ffbd7f070e2abdcd4ec74612a12a8bae7.ea734b61( )
          • FluidDexT1.swapIn( swap0to1_=False, amountIn_=126415814746, amountOutMin_=0, to_=0x0000000000000000000000000000000000000000 ) => ( amountOut_=126389894509 )
            • FluidLiquidityProxy.readFromStorage( slot_=A8E1248EDDF82E10C0ADC6C737B6D8DA17674ABF51801EA5A4549F41C2DFDF21 ) => ( result_=53442730890472801298876722550152728763181593523129625870866 )
            • FluidLiquidityProxy.readFromStorage( slot_=0A7E0E74B40A947DAF7B6DF34C66BE699F819F509940D1BD48C4D99BC5E3353C ) => ( result_=53038620290151228421945526754859365189439671649538406875669 )
            • FluidLiquidityProxy.readFromStorage( slot_=753784F12D1770404971A48547744D2C8005DDEAD6F02135C6C0539809FF6BF5 ) => ( result_=60728775274659520129011857219840934611559302066264299152948159261161473 )
            • FluidLiquidityProxy.readFromStorage( slot_=EF8CB1C130DE701FC867F50D55E6677C25D097155830B2703351BD2E2954160F ) => ( result_=61160136066838487686932571616202431190713997186187755335895298979557377 )
            • FluidLiquidityProxy.ad967e15( )
            • FluidLiquidityProxy.ad967e15( )
            • 0xa02264e8e722846d2b1ab1377b23c855fa7a2d25.d0a61f29( )
              • FiatTokenProxy.70a08231( )
                • FiatTokenV2_2.balanceOf( account=0x48b282b1632bf56f3393D3dE0D239f35Ff11D2e5 ) => ( 126389894510 )
                • PSMVariant1Actions.swapAndDeposit( receiver=0x48b282b1632bf56f3393D3dE0D239f35Ff11D2e5, amountIn=126389894510, minAmountOut=0 ) => ( amountOut=126389894510000000000000 )
                  • FiatTokenProxy.23b872dd( )
                  • ERC1967Proxy.70a08231( )
                  • UsdsPsmWrapper.sellGem( usr=0xd0A61F2963622e992e6534bde4D52fd0a89F39E0, gemAmt=126389894510 ) => ( usdsOutWad=126389894510000000000000 )
                  • ERC1967Proxy.70a08231( )
                  • ERC1967Proxy.6e553f65( )
                  • 0xc768a2a9d660e69fef58c0d0474a36c3dd200f4a.00836fe5( )
                    • ERC1967Proxy.70a08231( )
                      • SUsds.balanceOf( 0x48b282b1632bf56f3393D3dE0D239f35Ff11D2e5 ) => ( 117385063239980028149187 )
                      • CurveStableSwapNG.exchange( i=0, j=1, _dx=117385063239980028149186, _min_dy=0 ) => ( 126416703951 )
                        • ERC1967Proxy.07a2d13a( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • ERC1967Proxy.70a08231( )
                        • ERC1967Proxy.23b872dd( )
                        • ERC1967Proxy.70a08231( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • Null: 0x000...004.00000000( )
                        • TetherToken.transfer( _to=0x48b282b1632bf56f3393D3dE0D239f35Ff11D2e5, _value=126416703951 )
                        • TetherToken.transferFrom( _from=0x48b282b1632bf56f3393D3dE0D239f35Ff11D2e5, _to=0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb, _value=126415814746 )
                        • 0x91cac5d4a3284b13bec08befef15720375e0b1ee.ea7a2e09( )
                          • TetherToken.balanceOf( who=0x48b282b1632bf56f3393D3dE0D239f35Ff11D2e5 ) => ( 889205 )
                          • TetherToken.transfer( _to=0xEA7A2e09655c4028C18D9bF4249088B1Fd86483B, _value=889204 )
                            File 1 of 17: Morpho
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.19;
                            import {
                                Id,
                                IMorphoStaticTyping,
                                IMorphoBase,
                                MarketParams,
                                Position,
                                Market,
                                Authorization,
                                Signature
                            } from "./interfaces/IMorpho.sol";
                            import {
                                IMorphoLiquidateCallback,
                                IMorphoRepayCallback,
                                IMorphoSupplyCallback,
                                IMorphoSupplyCollateralCallback,
                                IMorphoFlashLoanCallback
                            } from "./interfaces/IMorphoCallbacks.sol";
                            import {IIrm} from "./interfaces/IIrm.sol";
                            import {IERC20} from "./interfaces/IERC20.sol";
                            import {IOracle} from "./interfaces/IOracle.sol";
                            import "./libraries/ConstantsLib.sol";
                            import {UtilsLib} from "./libraries/UtilsLib.sol";
                            import {EventsLib} from "./libraries/EventsLib.sol";
                            import {ErrorsLib} from "./libraries/ErrorsLib.sol";
                            import {MathLib, WAD} from "./libraries/MathLib.sol";
                            import {SharesMathLib} from "./libraries/SharesMathLib.sol";
                            import {MarketParamsLib} from "./libraries/MarketParamsLib.sol";
                            import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";
                            /// @title Morpho
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @notice The Morpho contract.
                            contract Morpho is IMorphoStaticTyping {
                                using MathLib for uint128;
                                using MathLib for uint256;
                                using UtilsLib for uint256;
                                using SharesMathLib for uint256;
                                using SafeTransferLib for IERC20;
                                using MarketParamsLib for MarketParams;
                                /* IMMUTABLES */
                                /// @inheritdoc IMorphoBase
                                bytes32 public immutable DOMAIN_SEPARATOR;
                                /* STORAGE */
                                /// @inheritdoc IMorphoBase
                                address public owner;
                                /// @inheritdoc IMorphoBase
                                address public feeRecipient;
                                /// @inheritdoc IMorphoStaticTyping
                                mapping(Id => mapping(address => Position)) public position;
                                /// @inheritdoc IMorphoStaticTyping
                                mapping(Id => Market) public market;
                                /// @inheritdoc IMorphoBase
                                mapping(address => bool) public isIrmEnabled;
                                /// @inheritdoc IMorphoBase
                                mapping(uint256 => bool) public isLltvEnabled;
                                /// @inheritdoc IMorphoBase
                                mapping(address => mapping(address => bool)) public isAuthorized;
                                /// @inheritdoc IMorphoBase
                                mapping(address => uint256) public nonce;
                                /// @inheritdoc IMorphoStaticTyping
                                mapping(Id => MarketParams) public idToMarketParams;
                                /* CONSTRUCTOR */
                                /// @param newOwner The new owner of the contract.
                                constructor(address newOwner) {
                                    require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
                                    DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
                                    owner = newOwner;
                                    emit EventsLib.SetOwner(newOwner);
                                }
                                /* MODIFIERS */
                                /// @dev Reverts if the caller is not the owner.
                                modifier onlyOwner() {
                                    require(msg.sender == owner, ErrorsLib.NOT_OWNER);
                                    _;
                                }
                                /* ONLY OWNER FUNCTIONS */
                                /// @inheritdoc IMorphoBase
                                function setOwner(address newOwner) external onlyOwner {
                                    require(newOwner != owner, ErrorsLib.ALREADY_SET);
                                    owner = newOwner;
                                    emit EventsLib.SetOwner(newOwner);
                                }
                                /// @inheritdoc IMorphoBase
                                function enableIrm(address irm) external onlyOwner {
                                    require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
                                    isIrmEnabled[irm] = true;
                                    emit EventsLib.EnableIrm(irm);
                                }
                                /// @inheritdoc IMorphoBase
                                function enableLltv(uint256 lltv) external onlyOwner {
                                    require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
                                    require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
                                    isLltvEnabled[lltv] = true;
                                    emit EventsLib.EnableLltv(lltv);
                                }
                                /// @inheritdoc IMorphoBase
                                function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner {
                                    Id id = marketParams.id();
                                    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
                                    require(newFee != market[id].fee, ErrorsLib.ALREADY_SET);
                                    require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
                                    // Accrue interest using the previous fee set before changing it.
                                    _accrueInterest(marketParams, id);
                                    // Safe "unchecked" cast.
                                    market[id].fee = uint128(newFee);
                                    emit EventsLib.SetFee(id, newFee);
                                }
                                /// @inheritdoc IMorphoBase
                                function setFeeRecipient(address newFeeRecipient) external onlyOwner {
                                    require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
                                    feeRecipient = newFeeRecipient;
                                    emit EventsLib.SetFeeRecipient(newFeeRecipient);
                                }
                                /* MARKET CREATION */
                                /// @inheritdoc IMorphoBase
                                function createMarket(MarketParams memory marketParams) external {
                                    Id id = marketParams.id();
                                    require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
                                    require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
                                    require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
                                    // Safe "unchecked" cast.
                                    market[id].lastUpdate = uint128(block.timestamp);
                                    idToMarketParams[id] = marketParams;
                                    emit EventsLib.CreateMarket(id, marketParams);
                                    // Call to initialize the IRM in case it is stateful.
                                    if (marketParams.irm != address(0)) IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
                                }
                                /* SUPPLY MANAGEMENT */
                                /// @inheritdoc IMorphoBase
                                function supply(
                                    MarketParams memory marketParams,
                                    uint256 assets,
                                    uint256 shares,
                                    address onBehalf,
                                    bytes calldata data
                                ) external returns (uint256, uint256) {
                                    Id id = marketParams.id();
                                    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
                                    require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
                                    require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
                                    _accrueInterest(marketParams, id);
                                    if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
                                    else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
                                    position[id][onBehalf].supplyShares += shares;
                                    market[id].totalSupplyShares += shares.toUint128();
                                    market[id].totalSupplyAssets += assets.toUint128();
                                    emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
                                    if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
                                    IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
                                    return (assets, shares);
                                }
                                /// @inheritdoc IMorphoBase
                                function withdraw(
                                    MarketParams memory marketParams,
                                    uint256 assets,
                                    uint256 shares,
                                    address onBehalf,
                                    address receiver
                                ) external returns (uint256, uint256) {
                                    Id id = marketParams.id();
                                    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
                                    require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
                                    require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
                                    // No need to verify that onBehalf != address(0) thanks to the following authorization check.
                                    require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
                                    _accrueInterest(marketParams, id);
                                    if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
                                    else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
                                    position[id][onBehalf].supplyShares -= shares;
                                    market[id].totalSupplyShares -= shares.toUint128();
                                    market[id].totalSupplyAssets -= assets.toUint128();
                                    require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
                                    emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
                                    IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
                                    return (assets, shares);
                                }
                                /* BORROW MANAGEMENT */
                                /// @inheritdoc IMorphoBase
                                function borrow(
                                    MarketParams memory marketParams,
                                    uint256 assets,
                                    uint256 shares,
                                    address onBehalf,
                                    address receiver
                                ) external returns (uint256, uint256) {
                                    Id id = marketParams.id();
                                    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
                                    require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
                                    require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
                                    // No need to verify that onBehalf != address(0) thanks to the following authorization check.
                                    require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
                                    _accrueInterest(marketParams, id);
                                    if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
                                    else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
                                    position[id][onBehalf].borrowShares += shares.toUint128();
                                    market[id].totalBorrowShares += shares.toUint128();
                                    market[id].totalBorrowAssets += assets.toUint128();
                                    require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
                                    require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
                                    emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);
                                    IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
                                    return (assets, shares);
                                }
                                /// @inheritdoc IMorphoBase
                                function repay(
                                    MarketParams memory marketParams,
                                    uint256 assets,
                                    uint256 shares,
                                    address onBehalf,
                                    bytes calldata data
                                ) external returns (uint256, uint256) {
                                    Id id = marketParams.id();
                                    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
                                    require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
                                    require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
                                    _accrueInterest(marketParams, id);
                                    if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
                                    else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
                                    position[id][onBehalf].borrowShares -= shares.toUint128();
                                    market[id].totalBorrowShares -= shares.toUint128();
                                    market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
                                    // `assets` may be greater than `totalBorrowAssets` by 1.
                                    emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
                                    if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
                                    IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
                                    return (assets, shares);
                                }
                                /* COLLATERAL MANAGEMENT */
                                /// @inheritdoc IMorphoBase
                                function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
                                    external
                                {
                                    Id id = marketParams.id();
                                    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
                                    require(assets != 0, ErrorsLib.ZERO_ASSETS);
                                    require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
                                    // Don't accrue interest because it's not required and it saves gas.
                                    position[id][onBehalf].collateral += assets.toUint128();
                                    emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
                                    if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
                                    IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
                                }
                                /// @inheritdoc IMorphoBase
                                function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
                                    external
                                {
                                    Id id = marketParams.id();
                                    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
                                    require(assets != 0, ErrorsLib.ZERO_ASSETS);
                                    require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
                                    // No need to verify that onBehalf != address(0) thanks to the following authorization check.
                                    require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
                                    _accrueInterest(marketParams, id);
                                    position[id][onBehalf].collateral -= assets.toUint128();
                                    require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
                                    emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);
                                    IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
                                }
                                /* LIQUIDATION */
                                /// @inheritdoc IMorphoBase
                                function liquidate(
                                    MarketParams memory marketParams,
                                    address borrower,
                                    uint256 seizedAssets,
                                    uint256 repaidShares,
                                    bytes calldata data
                                ) external returns (uint256, uint256) {
                                    Id id = marketParams.id();
                                    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
                                    require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
                                    _accrueInterest(marketParams, id);
                                    {
                                        uint256 collateralPrice = IOracle(marketParams.oracle).price();
                                        require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
                                        // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
                                        uint256 liquidationIncentiveFactor = UtilsLib.min(
                                            MAX_LIQUIDATION_INCENTIVE_FACTOR,
                                            WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
                                        );
                                        if (seizedAssets > 0) {
                                            uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE);
                                            repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentiveFactor).toSharesUp(
                                                market[id].totalBorrowAssets, market[id].totalBorrowShares
                                            );
                                        } else {
                                            seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares)
                                                .wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
                                        }
                                    }
                                    uint256 repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
                                    position[id][borrower].borrowShares -= repaidShares.toUint128();
                                    market[id].totalBorrowShares -= repaidShares.toUint128();
                                    market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
                                    position[id][borrower].collateral -= seizedAssets.toUint128();
                                    uint256 badDebtShares;
                                    uint256 badDebtAssets;
                                    if (position[id][borrower].collateral == 0) {
                                        badDebtShares = position[id][borrower].borrowShares;
                                        badDebtAssets = UtilsLib.min(
                                            market[id].totalBorrowAssets,
                                            badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
                                        );
                                        market[id].totalBorrowAssets -= badDebtAssets.toUint128();
                                        market[id].totalSupplyAssets -= badDebtAssets.toUint128();
                                        market[id].totalBorrowShares -= badDebtShares.toUint128();
                                        position[id][borrower].borrowShares = 0;
                                    }
                                    // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
                                    emit EventsLib.Liquidate(
                                        id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtAssets, badDebtShares
                                    );
                                    IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
                                    if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
                                    IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
                                    return (seizedAssets, repaidAssets);
                                }
                                /* FLASH LOANS */
                                /// @inheritdoc IMorphoBase
                                function flashLoan(address token, uint256 assets, bytes calldata data) external {
                                    require(assets != 0, ErrorsLib.ZERO_ASSETS);
                                    emit EventsLib.FlashLoan(msg.sender, token, assets);
                                    IERC20(token).safeTransfer(msg.sender, assets);
                                    IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
                                    IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
                                }
                                /* AUTHORIZATION */
                                /// @inheritdoc IMorphoBase
                                function setAuthorization(address authorized, bool newIsAuthorized) external {
                                    require(newIsAuthorized != isAuthorized[msg.sender][authorized], ErrorsLib.ALREADY_SET);
                                    isAuthorized[msg.sender][authorized] = newIsAuthorized;
                                    emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
                                }
                                /// @inheritdoc IMorphoBase
                                function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
                                    /// Do not check whether authorization is already set because the nonce increment is a desired side effect.
                                    require(block.timestamp <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED);
                                    require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
                                    bytes32 hashStruct = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization));
                                    bytes32 digest = keccak256(bytes.concat("\\x19\\x01", DOMAIN_SEPARATOR, hashStruct));
                                    address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
                                    require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
                                    emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
                                    isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
                                    emit EventsLib.SetAuthorization(
                                        msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
                                    );
                                }
                                /// @dev Returns whether the sender is authorized to manage `onBehalf`'s positions.
                                function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
                                    return msg.sender == onBehalf || isAuthorized[onBehalf][msg.sender];
                                }
                                /* INTEREST MANAGEMENT */
                                /// @inheritdoc IMorphoBase
                                function accrueInterest(MarketParams memory marketParams) external {
                                    Id id = marketParams.id();
                                    require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
                                    _accrueInterest(marketParams, id);
                                }
                                /// @dev Accrues interest for the given market `marketParams`.
                                /// @dev Assumes that the inputs `marketParams` and `id` match.
                                function _accrueInterest(MarketParams memory marketParams, Id id) internal {
                                    uint256 elapsed = block.timestamp - market[id].lastUpdate;
                                    if (elapsed == 0) return;
                                    if (marketParams.irm != address(0)) {
                                        uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
                                        uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
                                        market[id].totalBorrowAssets += interest.toUint128();
                                        market[id].totalSupplyAssets += interest.toUint128();
                                        uint256 feeShares;
                                        if (market[id].fee != 0) {
                                            uint256 feeAmount = interest.wMulDown(market[id].fee);
                                            // The fee amount is subtracted from the total supply in this calculation to compensate for the fact
                                            // that total supply is already increased by the full interest (including the fee amount).
                                            feeShares =
                                                feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares);
                                            position[id][feeRecipient].supplyShares += feeShares;
                                            market[id].totalSupplyShares += feeShares.toUint128();
                                        }
                                        emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
                                    }
                                    // Safe "unchecked" cast.
                                    market[id].lastUpdate = uint128(block.timestamp);
                                }
                                /* HEALTH CHECK */
                                /// @dev Returns whether the position of `borrower` in the given market `marketParams` is healthy.
                                /// @dev Assumes that the inputs `marketParams` and `id` match.
                                function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
                                    if (position[id][borrower].borrowShares == 0) return true;
                                    uint256 collateralPrice = IOracle(marketParams.oracle).price();
                                    return _isHealthy(marketParams, id, borrower, collateralPrice);
                                }
                                /// @dev Returns whether the position of `borrower` in the given market `marketParams` with the given
                                /// `collateralPrice` is healthy.
                                /// @dev Assumes that the inputs `marketParams` and `id` match.
                                /// @dev Rounds in favor of the protocol, so one might not be able to borrow exactly `maxBorrow` but one unit less.
                                function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
                                    internal
                                    view
                                    returns (bool)
                                {
                                    uint256 borrowed = uint256(position[id][borrower].borrowShares).toAssetsUp(
                                        market[id].totalBorrowAssets, market[id].totalBorrowShares
                                    );
                                    uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
                                        .wMulDown(marketParams.lltv);
                                    return maxBorrow >= borrowed;
                                }
                                /* STORAGE VIEW */
                                /// @inheritdoc IMorphoBase
                                function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
                                    uint256 nSlots = slots.length;
                                    res = new bytes32[](nSlots);
                                    for (uint256 i; i < nSlots;) {
                                        bytes32 slot = slots[i++];
                                        assembly ("memory-safe") {
                                            mstore(add(res, mul(i, 32)), sload(slot))
                                        }
                                    }
                                }
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity >=0.5.0;
                            type Id is bytes32;
                            struct MarketParams {
                                address loanToken;
                                address collateralToken;
                                address oracle;
                                address irm;
                                uint256 lltv;
                            }
                            /// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest
                            /// accrual.
                            struct Position {
                                uint256 supplyShares;
                                uint128 borrowShares;
                                uint128 collateral;
                            }
                            /// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
                            /// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
                            /// @dev Warning: `totalSupplyShares` does not contain the additional shares accrued by `feeRecipient` since the last
                            /// interest accrual.
                            struct Market {
                                uint128 totalSupplyAssets;
                                uint128 totalSupplyShares;
                                uint128 totalBorrowAssets;
                                uint128 totalBorrowShares;
                                uint128 lastUpdate;
                                uint128 fee;
                            }
                            struct Authorization {
                                address authorizer;
                                address authorized;
                                bool isAuthorized;
                                uint256 nonce;
                                uint256 deadline;
                            }
                            struct Signature {
                                uint8 v;
                                bytes32 r;
                                bytes32 s;
                            }
                            /// @dev This interface is used for factorizing IMorphoStaticTyping and IMorpho.
                            /// @dev Consider using the IMorpho interface instead of this one.
                            interface IMorphoBase {
                                /// @notice The EIP-712 domain separator.
                                /// @dev Warning: Every EIP-712 signed message based on this domain separator can be reused on another chain sharing
                                /// the same chain id because the domain separator would be the same.
                                function DOMAIN_SEPARATOR() external view returns (bytes32);
                                /// @notice The owner of the contract.
                                /// @dev It has the power to change the owner.
                                /// @dev It has the power to set fees on markets and set the fee recipient.
                                /// @dev It has the power to enable but not disable IRMs and LLTVs.
                                function owner() external view returns (address);
                                /// @notice The fee recipient of all markets.
                                /// @dev The recipient receives the fees of a given market through a supply position on that market.
                                function feeRecipient() external view returns (address);
                                /// @notice Whether the `irm` is enabled.
                                function isIrmEnabled(address irm) external view returns (bool);
                                /// @notice Whether the `lltv` is enabled.
                                function isLltvEnabled(uint256 lltv) external view returns (bool);
                                /// @notice Whether `authorized` is authorized to modify `authorizer`'s position on all markets.
                                /// @dev Anyone is authorized to modify their own positions, regardless of this variable.
                                function isAuthorized(address authorizer, address authorized) external view returns (bool);
                                /// @notice The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures.
                                function nonce(address authorizer) external view returns (uint256);
                                /// @notice Sets `newOwner` as `owner` of the contract.
                                /// @dev Warning: No two-step transfer ownership.
                                /// @dev Warning: The owner can be set to the zero address.
                                function setOwner(address newOwner) external;
                                /// @notice Enables `irm` as a possible IRM for market creation.
                                /// @dev Warning: It is not possible to disable an IRM.
                                function enableIrm(address irm) external;
                                /// @notice Enables `lltv` as a possible LLTV for market creation.
                                /// @dev Warning: It is not possible to disable a LLTV.
                                function enableLltv(uint256 lltv) external;
                                /// @notice Sets the `newFee` for the given market `marketParams`.
                                /// @param newFee The new fee, scaled by WAD.
                                /// @dev Warning: The recipient can be the zero address.
                                function setFee(MarketParams memory marketParams, uint256 newFee) external;
                                /// @notice Sets `newFeeRecipient` as `feeRecipient` of the fee.
                                /// @dev Warning: If the fee recipient is set to the zero address, fees will accrue there and will be lost.
                                /// @dev Modifying the fee recipient will allow the new recipient to claim any pending fees not yet accrued. To
                                /// ensure that the current recipient receives all due fees, accrue interest manually prior to making any changes.
                                function setFeeRecipient(address newFeeRecipient) external;
                                /// @notice Creates the market `marketParams`.
                                /// @dev Here is the list of assumptions on the market's dependencies (tokens, IRM and oracle) that guarantees
                                /// Morpho behaves as expected:
                                /// - The token should be ERC-20 compliant, except that it can omit return values on `transfer` and `transferFrom`.
                                /// - The token balance of Morpho should only decrease on `transfer` and `transferFrom`. In particular, tokens with
                                /// burn functions are not supported.
                                /// - The token should not re-enter Morpho on `transfer` nor `transferFrom`.
                                /// - The token balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount
                                /// on `transfer` and `transferFrom`. In particular, tokens with fees on transfer are not supported.
                                /// - The IRM should not re-enter Morpho.
                                /// - The oracle should return a price with the correct scaling.
                                /// @dev Here is a list of properties on the market's dependencies that could break Morpho's liveness properties
                                /// (funds could get stuck):
                                /// - The token can revert on `transfer` and `transferFrom` for a reason other than an approval or balance issue.
                                /// - A very high amount of assets (~1e35) supplied or borrowed can make the computation of `toSharesUp` and
                                /// `toSharesDown` overflow.
                                /// - The IRM can revert on `borrowRate`.
                                /// - A very high borrow rate returned by the IRM can make the computation of `interest` in `_accrueInterest`
                                /// overflow.
                                /// - The oracle can revert on `price`. Note that this can be used to prevent `borrow`, `withdrawCollateral` and
                                /// `liquidate` from being used under certain market conditions.
                                /// - A very high price returned by the oracle can make the computation of `maxBorrow` in `_isHealthy` overflow, or
                                /// the computation of `assetsRepaid` in `liquidate` overflow.
                                /// @dev The borrow share price of a market with less than 1e4 assets borrowed can be decreased by manipulations, to
                                /// the point where `totalBorrowShares` is very large and borrowing overflows.
                                function createMarket(MarketParams memory marketParams) external;
                                /// @notice Supplies `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's
                                /// `onMorphoSupply` function with the given `data`.
                                /// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the
                                /// caller is guaranteed to have `assets` tokens pulled from their balance, but the possibility to mint a specific
                                /// amount of shares is given for full compatibility and precision.
                                /// @dev Supplying a large amount can revert for overflow.
                                /// @dev Supplying an amount of shares may lead to supply more or fewer assets than expected due to slippage.
                                /// Consider using the `assets` parameter to avoid this.
                                /// @param marketParams The market to supply assets to.
                                /// @param assets The amount of assets to supply.
                                /// @param shares The amount of shares to mint.
                                /// @param onBehalf The address that will own the increased supply position.
                                /// @param data Arbitrary data to pass to the `onMorphoSupply` callback. Pass empty data if not needed.
                                /// @return assetsSupplied The amount of assets supplied.
                                /// @return sharesSupplied The amount of shares minted.
                                function supply(
                                    MarketParams memory marketParams,
                                    uint256 assets,
                                    uint256 shares,
                                    address onBehalf,
                                    bytes memory data
                                ) external returns (uint256 assetsSupplied, uint256 sharesSupplied);
                                /// @notice Withdraws `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`.
                                /// @dev Either `assets` or `shares` should be zero. To withdraw max, pass the `shares`'s balance of `onBehalf`.
                                /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
                                /// @dev Withdrawing an amount corresponding to more shares than supplied will revert for underflow.
                                /// @dev It is advised to use the `shares` input when withdrawing the full position to avoid reverts due to
                                /// conversion roundings between shares and assets.
                                /// @param marketParams The market to withdraw assets from.
                                /// @param assets The amount of assets to withdraw.
                                /// @param shares The amount of shares to burn.
                                /// @param onBehalf The address of the owner of the supply position.
                                /// @param receiver The address that will receive the withdrawn assets.
                                /// @return assetsWithdrawn The amount of assets withdrawn.
                                /// @return sharesWithdrawn The amount of shares burned.
                                function withdraw(
                                    MarketParams memory marketParams,
                                    uint256 assets,
                                    uint256 shares,
                                    address onBehalf,
                                    address receiver
                                ) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn);
                                /// @notice Borrows `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`.
                                /// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the
                                /// caller is guaranteed to borrow `assets` of tokens, but the possibility to mint a specific amount of shares is
                                /// given for full compatibility and precision.
                                /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
                                /// @dev Borrowing a large amount can revert for overflow.
                                /// @dev Borrowing an amount of shares may lead to borrow fewer assets than expected due to slippage.
                                /// Consider using the `assets` parameter to avoid this.
                                /// @param marketParams The market to borrow assets from.
                                /// @param assets The amount of assets to borrow.
                                /// @param shares The amount of shares to mint.
                                /// @param onBehalf The address that will own the increased borrow position.
                                /// @param receiver The address that will receive the borrowed assets.
                                /// @return assetsBorrowed The amount of assets borrowed.
                                /// @return sharesBorrowed The amount of shares minted.
                                function borrow(
                                    MarketParams memory marketParams,
                                    uint256 assets,
                                    uint256 shares,
                                    address onBehalf,
                                    address receiver
                                ) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed);
                                /// @notice Repays `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's
                                /// `onMorphoReplay` function with the given `data`.
                                /// @dev Either `assets` or `shares` should be zero. To repay max, pass the `shares`'s balance of `onBehalf`.
                                /// @dev Repaying an amount corresponding to more shares than borrowed will revert for underflow.
                                /// @dev It is advised to use the `shares` input when repaying the full position to avoid reverts due to conversion
                                /// roundings between shares and assets.
                                /// @dev An attacker can front-run a repay with a small repay making the transaction revert for underflow.
                                /// @param marketParams The market to repay assets to.
                                /// @param assets The amount of assets to repay.
                                /// @param shares The amount of shares to burn.
                                /// @param onBehalf The address of the owner of the debt position.
                                /// @param data Arbitrary data to pass to the `onMorphoRepay` callback. Pass empty data if not needed.
                                /// @return assetsRepaid The amount of assets repaid.
                                /// @return sharesRepaid The amount of shares burned.
                                function repay(
                                    MarketParams memory marketParams,
                                    uint256 assets,
                                    uint256 shares,
                                    address onBehalf,
                                    bytes memory data
                                ) external returns (uint256 assetsRepaid, uint256 sharesRepaid);
                                /// @notice Supplies `assets` of collateral on behalf of `onBehalf`, optionally calling back the caller's
                                /// `onMorphoSupplyCollateral` function with the given `data`.
                                /// @dev Interest are not accrued since it's not required and it saves gas.
                                /// @dev Supplying a large amount can revert for overflow.
                                /// @param marketParams The market to supply collateral to.
                                /// @param assets The amount of collateral to supply.
                                /// @param onBehalf The address that will own the increased collateral position.
                                /// @param data Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed.
                                function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory data)
                                    external;
                                /// @notice Withdraws `assets` of collateral on behalf of `onBehalf` and sends the assets to `receiver`.
                                /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
                                /// @dev Withdrawing an amount corresponding to more collateral than supplied will revert for underflow.
                                /// @param marketParams The market to withdraw collateral from.
                                /// @param assets The amount of collateral to withdraw.
                                /// @param onBehalf The address of the owner of the collateral position.
                                /// @param receiver The address that will receive the collateral assets.
                                function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
                                    external;
                                /// @notice Liquidates the given `repaidShares` of debt asset or seize the given `seizedAssets` of collateral on the
                                /// given market `marketParams` of the given `borrower`'s position, optionally calling back the caller's
                                /// `onMorphoLiquidate` function with the given `data`.
                                /// @dev Either `seizedAssets` or `repaidShares` should be zero.
                                /// @dev Seizing more than the collateral balance will underflow and revert without any error message.
                                /// @dev Repaying more than the borrow balance will underflow and revert without any error message.
                                /// @dev An attacker can front-run a liquidation with a small repay making the transaction revert for underflow.
                                /// @param marketParams The market of the position.
                                /// @param borrower The owner of the position.
                                /// @param seizedAssets The amount of collateral to seize.
                                /// @param repaidShares The amount of shares to repay.
                                /// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed.
                                /// @return The amount of assets seized.
                                /// @return The amount of assets repaid.
                                function liquidate(
                                    MarketParams memory marketParams,
                                    address borrower,
                                    uint256 seizedAssets,
                                    uint256 repaidShares,
                                    bytes memory data
                                ) external returns (uint256, uint256);
                                /// @notice Executes a flash loan.
                                /// @dev Flash loans have access to the whole balance of the contract (the liquidity and deposited collateral of all
                                /// markets combined, plus donations).
                                /// @dev Warning: Not ERC-3156 compliant but compatibility is easily reached:
                                /// - `flashFee` is zero.
                                /// - `maxFlashLoan` is the token's balance of this contract.
                                /// - The receiver of `assets` is the caller.
                                /// @param token The token to flash loan.
                                /// @param assets The amount of assets to flash loan.
                                /// @param data Arbitrary data to pass to the `onMorphoFlashLoan` callback.
                                function flashLoan(address token, uint256 assets, bytes calldata data) external;
                                /// @notice Sets the authorization for `authorized` to manage `msg.sender`'s positions.
                                /// @param authorized The authorized address.
                                /// @param newIsAuthorized The new authorization status.
                                function setAuthorization(address authorized, bool newIsAuthorized) external;
                                /// @notice Sets the authorization for `authorization.authorized` to manage `authorization.authorizer`'s positions.
                                /// @dev Warning: Reverts if the signature has already been submitted.
                                /// @dev The signature is malleable, but it has no impact on the security here.
                                /// @dev The nonce is passed as argument to be able to revert with a different error message.
                                /// @param authorization The `Authorization` struct.
                                /// @param signature The signature.
                                function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external;
                                /// @notice Accrues interest for the given market `marketParams`.
                                function accrueInterest(MarketParams memory marketParams) external;
                                /// @notice Returns the data stored on the different `slots`.
                                function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory);
                            }
                            /// @dev This interface is inherited by Morpho so that function signatures are checked by the compiler.
                            /// @dev Consider using the IMorpho interface instead of this one.
                            interface IMorphoStaticTyping is IMorphoBase {
                                /// @notice The state of the position of `user` on the market corresponding to `id`.
                                /// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest
                                /// accrual.
                                function position(Id id, address user)
                                    external
                                    view
                                    returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral);
                                /// @notice The state of the market corresponding to `id`.
                                /// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
                                /// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
                                /// @dev Warning: `totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last interest
                                /// accrual.
                                function market(Id id)
                                    external
                                    view
                                    returns (
                                        uint128 totalSupplyAssets,
                                        uint128 totalSupplyShares,
                                        uint128 totalBorrowAssets,
                                        uint128 totalBorrowShares,
                                        uint128 lastUpdate,
                                        uint128 fee
                                    );
                                /// @notice The market params corresponding to `id`.
                                /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer
                                /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`.
                                function idToMarketParams(Id id)
                                    external
                                    view
                                    returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv);
                            }
                            /// @title IMorpho
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @dev Use this interface for Morpho to have access to all the functions with the appropriate function signatures.
                            interface IMorpho is IMorphoBase {
                                /// @notice The state of the position of `user` on the market corresponding to `id`.
                                /// @dev Warning: For `feeRecipient`, `p.supplyShares` does not contain the accrued shares since the last interest
                                /// accrual.
                                function position(Id id, address user) external view returns (Position memory p);
                                /// @notice The state of the market corresponding to `id`.
                                /// @dev Warning: `m.totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
                                /// @dev Warning: `m.totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
                                /// @dev Warning: `m.totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last
                                /// interest accrual.
                                function market(Id id) external view returns (Market memory m);
                                /// @notice The market params corresponding to `id`.
                                /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer
                                /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`.
                                function idToMarketParams(Id id) external view returns (MarketParams memory);
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity >=0.5.0;
                            /// @title IMorphoLiquidateCallback
                            /// @notice Interface that liquidators willing to use `liquidate`'s callback must implement.
                            interface IMorphoLiquidateCallback {
                                /// @notice Callback called when a liquidation occurs.
                                /// @dev The callback is called only if data is not empty.
                                /// @param repaidAssets The amount of repaid assets.
                                /// @param data Arbitrary data passed to the `liquidate` function.
                                function onMorphoLiquidate(uint256 repaidAssets, bytes calldata data) external;
                            }
                            /// @title IMorphoRepayCallback
                            /// @notice Interface that users willing to use `repay`'s callback must implement.
                            interface IMorphoRepayCallback {
                                /// @notice Callback called when a repayment occurs.
                                /// @dev The callback is called only if data is not empty.
                                /// @param assets The amount of repaid assets.
                                /// @param data Arbitrary data passed to the `repay` function.
                                function onMorphoRepay(uint256 assets, bytes calldata data) external;
                            }
                            /// @title IMorphoSupplyCallback
                            /// @notice Interface that users willing to use `supply`'s callback must implement.
                            interface IMorphoSupplyCallback {
                                /// @notice Callback called when a supply occurs.
                                /// @dev The callback is called only if data is not empty.
                                /// @param assets The amount of supplied assets.
                                /// @param data Arbitrary data passed to the `supply` function.
                                function onMorphoSupply(uint256 assets, bytes calldata data) external;
                            }
                            /// @title IMorphoSupplyCollateralCallback
                            /// @notice Interface that users willing to use `supplyCollateral`'s callback must implement.
                            interface IMorphoSupplyCollateralCallback {
                                /// @notice Callback called when a supply of collateral occurs.
                                /// @dev The callback is called only if data is not empty.
                                /// @param assets The amount of supplied collateral.
                                /// @param data Arbitrary data passed to the `supplyCollateral` function.
                                function onMorphoSupplyCollateral(uint256 assets, bytes calldata data) external;
                            }
                            /// @title IMorphoFlashLoanCallback
                            /// @notice Interface that users willing to use `flashLoan`'s callback must implement.
                            interface IMorphoFlashLoanCallback {
                                /// @notice Callback called when a flash loan occurs.
                                /// @dev The callback is called only if data is not empty.
                                /// @param assets The amount of assets that was flash loaned.
                                /// @param data Arbitrary data passed to the `flashLoan` function.
                                function onMorphoFlashLoan(uint256 assets, bytes calldata data) external;
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity >=0.5.0;
                            import {MarketParams, Market} from "./IMorpho.sol";
                            /// @title IIrm
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @notice Interface that Interest Rate Models (IRMs) used by Morpho must implement.
                            interface IIrm {
                                /// @notice Returns the borrow rate per second (scaled by WAD) of the market `marketParams`.
                                /// @dev Assumes that `market` corresponds to `marketParams`.
                                function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256);
                                /// @notice Returns the borrow rate per second (scaled by WAD) of the market `marketParams` without modifying any
                                /// storage.
                                /// @dev Assumes that `market` corresponds to `marketParams`.
                                function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256);
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity >=0.5.0;
                            /// @title IERC20
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @dev Empty because we only call library functions. It prevents calling transfer (transferFrom) instead of
                            /// safeTransfer (safeTransferFrom).
                            interface IERC20 {}
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity >=0.5.0;
                            /// @title IOracle
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @notice Interface that oracles used by Morpho must implement.
                            /// @dev It is the user's responsibility to select markets with safe oracles.
                            interface IOracle {
                                /// @notice Returns the price of 1 asset of collateral token quoted in 1 asset of loan token, scaled by 1e36.
                                /// @dev It corresponds to the price of 10**(collateral token decimals) assets of collateral token quoted in
                                /// 10**(loan token decimals) assets of loan token with `36 + loan token decimals - collateral token decimals`
                                /// decimals of precision.
                                function price() external view returns (uint256);
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity ^0.8.0;
                            /// @dev The maximum fee a market can have (25%).
                            uint256 constant MAX_FEE = 0.25e18;
                            /// @dev Oracle price scale.
                            uint256 constant ORACLE_PRICE_SCALE = 1e36;
                            /// @dev Liquidation cursor.
                            uint256 constant LIQUIDATION_CURSOR = 0.3e18;
                            /// @dev Max liquidation incentive factor.
                            uint256 constant MAX_LIQUIDATION_INCENTIVE_FACTOR = 1.15e18;
                            /// @dev The EIP-712 typeHash for EIP712Domain.
                            bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
                            /// @dev The EIP-712 typeHash for Authorization.
                            bytes32 constant AUTHORIZATION_TYPEHASH =
                                keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)");
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity ^0.8.0;
                            import {ErrorsLib} from "../libraries/ErrorsLib.sol";
                            /// @title UtilsLib
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @notice Library exposing helpers.
                            /// @dev Inspired by https://github.com/morpho-org/morpho-utils.
                            library UtilsLib {
                                /// @dev Returns true if there is exactly one zero among `x` and `y`.
                                function exactlyOneZero(uint256 x, uint256 y) internal pure returns (bool z) {
                                    assembly {
                                        z := xor(iszero(x), iszero(y))
                                    }
                                }
                                /// @dev Returns the min of `x` and `y`.
                                function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
                                    assembly {
                                        z := xor(x, mul(xor(x, y), lt(y, x)))
                                    }
                                }
                                /// @dev Returns `x` safely cast to uint128.
                                function toUint128(uint256 x) internal pure returns (uint128) {
                                    require(x <= type(uint128).max, ErrorsLib.MAX_UINT128_EXCEEDED);
                                    return uint128(x);
                                }
                                /// @dev Returns max(0, x - y).
                                function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
                                    assembly {
                                        z := mul(gt(x, y), sub(x, y))
                                    }
                                }
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity ^0.8.0;
                            import {Id, MarketParams} from "../interfaces/IMorpho.sol";
                            /// @title EventsLib
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @notice Library exposing events.
                            library EventsLib {
                                /// @notice Emitted when setting a new owner.
                                /// @param newOwner The new owner of the contract.
                                event SetOwner(address indexed newOwner);
                                /// @notice Emitted when setting a new fee.
                                /// @param id The market id.
                                /// @param newFee The new fee.
                                event SetFee(Id indexed id, uint256 newFee);
                                /// @notice Emitted when setting a new fee recipient.
                                /// @param newFeeRecipient The new fee recipient.
                                event SetFeeRecipient(address indexed newFeeRecipient);
                                /// @notice Emitted when enabling an IRM.
                                /// @param irm The IRM that was enabled.
                                event EnableIrm(address indexed irm);
                                /// @notice Emitted when enabling an LLTV.
                                /// @param lltv The LLTV that was enabled.
                                event EnableLltv(uint256 lltv);
                                /// @notice Emitted when creating a market.
                                /// @param id The market id.
                                /// @param marketParams The market that was created.
                                event CreateMarket(Id indexed id, MarketParams marketParams);
                                /// @notice Emitted on supply of assets.
                                /// @dev Warning: `feeRecipient` receives some shares during interest accrual without any supply event emitted.
                                /// @param id The market id.
                                /// @param caller The caller.
                                /// @param onBehalf The owner of the modified position.
                                /// @param assets The amount of assets supplied.
                                /// @param shares The amount of shares minted.
                                event Supply(Id indexed id, address indexed caller, address indexed onBehalf, uint256 assets, uint256 shares);
                                /// @notice Emitted on withdrawal of assets.
                                /// @param id The market id.
                                /// @param caller The caller.
                                /// @param onBehalf The owner of the modified position.
                                /// @param receiver The address that received the withdrawn assets.
                                /// @param assets The amount of assets withdrawn.
                                /// @param shares The amount of shares burned.
                                event Withdraw(
                                    Id indexed id,
                                    address caller,
                                    address indexed onBehalf,
                                    address indexed receiver,
                                    uint256 assets,
                                    uint256 shares
                                );
                                /// @notice Emitted on borrow of assets.
                                /// @param id The market id.
                                /// @param caller The caller.
                                /// @param onBehalf The owner of the modified position.
                                /// @param receiver The address that received the borrowed assets.
                                /// @param assets The amount of assets borrowed.
                                /// @param shares The amount of shares minted.
                                event Borrow(
                                    Id indexed id,
                                    address caller,
                                    address indexed onBehalf,
                                    address indexed receiver,
                                    uint256 assets,
                                    uint256 shares
                                );
                                /// @notice Emitted on repayment of assets.
                                /// @param id The market id.
                                /// @param caller The caller.
                                /// @param onBehalf The owner of the modified position.
                                /// @param assets The amount of assets repaid. May be 1 over the corresponding market's `totalBorrowAssets`.
                                /// @param shares The amount of shares burned.
                                event Repay(Id indexed id, address indexed caller, address indexed onBehalf, uint256 assets, uint256 shares);
                                /// @notice Emitted on supply of collateral.
                                /// @param id The market id.
                                /// @param caller The caller.
                                /// @param onBehalf The owner of the modified position.
                                /// @param assets The amount of collateral supplied.
                                event SupplyCollateral(Id indexed id, address indexed caller, address indexed onBehalf, uint256 assets);
                                /// @notice Emitted on withdrawal of collateral.
                                /// @param id The market id.
                                /// @param caller The caller.
                                /// @param onBehalf The owner of the modified position.
                                /// @param receiver The address that received the withdrawn collateral.
                                /// @param assets The amount of collateral withdrawn.
                                event WithdrawCollateral(
                                    Id indexed id, address caller, address indexed onBehalf, address indexed receiver, uint256 assets
                                );
                                /// @notice Emitted on liquidation of a position.
                                /// @param id The market id.
                                /// @param caller The caller.
                                /// @param borrower The borrower of the position.
                                /// @param repaidAssets The amount of assets repaid. May be 1 over the corresponding market's `totalBorrowAssets`.
                                /// @param repaidShares The amount of shares burned.
                                /// @param seizedAssets The amount of collateral seized.
                                /// @param badDebtAssets The amount of assets of bad debt realized.
                                /// @param badDebtShares The amount of borrow shares of bad debt realized.
                                event Liquidate(
                                    Id indexed id,
                                    address indexed caller,
                                    address indexed borrower,
                                    uint256 repaidAssets,
                                    uint256 repaidShares,
                                    uint256 seizedAssets,
                                    uint256 badDebtAssets,
                                    uint256 badDebtShares
                                );
                                /// @notice Emitted on flash loan.
                                /// @param caller The caller.
                                /// @param token The token that was flash loaned.
                                /// @param assets The amount that was flash loaned.
                                event FlashLoan(address indexed caller, address indexed token, uint256 assets);
                                /// @notice Emitted when setting an authorization.
                                /// @param caller The caller.
                                /// @param authorizer The authorizer address.
                                /// @param authorized The authorized address.
                                /// @param newIsAuthorized The new authorization status.
                                event SetAuthorization(
                                    address indexed caller, address indexed authorizer, address indexed authorized, bool newIsAuthorized
                                );
                                /// @notice Emitted when setting an authorization with a signature.
                                /// @param caller The caller.
                                /// @param authorizer The authorizer address.
                                /// @param usedNonce The nonce that was used.
                                event IncrementNonce(address indexed caller, address indexed authorizer, uint256 usedNonce);
                                /// @notice Emitted when accruing interest.
                                /// @param id The market id.
                                /// @param prevBorrowRate The previous borrow rate.
                                /// @param interest The amount of interest accrued.
                                /// @param feeShares The amount of shares minted as fee.
                                event AccrueInterest(Id indexed id, uint256 prevBorrowRate, uint256 interest, uint256 feeShares);
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity ^0.8.0;
                            /// @title ErrorsLib
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @notice Library exposing error messages.
                            library ErrorsLib {
                                /// @notice Thrown when the caller is not the owner.
                                string internal constant NOT_OWNER = "not owner";
                                /// @notice Thrown when the LLTV to enable exceeds the maximum LLTV.
                                string internal constant MAX_LLTV_EXCEEDED = "max LLTV exceeded";
                                /// @notice Thrown when the fee to set exceeds the maximum fee.
                                string internal constant MAX_FEE_EXCEEDED = "max fee exceeded";
                                /// @notice Thrown when the value is already set.
                                string internal constant ALREADY_SET = "already set";
                                /// @notice Thrown when the IRM is not enabled at market creation.
                                string internal constant IRM_NOT_ENABLED = "IRM not enabled";
                                /// @notice Thrown when the LLTV is not enabled at market creation.
                                string internal constant LLTV_NOT_ENABLED = "LLTV not enabled";
                                /// @notice Thrown when the market is already created.
                                string internal constant MARKET_ALREADY_CREATED = "market already created";
                                /// @notice Thrown when a token to transfer doesn't have code.
                                string internal constant NO_CODE = "no code";
                                /// @notice Thrown when the market is not created.
                                string internal constant MARKET_NOT_CREATED = "market not created";
                                /// @notice Thrown when not exactly one of the input amount is zero.
                                string internal constant INCONSISTENT_INPUT = "inconsistent input";
                                /// @notice Thrown when zero assets is passed as input.
                                string internal constant ZERO_ASSETS = "zero assets";
                                /// @notice Thrown when a zero address is passed as input.
                                string internal constant ZERO_ADDRESS = "zero address";
                                /// @notice Thrown when the caller is not authorized to conduct an action.
                                string internal constant UNAUTHORIZED = "unauthorized";
                                /// @notice Thrown when the collateral is insufficient to `borrow` or `withdrawCollateral`.
                                string internal constant INSUFFICIENT_COLLATERAL = "insufficient collateral";
                                /// @notice Thrown when the liquidity is insufficient to `withdraw` or `borrow`.
                                string internal constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity";
                                /// @notice Thrown when the position to liquidate is healthy.
                                string internal constant HEALTHY_POSITION = "position is healthy";
                                /// @notice Thrown when the authorization signature is invalid.
                                string internal constant INVALID_SIGNATURE = "invalid signature";
                                /// @notice Thrown when the authorization signature is expired.
                                string internal constant SIGNATURE_EXPIRED = "signature expired";
                                /// @notice Thrown when the nonce is invalid.
                                string internal constant INVALID_NONCE = "invalid nonce";
                                /// @notice Thrown when a token transfer reverted.
                                string internal constant TRANSFER_REVERTED = "transfer reverted";
                                /// @notice Thrown when a token transfer returned false.
                                string internal constant TRANSFER_RETURNED_FALSE = "transfer returned false";
                                /// @notice Thrown when a token transferFrom reverted.
                                string internal constant TRANSFER_FROM_REVERTED = "transferFrom reverted";
                                /// @notice Thrown when a token transferFrom returned false
                                string internal constant TRANSFER_FROM_RETURNED_FALSE = "transferFrom returned false";
                                /// @notice Thrown when the maximum uint128 is exceeded.
                                string internal constant MAX_UINT128_EXCEEDED = "max uint128 exceeded";
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity ^0.8.0;
                            uint256 constant WAD = 1e18;
                            /// @title MathLib
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @notice Library to manage fixed-point arithmetic.
                            library MathLib {
                                /// @dev Returns (`x` * `y`) / `WAD` rounded down.
                                function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) {
                                    return mulDivDown(x, y, WAD);
                                }
                                /// @dev Returns (`x` * `WAD`) / `y` rounded down.
                                function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) {
                                    return mulDivDown(x, WAD, y);
                                }
                                /// @dev Returns (`x` * `WAD`) / `y` rounded up.
                                function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) {
                                    return mulDivUp(x, WAD, y);
                                }
                                /// @dev Returns (`x` * `y`) / `d` rounded down.
                                function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
                                    return (x * y) / d;
                                }
                                /// @dev Returns (`x` * `y`) / `d` rounded up.
                                function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
                                    return (x * y + (d - 1)) / d;
                                }
                                /// @dev Returns the sum of the first three non-zero terms of a Taylor expansion of e^(nx) - 1, to approximate a
                                /// continuous compound interest rate.
                                function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
                                    uint256 firstTerm = x * n;
                                    uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
                                    uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
                                    return firstTerm + secondTerm + thirdTerm;
                                }
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity ^0.8.0;
                            import {MathLib} from "./MathLib.sol";
                            /// @title SharesMathLib
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @notice Shares management library.
                            /// @dev This implementation mitigates share price manipulations, using OpenZeppelin's method of virtual shares:
                            /// https://docs.openzeppelin.com/contracts/4.x/erc4626#inflation-attack.
                            library SharesMathLib {
                                using MathLib for uint256;
                                /// @dev The number of virtual shares has been chosen low enough to prevent overflows, and high enough to ensure
                                /// high precision computations.
                                /// @dev Virtual shares can never be redeemed for the assets they are entitled to, but it is assumed the share price
                                /// stays low enough not to inflate these assets to a significant value.
                                /// @dev Warning: The assets to which virtual borrow shares are entitled behave like unrealizable bad debt.
                                uint256 internal constant VIRTUAL_SHARES = 1e6;
                                /// @dev A number of virtual assets of 1 enforces a conversion rate between shares and assets when a market is
                                /// empty.
                                uint256 internal constant VIRTUAL_ASSETS = 1;
                                /// @dev Calculates the value of `assets` quoted in shares, rounding down.
                                function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
                                    return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
                                }
                                /// @dev Calculates the value of `shares` quoted in assets, rounding down.
                                function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
                                    return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
                                }
                                /// @dev Calculates the value of `assets` quoted in shares, rounding up.
                                function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
                                    return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
                                }
                                /// @dev Calculates the value of `shares` quoted in assets, rounding up.
                                function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
                                    return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
                                }
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity ^0.8.0;
                            import {Id, MarketParams} from "../interfaces/IMorpho.sol";
                            /// @title MarketParamsLib
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @notice Library to convert a market to its id.
                            library MarketParamsLib {
                                /// @notice The length of the data used to compute the id of a market.
                                /// @dev The length is 5 * 32 because `MarketParams` has 5 variables of 32 bytes each.
                                uint256 internal constant MARKET_PARAMS_BYTES_LENGTH = 5 * 32;
                                /// @notice Returns the id of the market `marketParams`.
                                function id(MarketParams memory marketParams) internal pure returns (Id marketParamsId) {
                                    assembly ("memory-safe") {
                                        marketParamsId := keccak256(marketParams, MARKET_PARAMS_BYTES_LENGTH)
                                    }
                                }
                            }
                            // SPDX-License-Identifier: GPL-2.0-or-later
                            pragma solidity ^0.8.0;
                            import {IERC20} from "../interfaces/IERC20.sol";
                            import {ErrorsLib} from "../libraries/ErrorsLib.sol";
                            interface IERC20Internal {
                                function transfer(address to, uint256 value) external returns (bool);
                                function transferFrom(address from, address to, uint256 value) external returns (bool);
                            }
                            /// @title SafeTransferLib
                            /// @author Morpho Labs
                            /// @custom:contact [email protected]
                            /// @notice Library to manage transfers of tokens, even if calls to the transfer or transferFrom functions are not
                            /// returning a boolean.
                            library SafeTransferLib {
                                function safeTransfer(IERC20 token, address to, uint256 value) internal {
                                    require(address(token).code.length > 0, ErrorsLib.NO_CODE);
                                    (bool success, bytes memory returndata) =
                                        address(token).call(abi.encodeCall(IERC20Internal.transfer, (to, value)));
                                    require(success, ErrorsLib.TRANSFER_REVERTED);
                                    require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TRANSFER_RETURNED_FALSE);
                                }
                                function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                                    require(address(token).code.length > 0, ErrorsLib.NO_CODE);
                                    (bool success, bytes memory returndata) =
                                        address(token).call(abi.encodeCall(IERC20Internal.transferFrom, (from, to, value)));
                                    require(success, ErrorsLib.TRANSFER_FROM_REVERTED);
                                    require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TRANSFER_FROM_RETURNED_FALSE);
                                }
                            }
                            

                            File 2 of 17: TetherToken
                            pragma solidity ^0.4.17;
                            
                            /**
                             * @title SafeMath
                             * @dev Math operations with safety checks that throw on error
                             */
                            library SafeMath {
                                function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                                    if (a == 0) {
                                        return 0;
                                    }
                                    uint256 c = a * b;
                                    assert(c / a == b);
                                    return c;
                                }
                            
                                function div(uint256 a, uint256 b) internal pure returns (uint256) {
                                    // assert(b > 0); // Solidity automatically throws when dividing by 0
                                    uint256 c = a / b;
                                    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                                    return c;
                                }
                            
                                function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                                    assert(b <= a);
                                    return a - b;
                                }
                            
                                function add(uint256 a, uint256 b) internal pure returns (uint256) {
                                    uint256 c = a + b;
                                    assert(c >= a);
                                    return c;
                                }
                            }
                            
                            /**
                             * @title Ownable
                             * @dev The Ownable contract has an owner address, and provides basic authorization control
                             * functions, this simplifies the implementation of "user permissions".
                             */
                            contract Ownable {
                                address public owner;
                            
                                /**
                                  * @dev The Ownable constructor sets the original `owner` of the contract to the sender
                                  * account.
                                  */
                                function Ownable() public {
                                    owner = msg.sender;
                                }
                            
                                /**
                                  * @dev Throws if called by any account other than the owner.
                                  */
                                modifier onlyOwner() {
                                    require(msg.sender == owner);
                                    _;
                                }
                            
                                /**
                                * @dev Allows the current owner to transfer control of the contract to a newOwner.
                                * @param newOwner The address to transfer ownership to.
                                */
                                function transferOwnership(address newOwner) public onlyOwner {
                                    if (newOwner != address(0)) {
                                        owner = newOwner;
                                    }
                                }
                            
                            }
                            
                            /**
                             * @title ERC20Basic
                             * @dev Simpler version of ERC20 interface
                             * @dev see https://github.com/ethereum/EIPs/issues/20
                             */
                            contract ERC20Basic {
                                uint public _totalSupply;
                                function totalSupply() public constant returns (uint);
                                function balanceOf(address who) public constant returns (uint);
                                function transfer(address to, uint value) public;
                                event Transfer(address indexed from, address indexed to, uint value);
                            }
                            
                            /**
                             * @title ERC20 interface
                             * @dev see https://github.com/ethereum/EIPs/issues/20
                             */
                            contract ERC20 is ERC20Basic {
                                function allowance(address owner, address spender) public constant returns (uint);
                                function transferFrom(address from, address to, uint value) public;
                                function approve(address spender, uint value) public;
                                event Approval(address indexed owner, address indexed spender, uint value);
                            }
                            
                            /**
                             * @title Basic token
                             * @dev Basic version of StandardToken, with no allowances.
                             */
                            contract BasicToken is Ownable, ERC20Basic {
                                using SafeMath for uint;
                            
                                mapping(address => uint) public balances;
                            
                                // additional variables for use if transaction fees ever became necessary
                                uint public basisPointsRate = 0;
                                uint public maximumFee = 0;
                            
                                /**
                                * @dev Fix for the ERC20 short address attack.
                                */
                                modifier onlyPayloadSize(uint size) {
                                    require(!(msg.data.length < size + 4));
                                    _;
                                }
                            
                                /**
                                * @dev transfer token for a specified address
                                * @param _to The address to transfer to.
                                * @param _value The amount to be transferred.
                                */
                                function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) {
                                    uint fee = (_value.mul(basisPointsRate)).div(10000);
                                    if (fee > maximumFee) {
                                        fee = maximumFee;
                                    }
                                    uint sendAmount = _value.sub(fee);
                                    balances[msg.sender] = balances[msg.sender].sub(_value);
                                    balances[_to] = balances[_to].add(sendAmount);
                                    if (fee > 0) {
                                        balances[owner] = balances[owner].add(fee);
                                        Transfer(msg.sender, owner, fee);
                                    }
                                    Transfer(msg.sender, _to, sendAmount);
                                }
                            
                                /**
                                * @dev Gets the balance of the specified address.
                                * @param _owner The address to query the the balance of.
                                * @return An uint representing the amount owned by the passed address.
                                */
                                function balanceOf(address _owner) public constant returns (uint balance) {
                                    return balances[_owner];
                                }
                            
                            }
                            
                            /**
                             * @title Standard ERC20 token
                             *
                             * @dev Implementation of the basic standard token.
                             * @dev https://github.com/ethereum/EIPs/issues/20
                             * @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
                             */
                            contract StandardToken is BasicToken, ERC20 {
                            
                                mapping (address => mapping (address => uint)) public allowed;
                            
                                uint public constant MAX_UINT = 2**256 - 1;
                            
                                /**
                                * @dev Transfer tokens from one address to another
                                * @param _from address The address which you want to send tokens from
                                * @param _to address The address which you want to transfer to
                                * @param _value uint the amount of tokens to be transferred
                                */
                                function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
                                    var _allowance = allowed[_from][msg.sender];
                            
                                    // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
                                    // if (_value > _allowance) throw;
                            
                                    uint fee = (_value.mul(basisPointsRate)).div(10000);
                                    if (fee > maximumFee) {
                                        fee = maximumFee;
                                    }
                                    if (_allowance < MAX_UINT) {
                                        allowed[_from][msg.sender] = _allowance.sub(_value);
                                    }
                                    uint sendAmount = _value.sub(fee);
                                    balances[_from] = balances[_from].sub(_value);
                                    balances[_to] = balances[_to].add(sendAmount);
                                    if (fee > 0) {
                                        balances[owner] = balances[owner].add(fee);
                                        Transfer(_from, owner, fee);
                                    }
                                    Transfer(_from, _to, sendAmount);
                                }
                            
                                /**
                                * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
                                * @param _spender The address which will spend the funds.
                                * @param _value The amount of tokens to be spent.
                                */
                                function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
                            
                                    // To change the approve amount you first have to reduce the addresses`
                                    //  allowance to zero by calling `approve(_spender, 0)` if it is not
                                    //  already 0 to mitigate the race condition described here:
                                    //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                    require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
                            
                                    allowed[msg.sender][_spender] = _value;
                                    Approval(msg.sender, _spender, _value);
                                }
                            
                                /**
                                * @dev Function to check the amount of tokens than an owner allowed to a spender.
                                * @param _owner address The address which owns the funds.
                                * @param _spender address The address which will spend the funds.
                                * @return A uint specifying the amount of tokens still available for the spender.
                                */
                                function allowance(address _owner, address _spender) public constant returns (uint remaining) {
                                    return allowed[_owner][_spender];
                                }
                            
                            }
                            
                            
                            /**
                             * @title Pausable
                             * @dev Base contract which allows children to implement an emergency stop mechanism.
                             */
                            contract Pausable is Ownable {
                              event Pause();
                              event Unpause();
                            
                              bool public paused = false;
                            
                            
                              /**
                               * @dev Modifier to make a function callable only when the contract is not paused.
                               */
                              modifier whenNotPaused() {
                                require(!paused);
                                _;
                              }
                            
                              /**
                               * @dev Modifier to make a function callable only when the contract is paused.
                               */
                              modifier whenPaused() {
                                require(paused);
                                _;
                              }
                            
                              /**
                               * @dev called by the owner to pause, triggers stopped state
                               */
                              function pause() onlyOwner whenNotPaused public {
                                paused = true;
                                Pause();
                              }
                            
                              /**
                               * @dev called by the owner to unpause, returns to normal state
                               */
                              function unpause() onlyOwner whenPaused public {
                                paused = false;
                                Unpause();
                              }
                            }
                            
                            contract BlackList is Ownable, BasicToken {
                            
                                /////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) ///////
                                function getBlackListStatus(address _maker) external constant returns (bool) {
                                    return isBlackListed[_maker];
                                }
                            
                                function getOwner() external constant returns (address) {
                                    return owner;
                                }
                            
                                mapping (address => bool) public isBlackListed;
                                
                                function addBlackList (address _evilUser) public onlyOwner {
                                    isBlackListed[_evilUser] = true;
                                    AddedBlackList(_evilUser);
                                }
                            
                                function removeBlackList (address _clearedUser) public onlyOwner {
                                    isBlackListed[_clearedUser] = false;
                                    RemovedBlackList(_clearedUser);
                                }
                            
                                function destroyBlackFunds (address _blackListedUser) public onlyOwner {
                                    require(isBlackListed[_blackListedUser]);
                                    uint dirtyFunds = balanceOf(_blackListedUser);
                                    balances[_blackListedUser] = 0;
                                    _totalSupply -= dirtyFunds;
                                    DestroyedBlackFunds(_blackListedUser, dirtyFunds);
                                }
                            
                                event DestroyedBlackFunds(address _blackListedUser, uint _balance);
                            
                                event AddedBlackList(address _user);
                            
                                event RemovedBlackList(address _user);
                            
                            }
                            
                            contract UpgradedStandardToken is StandardToken{
                                // those methods are called by the legacy contract
                                // and they must ensure msg.sender to be the contract address
                                function transferByLegacy(address from, address to, uint value) public;
                                function transferFromByLegacy(address sender, address from, address spender, uint value) public;
                                function approveByLegacy(address from, address spender, uint value) public;
                            }
                            
                            contract TetherToken is Pausable, StandardToken, BlackList {
                            
                                string public name;
                                string public symbol;
                                uint public decimals;
                                address public upgradedAddress;
                                bool public deprecated;
                            
                                //  The contract can be initialized with a number of tokens
                                //  All the tokens are deposited to the owner address
                                //
                                // @param _balance Initial supply of the contract
                                // @param _name Token Name
                                // @param _symbol Token symbol
                                // @param _decimals Token decimals
                                function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public {
                                    _totalSupply = _initialSupply;
                                    name = _name;
                                    symbol = _symbol;
                                    decimals = _decimals;
                                    balances[owner] = _initialSupply;
                                    deprecated = false;
                                }
                            
                                // Forward ERC20 methods to upgraded contract if this one is deprecated
                                function transfer(address _to, uint _value) public whenNotPaused {
                                    require(!isBlackListed[msg.sender]);
                                    if (deprecated) {
                                        return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value);
                                    } else {
                                        return super.transfer(_to, _value);
                                    }
                                }
                            
                                // Forward ERC20 methods to upgraded contract if this one is deprecated
                                function transferFrom(address _from, address _to, uint _value) public whenNotPaused {
                                    require(!isBlackListed[_from]);
                                    if (deprecated) {
                                        return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value);
                                    } else {
                                        return super.transferFrom(_from, _to, _value);
                                    }
                                }
                            
                                // Forward ERC20 methods to upgraded contract if this one is deprecated
                                function balanceOf(address who) public constant returns (uint) {
                                    if (deprecated) {
                                        return UpgradedStandardToken(upgradedAddress).balanceOf(who);
                                    } else {
                                        return super.balanceOf(who);
                                    }
                                }
                            
                                // Forward ERC20 methods to upgraded contract if this one is deprecated
                                function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
                                    if (deprecated) {
                                        return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value);
                                    } else {
                                        return super.approve(_spender, _value);
                                    }
                                }
                            
                                // Forward ERC20 methods to upgraded contract if this one is deprecated
                                function allowance(address _owner, address _spender) public constant returns (uint remaining) {
                                    if (deprecated) {
                                        return StandardToken(upgradedAddress).allowance(_owner, _spender);
                                    } else {
                                        return super.allowance(_owner, _spender);
                                    }
                                }
                            
                                // deprecate current contract in favour of a new one
                                function deprecate(address _upgradedAddress) public onlyOwner {
                                    deprecated = true;
                                    upgradedAddress = _upgradedAddress;
                                    Deprecate(_upgradedAddress);
                                }
                            
                                // deprecate current contract if favour of a new one
                                function totalSupply() public constant returns (uint) {
                                    if (deprecated) {
                                        return StandardToken(upgradedAddress).totalSupply();
                                    } else {
                                        return _totalSupply;
                                    }
                                }
                            
                                // Issue a new amount of tokens
                                // these tokens are deposited into the owner address
                                //
                                // @param _amount Number of tokens to be issued
                                function issue(uint amount) public onlyOwner {
                                    require(_totalSupply + amount > _totalSupply);
                                    require(balances[owner] + amount > balances[owner]);
                            
                                    balances[owner] += amount;
                                    _totalSupply += amount;
                                    Issue(amount);
                                }
                            
                                // Redeem tokens.
                                // These tokens are withdrawn from the owner address
                                // if the balance must be enough to cover the redeem
                                // or the call will fail.
                                // @param _amount Number of tokens to be issued
                                function redeem(uint amount) public onlyOwner {
                                    require(_totalSupply >= amount);
                                    require(balances[owner] >= amount);
                            
                                    _totalSupply -= amount;
                                    balances[owner] -= amount;
                                    Redeem(amount);
                                }
                            
                                function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner {
                                    // Ensure transparency by hardcoding limit beyond which fees can never be added
                                    require(newBasisPoints < 20);
                                    require(newMaxFee < 50);
                            
                                    basisPointsRate = newBasisPoints;
                                    maximumFee = newMaxFee.mul(10**decimals);
                            
                                    Params(basisPointsRate, maximumFee);
                                }
                            
                                // Called when new token are issued
                                event Issue(uint amount);
                            
                                // Called when tokens are redeemed
                                event Redeem(uint amount);
                            
                                // Called when contract is deprecated
                                event Deprecate(address newAddress);
                            
                                // Called if contract ever adds fees
                                event Params(uint feeBasisPoints, uint maxFee);
                            }

                            File 3 of 17: FluidLiquidityProxy
                            //SPDX-License-Identifier: MIT
                            pragma solidity 0.8.21;
                            contract Error {
                                error FluidInfiniteProxyError(uint256 errorId_);
                            }
                            //SPDX-License-Identifier: MIT
                            pragma solidity 0.8.21;
                            library ErrorTypes {
                                /***********************************|
                                |         Infinite proxy            | 
                                |__________________________________*/
                                /// @notice thrown when an implementation does not exist
                                uint256 internal constant InfiniteProxy__ImplementationNotExist = 50001;
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            contract Events {
                                /// @notice emitted when a new admin is set
                                event LogSetAdmin(address indexed oldAdmin, address indexed newAdmin);
                                /// @notice emitted when a new dummy implementation is set
                                event LogSetDummyImplementation(address indexed oldDummyImplementation, address indexed newDummyImplementation);
                                /// @notice emitted when a new implementation is set with certain sigs
                                event LogSetImplementation(address indexed implementation, bytes4[] sigs);
                                /// @notice emitted when an implementation is removed
                                event LogRemoveImplementation(address indexed implementation);
                            }
                            // SPDX-License-Identifier: MIT
                            pragma solidity 0.8.21;
                            import { Events } from "./events.sol";
                            import { ErrorTypes } from "./errorTypes.sol";
                            import { Error } from "./error.sol";
                            import { StorageRead } from "../libraries/storageRead.sol";
                            contract CoreInternals is StorageRead, Events, Error {
                                struct SigsSlot {
                                    bytes4[] value;
                                }
                                /// @dev Storage slot with the admin of the contract.
                                /// This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                                /// validated in the constructor.
                                bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                                /// @dev Storage slot with the address of the current dummy-implementation.
                                /// This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                                /// validated in the constructor.
                                bytes32 internal constant _DUMMY_IMPLEMENTATION_SLOT =
                                    0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                                /// @dev use EIP1967 proxy slot (see _DUMMY_IMPLEMENTATION_SLOT) except for first 4 bytes,
                                // which are set to 0. This is combined with a sig which will be set in those first 4 bytes
                                bytes32 internal constant _SIG_SLOT_BASE = 0x000000003ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                                /// @dev Returns the storage slot which stores the sigs array set for the implementation.
                                function _getSlotImplSigsSlot(address implementation_) internal pure returns (bytes32) {
                                    return keccak256(abi.encode("eip1967.proxy.implementation", implementation_));
                                }
                                /// @dev Returns the storage slot which stores the implementation address for the function sig.
                                function _getSlotSigsImplSlot(bytes4 sig_) internal pure returns (bytes32 result_) {
                                    assembly {
                                        // or operator sets sig_ in first 4 bytes with rest of bytes32 having default value of _SIG_SLOT_BASE
                                        result_ := or(_SIG_SLOT_BASE, sig_)
                                    }
                                }
                                /// @dev Returns an address `data_` located at `slot_`.
                                function _getAddressSlot(bytes32 slot_) internal view returns (address data_) {
                                    assembly {
                                        data_ := sload(slot_)
                                    }
                                }
                                /// @dev Sets an address `data_` located at `slot_`.
                                function _setAddressSlot(bytes32 slot_, address data_) internal {
                                    assembly {
                                        sstore(slot_, data_)
                                    }
                                }
                                /// @dev Returns an `SigsSlot` with member `value` located at `slot`.
                                function _getSigsSlot(bytes32 slot_) internal pure returns (SigsSlot storage _r) {
                                    assembly {
                                        _r.slot := slot_
                                    }
                                }
                                /// @dev Sets new implementation and adds mapping from implementation to sigs and sig to implementation.
                                function _setImplementationSigs(address implementation_, bytes4[] memory sigs_) internal {
                                    require(sigs_.length != 0, "no-sigs");
                                    bytes32 slot_ = _getSlotImplSigsSlot(implementation_);
                                    bytes4[] memory sigsCheck_ = _getSigsSlot(slot_).value;
                                    require(sigsCheck_.length == 0, "implementation-already-exist");
                                    for (uint256 i; i < sigs_.length; i++) {
                                        bytes32 sigSlot_ = _getSlotSigsImplSlot(sigs_[i]);
                                        require(_getAddressSlot(sigSlot_) == address(0), "sig-already-exist");
                                        _setAddressSlot(sigSlot_, implementation_);
                                    }
                                    _getSigsSlot(slot_).value = sigs_;
                                    emit LogSetImplementation(implementation_, sigs_);
                                }
                                /// @dev Removes implementation and the mappings corresponding to it.
                                function _removeImplementationSigs(address implementation_) internal {
                                    bytes32 slot_ = _getSlotImplSigsSlot(implementation_);
                                    bytes4[] memory sigs_ = _getSigsSlot(slot_).value;
                                    require(sigs_.length != 0, "implementation-not-exist");
                                    for (uint256 i; i < sigs_.length; i++) {
                                        bytes32 sigSlot_ = _getSlotSigsImplSlot(sigs_[i]);
                                        _setAddressSlot(sigSlot_, address(0));
                                    }
                                    delete _getSigsSlot(slot_).value;
                                    emit LogRemoveImplementation(implementation_);
                                }
                                /// @dev Returns bytes4[] sigs from implementation address. If implemenatation is not registered then returns empty array.
                                function _getImplementationSigs(address implementation_) internal view returns (bytes4[] memory) {
                                    bytes32 slot_ = _getSlotImplSigsSlot(implementation_);
                                    return _getSigsSlot(slot_).value;
                                }
                                /// @dev Returns implementation address from bytes4 sig. If sig is not registered then returns address(0).
                                function _getSigImplementation(bytes4 sig_) internal view returns (address implementation_) {
                                    bytes32 slot_ = _getSlotSigsImplSlot(sig_);
                                    return _getAddressSlot(slot_);
                                }
                                /// @dev Returns the current admin.
                                function _getAdmin() internal view returns (address) {
                                    return _getAddressSlot(_ADMIN_SLOT);
                                }
                                /// @dev Returns the current dummy-implementation.
                                function _getDummyImplementation() internal view returns (address) {
                                    return _getAddressSlot(_DUMMY_IMPLEMENTATION_SLOT);
                                }
                                /// @dev Stores a new address in the EIP1967 admin slot.
                                function _setAdmin(address newAdmin_) internal {
                                    address oldAdmin_ = _getAdmin();
                                    require(newAdmin_ != address(0), "ERC1967: new admin is the zero address");
                                    _setAddressSlot(_ADMIN_SLOT, newAdmin_);
                                    emit LogSetAdmin(oldAdmin_, newAdmin_);
                                }
                                /// @dev Stores a new address in the EIP1967 implementation slot.
                                function _setDummyImplementation(address newDummyImplementation_) internal {
                                    address oldDummyImplementation_ = _getDummyImplementation();
                                    _setAddressSlot(_DUMMY_IMPLEMENTATION_SLOT, newDummyImplementation_);
                                    emit LogSetDummyImplementation(oldDummyImplementation_, newDummyImplementation_);
                                }
                            }
                            contract AdminInternals is CoreInternals {
                                /// @dev Only admin guard
                                modifier onlyAdmin() {
                                    require(msg.sender == _getAdmin(), "only-admin");
                                    _;
                                }
                                constructor(address admin_, address dummyImplementation_) {
                                    _setAdmin(admin_);
                                    _setDummyImplementation(dummyImplementation_);
                                }
                                /// @dev Sets new admin.
                                function setAdmin(address newAdmin_) external onlyAdmin {
                                    _setAdmin(newAdmin_);
                                }
                                /// @dev Sets new dummy-implementation.
                                function setDummyImplementation(address newDummyImplementation_) external onlyAdmin {
                                    _setDummyImplementation(newDummyImplementation_);
                                }
                                /// @dev Adds new implementation address.
                                function addImplementation(address implementation_, bytes4[] calldata sigs_) external onlyAdmin {
                                    _setImplementationSigs(implementation_, sigs_);
                                }
                                /// @dev Removes an existing implementation address.
                                function removeImplementation(address implementation_) external onlyAdmin {
                                    _removeImplementationSigs(implementation_);
                                }
                            }
                            /// @title Proxy
                            /// @notice This abstract contract provides a fallback function that delegates all calls to another contract using the EVM.
                            /// It implements the Instadapp infinite-proxy: https://github.com/Instadapp/infinite-proxy
                            abstract contract Proxy is AdminInternals {
                                constructor(address admin_, address dummyImplementation_) AdminInternals(admin_, dummyImplementation_) {}
                                /// @dev Returns admin's address.
                                function getAdmin() external view returns (address) {
                                    return _getAdmin();
                                }
                                /// @dev Returns dummy-implementations's address.
                                function getDummyImplementation() external view returns (address) {
                                    return _getDummyImplementation();
                                }
                                /// @dev Returns bytes4[] sigs from implementation address If not registered then returns empty array.
                                function getImplementationSigs(address impl_) external view returns (bytes4[] memory) {
                                    return _getImplementationSigs(impl_);
                                }
                                /// @dev Returns implementation address from bytes4 sig. If sig is not registered then returns address(0).
                                function getSigsImplementation(bytes4 sig_) external view returns (address) {
                                    return _getSigImplementation(sig_);
                                }
                                /// @dev Fallback function that delegates calls to the address returned by Implementations registry.
                                fallback() external payable {
                                    address implementation_;
                                    assembly {
                                        // get slot for sig and directly SLOAD implementation address from storage at that slot
                                        implementation_ := sload(
                                            // same as in `_getSlotSigsImplSlot()` but we must also load msg.sig from calldata.
                                            // msg.sig is first 4 bytes of calldata, so we can use calldataload(0) with a mask
                                            or(
                                                // or operator sets sig_ in first 4 bytes with rest of bytes32 having default value of _SIG_SLOT_BASE
                                                _SIG_SLOT_BASE,
                                                and(calldataload(0), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
                                            )
                                        )
                                    }
                                    if (implementation_ == address(0)) {
                                        revert FluidInfiniteProxyError(ErrorTypes.InfiniteProxy__ImplementationNotExist);
                                    }
                                    // 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
                                    assembly {
                                        // Copy msg.data. We take full control of memory in this inline assembly
                                        // block because it will not return to Solidity code. We overwrite the
                                        // Solidity scratch pad at memory position 0.
                                        calldatacopy(0, 0, calldatasize())
                                        // Call the implementation.
                                        // out and outsize are 0 because we don't know the size yet.
                                        let result := delegatecall(gas(), implementation_, 0, calldatasize(), 0, 0)
                                        // Copy the returned data.
                                        returndatacopy(0, 0, returndatasize())
                                        if eq(result, 0) {
                                            // delegatecall returns 0 on error.
                                            revert(0, returndatasize())
                                        }
                                        return(0, returndatasize())
                                    }
                                }
                                receive() external payable {
                                    // receive method can never have calldata in EVM so no need for any logic here
                                }
                            }
                            // 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
                                    }
                                }
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            import { Proxy } from "../infiniteProxy/proxy.sol";
                            /// @notice Fluid Liquidity infinte proxy.
                            /// Liquidity is the central point of the Instadapp Fluid architecture, it is the core interaction point
                            /// for all allow-listed protocols, such as fTokens, Vault, Flashloan, StETH protocol, DEX protocol etc.
                            contract FluidLiquidityProxy is Proxy {
                                constructor(address admin_, address dummyImplementation_) Proxy(admin_, dummyImplementation_) {}
                            }
                            

                            File 4 of 17: FiatTokenProxy
                            pragma solidity ^0.4.24;
                            
                            // File: zos-lib/contracts/upgradeability/Proxy.sol
                            
                            /**
                             * @title Proxy
                             * @dev Implements delegation of calls to other contracts, with proper
                             * forwarding of return values and bubbling of failures.
                             * It defines a fallback function that delegates all calls to the address
                             * returned by the abstract _implementation() internal function.
                             */
                            contract Proxy {
                              /**
                               * @dev Fallback function.
                               * Implemented entirely in `_fallback`.
                               */
                              function () payable external {
                                _fallback();
                              }
                            
                              /**
                               * @return The Address of the implementation.
                               */
                              function _implementation() internal view returns (address);
                            
                              /**
                               * @dev Delegates execution to an implementation contract.
                               * This is a low level function that doesn't return to its internal call site.
                               * It will return to the external caller whatever the implementation returns.
                               * @param implementation Address to delegate.
                               */
                              function _delegate(address implementation) internal {
                                assembly {
                                  // Copy msg.data. We take full control of memory in this inline assembly
                                  // block because it will not return to Solidity code. We overwrite the
                                  // Solidity scratch pad at memory position 0.
                                  calldatacopy(0, 0, calldatasize)
                            
                                  // Call the implementation.
                                  // out and outsize are 0 because we don't know the size yet.
                                  let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
                            
                                  // Copy the returned data.
                                  returndatacopy(0, 0, returndatasize)
                            
                                  switch result
                                  // delegatecall returns 0 on error.
                                  case 0 { revert(0, returndatasize) }
                                  default { return(0, returndatasize) }
                                }
                              }
                            
                              /**
                               * @dev Function that is run as the first thing in the fallback function.
                               * Can be redefined in derived contracts to add functionality.
                               * Redefinitions must call super._willFallback().
                               */
                              function _willFallback() internal {
                              }
                            
                              /**
                               * @dev fallback implementation.
                               * Extracted to enable manual triggering.
                               */
                              function _fallback() internal {
                                _willFallback();
                                _delegate(_implementation());
                              }
                            }
                            
                            // File: openzeppelin-solidity/contracts/AddressUtils.sol
                            
                            /**
                             * Utility library of inline functions on addresses
                             */
                            library AddressUtils {
                            
                              /**
                               * Returns whether the target address is a contract
                               * @dev This function will return false if invoked during the constructor of a contract,
                               * as the code is not actually created until after the constructor finishes.
                               * @param addr address to check
                               * @return whether the target address is a contract
                               */
                              function isContract(address addr) internal view returns (bool) {
                                uint256 size;
                                // XXX Currently there is no better way to check if there is a contract in an address
                                // than to check the size of the code at that address.
                                // See https://ethereum.stackexchange.com/a/14016/36603
                                // for more details about how this works.
                                // TODO Check this again before the Serenity release, because all addresses will be
                                // contracts then.
                                // solium-disable-next-line security/no-inline-assembly
                                assembly { size := extcodesize(addr) }
                                return size > 0;
                              }
                            
                            }
                            
                            // File: zos-lib/contracts/upgradeability/UpgradeabilityProxy.sol
                            
                            /**
                             * @title UpgradeabilityProxy
                             * @dev This contract implements a proxy that allows to change the
                             * implementation address to which it will delegate.
                             * Such a change is called an implementation upgrade.
                             */
                            contract UpgradeabilityProxy is Proxy {
                              /**
                               * @dev Emitted when the implementation is upgraded.
                               * @param implementation Address of the new implementation.
                               */
                              event Upgraded(address implementation);
                            
                              /**
                               * @dev Storage slot with the address of the current implementation.
                               * This is the keccak-256 hash of "org.zeppelinos.proxy.implementation", and is
                               * validated in the constructor.
                               */
                              bytes32 private constant IMPLEMENTATION_SLOT = 0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3;
                            
                              /**
                               * @dev Contract constructor.
                               * @param _implementation Address of the initial implementation.
                               */
                              constructor(address _implementation) public {
                                assert(IMPLEMENTATION_SLOT == keccak256("org.zeppelinos.proxy.implementation"));
                            
                                _setImplementation(_implementation);
                              }
                            
                              /**
                               * @dev Returns the current implementation.
                               * @return Address of the current implementation
                               */
                              function _implementation() internal view returns (address impl) {
                                bytes32 slot = IMPLEMENTATION_SLOT;
                                assembly {
                                  impl := sload(slot)
                                }
                              }
                            
                              /**
                               * @dev Upgrades the proxy to a new implementation.
                               * @param newImplementation Address of the new implementation.
                               */
                              function _upgradeTo(address newImplementation) internal {
                                _setImplementation(newImplementation);
                                emit Upgraded(newImplementation);
                              }
                            
                              /**
                               * @dev Sets the implementation address of the proxy.
                               * @param newImplementation Address of the new implementation.
                               */
                              function _setImplementation(address newImplementation) private {
                                require(AddressUtils.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
                            
                                bytes32 slot = IMPLEMENTATION_SLOT;
                            
                                assembly {
                                  sstore(slot, newImplementation)
                                }
                              }
                            }
                            
                            // File: zos-lib/contracts/upgradeability/AdminUpgradeabilityProxy.sol
                            
                            /**
                             * @title AdminUpgradeabilityProxy
                             * @dev This contract combines an upgradeability proxy with an authorization
                             * mechanism for administrative tasks.
                             * All external functions in this contract must be guarded by the
                             * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
                             * feature proposal that would enable this to be done automatically.
                             */
                            contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
                              /**
                               * @dev Emitted when the administration has been transferred.
                               * @param previousAdmin Address of the previous admin.
                               * @param newAdmin Address of the new admin.
                               */
                              event AdminChanged(address previousAdmin, address newAdmin);
                            
                              /**
                               * @dev Storage slot with the admin of the contract.
                               * This is the keccak-256 hash of "org.zeppelinos.proxy.admin", and is
                               * validated in the constructor.
                               */
                              bytes32 private constant ADMIN_SLOT = 0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b;
                            
                              /**
                               * @dev Modifier to check whether the `msg.sender` is the admin.
                               * If it is, it will run the function. Otherwise, it will delegate the call
                               * to the implementation.
                               */
                              modifier ifAdmin() {
                                if (msg.sender == _admin()) {
                                  _;
                                } else {
                                  _fallback();
                                }
                              }
                            
                              /**
                               * Contract constructor.
                               * It sets the `msg.sender` as the proxy administrator.
                               * @param _implementation address of the initial implementation.
                               */
                              constructor(address _implementation) UpgradeabilityProxy(_implementation) public {
                                assert(ADMIN_SLOT == keccak256("org.zeppelinos.proxy.admin"));
                            
                                _setAdmin(msg.sender);
                              }
                            
                              /**
                               * @return The address of the proxy admin.
                               */
                              function admin() external view ifAdmin returns (address) {
                                return _admin();
                              }
                            
                              /**
                               * @return The address of the implementation.
                               */
                              function implementation() external view ifAdmin returns (address) {
                                return _implementation();
                              }
                            
                              /**
                               * @dev Changes the admin of the proxy.
                               * Only the current admin can call this function.
                               * @param newAdmin Address to transfer proxy administration to.
                               */
                              function changeAdmin(address newAdmin) external ifAdmin {
                                require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
                                emit AdminChanged(_admin(), newAdmin);
                                _setAdmin(newAdmin);
                              }
                            
                              /**
                               * @dev Upgrade the backing implementation of the proxy.
                               * Only the admin can call this function.
                               * @param newImplementation Address of the new implementation.
                               */
                              function upgradeTo(address newImplementation) external ifAdmin {
                                _upgradeTo(newImplementation);
                              }
                            
                              /**
                               * @dev Upgrade the backing implementation of the proxy and call a function
                               * on the new implementation.
                               * This is useful to initialize the proxied contract.
                               * @param newImplementation Address of the new implementation.
                               * @param data Data to send as msg.data in the low level call.
                               * It should include the signature and the parameters of the function to be
                               * called, as described in
                               * https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding.
                               */
                              function upgradeToAndCall(address newImplementation, bytes data) payable external ifAdmin {
                                _upgradeTo(newImplementation);
                                require(address(this).call.value(msg.value)(data));
                              }
                            
                              /**
                               * @return The admin slot.
                               */
                              function _admin() internal view returns (address adm) {
                                bytes32 slot = ADMIN_SLOT;
                                assembly {
                                  adm := sload(slot)
                                }
                              }
                            
                              /**
                               * @dev Sets the address of the proxy admin.
                               * @param newAdmin Address of the new proxy admin.
                               */
                              function _setAdmin(address newAdmin) internal {
                                bytes32 slot = ADMIN_SLOT;
                            
                                assembly {
                                  sstore(slot, newAdmin)
                                }
                              }
                            
                              /**
                               * @dev Only fall back when the sender is not the admin.
                               */
                              function _willFallback() internal {
                                require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
                                super._willFallback();
                              }
                            }
                            
                            // File: contracts/FiatTokenProxy.sol
                            
                            /**
                            * Copyright CENTRE SECZ 2018
                            *
                            * Permission is hereby granted, free of charge, to any person obtaining a copy 
                            * of this software and associated documentation files (the "Software"), to deal 
                            * in the Software without restriction, including without limitation the rights 
                            * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
                            * copies of the Software, and to permit persons to whom the Software is furnished to 
                            * do so, subject to the following conditions:
                            *
                            * The above copyright notice and this permission notice shall be included in all 
                            * copies or substantial portions of the Software.
                            *
                            * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
                            * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
                            * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
                            * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
                            * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
                            * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
                            */
                            
                            pragma solidity ^0.4.24;
                            
                            
                            /**
                             * @title FiatTokenProxy
                             * @dev This contract proxies FiatToken calls and enables FiatToken upgrades
                            */ 
                            contract FiatTokenProxy is AdminUpgradeabilityProxy {
                                constructor(address _implementation) public AdminUpgradeabilityProxy(_implementation) {
                                }
                            }

                            File 5 of 17: FluidDexT1
                            // 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_);
                            }
                            // 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))));
                                }
                            }// 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)
                                        }
                                    }
                                }
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            import { BigMathMinified } from "./bigMathMinified.sol";
                            import { DexSlotsLink } from "./dexSlotsLink.sol";
                            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                            // @DEV ATTENTION: ON ANY CHANGES HERE, MAKE SURE THAT LOGIC IN VAULTS WILL STILL BE VALID.
                            // SOME CODE THERE ASSUMES DEXCALCS == LIQUIDITYCALCS.
                            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                            /// @notice implements calculation methods used for Fluid Dex such as updated withdrawal / borrow limits.
                            library DexCalcs {
                                // 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 X14 = 0x3fff;
                                uint256 internal constant X18 = 0x3ffff;
                                uint256 internal constant X24 = 0xffffff;
                                uint256 internal constant X33 = 0x1ffffffff;
                                uint256 internal constant X64 = 0xffffffffffffffff;
                                ///////////////////////////////////////////////////////////////////////////
                                //////////                      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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.BITS_USER_BORROW_EXPAND_DURATION) & X24)) + // expand duration can never be 0
                                        //  extract last set borrow limit
                                        BigMathMinified.fromBigNumber(
                                            (userBorrowData_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_ >> DexSlotsLink.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_;
                                }
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            /// @notice library that helps in reading / working with storage slot data of Fluid Dex.
                            /// @dev as all data for Fluid Dex is internal, any data must be fetched directly through manual
                            /// slot reading through this library or, if gas usage is less important, through the FluidDexResolver.
                            library DexSlotsLink {
                                /// @dev storage slot for variables at Dex
                                uint256 internal constant DEX_VARIABLES_SLOT = 0;
                                /// @dev storage slot for variables2 at Dex
                                uint256 internal constant DEX_VARIABLES2_SLOT = 1;
                                /// @dev storage slot for total supply shares at Dex
                                uint256 internal constant DEX_TOTAL_SUPPLY_SHARES_SLOT = 2;
                                /// @dev storage slot for user supply mapping at Dex
                                uint256 internal constant DEX_USER_SUPPLY_MAPPING_SLOT = 3;
                                /// @dev storage slot for total borrow shares at Dex
                                uint256 internal constant DEX_TOTAL_BORROW_SHARES_SLOT = 4;
                                /// @dev storage slot for user borrow mapping at Dex
                                uint256 internal constant DEX_USER_BORROW_MAPPING_SLOT = 5;
                                /// @dev storage slot for oracle mapping at Dex
                                uint256 internal constant DEX_ORACLE_MAPPING_SLOT = 6;
                                /// @dev storage slot for range and threshold shifts at Dex
                                uint256 internal constant DEX_RANGE_THRESHOLD_SHIFTS_SLOT = 7;
                                /// @dev storage slot for center price shift at Dex
                                uint256 internal constant DEX_CENTER_PRICE_SHIFT_SLOT = 8;
                                // --------------------------------
                                // @dev stacked uint256 storage slots bits position data for each:
                                // UserSupplyData
                                uint256 internal constant BITS_USER_SUPPLY_ALLOWED = 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;
                                // UserBorrowData
                                uint256 internal constant BITS_USER_BORROW_ALLOWED = 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;
                                // --------------------------------
                                /// @notice Calculating the slot ID for Dex 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 Dex 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_));
                                }
                            }
                            // 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;
                            }
                            // 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);
                                }
                            }
                            // 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_));
                                }
                            }
                            // 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);
                                    }
                                }
                            }
                            // 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
                                    }
                                }
                            }
                            // 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;
                                }
                            }
                            //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 {}
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            import { Structs } from "./poolT1/coreModule/structs.sol";
                            abstract contract Error {
                                error FluidDexError(uint256 errorId_);
                                error FluidDexFactoryError(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(Structs.PricesAndExchangePrice pex_);
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            library ErrorTypes {
                                /***********************************|
                                |             DexT1                 | 
                                |__________________________________*/
                                /// @notice thrown at reentrancy
                                uint256 internal constant DexT1__AlreadyEntered = 51001;
                                uint256 internal constant DexT1__NotAnAuth = 51002;
                                uint256 internal constant DexT1__SmartColNotEnabled = 51003;
                                uint256 internal constant DexT1__SmartDebtNotEnabled = 51004;
                                uint256 internal constant DexT1__PoolNotInitialized = 51005;
                                uint256 internal constant DexT1__TokenReservesTooLow = 51006;
                                uint256 internal constant DexT1__EthAndAmountInMisMatch = 51007;
                                uint256 internal constant DexT1__EthSentForNonNativeSwap = 51008;
                                uint256 internal constant DexT1__NoSwapRoute = 51009;
                                uint256 internal constant DexT1__NotEnoughAmountOut = 51010;
                                uint256 internal constant DexT1__LiquidityLayerTokenUtilizationCapReached = 51011;
                                uint256 internal constant DexT1__HookReturnedFalse = 51012;
                                // Either user's config are not set or user is paused
                                uint256 internal constant DexT1__UserSupplyInNotOn = 51013;
                                // Either user's config are not set or user is paused
                                uint256 internal constant DexT1__UserDebtInNotOn = 51014;
                                // Thrown when contract asks for more token0 or token1 than what user's wants to give on deposit
                                uint256 internal constant DexT1__AboveDepositMax = 51015;
                                uint256 internal constant DexT1__MsgValueLowOnDepositOrPayback = 51016;
                                uint256 internal constant DexT1__WithdrawLimitReached = 51017;
                                // Thrown when contract gives less token0 or token1 than what user's wants on withdraw
                                uint256 internal constant DexT1__BelowWithdrawMin = 51018;
                                uint256 internal constant DexT1__DebtLimitReached = 51019;
                                // Thrown when contract gives less token0 or token1 than what user's wants on borrow
                                uint256 internal constant DexT1__BelowBorrowMin = 51020;
                                // Thrown when contract asks for more token0 or token1 than what user's wants on payback
                                uint256 internal constant DexT1__AbovePaybackMax = 51021;
                                uint256 internal constant DexT1__InvalidDepositAmts = 51022;
                                uint256 internal constant DexT1__DepositAmtsZero = 51023;
                                uint256 internal constant DexT1__SharesMintedLess = 51024;
                                uint256 internal constant DexT1__WithdrawalNotEnough = 51025;
                                uint256 internal constant DexT1__InvalidWithdrawAmts = 51026;
                                uint256 internal constant DexT1__WithdrawAmtsZero = 51027;
                                uint256 internal constant DexT1__WithdrawExcessSharesBurn = 51028;
                                uint256 internal constant DexT1__InvalidBorrowAmts = 51029;
                                uint256 internal constant DexT1__BorrowAmtsZero = 51030;
                                uint256 internal constant DexT1__BorrowExcessSharesMinted = 51031;
                                uint256 internal constant DexT1__PaybackAmtTooHigh = 51032;
                                uint256 internal constant DexT1__InvalidPaybackAmts = 51033;
                                uint256 internal constant DexT1__PaybackAmtsZero = 51034;
                                uint256 internal constant DexT1__PaybackSharedBurnedLess = 51035;
                                uint256 internal constant DexT1__NothingToArbitrage = 51036;
                                uint256 internal constant DexT1__MsgSenderNotLiquidity = 51037;
                                // On liquidity callback reentrancy bit should be on
                                uint256 internal constant DexT1__ReentrancyBitShouldBeOn = 51038;
                                // Thrown is reentrancy is already on and someone tries to fetch oracle price. Should not be possible to this
                                uint256 internal constant DexT1__OraclePriceFetchAlreadyEntered = 51039;
                                // Thrown when swap changes the current price by more than 5%
                                uint256 internal constant DexT1__OracleUpdateHugeSwapDiff = 51040;
                                uint256 internal constant DexT1__Token0ShouldBeSmallerThanToken1 = 51041;
                                uint256 internal constant DexT1__OracleMappingOverflow = 51042;
                                /// @notice thrown if governance has paused the swapping & arbitrage so only perfect functions are usable
                                uint256 internal constant DexT1__SwapAndArbitragePaused = 51043;
                                uint256 internal constant DexT1__ExceedsAmountInMax = 51044;
                                /// @notice thrown if amount in is too high or too low
                                uint256 internal constant DexT1__SwapInLimitingAmounts = 51045;
                                /// @notice thrown if amount out is too high or too low
                                uint256 internal constant DexT1__SwapOutLimitingAmounts = 51046;
                                uint256 internal constant DexT1__MintAmtOverflow = 51047;
                                uint256 internal constant DexT1__BurnAmtOverflow = 51048;
                                uint256 internal constant DexT1__LimitingAmountsSwapAndNonPerfectActions = 51049;
                                uint256 internal constant DexT1__InsufficientOracleData = 51050;
                                uint256 internal constant DexT1__SharesAmountInsufficient = 51051;
                                uint256 internal constant DexT1__CenterPriceOutOfRange = 51052;
                                uint256 internal constant DexT1__DebtReservesTooLow = 51053;
                                uint256 internal constant DexT1__SwapAndDepositTooLowOrTooHigh = 51054;
                                uint256 internal constant DexT1__WithdrawAndSwapTooLowOrTooHigh = 51055;
                                uint256 internal constant DexT1__BorrowAndSwapTooLowOrTooHigh = 51056;
                                uint256 internal constant DexT1__SwapAndPaybackTooLowOrTooHigh = 51057;
                                uint256 internal constant DexT1__InvalidImplementation = 51058;
                                uint256 internal constant DexT1__OnlyDelegateCallAllowed = 51059;
                                uint256 internal constant DexT1__IncorrectDataLength = 51060;
                                uint256 internal constant DexT1__AmountToSendLessThanAmount = 51061;
                                uint256 internal constant DexT1__InvalidCollateralReserves = 51062;
                                uint256 internal constant DexT1__InvalidDebtReserves = 51063;
                                uint256 internal constant DexT1__SupplySharesOverflow = 51064;
                                uint256 internal constant DexT1__BorrowSharesOverflow = 51065;
                                uint256 internal constant DexT1__OracleNotActive = 51066;
                                /***********************************|
                                |            DEX Admin              | 
                                |__________________________________*/
                                /// @notice thrown when pool is not initialized
                                uint256 internal constant DexT1Admin__PoolNotInitialized = 52001;
                                uint256 internal constant DexT1Admin__SmartColIsAlreadyOn = 52002;
                                uint256 internal constant DexT1Admin__SmartDebtIsAlreadyOn = 52003;
                                /// @notice thrown when any of the configs value overflow the maximum limit
                                uint256 internal constant DexT1Admin__ConfigOverflow = 52004;
                                uint256 internal constant DexT1Admin__AddressNotAContract = 52005;
                                uint256 internal constant DexT1Admin__InvalidParams = 52006;
                                uint256 internal constant DexT1Admin__UserNotDefined = 52007;
                                uint256 internal constant DexT1Admin__OnlyDelegateCallAllowed = 52008;
                                uint256 internal constant DexT1Admin__UnexpectedPoolState = 52009;
                                /// @notice thrown when trying to pause or unpause but user is already in the target pause state
                                uint256 internal constant DexT1Admin__InvalidPauseToggle = 52009;
                                /***********************************|
                                |            DEX Factory            | 
                                |__________________________________*/
                                uint256 internal constant DexFactory__InvalidOperation = 53001;
                                uint256 internal constant DexFactory__Unauthorized = 53002;
                                uint256 internal constant DexFactory__SameTokenNotAllowed = 53003;
                                uint256 internal constant DexFactory__TokenConfigNotProper = 53004;
                                uint256 internal constant DexFactory__InvalidParams = 53005;
                                uint256 internal constant DexFactory__OnlyDelegateCallAllowed = 53006;
                                uint256 internal constant DexFactory__InvalidDexAddress = 53007;
                            }
                            // SPDX-License-Identifier: MIT
                            pragma solidity 0.8.21;
                            interface IFluidDexFactory {
                                /// @notice Global auth is auth for all dexes
                                function isGlobalAuth(address auth_) external view returns (bool);
                                /// @notice Dex auth is auth for a specific dex
                                function isDexAuth(address vault_, address auth_) external view returns (bool);
                                /// @notice Total dexes deployed.
                                function totalDexes() external view returns (uint256);
                                /// @notice Compute dexAddress
                                function getDexAddress(uint256 dexId_) external view returns (address);
                                /// @notice read uint256 `result_` for a storage `slot_` key
                                function readFromStorage(bytes32 slot_) external view returns (uint256 result_);
                            }
                            // 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_
                                );
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            import { StorageRead } from "../../../../libraries/storageRead.sol";
                            interface ITokenDecimals {
                                function decimals() external view returns (uint8);
                            }
                            abstract contract ConstantVariables is StorageRead {
                                /*//////////////////////////////////////////////////////////////
                                                      CONSTANTS / IMMUTABLES
                                //////////////////////////////////////////////////////////////*/
                                address internal constant TEAM_MULTISIG = 0x4F6F977aCDD1177DCD81aB83074855EcB9C2D49e;
                                address internal constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
                                uint256 internal constant NATIVE_TOKEN_DECIMALS = 18;
                                address internal constant ADDRESS_DEAD = 0x000000000000000000000000000000000000dEaD;
                                uint256 internal constant TOKENS_DECIMALS_PRECISION = 12;
                                uint256 internal constant TOKENS_DECIMALS = 1e12;
                                uint256 internal constant SMALL_COEFFICIENT_SIZE = 10;
                                uint256 internal constant DEFAULT_COEFFICIENT_SIZE = 56;
                                uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
                                uint256 internal constant DEFAULT_EXPONENT_MASK = 0xFF;
                                uint256 internal constant X2 = 0x3;
                                uint256 internal constant X3 = 0x7;
                                uint256 internal constant X5 = 0x1f;
                                uint256 internal constant X7 = 0x7f;
                                uint256 internal constant X8 = 0xff;
                                uint256 internal constant X9 = 0x1ff;
                                uint256 internal constant X10 = 0x3ff;
                                uint256 internal constant X11 = 0x7ff;
                                uint256 internal constant X14 = 0x3fff;
                                uint256 internal constant X16 = 0xffff;
                                uint256 internal constant X17 = 0x1ffff;
                                uint256 internal constant X18 = 0x3ffff;
                                uint256 internal constant X20 = 0xfffff;
                                uint256 internal constant X22 = 0x3fffff;
                                uint256 internal constant X23 = 0x7fffff;
                                uint256 internal constant X24 = 0xffffff;
                                uint256 internal constant X28 = 0xfffffff;
                                uint256 internal constant X30 = 0x3fffffff;
                                uint256 internal constant X32 = 0xffffffff;
                                uint256 internal constant X33 = 0x1ffffffff;
                                uint256 internal constant X40 = 0xffffffffff;
                                uint256 internal constant X64 = 0xffffffffffffffff;
                                uint256 internal constant X96 = 0xffffffffffffffffffffffff;
                                uint256 internal constant X128 = 0xffffffffffffffffffffffffffffffff;
                                uint256 internal constant TWO_DECIMALS = 1e2;
                                uint256 internal constant THREE_DECIMALS = 1e3;
                                uint256 internal constant FOUR_DECIMALS = 1e4;
                                uint256 internal constant FIVE_DECIMALS = 1e5;
                                uint256 internal constant SIX_DECIMALS = 1e6;
                                uint256 internal constant EIGHT_DECIMALS = 1e8;
                                uint256 internal constant NINE_DECIMALS = 1e9;
                                uint256 internal constant PRICE_PRECISION = 1e27;
                                uint256 internal constant ORACLE_PRECISION = 1e18; // 100%
                                uint256 internal constant ORACLE_LIMIT = 5 * 1e16; // 5%
                                /// after swap token0 reserves should not be less than token1InToken0 / MINIMUM_LIQUIDITY_SWAP
                                /// after swap token1 reserves should not be less than token0InToken1 / MINIMUM_LIQUIDITY_SWAP
                                uint256 internal constant MINIMUM_LIQUIDITY_SWAP = 1e4;
                                /// after user operations (deposit, withdraw, borrow, payback) token0 reserves should not be less than token1InToken0 / MINIMUM_LIQUIDITY_USER_OPERATIONS
                                /// after user operations (deposit, withdraw, borrow, payback) token1 reserves should not be less than token0InToken0 / MINIMUM_LIQUIDITY_USER_OPERATIONS
                                uint256 internal constant MINIMUM_LIQUIDITY_USER_OPERATIONS = 1e6;
                                /// To skip transfers in liquidity layer if token in & out is same and liquidity layer is on the winning side
                                bytes32 internal constant SKIP_TRANSFERS = keccak256(bytes("SKIP_TRANSFERS"));
                                function _decimals(address token_) internal view returns (uint256) {
                                    return (token_ == NATIVE_TOKEN) ? NATIVE_TOKEN_DECIMALS : ITokenDecimals(token_).decimals();
                                }
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            abstract contract Variables {
                                /*//////////////////////////////////////////////////////////////
                                                      STORAGE VARIABLES
                                //////////////////////////////////////////////////////////////*/
                                /// First 1 bit  => 0 => re-entrancy. If 0 then allow transaction to go, else throw.
                                /// Next 40 bits => 1-40 => last to last stored price. BigNumber (32 bits precision, 8 bits exponent)
                                /// Next 40 bits => 41-80 => last stored price of pool. BigNumber (32 bits precision, 8 bits exponent)
                                /// Next 40 bits => 81-120 => center price. Center price from where the ranges will be calculated. BigNumber (32 bits precision, 8 bits exponent)
                                /// Next 33 bits => 121-153 => last interaction time stamp
                                /// Next 22 bits => 154-175 => max 4194303 seconds (~1165 hrs, ~48.5 days), time difference between last to last and last price stored
                                /// Next 3 bits  => 176-178 => oracle checkpoint, if 0 then first slot, if 7 then last slot
                                /// Next 16 bits => 179-194 => current mapping or oracle, after every 8 transaction it will increase by 1. Max capacity is 65535 but it can be lower than that check dexVariables2
                                /// Next 1 bit  => 195 => is oracle active?
                                uint internal dexVariables;
                                /// Next  1 bit  => 0 => is smart collateral enabled?
                                /// Next  1 bit  => 1 => is smart debt enabled?
                                /// Next 17 bits => 2-18 => fee (1% = 10000, max value: 100000 = 10%, fee should not be more than 10%)
                                /// Next  7 bits => 19-25 => revenue cut from fee (1 = 1%, 100 = 100%). If fee is 1000 = 0.1% and revenue cut is 10 = 10% then governance get 0.01% of every swap
                                /// Next  1 bit  => 26 => percent active change going on or not, 0 = false, 1 = true, if true than that means governance has updated the below percents and the update should happen with a specified time.
                                /// Next 20 bits => 27-46 => upperPercent (1% = 10000, max value: 104.8575%) upperRange - upperRange * upperPercent = centerPrice. Hence, upperRange = centerPrice / (1 - upperPercent)
                                /// Next 20 bits => 47-66 => lowerPercent. lowerRange = centerPrice - centerPrice * lowerPercent.
                                /// Next  1 bit  => 67 => threshold percent active change going on or not, 0 = false, 1 = true, if true than that means governance has updated the below percents and the update should happen with a specified time.
                                /// Next 10 bits => 68-77 => upper shift threshold percent, 1 = 0.1%. 1000 = 100%. if currentPrice > (centerPrice + (upperRange - centerPrice) * (1000 - upperShiftThresholdPercent) / 1000) then trigger shift
                                /// Next 10 bits => 78-87 => lower shift threshold percent, 1 = 0.1%. 1000 = 100%. if currentPrice < (centerPrice - (centerPrice - lowerRange) * (1000 - lowerShiftThresholdPercent) / 1000) then trigger shift
                                /// Next 24 bits => 88-111 => Shifting time (~194 days) (rate = (% up + % down) / time ?)
                                /// Next 30 bits => 112-131 => Address of center price if center price should be fetched externally, for example, for wstETH <> ETH pool, fetch wstETH exchange rate into stETH from wstETH contract.
                                /// Why fetch it externally? Because let's say pool width is 0.1% and wstETH temporarily got depeg of 0.5% then pool will start to shift to newer pricing
                                /// but we don't want pool to shift to 0.5% because we know the depeg will recover so to avoid the loss for users.
                                /// Next 30 bits => 142-171 => Hooks bits, calculate hook address by storing deployment nonce from factory.
                                /// Next 28 bits => 172-199 => max center price. BigNumber (20 bits precision, 8 bits exponent)
                                /// Next 28 bits => 200-227 => min center price. BigNumber (20 bits precision, 8 bits exponent)
                                /// Next 10 bits => 228-237 => utilization limit of token0. Max value 1000 = 100%, if 100% then no need to check the utilization.
                                /// Next 10 bits => 238-247 => utilization limit of token1. Max value 1000 = 100%, if 100% then no need to check the utilization.
                                /// Next 1  bit  => 248     => is center price shift active
                                /// Last 1  bit  => 255     => Pause swap & arbitrage (only perfect functions will be usable), if we need to pause entire DEX then that can be done through pausing DEX on Liquidity Layer
                                uint internal dexVariables2;
                                /// first 128 bits => 0-127 => total supply shares
                                /// last 128 bits => 128-255 => max supply shares
                                uint internal _totalSupplyShares;
                                /// @dev user supply data: user -> data
                                /// Aside from 1st bit, entire bits here are same as liquidity layer _userSupplyData. Hence exact same supply & borrow limit library can be used
                                /// First  1 bit  =>       0 => is user allowed to supply? 0 = not allowed, 1 = allowed
                                /// Next  64 bits =>   1- 64 => user supply amount/shares; BigMath: 56 | 8
                                /// Next  64 bits =>  65-128 => previous user withdrawal limit; BigMath: 56 | 8
                                /// Next  33 bits => 129-161 => last triggered process timestamp (enough until 16 March 2242 -> max value 8589934591)
                                /// Next  14 bits => 162-175 => expand withdrawal limit percentage (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383).
                                ///                             @dev shrinking is instant
                                /// Next  24 bits => 176-199 => withdrawal limit expand duration in seconds.(Max value 16_777_215; ~4_660 hours, ~194 days)
                                /// Next  18 bits => 200-217 => base withdrawal limit: below this, 100% withdrawals can be done (aka shares can be burned); BigMath: 10 | 8
                                /// Next  38 bits => 218-255 => empty for future use
                                mapping(address => uint) internal _userSupplyData;
                                /// first 128 bits => 0-127 => total borrow shares
                                /// last 128 bits => 128-255 => max borrow shares
                                uint internal _totalBorrowShares;
                                /// @dev user borrow data: user -> data
                                /// Aside from 1st bit, entire bits here are same as liquidity layer _userBorrowData. Hence exact same supply & borrow limit library function can be used
                                /// First  1 bit  =>       0 => is user allowed to borrow? 0 = not allowed, 1 = allowed
                                /// Next  64 bits =>   1- 64 => user debt amount/shares; BigMath: 56 | 8
                                /// Next  64 bits =>  65-128 => previous user debt ceiling; BigMath: 56 | 8
                                /// Next  33 bits => 129-161 => last triggered process timestamp (enough until 16 March 2242 -> max value 8589934591)
                                /// Next  14 bits => 162-175 => expand debt ceiling percentage (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                                ///                             @dev shrinking is instant
                                /// Next  24 bits => 176-199 => debt ceiling expand duration in seconds (Max value 16_777_215; ~4_660 hours, ~194 days)
                                /// Next  18 bits => 200-217 => base debt ceiling: below this, there's no debt ceiling limits; BigMath: 10 | 8
                                /// Next  18 bits => 218-235 => max debt ceiling: absolute maximum debt ceiling can expand to; BigMath: 10 | 8
                                /// Next  20 bits => 236-255 => empty for future use
                                mapping(address => uint) internal _userBorrowData;
                                /// Price difference between last swap of last block & last swap of new block
                                /// If last swap happened at Block B - 4 and next swap happened after 4 blocks at Block B then it will store that difference
                                /// considering time difference between these 4 blocks is 48 seconds, hence time will be stored as 48
                                /// New oracle update:
                                /// time to 9 bits and precision to 22 bits
                                /// if time exceeds 9 bits which is 511 sec or ~8.5 min then we will use 2 oracle slot to store the data
                                /// we will leave the both time slot as 0 and on first sign + precision slot we will store time and
                                /// on second sign + precision slot we will store sign & precision
                                /// First 9 bits =>   0-  8 => time, 511 seconds
                                /// Next   1 bit  =>  9     => sign of percent in change, if 1 then 0 or positive, else negative
                                /// Next  22 bits =>  10- 31 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                                /// Next  9 bits =>  32- 40 => time, 511 seconds
                                /// Next   1 bit  =>  41     => sign of percent in change, if 1 then 0 or positive, else negative
                                /// Next  22 bits =>  42- 63 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                                /// Next  9 bits =>  64- 72 => time, 511 seconds
                                /// Next   1 bit  =>  73     => sign of percent in change, if 1 then 0 or positive, else negative
                                /// Next  22 bits =>  74- 95 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                                /// Next  9 bits =>  96-104 => time, 511 seconds
                                /// Next   1 bit  => 105     => sign of percent in change, if 1 then 0 or positive, else negative
                                /// Next  22 bits => 106-127 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                                /// Next  9 bits => 128-136 => time, 511 seconds
                                /// Next   1 bit  => 137     => sign of percent in change, if 1 then 0 or positive, else negative
                                /// Next  22 bits => 138-159 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                                /// Next  9 bits => 160-168 => time, 511 seconds
                                /// Next   1 bit  => 169     => sign of percent in change, if 1 then 0 or positive, else negative
                                /// Next  22 bits => 170-191 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                                /// Next  9 bits => 192-200 => time, 511 seconds
                                /// Next   1 bit  => 201     => sign of percent in change, if 1 then 0 or positive, else negative
                                /// Next  22 bits => 202-223 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                                /// Next  9 bits => 224-232 => time, 511 seconds
                                /// Next   1 bit  => 233     => sign of percent in change, if 1 then 0 or positive, else negative
                                /// Next  22 bits => 234-255 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                                mapping(uint => uint) internal _oracle;
                                /// First 20 bits =>  0-19 => old upper shift
                                /// Next  20 bits => 20-39 => old lower shift
                                /// Next  20 bits => 40-59 => in seconds, ~12 days max, shift can last for max ~12 days
                                /// Next  33 bits => 60-92 => timestamp of when the shift has started.
                                uint128 internal _rangeShift;
                                /// First 10 bits =>  0- 9 => old upper shift
                                /// Next  10 bits => 10-19 => empty so we can use same helper function
                                /// Next  10 bits => 20-29 => old lower shift
                                /// Next  10 bits => 30-39 => empty so we can use same helper function
                                /// Next  20 bits => 40-59 => in seconds, ~12 days max, shift can last for max ~12 days
                                /// Next  33 bits => 60-92 => timestamp of when the shift has started.
                                /// Next  24 bits => 93-116 => old threshold time
                                uint128 internal _thresholdShift;
                                /// Shifting is fuzzy and with time it'll keep on getting closer and then eventually get over
                                /// First 33 bits => 0 -32 => starting timestamp
                                /// Next  20 bits => 33-52 => % shift
                                /// Next  20 bits => 53-72 => time to shift that percent
                                uint256 internal _centerPriceShift;
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            import { CoreHelpers } from "../helpers/coreHelpers.sol";
                            import { SafeTransfer } from "../../../../../libraries/safeTransfer.sol";
                            import { DexSlotsLink } from "../../../../../libraries/dexSlotsLink.sol";
                            import { DexCalcs } from "../../../../../libraries/dexCalcs.sol";
                            import { BigMathMinified } from "../../../../../libraries/bigMathMinified.sol";
                            import { ErrorTypes } from "../../../errorTypes.sol";
                            import { IFluidDexT1 } from "../../../interfaces/iDexT1.sol";
                            interface IDexCallback {
                                function dexCallback(address token_, uint256 amount_) external;
                            }
                            /// @title FluidDexT1
                            /// @notice Implements core logics for Fluid Dex protocol.
                            /// Note Token transfers happen directly from user to Liquidity contract and vice-versa.
                            contract FluidDexT1 is CoreHelpers {
                                using BigMathMinified for uint256;
                                constructor(ConstantViews memory constantViews_) CoreHelpers(constantViews_) {
                                    // any implementations should not be zero
                                    if (
                                        constantViews_.implementations.shift == address(0) ||
                                        constantViews_.implementations.admin == address(0) ||
                                        constantViews_.implementations.colOperations == address(0) ||
                                        constantViews_.implementations.debtOperations == address(0) ||
                                        constantViews_.implementations.perfectOperationsAndSwapOut == address(0)
                                    ) {
                                        revert FluidDexError(ErrorTypes.DexT1__InvalidImplementation);
                                    }
                                }
                                struct SwapInExtras {
                                    address to;
                                    uint amountOutMin;
                                    bool isCallback;
                                }
                                /// @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 extras_ Additional parameters for the swap:
                                ///   - 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_
                                ///   - amountOutMin: The minimum amount of output tokens the user expects to receive
                                ///   - isCallback: If true, indicates that the input tokens should be transferred via a callback
                                /// @return amountOut_ The amount of output tokens received from the swap
                                function _swapIn(
                                    bool swap0to1_,
                                    uint256 amountIn_,
                                    SwapInExtras memory extras_
                                ) internal returns (uint256 amountOut_) {
                                    uint dexVariables_ = dexVariables;
                                    uint dexVariables2_ = dexVariables2;
                                    if ((dexVariables2_ >> 255) == 1) revert FluidDexError(ErrorTypes.DexT1__SwapAndArbitragePaused);
                                    _check(dexVariables_, dexVariables2_);
                                    if (extras_.to == address(0)) extras_.to = msg.sender;
                                    SwapInMemory memory s_;
                                    if (swap0to1_) {
                                        (s_.tokenIn, s_.tokenOut) = (TOKEN_0, TOKEN_1);
                                        unchecked {
                                            s_.amtInAdjusted = (amountIn_ * TOKEN_0_NUMERATOR_PRECISION) / TOKEN_0_DENOMINATOR_PRECISION;
                                        }
                                    } else {
                                        (s_.tokenIn, s_.tokenOut) = (TOKEN_1, TOKEN_0);
                                        unchecked {
                                            s_.amtInAdjusted = (amountIn_ * TOKEN_1_NUMERATOR_PRECISION) / TOKEN_1_DENOMINATOR_PRECISION;
                                        }
                                    }
                                    _verifySwapAndNonPerfectActions(s_.amtInAdjusted, amountIn_);
                                    PricesAndExchangePrice memory pex_ = _getPricesAndExchangePrices(dexVariables_, dexVariables2_);
                                    if (msg.value > 0) {
                                        if (msg.value != amountIn_) revert FluidDexError(ErrorTypes.DexT1__EthAndAmountInMisMatch);
                                        if (s_.tokenIn != NATIVE_TOKEN) revert FluidDexError(ErrorTypes.DexT1__EthSentForNonNativeSwap);
                                    }
                                    // is smart collateral pool enabled
                                    uint temp_ = dexVariables2_ & 1;
                                    // is smart debt pool enabled
                                    uint temp2_ = (dexVariables2_ >> 1) & 1;
                                    uint temp3_;
                                    uint temp4_;
                                    // extracting fee
                                    temp3_ = ((dexVariables2_ >> 2) & X17);
                                    unchecked {
                                        // revenueCut in 6 decimals, to have proper precision
                                        // if fee = 1% and revenue cut = 10% then revenueCut = 1e8 - (10000 * 10) = 99900000
                                        s_.revenueCut = EIGHT_DECIMALS - ((((dexVariables2_ >> 19) & X7) * temp3_));
                                        // fee in 4 decimals
                                        // 1 - fee. If fee is 1% then withoutFee will be 1e6 - 1e4
                                        // s_.fee => 1 - withdraw fee
                                        s_.fee = SIX_DECIMALS - temp3_;
                                    }
                                    CollateralReservesSwap memory cs_;
                                    DebtReservesSwap memory ds_;
                                    if (temp_ == 1) {
                                        // smart collateral is enabled
                                        {
                                            CollateralReserves memory c_ = _getCollateralReserves(
                                                pex_.geometricMean,
                                                pex_.upperRange,
                                                pex_.lowerRange,
                                                pex_.supplyToken0ExchangePrice,
                                                pex_.supplyToken1ExchangePrice
                                            );
                                            if (swap0to1_) {
                                                (
                                                    cs_.tokenInRealReserves,
                                                    cs_.tokenOutRealReserves,
                                                    cs_.tokenInImaginaryReserves,
                                                    cs_.tokenOutImaginaryReserves
                                                ) = (
                                                    c_.token0RealReserves,
                                                    c_.token1RealReserves,
                                                    c_.token0ImaginaryReserves,
                                                    c_.token1ImaginaryReserves
                                                );
                                            } else {
                                                (
                                                    cs_.tokenInRealReserves,
                                                    cs_.tokenOutRealReserves,
                                                    cs_.tokenInImaginaryReserves,
                                                    cs_.tokenOutImaginaryReserves
                                                ) = (
                                                    c_.token1RealReserves,
                                                    c_.token0RealReserves,
                                                    c_.token1ImaginaryReserves,
                                                    c_.token0ImaginaryReserves
                                                );
                                            }
                                        }
                                    }
                                    if (temp2_ == 1) {
                                        // smart debt is enabled
                                        {
                                            DebtReserves memory d_ = _getDebtReserves(
                                                pex_.geometricMean,
                                                pex_.upperRange,
                                                pex_.lowerRange,
                                                pex_.borrowToken0ExchangePrice,
                                                pex_.borrowToken1ExchangePrice
                                            );
                                            if (swap0to1_) {
                                                (
                                                    ds_.tokenInDebt,
                                                    ds_.tokenOutDebt,
                                                    ds_.tokenInRealReserves,
                                                    ds_.tokenOutRealReserves,
                                                    ds_.tokenInImaginaryReserves,
                                                    ds_.tokenOutImaginaryReserves
                                                ) = (
                                                    d_.token0Debt,
                                                    d_.token1Debt,
                                                    d_.token0RealReserves,
                                                    d_.token1RealReserves,
                                                    d_.token0ImaginaryReserves,
                                                    d_.token1ImaginaryReserves
                                                );
                                            } else {
                                                (
                                                    ds_.tokenInDebt,
                                                    ds_.tokenOutDebt,
                                                    ds_.tokenInRealReserves,
                                                    ds_.tokenOutRealReserves,
                                                    ds_.tokenInImaginaryReserves,
                                                    ds_.tokenOutImaginaryReserves
                                                ) = (
                                                    d_.token1Debt,
                                                    d_.token0Debt,
                                                    d_.token1RealReserves,
                                                    d_.token0RealReserves,
                                                    d_.token1ImaginaryReserves,
                                                    d_.token0ImaginaryReserves
                                                );
                                            }
                                        }
                                    }
                                    // limiting amtInAdjusted to be not more than 50% of both (collateral & debt) imaginary tokenIn reserves combined
                                    // basically, if this throws that means user is trying to swap 0.5x tokenIn if current tokenIn imaginary reserves is x
                                    // let's take x as token0 here, that means, initially the pool pricing might be:
                                    // token1Reserve / x and new pool pricing will become token1Reserve / 1.5x (token1Reserve will decrease after swap but for simplicity ignoring that)
                                    // So pool price is decreased by ~33.33% (oracle will throw error in this case as it only allows 5% price difference but better to limit it before hand)
                                    unchecked {
                                        if (s_.amtInAdjusted > ((cs_.tokenInImaginaryReserves + ds_.tokenInImaginaryReserves) / 2))
                                            revert FluidDexError(ErrorTypes.DexT1__SwapInLimitingAmounts);
                                    }
                                    if (temp_ == 1 && temp2_ == 1) {
                                        // unless both pools are enabled s_.swapRoutingAmt will be 0
                                        s_.swapRoutingAmt = _swapRoutingIn(
                                            s_.amtInAdjusted,
                                            cs_.tokenOutImaginaryReserves,
                                            cs_.tokenInImaginaryReserves,
                                            ds_.tokenOutImaginaryReserves,
                                            ds_.tokenInImaginaryReserves
                                        );
                                    }
                                    // In below if else statement temps are:
                                    // temp_ => deposit amt
                                    // temp2_ => withdraw amt
                                    // temp3_ => payback amt
                                    // temp4_ => borrow amt
                                    if (int(s_.amtInAdjusted) > s_.swapRoutingAmt && s_.swapRoutingAmt > 0) {
                                        // swap will route from the both pools
                                        // temp_ = amountInCol_
                                        temp_ = uint(s_.swapRoutingAmt);
                                        unchecked {
                                            // temp3_ = amountInDebt_
                                            temp3_ = s_.amtInAdjusted - temp_;
                                        }
                                        (temp2_, temp4_) = (0, 0);
                                        // debt pool price will be the same as collateral pool after the swap
                                        s_.withdrawTo = extras_.to;
                                        s_.borrowTo = extras_.to;
                                    } else if ((temp_ == 1 && temp2_ == 0) || (s_.swapRoutingAmt >= int(s_.amtInAdjusted))) {
                                        // entire swap will route through collateral pool
                                        (temp_, temp2_, temp3_, temp4_) = (s_.amtInAdjusted, 0, 0, 0);
                                        // price can slightly differ from debt pool but difference will be very small. Probably <0.01% for active DEX pools.
                                        s_.withdrawTo = extras_.to;
                                    } else if ((temp_ == 0 && temp2_ == 1) || (s_.swapRoutingAmt <= 0)) {
                                        // entire swap will route through debt pool
                                        (temp_, temp2_, temp3_, temp4_) = (0, 0, s_.amtInAdjusted, 0);
                                        // price can slightly differ from collateral pool but difference will be very small. Probably <0.01% for active DEX pools.
                                        s_.borrowTo = extras_.to;
                                    } else {
                                        // swap should never reach this point but if it does then reverting
                                        revert FluidDexError(ErrorTypes.DexT1__NoSwapRoute);
                                    }
                                    if (temp_ > 0) {
                                        // temp2_ = amountOutCol_
                                        temp2_ = _getAmountOut(
                                            ((temp_ * s_.fee) / SIX_DECIMALS),
                                            cs_.tokenInImaginaryReserves,
                                            cs_.tokenOutImaginaryReserves
                                        );
                                        swap0to1_
                                            ? _verifyToken1Reserves(
                                                (cs_.tokenInRealReserves + temp_),
                                                (cs_.tokenOutRealReserves - temp2_),
                                                pex_.centerPrice,
                                                MINIMUM_LIQUIDITY_SWAP
                                            )
                                            : _verifyToken0Reserves(
                                                (cs_.tokenOutRealReserves - temp2_),
                                                (cs_.tokenInRealReserves + temp_),
                                                pex_.centerPrice,
                                                MINIMUM_LIQUIDITY_SWAP
                                            );
                                    }
                                    if (temp3_ > 0) {
                                        // temp4_ = amountOutDebt_
                                        temp4_ = _getAmountOut(
                                            ((temp3_ * s_.fee) / SIX_DECIMALS),
                                            ds_.tokenInImaginaryReserves,
                                            ds_.tokenOutImaginaryReserves
                                        );
                                        swap0to1_
                                            ? _verifyToken1Reserves(
                                                (ds_.tokenInRealReserves + temp3_),
                                                (ds_.tokenOutRealReserves - temp4_),
                                                pex_.centerPrice,
                                                MINIMUM_LIQUIDITY_SWAP
                                            )
                                            : _verifyToken0Reserves(
                                                (ds_.tokenOutRealReserves - temp4_),
                                                (ds_.tokenInRealReserves + temp3_),
                                                pex_.centerPrice,
                                                MINIMUM_LIQUIDITY_SWAP
                                            );
                                    }
                                    // (temp_ + temp3_) == amountIn_ == msg.value (for native token), if there is revenue cut then this statement is not true
                                    temp_ = (temp_ * s_.revenueCut) / EIGHT_DECIMALS;
                                    temp3_ = (temp3_ * s_.revenueCut) / EIGHT_DECIMALS;
                                    // from whatever pool higher amount of swap is routing we are taking that as final price, does not matter much because both pools final price should be same
                                    if (temp_ > temp3_) {
                                        // new pool price from col pool
                                        s_.price = swap0to1_
                                            ? ((cs_.tokenOutImaginaryReserves - temp2_) * 1e27) / (cs_.tokenInImaginaryReserves + temp_)
                                            : ((cs_.tokenInImaginaryReserves + temp_) * 1e27) / (cs_.tokenOutImaginaryReserves - temp2_);
                                    } else {
                                        // new pool price from debt pool
                                        s_.price = swap0to1_
                                            ? ((ds_.tokenOutImaginaryReserves - temp4_) * 1e27) / (ds_.tokenInImaginaryReserves + temp3_)
                                            : ((ds_.tokenInImaginaryReserves + temp3_) * 1e27) / (ds_.tokenOutImaginaryReserves - temp4_);
                                    }
                                    // converting into normal token amounts
                                    if (swap0to1_) {
                                        temp_ = ((temp_ * TOKEN_0_DENOMINATOR_PRECISION) / TOKEN_0_NUMERATOR_PRECISION);
                                        temp3_ = ((temp3_ * TOKEN_0_DENOMINATOR_PRECISION) / TOKEN_0_NUMERATOR_PRECISION);
                                        // only adding uncheck in out amount
                                        unchecked {
                                            temp2_ = ((temp2_ * TOKEN_1_DENOMINATOR_PRECISION) / TOKEN_1_NUMERATOR_PRECISION);
                                            temp4_ = ((temp4_ * TOKEN_1_DENOMINATOR_PRECISION) / TOKEN_1_NUMERATOR_PRECISION);
                                        }
                                    } else {
                                        temp_ = ((temp_ * TOKEN_1_DENOMINATOR_PRECISION) / TOKEN_1_NUMERATOR_PRECISION);
                                        temp3_ = ((temp3_ * TOKEN_1_DENOMINATOR_PRECISION) / TOKEN_1_NUMERATOR_PRECISION);
                                        // only adding uncheck in out amount
                                        unchecked {
                                            temp2_ = ((temp2_ * TOKEN_0_DENOMINATOR_PRECISION) / TOKEN_0_NUMERATOR_PRECISION);
                                            temp4_ = ((temp4_ * TOKEN_0_DENOMINATOR_PRECISION) / TOKEN_0_NUMERATOR_PRECISION);
                                        }
                                    }
                                    unchecked {
                                        amountOut_ = temp2_ + temp4_;
                                    }
                                    // if address dead then reverting with amountOut
                                    if (extras_.to == ADDRESS_DEAD) revert FluidDexSwapResult(amountOut_);
                                    if (amountOut_ < extras_.amountOutMin) revert FluidDexError(ErrorTypes.DexT1__NotEnoughAmountOut);
                                    // allocating to avoid stack-too-deep error
                                    // not setting in the callbackData as last 2nd to avoid SKIP_TRANSFERS clashing
                                    s_.data = abi.encode(amountIn_, extras_.isCallback, msg.sender); // true/false is to decide if dex should do callback or directly transfer from user
                                    // deposit & payback token in at liquidity
                                    LIQUIDITY.operate{ value: msg.value }(s_.tokenIn, int(temp_), -int(temp3_), address(0), address(0), s_.data);
                                    // withdraw & borrow token out at liquidity
                                    LIQUIDITY.operate(s_.tokenOut, -int(temp2_), int(temp4_), s_.withdrawTo, s_.borrowTo, new bytes(0));
                                    // if hook exists then calling hook
                                    temp_ = (dexVariables2_ >> 142) & X30;
                                    if (temp_ > 0) {
                                        s_.swap0to1 = swap0to1_;
                                        _hookVerify(temp_, 1, s_.swap0to1, s_.price);
                                    }
                                    swap0to1_
                                        ? _utilizationVerify(((dexVariables2_ >> 238) & X10), EXCHANGE_PRICE_TOKEN_1_SLOT)
                                        : _utilizationVerify(((dexVariables2_ >> 228) & X10), EXCHANGE_PRICE_TOKEN_0_SLOT);
                                    dexVariables = _updateOracle(s_.price, pex_.centerPrice, dexVariables_);
                                    emit Swap(swap0to1_, amountIn_, amountOut_, extras_.to);
                                }
                                /// @dev Swap tokens with perfect amount in
                                /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
                                /// @param amountIn_ The exact amount of tokens to swap in
                                /// @param amountOutMin_ The minimum amount of tokens to receive after swap
                                /// @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_
                                ) public payable returns (uint256 amountOut_) {
                                    return _swapIn(swap0to1_, amountIn_, SwapInExtras(to_, amountOutMin_, false));
                                }
                                /// @dev Swap tokens with perfect amount in and callback functionality
                                /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
                                /// @param amountIn_ The exact amount of tokens to swap in
                                /// @param amountOutMin_ The minimum amount of tokens to receive after swap
                                /// @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 swapInWithCallback(
                                    bool swap0to1_,
                                    uint256 amountIn_,
                                    uint256 amountOutMin_,
                                    address to_
                                ) public payable returns (uint256 amountOut_) {
                                    return _swapIn(swap0to1_, amountIn_, SwapInExtras(to_, amountOutMin_, true));
                                }
                                /// @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_
                                ) public payable returns (uint256 amountIn_) {
                                    return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256));
                                }
                                /// @dev Swap tokens with perfect amount out and callback functionality
                                /// @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 swapOutWithCallback(
                                    bool swap0to1_,
                                    uint256 amountOut_,
                                    uint256 amountInMax_,
                                    address to_
                                ) public payable returns (uint256 amountIn_) {
                                    return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256));
                                }
                                /// @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_
                                ) public payable returns (uint token0Amt_, uint token1Amt_) {
                                    return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256, uint256));
                                }
                                /// @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 withdrawn 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_
                                ) public returns (uint token0Amt_, uint token1Amt_) {
                                    return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256, uint256));
                                }
                                /// @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 borrowed 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_
                                ) public returns (uint token0Amt_, uint token1Amt_) {
                                    return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256, uint256));
                                }
                                /// @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_
                                ) public payable returns (uint token0Amt_, uint token1Amt_) {
                                    return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256, uint256));
                                }
                                /// @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_
                                ) public payable returns (uint shares_) {
                                    return abi.decode(_spell(COL_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                                }
                                /// @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 withdrawn 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_
                                ) public returns (uint shares_) {
                                    return abi.decode(_spell(COL_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                                }
                                /// @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 borrowed 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_
                                ) public returns (uint shares_) {
                                    return abi.decode(_spell(DEBT_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                                }
                                /// @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_
                                ) public payable returns (uint shares_) {
                                    return abi.decode(_spell(DEBT_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                                }
                                /// @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 withdrawn 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_
                                ) public returns (uint withdrawAmt_) {
                                    return abi.decode(_spell(COL_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                                }
                                /// @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_
                                ) public payable returns (uint paybackAmt_) {
                                    return abi.decode(_spell(DEBT_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                                }
                                /// @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 FluidDexError(ErrorTypes.DexT1__MsgSenderNotLiquidity);
                                    if (dexVariables & 1 == 0) revert FluidDexError(ErrorTypes.DexT1__ReentrancyBitShouldBeOn);
                                    if (data_.length != 96) revert FluidDexError(ErrorTypes.DexT1__IncorrectDataLength);
                                    (uint amountToSend_, bool isCallback_, address from_) = abi.decode(data_, (uint, bool, address));
                                    if (amountToSend_ < amount_) revert FluidDexError(ErrorTypes.DexT1__AmountToSendLessThanAmount);
                                    if (isCallback_) {
                                        IDexCallback(from_).dexCallback(token_, amountToSend_);
                                    } else {
                                        SafeTransfer.safeTransferFrom(token_, from_, address(LIQUIDITY), amountToSend_);
                                    }
                                }
                                /// @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_) {
                                    OraclePriceMemory memory o_;
                                    uint dexVariables_ = dexVariables;
                                    if ((dexVariables_ >> 195) & 1 == 0) {
                                        revert FluidDexError(ErrorTypes.DexT1__OracleNotActive);
                                    }
                                    twaps_ = new Oracle[](secondsAgos_.length);
                                    uint totalTime_;
                                    uint time_;
                                    uint i;
                                    uint secondsAgo_ = secondsAgos_[0];
                                    currentPrice_ = (dexVariables_ >> 41) & X40;
                                    currentPrice_ = (currentPrice_ >> DEFAULT_EXPONENT_SIZE) << (currentPrice_ & DEFAULT_EXPONENT_MASK);
                                    uint price_ = currentPrice_;
                                    o_.lowestPrice1by0 = currentPrice_;
                                    o_.highestPrice1by0 = currentPrice_;
                                    uint twap1by0_;
                                    uint twap0by1_;
                                    uint j;
                                    o_.oracleSlot = (dexVariables_ >> 176) & X3;
                                    o_.oracleMap = (dexVariables_ >> 179) & X16;
                                    // if o_.oracleSlot == 7 then it'll enter the if statement in the below while loop
                                    o_.oracle = o_.oracleSlot < 7 ? _oracle[o_.oracleMap] : 0;
                                    uint slotData_;
                                    uint percentDiff_;
                                    if (((dexVariables_ >> 121) & X33) < block.timestamp) {
                                        // last swap didn't occured in this block.
                                        // hence last price is current price of pool & also the last price
                                        time_ = block.timestamp - ((dexVariables_ >> 121) & X33);
                                    } else {
                                        // last swap occured in this block, that means current price is active for 0 secs. Hence TWAP for it will be 0.
                                        ++j;
                                    }
                                    while (true) {
                                        if (j == 2) {
                                            if (++o_.oracleSlot == 8) {
                                                o_.oracleSlot = 0;
                                                if (o_.oracleMap == 0) {
                                                    o_.oracleMap = TOTAL_ORACLE_MAPPING;
                                                }
                                                o_.oracle = _oracle[--o_.oracleMap];
                                            }
                                            slotData_ = (o_.oracle >> (o_.oracleSlot * 32)) & X32;
                                            if (slotData_ > 0) {
                                                time_ = slotData_ & X9;
                                                if (time_ == 0) {
                                                    // time is in precision & sign bits
                                                    time_ = slotData_ >> 9;
                                                    // if o_.oracleSlot is 7 then precision & bits and stored in 1 less map
                                                    if (o_.oracleSlot == 7) {
                                                        o_.oracleSlot = 0;
                                                        if (o_.oracleMap == 0) {
                                                            o_.oracleMap = TOTAL_ORACLE_MAPPING;
                                                        }
                                                        o_.oracle = _oracle[--o_.oracleMap];
                                                        slotData_ = o_.oracle & X32;
                                                    } else {
                                                        ++o_.oracleSlot;
                                                        slotData_ = (o_.oracle >> (o_.oracleSlot * 32)) & X32;
                                                    }
                                                }
                                                percentDiff_ = slotData_ >> 10;
                                                percentDiff_ = (ORACLE_LIMIT * percentDiff_) / X22;
                                                if (((slotData_ >> 9) & 1 == 1)) {
                                                    // if positive then old price was lower than current hence subtracting
                                                    price_ = price_ - (price_ * percentDiff_) / ORACLE_PRECISION;
                                                } else {
                                                    // if negative then old price was higher than current hence adding
                                                    price_ = price_ + (price_ * percentDiff_) / ORACLE_PRECISION;
                                                }
                                            } else {
                                                // oracle data does not exist. Probably due to pool recently got initialized and not have much swaps.
                                                revert FluidDexError(ErrorTypes.DexT1__InsufficientOracleData);
                                            }
                                        } else if (j == 1) {
                                            // last & last to last price
                                            price_ = (dexVariables_ >> 1) & X40;
                                            price_ = (price_ >> DEFAULT_EXPONENT_SIZE) << (price_ & DEFAULT_EXPONENT_MASK);
                                            time_ = (dexVariables_ >> 154) & X22;
                                            ++j;
                                        } else if (j == 0) {
                                            ++j;
                                        }
                                        totalTime_ += time_;
                                        if (o_.lowestPrice1by0 > price_) o_.lowestPrice1by0 = price_;
                                        if (o_.highestPrice1by0 < price_) o_.highestPrice1by0 = price_;
                                        if (totalTime_ < secondsAgo_) {
                                            twap1by0_ += price_ * time_;
                                            twap0by1_ += (1e54 / price_) * time_;
                                        } else {
                                            time_ = time_ + secondsAgo_ - totalTime_;
                                            twap1by0_ += price_ * time_;
                                            twap0by1_ += (1e54 / price_) * time_;
                                            // also auto checks that secondsAgos_ should not be == 0
                                            twap1by0_ = twap1by0_ / secondsAgo_;
                                            twap0by1_ = twap0by1_ / secondsAgo_;
                                            twaps_[i] = Oracle(
                                                twap1by0_,
                                                o_.lowestPrice1by0,
                                                o_.highestPrice1by0,
                                                twap0by1_,
                                                (1e54 / o_.highestPrice1by0),
                                                (1e54 / o_.lowestPrice1by0)
                                            );
                                            // TWAP for next secondsAgo will start with price_
                                            o_.lowestPrice1by0 = price_;
                                            o_.highestPrice1by0 = price_;
                                            while (++i < secondsAgos_.length) {
                                                // secondsAgo_ = [60, 15, 0]
                                                time_ = totalTime_ - secondsAgo_;
                                                // updating total time as new seconds ago started
                                                totalTime_ = time_;
                                                // also auto checks that secondsAgos_[i + 1] > secondsAgos_[i]
                                                secondsAgo_ = secondsAgos_[i] - secondsAgos_[i - 1];
                                                if (totalTime_ < secondsAgo_) {
                                                    twap1by0_ = price_ * time_;
                                                    twap0by1_ = (1e54 / price_) * time_;
                                                    // if time_ comes out as 0 here then lowestPrice & highestPrice should not be price_, it should be next price_ that we will calculate
                                                    if (time_ == 0) {
                                                        o_.lowestPrice1by0 = type(uint).max;
                                                        o_.highestPrice1by0 = 0;
                                                    }
                                                    break;
                                                } else {
                                                    time_ = time_ + secondsAgo_ - totalTime_;
                                                    // twap1by0_ = price_ here
                                                    twap1by0_ = price_ * time_;
                                                    // twap0by1_ = (1e54 / price_) * time_;
                                                    twap0by1_ = (1e54 / price_) * time_;
                                                    twap1by0_ = twap1by0_ / secondsAgo_;
                                                    twap0by1_ = twap0by1_ / secondsAgo_;
                                                    twaps_[i] = Oracle(
                                                        twap1by0_,
                                                        o_.lowestPrice1by0,
                                                        o_.highestPrice1by0,
                                                        twap0by1_,
                                                        (1e54 / o_.highestPrice1by0),
                                                        (1e54 / o_.lowestPrice1by0)
                                                    );
                                                }
                                            }
                                            if (i == secondsAgos_.length) return (twaps_, currentPrice_); // oracle fetch over
                                        }
                                    }
                                }
                                function getPricesAndExchangePrices() public {
                                    uint dexVariables_ = dexVariables;
                                    uint dexVariables2_ = dexVariables2;
                                    _check(dexVariables_, dexVariables2_);
                                    PricesAndExchangePrice memory pex_ = _getPricesAndExchangePrices(dexVariables, dexVariables2);
                                    revert FluidDexPricesAndExchangeRates(pex_);
                                }
                                /// @dev Internal fallback function to handle calls to non-existent functions
                                /// @notice This function is called when a transaction is sent to the contract without matching any other function
                                /// @notice It checks if the caller is authorized, enables re-entrancy protection, delegates the call to the admin implementation, and then disables re-entrancy protection
                                /// @notice Only authorized callers (global or dex auth) can trigger this function
                                /// @notice This function uses assembly to perform a delegatecall to the admin implementation to update configs related to DEX
                                function _fallback() private {
                                    if (!(DEX_FACTORY.isGlobalAuth(msg.sender) || DEX_FACTORY.isDexAuth(address(this), msg.sender))) {
                                        revert FluidDexError(ErrorTypes.DexT1__NotAnAuth);
                                    }
                                    uint dexVariables_ = dexVariables;
                                    if (dexVariables_ & 1 == 1) revert FluidDexError(ErrorTypes.DexT1__AlreadyEntered);
                                    // enabling re-entrancy
                                    dexVariables = dexVariables_ | 1;
                                    // Delegate the current call to `ADMIN_IMPLEMENTATION`.
                                    _spell(ADMIN_IMPLEMENTATION, msg.data);
                                    // disabling re-entrancy
                                    // directly fetching from storage so updates from Admin module will get auto covered
                                    dexVariables = dexVariables & ~uint(1);
                                }
                                fallback() external payable {
                                    _fallback();
                                }
                                receive() external payable {
                                    if (msg.sig != 0x00000000) {
                                        _fallback();
                                    }
                                }
                                /// @notice returns all Vault constants
                                function constantsView() external view returns (ConstantViews memory constantsView_) {
                                    constantsView_.dexId = DEX_ID;
                                    constantsView_.liquidity = address(LIQUIDITY);
                                    constantsView_.factory = address(DEX_FACTORY);
                                    constantsView_.token0 = TOKEN_0;
                                    constantsView_.token1 = TOKEN_1;
                                    constantsView_.implementations.shift = SHIFT_IMPLEMENTATION;
                                    constantsView_.implementations.admin = ADMIN_IMPLEMENTATION;
                                    constantsView_.implementations.colOperations = COL_OPERATIONS_IMPLEMENTATION;
                                    constantsView_.implementations.debtOperations = DEBT_OPERATIONS_IMPLEMENTATION;
                                    constantsView_.implementations.perfectOperationsAndSwapOut = PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION;
                                    constantsView_.deployerContract = DEPLOYER_CONTRACT;
                                    constantsView_.supplyToken0Slot = SUPPLY_TOKEN_0_SLOT;
                                    constantsView_.borrowToken0Slot = BORROW_TOKEN_0_SLOT;
                                    constantsView_.supplyToken1Slot = SUPPLY_TOKEN_1_SLOT;
                                    constantsView_.borrowToken1Slot = BORROW_TOKEN_1_SLOT;
                                    constantsView_.exchangePriceToken0Slot = EXCHANGE_PRICE_TOKEN_0_SLOT;
                                    constantsView_.exchangePriceToken1Slot = EXCHANGE_PRICE_TOKEN_1_SLOT;
                                    constantsView_.oracleMapping = TOTAL_ORACLE_MAPPING;
                                }
                                /// @notice returns all Vault constants
                                function constantsView2() external view returns (ConstantViews2 memory constantsView2_) {
                                    constantsView2_.token0NumeratorPrecision = TOKEN_0_NUMERATOR_PRECISION;
                                    constantsView2_.token0DenominatorPrecision = TOKEN_0_DENOMINATOR_PRECISION;
                                    constantsView2_.token1NumeratorPrecision = TOKEN_1_NUMERATOR_PRECISION;
                                    constantsView2_.token1DenominatorPrecision = TOKEN_1_DENOMINATOR_PRECISION;
                                }
                                /// @notice Calculates the real and imaginary reserves for collateral tokens
                                /// @dev This function retrieves the supply of both tokens from the liquidity layer,
                                ///      adjusts them based on exchange prices, and calculates imaginary reserves
                                ///      based on the geometric mean and price range
                                /// @param geometricMean_ The geometric mean of the token prices
                                /// @param upperRange_ The upper price range
                                /// @param lowerRange_ The lower price range
                                /// @param token0SupplyExchangePrice_ The exchange price for token0 from liquidity layer
                                /// @param token1SupplyExchangePrice_ The exchange price for token1 from liquidity layer
                                /// @return c_ A struct containing the calculated real and imaginary reserves for both tokens:
                                ///         - token0RealReserves: The real reserves of token0
                                ///         - token1RealReserves: The real reserves of token1
                                ///         - token0ImaginaryReserves: The imaginary reserves of token0
                                ///         - token1ImaginaryReserves: The imaginary reserves of token1
                                function getCollateralReserves(
                                    uint geometricMean_,
                                    uint upperRange_,
                                    uint lowerRange_,
                                    uint token0SupplyExchangePrice_,
                                    uint token1SupplyExchangePrice_
                                ) public view returns (CollateralReserves memory c_) {
                                    return
                                        _getCollateralReserves(
                                            geometricMean_,
                                            upperRange_,
                                            lowerRange_,
                                            token0SupplyExchangePrice_,
                                            token1SupplyExchangePrice_
                                        );
                                }
                                /// @notice Calculates the debt reserves for both tokens
                                /// @param geometricMean_ The geometric mean of the upper and lower price ranges
                                /// @param upperRange_ The upper price range
                                /// @param lowerRange_ The lower price range
                                /// @param token0BorrowExchangePrice_ The exchange price of token0 from liquidity layer
                                /// @param token1BorrowExchangePrice_ The exchange price of token1 from liquidity layer
                                /// @return d_ The calculated debt reserves for both tokens, containing:
                                ///         - token0Debt: The debt amount of token0
                                ///         - token1Debt: The debt amount of token1
                                ///         - token0RealReserves: The real reserves of token0 derived from token1 debt
                                ///         - token1RealReserves: The real reserves of token1 derived from token0 debt
                                ///         - token0ImaginaryReserves: The imaginary debt reserves of token0
                                ///         - token1ImaginaryReserves: The imaginary debt reserves of token1
                                function getDebtReserves(
                                    uint geometricMean_,
                                    uint upperRange_,
                                    uint lowerRange_,
                                    uint token0BorrowExchangePrice_,
                                    uint token1BorrowExchangePrice_
                                ) public view returns (DebtReserves memory d_) {
                                    return
                                        _getDebtReserves(
                                            geometricMean_,
                                            upperRange_,
                                            lowerRange_,
                                            token0BorrowExchangePrice_,
                                            token1BorrowExchangePrice_
                                        );
                                }
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            abstract contract Events {
                                /// @notice Emitted on token swaps
                                /// @param swap0to1 Indicates whether the swap is from token0 to token1 or vice-versa.
                                /// @param amountIn The amount of tokens to be sent to the vault to swap.
                                /// @param amountOut The amount of tokens user got from the swap.
                                /// @param to Recepient of swapped tokens.
                                event Swap(bool swap0to1, uint256 amountIn, uint256 amountOut, address to);
                                /// @notice Emitted when liquidity is added with shares specified.
                                /// @param shares Expected exact shares to be received.
                                /// @param token0Amt Amount of token0 deposited.
                                /// @param token0Amt Amount of token1 deposited.
                                event LogDepositPerfectColLiquidity(uint shares, uint token0Amt, uint token1Amt);
                                /// @notice Emitted when liquidity is withdrawn with shares specified.
                                /// @param shares shares burned
                                /// @param token0Amt Amount of token0 withdrawn.
                                /// @param token1Amt Amount of token1 withdrawn.
                                event LogWithdrawPerfectColLiquidity(uint shares, uint token0Amt, uint token1Amt);
                                /// @notice Emitted when liquidity is borrowed with shares specified.
                                /// @param shares shares minted
                                /// @param token0Amt Amount of token0 borrowed.
                                /// @param token1Amt Amount of token1 borrowed.
                                event LogBorrowPerfectDebtLiquidity(uint shares, uint token0Amt, uint token1Amt);
                                /// @notice Emitted when liquidity is paid back with shares specified.
                                /// @param shares shares burned
                                /// @param token0Amt Amount of token0 paid back.
                                /// @param token1Amt Amount of token1 paid back.
                                event LogPaybackPerfectDebtLiquidity(uint shares, uint token0Amt, uint token1Amt);
                                /// @notice Emitted when liquidity is deposited with specified token0 & token1 amount
                                /// @param amount0 Amount of token0 deposited.
                                /// @param amount1 Amount of token1 deposited.
                                /// @param shares Amount of shares minted.
                                event LogDepositColLiquidity(uint amount0, uint amount1, uint shares);
                                /// @notice Emitted when liquidity is withdrawn with specified token0 & token1 amount
                                /// @param amount0 Amount of token0 withdrawn.
                                /// @param amount1 Amount of token1 withdrawn.
                                /// @param shares Amount of shares burned.
                                event LogWithdrawColLiquidity(uint amount0, uint amount1, uint shares);
                                /// @notice Emitted when liquidity is borrowed with specified token0 & token1 amount
                                /// @param amount0 Amount of token0 borrowed.
                                /// @param amount1 Amount of token1 borrowed.
                                /// @param shares Amount of shares minted.
                                event LogBorrowDebtLiquidity(uint amount0, uint amount1, uint shares);
                                /// @notice Emitted when liquidity is paid back with specified token0 & token1 amount
                                /// @param amount0 Amount of token0 paid back.
                                /// @param amount1 Amount of token1 paid back.
                                /// @param shares Amount of shares burned.
                                event LogPaybackDebtLiquidity(uint amount0, uint amount1, uint shares);
                                /// @notice Emitted when liquidity is withdrawn with shares specified into one token only.
                                /// @param shares shares burned
                                /// @param token0Amt Amount of token0 withdrawn.
                                /// @param token1Amt Amount of token1 withdrawn.
                                event LogWithdrawColInOneToken(uint shares, uint token0Amt, uint token1Amt);
                                /// @notice Emitted when liquidity is paid back with shares specified from one token only.
                                /// @param shares shares burned
                                /// @param token0Amt Amount of token0 paid back.
                                /// @param token1Amt Amount of token1 paid back.
                                event LogPaybackDebtInOneToken(uint shares, uint token0Amt, uint token1Amt);
                                /// @notice Emitted when internal arbitrage between 2 pools happen
                                /// @param routing if positive then routing is amtIn of token0 in deposit & borrow else token0 withdraw & payback
                                /// @param amtOut if routing is positive then token1 withdraw & payback amount else token1 deposit & borrow
                                event LogArbitrage(int routing, uint amtOut);
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol";
                            import { Variables } from "../../common/variables.sol";
                            import { ImmutableVariables } from "../immutableVariables.sol";
                            import { Events } from "../events.sol";
                            import { ErrorTypes } from "../../../errorTypes.sol";
                            import { IHook, ICenterPrice } from "../interfaces.sol";
                            import { LiquiditySlotsLink } from "../../../../../libraries/liquiditySlotsLink.sol";
                            import { LiquidityCalcs } from "../../../../../libraries/liquidityCalcs.sol";
                            import { DexSlotsLink } from "../../../../../libraries/dexSlotsLink.sol";
                            import { DexCalcs } from "../../../../../libraries/dexCalcs.sol";
                            import { BigMathMinified } from "../../../../../libraries/bigMathMinified.sol";
                            import { AddressCalcs } from "../../../../../libraries/addressCalcs.sol";
                            interface IShifting {
                                /// @dev Calculates the new upper and lower range values during an active range shift
                                /// @param upperRange_ The target upper range value
                                /// @param lowerRange_ The target lower range value
                                /// @param dexVariables2_ needed in case shift is ended and we need to update dexVariables2
                                /// @return The updated upper range, lower range, and dexVariables2
                                function _calcRangeShifting(
                                    uint upperRange_,
                                    uint lowerRange_,
                                    uint dexVariables2_
                                ) external payable returns (uint, uint, uint);
                                /// @dev Calculates the new threshold values during an active threshold shift
                                /// @param upperThreshold_ The target upper threshold value
                                /// @param lowerThreshold_ The target lower threshold value
                                /// @param dexVariables2_ needed in case shift is ended and we need to update dexVariables2
                                /// @return The updated upper threshold, lower threshold, and dexVariables2
                                function _calcThresholdShifting(
                                    uint upperThreshold_,
                                    uint lowerThreshold_,
                                    uint dexVariables2_
                                ) external payable returns (uint, uint, uint);
                                /// @dev Calculates the new center price during an active center price shift
                                /// @param dexVariables_ The current state of dex variables
                                /// @param dexVariables2_ Additional dex variables
                                /// @return The updated center price
                                function _calcCenterPrice(
                                    uint dexVariables_,
                                    uint dexVariables2_
                                ) external payable returns (uint);
                            }
                            abstract contract CoreHelpers is Variables, ImmutableVariables, Events {
                                using BigMathMinified for uint256;
                                /// @dev            do any arbitrary call
                                /// @param target_  Address to which the call needs to be delegated
                                /// @param data_    Data to execute at the delegated address
                                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)
                                        if iszero(succeeded) {
                                            // throw if delegatecall failed
                                            returndatacopy(0x00, 0x00, size)
                                            revert(0x00, size)
                                        }
                                    }
                                }
                                /// @dev Given an input amount of asset and pair reserves, returns the maximum output amount of the other asset
                                /// @param amountIn_ The amount of input asset.
                                /// @param iReserveIn_ Imaginary token reserve with input amount.
                                /// @param iReserveOut_ Imaginary token reserve of output amount.
                                function _getAmountOut(
                                    uint256 amountIn_,
                                    uint iReserveIn_,
                                    uint iReserveOut_
                                ) internal pure returns (uint256 amountOut_) {
                                    unchecked {
                                        // Both numerator and denominator are scaled to 1e6 to factor in fee scaling.
                                        uint256 numerator_ = amountIn_ * iReserveOut_;
                                        uint256 denominator_ = iReserveIn_ + amountIn_;
                                        // Using the swap formula: (AmountIn * iReserveY) / (iReserveX + AmountIn)
                                        amountOut_ = numerator_ / denominator_;
                                    }
                                }
                                /// @dev Given an output amount of asset and pair reserves, returns the input amount of the other asset
                                /// @param amountOut_ Desired output amount of the asset.
                                /// @param iReserveIn_ Imaginary token reserve of input amount.
                                /// @param iReserveOut_ Imaginary token reserve of output amount.
                                function _getAmountIn(
                                    uint256 amountOut_,
                                    uint iReserveIn_,
                                    uint iReserveOut_
                                ) internal pure returns (uint256 amountIn_) {
                                    // Both numerator and denominator are scaled to 1e6 to factor in fee scaling.
                                    uint256 numerator_ = amountOut_ * iReserveIn_;
                                    uint256 denominator_ = iReserveOut_ - amountOut_;
                                    // Using the swap formula: (AmountOut * iReserveX) / (iReserveY - AmountOut)
                                    amountIn_ = numerator_ / denominator_;
                                }
                                /// @param t total amount in
                                /// @param x imaginary reserves of token out of collateral
                                /// @param y imaginary reserves of token in of collateral
                                /// @param x2 imaginary reserves of token out of debt
                                /// @param y2 imaginary reserves of token in of debt
                                /// @return a_ how much swap should go through collateral pool. Remaining will go from debt
                                /// note if a < 0 then entire trade route through debt pool and debt pool arbitrage with col pool
                                /// note if a > t then entire trade route through col pool and col pool arbitrage with debt pool
                                /// note if a > 0 & a < t then swap will route through both pools
                                function _swapRoutingIn(uint t, uint x, uint y, uint x2, uint y2) internal pure returns (int a_) {
                                    // Main equations:
                                    // 1. out = x * a / (y + a)
                                    // 2. out2 = x2 * (t - a) / (y2 + (t - a))
                                    // final price should be same
                                    // 3. (y + a) / (x - out) = (y2 + (t - a)) / (x2 - out2)
                                    // derivation: https://chatgpt.com/share/dce6f381-ee5f-4d5f-b6ea-5996e84d5b57
                                    // adding 1e18 precision
                                    uint xyRoot_ = FixedPointMathLib.sqrt(x * y * 1e18);
                                    uint x2y2Root_ = FixedPointMathLib.sqrt(x2 * y2 * 1e18);
                                    a_ = (int(y2 * xyRoot_ + t * xyRoot_) - int(y * x2y2Root_)) / int(xyRoot_ + x2y2Root_);
                                }
                                /// @param t total amount out
                                /// @param x imaginary reserves of token in of collateral
                                /// @param y imaginary reserves of token out of collateral
                                /// @param x2 imaginary reserves of token in of debt
                                /// @param y2 imaginary reserves of token out of debt
                                /// @return a_ how much swap should go through collateral pool. Remaining will go from debt
                                /// note if a < 0 then entire trade route through debt pool and debt pool arbitrage with col pool
                                /// note if a > t then entire trade route through col pool and col pool arbitrage with debt pool
                                /// note if a > 0 & a < t then swap will route through both pools
                                function _swapRoutingOut(uint t, uint x, uint y, uint x2, uint y2) internal pure returns (int a_) {
                                    // Main equations:
                                    // 1. in = (x * a) / (y - a)
                                    // 2. in2 = (x2 * (t - a)) / (y2 - (t - a))
                                    // final price should be same
                                    // 3. (y - a) / (x + in) = (y2 - (t - a)) / (x2 + in2)
                                    // derivation: https://chatgpt.com/share/6585bc28-841f-49ec-aea2-1e5c5b7f4fa9
                                    // adding 1e18 precision
                                    uint xyRoot_ = FixedPointMathLib.sqrt(x * y * 1e18);
                                    uint x2y2Root_ = FixedPointMathLib.sqrt(x2 * y2 * 1e18);
                                    // 1e18 precision gets cancelled out in division
                                    a_ = (int(t * xyRoot_ + y * x2y2Root_) - int(y2 * xyRoot_)) / int(xyRoot_ + x2y2Root_);
                                }
                                function _utilizationVerify(uint utilizationLimit_, bytes32 exchangePriceSlot_) internal view {
                                    if (utilizationLimit_ < THREE_DECIMALS) {
                                        utilizationLimit_ = utilizationLimit_ * 10;
                                        // extracting utilization of token from liquidity layer
                                        uint liquidityLayerUtilization_ = LIQUIDITY.readFromStorage(exchangePriceSlot_);
                                        liquidityLayerUtilization_ =
                                            (liquidityLayerUtilization_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) &
                                            X14;
                                        // Note: this can go slightly above the utilization limit if no update is written to storage at liquidity layer
                                        // if swap was not big enough to go far enough above or any other storage update threshold write cause there
                                        // so just to keep in mind when configuring the actual limit reachable can be utilizationLimit_ + storageUpdateThreshold at Liquidity
                                        if (liquidityLayerUtilization_ > utilizationLimit_)
                                            revert FluidDexError(ErrorTypes.DexT1__LiquidityLayerTokenUtilizationCapReached);
                                    }
                                }
                                function _check(uint dexVariables_, uint dexVariables2_) internal {
                                    if (dexVariables_ & 1 == 1) revert FluidDexError(ErrorTypes.DexT1__AlreadyEntered);
                                    if (dexVariables2_ & 3 == 0) revert FluidDexError(ErrorTypes.DexT1__PoolNotInitialized);
                                    // enabling re-entrancy
                                    dexVariables = dexVariables_ | 1;
                                }
                                /// @dev if token0 reserves are too low w.r.t token1 then revert, this is to avoid edge case scenario and making sure that precision on calculations should be high enough
                                function _verifyToken0Reserves(
                                    uint token0Reserves_,
                                    uint token1Reserves_,
                                    uint centerPrice_,
                                    uint minLiquidity_
                                ) internal pure {
                                    if (((token0Reserves_) < ((token1Reserves_ * 1e27) / (centerPrice_ * minLiquidity_)))) {
                                        revert FluidDexError(ErrorTypes.DexT1__TokenReservesTooLow);
                                    }
                                }
                                /// @dev if token1 reserves are too low w.r.t token0 then revert, this is to avoid edge case scenario and making sure that precision on calculations should be high enough
                                function _verifyToken1Reserves(
                                    uint token0Reserves_,
                                    uint token1Reserves_,
                                    uint centerPrice_,
                                    uint minLiquidity_
                                ) internal pure {
                                    if (((token1Reserves_) < ((token0Reserves_ * centerPrice_) / (1e27 * minLiquidity_)))) {
                                        revert FluidDexError(ErrorTypes.DexT1__TokenReservesTooLow);
                                    }
                                }
                                function _verifySwapAndNonPerfectActions(uint amountAdjusted_, uint amount_) internal pure {
                                    // after shifting amount should not become 0
                                    // limiting to six decimals which means in case of USDC, USDT it's 1 wei, for WBTC 100 wei, for ETH 1000 gwei
                                    if (amountAdjusted_ < SIX_DECIMALS || amountAdjusted_ > X96 || amount_ < TWO_DECIMALS || amount_ > X128)
                                        revert FluidDexError(ErrorTypes.DexT1__LimitingAmountsSwapAndNonPerfectActions);
                                }
                                /// @dev Calculates the new upper and lower range values during an active range shift
                                /// @param upperRange_ The target upper range value
                                /// @param lowerRange_ The target lower range value
                                /// @param dexVariables2_ needed in case shift is ended and we need to update dexVariables2
                                /// @return The updated upper range, lower range, and dexVariables2
                                /// @notice This function handles the gradual shifting of range values over time
                                /// @notice If the shift is complete, it updates the state and clears the shift data
                                function _calcRangeShifting(
                                    uint upperRange_,
                                    uint lowerRange_,
                                    uint dexVariables2_
                                ) internal returns (uint, uint, uint) {
                                    return
                                        abi.decode(
                                            _spell(
                                                SHIFT_IMPLEMENTATION,
                                                abi.encodeWithSelector(
                                                    IShifting._calcRangeShifting.selector,
                                                    upperRange_,
                                                    lowerRange_,
                                                    dexVariables2_
                                                )
                                            ),
                                            (uint, uint, uint)
                                        );
                                }
                                /// @dev Calculates the new upper and lower threshold values during an active threshold shift
                                /// @param upperThreshold_ The target upper threshold value
                                /// @param lowerThreshold_ The target lower threshold value
                                /// @param thresholdTime_ The time passed since shifting started
                                /// @return The updated upper threshold, lower threshold, and threshold time
                                /// @notice This function handles the gradual shifting of threshold values over time
                                /// @notice If the shift is complete, it updates the state and clears the shift data
                                function _calcThresholdShifting(
                                    uint upperThreshold_,
                                    uint lowerThreshold_,
                                    uint thresholdTime_
                                ) internal returns (uint, uint, uint) {
                                    return
                                        abi.decode(
                                            _spell(
                                                SHIFT_IMPLEMENTATION,
                                                abi.encodeWithSelector(
                                                    IShifting._calcThresholdShifting.selector,
                                                    upperThreshold_,
                                                    lowerThreshold_,
                                                    thresholdTime_
                                                )
                                            ),
                                            (uint, uint, uint)
                                        );
                                }
                                /// @dev Calculates the new center price during an active price shift
                                /// @param dexVariables_ The current state of dex variables
                                /// @param dexVariables2_ Additional dex variables
                                /// @return newCenterPrice_ The updated center price
                                /// @notice This function gradually shifts the center price towards a new target price over time
                                /// @notice It uses an external price source (via ICenterPrice) to determine the target price
                                /// @notice The shift continues until the current price reaches the target, or the shift duration ends
                                /// @notice Once the shift is complete, it updates the state and clears the shift data
                                /// @notice The shift rate is dynamic and depends on:
                                /// @notice - Time remaining in the shift duration
                                /// @notice - The new center price (fetched externally, which may change)
                                /// @notice - The current (old) center price
                                /// @notice This results in a fuzzy shifting mechanism where the rate can change as these parameters evolve
                                /// @notice The externally fetched new center price is expected to not differ significantly from the last externally fetched center price
                                function _calcCenterPrice(uint dexVariables_, uint dexVariables2_) internal returns (uint newCenterPrice_) {
                                    return
                                        abi.decode(
                                            _spell(
                                                SHIFT_IMPLEMENTATION,
                                                abi.encodeWithSelector(IShifting._calcCenterPrice.selector, dexVariables_, dexVariables2_)
                                            ),
                                            (uint)
                                        );
                                }
                                /// @notice Calculates and returns the current prices and exchange prices for the pool
                                /// @param dexVariables_ The first set of DEX variables containing various pool parameters
                                /// @param dexVariables2_ The second set of DEX variables containing additional pool parameters
                                /// @return pex_ A struct containing the calculated prices and exchange prices:
                                ///         - pex_.lastStoredPrice: The last stored price in 1e27 decimals
                                ///         - pex_.centerPrice: The calculated or fetched center price in 1e27 decimals
                                ///         - pex_.upperRange: The upper range price limit in 1e27 decimals
                                ///         - pex_.lowerRange: The lower range price limit in 1e27 decimals
                                ///         - pex_.geometricMean: The geometric mean of upper range & lower range in 1e27 decimals
                                ///         - pex_.supplyToken0ExchangePrice: The current exchange price for supplying token0
                                ///         - pex_.borrowToken0ExchangePrice: The current exchange price for borrowing token0
                                ///         - pex_.supplyToken1ExchangePrice: The current exchange price for supplying token1
                                ///         - pex_.borrowToken1ExchangePrice: The current exchange price for borrowing token1
                                /// @dev This function performs the following operations:
                                ///      1. Determines the center price (either from storage, external source, or calculated)
                                ///      2. Retrieves the last stored price from dexVariables_
                                ///      3. Calculates the upper and lower range prices based on the center price and range percentages
                                ///      4. Checks if rebalancing is needed based on threshold settings
                                ///      5. Adjusts prices if necessary based on the time elapsed and threshold conditions
                                ///      6. Update the dexVariables2_ if changes were made
                                ///      7. Returns the calculated prices and exchange prices in the PricesAndExchangePrice struct
                                function _getPricesAndExchangePrices(
                                    uint dexVariables_,
                                    uint dexVariables2_
                                ) internal returns (PricesAndExchangePrice memory pex_) {
                                    uint centerPrice_;
                                    if (((dexVariables2_ >> 248) & 1) == 0) {
                                        // centerPrice_ => center price hook
                                        centerPrice_ = (dexVariables2_ >> 112) & X30;
                                        if (centerPrice_ == 0) {
                                            centerPrice_ = (dexVariables_ >> 81) & X40;
                                            centerPrice_ = (centerPrice_ >> DEFAULT_EXPONENT_SIZE) << (centerPrice_ & DEFAULT_EXPONENT_MASK);
                                        } else {
                                            // center price should be fetched from external source. For exmaple, in case of wstETH <> ETH pool,
                                            // we would want the center price to be pegged to wstETH exchange rate into ETH
                                            centerPrice_ = ICenterPrice(AddressCalcs.addressCalc(DEPLOYER_CONTRACT, centerPrice_)).centerPrice();
                                        }
                                    } else {
                                        // an active centerPrice_ shift is going on
                                        centerPrice_ = _calcCenterPrice(dexVariables_, dexVariables2_);
                                    }
                                    uint lastStoredPrice_ = (dexVariables_ >> 41) & X40;
                                    lastStoredPrice_ = (lastStoredPrice_ >> DEFAULT_EXPONENT_SIZE) << (lastStoredPrice_ & DEFAULT_EXPONENT_MASK);
                                    uint upperRange_ = ((dexVariables2_ >> 27) & X20);
                                    uint lowerRange_ = ((dexVariables2_ >> 47) & X20);
                                    if (((dexVariables2_ >> 26) & 1) == 1) {
                                        // an active range shift is going on
                                        (upperRange_, lowerRange_, dexVariables2_) = _calcRangeShifting(upperRange_, lowerRange_, dexVariables2_);
                                    }
                                    unchecked {
                                        // adding into unchecked because upperRange_ & lowerRange_ can only be > 0 & < SIX_DECIMALS
                                        // 1% = 1e4, 100% = 1e6
                                        upperRange_ = (centerPrice_ * SIX_DECIMALS) / (SIX_DECIMALS - upperRange_);
                                        // 1% = 1e4, 100% = 1e6
                                        lowerRange_ = (centerPrice_ * (SIX_DECIMALS - lowerRange_)) / SIX_DECIMALS;
                                    }
                                    bool changed_;
                                    {
                                        // goal will be to keep threshold percents 0 if center price is fetched from external source
                                        // checking if threshold are set non 0 then only rebalancing is on
                                        if (((dexVariables2_ >> 68) & X20) > 0) {
                                            uint upperThreshold_ = (dexVariables2_ >> 68) & X10;
                                            uint lowerThreshold_ = (dexVariables2_ >> 78) & X10;
                                            uint shiftingTime_ = (dexVariables2_ >> 88) & X24;
                                            if (((dexVariables2_ >> 67) & 1) == 1) {
                                                // if active shift is going on for threshold then calculate threshold real time
                                                (upperThreshold_, lowerThreshold_, shiftingTime_) = _calcThresholdShifting(
                                                    upperThreshold_,
                                                    lowerThreshold_,
                                                    shiftingTime_
                                                );
                                            }
                                            unchecked {
                                                if (
                                                    lastStoredPrice_ >
                                                    (centerPrice_ +
                                                        ((upperRange_ - centerPrice_) * (THREE_DECIMALS - upperThreshold_)) /
                                                        THREE_DECIMALS)
                                                ) {
                                                    uint timeElapsed_ = block.timestamp - ((dexVariables_ >> 121) & X33);
                                                    // price shifting towards upper range
                                                    if (timeElapsed_ < shiftingTime_) {
                                                        centerPrice_ = centerPrice_ + ((upperRange_ - centerPrice_) * timeElapsed_) / shiftingTime_;
                                                    } else {
                                                        // 100% price shifted
                                                        centerPrice_ = upperRange_;
                                                    }
                                                    changed_ = true;
                                                } else if (
                                                    lastStoredPrice_ <
                                                    (centerPrice_ -
                                                        ((centerPrice_ - lowerRange_) * (THREE_DECIMALS - lowerThreshold_)) /
                                                        THREE_DECIMALS)
                                                ) {
                                                    uint timeElapsed_ = block.timestamp - ((dexVariables_ >> 121) & X33);
                                                    // price shifting towards lower range
                                                    if (timeElapsed_ < shiftingTime_) {
                                                        centerPrice_ = centerPrice_ - ((centerPrice_ - lowerRange_) * timeElapsed_) / shiftingTime_;
                                                    } else {
                                                        // 100% price shifted
                                                        centerPrice_ = lowerRange_;
                                                    }
                                                    changed_ = true;
                                                }
                                            }
                                        }
                                    }
                                    // temp_ => max center price
                                    uint temp_ = (dexVariables2_ >> 172) & X28;
                                    temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                                    if (centerPrice_ > temp_) {
                                        // if center price is greater than max center price
                                        centerPrice_ = temp_;
                                        changed_ = true;
                                    } else {
                                        // check if center price is less than min center price
                                        // temp_ => min center price
                                        temp_ = (dexVariables2_ >> 200) & X28;
                                        temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                                        if (centerPrice_ < temp_) {
                                            centerPrice_ = temp_;
                                            changed_ = true;
                                        }
                                    }
                                    // if centerPrice_ is changed then calculating upper and lower range again
                                    if (changed_) {
                                        upperRange_ = ((dexVariables2_ >> 27) & X20);
                                        lowerRange_ = ((dexVariables2_ >> 47) & X20);
                                        if (((dexVariables2_ >> 26) & 1) == 1) {
                                            (upperRange_, lowerRange_, dexVariables2_) = _calcRangeShifting(
                                                upperRange_,
                                                lowerRange_,
                                                dexVariables2_
                                            );
                                        }
                                        unchecked {
                                            // adding into unchecked because upperRange_ & lowerRange_ can only be > 0 & < SIX_DECIMALS
                                            // 1% = 1e4, 100% = 1e6
                                            upperRange_ = (centerPrice_ * SIX_DECIMALS) / (SIX_DECIMALS - upperRange_);
                                            // 1% = 1e4, 100% = 1e6
                                            lowerRange_ = (centerPrice_ * (SIX_DECIMALS - lowerRange_)) / SIX_DECIMALS;
                                        }
                                    }
                                    pex_.lastStoredPrice = lastStoredPrice_;
                                    pex_.centerPrice = centerPrice_;
                                    pex_.upperRange = upperRange_;
                                    pex_.lowerRange = lowerRange_;
                                    unchecked {
                                        if (upperRange_ < 1e38) {
                                            // 1e38 * 1e38 = 1e76 which is less than max uint limit
                                            pex_.geometricMean = FixedPointMathLib.sqrt(upperRange_ * lowerRange_);
                                        } else {
                                            // upperRange_ price is pretty large hence lowerRange_ will also be pretty large
                                            pex_.geometricMean = FixedPointMathLib.sqrt((upperRange_ / 1e18) * (lowerRange_ / 1e18)) * 1e18;
                                        }
                                    }
                                    // Exchange price will remain same as Liquidity Layer
                                    (pex_.supplyToken0ExchangePrice, pex_.borrowToken0ExchangePrice) = LiquidityCalcs.calcExchangePrices(
                                        LIQUIDITY.readFromStorage(EXCHANGE_PRICE_TOKEN_0_SLOT)
                                    );
                                    (pex_.supplyToken1ExchangePrice, pex_.borrowToken1ExchangePrice) = LiquidityCalcs.calcExchangePrices(
                                        LIQUIDITY.readFromStorage(EXCHANGE_PRICE_TOKEN_1_SLOT)
                                    );
                                }
                                /// @dev getting reserves outside range.
                                /// @param gp_ is geometric mean pricing of upper percent & lower percent
                                /// @param pa_ price of upper range or lower range
                                /// @param rx_ real reserves of token0 or token1
                                /// @param ry_ whatever is rx_ the other will be ry_
                                function _calculateReservesOutsideRange(
                                    uint gp_,
                                    uint pa_,
                                    uint rx_,
                                    uint ry_
                                ) internal pure returns (uint xa_, uint yb_) {
                                    // equations we have:
                                    // 1. x*y = k
                                    // 2. xa*ya = k
                                    // 3. xb*yb = k
                                    // 4. Pa = ya / xa = upperRange_ (known)
                                    // 5. Pb = yb / xb = lowerRange_ (known)
                                    // 6. x - xa = rx = real reserve of x (known)
                                    // 7. y - yb = ry = real reserve of y (known)
                                    // With solving we get:
                                    // ((Pa*Pb)^(1/2) - Pa)*xa^2 + (rx * (Pa*Pb)^(1/2) + ry)*xa + rx*ry = 0
                                    // yb = yb = xa * (Pa * Pb)^(1/2)
                                    // xa = (GP⋅rx + ry + (-rx⋅ry⋅4⋅(GP - Pa) + (GP⋅rx + ry)^2)^0.5) / (2Pa - 2GP)
                                    // multiply entire equation by 1e27 to remove the price decimals precision of 1e27
                                    // xa = (GP⋅rx + ry⋅1e27 + (rx⋅ry⋅4⋅(Pa - GP)⋅1e27 + (GP⋅rx + ry⋅1e27)^2)^0.5) / 2*(Pa - GP)
                                    // dividing the equation with 2*(Pa - GP). Pa is always > GP so answer will be positive.
                                    // xa = (((GP⋅rx + ry⋅1e27) / 2*(Pa - GP)) + (((rx⋅ry⋅4⋅(Pa - GP)⋅1e27) / 4*(Pa - GP)^2) + ((GP⋅rx + ry⋅1e27) / 2*(Pa - GP))^2)^0.5)
                                    // xa = (((GP⋅rx + ry⋅1e27) / 2*(Pa - GP)) + (((rx⋅ry⋅1e27) / (Pa - GP)) + ((GP⋅rx + ry⋅1e27) / 2*(Pa - GP))^2)^0.5)
                                    // dividing in 3 parts for simplification:
                                    // part1 = (Pa - GP)
                                    // part2 = (GP⋅rx + ry⋅1e27) / (2*part1)
                                    // part3 = rx⋅ry
                                    // note: part1 will almost always be < 1e28 but in case it goes above 1e27 then it's extremely unlikely it'll go above > 1e29
                                    uint p1_ = pa_ - gp_;
                                    uint p2_ = ((gp_ * rx_) + (ry_ * 1e27)) / (2 * p1_);
                                    uint p3_ = rx_ * ry_;
                                    // to avoid overflowing
                                    p3_ = (p3_ < 1e50) ? ((p3_ * 1e27) / p1_) : (p3_ / p1_) * 1e27;
                                    // xa = part2 + (part3 + (part2 * part2))^(1/2)
                                    // yb = xa_ * gp_
                                    xa_ = p2_ + FixedPointMathLib.sqrt((p3_ + (p2_ * p2_)));
                                    yb_ = (xa_ * gp_) / 1e27;
                                }
                                /// @dev Retrieves collateral amount from liquidity layer for a given token
                                /// @param supplyTokenSlot_ The storage slot for the supply token data
                                /// @param tokenExchangePrice_ The exchange price of the token
                                /// @param isToken0_ Boolean indicating if the token is token0 (true) or token1 (false)
                                /// @return tokenSupply_ The calculated liquidity collateral amount
                                function _getLiquidityCollateral(
                                    bytes32 supplyTokenSlot_,
                                    uint tokenExchangePrice_,
                                    bool isToken0_
                                ) internal view returns (uint tokenSupply_) {
                                    uint tokenSupplyData_ = LIQUIDITY.readFromStorage(supplyTokenSlot_);
                                    tokenSupply_ = (tokenSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) & X64;
                                    tokenSupply_ = (tokenSupply_ >> DEFAULT_EXPONENT_SIZE) << (tokenSupply_ & DEFAULT_EXPONENT_MASK);
                                    if (tokenSupplyData_ & 1 == 1) {
                                        // supply with interest is on
                                        unchecked {
                                            tokenSupply_ = (tokenSupply_ * tokenExchangePrice_) / LiquidityCalcs.EXCHANGE_PRICES_PRECISION;
                                        }
                                    }
                                    unchecked {
                                        tokenSupply_ = isToken0_
                                            ? ((tokenSupply_ * TOKEN_0_NUMERATOR_PRECISION) / TOKEN_0_DENOMINATOR_PRECISION)
                                            : ((tokenSupply_ * TOKEN_1_NUMERATOR_PRECISION) / TOKEN_1_DENOMINATOR_PRECISION);
                                    }
                                }
                                /// @notice Calculates the real and imaginary reserves for collateral tokens
                                /// @dev This function retrieves the supply of both tokens from the liquidity layer,
                                ///      adjusts them based on exchange prices, and calculates imaginary reserves
                                ///      based on the geometric mean and price range
                                /// @param geometricMean_ The geometric mean of the token prices
                                /// @param upperRange_ The upper price range
                                /// @param lowerRange_ The lower price range
                                /// @param token0SupplyExchangePrice_ The exchange price for token0 from liquidity layer
                                /// @param token1SupplyExchangePrice_ The exchange price for token1 from liquidity layer
                                /// @return c_ A struct containing the calculated real and imaginary reserves for both tokens:
                                ///         - token0RealReserves: The real reserves of token0
                                ///         - token1RealReserves: The real reserves of token1
                                ///         - token0ImaginaryReserves: The imaginary reserves of token0
                                ///         - token1ImaginaryReserves: The imaginary reserves of token1
                                function _getCollateralReserves(
                                    uint geometricMean_,
                                    uint upperRange_,
                                    uint lowerRange_,
                                    uint token0SupplyExchangePrice_,
                                    uint token1SupplyExchangePrice_
                                ) internal view returns (CollateralReserves memory c_) {
                                    uint token0Supply_ = _getLiquidityCollateral(SUPPLY_TOKEN_0_SLOT, token0SupplyExchangePrice_, true);
                                    uint token1Supply_ = _getLiquidityCollateral(SUPPLY_TOKEN_1_SLOT, token1SupplyExchangePrice_, false);
                                    if (geometricMean_ < 1e27) {
                                        (c_.token0ImaginaryReserves, c_.token1ImaginaryReserves) = _calculateReservesOutsideRange(
                                            geometricMean_,
                                            upperRange_,
                                            token0Supply_,
                                            token1Supply_
                                        );
                                    } else {
                                        // inversing, something like `xy = k` so for calculation we are making everything related to x into y & y into x
                                        // 1 / geometricMean for new geometricMean
                                        // 1 / lowerRange will become upper range
                                        // 1 / upperRange will become lower range
                                        (c_.token1ImaginaryReserves, c_.token0ImaginaryReserves) = _calculateReservesOutsideRange(
                                            (1e54 / geometricMean_),
                                            (1e54 / lowerRange_),
                                            token1Supply_,
                                            token0Supply_
                                        );
                                    }
                                    c_.token0RealReserves = token0Supply_;
                                    c_.token1RealReserves = token1Supply_;
                                    unchecked {
                                        c_.token0ImaginaryReserves += token0Supply_;
                                        c_.token1ImaginaryReserves += token1Supply_;
                                    }
                                }
                                /// @notice Calculates the real and imaginary debt reserves for both tokens
                                /// @dev This function uses a quadratic equation to determine the debt reserves
                                ///      based on the geometric mean price and the current debt amounts
                                /// @param gp_ The geometric mean price of upper range & lower range
                                /// @param pb_ The price of lower range
                                /// @param dx_ The debt amount of one token
                                /// @param dy_ The debt amount of the other token
                                /// @return rx_ The real debt reserve of the first token
                                /// @return ry_ The real debt reserve of the second token
                                /// @return irx_ The imaginary debt reserve of the first token
                                /// @return iry_ The imaginary debt reserve of the second token
                                function _calculateDebtReserves(
                                    uint gp_,
                                    uint pb_,
                                    uint dx_,
                                    uint dy_
                                ) internal pure returns (uint rx_, uint ry_, uint irx_, uint iry_) {
                                    // Assigning letter to knowns:
                                    // c = debtA
                                    // d = debtB
                                    // e = upperPrice
                                    // f = lowerPrice
                                    // g = upperPrice^1/2
                                    // h = lowerPrice^1/2
                                    // c = 1
                                    // d = 2000
                                    // e = 2222.222222
                                    // f = 1800
                                    // g = 2222.222222^1/2
                                    // h = 1800^1/2
                                    // Assigning letter to unknowns:
                                    // w = realDebtReserveA
                                    // x = realDebtReserveB
                                    // y = imaginaryDebtReserveA
                                    // z = imaginaryDebtReserveB
                                    // k = k
                                    // below quadratic will give answer of realDebtReserveB
                                    // A, B, C of quadratic equation:
                                    // A = h
                                    // B = dh - cfg
                                    // C = -cfdh
                                    // A = lowerPrice^1/2
                                    // B = debtB⋅lowerPrice^1/2 - debtA⋅lowerPrice⋅upperPrice^1/2
                                    // C = -(debtA⋅lowerPrice⋅debtB⋅lowerPrice^1/2)
                                    // x = (cfg − dh + (4cdf(h^2)+(cfg−dh)^2))^(1/2)) / 2h
                                    // simplifying dividing by h, note h = f^1/2
                                    // x = ((c⋅g⋅(f^1/2) − d) / 2 + ((4⋅c⋅d⋅f⋅f) / (4h^2) + ((c⋅f⋅g) / 2h − (d⋅h) / 2h)^2))^(1/2))
                                    // x = ((c⋅g⋅(f^1/2) − d) / 2 + ((c⋅d⋅f) + ((c⋅g⋅(f^1/2) − d) / 2)^2))^(1/2))
                                    // dividing in 3 parts for simplification:
                                    // part1 = (c⋅g⋅(f^1/2) − d) / 2
                                    // part2 = (c⋅d⋅f)
                                    // x = (part1 + (part2 + part1^2)^(1/2))
                                    // note: part1 will almost always be < 1e27 but in case it goes above 1e27 then it's extremely unlikely it'll go above > 1e28
                                    // part1 = ((debtA * upperPrice^1/2 * lowerPrice^1/2) - debtB) / 2
                                    // note: upperPrice^1/2 * lowerPrice^1/2 = geometric mean
                                    // part1 = ((debtA * geometricMean) - debtB) / 2
                                    // part2 = debtA * debtB * lowerPrice
                                    // converting decimals properly as price is in 1e27 decimals
                                    // part1 = ((debtA * geometricMean) - (debtB * 1e27)) / (2 * 1e27)
                                    // part2 = (debtA * debtB * lowerPrice) / 1e27
                                    // final x equals:
                                    // x = (part1 + (part2 + part1^2)^(1/2))
                                    int p1_ = (int(dx_ * gp_) - int(dy_ * 1e27)) / (2 * 1e27);
                                    uint p2_ = (dx_ * dy_);
                                    p2_ = p2_ < 1e50 ? (p2_ * pb_) / 1e27 : (p2_ / 1e27) * pb_;
                                    ry_ = uint(p1_ + int(FixedPointMathLib.sqrt((p2_ + uint(p1_ * p1_)))));
                                    // finding z:
                                    // x^2 - zx + cfz = 0
                                    // z*(x - cf) = x^2
                                    // z = x^2 / (x - cf)
                                    // z = x^2 / (x - debtA * lowerPrice)
                                    // converting decimals properly as price is in 1e27 decimals
                                    // z = (x^2 * 1e27) / ((x * 1e27) - (debtA * lowerPrice))
                                    iry_ = ((ry_ * 1e27) - (dx_ * pb_));
                                    if (iry_ < SIX_DECIMALS) {
                                        // almost impossible situation to ever get here
                                        revert FluidDexError(ErrorTypes.DexT1__DebtReservesTooLow);
                                    }
                                    if (ry_ < 1e25) {
                                        iry_ = (ry_ * ry_ * 1e27) / iry_;
                                    } else {
                                        // note: it can never result in negative as final result will always be in positive
                                        iry_ = (ry_ * ry_) / (iry_ / 1e27);
                                    }
                                    // finding y
                                    // x = z * c / (y + c)
                                    // y + c = z * c / x
                                    // y = (z * c / x) - c
                                    // y = (z * debtA / x) - debtA
                                    irx_ = ((iry_ * dx_) / ry_) - dx_;
                                    // finding w
                                    // w = y * d / (z + d)
                                    // w = (y * debtB) / (z + debtB)
                                    rx_ = (irx_ * dy_) / (iry_ + dy_);
                                }
                                /// @notice Calculates the debt amount for a given token from liquidity layer
                                /// @param borrowTokenSlot_ The storage slot for the token's borrow data
                                /// @param tokenExchangePrice_ The current exchange price of the token
                                /// @param isToken0_ Boolean indicating if this is for token0 (true) or token1 (false)
                                /// @return tokenDebt_ The calculated debt amount for the token
                                function _getLiquidityDebt(
                                    bytes32 borrowTokenSlot_,
                                    uint tokenExchangePrice_,
                                    bool isToken0_
                                ) internal view returns (uint tokenDebt_) {
                                    uint tokenBorrowData_ = LIQUIDITY.readFromStorage(borrowTokenSlot_);
                                    tokenDebt_ = (tokenBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_AMOUNT) & X64;
                                    tokenDebt_ = (tokenDebt_ >> 8) << (tokenDebt_ & X8);
                                    if (tokenBorrowData_ & 1 == 1) {
                                        // borrow with interest is on
                                        unchecked {
                                            tokenDebt_ = (tokenDebt_ * tokenExchangePrice_) / LiquidityCalcs.EXCHANGE_PRICES_PRECISION;
                                        }
                                    }
                                    unchecked {
                                        tokenDebt_ = isToken0_
                                            ? ((tokenDebt_ * TOKEN_0_NUMERATOR_PRECISION) / TOKEN_0_DENOMINATOR_PRECISION)
                                            : ((tokenDebt_ * TOKEN_1_NUMERATOR_PRECISION) / TOKEN_1_DENOMINATOR_PRECISION);
                                    }
                                }
                                /// @notice Calculates the debt reserves for both tokens
                                /// @param geometricMean_ The geometric mean of the upper and lower price ranges
                                /// @param upperRange_ The upper price range
                                /// @param lowerRange_ The lower price range
                                /// @param token0BorrowExchangePrice_ The exchange price of token0 from liquidity layer
                                /// @param token1BorrowExchangePrice_ The exchange price of token1 from liquidity layer
                                /// @return d_ The calculated debt reserves for both tokens, containing:
                                ///         - token0Debt: The debt amount of token0
                                ///         - token1Debt: The debt amount of token1
                                ///         - token0RealReserves: The real reserves of token0 derived from token1 debt
                                ///         - token1RealReserves: The real reserves of token1 derived from token0 debt
                                ///         - token0ImaginaryReserves: The imaginary debt reserves of token0
                                ///         - token1ImaginaryReserves: The imaginary debt reserves of token1
                                function _getDebtReserves(
                                    uint geometricMean_,
                                    uint upperRange_,
                                    uint lowerRange_,
                                    uint token0BorrowExchangePrice_,
                                    uint token1BorrowExchangePrice_
                                ) internal view returns (DebtReserves memory d_) {
                                    uint token0Debt_ = _getLiquidityDebt(BORROW_TOKEN_0_SLOT, token0BorrowExchangePrice_, true);
                                    uint token1Debt_ = _getLiquidityDebt(BORROW_TOKEN_1_SLOT, token1BorrowExchangePrice_, false);
                                    d_.token0Debt = token0Debt_;
                                    d_.token1Debt = token1Debt_;
                                    if (geometricMean_ < 1e27) {
                                        (
                                            d_.token0RealReserves,
                                            d_.token1RealReserves,
                                            d_.token0ImaginaryReserves,
                                            d_.token1ImaginaryReserves
                                        ) = _calculateDebtReserves(geometricMean_, lowerRange_, token0Debt_, token1Debt_);
                                    } else {
                                        // inversing, something like `xy = k` so for calculation we are making everything related to x into y & y into x
                                        // 1 / geometricMean for new geometricMean
                                        // 1 / lowerRange will become upper range
                                        // 1 / upperRange will become lower range
                                        (
                                            d_.token1RealReserves,
                                            d_.token0RealReserves,
                                            d_.token1ImaginaryReserves,
                                            d_.token0ImaginaryReserves
                                        ) = _calculateDebtReserves((1e54 / geometricMean_), (1e54 / upperRange_), token1Debt_, token0Debt_);
                                    }
                                }
                                function _priceDiffCheck(uint oldPrice_, uint newPrice_) internal pure returns (int priceDiff_) {
                                    // check newPrice_ & oldPrice_ difference should not be more than 5%
                                    // old price w.r.t new price
                                    priceDiff_ = int(ORACLE_PRECISION) - int((oldPrice_ * ORACLE_PRECISION) / newPrice_);
                                    unchecked {
                                        if ((priceDiff_ > int(ORACLE_LIMIT)) || (priceDiff_ < -int(ORACLE_LIMIT))) {
                                            // if oracle price difference is more than 5% then revert
                                            // in 1 swap price should only change by <= 5%
                                            // if a total fall by let's say 8% then in current block price can only fall by 5% and
                                            // in next block it'll fall the remaining 3%
                                            revert FluidDexError(ErrorTypes.DexT1__OracleUpdateHugeSwapDiff);
                                        }
                                    }
                                }
                                function _updateOracle(uint newPrice_, uint centerPrice_, uint dexVariables_) internal returns (uint) {
                                    // time difference between last & current swap
                                    uint timeDiff_ = block.timestamp - ((dexVariables_ >> 121) & X33);
                                    uint temp_;
                                    uint temp2_;
                                    uint temp3_;
                                    if (timeDiff_ == 0) {
                                        // doesn't matter if oracle is on or off when timediff = 0 code for both is same
                                        // temp_ => oldCenterPrice
                                        temp_ = (dexVariables_ >> 81) & X40;
                                        temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                                        // Ensure that the center price is within the acceptable range of the old center price if it's not the first swap in the same block
                                        unchecked {
                                            if (
                                                (centerPrice_ < (((EIGHT_DECIMALS - 1) * temp_) / EIGHT_DECIMALS)) ||
                                                (centerPrice_ > (((EIGHT_DECIMALS + 1) * temp_) / EIGHT_DECIMALS))
                                            ) {
                                                revert FluidDexError(ErrorTypes.DexT1__CenterPriceOutOfRange);
                                            }
                                        }
                                        // olderPrice_ => temp_
                                        temp_ = (dexVariables_ >> 1) & X40;
                                        temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                                        _priceDiffCheck(temp_, newPrice_);
                                        // 2nd swap in same block no need to update anything around oracle, only need to update last swap price in dexVariables
                                        return ((dexVariables_ & 0xfffffffffffffffffffffffffffffffffffffffffffe0000000001ffffffffff) |
                                            (newPrice_.toBigNumber(32, 8, BigMathMinified.ROUND_DOWN) << 41));
                                    }
                                    if (((dexVariables_ >> 195) & 1) == 0) {
                                        // if oracle is not active then just returning updated DEX variable
                                        temp_ = ((dexVariables_ >> 41) & X40);
                                        temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                                        _priceDiffCheck(temp_, newPrice_);
                                        
                                        return ((dexVariables_ & 0xfffffffffffffffffffffffffc00000000000000000000000000000000000001) |
                                            (((dexVariables_ >> 41) & X40) << 1) |
                                            (newPrice_.toBigNumber(32, 8, BigMathMinified.ROUND_DOWN) << 41) |
                                            (centerPrice_.toBigNumber(32, 8, BigMathMinified.ROUND_DOWN) << 81) |
                                            (block.timestamp << 121));
                                    } else {
                                        // oracle is active hence update oracle
                                        // olderPrice_ => temp_
                                        temp_ = (dexVariables_ >> 1) & X40;
                                        temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                                        // oldPrice_ => temp2_
                                        temp2_ = (dexVariables_ >> 41) & X40;
                                        temp2_ = (temp2_ >> DEFAULT_EXPONENT_SIZE) << (temp2_ & DEFAULT_EXPONENT_MASK);
                                        int priceDiff_ = _priceDiffCheck(temp2_, newPrice_);
                                        unchecked {
                                            // older price w.r.t old price
                                            priceDiff_ = int(ORACLE_PRECISION) - int((temp_ * ORACLE_PRECISION) / temp2_);
                                        }
                                        // priceDiffInPercentAndSign_ => temp3_
                                        // priceDiff_ will always be lower than ORACLE_LIMIT due to above check
                                        unchecked {
                                            if (priceDiff_ < 0) {
                                                temp3_ = ((uint(-priceDiff_) * X22) / ORACLE_LIMIT) << 1;
                                            } else {
                                                // if greater than or equal to 0 then make sign flag 1
                                                temp3_ = (((uint(priceDiff_) * X22) / ORACLE_LIMIT) << 1) | 1;
                                            }
                                        }
                                        if (timeDiff_ > X22) {
                                            // if time difference is this then that means DEX has been inactive ~45 days
                                            // that means oracle price of this DEX should not be used.
                                            timeDiff_ = X22;
                                        }
                                        // temp_ => lastTimeDiff_
                                        temp_ = (dexVariables_ >> 154) & X22;
                                        uint nextOracleSlot_ = ((dexVariables_ >> 176) & X3);
                                        uint oracleMap_ = (dexVariables_ >> 179) & X16;
                                        if (temp_ > X9) {
                                            if (nextOracleSlot_ > 0) {
                                                // if greater than 0 then current slot has 2 or more oracle slot empty
                                                // First 9 bits are of time, so not using that
                                                temp3_ = (temp3_ << 41) | (temp_ << 9);
                                                _oracle[oracleMap_] = _oracle[oracleMap_] | (temp3_ << (--nextOracleSlot_ * 32));
                                                if (nextOracleSlot_ > 0) {
                                                    --nextOracleSlot_;
                                                } else {
                                                    // if == 0 that means the oracle slots will get filled and shift to next oracle map
                                                    nextOracleSlot_ = 7;
                                                    unchecked {
                                                        oracleMap_ = (oracleMap_ + 1) % TOTAL_ORACLE_MAPPING;
                                                    }
                                                    _oracle[oracleMap_] = 0;
                                                }
                                            } else {
                                                // if == 0
                                                // then seconds will be in last map
                                                // precision will be in last map + 1
                                                // Storing precision & sign slot in first precision & sign slot and leaving time slot empty
                                                temp3_ = temp3_ << 9;
                                                _oracle[oracleMap_] = _oracle[oracleMap_] | temp3_;
                                                nextOracleSlot_ = 6; // storing 6 here as 7 is going to occupied right now
                                                unchecked {
                                                    oracleMap_ = (oracleMap_ + 1) % TOTAL_ORACLE_MAPPING;
                                                }
                                                // Storing time in 2nd precision & sign and leaving time slot empty
                                                _oracle[oracleMap_] = temp_ << ((7 * 32) + 9);
                                            }
                                        } else {
                                            temp3_ = (temp3_ << 9) | temp_;
                                            unchecked {
                                                if (nextOracleSlot_ < 7) {
                                                    _oracle[oracleMap_] = _oracle[oracleMap_] | (temp3_ << (nextOracleSlot_ * 32));
                                                } else {
                                                    _oracle[oracleMap_] = temp3_ << ((7 * 32));
                                                }
                                            }
                                            if (nextOracleSlot_ > 0) {
                                                --nextOracleSlot_;
                                            } else {
                                                nextOracleSlot_ = 7;
                                                unchecked {
                                                    oracleMap_ = (oracleMap_ + 1) % TOTAL_ORACLE_MAPPING;
                                                }
                                                _oracle[oracleMap_] = 0;
                                            }
                                        }
                                        // doing this due to stack too deep error when using params memory variables
                                        temp_ = newPrice_;
                                        temp2_ = centerPrice_;
                                        temp3_ = dexVariables_;
                                        // then update last price
                                        return ((temp3_ & 0xfffffffffffffff8000000000000000000000000000000000000000000000001) |
                                            (((temp3_ >> 41) & X40) << 1) |
                                            (temp_.toBigNumber(32, 8, BigMathMinified.ROUND_DOWN) << 41) |
                                            (temp2_.toBigNumber(32, 8, BigMathMinified.ROUND_DOWN) << 81) |
                                            (block.timestamp << 121) |
                                            (timeDiff_ << 154) |
                                            (nextOracleSlot_ << 176) |
                                            (oracleMap_ << 179));
                                    }
                                }
                                function _hookVerify(uint hookAddress_, uint mode_, bool swap0to1_, uint price_) internal {
                                    try
                                        IHook(AddressCalcs.addressCalc(DEPLOYER_CONTRACT, hookAddress_)).dexPrice(
                                            mode_,
                                            swap0to1_,
                                            TOKEN_0,
                                            TOKEN_1,
                                            price_
                                        )
                                    returns (bool isOk_) {
                                        if (!isOk_) revert FluidDexError(ErrorTypes.DexT1__HookReturnedFalse);
                                    } catch (bytes memory /*lowLevelData*/) {
                                        // skip checking hook nothing
                                    }
                                }
                                function _updateSupplyShares(uint newTotalShares_) internal {
                                    uint totalSupplyShares_ = _totalSupplyShares;
                                    // new total shares are greater than old total shares && new total shares are greater than max supply shares
                                    if (
                                        (newTotalShares_ > (totalSupplyShares_ & X128)) && 
                                        newTotalShares_ > (totalSupplyShares_ >> 128)
                                    ) {
                                        revert FluidDexError(ErrorTypes.DexT1__SupplySharesOverflow);
                                    }
                                    // keeping max supply shares intact
                                    _totalSupplyShares = ((totalSupplyShares_ >> 128) << 128) | newTotalShares_;
                                }
                                function _updateBorrowShares(uint newTotalShares_) internal {
                                    uint totalBorrowShares_ = _totalBorrowShares;
                                    // new total shares are greater than old total shares && new total shares are greater than max borrow shares
                                    if (
                                        (newTotalShares_ > (totalBorrowShares_ & X128)) && 
                                        newTotalShares_ > (totalBorrowShares_ >> 128)
                                    ) {
                                        revert FluidDexError(ErrorTypes.DexT1__BorrowSharesOverflow);
                                    }
                                    // keeping max borrow shares intact
                                    _totalBorrowShares = ((totalBorrowShares_ >> 128) << 128) | newTotalShares_;
                                }
                                constructor(ConstantViews memory constantViews_) ImmutableVariables(constantViews_) {}
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            import { IFluidLiquidity } from "../../../../liquidity/interfaces/iLiquidity.sol";
                            import { Structs } from "./structs.sol";
                            import { ConstantVariables } from "../common/constantVariables.sol";
                            import { IFluidDexFactory } from "../../interfaces/iDexFactory.sol";
                            import { Error } from "../../error.sol";
                            import { ErrorTypes } from "../../errorTypes.sol";
                            abstract contract ImmutableVariables is ConstantVariables, Structs, Error {
                                /*//////////////////////////////////////////////////////////////
                                                      CONSTANTS / IMMUTABLES
                                //////////////////////////////////////////////////////////////*/
                                uint256 public immutable DEX_ID;
                                /// @dev Address of token 0
                                address internal immutable TOKEN_0;
                                /// @dev Address of token 1
                                address internal immutable TOKEN_1;
                                address internal immutable THIS_CONTRACT;
                                uint256 internal immutable TOKEN_0_NUMERATOR_PRECISION;
                                uint256 internal immutable TOKEN_0_DENOMINATOR_PRECISION;
                                uint256 internal immutable TOKEN_1_NUMERATOR_PRECISION;
                                uint256 internal immutable TOKEN_1_DENOMINATOR_PRECISION;
                                /// @dev Address of liquidity contract
                                IFluidLiquidity internal immutable LIQUIDITY;
                                /// @dev Address of DEX factory contract
                                IFluidDexFactory internal immutable DEX_FACTORY;
                                /// @dev Address of Shift implementation
                                address internal immutable SHIFT_IMPLEMENTATION;
                                /// @dev Address of Admin implementation
                                address internal immutable ADMIN_IMPLEMENTATION;
                                /// @dev Address of Col Operations implementation
                                address internal immutable COL_OPERATIONS_IMPLEMENTATION;
                                /// @dev Address of Debt Operations implementation
                                address internal immutable DEBT_OPERATIONS_IMPLEMENTATION;
                                /// @dev Address of Perfect Operations and Swap Out implementation
                                address internal immutable PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION;
                                /// @dev Address of contract used for deploying center price & hook related contract
                                address internal immutable DEPLOYER_CONTRACT;
                                /// @dev Liquidity layer slots
                                bytes32 internal immutable SUPPLY_TOKEN_0_SLOT;
                                bytes32 internal immutable BORROW_TOKEN_0_SLOT;
                                bytes32 internal immutable SUPPLY_TOKEN_1_SLOT;
                                bytes32 internal immutable BORROW_TOKEN_1_SLOT;
                                bytes32 internal immutable EXCHANGE_PRICE_TOKEN_0_SLOT;
                                bytes32 internal immutable EXCHANGE_PRICE_TOKEN_1_SLOT;
                                uint256 internal immutable TOTAL_ORACLE_MAPPING;
                                function _calcNumeratorAndDenominator(
                                    address token_
                                ) private view returns (uint256 numerator_, uint256 denominator_) {
                                    uint256 decimals_ = _decimals(token_);
                                    if (decimals_ > TOKENS_DECIMALS_PRECISION) {
                                        numerator_ = 1;
                                        denominator_ = 10 ** (decimals_ - TOKENS_DECIMALS_PRECISION);
                                    } else {
                                        numerator_ = 10 ** (TOKENS_DECIMALS_PRECISION - decimals_);
                                        denominator_ = 1;
                                    }
                                }
                                constructor(ConstantViews memory constants_) {
                                    THIS_CONTRACT = address(this);
                                    DEX_ID = constants_.dexId;
                                    LIQUIDITY = IFluidLiquidity(constants_.liquidity);
                                    DEX_FACTORY = IFluidDexFactory(constants_.factory);
                                    TOKEN_0 = constants_.token0;
                                    TOKEN_1 = constants_.token1;
                                    if (TOKEN_0 >= TOKEN_1) revert FluidDexError(ErrorTypes.DexT1__Token0ShouldBeSmallerThanToken1);
                                    (TOKEN_0_NUMERATOR_PRECISION, TOKEN_0_DENOMINATOR_PRECISION) = _calcNumeratorAndDenominator(TOKEN_0);
                                    (TOKEN_1_NUMERATOR_PRECISION, TOKEN_1_DENOMINATOR_PRECISION) = _calcNumeratorAndDenominator(TOKEN_1);
                                    if (constants_.implementations.shift != address(0)) {
                                        SHIFT_IMPLEMENTATION = constants_.implementations.shift;
                                    } else {
                                        SHIFT_IMPLEMENTATION = address(this);
                                    }
                                    if (constants_.implementations.admin != address(0)) {
                                        ADMIN_IMPLEMENTATION = constants_.implementations.admin;
                                    } else {
                                        ADMIN_IMPLEMENTATION = address(this);
                                    }
                                    if (constants_.implementations.colOperations != address(0)) {
                                        COL_OPERATIONS_IMPLEMENTATION = constants_.implementations.colOperations;
                                    } else {
                                        COL_OPERATIONS_IMPLEMENTATION = address(this);
                                    }
                                    if (constants_.implementations.debtOperations != address(0)) {
                                        DEBT_OPERATIONS_IMPLEMENTATION = constants_.implementations.debtOperations;
                                    } else {
                                        DEBT_OPERATIONS_IMPLEMENTATION = address(this);
                                    }
                                    if (constants_.implementations.perfectOperationsAndSwapOut != address(0)) {
                                        PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION = constants_.implementations.perfectOperationsAndSwapOut;
                                    } else {
                                        PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION = address(this);
                                    }
                                    DEPLOYER_CONTRACT = constants_.deployerContract;
                                    SUPPLY_TOKEN_0_SLOT = constants_.supplyToken0Slot;
                                    BORROW_TOKEN_0_SLOT = constants_.borrowToken0Slot;
                                    SUPPLY_TOKEN_1_SLOT = constants_.supplyToken1Slot;
                                    BORROW_TOKEN_1_SLOT = constants_.borrowToken1Slot;
                                    EXCHANGE_PRICE_TOKEN_0_SLOT = constants_.exchangePriceToken0Slot;
                                    EXCHANGE_PRICE_TOKEN_1_SLOT = constants_.exchangePriceToken1Slot;
                                    if (constants_.oracleMapping > X16) revert FluidDexError(ErrorTypes.DexT1__OracleMappingOverflow);
                                    TOTAL_ORACLE_MAPPING = constants_.oracleMapping;
                                }
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            interface IHook {
                                /// @notice Hook function to check for liquidation opportunities before external swaps
                                /// @dev The primary use of this hook is to check if a particular pair vault has liquidation available.
                                ///      If liquidation is available, it gives priority to the liquidation process before allowing external swaps.
                                ///      In most cases, this hook will not be set.
                                /// @param id_ Identifier for the operation type: 1 for swap, 2 for internal arbitrage
                                /// @param swap0to1_ Direction of the swap: true if swapping token0 for token1, false otherwise
                                /// @param token0_ Address of the first token in the pair
                                /// @param token1_ Address of the second token in the pair
                                /// @param price_ The price ratio of token1 to token0, expressed with 27 decimal places
                                /// @return isOk_ Boolean indicating whether the operation should proceed
                                function dexPrice(
                                    uint id_,
                                    bool swap0to1_,
                                    address token0_,
                                    address token1_,
                                    uint price_
                                ) external returns (bool isOk_);
                            }
                            interface ICenterPrice {
                                /// @notice Retrieves the center price for the pool
                                /// @dev This function is marked as non-constant (potentially state-changing) to allow flexibility in price fetching mechanisms.
                                ///      While typically used as a read-only operation, this design permits write operations if needed for certain token pairs
                                ///      (e.g., fetching up-to-date exchange rates that may require state changes).
                                /// @return price The current price ratio of token1 to token0, expressed with 27 decimal places
                                function centerPrice() external returns (uint price);
                            }
                            // SPDX-License-Identifier: BUSL-1.1
                            pragma solidity 0.8.21;
                            abstract contract Structs {
                                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 ExchangePrices {
                                    uint supplyToken0ExchangePrice;
                                    uint borrowToken0ExchangePrice;
                                    uint supplyToken1ExchangePrice;
                                    uint borrowToken1ExchangePrice;
                                }
                                struct CollateralReserves {
                                    uint token0RealReserves;
                                    uint token1RealReserves;
                                    uint token0ImaginaryReserves;
                                    uint token1ImaginaryReserves;
                                }
                                struct CollateralReservesSwap {
                                    uint tokenInRealReserves;
                                    uint tokenOutRealReserves;
                                    uint tokenInImaginaryReserves;
                                    uint tokenOutImaginaryReserves;
                                }
                                struct DebtReserves {
                                    uint token0Debt;
                                    uint token1Debt;
                                    uint token0RealReserves;
                                    uint token1RealReserves;
                                    uint token0ImaginaryReserves;
                                    uint token1ImaginaryReserves;
                                }
                                struct DebtReservesSwap {
                                    uint tokenInDebt;
                                    uint tokenOutDebt;
                                    uint tokenInRealReserves;
                                    uint tokenOutRealReserves;
                                    uint tokenInImaginaryReserves;
                                    uint tokenOutImaginaryReserves;
                                }
                                struct SwapInMemory {
                                    address tokenIn;
                                    address tokenOut;
                                    uint256 amtInAdjusted;
                                    address withdrawTo;
                                    address borrowTo;
                                    uint price; // price of pool after swap
                                    uint fee; // fee of pool
                                    uint revenueCut; // revenue cut of pool
                                    bool swap0to1;
                                    int swapRoutingAmt;
                                    bytes data; // just added to avoid stack-too-deep error
                                }
                                struct SwapOutMemory {
                                    address tokenIn;
                                    address tokenOut;
                                    uint256 amtOutAdjusted;
                                    address withdrawTo;
                                    address borrowTo;
                                    uint price; // price of pool after swap
                                    uint fee;
                                    uint revenueCut; // revenue cut of pool
                                    bool swap0to1;
                                    int swapRoutingAmt;
                                    bytes data; // just added to avoid stack-too-deep error
                                    uint msgValue;
                                }
                                struct DepositColMemory {
                                    uint256 token0AmtAdjusted;
                                    uint256 token1AmtAdjusted;
                                    uint256 token0ReservesInitial;
                                    uint256 token1ReservesInitial;
                                }
                                struct WithdrawColMemory {
                                    uint256 token0AmtAdjusted;
                                    uint256 token1AmtAdjusted;
                                    uint256 token0ReservesInitial;
                                    uint256 token1ReservesInitial;
                                    address to;
                                }
                                struct BorrowDebtMemory {
                                    uint256 token0AmtAdjusted;
                                    uint256 token1AmtAdjusted;
                                    uint256 token0DebtInitial;
                                    uint256 token1DebtInitial;
                                    address to;
                                }
                                struct PaybackDebtMemory {
                                    uint256 token0AmtAdjusted;
                                    uint256 token1AmtAdjusted;
                                    uint256 token0DebtInitial;
                                    uint256 token1DebtInitial;
                                }
                                struct OraclePriceMemory {
                                    uint lowestPrice1by0;
                                    uint highestPrice1by0;
                                    uint oracleSlot;
                                    uint oracleMap;
                                    uint oracle;
                                }
                                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
                                }
                                struct Implementations {
                                    address shift;
                                    address admin;
                                    address colOperations;
                                    address debtOperations;
                                    address perfectOperationsAndSwapOut;
                                }
                                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;
                                }
                            }
                            // SPDX-License-Identifier: AGPL-3.0-only
                            pragma solidity >=0.8.0;
                            /// @notice Arithmetic library with operations for fixed-point numbers.
                            /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
                            /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
                            library FixedPointMathLib {
                                /*//////////////////////////////////////////////////////////////
                                                SIMPLIFIED FIXED POINT OPERATIONS
                                //////////////////////////////////////////////////////////////*/
                                uint256 internal constant MAX_UINT256 = 2**256 - 1;
                                uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
                                function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                                    return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
                                }
                                function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                                    return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
                                }
                                function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                                    return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
                                }
                                function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                                    return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
                                }
                                /*//////////////////////////////////////////////////////////////
                                                LOW LEVEL FIXED POINT OPERATIONS
                                //////////////////////////////////////////////////////////////*/
                                function mulDivDown(
                                    uint256 x,
                                    uint256 y,
                                    uint256 denominator
                                ) internal pure returns (uint256 z) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                                        if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                                            revert(0, 0)
                                        }
                                        // Divide x * y by the denominator.
                                        z := div(mul(x, y), denominator)
                                    }
                                }
                                function mulDivUp(
                                    uint256 x,
                                    uint256 y,
                                    uint256 denominator
                                ) internal pure returns (uint256 z) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                                        if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                                            revert(0, 0)
                                        }
                                        // If x * y modulo the denominator is strictly greater than 0,
                                        // 1 is added to round up the division of x * y by the denominator.
                                        z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
                                    }
                                }
                                function rpow(
                                    uint256 x,
                                    uint256 n,
                                    uint256 scalar
                                ) internal pure returns (uint256 z) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        switch x
                                        case 0 {
                                            switch n
                                            case 0 {
                                                // 0 ** 0 = 1
                                                z := scalar
                                            }
                                            default {
                                                // 0 ** n = 0
                                                z := 0
                                            }
                                        }
                                        default {
                                            switch mod(n, 2)
                                            case 0 {
                                                // If n is even, store scalar in z for now.
                                                z := scalar
                                            }
                                            default {
                                                // If n is odd, store x in z for now.
                                                z := x
                                            }
                                            // Shifting right by 1 is like dividing by 2.
                                            let half := shr(1, scalar)
                                            for {
                                                // Shift n right by 1 before looping to halve it.
                                                n := shr(1, n)
                                            } n {
                                                // Shift n right by 1 each iteration to halve it.
                                                n := shr(1, n)
                                            } {
                                                // Revert immediately if x ** 2 would overflow.
                                                // Equivalent to iszero(eq(div(xx, x), x)) here.
                                                if shr(128, x) {
                                                    revert(0, 0)
                                                }
                                                // Store x squared.
                                                let xx := mul(x, x)
                                                // Round to the nearest number.
                                                let xxRound := add(xx, half)
                                                // Revert if xx + half overflowed.
                                                if lt(xxRound, xx) {
                                                    revert(0, 0)
                                                }
                                                // Set x to scaled xxRound.
                                                x := div(xxRound, scalar)
                                                // If n is even:
                                                if mod(n, 2) {
                                                    // Compute z * x.
                                                    let zx := mul(z, x)
                                                    // If z * x overflowed:
                                                    if iszero(eq(div(zx, x), z)) {
                                                        // Revert if x is non-zero.
                                                        if iszero(iszero(x)) {
                                                            revert(0, 0)
                                                        }
                                                    }
                                                    // Round to the nearest number.
                                                    let zxRound := add(zx, half)
                                                    // Revert if zx + half overflowed.
                                                    if lt(zxRound, zx) {
                                                        revert(0, 0)
                                                    }
                                                    // Return properly scaled zxRound.
                                                    z := div(zxRound, scalar)
                                                }
                                            }
                                        }
                                    }
                                }
                                /*//////////////////////////////////////////////////////////////
                                                    GENERAL NUMBER UTILITIES
                                //////////////////////////////////////////////////////////////*/
                                function sqrt(uint256 x) internal pure returns (uint256 z) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        let y := x // We start y at x, which will help us make our initial estimate.
                                        z := 181 // The "correct" value is 1, but this saves a multiplication later.
                                        // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
                                        // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
                                        // We check y >= 2^(k + 8) but shift right by k bits
                                        // each branch to ensure that if x >= 256, then y >= 256.
                                        if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                                            y := shr(128, y)
                                            z := shl(64, z)
                                        }
                                        if iszero(lt(y, 0x1000000000000000000)) {
                                            y := shr(64, y)
                                            z := shl(32, z)
                                        }
                                        if iszero(lt(y, 0x10000000000)) {
                                            y := shr(32, y)
                                            z := shl(16, z)
                                        }
                                        if iszero(lt(y, 0x1000000)) {
                                            y := shr(16, y)
                                            z := shl(8, z)
                                        }
                                        // Goal was to get z*z*y within a small factor of x. More iterations could
                                        // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
                                        // We ensured y >= 256 so that the relative difference between y and y+1 is small.
                                        // That's not possible if x < 256 but we can just verify those cases exhaustively.
                                        // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
                                        // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
                                        // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
                                        // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
                                        // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
                                        // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
                                        // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
                                        // There is no overflow risk here since y < 2^136 after the first branch above.
                                        z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
                                        // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
                                        z := shr(1, add(z, div(x, z)))
                                        z := shr(1, add(z, div(x, z)))
                                        z := shr(1, add(z, div(x, z)))
                                        z := shr(1, add(z, div(x, z)))
                                        z := shr(1, add(z, div(x, z)))
                                        z := shr(1, add(z, div(x, z)))
                                        z := shr(1, add(z, div(x, z)))
                                        // If x+1 is a perfect square, the Babylonian method cycles between
                                        // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
                                        // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
                                        // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
                                        // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
                                        z := sub(z, lt(div(x, z), z))
                                    }
                                }
                                function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        // Mod x by y. Note this will return
                                        // 0 instead of reverting if y is zero.
                                        z := mod(x, y)
                                    }
                                }
                                function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        // Divide x by y. Note this will return
                                        // 0 instead of reverting if y is zero.
                                        r := div(x, y)
                                    }
                                }
                                function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        // Add 1 to x * y if x % y > 0. Note this will
                                        // return 0 instead of reverting if y is zero.
                                        z := add(gt(mod(x, y), 0), div(x, y))
                                    }
                                }
                            }
                            

                            File 6 of 17: DssLitePsm
                            // SPDX-FileCopyrightText: © 2023 Dai Foundation <www.daifoundation.org>
                            // SPDX-License-Identifier: AGPL-3.0-or-later
                            //
                            // This program is free software: you can redistribute it and/or modify
                            // it under the terms of the GNU Affero General Public License as published by
                            // the Free Software Foundation, either version 3 of the License, or
                            // (at your option) any later version.
                            //
                            // This program is distributed in the hope that it will be useful,
                            // but WITHOUT ANY WARRANTY; without even the implied warranty of
                            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                            // GNU Affero General Public License for more details.
                            //
                            // You should have received a copy of the GNU Affero General Public License
                            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
                            pragma solidity ^0.8.16;
                            interface VatLike {
                                function frob(bytes32, address, address, address, int256, int256) external;
                                function hope(address) external;
                                function ilks(bytes32) external view returns (uint256, uint256, uint256, uint256, uint256);
                                function debt() external view returns (uint256);
                                function Line() external view returns (uint256);
                                function urns(bytes32, address) external view returns (uint256, uint256);
                                function live() external view returns (uint256);
                            }
                            interface GemLike {
                                function balanceOf(address) external view returns (uint256);
                                function decimals() external view returns (uint8);
                                function approve(address, uint256) external;
                                function transfer(address, uint256) external;
                                function transferFrom(address, address, uint256) external;
                            }
                            interface DaiJoinLike {
                                function dai() external view returns (address);
                                function vat() external view returns (address);
                                function exit(address, uint256) external;
                                function join(address, uint256) external;
                            }
                            /**
                             * @title A lightweight PSM implementation.
                             * @notice Swaps Dai for `gem` at a 1:1 exchange rate.
                             * @notice Fees `tin` and `tout` might apply.
                             * @dev `gem` balance is kept in `pocket` instead of this contract.
                             * @dev A few assumptions are made:
                             *      1. There are no other urns for the same `ilk`
                             *      2. Stability fee is always zero for the `ilk`
                             *      3. The `spot` price for gem is always 1 (`10**27`).
                             *      4. The `spotter.par` (Dai parity) is always 1 (`10**27`).
                             *      5. This contract can freely transfer `gem` on behalf of `pocket`.
                             */
                            contract DssLitePsm {
                                /// @notice Special value for `tin` and/or `tout` to indicate swaps are halted.
                                /// @dev Setting `tin` or `tout` to `type(uint256).max` will cause sell gem and buy gem functions respectively to revert.
                                uint256 public constant HALTED = type(uint256).max;
                                /// @notice Collateral type identifier.
                                bytes32 public immutable ilk;
                                /// @notice Maker Protocol core engine.
                                VatLike public immutable vat;
                                /// @notice Dai adapter.
                                DaiJoinLike public immutable daiJoin;
                                /// @notice Dai token.
                                GemLike public immutable dai;
                                /// @notice Gem to exchange with Dai.
                                GemLike public immutable gem;
                                /// @notice Precision conversion factor for `gem`, since Dai is expected to always have 18 decimals.
                                uint256 public immutable to18ConversionFactor;
                                /// @notice The ultimate holder of the gems.
                                /// @dev This contract should be able to freely transfer `gem` on behalf of `pocket`.
                                address public immutable pocket;
                                /// @notice Addresses with admin access on this contract. `wards[usr]`.
                                mapping(address => uint256) public wards;
                                /// @notice Addresses with permission to swap with no fees. `bud[usr]`.
                                mapping(address => uint256) public bud;
                                /// @notice Maker Protocol balance sheet.
                                address public vow;
                                /// @notice Fee for selling gems.
                                /// @dev `wad` precision. 1 * WAD means a 100% fee.
                                uint256 public tin;
                                /// @notice Fee for buying gems.
                                /// @dev `wad` precision. 1 * WAD means a 100% fee.
                                uint256 public tout;
                                /// @notice Buffer for pre-minted Dai.
                                /// @dev `wad` precision.
                                uint256 public buf;
                                /// @dev `wad` precision.
                                uint256 internal constant WAD = 10 ** 18;
                                /// @dev `ray` precision for `vat` manipulation.
                                uint256 internal constant RAY = 10 ** 27;
                                /// @dev Workaround to explicitly revert with an arithmetic error.
                                string internal constant ARITHMETIC_ERROR = string(abi.encodeWithSignature("Panic(uint256)", 0x11));
                                /**
                                 * @notice `usr` was granted admin access.
                                 * @param usr The user address.
                                 */
                                event Rely(address indexed usr);
                                /**
                                 * @notice `usr` admin access was revoked.
                                 * @param usr The user address.
                                 */
                                event Deny(address indexed usr);
                                /**
                                 * @notice `usr` was granted permission to swap without any fees.
                                 * @param usr The user address.
                                 */
                                event Kiss(address indexed usr);
                                /**
                                 * @notice Permission revoked for `usr` to swap without any fees.
                                 * @param usr The user address.
                                 */
                                event Diss(address indexed usr);
                                /**
                                 * @notice A contract parameter was updated.
                                 * @param what The changed parameter name. ["vow"].
                                 * @param data The new value of the parameter.
                                 */
                                event File(bytes32 indexed what, address data);
                                /**
                                 * @notice A contract parameter was updated.
                                 * @param what The changed parameter name. ["tin", "tout", "buf"].
                                 * @param data The new value of the parameter.
                                 */
                                event File(bytes32 indexed what, uint256 data);
                                /**
                                 * @notice A user sold `gem` for Dai.
                                 * @param owner The address receiving Dai.
                                 * @param value The amount of `gem` sold. [`gem` precision].
                                 * @param fee The fee in Dai paid by the user. [`wad`].
                                 */
                                event SellGem(address indexed owner, uint256 value, uint256 fee);
                                /**
                                 * @notice A user bought `gem` with Dai.
                                 * @param owner The address receiving `gem`.
                                 * @param value The amount of `gem` bought. [`gem` precision].
                                 * @param fee The fee in Dai paid by the user. [`wad`].
                                 */
                                event BuyGem(address indexed owner, uint256 value, uint256 fee);
                                /**
                                 * @notice The contract was filled with Dai.
                                 * @param wad The amount of Dai filled.
                                 */
                                event Fill(uint256 wad);
                                /**
                                 * @notice The contract was trimmed of excess Dai.
                                 * @param wad The amount of Dai trimmed.
                                 */
                                event Trim(uint256 wad);
                                /**
                                 * @notice Dai accumulated as swap fees was added to the surplus buffer.
                                 * @param wad The amount of Dai added.
                                 */
                                event Chug(uint256 wad);
                                modifier auth() {
                                    require(wards[msg.sender] == 1, "DssLitePsm/not-authorized");
                                    _;
                                }
                                modifier toll() {
                                    require(bud[msg.sender] == 1, "DssLitePsm/not-whitelisted");
                                    _;
                                }
                                /**
                                 * @param ilk_ The collateral type identifier.
                                 * @param gem_ The gem to exchange with Dai.
                                 * @param daiJoin_ The Dai adapter.
                                 * @param pocket_ The ultimate holder of `gem`.
                                 */
                                constructor(bytes32 ilk_, address gem_, address daiJoin_, address pocket_) {
                                    ilk = ilk_;
                                    gem = GemLike(gem_);
                                    daiJoin = DaiJoinLike(daiJoin_);
                                    vat = VatLike(daiJoin.vat());
                                    dai = GemLike(daiJoin.dai());
                                    pocket = pocket_;
                                    to18ConversionFactor = 10 ** (18 - gem.decimals());
                                    dai.approve(daiJoin_, type(uint256).max);
                                    vat.hope(daiJoin_);
                                    wards[msg.sender] = 1;
                                    emit Rely(msg.sender);
                                }
                                /*//////////////////////////////////
                                                Math
                                //////////////////////////////////*/
                                ///@dev Safely converts `uint256` to `int256`. Reverts if it overflows.
                                function _int256(uint256 x) internal pure returns (int256 y) {
                                    require((y = int256(x)) >= 0, ARITHMETIC_ERROR);
                                }
                                ///@dev Returns the min between `x` and `y`.
                                function _min(uint256 x, uint256 y) internal pure returns (uint256 z) {
                                    return x < y ? x : y;
                                }
                                ///@dev Returns the max between `x` and `y`.
                                function _max(uint256 x, uint256 y) internal pure returns (uint256 z) {
                                    return x > y ? x : y;
                                }
                                ///@dev Returns the difference between `x` and `y` if `x > y` or zero otherwise.
                                function _subcap(uint256 x, uint256 y) internal pure returns (uint256 z) {
                                    unchecked {
                                        z = x > y ? x - y : 0;
                                    }
                                }
                                /*//////////////////////////////////
                                           Administration
                                //////////////////////////////////*/
                                /**
                                 * @notice Grants `usr` admin access to this contract.
                                 * @param usr The user address.
                                 */
                                function rely(address usr) external auth {
                                    wards[usr] = 1;
                                    emit Rely(usr);
                                }
                                /**
                                 * @notice Revokes `usr` admin access from this contract.
                                 * @param usr The user address.
                                 */
                                function deny(address usr) external auth {
                                    wards[usr] = 0;
                                    emit Deny(usr);
                                }
                                /**
                                 * @notice Grants `usr` permission to swap without any fees.
                                 * @param usr The user address.
                                 */
                                function kiss(address usr) external auth {
                                    bud[usr] = 1;
                                    emit Kiss(usr);
                                }
                                /**
                                 * @notice Revokes `usr` permission to swap without any fees.
                                 * @param usr The user address.
                                 */
                                function diss(address usr) external auth {
                                    bud[usr] = 0;
                                    emit Diss(usr);
                                }
                                /**
                                 * @notice Updates a contract parameter.
                                 * @param what The changed parameter name. ["vow"].
                                 * @param data The new value of the parameter.
                                 */
                                function file(bytes32 what, address data) external auth {
                                    if (what == "vow") {
                                        vow = data;
                                    } else {
                                        revert("DssLitePsm/file-unrecognized-param");
                                    }
                                    emit File(what, data);
                                }
                                /**
                                 * @notice Updates a contract parameter.
                                 * @dev Swapping fees may not apply due to rounding errors for small swaps where
                                 *      `gemAmt < 10**gem.decimals() / tin` or
                                 *      `gemAmt < 10**gem.decimals() / tout`.
                                 * @dev Setting `tin` or `tout` to `HALTED` effectively disables selling and buying gems respectively.
                                 * @param what The changed parameter name. ["tin", "tout", "buf"].
                                 * @param data The new value of the parameter.
                                 */
                                function file(bytes32 what, uint256 data) external auth {
                                    if (what == "tin") {
                                        require(data == HALTED || data <= WAD, "DssLitePsm/tin-out-of-range");
                                        tin = data;
                                    } else if (what == "tout") {
                                        require(data == HALTED || data <= WAD, "DssLitePsm/tout-out-of-range");
                                        tout = data;
                                    } else if (what == "buf") {
                                        buf = data;
                                    } else {
                                        revert("DssLitePsm/file-unrecognized-param");
                                    }
                                    emit File(what, data);
                                }
                                /*//////////////////////////////////
                                              Swapping
                                //////////////////////////////////*/
                                /**
                                 * @notice Function that swaps `gem` into Dai.
                                 * @dev Reverts if `tin` is set to `HALTED`.
                                 * @param usr The destination of the bought Dai.
                                 * @param gemAmt The amount of gem to sell. [`gem` precision].
                                 * @return daiOutWad The amount of Dai bought.
                                 */
                                function sellGem(address usr, uint256 gemAmt) external returns (uint256 daiOutWad) {
                                    uint256 tin_ = tin;
                                    require(tin_ != HALTED, "DssLitePsm/sell-gem-halted");
                                    daiOutWad = _sellGem(usr, gemAmt, tin_);
                                }
                                /**
                                 * @notice Function that swaps `gem` into Dai without any fees.
                                 * @dev Only users whitelisted through `kiss()` can call this function.
                                 *      Reverts if `tin` is set to `HALTED`.
                                 * @param usr The destination of the bought Dai.
                                 * @param gemAmt The amount of gem to sell. [`gem` precision].
                                 * @return daiOutWad The amount of Dai bought.
                                 */
                                function sellGemNoFee(address usr, uint256 gemAmt) external toll returns (uint256 daiOutWad) {
                                    require(tin != HALTED, "DssLitePsm/sell-gem-halted");
                                    daiOutWad = _sellGem(usr, gemAmt, 0);
                                }
                                /**
                                 * @notice Internal function that implements the logic to swaps `gem` into Dai.
                                 * @param usr The destination of the bought Dai.
                                 * @param gemAmt The amount of gem to sell. [`gem` precision].
                                 * @param tin_ The fee rate applicable to the swap [`1 * WAD` = 100%].
                                 * @return daiOutWad The amount of Dai bought.
                                 */
                                function _sellGem(address usr, uint256 gemAmt, uint256 tin_) internal returns (uint256 daiOutWad) {
                                    daiOutWad = gemAmt * to18ConversionFactor;
                                    uint256 fee;
                                    if (tin_ > 0) {
                                        fee = daiOutWad * tin_ / WAD;
                                        // At this point, `tin_ <= 1 WAD`, so an underflow is not possible.
                                        unchecked {
                                            daiOutWad -= fee;
                                        }
                                    }
                                    gem.transferFrom(msg.sender, pocket, gemAmt);
                                    // This can consume the whole balance including system fees not withdrawn.
                                    dai.transfer(usr, daiOutWad);
                                    emit SellGem(usr, gemAmt, fee);
                                }
                                /**
                                 * @notice Function that swaps Dai into `gem`.
                                 * @dev Reverts if `tout` is set to `HALTED`.
                                 * @param usr The destination of the bought gems.
                                 * @param gemAmt The amount of gem to buy. [`gem` precision].
                                 * @return daiInWad The amount of Dai required to sell.
                                 */
                                function buyGem(address usr, uint256 gemAmt) external returns (uint256 daiInWad) {
                                    uint256 tout_ = tout;
                                    require(tout_ != HALTED, "DssLitePsm/buy-gem-halted");
                                    daiInWad = _buyGem(usr, gemAmt, tout_);
                                }
                                /**
                                 * @notice Function that swaps Dai into `gem` without any fees.
                                 * @dev Only users whitelisted through `kiss()` can call this function.
                                 *      Reverts if `tout` is set to `HALTED`.
                                 * @param usr The destination of the bought gems.
                                 * @param gemAmt The amount of gem to buy. [`gem` precision].
                                 * @return daiInWad The amount of Dai required to sell.
                                 */
                                function buyGemNoFee(address usr, uint256 gemAmt) external toll returns (uint256 daiInWad) {
                                    require(tout != HALTED, "DssLitePsm/buy-gem-halted");
                                    daiInWad = _buyGem(usr, gemAmt, 0);
                                }
                                /**
                                 * @notice Internal function implementing the logic that swaps Dai into `gem`.
                                 * @param usr The destination of the bought gems.
                                 * @param gemAmt The amount of gem to buy. [`gem` precision].
                                 * @param tout_ The fee rate applicable to the swap [`1 * WAD` = 100%].
                                 * @return daiInWad The amount of Dai required to sell.
                                 */
                                function _buyGem(address usr, uint256 gemAmt, uint256 tout_) internal returns (uint256 daiInWad) {
                                    daiInWad = gemAmt * to18ConversionFactor;
                                    uint256 fee;
                                    if (tout_ > 0) {
                                        fee = daiInWad * tout_ / WAD;
                                        daiInWad += fee;
                                    }
                                    dai.transferFrom(msg.sender, address(this), daiInWad);
                                    gem.transferFrom(pocket, usr, gemAmt);
                                    emit BuyGem(usr, gemAmt, fee);
                                }
                                /*//////////////////////////////////
                                            Bookkeeping
                                //////////////////////////////////*/
                                /**
                                 * @notice Mints Dai into this contract.
                                 * @dev Both `buf`, the local and global debt ceilings limit the actual minted amount.
                                 *      Notice that `gem` donations or extraneous debt repayments can also affect the amount.
                                 * @return wad The amount of Dai minted.
                                 */
                                function fill() external returns (uint256 wad) {
                                    wad = rush();
                                    require(wad > 0, "DssLitePsm/nothing-to-fill");
                                    // The `urn` for this contract in the `Vat` is expected to have "unlimited" `ink`.
                                    vat.frob(ilk, address(this), address(0), address(this), 0, _int256(wad));
                                    daiJoin.exit(address(this), wad);
                                    emit Fill(wad);
                                }
                                /**
                                 * @notice Burns any excess of Dai from this contract.
                                 * @dev The total outstanding debt can still be larger than the debt ceiling after `trim`.
                                 *      Additional `buyGem` calls will enable further `trim` calls.
                                 * @return wad The amount of Dai burned.
                                 */
                                function trim() external returns (uint256 wad) {
                                    wad = gush();
                                    require(wad > 0, "DssLitePsm/nothing-to-trim");
                                    daiJoin.join(address(this), wad);
                                    // The `urn` for this contract in the `Vat` is expected to have "unlimited" `ink`.
                                    vat.frob(ilk, address(this), address(0), address(this), 0, -_int256(wad));
                                    emit Trim(wad);
                                }
                                /**
                                 * @notice Incorporates any outstanding accumulated fees into the surplus buffer.
                                 * @return wad The amount added to the surplus buffer.
                                 */
                                function chug() external returns (uint256 wad) {
                                    address vow_ = vow;
                                    require(vow_ != address(0), "DssLitePsm/chug-missing-vow");
                                    wad = cut();
                                    require(wad > 0, "DssLitePsm/nothing-to-chug");
                                    daiJoin.join(vow_, wad);
                                    emit Chug(wad);
                                }
                                /*//////////////////////////////////
                                              Getters
                                //////////////////////////////////*/
                                /**
                                 * @notice Returns the missing Dai that can be filled into this contract.
                                 * @return wad The amount of Dai.
                                 */
                                function rush() public view returns (uint256 wad) {
                                    (uint256 Art, uint256 rate,, uint256 line,) = vat.ilks(ilk);
                                    require(rate == RAY, "DssLitePsm/rate-not-RAY");
                                    uint256 tArt = gem.balanceOf(pocket) * to18ConversionFactor + buf;
                                    wad = _min(
                                        _min(
                                            // To avoid two extra SLOADs it assumes urn.art == ilk.Art.
                                            _subcap(tArt, Art),
                                            _subcap(line / RAY, Art)
                                        ),
                                        _subcap(vat.Line(), vat.debt()) / RAY
                                    );
                                }
                                /**
                                 * @notice Returns the excess Dai that can be trimmed from this contract.
                                 * @return wad The amount of Dai.
                                 */
                                function gush() public view returns (uint256 wad) {
                                    (uint256 Art, uint256 rate,, uint256 line,) = vat.ilks(ilk);
                                    require(rate == RAY, "DssLitePsm/rate-not-RAY");
                                    uint256 tArt = gem.balanceOf(pocket) * to18ConversionFactor + buf;
                                    wad = _min(
                                        _max(
                                            // To avoid two extra SLOADs it assumes urn.art == ilk.Art.
                                            _subcap(Art, tArt),
                                            _subcap(Art, line / RAY)
                                        ),
                                        // Cannot burn more than the current balance.
                                        dai.balanceOf(address(this))
                                    );
                                }
                                /**
                                 * @notice Returns the amount of swapping fees that can be chugged by this contract.
                                 * @dev To keep `_sellGem` gas usage low, it allows users to take pre-minted Dai up to the whole balance, regardless
                                 *      if part of it consist of collected fees.
                                 *      If there is not enough balance, it will need to wait for new pre-minted Dai to be generated or Dai swapped
                                 *      back to complete the withdrawal of fees.
                                 * @return wad The amount of Dai.
                                 */
                                function cut() public view returns (uint256 wad) {
                                    (, uint256 art) = vat.urns(ilk, address(this));
                                    uint256 cash = dai.balanceOf(address(this));
                                    wad = _min(cash, cash + gem.balanceOf(pocket) * to18ConversionFactor - art);
                                }
                                /*//////////////////////////////////
                                        Compatibility Layer
                                //////////////////////////////////*/
                                /**
                                 * @notice Returns the address of the LitePsm contract itself.
                                 * @dev LitePsm does not have an external gem join. All logic is handled internally.
                                 *      This function is required because there are some dependencies that assume every PSM has a gem join.
                                 * @return The address of this contract.
                                 */
                                function gemJoin() external view returns (address) {
                                    return address(this);
                                }
                                /**
                                 * @notice Returns the number of decimals for `gem`.
                                 * @return The number of decimals for `gem`.
                                 */
                                function dec() external view returns (uint256) {
                                    return gem.decimals();
                                }
                                /**
                                 * @notice Returns whether the contract is live or not.
                                 * @return Whether the contract is live or not.
                                 */
                                function live() external view returns (uint256) {
                                    return vat.live();
                                }
                            }
                            

                            File 7 of 17: UsdsPsmWrapper
                            // SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org>
                            // SPDX-License-Identifier: AGPL-3.0-or-later
                            //
                            // This program is free software: you can redistribute it and/or modify
                            // it under the terms of the GNU Affero General Public License as published by
                            // the Free Software Foundation, either version 3 of the License, or
                            // (at your option) any later version.
                            //
                            // This program is distributed in the hope that it will be useful,
                            // but WITHOUT ANY WARRANTY; without even the implied warranty of
                            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                            // GNU Affero General Public License for more details.
                            //
                            // You should have received a copy of the GNU Affero General Public License
                            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
                            pragma solidity ^0.8.21;
                            interface PsmLike {
                                function gem() external view returns (address);
                                function vat() external view returns (address);
                                function daiJoin() external view returns (address);
                                function pocket() external view returns (address);
                                function tin() external view returns (uint256);
                                function tout() external view returns (uint256);
                                function buf() external view returns (uint256);
                                function sellGem(address, uint256) external returns (uint256);
                                function buyGem(address, uint256) external returns (uint256);
                                function ilk() external view returns (bytes32);
                                function vow() external view returns (address);
                            }
                            interface GemLike {
                                function decimals() external view returns (uint8);
                                function approve(address, uint256) external;
                                function transferFrom(address, address, uint256) external;
                            }
                            interface DaiJoinLike {
                                function dai() external view returns (address);
                                function join(address, uint256) external;
                                function exit(address, uint256) external;
                            }
                            interface UsdsJoinLike {
                                function usds() external view returns (address);
                                function join(address, uint256) external;
                                function exit(address, uint256) external;
                            }
                            interface VatLike {
                                function hope(address) external;
                                function live() external view returns (uint256);
                            }
                            // A wrapper around the Lite PSM contract
                            contract UsdsPsmWrapper {
                                PsmLike      public   immutable psm;
                                GemLike      public   immutable gem;
                                UsdsJoinLike public   immutable usdsJoin;
                                GemLike      public   immutable usds;
                                VatLike      public   immutable vat;
                                bytes32      public   immutable ilk;    // For backwards compatibility with the Lite PSM
                                address      public   immutable pocket; // For backwards compatibility with the Lite PSM
                                uint256      public   immutable dec;    // For backwards compatibility with the Lite PSM
                                uint256      public   immutable to18ConversionFactor;
                                DaiJoinLike  internal immutable legacyDaiJoin;
                                GemLike      internal immutable legacyDai;
                                uint256 constant WAD = 10 ** 18;
                                uint256 public constant HALTED = type(uint256).max; // For backwards compatibility with the Lite PSM
                                constructor(address psm_, address usdsJoin_) {
                                    psm           = PsmLike(psm_);
                                    gem           = GemLike(psm.gem());
                                    usdsJoin      = UsdsJoinLike(usdsJoin_);
                                    usds          = GemLike(usdsJoin.usds());
                                    vat           = VatLike(psm.vat());
                                    ilk           = psm.ilk();
                                    pocket        = psm.pocket();
                                    dec           = gem.decimals();
                                    legacyDaiJoin = DaiJoinLike(psm.daiJoin());
                                    legacyDai     = GemLike(legacyDaiJoin.dai());
                                    to18ConversionFactor = 10 ** (18 - gem.decimals());
                                    legacyDai.approve(address(psm), type(uint256).max);
                                    gem.approve(address(psm), type(uint256).max);
                                    legacyDai.approve(address(legacyDaiJoin), type(uint256).max);
                                    usds.approve(address(usdsJoin), type(uint256).max);
                                    vat.hope(address(legacyDaiJoin));
                                    vat.hope(address(usdsJoin));
                                }
                                function sellGem(address usr, uint256 gemAmt) external returns (uint256 usdsOutWad) {
                                    gem.transferFrom(msg.sender, address(this), gemAmt);
                                    usdsOutWad = psm.sellGem(address(this), gemAmt);
                                    legacyDaiJoin.join(address(this), usdsOutWad);
                                    usdsJoin.exit(usr, usdsOutWad);
                                }
                                function buyGem(address usr, uint256 gemAmt) external returns (uint256 usdsInWad) {
                                    uint256 gemAmt18 = gemAmt * to18ConversionFactor;
                                    usdsInWad = gemAmt18 + gemAmt18 * psm.tout() / WAD;
                                    usds.transferFrom(msg.sender, address(this), usdsInWad);
                                    usdsJoin.join(address(this), usdsInWad);
                                    legacyDaiJoin.exit(address(this), usdsInWad);
                                    psm.buyGem(usr, gemAmt);
                                }
                                // Partial Backward Compatibility Getters With the Lite Psm
                                function vow() external view returns (address) {
                                    return psm.vow();
                                }
                                function dai() external view returns (address) {
                                    return address(usds); // Supports not changing integrating code that works with the legacy dai based lite psm
                                }
                                function gemJoin() external view returns (address) {
                                    return address(this); // Supports not changing integrating code that queries and approves the gemJoin
                                }
                                function tin() external view returns (uint256) {
                                    return psm.tin();
                                }
                                function tout() external view returns (uint256) {
                                    return psm.tout();
                                }
                                function buf() external view returns (uint256) {
                                    return psm.buf();
                                }
                                function live() external view returns (uint256) {
                                    return vat.live();
                                }
                            }
                            

                            File 8 of 17: Dai
                            // hevm: flattened sources of /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol
                            pragma solidity =0.5.12;
                            
                            ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/lib.sol
                            // This program is free software: you can redistribute it and/or modify
                            // it under the terms of the GNU General Public License as published by
                            // the Free Software Foundation, either version 3 of the License, or
                            // (at your option) any later version.
                            
                            // This program is distributed in the hope that it will be useful,
                            // but WITHOUT ANY WARRANTY; without even the implied warranty of
                            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                            // GNU General Public License for more details.
                            
                            // You should have received a copy of the GNU General Public License
                            // along with this program.  If not, see <http://www.gnu.org/licenses/>.
                            
                            /* pragma solidity 0.5.12; */
                            
                            contract LibNote {
                                event LogNote(
                                    bytes4   indexed  sig,
                                    address  indexed  usr,
                                    bytes32  indexed  arg1,
                                    bytes32  indexed  arg2,
                                    bytes             data
                                ) anonymous;
                            
                                modifier note {
                                    _;
                                    assembly {
                                        // log an 'anonymous' event with a constant 6 words of calldata
                                        // and four indexed topics: selector, caller, arg1 and arg2
                                        let mark := msize                         // end of memory ensures zero
                                        mstore(0x40, add(mark, 288))              // update free memory pointer
                                        mstore(mark, 0x20)                        // bytes type data offset
                                        mstore(add(mark, 0x20), 224)              // bytes size (padded)
                                        calldatacopy(add(mark, 0x40), 0, 224)     // bytes payload
                                        log4(mark, 288,                           // calldata
                                             shl(224, shr(224, calldataload(0))), // msg.sig
                                             caller,                              // msg.sender
                                             calldataload(4),                     // arg1
                                             calldataload(36)                     // arg2
                                            )
                                    }
                                }
                            }
                            
                            ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol
                            // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico
                            
                            // This program is free software: you can redistribute it and/or modify
                            // it under the terms of the GNU Affero General Public License as published by
                            // the Free Software Foundation, either version 3 of the License, or
                            // (at your option) any later version.
                            //
                            // This program is distributed in the hope that it will be useful,
                            // but WITHOUT ANY WARRANTY; without even the implied warranty of
                            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                            // GNU Affero General Public License for more details.
                            //
                            // You should have received a copy of the GNU Affero General Public License
                            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
                            
                            /* pragma solidity 0.5.12; */
                            
                            /* import "./lib.sol"; */
                            
                            contract Dai is LibNote {
                                // --- Auth ---
                                mapping (address => uint) public wards;
                                function rely(address guy) external note auth { wards[guy] = 1; }
                                function deny(address guy) external note auth { wards[guy] = 0; }
                                modifier auth {
                                    require(wards[msg.sender] == 1, "Dai/not-authorized");
                                    _;
                                }
                            
                                // --- ERC20 Data ---
                                string  public constant name     = "Dai Stablecoin";
                                string  public constant symbol   = "DAI";
                                string  public constant version  = "1";
                                uint8   public constant decimals = 18;
                                uint256 public totalSupply;
                            
                                mapping (address => uint)                      public balanceOf;
                                mapping (address => mapping (address => uint)) public allowance;
                                mapping (address => uint)                      public nonces;
                            
                                event Approval(address indexed src, address indexed guy, uint wad);
                                event Transfer(address indexed src, address indexed dst, uint wad);
                            
                                // --- Math ---
                                function add(uint x, uint y) internal pure returns (uint z) {
                                    require((z = x + y) >= x);
                                }
                                function sub(uint x, uint y) internal pure returns (uint z) {
                                    require((z = x - y) <= x);
                                }
                            
                                // --- EIP712 niceties ---
                                bytes32 public DOMAIN_SEPARATOR;
                                // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
                                bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
                            
                                constructor(uint256 chainId_) public {
                                    wards[msg.sender] = 1;
                                    DOMAIN_SEPARATOR = keccak256(abi.encode(
                                        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                                        keccak256(bytes(name)),
                                        keccak256(bytes(version)),
                                        chainId_,
                                        address(this)
                                    ));
                                }
                            
                                // --- Token ---
                                function transfer(address dst, uint wad) external returns (bool) {
                                    return transferFrom(msg.sender, dst, wad);
                                }
                                function transferFrom(address src, address dst, uint wad)
                                    public returns (bool)
                                {
                                    require(balanceOf[src] >= wad, "Dai/insufficient-balance");
                                    if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
                                        require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance");
                                        allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad);
                                    }
                                    balanceOf[src] = sub(balanceOf[src], wad);
                                    balanceOf[dst] = add(balanceOf[dst], wad);
                                    emit Transfer(src, dst, wad);
                                    return true;
                                }
                                function mint(address usr, uint wad) external auth {
                                    balanceOf[usr] = add(balanceOf[usr], wad);
                                    totalSupply    = add(totalSupply, wad);
                                    emit Transfer(address(0), usr, wad);
                                }
                                function burn(address usr, uint wad) external {
                                    require(balanceOf[usr] >= wad, "Dai/insufficient-balance");
                                    if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) {
                                        require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance");
                                        allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad);
                                    }
                                    balanceOf[usr] = sub(balanceOf[usr], wad);
                                    totalSupply    = sub(totalSupply, wad);
                                    emit Transfer(usr, address(0), wad);
                                }
                                function approve(address usr, uint wad) external returns (bool) {
                                    allowance[msg.sender][usr] = wad;
                                    emit Approval(msg.sender, usr, wad);
                                    return true;
                                }
                            
                                // --- Alias ---
                                function push(address usr, uint wad) external {
                                    transferFrom(msg.sender, usr, wad);
                                }
                                function pull(address usr, uint wad) external {
                                    transferFrom(usr, msg.sender, wad);
                                }
                                function move(address src, address dst, uint wad) external {
                                    transferFrom(src, dst, wad);
                                }
                            
                                // --- Approve by signature ---
                                function permit(address holder, address spender, uint256 nonce, uint256 expiry,
                                                bool allowed, uint8 v, bytes32 r, bytes32 s) external
                                {
                                    bytes32 digest =
                                        keccak256(abi.encodePacked(
                                            "\x19\x01",
                                            DOMAIN_SEPARATOR,
                                            keccak256(abi.encode(PERMIT_TYPEHASH,
                                                                 holder,
                                                                 spender,
                                                                 nonce,
                                                                 expiry,
                                                                 allowed))
                                    ));
                            
                                    require(holder != address(0), "Dai/invalid-address-0");
                                    require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit");
                                    require(expiry == 0 || now <= expiry, "Dai/permit-expired");
                                    require(nonce == nonces[holder]++, "Dai/invalid-nonce");
                                    uint wad = allowed ? uint(-1) : 0;
                                    allowance[holder][spender] = wad;
                                    emit Approval(holder, spender, wad);
                                }
                            }

                            File 9 of 17: Vat
                            // hevm: flattened sources of /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/vat.sol
                            pragma solidity =0.5.12;
                            
                            ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/vat.sol
                            /// vat.sol -- Dai CDP database
                            
                            // Copyright (C) 2018 Rain <[email protected]>
                            //
                            // This program is free software: you can redistribute it and/or modify
                            // it under the terms of the GNU Affero General Public License as published by
                            // the Free Software Foundation, either version 3 of the License, or
                            // (at your option) any later version.
                            //
                            // This program is distributed in the hope that it will be useful,
                            // but WITHOUT ANY WARRANTY; without even the implied warranty of
                            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                            // GNU Affero General Public License for more details.
                            //
                            // You should have received a copy of the GNU Affero General Public License
                            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
                            
                            /* pragma solidity 0.5.12; */
                            
                            contract Vat {
                                // --- Auth ---
                                mapping (address => uint) public wards;
                                function rely(address usr) external note auth { require(live == 1, "Vat/not-live"); wards[usr] = 1; }
                                function deny(address usr) external note auth { require(live == 1, "Vat/not-live"); wards[usr] = 0; }
                                modifier auth {
                                    require(wards[msg.sender] == 1, "Vat/not-authorized");
                                    _;
                                }
                            
                                mapping(address => mapping (address => uint)) public can;
                                function hope(address usr) external note { can[msg.sender][usr] = 1; }
                                function nope(address usr) external note { can[msg.sender][usr] = 0; }
                                function wish(address bit, address usr) internal view returns (bool) {
                                    return either(bit == usr, can[bit][usr] == 1);
                                }
                            
                                // --- Data ---
                                struct Ilk {
                                    uint256 Art;   // Total Normalised Debt     [wad]
                                    uint256 rate;  // Accumulated Rates         [ray]
                                    uint256 spot;  // Price with Safety Margin  [ray]
                                    uint256 line;  // Debt Ceiling              [rad]
                                    uint256 dust;  // Urn Debt Floor            [rad]
                                }
                                struct Urn {
                                    uint256 ink;   // Locked Collateral  [wad]
                                    uint256 art;   // Normalised Debt    [wad]
                                }
                            
                                mapping (bytes32 => Ilk)                       public ilks;
                                mapping (bytes32 => mapping (address => Urn )) public urns;
                                mapping (bytes32 => mapping (address => uint)) public gem;  // [wad]
                                mapping (address => uint256)                   public dai;  // [rad]
                                mapping (address => uint256)                   public sin;  // [rad]
                            
                                uint256 public debt;  // Total Dai Issued    [rad]
                                uint256 public vice;  // Total Unbacked Dai  [rad]
                                uint256 public Line;  // Total Debt Ceiling  [rad]
                                uint256 public live;  // Access Flag
                            
                                // --- Logs ---
                                event LogNote(
                                    bytes4   indexed  sig,
                                    bytes32  indexed  arg1,
                                    bytes32  indexed  arg2,
                                    bytes32  indexed  arg3,
                                    bytes             data
                                ) anonymous;
                            
                                modifier note {
                                    _;
                                    assembly {
                                        // log an 'anonymous' event with a constant 6 words of calldata
                                        // and four indexed topics: the selector and the first three args
                                        let mark := msize                         // end of memory ensures zero
                                        mstore(0x40, add(mark, 288))              // update free memory pointer
                                        mstore(mark, 0x20)                        // bytes type data offset
                                        mstore(add(mark, 0x20), 224)              // bytes size (padded)
                                        calldatacopy(add(mark, 0x40), 0, 224)     // bytes payload
                                        log4(mark, 288,                           // calldata
                                             shl(224, shr(224, calldataload(0))), // msg.sig
                                             calldataload(4),                     // arg1
                                             calldataload(36),                    // arg2
                                             calldataload(68)                     // arg3
                                            )
                                    }
                                }
                            
                                // --- Init ---
                                constructor() public {
                                    wards[msg.sender] = 1;
                                    live = 1;
                                }
                            
                                // --- Math ---
                                function add(uint x, int y) internal pure returns (uint z) {
                                    z = x + uint(y);
                                    require(y >= 0 || z <= x);
                                    require(y <= 0 || z >= x);
                                }
                                function sub(uint x, int y) internal pure returns (uint z) {
                                    z = x - uint(y);
                                    require(y <= 0 || z <= x);
                                    require(y >= 0 || z >= x);
                                }
                                function mul(uint x, int y) internal pure returns (int z) {
                                    z = int(x) * y;
                                    require(int(x) >= 0);
                                    require(y == 0 || z / y == int(x));
                                }
                                function add(uint x, uint y) internal pure returns (uint z) {
                                    require((z = x + y) >= x);
                                }
                                function sub(uint x, uint y) internal pure returns (uint z) {
                                    require((z = x - y) <= x);
                                }
                                function mul(uint x, uint y) internal pure returns (uint z) {
                                    require(y == 0 || (z = x * y) / y == x);
                                }
                            
                                // --- Administration ---
                                function init(bytes32 ilk) external note auth {
                                    require(ilks[ilk].rate == 0, "Vat/ilk-already-init");
                                    ilks[ilk].rate = 10 ** 27;
                                }
                                function file(bytes32 what, uint data) external note auth {
                                    require(live == 1, "Vat/not-live");
                                    if (what == "Line") Line = data;
                                    else revert("Vat/file-unrecognized-param");
                                }
                                function file(bytes32 ilk, bytes32 what, uint data) external note auth {
                                    require(live == 1, "Vat/not-live");
                                    if (what == "spot") ilks[ilk].spot = data;
                                    else if (what == "line") ilks[ilk].line = data;
                                    else if (what == "dust") ilks[ilk].dust = data;
                                    else revert("Vat/file-unrecognized-param");
                                }
                                function cage() external note auth {
                                    live = 0;
                                }
                            
                                // --- Fungibility ---
                                function slip(bytes32 ilk, address usr, int256 wad) external note auth {
                                    gem[ilk][usr] = add(gem[ilk][usr], wad);
                                }
                                function flux(bytes32 ilk, address src, address dst, uint256 wad) external note {
                                    require(wish(src, msg.sender), "Vat/not-allowed");
                                    gem[ilk][src] = sub(gem[ilk][src], wad);
                                    gem[ilk][dst] = add(gem[ilk][dst], wad);
                                }
                                function move(address src, address dst, uint256 rad) external note {
                                    require(wish(src, msg.sender), "Vat/not-allowed");
                                    dai[src] = sub(dai[src], rad);
                                    dai[dst] = add(dai[dst], rad);
                                }
                            
                                function either(bool x, bool y) internal pure returns (bool z) {
                                    assembly{ z := or(x, y)}
                                }
                                function both(bool x, bool y) internal pure returns (bool z) {
                                    assembly{ z := and(x, y)}
                                }
                            
                                // --- CDP Manipulation ---
                                function frob(bytes32 i, address u, address v, address w, int dink, int dart) external note {
                                    // system is live
                                    require(live == 1, "Vat/not-live");
                            
                                    Urn memory urn = urns[i][u];
                                    Ilk memory ilk = ilks[i];
                                    // ilk has been initialised
                                    require(ilk.rate != 0, "Vat/ilk-not-init");
                            
                                    urn.ink = add(urn.ink, dink);
                                    urn.art = add(urn.art, dart);
                                    ilk.Art = add(ilk.Art, dart);
                            
                                    int dtab = mul(ilk.rate, dart);
                                    uint tab = mul(ilk.rate, urn.art);
                                    debt     = add(debt, dtab);
                            
                                    // either debt has decreased, or debt ceilings are not exceeded
                                    require(either(dart <= 0, both(mul(ilk.Art, ilk.rate) <= ilk.line, debt <= Line)), "Vat/ceiling-exceeded");
                                    // urn is either less risky than before, or it is safe
                                    require(either(both(dart <= 0, dink >= 0), tab <= mul(urn.ink, ilk.spot)), "Vat/not-safe");
                            
                                    // urn is either more safe, or the owner consents
                                    require(either(both(dart <= 0, dink >= 0), wish(u, msg.sender)), "Vat/not-allowed-u");
                                    // collateral src consents
                                    require(either(dink <= 0, wish(v, msg.sender)), "Vat/not-allowed-v");
                                    // debt dst consents
                                    require(either(dart >= 0, wish(w, msg.sender)), "Vat/not-allowed-w");
                            
                                    // urn has no debt, or a non-dusty amount
                                    require(either(urn.art == 0, tab >= ilk.dust), "Vat/dust");
                            
                                    gem[i][v] = sub(gem[i][v], dink);
                                    dai[w]    = add(dai[w],    dtab);
                            
                                    urns[i][u] = urn;
                                    ilks[i]    = ilk;
                                }
                                // --- CDP Fungibility ---
                                function fork(bytes32 ilk, address src, address dst, int dink, int dart) external note {
                                    Urn storage u = urns[ilk][src];
                                    Urn storage v = urns[ilk][dst];
                                    Ilk storage i = ilks[ilk];
                            
                                    u.ink = sub(u.ink, dink);
                                    u.art = sub(u.art, dart);
                                    v.ink = add(v.ink, dink);
                                    v.art = add(v.art, dart);
                            
                                    uint utab = mul(u.art, i.rate);
                                    uint vtab = mul(v.art, i.rate);
                            
                                    // both sides consent
                                    require(both(wish(src, msg.sender), wish(dst, msg.sender)), "Vat/not-allowed");
                            
                                    // both sides safe
                                    require(utab <= mul(u.ink, i.spot), "Vat/not-safe-src");
                                    require(vtab <= mul(v.ink, i.spot), "Vat/not-safe-dst");
                            
                                    // both sides non-dusty
                                    require(either(utab >= i.dust, u.art == 0), "Vat/dust-src");
                                    require(either(vtab >= i.dust, v.art == 0), "Vat/dust-dst");
                                }
                                // --- CDP Confiscation ---
                                function grab(bytes32 i, address u, address v, address w, int dink, int dart) external note auth {
                                    Urn storage urn = urns[i][u];
                                    Ilk storage ilk = ilks[i];
                            
                                    urn.ink = add(urn.ink, dink);
                                    urn.art = add(urn.art, dart);
                                    ilk.Art = add(ilk.Art, dart);
                            
                                    int dtab = mul(ilk.rate, dart);
                            
                                    gem[i][v] = sub(gem[i][v], dink);
                                    sin[w]    = sub(sin[w],    dtab);
                                    vice      = sub(vice,      dtab);
                                }
                            
                                // --- Settlement ---
                                function heal(uint rad) external note {
                                    address u = msg.sender;
                                    sin[u] = sub(sin[u], rad);
                                    dai[u] = sub(dai[u], rad);
                                    vice   = sub(vice,   rad);
                                    debt   = sub(debt,   rad);
                                }
                                function suck(address u, address v, uint rad) external note auth {
                                    sin[u] = add(sin[u], rad);
                                    dai[v] = add(dai[v], rad);
                                    vice   = add(vice,   rad);
                                    debt   = add(debt,   rad);
                                }
                            
                                // --- Rates ---
                                function fold(bytes32 i, address u, int rate) external note auth {
                                    require(live == 1, "Vat/not-live");
                                    Ilk storage ilk = ilks[i];
                                    ilk.rate = add(ilk.rate, rate);
                                    int rad  = mul(ilk.Art, rate);
                                    dai[u]   = add(dai[u], rad);
                                    debt     = add(debt,   rad);
                                }
                            }

                            File 10 of 17: DaiJoin
                            // hevm: flattened sources of /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/join.sol
                            pragma solidity =0.5.12;
                            
                            ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/lib.sol
                            // This program is free software: you can redistribute it and/or modify
                            // it under the terms of the GNU General Public License as published by
                            // the Free Software Foundation, either version 3 of the License, or
                            // (at your option) any later version.
                            
                            // This program is distributed in the hope that it will be useful,
                            // but WITHOUT ANY WARRANTY; without even the implied warranty of
                            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                            // GNU General Public License for more details.
                            
                            // You should have received a copy of the GNU General Public License
                            // along with this program.  If not, see <http://www.gnu.org/licenses/>.
                            
                            /* pragma solidity 0.5.12; */
                            
                            contract LibNote {
                                event LogNote(
                                    bytes4   indexed  sig,
                                    address  indexed  usr,
                                    bytes32  indexed  arg1,
                                    bytes32  indexed  arg2,
                                    bytes             data
                                ) anonymous;
                            
                                modifier note {
                                    _;
                                    assembly {
                                        // log an 'anonymous' event with a constant 6 words of calldata
                                        // and four indexed topics: selector, caller, arg1 and arg2
                                        let mark := msize                         // end of memory ensures zero
                                        mstore(0x40, add(mark, 288))              // update free memory pointer
                                        mstore(mark, 0x20)                        // bytes type data offset
                                        mstore(add(mark, 0x20), 224)              // bytes size (padded)
                                        calldatacopy(add(mark, 0x40), 0, 224)     // bytes payload
                                        log4(mark, 288,                           // calldata
                                             shl(224, shr(224, calldataload(0))), // msg.sig
                                             caller,                              // msg.sender
                                             calldataload(4),                     // arg1
                                             calldataload(36)                     // arg2
                                            )
                                    }
                                }
                            }
                            
                            ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/join.sol
                            /// join.sol -- Basic token adapters
                            
                            // Copyright (C) 2018 Rain <[email protected]>
                            //
                            // This program is free software: you can redistribute it and/or modify
                            // it under the terms of the GNU Affero General Public License as published by
                            // the Free Software Foundation, either version 3 of the License, or
                            // (at your option) any later version.
                            //
                            // This program is distributed in the hope that it will be useful,
                            // but WITHOUT ANY WARRANTY; without even the implied warranty of
                            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                            // GNU Affero General Public License for more details.
                            //
                            // You should have received a copy of the GNU Affero General Public License
                            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
                            
                            /* pragma solidity 0.5.12; */
                            
                            /* import "./lib.sol"; */
                            
                            contract GemLike {
                                function decimals() public view returns (uint);
                                function transfer(address,uint) external returns (bool);
                                function transferFrom(address,address,uint) external returns (bool);
                            }
                            
                            contract DSTokenLike {
                                function mint(address,uint) external;
                                function burn(address,uint) external;
                            }
                            
                            contract VatLike {
                                function slip(bytes32,address,int) external;
                                function move(address,address,uint) external;
                            }
                            
                            /*
                                Here we provide *adapters* to connect the Vat to arbitrary external
                                token implementations, creating a bounded context for the Vat. The
                                adapters here are provided as working examples:
                            
                                  - `GemJoin`: For well behaved ERC20 tokens, with simple transfer
                                               semantics.
                            
                                  - `ETHJoin`: For native Ether.
                            
                                  - `DaiJoin`: For connecting internal Dai balances to an external
                                               `DSToken` implementation.
                            
                                In practice, adapter implementations will be varied and specific to
                                individual collateral types, accounting for different transfer
                                semantics and token standards.
                            
                                Adapters need to implement two basic methods:
                            
                                  - `join`: enter collateral into the system
                                  - `exit`: remove collateral from the system
                            
                            */
                            
                            contract GemJoin is LibNote {
                                // --- Auth ---
                                mapping (address => uint) public wards;
                                function rely(address usr) external note auth { wards[usr] = 1; }
                                function deny(address usr) external note auth { wards[usr] = 0; }
                                modifier auth {
                                    require(wards[msg.sender] == 1, "GemJoin/not-authorized");
                                    _;
                                }
                            
                                VatLike public vat;
                                bytes32 public ilk;
                                GemLike public gem;
                                uint    public dec;
                                uint    public live;  // Access Flag
                            
                                constructor(address vat_, bytes32 ilk_, address gem_) public {
                                    wards[msg.sender] = 1;
                                    live = 1;
                                    vat = VatLike(vat_);
                                    ilk = ilk_;
                                    gem = GemLike(gem_);
                                    dec = gem.decimals();
                                }
                                function cage() external note auth {
                                    live = 0;
                                }
                                function join(address usr, uint wad) external note {
                                    require(live == 1, "GemJoin/not-live");
                                    require(int(wad) >= 0, "GemJoin/overflow");
                                    vat.slip(ilk, usr, int(wad));
                                    require(gem.transferFrom(msg.sender, address(this), wad), "GemJoin/failed-transfer");
                                }
                                function exit(address usr, uint wad) external note {
                                    require(wad <= 2 ** 255, "GemJoin/overflow");
                                    vat.slip(ilk, msg.sender, -int(wad));
                                    require(gem.transfer(usr, wad), "GemJoin/failed-transfer");
                                }
                            }
                            
                            contract ETHJoin is LibNote {
                                // --- Auth ---
                                mapping (address => uint) public wards;
                                function rely(address usr) external note auth { wards[usr] = 1; }
                                function deny(address usr) external note auth { wards[usr] = 0; }
                                modifier auth {
                                    require(wards[msg.sender] == 1, "ETHJoin/not-authorized");
                                    _;
                                }
                            
                                VatLike public vat;
                                bytes32 public ilk;
                                uint    public live;  // Access Flag
                            
                                constructor(address vat_, bytes32 ilk_) public {
                                    wards[msg.sender] = 1;
                                    live = 1;
                                    vat = VatLike(vat_);
                                    ilk = ilk_;
                                }
                                function cage() external note auth {
                                    live = 0;
                                }
                                function join(address usr) external payable note {
                                    require(live == 1, "ETHJoin/not-live");
                                    require(int(msg.value) >= 0, "ETHJoin/overflow");
                                    vat.slip(ilk, usr, int(msg.value));
                                }
                                function exit(address payable usr, uint wad) external note {
                                    require(int(wad) >= 0, "ETHJoin/overflow");
                                    vat.slip(ilk, msg.sender, -int(wad));
                                    usr.transfer(wad);
                                }
                            }
                            
                            contract DaiJoin is LibNote {
                                // --- Auth ---
                                mapping (address => uint) public wards;
                                function rely(address usr) external note auth { wards[usr] = 1; }
                                function deny(address usr) external note auth { wards[usr] = 0; }
                                modifier auth {
                                    require(wards[msg.sender] == 1, "DaiJoin/not-authorized");
                                    _;
                                }
                            
                                VatLike public vat;
                                DSTokenLike public dai;
                                uint    public live;  // Access Flag
                            
                                constructor(address vat_, address dai_) public {
                                    wards[msg.sender] = 1;
                                    live = 1;
                                    vat = VatLike(vat_);
                                    dai = DSTokenLike(dai_);
                                }
                                function cage() external note auth {
                                    live = 0;
                                }
                                uint constant ONE = 10 ** 27;
                                function mul(uint x, uint y) internal pure returns (uint z) {
                                    require(y == 0 || (z = x * y) / y == x);
                                }
                                function join(address usr, uint wad) external note {
                                    vat.move(address(this), usr, mul(ONE, wad));
                                    dai.burn(msg.sender, wad);
                                }
                                function exit(address usr, uint wad) external note {
                                    require(live == 1, "DaiJoin/not-live");
                                    vat.move(msg.sender, address(this), mul(ONE, wad));
                                    dai.mint(usr, wad);
                                }
                            }

                            File 11 of 17: ERC1967Proxy
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Proxy.sol)
                            pragma solidity ^0.8.20;
                            import {Proxy} from "../Proxy.sol";
                            import {ERC1967Utils} from "./ERC1967Utils.sol";
                            /**
                             * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
                             * implementation address that can be changed. This address is stored in storage in the location specified by
                             * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
                             * implementation behind the proxy.
                             */
                            contract ERC1967Proxy is Proxy {
                                /**
                                 * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.
                                 *
                                 * If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an
                                 * encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.
                                 *
                                 * Requirements:
                                 *
                                 * - If `data` is empty, `msg.value` must be zero.
                                 */
                                constructor(address implementation, bytes memory _data) payable {
                                    ERC1967Utils.upgradeToAndCall(implementation, _data);
                                }
                                /**
                                 * @dev Returns the current implementation address.
                                 *
                                 * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
                                 * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                                 * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
                                 */
                                function _implementation() internal view virtual override returns (address) {
                                    return ERC1967Utils.getImplementation();
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol)
                            pragma solidity ^0.8.20;
                            /**
                             * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
                             * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
                             * be specified by overriding the virtual {_implementation} function.
                             *
                             * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
                             * different contract through the {_delegate} function.
                             *
                             * The success and return data of the delegated call will be returned back to the caller of the proxy.
                             */
                            abstract contract Proxy {
                                /**
                                 * @dev Delegates the current call to `implementation`.
                                 *
                                 * This function does not return to its internal call site, it will return directly to the external caller.
                                 */
                                function _delegate(address implementation) internal virtual {
                                    assembly {
                                        // Copy msg.data. We take full control of memory in this inline assembly
                                        // block because it will not return to Solidity code. We overwrite the
                                        // Solidity scratch pad at memory position 0.
                                        calldatacopy(0, 0, calldatasize())
                                        // Call the implementation.
                                        // out and outsize are 0 because we don't know the size yet.
                                        let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                                        // Copy the returned data.
                                        returndatacopy(0, 0, returndatasize())
                                        switch result
                                        // delegatecall returns 0 on error.
                                        case 0 {
                                            revert(0, returndatasize())
                                        }
                                        default {
                                            return(0, returndatasize())
                                        }
                                    }
                                }
                                /**
                                 * @dev This is a virtual function that should be overridden so it returns the address to which the fallback
                                 * function and {_fallback} should delegate.
                                 */
                                function _implementation() internal view virtual returns (address);
                                /**
                                 * @dev Delegates the current call to the address returned by `_implementation()`.
                                 *
                                 * This function does not return to its internal call site, it will return directly to the external caller.
                                 */
                                function _fallback() internal virtual {
                                    _delegate(_implementation());
                                }
                                /**
                                 * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
                                 * function in the contract matches the call data.
                                 */
                                fallback() external payable virtual {
                                    _fallback();
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol)
                            pragma solidity ^0.8.20;
                            import {IBeacon} from "../beacon/IBeacon.sol";
                            import {Address} from "../../utils/Address.sol";
                            import {StorageSlot} from "../../utils/StorageSlot.sol";
                            /**
                             * @dev This abstract contract provides getters and event emitting update functions for
                             * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
                             */
                            library ERC1967Utils {
                                // We re-declare ERC-1967 events here because they can't be used directly from IERC1967.
                                // This will be fixed in Solidity 0.8.21. At that point we should remove these events.
                                /**
                                 * @dev Emitted when the implementation is upgraded.
                                 */
                                event Upgraded(address indexed implementation);
                                /**
                                 * @dev Emitted when the admin account has changed.
                                 */
                                event AdminChanged(address previousAdmin, address newAdmin);
                                /**
                                 * @dev Emitted when the beacon is changed.
                                 */
                                event BeaconUpgraded(address indexed beacon);
                                /**
                                 * @dev Storage slot with the address of the current implementation.
                                 * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
                                 */
                                // solhint-disable-next-line private-vars-leading-underscore
                                bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                                /**
                                 * @dev The `implementation` of the proxy is invalid.
                                 */
                                error ERC1967InvalidImplementation(address implementation);
                                /**
                                 * @dev The `admin` of the proxy is invalid.
                                 */
                                error ERC1967InvalidAdmin(address admin);
                                /**
                                 * @dev The `beacon` of the proxy is invalid.
                                 */
                                error ERC1967InvalidBeacon(address beacon);
                                /**
                                 * @dev An upgrade function sees `msg.value > 0` that may be lost.
                                 */
                                error ERC1967NonPayable();
                                /**
                                 * @dev Returns the current implementation address.
                                 */
                                function getImplementation() internal view returns (address) {
                                    return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
                                }
                                /**
                                 * @dev Stores a new address in the EIP1967 implementation slot.
                                 */
                                function _setImplementation(address newImplementation) private {
                                    if (newImplementation.code.length == 0) {
                                        revert ERC1967InvalidImplementation(newImplementation);
                                    }
                                    StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
                                }
                                /**
                                 * @dev Performs implementation upgrade with additional setup call if data is nonempty.
                                 * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
                                 * to avoid stuck value in the contract.
                                 *
                                 * Emits an {IERC1967-Upgraded} event.
                                 */
                                function upgradeToAndCall(address newImplementation, bytes memory data) internal {
                                    _setImplementation(newImplementation);
                                    emit Upgraded(newImplementation);
                                    if (data.length > 0) {
                                        Address.functionDelegateCall(newImplementation, data);
                                    } else {
                                        _checkNonPayable();
                                    }
                                }
                                /**
                                 * @dev Storage slot with the admin of the contract.
                                 * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
                                 */
                                // solhint-disable-next-line private-vars-leading-underscore
                                bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                                /**
                                 * @dev Returns the current admin.
                                 *
                                 * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
                                 * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                                 * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
                                 */
                                function getAdmin() internal view returns (address) {
                                    return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
                                }
                                /**
                                 * @dev Stores a new address in the EIP1967 admin slot.
                                 */
                                function _setAdmin(address newAdmin) private {
                                    if (newAdmin == address(0)) {
                                        revert ERC1967InvalidAdmin(address(0));
                                    }
                                    StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
                                }
                                /**
                                 * @dev Changes the admin of the proxy.
                                 *
                                 * Emits an {IERC1967-AdminChanged} event.
                                 */
                                function changeAdmin(address newAdmin) internal {
                                    emit AdminChanged(getAdmin(), newAdmin);
                                    _setAdmin(newAdmin);
                                }
                                /**
                                 * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
                                 * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
                                 */
                                // solhint-disable-next-line private-vars-leading-underscore
                                bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
                                /**
                                 * @dev Returns the current beacon.
                                 */
                                function getBeacon() internal view returns (address) {
                                    return StorageSlot.getAddressSlot(BEACON_SLOT).value;
                                }
                                /**
                                 * @dev Stores a new beacon in the EIP1967 beacon slot.
                                 */
                                function _setBeacon(address newBeacon) private {
                                    if (newBeacon.code.length == 0) {
                                        revert ERC1967InvalidBeacon(newBeacon);
                                    }
                                    StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
                                    address beaconImplementation = IBeacon(newBeacon).implementation();
                                    if (beaconImplementation.code.length == 0) {
                                        revert ERC1967InvalidImplementation(beaconImplementation);
                                    }
                                }
                                /**
                                 * @dev Change the beacon and trigger a setup call if data is nonempty.
                                 * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
                                 * to avoid stuck value in the contract.
                                 *
                                 * Emits an {IERC1967-BeaconUpgraded} event.
                                 *
                                 * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
                                 * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
                                 * efficiency.
                                 */
                                function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
                                    _setBeacon(newBeacon);
                                    emit BeaconUpgraded(newBeacon);
                                    if (data.length > 0) {
                                        Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                                    } else {
                                        _checkNonPayable();
                                    }
                                }
                                /**
                                 * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
                                 * if an upgrade doesn't perform an initialization call.
                                 */
                                function _checkNonPayable() private {
                                    if (msg.value > 0) {
                                        revert ERC1967NonPayable();
                                    }
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)
                            pragma solidity ^0.8.20;
                            /**
                             * @dev This is the interface that {BeaconProxy} expects of its beacon.
                             */
                            interface IBeacon {
                                /**
                                 * @dev Must return an address that can be used as a delegate call target.
                                 *
                                 * {UpgradeableBeacon} will check that this address is a contract.
                                 */
                                function implementation() external view returns (address);
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
                            pragma solidity ^0.8.20;
                            /**
                             * @dev Collection of functions related to the address type
                             */
                            library Address {
                                /**
                                 * @dev The ETH balance of the account is not enough to perform the operation.
                                 */
                                error AddressInsufficientBalance(address account);
                                /**
                                 * @dev There's no code at `target` (it is not a contract).
                                 */
                                error AddressEmptyCode(address target);
                                /**
                                 * @dev A call to an address target failed. The target may have reverted.
                                 */
                                error FailedInnerCall();
                                /**
                                 * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                                 * `recipient`, forwarding all available gas and reverting on errors.
                                 *
                                 * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                                 * of certain opcodes, possibly making contracts go over the 2300 gas limit
                                 * imposed by `transfer`, making them unable to receive funds via
                                 * `transfer`. {sendValue} removes this limitation.
                                 *
                                 * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                                 *
                                 * IMPORTANT: because control is transferred to `recipient`, care must be
                                 * taken to not create reentrancy vulnerabilities. Consider using
                                 * {ReentrancyGuard} or the
                                 * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                                 */
                                function sendValue(address payable recipient, uint256 amount) internal {
                                    if (address(this).balance < amount) {
                                        revert AddressInsufficientBalance(address(this));
                                    }
                                    (bool success, ) = recipient.call{value: amount}("");
                                    if (!success) {
                                        revert FailedInnerCall();
                                    }
                                }
                                /**
                                 * @dev Performs a Solidity function call using a low level `call`. A
                                 * plain `call` is an unsafe replacement for a function call: use this
                                 * function instead.
                                 *
                                 * If `target` reverts with a revert reason or custom error, it is bubbled
                                 * up by this function (like regular Solidity function calls). However, if
                                 * the call reverted with no returned reason, this function reverts with a
                                 * {FailedInnerCall} error.
                                 *
                                 * Returns the raw returned data. To convert to the expected return value,
                                 * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                                 *
                                 * Requirements:
                                 *
                                 * - `target` must be a contract.
                                 * - calling `target` with `data` must not revert.
                                 */
                                function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                                    return functionCallWithValue(target, data, 0);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but also transferring `value` wei to `target`.
                                 *
                                 * Requirements:
                                 *
                                 * - the calling contract must have an ETH balance of at least `value`.
                                 * - the called Solidity function must be `payable`.
                                 */
                                function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                                    if (address(this).balance < value) {
                                        revert AddressInsufficientBalance(address(this));
                                    }
                                    (bool success, bytes memory returndata) = target.call{value: value}(data);
                                    return verifyCallResultFromTarget(target, success, returndata);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but performing a static call.
                                 */
                                function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                                    (bool success, bytes memory returndata) = target.staticcall(data);
                                    return verifyCallResultFromTarget(target, success, returndata);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but performing a delegate call.
                                 */
                                function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                                    (bool success, bytes memory returndata) = target.delegatecall(data);
                                    return verifyCallResultFromTarget(target, success, returndata);
                                }
                                /**
                                 * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
                                 * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
                                 * unsuccessful call.
                                 */
                                function verifyCallResultFromTarget(
                                    address target,
                                    bool success,
                                    bytes memory returndata
                                ) internal view returns (bytes memory) {
                                    if (!success) {
                                        _revert(returndata);
                                    } else {
                                        // only check if target is a contract if the call was successful and the return data is empty
                                        // otherwise we already know that it was a contract
                                        if (returndata.length == 0 && target.code.length == 0) {
                                            revert AddressEmptyCode(target);
                                        }
                                        return returndata;
                                    }
                                }
                                /**
                                 * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
                                 * revert reason or with a default {FailedInnerCall} error.
                                 */
                                function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
                                    if (!success) {
                                        _revert(returndata);
                                    } else {
                                        return returndata;
                                    }
                                }
                                /**
                                 * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
                                 */
                                function _revert(bytes memory returndata) private pure {
                                    // Look for revert reason and bubble it up if present
                                    if (returndata.length > 0) {
                                        // The easiest way to bubble the revert reason is using memory via assembly
                                        /// @solidity memory-safe-assembly
                                        assembly {
                                            let returndata_size := mload(returndata)
                                            revert(add(32, returndata), returndata_size)
                                        }
                                    } else {
                                        revert FailedInnerCall();
                                    }
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
                            // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
                            pragma solidity ^0.8.20;
                            /**
                             * @dev Library for reading and writing primitive types to specific storage slots.
                             *
                             * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
                             * This library helps with reading and writing to such slots without the need for inline assembly.
                             *
                             * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
                             *
                             * Example usage to set ERC1967 implementation slot:
                             * ```solidity
                             * contract ERC1967 {
                             *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                             *
                             *     function _getImplementation() internal view returns (address) {
                             *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                             *     }
                             *
                             *     function _setImplementation(address newImplementation) internal {
                             *         require(newImplementation.code.length > 0);
                             *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                             *     }
                             * }
                             * ```
                             */
                            library StorageSlot {
                                struct AddressSlot {
                                    address value;
                                }
                                struct BooleanSlot {
                                    bool value;
                                }
                                struct Bytes32Slot {
                                    bytes32 value;
                                }
                                struct Uint256Slot {
                                    uint256 value;
                                }
                                struct StringSlot {
                                    string value;
                                }
                                struct BytesSlot {
                                    bytes value;
                                }
                                /**
                                 * @dev Returns an `AddressSlot` with member `value` located at `slot`.
                                 */
                                function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
                                 */
                                function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
                                 */
                                function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
                                 */
                                function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `StringSlot` with member `value` located at `slot`.
                                 */
                                function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
                                 */
                                function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := store.slot
                                    }
                                }
                                /**
                                 * @dev Returns an `BytesSlot` with member `value` located at `slot`.
                                 */
                                function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
                                 */
                                function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := store.slot
                                    }
                                }
                            }
                            

                            File 12 of 17: UsdsJoin
                            // SPDX-License-Identifier: AGPL-3.0-or-later
                            /// UsdsJoin.sol -- Usds adapter
                            // Copyright (C) 2018 Rain <[email protected]>
                            // Copyright (C) 2023 Dai Foundation
                            //
                            // This program is free software: you can redistribute it and/or modify
                            // it under the terms of the GNU Affero General Public License as published by
                            // the Free Software Foundation, either version 3 of the License, or
                            // (at your option) any later version.
                            //
                            // This program is distributed in the hope that it will be useful,
                            // but WITHOUT ANY WARRANTY; without even the implied warranty of
                            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                            // GNU Affero General Public License for more details.
                            //
                            // You should have received a copy of the GNU Affero General Public License
                            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
                            pragma solidity ^0.8.21;
                            interface UsdsLike {
                                function burn(address,uint256) external;
                                function mint(address,uint256) external;
                            }
                            interface VatLike {
                                function move(address,address,uint256) external;
                            }
                            contract UsdsJoin {
                                VatLike  public immutable vat;  // CDP Engine
                                UsdsLike public immutable usds; // Stablecoin Token
                                uint256 constant RAY = 10 ** 27;
                                // --- Events ---
                                event Join(address indexed caller, address indexed usr, uint256 wad);
                                event Exit(address indexed caller, address indexed usr, uint256 wad);
                                constructor(address vat_, address usds_) {
                                    vat  = VatLike(vat_);
                                    usds = UsdsLike(usds_);
                                }
                                function join(address usr, uint256 wad) external {
                                    vat.move(address(this), usr, RAY * wad);
                                    usds.burn(msg.sender, wad);
                                    emit Join(msg.sender, usr, wad);
                                }
                                function exit(address usr, uint256 wad) external {
                                    vat.move(msg.sender, address(this), RAY * wad);
                                    usds.mint(usr, wad);
                                    emit Exit(msg.sender, usr, wad);
                                }
                                // To fully cover daiJoin abi
                                function dai() external view returns (address) {
                                    return address(usds);
                                }
                            }
                            

                            File 13 of 17: ERC1967Proxy
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Proxy.sol)
                            pragma solidity ^0.8.20;
                            import {Proxy} from "../Proxy.sol";
                            import {ERC1967Utils} from "./ERC1967Utils.sol";
                            /**
                             * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
                             * implementation address that can be changed. This address is stored in storage in the location specified by
                             * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
                             * implementation behind the proxy.
                             */
                            contract ERC1967Proxy is Proxy {
                                /**
                                 * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.
                                 *
                                 * If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an
                                 * encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.
                                 *
                                 * Requirements:
                                 *
                                 * - If `data` is empty, `msg.value` must be zero.
                                 */
                                constructor(address implementation, bytes memory _data) payable {
                                    ERC1967Utils.upgradeToAndCall(implementation, _data);
                                }
                                /**
                                 * @dev Returns the current implementation address.
                                 *
                                 * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
                                 * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                                 * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
                                 */
                                function _implementation() internal view virtual override returns (address) {
                                    return ERC1967Utils.getImplementation();
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol)
                            pragma solidity ^0.8.20;
                            /**
                             * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
                             * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
                             * be specified by overriding the virtual {_implementation} function.
                             *
                             * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
                             * different contract through the {_delegate} function.
                             *
                             * The success and return data of the delegated call will be returned back to the caller of the proxy.
                             */
                            abstract contract Proxy {
                                /**
                                 * @dev Delegates the current call to `implementation`.
                                 *
                                 * This function does not return to its internal call site, it will return directly to the external caller.
                                 */
                                function _delegate(address implementation) internal virtual {
                                    assembly {
                                        // Copy msg.data. We take full control of memory in this inline assembly
                                        // block because it will not return to Solidity code. We overwrite the
                                        // Solidity scratch pad at memory position 0.
                                        calldatacopy(0, 0, calldatasize())
                                        // Call the implementation.
                                        // out and outsize are 0 because we don't know the size yet.
                                        let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                                        // Copy the returned data.
                                        returndatacopy(0, 0, returndatasize())
                                        switch result
                                        // delegatecall returns 0 on error.
                                        case 0 {
                                            revert(0, returndatasize())
                                        }
                                        default {
                                            return(0, returndatasize())
                                        }
                                    }
                                }
                                /**
                                 * @dev This is a virtual function that should be overridden so it returns the address to which the fallback
                                 * function and {_fallback} should delegate.
                                 */
                                function _implementation() internal view virtual returns (address);
                                /**
                                 * @dev Delegates the current call to the address returned by `_implementation()`.
                                 *
                                 * This function does not return to its internal call site, it will return directly to the external caller.
                                 */
                                function _fallback() internal virtual {
                                    _delegate(_implementation());
                                }
                                /**
                                 * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
                                 * function in the contract matches the call data.
                                 */
                                fallback() external payable virtual {
                                    _fallback();
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol)
                            pragma solidity ^0.8.20;
                            import {IBeacon} from "../beacon/IBeacon.sol";
                            import {Address} from "../../utils/Address.sol";
                            import {StorageSlot} from "../../utils/StorageSlot.sol";
                            /**
                             * @dev This abstract contract provides getters and event emitting update functions for
                             * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
                             */
                            library ERC1967Utils {
                                // We re-declare ERC-1967 events here because they can't be used directly from IERC1967.
                                // This will be fixed in Solidity 0.8.21. At that point we should remove these events.
                                /**
                                 * @dev Emitted when the implementation is upgraded.
                                 */
                                event Upgraded(address indexed implementation);
                                /**
                                 * @dev Emitted when the admin account has changed.
                                 */
                                event AdminChanged(address previousAdmin, address newAdmin);
                                /**
                                 * @dev Emitted when the beacon is changed.
                                 */
                                event BeaconUpgraded(address indexed beacon);
                                /**
                                 * @dev Storage slot with the address of the current implementation.
                                 * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
                                 */
                                // solhint-disable-next-line private-vars-leading-underscore
                                bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                                /**
                                 * @dev The `implementation` of the proxy is invalid.
                                 */
                                error ERC1967InvalidImplementation(address implementation);
                                /**
                                 * @dev The `admin` of the proxy is invalid.
                                 */
                                error ERC1967InvalidAdmin(address admin);
                                /**
                                 * @dev The `beacon` of the proxy is invalid.
                                 */
                                error ERC1967InvalidBeacon(address beacon);
                                /**
                                 * @dev An upgrade function sees `msg.value > 0` that may be lost.
                                 */
                                error ERC1967NonPayable();
                                /**
                                 * @dev Returns the current implementation address.
                                 */
                                function getImplementation() internal view returns (address) {
                                    return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
                                }
                                /**
                                 * @dev Stores a new address in the EIP1967 implementation slot.
                                 */
                                function _setImplementation(address newImplementation) private {
                                    if (newImplementation.code.length == 0) {
                                        revert ERC1967InvalidImplementation(newImplementation);
                                    }
                                    StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
                                }
                                /**
                                 * @dev Performs implementation upgrade with additional setup call if data is nonempty.
                                 * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
                                 * to avoid stuck value in the contract.
                                 *
                                 * Emits an {IERC1967-Upgraded} event.
                                 */
                                function upgradeToAndCall(address newImplementation, bytes memory data) internal {
                                    _setImplementation(newImplementation);
                                    emit Upgraded(newImplementation);
                                    if (data.length > 0) {
                                        Address.functionDelegateCall(newImplementation, data);
                                    } else {
                                        _checkNonPayable();
                                    }
                                }
                                /**
                                 * @dev Storage slot with the admin of the contract.
                                 * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
                                 */
                                // solhint-disable-next-line private-vars-leading-underscore
                                bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                                /**
                                 * @dev Returns the current admin.
                                 *
                                 * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
                                 * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                                 * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
                                 */
                                function getAdmin() internal view returns (address) {
                                    return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
                                }
                                /**
                                 * @dev Stores a new address in the EIP1967 admin slot.
                                 */
                                function _setAdmin(address newAdmin) private {
                                    if (newAdmin == address(0)) {
                                        revert ERC1967InvalidAdmin(address(0));
                                    }
                                    StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
                                }
                                /**
                                 * @dev Changes the admin of the proxy.
                                 *
                                 * Emits an {IERC1967-AdminChanged} event.
                                 */
                                function changeAdmin(address newAdmin) internal {
                                    emit AdminChanged(getAdmin(), newAdmin);
                                    _setAdmin(newAdmin);
                                }
                                /**
                                 * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
                                 * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
                                 */
                                // solhint-disable-next-line private-vars-leading-underscore
                                bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
                                /**
                                 * @dev Returns the current beacon.
                                 */
                                function getBeacon() internal view returns (address) {
                                    return StorageSlot.getAddressSlot(BEACON_SLOT).value;
                                }
                                /**
                                 * @dev Stores a new beacon in the EIP1967 beacon slot.
                                 */
                                function _setBeacon(address newBeacon) private {
                                    if (newBeacon.code.length == 0) {
                                        revert ERC1967InvalidBeacon(newBeacon);
                                    }
                                    StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
                                    address beaconImplementation = IBeacon(newBeacon).implementation();
                                    if (beaconImplementation.code.length == 0) {
                                        revert ERC1967InvalidImplementation(beaconImplementation);
                                    }
                                }
                                /**
                                 * @dev Change the beacon and trigger a setup call if data is nonempty.
                                 * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
                                 * to avoid stuck value in the contract.
                                 *
                                 * Emits an {IERC1967-BeaconUpgraded} event.
                                 *
                                 * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
                                 * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
                                 * efficiency.
                                 */
                                function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
                                    _setBeacon(newBeacon);
                                    emit BeaconUpgraded(newBeacon);
                                    if (data.length > 0) {
                                        Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                                    } else {
                                        _checkNonPayable();
                                    }
                                }
                                /**
                                 * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
                                 * if an upgrade doesn't perform an initialization call.
                                 */
                                function _checkNonPayable() private {
                                    if (msg.value > 0) {
                                        revert ERC1967NonPayable();
                                    }
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)
                            pragma solidity ^0.8.20;
                            /**
                             * @dev This is the interface that {BeaconProxy} expects of its beacon.
                             */
                            interface IBeacon {
                                /**
                                 * @dev Must return an address that can be used as a delegate call target.
                                 *
                                 * {UpgradeableBeacon} will check that this address is a contract.
                                 */
                                function implementation() external view returns (address);
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
                            pragma solidity ^0.8.20;
                            /**
                             * @dev Collection of functions related to the address type
                             */
                            library Address {
                                /**
                                 * @dev The ETH balance of the account is not enough to perform the operation.
                                 */
                                error AddressInsufficientBalance(address account);
                                /**
                                 * @dev There's no code at `target` (it is not a contract).
                                 */
                                error AddressEmptyCode(address target);
                                /**
                                 * @dev A call to an address target failed. The target may have reverted.
                                 */
                                error FailedInnerCall();
                                /**
                                 * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                                 * `recipient`, forwarding all available gas and reverting on errors.
                                 *
                                 * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                                 * of certain opcodes, possibly making contracts go over the 2300 gas limit
                                 * imposed by `transfer`, making them unable to receive funds via
                                 * `transfer`. {sendValue} removes this limitation.
                                 *
                                 * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                                 *
                                 * IMPORTANT: because control is transferred to `recipient`, care must be
                                 * taken to not create reentrancy vulnerabilities. Consider using
                                 * {ReentrancyGuard} or the
                                 * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                                 */
                                function sendValue(address payable recipient, uint256 amount) internal {
                                    if (address(this).balance < amount) {
                                        revert AddressInsufficientBalance(address(this));
                                    }
                                    (bool success, ) = recipient.call{value: amount}("");
                                    if (!success) {
                                        revert FailedInnerCall();
                                    }
                                }
                                /**
                                 * @dev Performs a Solidity function call using a low level `call`. A
                                 * plain `call` is an unsafe replacement for a function call: use this
                                 * function instead.
                                 *
                                 * If `target` reverts with a revert reason or custom error, it is bubbled
                                 * up by this function (like regular Solidity function calls). However, if
                                 * the call reverted with no returned reason, this function reverts with a
                                 * {FailedInnerCall} error.
                                 *
                                 * Returns the raw returned data. To convert to the expected return value,
                                 * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                                 *
                                 * Requirements:
                                 *
                                 * - `target` must be a contract.
                                 * - calling `target` with `data` must not revert.
                                 */
                                function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                                    return functionCallWithValue(target, data, 0);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but also transferring `value` wei to `target`.
                                 *
                                 * Requirements:
                                 *
                                 * - the calling contract must have an ETH balance of at least `value`.
                                 * - the called Solidity function must be `payable`.
                                 */
                                function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                                    if (address(this).balance < value) {
                                        revert AddressInsufficientBalance(address(this));
                                    }
                                    (bool success, bytes memory returndata) = target.call{value: value}(data);
                                    return verifyCallResultFromTarget(target, success, returndata);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but performing a static call.
                                 */
                                function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                                    (bool success, bytes memory returndata) = target.staticcall(data);
                                    return verifyCallResultFromTarget(target, success, returndata);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but performing a delegate call.
                                 */
                                function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                                    (bool success, bytes memory returndata) = target.delegatecall(data);
                                    return verifyCallResultFromTarget(target, success, returndata);
                                }
                                /**
                                 * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
                                 * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
                                 * unsuccessful call.
                                 */
                                function verifyCallResultFromTarget(
                                    address target,
                                    bool success,
                                    bytes memory returndata
                                ) internal view returns (bytes memory) {
                                    if (!success) {
                                        _revert(returndata);
                                    } else {
                                        // only check if target is a contract if the call was successful and the return data is empty
                                        // otherwise we already know that it was a contract
                                        if (returndata.length == 0 && target.code.length == 0) {
                                            revert AddressEmptyCode(target);
                                        }
                                        return returndata;
                                    }
                                }
                                /**
                                 * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
                                 * revert reason or with a default {FailedInnerCall} error.
                                 */
                                function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
                                    if (!success) {
                                        _revert(returndata);
                                    } else {
                                        return returndata;
                                    }
                                }
                                /**
                                 * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
                                 */
                                function _revert(bytes memory returndata) private pure {
                                    // Look for revert reason and bubble it up if present
                                    if (returndata.length > 0) {
                                        // The easiest way to bubble the revert reason is using memory via assembly
                                        /// @solidity memory-safe-assembly
                                        assembly {
                                            let returndata_size := mload(returndata)
                                            revert(add(32, returndata), returndata_size)
                                        }
                                    } else {
                                        revert FailedInnerCall();
                                    }
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
                            // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
                            pragma solidity ^0.8.20;
                            /**
                             * @dev Library for reading and writing primitive types to specific storage slots.
                             *
                             * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
                             * This library helps with reading and writing to such slots without the need for inline assembly.
                             *
                             * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
                             *
                             * Example usage to set ERC1967 implementation slot:
                             * ```solidity
                             * contract ERC1967 {
                             *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                             *
                             *     function _getImplementation() internal view returns (address) {
                             *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                             *     }
                             *
                             *     function _setImplementation(address newImplementation) internal {
                             *         require(newImplementation.code.length > 0);
                             *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                             *     }
                             * }
                             * ```
                             */
                            library StorageSlot {
                                struct AddressSlot {
                                    address value;
                                }
                                struct BooleanSlot {
                                    bool value;
                                }
                                struct Bytes32Slot {
                                    bytes32 value;
                                }
                                struct Uint256Slot {
                                    uint256 value;
                                }
                                struct StringSlot {
                                    string value;
                                }
                                struct BytesSlot {
                                    bytes value;
                                }
                                /**
                                 * @dev Returns an `AddressSlot` with member `value` located at `slot`.
                                 */
                                function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
                                 */
                                function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
                                 */
                                function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
                                 */
                                function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `StringSlot` with member `value` located at `slot`.
                                 */
                                function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
                                 */
                                function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := store.slot
                                    }
                                }
                                /**
                                 * @dev Returns an `BytesSlot` with member `value` located at `slot`.
                                 */
                                function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
                                 */
                                function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := store.slot
                                    }
                                }
                            }
                            

                            File 14 of 17: CurveStableSwapNG
                            # pragma version 0.3.10
                            # pragma optimize codesize
                            # pragma evm-version shanghai
                            """
                            @title CurveStableSwapNG
                            @author Curve.Fi
                            @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved
                            @notice Stableswap implementation for up to 8 coins with no rehypothecation,
                                    i.e. the AMM does not deposit tokens into other contracts. The Pool contract also
                                    records exponential moving averages for coins relative to coin 0.
                            @dev Asset Types:
                                    0. Standard ERC20 token with no additional features.
                                                      Note: Users are advised to do careful due-diligence on
                                                            ERC20 tokens that they interact with, as this
                                                            contract cannot differentiate between harmless and
                                                            malicious ERC20 tokens.
                                    1. Oracle - token with rate oracle (e.g. wstETH)
                                                Note: Oracles may be controlled externally by an EOA. Users
                                                      are advised to proceed with caution.
                                    2. Rebasing - token with rebase (e.g. stETH).
                                                  Note: Users and Integrators are advised to understand how
                                                        the AMM contract works with rebasing balances.
                                    3. ERC4626 - token with convertToAssets method (e.g. sDAI).
                                                 Note: Some ERC4626 implementations may be susceptible to
                                                       Donation/Inflation attacks. Users are advised to
                                                       proceed with caution.
                                    NOTE: Pool Cannot support tokens with multiple asset types: e.g. ERC4626
                                          with fees are not supported.
                                 Supports:
                                    1. ERC20 support for return True/revert, return True/False, return None
                                    2. ERC20 tokens can have arbitrary decimals (<=18).
                                    3. ERC20 tokens that rebase (either positive or fee on transfer)
                                    4. ERC20 tokens that have a rate oracle (e.g. wstETH, cbETH, sDAI, etc.)
                                       Note: Oracle precision _must_ be 10**18.
                                    5. ERC4626 tokens with arbitrary precision (<=18) of Vault token and underlying
                                       asset.
                                 Additional features include:
                                    1. Adds price oracles based on AMM State Price (and _not_ last traded price).
                                    2. Adds TVL oracle based on D.
                                    3. `exchange_received`: swaps that expect an ERC20 transfer to have occurred
                                       prior to executing the swap.
                                       Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing)
                                                then calling `exchange_received` will REVERT.
                                             b. If pool contains rebasing token and `asset_types` does not contain 2 (Rebasing)
                                                then this is an incorrect implementation and rebases can be
                                                stolen.
                                    4. Adds `get_dx`: Similar to `get_dy` which returns an expected output
                                       of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected
                                       input of coin[i] for an output amount of coin[j].
                                    5. Fees are dynamic: AMM will charge a higher fee if pool depegs. This can cause very
                                                         slight discrepancies between calculated fees and realised fees.
                            """
                            
                            from vyper.interfaces import ERC20
                            from vyper.interfaces import ERC20Detailed
                            from vyper.interfaces import ERC4626
                            
                            implements: ERC20
                            
                            # ------------------------------- Interfaces ---------------------------------
                            
                            interface Factory:
                                def fee_receiver() -> address: view
                                def admin() -> address: view
                                def views_implementation() -> address: view
                            
                            interface ERC1271:
                                def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view
                            
                            interface StableSwapViews:
                                def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view
                                def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: view
                                def dynamic_fee(i: int128, j: int128, pool: address) -> uint256: view
                                def calc_token_amount(
                                    _amounts: DynArray[uint256, MAX_COINS],
                                    _is_deposit: bool,
                                    _pool: address
                                ) -> uint256: view
                            
                            # --------------------------------- Events -----------------------------------
                            
                            event Transfer:
                                sender: indexed(address)
                                receiver: indexed(address)
                                value: uint256
                            
                            event Approval:
                                owner: indexed(address)
                                spender: indexed(address)
                                value: uint256
                            
                            event TokenExchange:
                                buyer: indexed(address)
                                sold_id: int128
                                tokens_sold: uint256
                                bought_id: int128
                                tokens_bought: uint256
                            
                            event TokenExchangeUnderlying:
                                buyer: indexed(address)
                                sold_id: int128
                                tokens_sold: uint256
                                bought_id: int128
                                tokens_bought: uint256
                            
                            event AddLiquidity:
                                provider: indexed(address)
                                token_amounts: DynArray[uint256, MAX_COINS]
                                fees: DynArray[uint256, MAX_COINS]
                                invariant: uint256
                                token_supply: uint256
                            
                            event RemoveLiquidity:
                                provider: indexed(address)
                                token_amounts: DynArray[uint256, MAX_COINS]
                                fees: DynArray[uint256, MAX_COINS]
                                token_supply: uint256
                            
                            event RemoveLiquidityOne:
                                provider: indexed(address)
                                token_id: int128
                                token_amount: uint256
                                coin_amount: uint256
                                token_supply: uint256
                            
                            event RemoveLiquidityImbalance:
                                provider: indexed(address)
                                token_amounts: DynArray[uint256, MAX_COINS]
                                fees: DynArray[uint256, MAX_COINS]
                                invariant: uint256
                                token_supply: uint256
                            
                            event RampA:
                                old_A: uint256
                                new_A: uint256
                                initial_time: uint256
                                future_time: uint256
                            
                            event StopRampA:
                                A: uint256
                                t: uint256
                            
                            event ApplyNewFee:
                                fee: uint256
                                offpeg_fee_multiplier: uint256
                            
                            event SetNewMATime:
                                ma_exp_time: uint256
                                D_ma_time: uint256
                            
                            
                            MAX_COINS: constant(uint256) = 8  # max coins is 8 in the factory
                            MAX_COINS_128: constant(int128) = 8
                            
                            # ---------------------------- Pool Variables --------------------------------
                            
                            N_COINS: public(immutable(uint256))
                            N_COINS_128: immutable(int128)
                            PRECISION: constant(uint256) = 10 ** 18
                            
                            factory: immutable(Factory)
                            coins: public(immutable(DynArray[address, MAX_COINS]))
                            asset_types: immutable(DynArray[uint8, MAX_COINS])
                            pool_contains_rebasing_tokens: immutable(bool)
                            stored_balances: DynArray[uint256, MAX_COINS]
                            
                            # Fee specific vars
                            FEE_DENOMINATOR: constant(uint256) = 10 ** 10
                            fee: public(uint256)  # fee * 1e10
                            offpeg_fee_multiplier: public(uint256)  # * 1e10
                            admin_fee: public(constant(uint256)) = 5000000000
                            MAX_FEE: constant(uint256) = 5 * 10 ** 9
                            
                            # ---------------------- Pool Amplification Parameters -----------------------
                            
                            A_PRECISION: constant(uint256) = 100
                            MAX_A: constant(uint256) = 10 ** 6
                            MAX_A_CHANGE: constant(uint256) = 10
                            
                            initial_A: public(uint256)
                            future_A: public(uint256)
                            initial_A_time: public(uint256)
                            future_A_time: public(uint256)
                            
                            # ---------------------------- Admin Variables -------------------------------
                            
                            MIN_RAMP_TIME: constant(uint256) = 86400
                            admin_balances: public(DynArray[uint256, MAX_COINS])
                            
                            # ----------------------- Oracle Specific vars -------------------------------
                            
                            rate_multipliers: immutable(DynArray[uint256, MAX_COINS])
                            # [bytes4 method_id][bytes8 <empty>][bytes20 oracle]
                            rate_oracles: immutable(DynArray[uint256, MAX_COINS])
                            
                            # For ERC4626 tokens, we need:
                            call_amount: immutable(DynArray[uint256, MAX_COINS])
                            scale_factor: immutable(DynArray[uint256, MAX_COINS])
                            
                            last_prices_packed: DynArray[uint256, MAX_COINS]  #  packing: last_price, ma_price
                            last_D_packed: uint256                            #  packing: last_D, ma_D
                            ma_exp_time: public(uint256)
                            D_ma_time: public(uint256)
                            ma_last_time: public(uint256)                     # packing: ma_last_time_p, ma_last_time_D
                            # ma_last_time has a distinction for p and D because p is _not_ updated if
                            # users remove_liquidity, but D is.
                            
                            # shift(2**32 - 1, 224)
                            ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28
                            
                            # --------------------------- ERC20 Specific Vars ----------------------------
                            
                            name: public(immutable(String[64]))
                            symbol: public(immutable(String[32]))
                            decimals: public(constant(uint8)) = 18
                            version: public(constant(String[8])) = "v7.0.0"
                            
                            balanceOf: public(HashMap[address, uint256])
                            allowance: public(HashMap[address, HashMap[address, uint256]])
                            total_supply: uint256
                            nonces: public(HashMap[address, uint256])
                            
                            # keccak256("isValidSignature(bytes32,bytes)")[:4] << 224
                            ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000
                            EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)")
                            EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
                            
                            VERSION_HASH: constant(bytes32) = keccak256(version)
                            NAME_HASH: immutable(bytes32)
                            CACHED_CHAIN_ID: immutable(uint256)
                            salt: public(immutable(bytes32))
                            CACHED_DOMAIN_SEPARATOR: immutable(bytes32)
                            
                            
                            # ------------------------------ AMM Setup -----------------------------------
                            
                            
                            @external
                            def __init__(
                                _name: String[32],
                                _symbol: String[10],
                                _A: uint256,
                                _fee: uint256,
                                _offpeg_fee_multiplier: uint256,
                                _ma_exp_time: uint256,
                                _coins: DynArray[address, MAX_COINS],
                                _rate_multipliers: DynArray[uint256, MAX_COINS],
                                _asset_types: DynArray[uint8, MAX_COINS],
                                _method_ids: DynArray[bytes4, MAX_COINS],
                                _oracles: DynArray[address, MAX_COINS],
                            ):
                                """
                                @notice Initialize the pool contract
                                @param _name Name of the new plain pool.
                                @param _symbol Symbol for the new plain pool.
                                @param _A Amplification co-efficient - a lower value here means
                                          less tolerance for imbalance within the pool's assets.
                                          Suggested values include:
                                           * Uncollateralized algorithmic stablecoins: 5-10
                                           * Non-redeemable, collateralized assets: 100
                                           * Redeemable assets: 200-400
                                @param _fee Trade fee, given as an integer with 1e10 precision. The
                                            the maximum is 1% (100000000).
                                            50% of the fee is distributed to veCRV holders.
                                @param _offpeg_fee_multiplier A multiplier that determines how much to increase
                                                              Fees by when assets in the AMM depeg. Example value: 20000000000
                                @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2)
                                                    Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866
                                @param _coins List of addresses of the coins being used in the pool.
                                @param _rate_multipliers An array of: [10 ** (36 - _coins[n].decimals()), ... for n in range(N_COINS)]
                                @param _asset_types Array of uint8 representing tokens in pool
                                @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures
                                                   of the oracle addresses that gives rate oracles.
                                                   Calculated as: keccak(text=event_signature.replace(" ", ""))[:4]
                                @param _oracles Array of rate oracle addresses.
                                """
                            
                                coins = _coins
                                asset_types = _asset_types
                                pool_contains_rebasing_tokens = 2 in asset_types
                                __n_coins: uint256 = len(_coins)
                                N_COINS = __n_coins
                                N_COINS_128 = convert(__n_coins, int128)
                            
                                rate_multipliers = _rate_multipliers
                            
                                factory = Factory(msg.sender)
                            
                                A: uint256 = unsafe_mul(_A, A_PRECISION)
                                self.initial_A = A
                                self.future_A = A
                                self.fee = _fee
                                self.offpeg_fee_multiplier = _offpeg_fee_multiplier
                            
                                assert _ma_exp_time != 0
                                self.ma_exp_time = _ma_exp_time
                                self.D_ma_time = 62324  # <--------- 12 hours default on contract start.
                                self.ma_last_time = self.pack_2(block.timestamp, block.timestamp)
                            
                                #  ------------------- initialize storage for DynArrays ------------------
                            
                                _call_amount: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
                                _scale_factor: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
                                _rate_oracles: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
                                for i in range(N_COINS_128, bound=MAX_COINS_128):
                            
                                    if i < N_COINS_128 - 1:
                                        self.last_prices_packed.append(self.pack_2(10**18, 10**18))
                            
                                    _rate_oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256))
                                    self.stored_balances.append(0)
                                    self.admin_balances.append(0)
                            
                                    if _asset_types[i] == 3:
                            
                                        _call_amount.append(10**convert(ERC20Detailed(_coins[i]).decimals(), uint256))
                                        _underlying_asset: address = ERC4626(_coins[i]).asset()
                                        _scale_factor.append(10**(18 - convert(ERC20Detailed(_underlying_asset).decimals(), uint256)))
                            
                                    else:
                            
                                        _call_amount.append(0)
                                        _scale_factor.append(0)
                            
                                call_amount = _call_amount
                                scale_factor = _scale_factor
                                rate_oracles = _rate_oracles
                            
                                # ----------------------------- ERC20 stuff ------------------------------
                            
                                name = _name
                                symbol = _symbol
                            
                                # EIP712 related params -----------------
                                NAME_HASH = keccak256(name)
                                salt = block.prevhash
                                CACHED_CHAIN_ID = chain.id
                                CACHED_DOMAIN_SEPARATOR = keccak256(
                                    _abi_encode(
                                        EIP712_TYPEHASH,
                                        NAME_HASH,
                                        VERSION_HASH,
                                        chain.id,
                                        self,
                                        salt,
                                    )
                                )
                            
                                # ------------------------ Fire a transfer event -------------------------
                            
                                log Transfer(empty(address), msg.sender, 0)
                            
                            
                            # ------------------ Token transfers in and out of the AMM -------------------
                            
                            
                            @internal
                            def _transfer_in(
                                coin_idx: int128,
                                dx: uint256,
                                sender: address,
                                expect_optimistic_transfer: bool,
                            ) -> uint256:
                                """
                                @notice Contains all logic to handle ERC20 token transfers.
                                @param coin_idx Index of the coin to transfer in.
                                @param dx amount of `_coin` to transfer into the pool.
                                @param sender address to transfer `_coin` from.
                                @param receiver address to transfer `_coin` to.
                                @param expect_optimistic_transfer True if contract expects an optimistic coin transfer
                                """
                                _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self)
                            
                                # ------------------------- Handle Transfers -----------------------------
                            
                                if expect_optimistic_transfer:
                            
                                    _dx = _dx - self.stored_balances[coin_idx]
                                    assert _dx >= dx
                            
                                else:
                            
                                    assert dx > 0  # dev : do not transferFrom 0 tokens into the pool
                                    assert ERC20(coins[coin_idx]).transferFrom(
                                        sender, self, dx, default_return_value=True
                                    )
                            
                                    _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx
                            
                                # --------------------------- Store transferred in amount ---------------------------
                            
                                self.stored_balances[coin_idx] += _dx
                            
                                return _dx
                            
                            
                            @internal
                            def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address):
                                """
                                @notice Transfer a single token from the pool to receiver.
                                @dev This function is called by `remove_liquidity` and
                                     `remove_liquidity_one_coin`, `_exchange`, `_withdraw_admin_fees` and
                                     `remove_liquidity_imbalance` methods.
                                @param _coin_idx Index of the token to transfer out
                                @param _amount Amount of token to transfer out
                                @param receiver Address to send the tokens to
                                """
                                assert receiver != empty(address)  # dev: do not send tokens to zero_address
                            
                                if not pool_contains_rebasing_tokens:
                            
                                    # we need not cache balanceOf pool before swap out
                                    self.stored_balances[_coin_idx] -= _amount
                                    assert ERC20(coins[_coin_idx]).transfer(
                                        receiver, _amount, default_return_value=True
                                    )
                            
                                else:
                            
                                    # cache balances pre and post to account for fee on transfers etc.
                                    coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self)
                                    assert ERC20(coins[_coin_idx]).transfer(
                                        receiver, _amount, default_return_value=True
                                    )
                                    self.stored_balances[_coin_idx] = coin_balance - _amount
                            
                            
                            # -------------------------- AMM Special Methods -----------------------------
                            
                            
                            @view
                            @internal
                            def _stored_rates() -> DynArray[uint256, MAX_COINS]:
                                """
                                @notice Gets rate multipliers for each coin.
                                @dev If the coin has a rate oracle that has been properly initialised,
                                     this method queries that rate by static-calling an external
                                     contract.
                                """
                                rates: DynArray[uint256, MAX_COINS] = rate_multipliers
                            
                                for i in range(N_COINS_128, bound=MAX_COINS_128):
                            
                                    if asset_types[i] == 1 and not rate_oracles[i] == 0:
                            
                                        # NOTE: fetched_rate is assumed to be 10**18 precision
                                        oracle_response: Bytes[32] = raw_call(
                                            convert(rate_oracles[i] % 2**160, address),
                                            _abi_encode(rate_oracles[i] & ORACLE_BIT_MASK),
                                            max_outsize=32,
                                            is_static_call=True,
                                        )
                                        assert len(oracle_response) == 32
                                        fetched_rate: uint256 = convert(oracle_response, uint256)
                            
                                        rates[i] = unsafe_div(rates[i] * fetched_rate, PRECISION)
                            
                                    elif asset_types[i] == 3:  # ERC4626
                            
                                        # fetched_rate: uint256 = ERC4626(coins[i]).convertToAssets(call_amount[i]) * scale_factor[i]
                                        # here: call_amount has ERC4626 precision, but the returned value is scaled up to 18
                                        # using scale_factor which is (18 - n) if underlying asset has n decimals.
                                        rates[i] = unsafe_div(
                                            rates[i] * ERC4626(coins[i]).convertToAssets(call_amount[i]) * scale_factor[i],
                                            PRECISION
                                        )  # 1e18 precision
                            
                                return rates
                            
                            
                            @view
                            @internal
                            def _balances() -> DynArray[uint256, MAX_COINS]:
                                """
                                @notice Calculates the pool's balances _excluding_ the admin's balances.
                                @dev If the pool contains rebasing tokens, this method ensures LPs keep all
                                        rebases and admin only claims swap fees. This also means that, since
                                        admin's balances are stored in an array and not inferred from read balances,
                                        the fees in the rebasing token that the admin collects is immune to
                                        slashing events.
                                """
                                result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
                                balances_i: uint256 = 0
                            
                                for i in range(N_COINS_128, bound=MAX_COINS_128):
                            
                                    if pool_contains_rebasing_tokens:
                                        # Read balances by gulping to account for rebases
                                        balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i]
                                    else:
                                        # Use cached balances
                                        balances_i = self.stored_balances[i] - self.admin_balances[i]
                            
                                    result.append(balances_i)
                            
                                return result
                            
                            
                            # -------------------------- AMM Main Functions ------------------------------
                            
                            
                            @external
                            @nonreentrant('lock')
                            def exchange(
                                i: int128,
                                j: int128,
                                _dx: uint256,
                                _min_dy: uint256,
                                _receiver: address = msg.sender,
                            ) -> uint256:
                                """
                                @notice Perform an exchange between two coins
                                @dev Index values can be found via the `coins` public getter method
                                @param i Index value for the coin to send
                                @param j Index value of the coin to receive
                                @param _dx Amount of `i` being exchanged
                                @param _min_dy Minimum amount of `j` to receive
                                @param _receiver Address that receives `j`
                                @return Actual amount of `j` received
                                """
                                return self._exchange(
                                    msg.sender,
                                    i,
                                    j,
                                    _dx,
                                    _min_dy,
                                    _receiver,
                                    False
                                )
                            
                            
                            @external
                            @nonreentrant('lock')
                            def exchange_received(
                                i: int128,
                                j: int128,
                                _dx: uint256,
                                _min_dy: uint256,
                                _receiver: address = msg.sender,
                            ) -> uint256:
                                """
                                @notice Perform an exchange between two coins without transferring token in
                                @dev The contract swaps tokens based on a change in balance of coin[i]. The
                                     dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of
                                     this method are dex aggregators, arbitrageurs, or other users who do not
                                     wish to grant approvals to the contract: they would instead send tokens
                                     directly to the contract and call `exchange_received`.
                                     Note: This is disabled if pool contains rebasing tokens.
                                @param i Index value for the coin to send
                                @param j Index value of the coin to receive
                                @param _dx Amount of `i` being exchanged
                                @param _min_dy Minimum amount of `j` to receive
                                @param _receiver Address that receives `j`
                                @return Actual amount of `j` received
                                """
                                assert not pool_contains_rebasing_tokens  # dev: exchange_received not supported if pool contains rebasing tokens
                                return self._exchange(
                                    msg.sender,
                                    i,
                                    j,
                                    _dx,
                                    _min_dy,
                                    _receiver,
                                    True,  # <--------------------------------------- swap optimistically.
                                )
                            
                            
                            @external
                            @nonreentrant('lock')
                            def add_liquidity(
                                _amounts: DynArray[uint256, MAX_COINS],
                                _min_mint_amount: uint256,
                                _receiver: address = msg.sender
                            ) -> uint256:
                                """
                                @notice Deposit coins into the pool
                                @param _amounts List of amounts of coins to deposit
                                @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
                                @param _receiver Address that owns the minted LP tokens
                                @return Amount of LP tokens received by depositing
                                """
                                assert _receiver != empty(address)  # dev: do not send LP tokens to zero_address
                            
                                amp: uint256 = self._A()
                                old_balances: DynArray[uint256, MAX_COINS] = self._balances()
                                rates: DynArray[uint256, MAX_COINS] = self._stored_rates()
                            
                                # Initial invariant
                                D0: uint256 = self.get_D_mem(rates, old_balances, amp)
                            
                                total_supply: uint256 = self.total_supply
                                new_balances: DynArray[uint256, MAX_COINS] = old_balances
                            
                                # -------------------------- Do Transfers In -----------------------------
                            
                                for i in range(N_COINS_128, bound=MAX_COINS_128):
                            
                                    if _amounts[i] > 0:
                            
                                        new_balances[i] += self._transfer_in(
                                            i,
                                            _amounts[i],
                                            msg.sender,
                                            False,  # expect_optimistic_transfer
                                        )
                            
                                    else:
                            
                                        assert total_supply != 0  # dev: initial deposit requires all coins
                            
                                # ------------------------------------------------------------------------
                            
                                # Invariant after change
                                D1: uint256 = self.get_D_mem(rates, new_balances, amp)
                                assert D1 > D0
                            
                                # We need to recalculate the invariant accounting for fees
                                # to calculate fair user's share
                                fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
                                mint_amount: uint256 = 0
                            
                                if total_supply > 0:
                            
                                    ideal_balance: uint256 = 0
                                    difference: uint256 = 0
                                    new_balance: uint256 = 0
                            
                                    ys: uint256 = unsafe_div(D0 + D1, N_COINS)
                                    xs: uint256 = 0
                                    _dynamic_fee_i: uint256 = 0
                            
                                    # Only account for fees if we are not the first to deposit
                                    base_fee: uint256 = unsafe_div(
                                        unsafe_mul(self.fee, N_COINS),
                                        unsafe_mul(4, unsafe_sub(N_COINS, 1))
                                    )
                            
                                    for i in range(N_COINS_128, bound=MAX_COINS_128):
                            
                                        ideal_balance = D1 * old_balances[i] / D0
                                        difference = 0
                                        new_balance = new_balances[i]
                            
                                        if ideal_balance > new_balance:
                                            difference = unsafe_sub(ideal_balance, new_balance)
                                        else:
                                            difference = unsafe_sub(new_balance, ideal_balance)
                            
                                        # fee[i] = _dynamic_fee(i, j) * difference / FEE_DENOMINATOR
                                        xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION)
                                        _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee)
                                        fees.append(unsafe_div(_dynamic_fee_i * difference, FEE_DENOMINATOR))
                                        self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR)
                                        new_balances[i] -= fees[i]
                            
                                    xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances)
                                    D1 = self.get_D(xp, amp)  # <--------------- Reuse D1 for new D value.
                                    mint_amount = unsafe_div(total_supply * (D1 - D0), D0)
                                    self.upkeep_oracles(xp, amp, D1)
                            
                                else:
                            
                                    mint_amount = D1  # Take the dust if there was any
                            
                                    # (re)instantiate D oracle if totalSupply is zero.
                                    self.last_D_packed = self.pack_2(D1, D1)
                            
                                    # Update D ma time:
                                    ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time)
                                    if ma_last_time_unpacked[1] < block.timestamp:
                                        ma_last_time_unpacked[1] = block.timestamp
                                        self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1])
                            
                                assert mint_amount >= _min_mint_amount, "Slippage screwed you"
                            
                                # Mint pool tokens
                                total_supply += mint_amount
                                self.balanceOf[_receiver] += mint_amount
                                self.total_supply = total_supply
                                log Transfer(empty(address), _receiver, mint_amount)
                            
                                log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply)
                            
                                return mint_amount
                            
                            
                            @external
                            @nonreentrant('lock')
                            def remove_liquidity_one_coin(
                                _burn_amount: uint256,
                                i: int128,
                                _min_received: uint256,
                                _receiver: address = msg.sender,
                            ) -> uint256:
                                """
                                @notice Withdraw a single coin from the pool
                                @param _burn_amount Amount of LP tokens to burn in the withdrawal
                                @param i Index value of the coin to withdraw
                                @param _min_received Minimum amount of coin to receive
                                @param _receiver Address that receives the withdrawn coins
                                @return Amount of coin received
                                """
                                assert _burn_amount > 0  # dev: do not remove 0 LP tokens
                                dy: uint256 = 0
                                fee: uint256 = 0
                                xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
                                amp: uint256 = empty(uint256)
                                D: uint256 = empty(uint256)
                            
                                dy, fee, xp, amp, D = self._calc_withdraw_one_coin(_burn_amount, i)
                                assert dy >= _min_received, "Not enough coins removed"
                            
                                self.admin_balances[i] += unsafe_div(fee * admin_fee, FEE_DENOMINATOR)
                            
                                self._burnFrom(msg.sender, _burn_amount)
                            
                                self._transfer_out(i, dy, _receiver)
                            
                                log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.total_supply)
                            
                                self.upkeep_oracles(xp, amp, D)
                            
                                return dy
                            
                            
                            @external
                            @nonreentrant('lock')
                            def remove_liquidity_imbalance(
                                _amounts: DynArray[uint256, MAX_COINS],
                                _max_burn_amount: uint256,
                                _receiver: address = msg.sender
                            ) -> uint256:
                                """
                                @notice Withdraw coins from the pool in an imbalanced amount
                                @param _amounts List of amounts of underlying coins to withdraw
                                @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal
                                @param _receiver Address that receives the withdrawn coins
                                @return Actual amount of the LP token burned in the withdrawal
                                """
                                amp: uint256 = self._A()
                                rates: DynArray[uint256, MAX_COINS] = self._stored_rates()
                                old_balances: DynArray[uint256, MAX_COINS] = self._balances()
                                D0: uint256 = self.get_D_mem(rates, old_balances, amp)
                                new_balances: DynArray[uint256, MAX_COINS] = old_balances
                            
                                for i in range(N_COINS_128, bound=MAX_COINS_128):
                            
                                    if _amounts[i] != 0:
                                        new_balances[i] -= _amounts[i]
                                        self._transfer_out(i, _amounts[i], _receiver)
                            
                                D1: uint256 = self.get_D_mem(rates, new_balances, amp)
                                base_fee: uint256 = unsafe_div(
                                    unsafe_mul(self.fee, N_COINS),
                                    unsafe_mul(4, unsafe_sub(N_COINS, 1))
                                )
                                ys: uint256 = unsafe_div((D0 + D1), N_COINS)
                            
                                fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
                                dynamic_fee: uint256 = 0
                                xs: uint256 = 0
                                ideal_balance: uint256 = 0
                                difference: uint256 = 0
                                new_balance: uint256 = 0
                            
                                for i in range(N_COINS_128, bound=MAX_COINS_128):
                            
                                    ideal_balance = D1 * old_balances[i] / D0
                                    difference = 0
                                    new_balance = new_balances[i]
                            
                                    if ideal_balance > new_balance:
                                        difference = unsafe_sub(ideal_balance, new_balance)
                                    else:
                                        difference = unsafe_sub(new_balance, ideal_balance)
                            
                                    xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION)
                                    dynamic_fee = self._dynamic_fee(xs, ys, base_fee)
                                    fees.append(unsafe_div(dynamic_fee * difference, FEE_DENOMINATOR))
                            
                                    self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR)
                                    new_balances[i] -= fees[i]
                            
                                D1 = self.get_D_mem(rates, new_balances, amp)  # dev: reuse D1 for new D.
                                self.upkeep_oracles(self._xp_mem(rates, new_balances), amp, D1)
                            
                                total_supply: uint256 = self.total_supply
                                burn_amount: uint256 = unsafe_div((D0 - D1) * total_supply, D0) + 1
                                assert burn_amount > 1  # dev: zero tokens burned
                                assert burn_amount <= _max_burn_amount, "Slippage screwed you"
                            
                                self._burnFrom(msg.sender, burn_amount)
                            
                                log RemoveLiquidityImbalance(
                                    msg.sender,
                                    _amounts,
                                    fees,
                                    D1,
                                    total_supply - burn_amount
                                )
                            
                                return burn_amount
                            
                            
                            @external
                            @nonreentrant('lock')
                            def remove_liquidity(
                                _burn_amount: uint256,
                                _min_amounts: DynArray[uint256, MAX_COINS],
                                _receiver: address = msg.sender,
                                _claim_admin_fees: bool = True,
                            ) -> DynArray[uint256, MAX_COINS]:
                                """
                                @notice Withdraw coins from the pool
                                @dev Withdrawal amounts are based on current deposit ratios
                                @param _burn_amount Quantity of LP tokens to burn in the withdrawal
                                @param _min_amounts Minimum amounts of underlying coins to receive
                                @param _receiver Address that receives the withdrawn coins
                                @return List of amounts of coins that were withdrawn
                                """
                                total_supply: uint256 = self.total_supply
                                assert _burn_amount > 0  # dev: invalid burn amount
                                assert len(_min_amounts) == N_COINS  # dev: invalid array length for _min_amounts
                            
                                amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
                                balances: DynArray[uint256, MAX_COINS] = self._balances()
                            
                                value: uint256 = 0
                                for i in range(N_COINS_128, bound=MAX_COINS_128):
                            
                                    value = unsafe_div(balances[i] * _burn_amount, total_supply)
                                    assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected"
                                    amounts.append(value)
                                    self._transfer_out(i, value, _receiver)
                            
                                self._burnFrom(msg.sender, _burn_amount)  # <---- Updates self.total_supply
                            
                                # --------------------------- Upkeep D_oracle ----------------------------
                            
                                ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time)
                                last_D_packed_current: uint256 = self.last_D_packed
                                old_D: uint256 = last_D_packed_current & (2**128 - 1)
                            
                                self.last_D_packed = self.pack_2(
                                    old_D - unsafe_div(old_D * _burn_amount, total_supply),  # new_D = proportionally reduce D.
                                    self._calc_moving_average(
                                        last_D_packed_current,
                                        self.D_ma_time,
                                        ma_last_time_unpacked[1]
                                    )
                                )
                            
                                if ma_last_time_unpacked[1] < block.timestamp:
                                    ma_last_time_unpacked[1] = block.timestamp
                                    self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1])
                            
                                # ------------------------------- Log event ------------------------------
                            
                                log RemoveLiquidity(
                                    msg.sender,
                                    amounts,
                                    empty(DynArray[uint256, MAX_COINS]),
                                    unsafe_sub(total_supply, _burn_amount)
                                )
                            
                                # ------- Withdraw admin fees if _claim_admin_fees is set to True --------
                                if _claim_admin_fees:
                                    self._withdraw_admin_fees()
                            
                                return amounts
                            
                            
                            @external
                            @nonreentrant('lock')
                            def withdraw_admin_fees():
                                """
                                @notice Claim admin fees. Callable by anyone.
                                """
                                self._withdraw_admin_fees()
                            
                            
                            # ------------------------ AMM Internal Functions ----------------------------
                            
                            
                            @view
                            @internal
                            def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256:
                            
                                _offpeg_fee_multiplier: uint256 = self.offpeg_fee_multiplier
                                if _offpeg_fee_multiplier <= FEE_DENOMINATOR:
                                    return _fee
                            
                                xps2: uint256 = (xpi + xpj) ** 2
                                return unsafe_div(
                                    unsafe_mul(_offpeg_fee_multiplier, _fee),
                                    unsafe_add(
                                        unsafe_sub(_offpeg_fee_multiplier, FEE_DENOMINATOR) * 4 * xpi * xpj / xps2,
                                        FEE_DENOMINATOR
                                    )
                                )
                            
                            
                            @internal
                            def __exchange(
                                x: uint256,
                                _xp: DynArray[uint256, MAX_COINS],
                                rates: DynArray[uint256, MAX_COINS],
                                i: int128,
                                j: int128,
                            ) -> uint256:
                            
                                amp: uint256 = self._A()
                                D: uint256 = self.get_D(_xp, amp)
                                y: uint256 = self.get_y(i, j, x, _xp, amp, D)
                            
                                dy: uint256 = _xp[j] - y - 1  # -1 just in case there were some rounding errors
                                dy_fee: uint256 = unsafe_div(
                                    dy * self._dynamic_fee(
                                        unsafe_div(_xp[i] + x, 2), unsafe_div(_xp[j] + y, 2), self.fee
                                    ),
                                    FEE_DENOMINATOR
                                )
                            
                                # Convert all to real units
                                dy = (dy - dy_fee) * PRECISION / rates[j]
                            
                                self.admin_balances[j] += unsafe_div(
                                    unsafe_div(dy_fee * admin_fee, FEE_DENOMINATOR) * PRECISION,
                                    rates[j]
                                )
                            
                                # Calculate and store state prices:
                                xp: DynArray[uint256, MAX_COINS] = _xp
                                xp[i] = x
                                xp[j] = y
                                # D is not changed because we did not apply a fee
                                self.upkeep_oracles(xp, amp, D)
                            
                                return dy
                            
                            
                            @internal
                            def _exchange(
                                sender: address,
                                i: int128,
                                j: int128,
                                _dx: uint256,
                                _min_dy: uint256,
                                receiver: address,
                                expect_optimistic_transfer: bool
                            ) -> uint256:
                            
                                assert i != j  # dev: coin index out of range
                                assert _dx > 0  # dev: do not exchange 0 coins
                            
                                rates: DynArray[uint256, MAX_COINS] = self._stored_rates()
                                old_balances: DynArray[uint256, MAX_COINS] = self._balances()
                                xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances)
                            
                                # --------------------------- Do Transfer in -----------------------------
                            
                                # `dx` is whatever the pool received after ERC20 transfer:
                                dx: uint256 = self._transfer_in(
                                    i,
                                    _dx,
                                    sender,
                                    expect_optimistic_transfer
                                )
                            
                                # ------------------------------- Exchange -------------------------------
                            
                                x: uint256 = xp[i] + unsafe_div(dx * rates[i], PRECISION)
                                dy: uint256 = self.__exchange(x, xp, rates, i, j)
                                assert dy >= _min_dy, "Exchange resulted in fewer coins than expected"
                            
                                # --------------------------- Do Transfer out ----------------------------
                            
                                self._transfer_out(j, dy, receiver)
                            
                                # ------------------------------------------------------------------------
                            
                                log TokenExchange(msg.sender, i, dx, j, dy)
                            
                                return dy
                            
                            
                            @internal
                            def _withdraw_admin_fees():
                                fee_receiver: address = factory.fee_receiver()
                                if fee_receiver == empty(address):
                                    return  # Do nothing.
                            
                                admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances
                                for i in range(N_COINS_128, bound=MAX_COINS_128):
                            
                                    if admin_balances[i] > 0:
                            
                                        self._transfer_out(i, admin_balances[i], fee_receiver)
                                        admin_balances[i] = 0
                            
                                self.admin_balances = admin_balances
                            
                            
                            # --------------------------- AMM Math Functions -----------------------------
                            
                            
                            @view
                            @internal
                            def get_y(
                                i: int128,
                                j: int128,
                                x: uint256,
                                xp: DynArray[uint256, MAX_COINS],
                                _amp: uint256,
                                _D: uint256
                            ) -> uint256:
                                """
                                Calculate x[j] if one makes x[i] = x
                            
                                Done by solving quadratic equation iteratively.
                                x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A)
                                x_1**2 + b*x_1 = c
                            
                                x_1 = (x_1**2 + c) / (2*x_1 + b)
                                """
                                # x in the input is converted to the same price/precision
                            
                                assert i != j       # dev: same coin
                                assert j >= 0       # dev: j below zero
                                assert j < N_COINS_128  # dev: j above N_COINS
                            
                                # should be unreachable, but good for safety
                                assert i >= 0
                                assert i < N_COINS_128
                            
                                amp: uint256 = _amp
                                D: uint256 = _D
                            
                                S_: uint256 = 0
                                _x: uint256 = 0
                                y_prev: uint256 = 0
                                c: uint256 = D
                                Ann: uint256 = amp * N_COINS
                            
                                for _i in range(MAX_COINS_128):
                            
                                    if _i == N_COINS_128:
                                        break
                            
                                    if _i == i:
                                        _x = x
                                    elif _i != j:
                                        _x = xp[_i]
                                    else:
                                        continue
                            
                                    S_ += _x
                                    c = c * D / (_x * N_COINS)
                            
                                c = c * D * A_PRECISION / (Ann * N_COINS)
                                b: uint256 = S_ + D * A_PRECISION / Ann  # - D
                                y: uint256 = D
                            
                                for _i in range(255):
                                    y_prev = y
                                    y = (y*y + c) / (2 * y + b - D)
                                    # Equality with the precision of 1
                                    if y > y_prev:
                                        if y - y_prev <= 1:
                                            return y
                                    else:
                                        if y_prev - y <= 1:
                                            return y
                                raise
                            
                            
                            @pure
                            @internal
                            def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256:
                                """
                                D invariant calculation in non-overflowing integer operations
                                iteratively
                            
                                A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i))
                            
                                Converging solution:
                                D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1)
                                """
                                S: uint256 = 0
                                for x in _xp:
                                    S += x
                                if S == 0:
                                    return 0
                            
                                D: uint256 = S
                                Ann: uint256 = _amp * N_COINS
                            
                                for i in range(255):
                            
                                    D_P: uint256 = D
                                    for x in _xp:
                                        D_P = D_P * D / x
                                    D_P /= pow_mod256(N_COINS, N_COINS)
                                    Dprev: uint256 = D
                            
                                    # (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P)
                                    D = (
                                        (unsafe_div(Ann * S, A_PRECISION) + D_P * N_COINS) * D
                                        /
                                        (
                                            unsafe_div((Ann - A_PRECISION) * D, A_PRECISION) +
                                            unsafe_add(N_COINS, 1) * D_P
                                        )
                                    )
                            
                                    # Equality with the precision of 1
                                    if D > Dprev:
                                        if D - Dprev <= 1:
                                            return D
                                    else:
                                        if Dprev - D <= 1:
                                            return D
                                # convergence typically occurs in 4 rounds or less, this should be unreachable!
                                # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity`
                                raise
                            
                            
                            @pure
                            @internal
                            def get_y_D(
                                A: uint256,
                                i: int128,
                                xp: DynArray[uint256, MAX_COINS],
                                D: uint256
                            ) -> uint256:
                                """
                                Calculate x[i] if one reduces D from being calculated for xp to D
                            
                                Done by solving quadratic equation iteratively.
                                x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A)
                                x_1**2 + b*x_1 = c
                            
                                x_1 = (x_1**2 + c) / (2*x_1 + b)
                                """
                                # x in the input is converted to the same price/precision
                            
                                assert i >= 0  # dev: i below zero
                                assert i < N_COINS_128  # dev: i above N_COINS
                            
                                S_: uint256 = 0
                                _x: uint256 = 0
                                y_prev: uint256 = 0
                                c: uint256 = D
                                Ann: uint256 = A * N_COINS
                            
                                for _i in range(MAX_COINS_128):
                            
                                    if _i == N_COINS_128:
                                        break
                            
                                    if _i != i:
                                        _x = xp[_i]
                                    else:
                                        continue
                                    S_ += _x
                                    c = c * D / (_x * N_COINS)
                            
                                c = c * D * A_PRECISION / (Ann * N_COINS)
                                b: uint256 = S_ + D * A_PRECISION / Ann
                                y: uint256 = D
                            
                                for _i in range(255):
                                    y_prev = y
                                    y = (y*y + c) / (2 * y + b - D)
                                    # Equality with the precision of 1
                                    if y > y_prev:
                                        if y - y_prev <= 1:
                                            return y
                                    else:
                                        if y_prev - y <= 1:
                                            return y
                                raise
                            
                            
                            @view
                            @internal
                            def _A() -> uint256:
                                """
                                Handle ramping A up or down
                                """
                                t1: uint256 = self.future_A_time
                                A1: uint256 = self.future_A
                            
                                if block.timestamp < t1:
                                    A0: uint256 = self.initial_A
                                    t0: uint256 = self.initial_A_time
                                    # Expressions in uint256 cannot have negative numbers, thus "if"
                                    if A1 > A0:
                                        return A0 + unsafe_sub(A1, A0) * (block.timestamp - t0) / (t1 - t0)
                                    else:
                                        return A0 - unsafe_sub(A0, A1) * (block.timestamp - t0) / (t1 - t0)
                            
                                else:  # when t1 == 0 or block.timestamp >= t1
                                    return A1
                            
                            
                            @pure
                            @internal
                            def _xp_mem(
                                _rates: DynArray[uint256, MAX_COINS],
                                _balances: DynArray[uint256, MAX_COINS]
                            ) -> DynArray[uint256, MAX_COINS]:
                            
                                result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
                                for i in range(N_COINS_128, bound=MAX_COINS_128):
                                    result.append(unsafe_div(_rates[i] * _balances[i], PRECISION))
                                return result
                            
                            
                            @view
                            @internal
                            def get_D_mem(
                                _rates: DynArray[uint256, MAX_COINS],
                                _balances: DynArray[uint256, MAX_COINS],
                                _amp: uint256
                            ) -> uint256:
                                xp: DynArray[uint256, MAX_COINS] = self._xp_mem(_rates, _balances)
                                return self.get_D(xp, _amp)
                            
                            
                            @view
                            @internal
                            def _calc_withdraw_one_coin(
                                _burn_amount: uint256,
                                i: int128
                            ) -> (
                                uint256,
                                uint256,
                                DynArray[uint256, MAX_COINS],
                                uint256,
                                uint256
                            ):
                                # First, need to calculate
                                # * Get current D
                                # * Solve Eqn against y_i for D - _token_amount
                                amp: uint256 = self._A()
                                rates: DynArray[uint256, MAX_COINS] = self._stored_rates()
                                xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances())
                                D0: uint256 = self.get_D(xp, amp)
                            
                                total_supply: uint256 = self.total_supply
                                D1: uint256 = D0 - _burn_amount * D0 / total_supply
                                new_y: uint256 = self.get_y_D(amp, i, xp, D1)
                            
                                base_fee: uint256 = unsafe_div(
                                    unsafe_mul(self.fee, N_COINS),
                                    unsafe_mul(4, unsafe_sub(N_COINS, 1))
                                )
                                xp_reduced: DynArray[uint256, MAX_COINS] = xp
                                ys: uint256 = unsafe_div((D0 + D1), unsafe_mul(2, N_COINS))
                            
                                dx_expected: uint256 = 0
                                xp_j: uint256 = 0
                                xavg: uint256 = 0
                                dynamic_fee: uint256 = 0
                            
                                for j in range(MAX_COINS_128):
                            
                                    if j == N_COINS_128:
                                        break
                            
                                    dx_expected = 0
                                    xp_j = xp[j]
                            
                                    if j == i:
                                        dx_expected = xp_j * D1 / D0 - new_y
                                        xavg = unsafe_div((xp_j + new_y), 2)
                                    else:
                                        dx_expected = xp_j - xp_j * D1 / D0
                                        xavg = xp_j
                            
                                    dynamic_fee = self._dynamic_fee(xavg, ys, base_fee)
                                    xp_reduced[j] = xp_j - unsafe_div(dynamic_fee * dx_expected, FEE_DENOMINATOR)
                            
                                dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1)
                                dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i]  # w/o fees
                                dy = unsafe_div((dy - 1) * PRECISION, rates[i])  # Withdraw less to account for rounding errors
                            
                                # update xp with new_y for p calculations.
                                xp[i] = new_y
                            
                                return dy, dy_0 - dy, xp, amp, D1
                            
                            
                            # -------------------------- AMM Price Methods -------------------------------
                            
                            @pure
                            @internal
                            def pack_2(p1: uint256, p2: uint256) -> uint256:
                                assert p1 < 2**128
                                assert p2 < 2**128
                                return p1 | (p2 << 128)
                            
                            
                            @pure
                            @internal
                            def unpack_2(packed: uint256) -> uint256[2]:
                                return [packed & (2**128 - 1), packed >> 128]
                            
                            
                            @internal
                            @pure
                            def _get_p(
                                xp: DynArray[uint256, MAX_COINS],
                                amp: uint256,
                                D: uint256,
                            ) -> DynArray[uint256, MAX_COINS]:
                            
                                # dx_0 / dx_1 only, however can have any number of coins in pool
                                ANN: uint256 = unsafe_mul(amp, N_COINS)
                                Dr: uint256 = unsafe_div(D, pow_mod256(N_COINS, N_COINS))
                            
                                for i in range(N_COINS_128, bound=MAX_COINS_128):
                                    Dr = Dr * D / xp[i]
                            
                                p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
                                xp0_A: uint256 = unsafe_div(ANN * xp[0], A_PRECISION)
                            
                                for i in range(1, MAX_COINS):
                            
                                    if i == N_COINS:
                                        break
                            
                                    p.append(10**18 * (xp0_A + unsafe_div(Dr * xp[0], xp[i])) / (xp0_A + Dr))
                            
                                return p
                            
                            
                            @internal
                            def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256):
                                """
                                @notice Upkeeps price and D oracles.
                                """
                                ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time)
                                last_prices_packed_current: DynArray[uint256, MAX_COINS] = self.last_prices_packed
                                last_prices_packed_new: DynArray[uint256, MAX_COINS] = last_prices_packed_current
                            
                                spot_price: DynArray[uint256, MAX_COINS] = self._get_p(xp, amp, D)
                            
                                # -------------------------- Upkeep price oracle -------------------------
                            
                                for i in range(MAX_COINS):
                            
                                    if i == N_COINS - 1:
                                        break
                            
                                    if spot_price[i] != 0:
                            
                                        # Update packed prices -----------------
                                        last_prices_packed_new[i] = self.pack_2(
                                            min(spot_price[i], 2 * 10**18),  # <----- Cap spot value by 2.
                                            self._calc_moving_average(
                                                last_prices_packed_current[i],
                                                self.ma_exp_time,
                                                ma_last_time_unpacked[0],  # index 0 is ma_last_time for prices
                                            )
                                        )
                            
                                self.last_prices_packed = last_prices_packed_new
                            
                                # ---------------------------- Upkeep D oracle ---------------------------
                            
                                last_D_packed_current: uint256 = self.last_D_packed
                                self.last_D_packed = self.pack_2(
                                    D,
                                    self._calc_moving_average(
                                        last_D_packed_current,
                                        self.D_ma_time,
                                        ma_last_time_unpacked[1],  # index 1 is ma_last_time for D
                                    )
                                )
                            
                                # Housekeeping: Update ma_last_time for p and D oracles ------------------
                                for i in range(2):
                                    if ma_last_time_unpacked[i] < block.timestamp:
                                        ma_last_time_unpacked[i] = block.timestamp
                            
                                self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1])
                            
                            
                            @internal
                            @view
                            def _calc_moving_average(
                                packed_value: uint256,
                                averaging_window: uint256,
                                ma_last_time: uint256
                            ) -> uint256:
                            
                                last_spot_value: uint256 = packed_value & (2**128 - 1)
                                last_ema_value: uint256 = (packed_value >> 128)
                            
                                if ma_last_time < block.timestamp:  # calculate new_ema_value and return that.
                                    alpha: uint256 = self.exp(
                                        -convert(
                                            unsafe_div(unsafe_mul(unsafe_sub(block.timestamp, ma_last_time), 10**18), averaging_window), int256
                                        )
                                    )
                                    return unsafe_div(last_spot_value * (10**18 - alpha) + last_ema_value * alpha, 10**18)
                            
                                return last_ema_value
                            
                            
                            @view
                            @external
                            def last_price(i: uint256) -> uint256:
                                return self.last_prices_packed[i] & (2**128 - 1)
                            
                            
                            @view
                            @external
                            def ema_price(i: uint256) -> uint256:
                                return (self.last_prices_packed[i] >> 128)
                            
                            
                            @external
                            @view
                            def get_p(i: uint256) -> uint256:
                                """
                                @notice Returns the AMM State price of token
                                @dev if i = 0, it will return the state price of coin[1].
                                @param i index of state price (0 for coin[1], 1 for coin[2], ...)
                                @return uint256 The state price quoted by the AMM for coin[i+1]
                                """
                                amp: uint256 = self._A()
                                xp: DynArray[uint256, MAX_COINS] = self._xp_mem(
                                    self._stored_rates(), self._balances()
                                )
                                D: uint256 = self.get_D(xp, amp)
                                return self._get_p(xp, amp, D)[i]
                            
                            
                            @external
                            @view
                            @nonreentrant('lock')
                            def price_oracle(i: uint256) -> uint256:
                                return self._calc_moving_average(
                                    self.last_prices_packed[i],
                                    self.ma_exp_time,
                                    self.ma_last_time & (2**128 - 1)
                                )
                            
                            
                            @external
                            @view
                            @nonreentrant('lock')
                            def D_oracle() -> uint256:
                                return self._calc_moving_average(
                                    self.last_D_packed,
                                    self.D_ma_time,
                                    self.ma_last_time >> 128
                                )
                            
                            
                            # ----------------------------- Math Utils -----------------------------------
                            
                            
                            @internal
                            @pure
                            def exp(x: int256) -> uint256:
                                """
                                @dev Calculates the natural exponential function of a signed integer with
                                     a precision of 1e18.
                                @notice Note that this function consumes about 810 gas units. The implementation
                                        is inspired by Remco Bloemen's implementation under the MIT license here:
                                        https://xn--2-umb.com/22/exp-ln.
                                @dev This implementation is derived from Snekmate, which is authored
                                     by pcaversaccio (Snekmate), distributed under the AGPL-3.0 license.
                                     https://github.com/pcaversaccio/snekmate
                                @param x The 32-byte variable.
                                @return int256 The 32-byte calculation result.
                                """
                                value: int256 = x
                            
                                # If the result is `< 0.5`, we return zero. This happens when we have the following:
                                # "x <= floor(log(0.5e18) * 1e18) ~ -42e18".
                                if (x <= -41446531673892822313):
                                    return empty(uint256)
                            
                                # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer.
                                # This happens when "x >= floor(log((2 ** 255 - 1) / 1e18) * 1e18) ~ 135".
                                assert x < 135305999368893231589, "wad_exp overflow"
                            
                                # `x` is now in the range "(-42, 136) * 1e18". Convert to "(-42, 136) * 2 ** 96" for higher
                                # intermediate precision and a binary base. This base conversion is a multiplication with
                                # "1e18 / 2 ** 96 = 5 ** 18 / 2 ** 78".
                                value = unsafe_div(x << 78, 5 ** 18)
                            
                                # Reduce the range of `x` to "(-½ ln 2, ½ ln 2) * 2 ** 96" by factoring out powers of two
                                # so that "exp(x) = exp(x') * 2 ** k", where `k` is a signer integer. Solving this gives
                                # "k = round(x / log(2))" and "x' = x - k * log(2)". Thus, `k` is in the range "[-61, 195]".
                                k: int256 = unsafe_add(unsafe_div(value << 96, 54916777467707473351141471128), 2 ** 95) >> 96
                                value = unsafe_sub(value, unsafe_mul(k, 54916777467707473351141471128))
                            
                                # Evaluate using a "(6, 7)"-term rational approximation. Since `p` is monic,
                                # we will multiply by a scaling factor later.
                                y: int256 = unsafe_add(unsafe_mul(unsafe_add(value, 1346386616545796478920950773328), value) >> 96, 57155421227552351082224309758442)
                                p: int256 = unsafe_add(unsafe_mul(unsafe_add(unsafe_mul(unsafe_sub(unsafe_add(y, value), 94201549194550492254356042504812), y) >> 96,\
                                                       28719021644029726153956944680412240), value), 4385272521454847904659076985693276 << 96)
                            
                                # We leave `p` in the "2 ** 192" base so that we do not have to scale it up
                                # again for the division.
                                q: int256 = unsafe_add(unsafe_mul(unsafe_sub(value, 2855989394907223263936484059900), value) >> 96, 50020603652535783019961831881945)
                                q = unsafe_sub(unsafe_mul(q, value) >> 96, 533845033583426703283633433725380)
                                q = unsafe_add(unsafe_mul(q, value) >> 96, 3604857256930695427073651918091429)
                                q = unsafe_sub(unsafe_mul(q, value) >> 96, 14423608567350463180887372962807573)
                                q = unsafe_add(unsafe_mul(q, value) >> 96, 26449188498355588339934803723976023)
                            
                                # The polynomial `q` has no zeros in the range because all its roots are complex.
                                # No scaling is required, as `p` is already "2 ** 96" too large. Also,
                                # `r` is in the range "(0.09, 0.25) * 2**96" after the division.
                                r: int256 = unsafe_div(p, q)
                            
                                # To finalise the calculation, we have to multiply `r` by:
                                #   - the scale factor "s = ~6.031367120",
                                #   - the factor "2 ** k" from the range reduction, and
                                #   - the factor "1e18 / 2 ** 96" for the base conversion.
                                # We do this all at once, with an intermediate result in "2**213" base,
                                # so that the final right shift always gives a positive value.
                            
                                # Note that to circumvent Vyper's safecast feature for the potentially
                                # negative parameter value `r`, we first convert `r` to `bytes32` and
                                # subsequently to `uint256`. Remember that the EVM default behaviour is
                                # to use two's complement representation to handle signed integers.
                                return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256)
                            
                            
                            # ---------------------------- ERC20 Utils -----------------------------------
                            
                            @view
                            @internal
                            def _domain_separator() -> bytes32:
                                if chain.id != CACHED_CHAIN_ID:
                                    return keccak256(
                                        _abi_encode(
                                            EIP712_TYPEHASH,
                                            NAME_HASH,
                                            VERSION_HASH,
                                            chain.id,
                                            self,
                                            salt,
                                        )
                                    )
                                return CACHED_DOMAIN_SEPARATOR
                            
                            
                            @internal
                            def _transfer(_from: address, _to: address, _value: uint256):
                                # # NOTE: vyper does not allow underflows
                                # #       so the following subtraction would revert on insufficient balance
                                self.balanceOf[_from] -= _value
                                self.balanceOf[_to] += _value
                            
                                log Transfer(_from, _to, _value)
                            
                            
                            @internal
                            def _burnFrom(_from: address, _burn_amount: uint256):
                            
                                self.total_supply -= _burn_amount
                                self.balanceOf[_from] -= _burn_amount
                                log Transfer(_from, empty(address), _burn_amount)
                            
                            
                            @external
                            def transfer(_to : address, _value : uint256) -> bool:
                                """
                                @dev Transfer token for a specified address
                                @param _to The address to transfer to.
                                @param _value The amount to be transferred.
                                """
                                self._transfer(msg.sender, _to, _value)
                                return True
                            
                            
                            @external
                            def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
                                """
                                 @dev Transfer tokens from one address to another.
                                 @param _from address The address which you want to send tokens from
                                 @param _to address The address which you want to transfer to
                                 @param _value uint256 the amount of tokens to be transferred
                                """
                                self._transfer(_from, _to, _value)
                            
                                _allowance: uint256 = self.allowance[_from][msg.sender]
                                if _allowance != max_value(uint256):
                                    _new_allowance: uint256 = _allowance - _value
                                    self.allowance[_from][msg.sender] = _new_allowance
                                    log Approval(_from, msg.sender, _new_allowance)
                            
                                return True
                            
                            
                            @external
                            def approve(_spender : address, _value : uint256) -> bool:
                                """
                                @notice Approve the passed address to transfer the specified amount of
                                        tokens on behalf of msg.sender
                                @dev Beware that changing an allowance via this method brings the risk that
                                     someone may use both the old and new allowance by unfortunate transaction
                                     ordering: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                @param _spender The address which will transfer the funds
                                @param _value The amount of tokens that may be transferred
                                @return bool success
                                """
                                self.allowance[msg.sender][_spender] = _value
                            
                                log Approval(msg.sender, _spender, _value)
                                return True
                            
                            
                            @external
                            def permit(
                                _owner: address,
                                _spender: address,
                                _value: uint256,
                                _deadline: uint256,
                                _v: uint8,
                                _r: bytes32,
                                _s: bytes32
                            ) -> bool:
                                """
                                @notice Approves spender by owner's signature to expend owner's tokens.
                                    See https://eips.ethereum.org/EIPS/eip-2612.
                                @dev Inspired by https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L753-L793
                                @dev Supports smart contract wallets which implement ERC1271
                                    https://eips.ethereum.org/EIPS/eip-1271
                                @param _owner The address which is a source of funds and has signed the Permit.
                                @param _spender The address which is allowed to spend the funds.
                                @param _value The amount of tokens to be spent.
                                @param _deadline The timestamp after which the Permit is no longer valid.
                                @param _v The bytes[64] of the valid secp256k1 signature of permit by owner
                                @param _r The bytes[0:32] of the valid secp256k1 signature of permit by owner
                                @param _s The bytes[32:64] of the valid secp256k1 signature of permit by owner
                                @return True, if transaction completes successfully
                                """
                                assert _owner != empty(address)
                                assert block.timestamp <= _deadline
                            
                                nonce: uint256 = self.nonces[_owner]
                                digest: bytes32 = keccak256(
                                    concat(
                                        b"\x19\x01",
                                        self._domain_separator(),
                                        keccak256(_abi_encode(EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline))
                                    )
                                )
                            
                                if _owner.is_contract:
                                    sig: Bytes[65] = concat(_abi_encode(_r, _s), slice(convert(_v, bytes32), 31, 1))
                                    # reentrancy not a concern since this is a staticcall
                                    assert ERC1271(_owner).isValidSignature(digest, sig) == ERC1271_MAGIC_VAL
                                else:
                                    assert ecrecover(digest, convert(_v, uint256), convert(_r, uint256), convert(_s, uint256)) == _owner
                            
                                self.allowance[_owner][_spender] = _value
                                self.nonces[_owner] = unsafe_add(nonce, 1)
                            
                                log Approval(_owner, _spender, _value)
                                return True
                            
                            
                            @view
                            @external
                            def DOMAIN_SEPARATOR() -> bytes32:
                                """
                                @notice EIP712 domain separator.
                                @return bytes32 Domain Separator set for the current chain.
                                """
                                return self._domain_separator()
                            
                            
                            # ------------------------- AMM View Functions -------------------------------
                            
                            
                            @view
                            @external
                            def get_dx(i: int128, j: int128, dy: uint256) -> uint256:
                                """
                                @notice Calculate the current input dx given output dy
                                @dev Index values can be found via the `coins` public getter method
                                @param i Index value for the coin to send
                                @param j Index value of the coin to receive
                                @param dy Amount of `j` being received after exchange
                                @return Amount of `i` predicted
                                """
                                return StableSwapViews(factory.views_implementation()).get_dx(i, j, dy, self)
                            
                            
                            @view
                            @external
                            def get_dy(i: int128, j: int128, dx: uint256) -> uint256:
                                """
                                @notice Calculate the current output dy given input dx
                                @dev Index values can be found via the `coins` public getter method
                                @param i Index value for the coin to send
                                @param j Index value of the coin to receive
                                @param dx Amount of `i` being exchanged
                                @return Amount of `j` predicted
                                """
                                return StableSwapViews(factory.views_implementation()).get_dy(i, j, dx, self)
                            
                            
                            @view
                            @external
                            def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256:
                                """
                                @notice Calculate the amount received when withdrawing a single coin
                                @param _burn_amount Amount of LP tokens to burn in the withdrawal
                                @param i Index value of the coin to withdraw
                                @return Amount of coin received
                                """
                                return self._calc_withdraw_one_coin(_burn_amount, i)[0]
                            
                            
                            @view
                            @external
                            @nonreentrant('lock')
                            def totalSupply() -> uint256:
                                """
                                @notice The total supply of pool LP tokens
                                @return self.total_supply, 18 decimals.
                                """
                                return self.total_supply
                            
                            
                            @view
                            @external
                            @nonreentrant('lock')
                            def get_virtual_price() -> uint256:
                                """
                                @notice The current virtual price of the pool LP token
                                @dev Useful for calculating profits.
                                     The method may be vulnerable to donation-style attacks if implementation
                                     contains rebasing tokens. For integrators, caution is advised.
                                @return LP token virtual price normalized to 1e18
                                """
                                amp: uint256 = self._A()
                                xp: DynArray[uint256, MAX_COINS] = self._xp_mem(
                                    self._stored_rates(), self._balances()
                                )
                                D: uint256 = self.get_D(xp, amp)
                                # D is in the units similar to DAI (e.g. converted to precision 1e18)
                                # When balanced, D = n * x_u - total virtual value of the portfolio
                                return D * PRECISION / self.total_supply
                            
                            
                            @view
                            @external
                            def calc_token_amount(
                                _amounts: DynArray[uint256, MAX_COINS],
                                _is_deposit: bool
                            ) -> uint256:
                                """
                                @notice Calculate addition or reduction in token supply from a deposit or withdrawal
                                @param _amounts Amount of each coin being deposited
                                @param _is_deposit set True for deposits, False for withdrawals
                                @return Expected amount of LP tokens received
                                """
                                return StableSwapViews(factory.views_implementation()).calc_token_amount(_amounts, _is_deposit, self)
                            
                            
                            @view
                            @external
                            def A() -> uint256:
                                return unsafe_div(self._A(), A_PRECISION)
                            
                            
                            @view
                            @external
                            def A_precise() -> uint256:
                                return self._A()
                            
                            
                            @view
                            @external
                            def balances(i: uint256) -> uint256:
                                """
                                @notice Get the current balance of a coin within the
                                        pool, less the accrued admin fees
                                @param i Index value for the coin to query balance of
                                @return Token balance
                                """
                                return self._balances()[i]
                            
                            
                            @view
                            @external
                            def get_balances() -> DynArray[uint256, MAX_COINS]:
                                return self._balances()
                            
                            
                            @view
                            @external
                            def stored_rates() -> DynArray[uint256, MAX_COINS]:
                                return self._stored_rates()
                            
                            
                            @view
                            @external
                            def dynamic_fee(i: int128, j: int128) -> uint256:
                                """
                                @notice Return the fee for swapping between `i` and `j`
                                @param i Index value for the coin to send
                                @param j Index value of the coin to receive
                                @return Swap fee expressed as an integer with 1e10 precision
                                """
                                return StableSwapViews(factory.views_implementation()).dynamic_fee(i, j, self)
                            
                            
                            # --------------------------- AMM Admin Functions ----------------------------
                            
                            
                            @external
                            def ramp_A(_future_A: uint256, _future_time: uint256):
                                assert msg.sender == factory.admin()  # dev: only owner
                                assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME
                                assert _future_time >= block.timestamp + MIN_RAMP_TIME  # dev: insufficient time
                            
                                _initial_A: uint256 = self._A()
                                _future_A_p: uint256 = _future_A * A_PRECISION
                            
                                assert _future_A > 0 and _future_A < MAX_A
                                if _future_A_p < _initial_A:
                                    assert _future_A_p * MAX_A_CHANGE >= _initial_A
                                else:
                                    assert _future_A_p <= _initial_A * MAX_A_CHANGE
                            
                                self.initial_A = _initial_A
                                self.future_A = _future_A_p
                                self.initial_A_time = block.timestamp
                                self.future_A_time = _future_time
                            
                                log RampA(_initial_A, _future_A_p, block.timestamp, _future_time)
                            
                            
                            @external
                            def stop_ramp_A():
                                assert msg.sender == factory.admin()  # dev: only owner
                            
                                current_A: uint256 = self._A()
                                self.initial_A = current_A
                                self.future_A = current_A
                                self.initial_A_time = block.timestamp
                                self.future_A_time = block.timestamp
                                # now (block.timestamp < t1) is always False, so we return saved A
                            
                                log StopRampA(current_A, block.timestamp)
                            
                            
                            @external
                            def set_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256):
                            
                                assert msg.sender == factory.admin()
                            
                                # set new fee:
                                assert _new_fee <= MAX_FEE
                                self.fee = _new_fee
                            
                                # set new offpeg_fee_multiplier:
                                assert _new_offpeg_fee_multiplier * _new_fee <= MAX_FEE * FEE_DENOMINATOR  # dev: offpeg multiplier exceeds maximum
                                self.offpeg_fee_multiplier = _new_offpeg_fee_multiplier
                            
                                log ApplyNewFee(_new_fee, _new_offpeg_fee_multiplier)
                            
                            
                            @external
                            def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256):
                                """
                                @notice Set the moving average window of the price oracles.
                                @param _ma_exp_time Moving average window for the price oracle. It is time_in_seconds / ln(2).
                                @param _D_ma_time Moving average window for the D oracle. It is time_in_seconds / ln(2).
                                """
                                assert msg.sender == factory.admin()  # dev: only owner
                                assert unsafe_mul(_ma_exp_time, _D_ma_time) > 0  # dev: 0 in input values
                            
                                self.ma_exp_time = _ma_exp_time
                                self.D_ma_time = _D_ma_time
                            
                                log SetNewMATime(_ma_exp_time, _D_ma_time)

                            File 15 of 17: FiatTokenV2_2
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { EIP712Domain } from "./EIP712Domain.sol"; // solhint-disable-line no-unused-import
                            import { Blacklistable } from "../v1/Blacklistable.sol"; // solhint-disable-line no-unused-import
                            import { FiatTokenV1 } from "../v1/FiatTokenV1.sol"; // solhint-disable-line no-unused-import
                            import { FiatTokenV2 } from "./FiatTokenV2.sol"; // solhint-disable-line no-unused-import
                            import { FiatTokenV2_1 } from "./FiatTokenV2_1.sol";
                            import { EIP712 } from "../util/EIP712.sol";
                            // solhint-disable func-name-mixedcase
                            /**
                             * @title FiatToken V2.2
                             * @notice ERC20 Token backed by fiat reserves, version 2.2
                             */
                            contract FiatTokenV2_2 is FiatTokenV2_1 {
                                /**
                                 * @notice Initialize v2.2
                                 * @param accountsToBlacklist   A list of accounts to migrate from the old blacklist
                                 * @param newSymbol             New token symbol
                                 * data structure to the new blacklist data structure.
                                 */
                                function initializeV2_2(
                                    address[] calldata accountsToBlacklist,
                                    string calldata newSymbol
                                ) external {
                                    // solhint-disable-next-line reason-string
                                    require(_initializedVersion == 2);
                                    // Update fiat token symbol
                                    symbol = newSymbol;
                                    // Add previously blacklisted accounts to the new blacklist data structure
                                    // and remove them from the old blacklist data structure.
                                    for (uint256 i = 0; i < accountsToBlacklist.length; i++) {
                                        require(
                                            _deprecatedBlacklisted[accountsToBlacklist[i]],
                                            "FiatTokenV2_2: Blacklisting previously unblacklisted account!"
                                        );
                                        _blacklist(accountsToBlacklist[i]);
                                        delete _deprecatedBlacklisted[accountsToBlacklist[i]];
                                    }
                                    _blacklist(address(this));
                                    delete _deprecatedBlacklisted[address(this)];
                                    _initializedVersion = 3;
                                }
                                /**
                                 * @dev Internal function to get the current chain id.
                                 * @return The current chain id.
                                 */
                                function _chainId() internal virtual view returns (uint256) {
                                    uint256 chainId;
                                    assembly {
                                        chainId := chainid()
                                    }
                                    return chainId;
                                }
                                /**
                                 * @inheritdoc EIP712Domain
                                 */
                                function _domainSeparator() internal override view returns (bytes32) {
                                    return EIP712.makeDomainSeparator(name, "2", _chainId());
                                }
                                /**
                                 * @notice Update allowance with a signed permit
                                 * @dev EOA wallet signatures should be packed in the order of r, s, v.
                                 * @param owner       Token owner's address (Authorizer)
                                 * @param spender     Spender's address
                                 * @param value       Amount of allowance
                                 * @param deadline    The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                                 * @param signature   Signature bytes signed by an EOA wallet or a contract wallet
                                 */
                                function permit(
                                    address owner,
                                    address spender,
                                    uint256 value,
                                    uint256 deadline,
                                    bytes memory signature
                                ) external whenNotPaused {
                                    _permit(owner, spender, value, deadline, signature);
                                }
                                /**
                                 * @notice Execute a transfer with a signed authorization
                                 * @dev EOA wallet signatures should be packed in the order of r, s, v.
                                 * @param from          Payer's address (Authorizer)
                                 * @param to            Payee's address
                                 * @param value         Amount to be transferred
                                 * @param validAfter    The time after which this is valid (unix time)
                                 * @param validBefore   The time before which this is valid (unix time)
                                 * @param nonce         Unique nonce
                                 * @param signature     Signature bytes signed by an EOA wallet or a contract wallet
                                 */
                                function transferWithAuthorization(
                                    address from,
                                    address to,
                                    uint256 value,
                                    uint256 validAfter,
                                    uint256 validBefore,
                                    bytes32 nonce,
                                    bytes memory signature
                                ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                    _transferWithAuthorization(
                                        from,
                                        to,
                                        value,
                                        validAfter,
                                        validBefore,
                                        nonce,
                                        signature
                                    );
                                }
                                /**
                                 * @notice Receive a transfer with a signed authorization from the payer
                                 * @dev This has an additional check to ensure that the payee's address
                                 * matches the caller of this function to prevent front-running attacks.
                                 * EOA wallet signatures should be packed in the order of r, s, v.
                                 * @param from          Payer's address (Authorizer)
                                 * @param to            Payee's address
                                 * @param value         Amount to be transferred
                                 * @param validAfter    The time after which this is valid (unix time)
                                 * @param validBefore   The time before which this is valid (unix time)
                                 * @param nonce         Unique nonce
                                 * @param signature     Signature bytes signed by an EOA wallet or a contract wallet
                                 */
                                function receiveWithAuthorization(
                                    address from,
                                    address to,
                                    uint256 value,
                                    uint256 validAfter,
                                    uint256 validBefore,
                                    bytes32 nonce,
                                    bytes memory signature
                                ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                    _receiveWithAuthorization(
                                        from,
                                        to,
                                        value,
                                        validAfter,
                                        validBefore,
                                        nonce,
                                        signature
                                    );
                                }
                                /**
                                 * @notice Attempt to cancel an authorization
                                 * @dev Works only if the authorization is not yet used.
                                 * EOA wallet signatures should be packed in the order of r, s, v.
                                 * @param authorizer    Authorizer's address
                                 * @param nonce         Nonce of the authorization
                                 * @param signature     Signature bytes signed by an EOA wallet or a contract wallet
                                 */
                                function cancelAuthorization(
                                    address authorizer,
                                    bytes32 nonce,
                                    bytes memory signature
                                ) external whenNotPaused {
                                    _cancelAuthorization(authorizer, nonce, signature);
                                }
                                /**
                                 * @dev Helper method that sets the blacklist state of an account on balanceAndBlacklistStates.
                                 * If _shouldBlacklist is true, we apply a (1 << 255) bitmask with an OR operation on the
                                 * account's balanceAndBlacklistState. This flips the high bit for the account to 1,
                                 * indicating that the account is blacklisted.
                                 *
                                 * If _shouldBlacklist if false, we reset the account's balanceAndBlacklistStates to their
                                 * balances. This clears the high bit for the account, indicating that the account is unblacklisted.
                                 * @param _account         The address of the account.
                                 * @param _shouldBlacklist True if the account should be blacklisted, false if the account should be unblacklisted.
                                 */
                                function _setBlacklistState(address _account, bool _shouldBlacklist)
                                    internal
                                    override
                                {
                                    balanceAndBlacklistStates[_account] = _shouldBlacklist
                                        ? balanceAndBlacklistStates[_account] | (1 << 255)
                                        : _balanceOf(_account);
                                }
                                /**
                                 * @dev Helper method that sets the balance of an account on balanceAndBlacklistStates.
                                 * Since balances are stored in the last 255 bits of the balanceAndBlacklistStates value,
                                 * we need to ensure that the updated balance does not exceed (2^255 - 1).
                                 * Since blacklisted accounts' balances cannot be updated, the method will also
                                 * revert if the account is blacklisted
                                 * @param _account The address of the account.
                                 * @param _balance The new fiat token balance of the account (max: (2^255 - 1)).
                                 */
                                function _setBalance(address _account, uint256 _balance) internal override {
                                    require(
                                        _balance <= ((1 << 255) - 1),
                                        "FiatTokenV2_2: Balance exceeds (2^255 - 1)"
                                    );
                                    require(
                                        !_isBlacklisted(_account),
                                        "FiatTokenV2_2: Account is blacklisted"
                                    );
                                    balanceAndBlacklistStates[_account] = _balance;
                                }
                                /**
                                 * @inheritdoc Blacklistable
                                 */
                                function _isBlacklisted(address _account)
                                    internal
                                    override
                                    view
                                    returns (bool)
                                {
                                    return balanceAndBlacklistStates[_account] >> 255 == 1;
                                }
                                /**
                                 * @dev Helper method to obtain the balance of an account. Since balances
                                 * are stored in the last 255 bits of the balanceAndBlacklistStates value,
                                 * we apply a ((1 << 255) - 1) bit bitmask with an AND operation on the
                                 * balanceAndBlacklistState to obtain the balance.
                                 * @param _account  The address of the account.
                                 * @return          The fiat token balance of the account.
                                 */
                                function _balanceOf(address _account)
                                    internal
                                    override
                                    view
                                    returns (uint256)
                                {
                                    return balanceAndBlacklistStates[_account] & ((1 << 255) - 1);
                                }
                                /**
                                 * @inheritdoc FiatTokenV1
                                 */
                                function approve(address spender, uint256 value)
                                    external
                                    override
                                    whenNotPaused
                                    returns (bool)
                                {
                                    _approve(msg.sender, spender, value);
                                    return true;
                                }
                                /**
                                 * @inheritdoc FiatTokenV2
                                 */
                                function permit(
                                    address owner,
                                    address spender,
                                    uint256 value,
                                    uint256 deadline,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                ) external override whenNotPaused {
                                    _permit(owner, spender, value, deadline, v, r, s);
                                }
                                /**
                                 * @inheritdoc FiatTokenV2
                                 */
                                function increaseAllowance(address spender, uint256 increment)
                                    external
                                    override
                                    whenNotPaused
                                    returns (bool)
                                {
                                    _increaseAllowance(msg.sender, spender, increment);
                                    return true;
                                }
                                /**
                                 * @inheritdoc FiatTokenV2
                                 */
                                function decreaseAllowance(address spender, uint256 decrement)
                                    external
                                    override
                                    whenNotPaused
                                    returns (bool)
                                {
                                    _decreaseAllowance(msg.sender, spender, decrement);
                                    return true;
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            pragma solidity >=0.6.2 <0.8.0;
                            /**
                             * @dev Collection of functions related to the address type
                             */
                            library Address {
                                /**
                                 * @dev Returns true if `account` is a contract.
                                 *
                                 * [IMPORTANT]
                                 * ====
                                 * It is unsafe to assume that an address for which this function returns
                                 * false is an externally-owned account (EOA) and not a contract.
                                 *
                                 * Among others, `isContract` will return false for the following
                                 * types of addresses:
                                 *
                                 *  - an externally-owned account
                                 *  - a contract in construction
                                 *  - an address where a contract will be created
                                 *  - an address where a contract lived, but was destroyed
                                 * ====
                                 */
                                function isContract(address account) internal view returns (bool) {
                                    // This method relies on extcodesize, which returns 0 for contracts in
                                    // construction, since the code is only stored at the end of the
                                    // constructor execution.
                                    uint256 size;
                                    // solhint-disable-next-line no-inline-assembly
                                    assembly { size := extcodesize(account) }
                                    return size > 0;
                                }
                                /**
                                 * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                                 * `recipient`, forwarding all available gas and reverting on errors.
                                 *
                                 * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                                 * of certain opcodes, possibly making contracts go over the 2300 gas limit
                                 * imposed by `transfer`, making them unable to receive funds via
                                 * `transfer`. {sendValue} removes this limitation.
                                 *
                                 * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                                 *
                                 * IMPORTANT: because control is transferred to `recipient`, care must be
                                 * taken to not create reentrancy vulnerabilities. Consider using
                                 * {ReentrancyGuard} or the
                                 * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                                 */
                                function sendValue(address payable recipient, uint256 amount) internal {
                                    require(address(this).balance >= amount, "Address: insufficient balance");
                                    // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                                    (bool success, ) = recipient.call{ value: amount }("");
                                    require(success, "Address: unable to send value, recipient may have reverted");
                                }
                                /**
                                 * @dev Performs a Solidity function call using a low level `call`. A
                                 * plain`call` is an unsafe replacement for a function call: use this
                                 * function instead.
                                 *
                                 * If `target` reverts with a revert reason, it is bubbled up by this
                                 * function (like regular Solidity function calls).
                                 *
                                 * Returns the raw returned data. To convert to the expected return value,
                                 * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                                 *
                                 * Requirements:
                                 *
                                 * - `target` must be a contract.
                                 * - calling `target` with `data` must not revert.
                                 *
                                 * _Available since v3.1._
                                 */
                                function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                                  return functionCall(target, data, "Address: low-level call failed");
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                                 * `errorMessage` as a fallback revert reason when `target` reverts.
                                 *
                                 * _Available since v3.1._
                                 */
                                function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                                    return functionCallWithValue(target, data, 0, errorMessage);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but also transferring `value` wei to `target`.
                                 *
                                 * Requirements:
                                 *
                                 * - the calling contract must have an ETH balance of at least `value`.
                                 * - the called Solidity function must be `payable`.
                                 *
                                 * _Available since v3.1._
                                 */
                                function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                                    return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                                 * with `errorMessage` as a fallback revert reason when `target` reverts.
                                 *
                                 * _Available since v3.1._
                                 */
                                function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                                    require(address(this).balance >= value, "Address: insufficient balance for call");
                                    require(isContract(target), "Address: call to non-contract");
                                    // solhint-disable-next-line avoid-low-level-calls
                                    (bool success, bytes memory returndata) = target.call{ value: value }(data);
                                    return _verifyCallResult(success, returndata, errorMessage);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but performing a static call.
                                 *
                                 * _Available since v3.3._
                                 */
                                function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                                    return functionStaticCall(target, data, "Address: low-level static call failed");
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                                 * but performing a static call.
                                 *
                                 * _Available since v3.3._
                                 */
                                function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
                                    require(isContract(target), "Address: static call to non-contract");
                                    // solhint-disable-next-line avoid-low-level-calls
                                    (bool success, bytes memory returndata) = target.staticcall(data);
                                    return _verifyCallResult(success, returndata, errorMessage);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but performing a delegate call.
                                 *
                                 * _Available since v3.4._
                                 */
                                function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                                    return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                                 * but performing a delegate call.
                                 *
                                 * _Available since v3.4._
                                 */
                                function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                                    require(isContract(target), "Address: delegate call to non-contract");
                                    // solhint-disable-next-line avoid-low-level-calls
                                    (bool success, bytes memory returndata) = target.delegatecall(data);
                                    return _verifyCallResult(success, returndata, errorMessage);
                                }
                                function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                                    if (success) {
                                        return returndata;
                                    } else {
                                        // Look for revert reason and bubble it up if present
                                        if (returndata.length > 0) {
                                            // The easiest way to bubble the revert reason is using memory via assembly
                                            // solhint-disable-next-line no-inline-assembly
                                            assembly {
                                                let returndata_size := mload(returndata)
                                                revert(add(32, returndata), returndata_size)
                                            }
                                        } else {
                                            revert(errorMessage);
                                        }
                                    }
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            pragma solidity >=0.6.0 <0.8.0;
                            import "./IERC20.sol";
                            import "../../math/SafeMath.sol";
                            import "../../utils/Address.sol";
                            /**
                             * @title SafeERC20
                             * @dev Wrappers around ERC20 operations that throw on failure (when the token
                             * contract returns false). Tokens that return no value (and instead revert or
                             * throw on failure) are also supported, non-reverting calls are assumed to be
                             * successful.
                             * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
                             * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                             */
                            library SafeERC20 {
                                using SafeMath for uint256;
                                using Address for address;
                                function safeTransfer(IERC20 token, address to, uint256 value) internal {
                                    _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                                }
                                function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                                    _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                                }
                                /**
                                 * @dev Deprecated. This function has issues similar to the ones found in
                                 * {IERC20-approve}, and its usage is discouraged.
                                 *
                                 * Whenever possible, use {safeIncreaseAllowance} and
                                 * {safeDecreaseAllowance} instead.
                                 */
                                function safeApprove(IERC20 token, address spender, uint256 value) internal {
                                    // safeApprove should only be called when setting an initial allowance,
                                    // or when resetting it to zero. To increase and decrease it, use
                                    // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                                    // solhint-disable-next-line max-line-length
                                    require((value == 0) || (token.allowance(address(this), spender) == 0),
                                        "SafeERC20: approve from non-zero to non-zero allowance"
                                    );
                                    _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                                }
                                function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                    uint256 newAllowance = token.allowance(address(this), spender).add(value);
                                    _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                                }
                                function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                    uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                                    _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                                }
                                /**
                                 * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                                 * on the return value: the return value is optional (but if data is returned, it must not be false).
                                 * @param token The token targeted by the call.
                                 * @param data The call data (encoded using abi.encode or one of its variants).
                                 */
                                function _callOptionalReturn(IERC20 token, bytes memory data) private {
                                    // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                                    // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                                    // the target address contains contract code and also asserts for success in the low-level call.
                                    bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                                    if (returndata.length > 0) { // Return data is optional
                                        // solhint-disable-next-line max-line-length
                                        require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                                    }
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            pragma solidity >=0.6.0 <0.8.0;
                            /**
                             * @dev Interface of the ERC20 standard as defined in the EIP.
                             */
                            interface IERC20 {
                                /**
                                 * @dev Returns the amount of tokens in existence.
                                 */
                                function totalSupply() external view returns (uint256);
                                /**
                                 * @dev Returns the amount of tokens owned by `account`.
                                 */
                                function balanceOf(address account) external view returns (uint256);
                                /**
                                 * @dev Moves `amount` tokens from the caller's account to `recipient`.
                                 *
                                 * Returns a boolean value indicating whether the operation succeeded.
                                 *
                                 * Emits a {Transfer} event.
                                 */
                                function transfer(address recipient, uint256 amount) external returns (bool);
                                /**
                                 * @dev Returns the remaining number of tokens that `spender` will be
                                 * allowed to spend on behalf of `owner` through {transferFrom}. This is
                                 * zero by default.
                                 *
                                 * This value changes when {approve} or {transferFrom} are called.
                                 */
                                function allowance(address owner, address spender) external view returns (uint256);
                                /**
                                 * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                                 *
                                 * Returns a boolean value indicating whether the operation succeeded.
                                 *
                                 * IMPORTANT: Beware that changing an allowance with this method brings the risk
                                 * that someone may use both the old and the new allowance by unfortunate
                                 * transaction ordering. One possible solution to mitigate this race
                                 * condition is to first reduce the spender's allowance to 0 and set the
                                 * desired value afterwards:
                                 * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                 *
                                 * Emits an {Approval} event.
                                 */
                                function approve(address spender, uint256 amount) external returns (bool);
                                /**
                                 * @dev Moves `amount` tokens from `sender` to `recipient` using the
                                 * allowance mechanism. `amount` is then deducted from the caller's
                                 * allowance.
                                 *
                                 * Returns a boolean value indicating whether the operation succeeded.
                                 *
                                 * Emits a {Transfer} event.
                                 */
                                function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                                /**
                                 * @dev Emitted when `value` tokens are moved from one account (`from`) to
                                 * another (`to`).
                                 *
                                 * Note that `value` may be zero.
                                 */
                                event Transfer(address indexed from, address indexed to, uint256 value);
                                /**
                                 * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                                 * a call to {approve}. `value` is the new allowance.
                                 */
                                event Approval(address indexed owner, address indexed spender, uint256 value);
                            }
                            // SPDX-License-Identifier: MIT
                            pragma solidity >=0.6.0 <0.8.0;
                            /**
                             * @dev Wrappers over Solidity's arithmetic operations with added overflow
                             * checks.
                             *
                             * Arithmetic operations in Solidity wrap on overflow. This can easily result
                             * in bugs, because programmers usually assume that an overflow raises an
                             * error, which is the standard behavior in high level programming languages.
                             * `SafeMath` restores this intuition by reverting the transaction when an
                             * operation overflows.
                             *
                             * Using this library instead of the unchecked operations eliminates an entire
                             * class of bugs, so it's recommended to use it always.
                             */
                            library SafeMath {
                                /**
                                 * @dev Returns the addition of two unsigned integers, with an overflow flag.
                                 *
                                 * _Available since v3.4._
                                 */
                                function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                    uint256 c = a + b;
                                    if (c < a) return (false, 0);
                                    return (true, c);
                                }
                                /**
                                 * @dev Returns the substraction of two unsigned integers, with an overflow flag.
                                 *
                                 * _Available since v3.4._
                                 */
                                function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                    if (b > a) return (false, 0);
                                    return (true, a - b);
                                }
                                /**
                                 * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
                                 *
                                 * _Available since v3.4._
                                 */
                                function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                                    // benefit is lost if 'b' is also tested.
                                    // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                                    if (a == 0) return (true, 0);
                                    uint256 c = a * b;
                                    if (c / a != b) return (false, 0);
                                    return (true, c);
                                }
                                /**
                                 * @dev Returns the division of two unsigned integers, with a division by zero flag.
                                 *
                                 * _Available since v3.4._
                                 */
                                function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                    if (b == 0) return (false, 0);
                                    return (true, a / b);
                                }
                                /**
                                 * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
                                 *
                                 * _Available since v3.4._
                                 */
                                function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                    if (b == 0) return (false, 0);
                                    return (true, a % b);
                                }
                                /**
                                 * @dev Returns the addition of two unsigned integers, reverting on
                                 * overflow.
                                 *
                                 * Counterpart to Solidity's `+` operator.
                                 *
                                 * Requirements:
                                 *
                                 * - Addition cannot overflow.
                                 */
                                function add(uint256 a, uint256 b) internal pure returns (uint256) {
                                    uint256 c = a + b;
                                    require(c >= a, "SafeMath: addition overflow");
                                    return c;
                                }
                                /**
                                 * @dev Returns the subtraction of two unsigned integers, reverting on
                                 * overflow (when the result is negative).
                                 *
                                 * Counterpart to Solidity's `-` operator.
                                 *
                                 * Requirements:
                                 *
                                 * - Subtraction cannot overflow.
                                 */
                                function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                                    require(b <= a, "SafeMath: subtraction overflow");
                                    return a - b;
                                }
                                /**
                                 * @dev Returns the multiplication of two unsigned integers, reverting on
                                 * overflow.
                                 *
                                 * Counterpart to Solidity's `*` operator.
                                 *
                                 * Requirements:
                                 *
                                 * - Multiplication cannot overflow.
                                 */
                                function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                                    if (a == 0) return 0;
                                    uint256 c = a * b;
                                    require(c / a == b, "SafeMath: multiplication overflow");
                                    return c;
                                }
                                /**
                                 * @dev Returns the integer division of two unsigned integers, reverting on
                                 * division by zero. The result is rounded towards zero.
                                 *
                                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                                 * uses an invalid opcode to revert (consuming all remaining gas).
                                 *
                                 * Requirements:
                                 *
                                 * - The divisor cannot be zero.
                                 */
                                function div(uint256 a, uint256 b) internal pure returns (uint256) {
                                    require(b > 0, "SafeMath: division by zero");
                                    return a / b;
                                }
                                /**
                                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                                 * reverting when dividing by zero.
                                 *
                                 * Counterpart to Solidity's `%` operator. This function uses a `revert`
                                 * opcode (which leaves remaining gas untouched) while Solidity uses an
                                 * invalid opcode to revert (consuming all remaining gas).
                                 *
                                 * Requirements:
                                 *
                                 * - The divisor cannot be zero.
                                 */
                                function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                                    require(b > 0, "SafeMath: modulo by zero");
                                    return a % b;
                                }
                                /**
                                 * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                                 * overflow (when the result is negative).
                                 *
                                 * CAUTION: This function is deprecated because it requires allocating memory for the error
                                 * message unnecessarily. For custom revert reasons use {trySub}.
                                 *
                                 * Counterpart to Solidity's `-` operator.
                                 *
                                 * Requirements:
                                 *
                                 * - Subtraction cannot overflow.
                                 */
                                function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                    require(b <= a, errorMessage);
                                    return a - b;
                                }
                                /**
                                 * @dev Returns the integer division of two unsigned integers, reverting with custom message on
                                 * division by zero. The result is rounded towards zero.
                                 *
                                 * CAUTION: This function is deprecated because it requires allocating memory for the error
                                 * message unnecessarily. For custom revert reasons use {tryDiv}.
                                 *
                                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                                 * uses an invalid opcode to revert (consuming all remaining gas).
                                 *
                                 * Requirements:
                                 *
                                 * - The divisor cannot be zero.
                                 */
                                function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                    require(b > 0, errorMessage);
                                    return a / b;
                                }
                                /**
                                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                                 * reverting with custom message when dividing by zero.
                                 *
                                 * CAUTION: This function is deprecated because it requires allocating memory for the error
                                 * message unnecessarily. For custom revert reasons use {tryMod}.
                                 *
                                 * Counterpart to Solidity's `%` operator. This function uses a `revert`
                                 * opcode (which leaves remaining gas untouched) while Solidity uses an
                                 * invalid opcode to revert (consuming all remaining gas).
                                 *
                                 * Requirements:
                                 *
                                 * - The divisor cannot be zero.
                                 */
                                function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                    require(b > 0, errorMessage);
                                    return a % b;
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { FiatTokenV2 } from "./FiatTokenV2.sol";
                            // solhint-disable func-name-mixedcase
                            /**
                             * @title FiatToken V2.1
                             * @notice ERC20 Token backed by fiat reserves, version 2.1
                             */
                            contract FiatTokenV2_1 is FiatTokenV2 {
                                /**
                                 * @notice Initialize v2.1
                                 * @param lostAndFound  The address to which the locked funds are sent
                                 */
                                function initializeV2_1(address lostAndFound) external {
                                    // solhint-disable-next-line reason-string
                                    require(_initializedVersion == 1);
                                    uint256 lockedAmount = _balanceOf(address(this));
                                    if (lockedAmount > 0) {
                                        _transfer(address(this), lostAndFound, lockedAmount);
                                    }
                                    _blacklist(address(this));
                                    _initializedVersion = 2;
                                }
                                /**
                                 * @notice Version string for the EIP712 domain separator
                                 * @return Version string
                                 */
                                function version() external pure returns (string memory) {
                                    return "2";
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { FiatTokenV1_1 } from "../v1.1/FiatTokenV1_1.sol";
                            import { EIP712 } from "../util/EIP712.sol";
                            import { EIP3009 } from "./EIP3009.sol";
                            import { EIP2612 } from "./EIP2612.sol";
                            /**
                             * @title FiatToken V2
                             * @notice ERC20 Token backed by fiat reserves, version 2
                             */
                            contract FiatTokenV2 is FiatTokenV1_1, EIP3009, EIP2612 {
                                uint8 internal _initializedVersion;
                                /**
                                 * @notice Initialize v2
                                 * @param newName   New token name
                                 */
                                function initializeV2(string calldata newName) external {
                                    // solhint-disable-next-line reason-string
                                    require(initialized && _initializedVersion == 0);
                                    name = newName;
                                    _DEPRECATED_CACHED_DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(
                                        newName,
                                        "2"
                                    );
                                    _initializedVersion = 1;
                                }
                                /**
                                 * @notice Increase the allowance by a given increment
                                 * @param spender   Spender's address
                                 * @param increment Amount of increase in allowance
                                 * @return True if successful
                                 */
                                function increaseAllowance(address spender, uint256 increment)
                                    external
                                    virtual
                                    whenNotPaused
                                    notBlacklisted(msg.sender)
                                    notBlacklisted(spender)
                                    returns (bool)
                                {
                                    _increaseAllowance(msg.sender, spender, increment);
                                    return true;
                                }
                                /**
                                 * @notice Decrease the allowance by a given decrement
                                 * @param spender   Spender's address
                                 * @param decrement Amount of decrease in allowance
                                 * @return True if successful
                                 */
                                function decreaseAllowance(address spender, uint256 decrement)
                                    external
                                    virtual
                                    whenNotPaused
                                    notBlacklisted(msg.sender)
                                    notBlacklisted(spender)
                                    returns (bool)
                                {
                                    _decreaseAllowance(msg.sender, spender, decrement);
                                    return true;
                                }
                                /**
                                 * @notice Execute a transfer with a signed authorization
                                 * @param from          Payer's address (Authorizer)
                                 * @param to            Payee's address
                                 * @param value         Amount to be transferred
                                 * @param validAfter    The time after which this is valid (unix time)
                                 * @param validBefore   The time before which this is valid (unix time)
                                 * @param nonce         Unique nonce
                                 * @param v             v of the signature
                                 * @param r             r of the signature
                                 * @param s             s of the signature
                                 */
                                function transferWithAuthorization(
                                    address from,
                                    address to,
                                    uint256 value,
                                    uint256 validAfter,
                                    uint256 validBefore,
                                    bytes32 nonce,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                    _transferWithAuthorization(
                                        from,
                                        to,
                                        value,
                                        validAfter,
                                        validBefore,
                                        nonce,
                                        v,
                                        r,
                                        s
                                    );
                                }
                                /**
                                 * @notice Receive a transfer with a signed authorization from the payer
                                 * @dev This has an additional check to ensure that the payee's address
                                 * matches the caller of this function to prevent front-running attacks.
                                 * @param from          Payer's address (Authorizer)
                                 * @param to            Payee's address
                                 * @param value         Amount to be transferred
                                 * @param validAfter    The time after which this is valid (unix time)
                                 * @param validBefore   The time before which this is valid (unix time)
                                 * @param nonce         Unique nonce
                                 * @param v             v of the signature
                                 * @param r             r of the signature
                                 * @param s             s of the signature
                                 */
                                function receiveWithAuthorization(
                                    address from,
                                    address to,
                                    uint256 value,
                                    uint256 validAfter,
                                    uint256 validBefore,
                                    bytes32 nonce,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                    _receiveWithAuthorization(
                                        from,
                                        to,
                                        value,
                                        validAfter,
                                        validBefore,
                                        nonce,
                                        v,
                                        r,
                                        s
                                    );
                                }
                                /**
                                 * @notice Attempt to cancel an authorization
                                 * @dev Works only if the authorization is not yet used.
                                 * @param authorizer    Authorizer's address
                                 * @param nonce         Nonce of the authorization
                                 * @param v             v of the signature
                                 * @param r             r of the signature
                                 * @param s             s of the signature
                                 */
                                function cancelAuthorization(
                                    address authorizer,
                                    bytes32 nonce,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                ) external whenNotPaused {
                                    _cancelAuthorization(authorizer, nonce, v, r, s);
                                }
                                /**
                                 * @notice Update allowance with a signed permit
                                 * @param owner       Token owner's address (Authorizer)
                                 * @param spender     Spender's address
                                 * @param value       Amount of allowance
                                 * @param deadline    The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                                 * @param v           v of the signature
                                 * @param r           r of the signature
                                 * @param s           s of the signature
                                 */
                                function permit(
                                    address owner,
                                    address spender,
                                    uint256 value,
                                    uint256 deadline,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                )
                                    external
                                    virtual
                                    whenNotPaused
                                    notBlacklisted(owner)
                                    notBlacklisted(spender)
                                {
                                    _permit(owner, spender, value, deadline, v, r, s);
                                }
                                /**
                                 * @dev Internal function to increase the allowance by a given increment
                                 * @param owner     Token owner's address
                                 * @param spender   Spender's address
                                 * @param increment Amount of increase
                                 */
                                function _increaseAllowance(
                                    address owner,
                                    address spender,
                                    uint256 increment
                                ) internal override {
                                    _approve(owner, spender, allowed[owner][spender].add(increment));
                                }
                                /**
                                 * @dev Internal function to decrease the allowance by a given decrement
                                 * @param owner     Token owner's address
                                 * @param spender   Spender's address
                                 * @param decrement Amount of decrease
                                 */
                                function _decreaseAllowance(
                                    address owner,
                                    address spender,
                                    uint256 decrement
                                ) internal override {
                                    _approve(
                                        owner,
                                        spender,
                                        allowed[owner][spender].sub(
                                            decrement,
                                            "ERC20: decreased allowance below zero"
                                        )
                                    );
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            // solhint-disable func-name-mixedcase
                            /**
                             * @title EIP712 Domain
                             */
                            contract EIP712Domain {
                                // was originally DOMAIN_SEPARATOR
                                // but that has been moved to a method so we can override it in V2_2+
                                bytes32 internal _DEPRECATED_CACHED_DOMAIN_SEPARATOR;
                                /**
                                 * @notice Get the EIP712 Domain Separator.
                                 * @return The bytes32 EIP712 domain separator.
                                 */
                                function DOMAIN_SEPARATOR() external view returns (bytes32) {
                                    return _domainSeparator();
                                }
                                /**
                                 * @dev Internal method to get the EIP712 Domain Separator.
                                 * @return The bytes32 EIP712 domain separator.
                                 */
                                function _domainSeparator() internal virtual view returns (bytes32) {
                                    return _DEPRECATED_CACHED_DOMAIN_SEPARATOR;
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { AbstractFiatTokenV2 } from "./AbstractFiatTokenV2.sol";
                            import { EIP712Domain } from "./EIP712Domain.sol";
                            import { SignatureChecker } from "../util/SignatureChecker.sol";
                            import { MessageHashUtils } from "../util/MessageHashUtils.sol";
                            /**
                             * @title EIP-3009
                             * @notice Provide internal implementation for gas-abstracted transfers
                             * @dev Contracts that inherit from this must wrap these with publicly
                             * accessible functions, optionally adding modifiers where necessary
                             */
                            abstract contract EIP3009 is AbstractFiatTokenV2, EIP712Domain {
                                // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
                                bytes32
                                    public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;
                                // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
                                bytes32
                                    public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;
                                // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
                                bytes32
                                    public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;
                                /**
                                 * @dev authorizer address => nonce => bool (true if nonce is used)
                                 */
                                mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
                                event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
                                event AuthorizationCanceled(
                                    address indexed authorizer,
                                    bytes32 indexed nonce
                                );
                                /**
                                 * @notice Returns the state of an authorization
                                 * @dev Nonces are randomly generated 32-byte data unique to the
                                 * authorizer's address
                                 * @param authorizer    Authorizer's address
                                 * @param nonce         Nonce of the authorization
                                 * @return True if the nonce is used
                                 */
                                function authorizationState(address authorizer, bytes32 nonce)
                                    external
                                    view
                                    returns (bool)
                                {
                                    return _authorizationStates[authorizer][nonce];
                                }
                                /**
                                 * @notice Execute a transfer with a signed authorization
                                 * @param from          Payer's address (Authorizer)
                                 * @param to            Payee's address
                                 * @param value         Amount to be transferred
                                 * @param validAfter    The time after which this is valid (unix time)
                                 * @param validBefore   The time before which this is valid (unix time)
                                 * @param nonce         Unique nonce
                                 * @param v             v of the signature
                                 * @param r             r of the signature
                                 * @param s             s of the signature
                                 */
                                function _transferWithAuthorization(
                                    address from,
                                    address to,
                                    uint256 value,
                                    uint256 validAfter,
                                    uint256 validBefore,
                                    bytes32 nonce,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                ) internal {
                                    _transferWithAuthorization(
                                        from,
                                        to,
                                        value,
                                        validAfter,
                                        validBefore,
                                        nonce,
                                        abi.encodePacked(r, s, v)
                                    );
                                }
                                /**
                                 * @notice Execute a transfer with a signed authorization
                                 * @dev EOA wallet signatures should be packed in the order of r, s, v.
                                 * @param from          Payer's address (Authorizer)
                                 * @param to            Payee's address
                                 * @param value         Amount to be transferred
                                 * @param validAfter    The time after which this is valid (unix time)
                                 * @param validBefore   The time before which this is valid (unix time)
                                 * @param nonce         Unique nonce
                                 * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                                 */
                                function _transferWithAuthorization(
                                    address from,
                                    address to,
                                    uint256 value,
                                    uint256 validAfter,
                                    uint256 validBefore,
                                    bytes32 nonce,
                                    bytes memory signature
                                ) internal {
                                    _requireValidAuthorization(from, nonce, validAfter, validBefore);
                                    _requireValidSignature(
                                        from,
                                        keccak256(
                                            abi.encode(
                                                TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
                                                from,
                                                to,
                                                value,
                                                validAfter,
                                                validBefore,
                                                nonce
                                            )
                                        ),
                                        signature
                                    );
                                    _markAuthorizationAsUsed(from, nonce);
                                    _transfer(from, to, value);
                                }
                                /**
                                 * @notice Receive a transfer with a signed authorization from the payer
                                 * @dev This has an additional check to ensure that the payee's address
                                 * matches the caller of this function to prevent front-running attacks.
                                 * @param from          Payer's address (Authorizer)
                                 * @param to            Payee's address
                                 * @param value         Amount to be transferred
                                 * @param validAfter    The time after which this is valid (unix time)
                                 * @param validBefore   The time before which this is valid (unix time)
                                 * @param nonce         Unique nonce
                                 * @param v             v of the signature
                                 * @param r             r of the signature
                                 * @param s             s of the signature
                                 */
                                function _receiveWithAuthorization(
                                    address from,
                                    address to,
                                    uint256 value,
                                    uint256 validAfter,
                                    uint256 validBefore,
                                    bytes32 nonce,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                ) internal {
                                    _receiveWithAuthorization(
                                        from,
                                        to,
                                        value,
                                        validAfter,
                                        validBefore,
                                        nonce,
                                        abi.encodePacked(r, s, v)
                                    );
                                }
                                /**
                                 * @notice Receive a transfer with a signed authorization from the payer
                                 * @dev This has an additional check to ensure that the payee's address
                                 * matches the caller of this function to prevent front-running attacks.
                                 * EOA wallet signatures should be packed in the order of r, s, v.
                                 * @param from          Payer's address (Authorizer)
                                 * @param to            Payee's address
                                 * @param value         Amount to be transferred
                                 * @param validAfter    The time after which this is valid (unix time)
                                 * @param validBefore   The time before which this is valid (unix time)
                                 * @param nonce         Unique nonce
                                 * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                                 */
                                function _receiveWithAuthorization(
                                    address from,
                                    address to,
                                    uint256 value,
                                    uint256 validAfter,
                                    uint256 validBefore,
                                    bytes32 nonce,
                                    bytes memory signature
                                ) internal {
                                    require(to == msg.sender, "FiatTokenV2: caller must be the payee");
                                    _requireValidAuthorization(from, nonce, validAfter, validBefore);
                                    _requireValidSignature(
                                        from,
                                        keccak256(
                                            abi.encode(
                                                RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
                                                from,
                                                to,
                                                value,
                                                validAfter,
                                                validBefore,
                                                nonce
                                            )
                                        ),
                                        signature
                                    );
                                    _markAuthorizationAsUsed(from, nonce);
                                    _transfer(from, to, value);
                                }
                                /**
                                 * @notice Attempt to cancel an authorization
                                 * @param authorizer    Authorizer's address
                                 * @param nonce         Nonce of the authorization
                                 * @param v             v of the signature
                                 * @param r             r of the signature
                                 * @param s             s of the signature
                                 */
                                function _cancelAuthorization(
                                    address authorizer,
                                    bytes32 nonce,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                ) internal {
                                    _cancelAuthorization(authorizer, nonce, abi.encodePacked(r, s, v));
                                }
                                /**
                                 * @notice Attempt to cancel an authorization
                                 * @dev EOA wallet signatures should be packed in the order of r, s, v.
                                 * @param authorizer    Authorizer's address
                                 * @param nonce         Nonce of the authorization
                                 * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                                 */
                                function _cancelAuthorization(
                                    address authorizer,
                                    bytes32 nonce,
                                    bytes memory signature
                                ) internal {
                                    _requireUnusedAuthorization(authorizer, nonce);
                                    _requireValidSignature(
                                        authorizer,
                                        keccak256(
                                            abi.encode(CANCEL_AUTHORIZATION_TYPEHASH, authorizer, nonce)
                                        ),
                                        signature
                                    );
                                    _authorizationStates[authorizer][nonce] = true;
                                    emit AuthorizationCanceled(authorizer, nonce);
                                }
                                /**
                                 * @notice Validates that signature against input data struct
                                 * @param signer        Signer's address
                                 * @param dataHash      Hash of encoded data struct
                                 * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                                 */
                                function _requireValidSignature(
                                    address signer,
                                    bytes32 dataHash,
                                    bytes memory signature
                                ) private view {
                                    require(
                                        SignatureChecker.isValidSignatureNow(
                                            signer,
                                            MessageHashUtils.toTypedDataHash(_domainSeparator(), dataHash),
                                            signature
                                        ),
                                        "FiatTokenV2: invalid signature"
                                    );
                                }
                                /**
                                 * @notice Check that an authorization is unused
                                 * @param authorizer    Authorizer's address
                                 * @param nonce         Nonce of the authorization
                                 */
                                function _requireUnusedAuthorization(address authorizer, bytes32 nonce)
                                    private
                                    view
                                {
                                    require(
                                        !_authorizationStates[authorizer][nonce],
                                        "FiatTokenV2: authorization is used or canceled"
                                    );
                                }
                                /**
                                 * @notice Check that authorization is valid
                                 * @param authorizer    Authorizer's address
                                 * @param nonce         Nonce of the authorization
                                 * @param validAfter    The time after which this is valid (unix time)
                                 * @param validBefore   The time before which this is valid (unix time)
                                 */
                                function _requireValidAuthorization(
                                    address authorizer,
                                    bytes32 nonce,
                                    uint256 validAfter,
                                    uint256 validBefore
                                ) private view {
                                    require(
                                        now > validAfter,
                                        "FiatTokenV2: authorization is not yet valid"
                                    );
                                    require(now < validBefore, "FiatTokenV2: authorization is expired");
                                    _requireUnusedAuthorization(authorizer, nonce);
                                }
                                /**
                                 * @notice Mark an authorization as used
                                 * @param authorizer    Authorizer's address
                                 * @param nonce         Nonce of the authorization
                                 */
                                function _markAuthorizationAsUsed(address authorizer, bytes32 nonce)
                                    private
                                {
                                    _authorizationStates[authorizer][nonce] = true;
                                    emit AuthorizationUsed(authorizer, nonce);
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { AbstractFiatTokenV2 } from "./AbstractFiatTokenV2.sol";
                            import { EIP712Domain } from "./EIP712Domain.sol";
                            import { MessageHashUtils } from "../util/MessageHashUtils.sol";
                            import { SignatureChecker } from "../util/SignatureChecker.sol";
                            /**
                             * @title EIP-2612
                             * @notice Provide internal implementation for gas-abstracted approvals
                             */
                            abstract contract EIP2612 is AbstractFiatTokenV2, EIP712Domain {
                                // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
                                bytes32
                                    public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
                                mapping(address => uint256) private _permitNonces;
                                /**
                                 * @notice Nonces for permit
                                 * @param owner Token owner's address (Authorizer)
                                 * @return Next nonce
                                 */
                                function nonces(address owner) external view returns (uint256) {
                                    return _permitNonces[owner];
                                }
                                /**
                                 * @notice Verify a signed approval permit and execute if valid
                                 * @param owner     Token owner's address (Authorizer)
                                 * @param spender   Spender's address
                                 * @param value     Amount of allowance
                                 * @param deadline  The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                                 * @param v         v of the signature
                                 * @param r         r of the signature
                                 * @param s         s of the signature
                                 */
                                function _permit(
                                    address owner,
                                    address spender,
                                    uint256 value,
                                    uint256 deadline,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                ) internal {
                                    _permit(owner, spender, value, deadline, abi.encodePacked(r, s, v));
                                }
                                /**
                                 * @notice Verify a signed approval permit and execute if valid
                                 * @dev EOA wallet signatures should be packed in the order of r, s, v.
                                 * @param owner      Token owner's address (Authorizer)
                                 * @param spender    Spender's address
                                 * @param value      Amount of allowance
                                 * @param deadline   The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                                 * @param signature  Signature byte array signed by an EOA wallet or a contract wallet
                                 */
                                function _permit(
                                    address owner,
                                    address spender,
                                    uint256 value,
                                    uint256 deadline,
                                    bytes memory signature
                                ) internal {
                                    require(
                                        deadline == type(uint256).max || deadline >= now,
                                        "FiatTokenV2: permit is expired"
                                    );
                                    bytes32 typedDataHash = MessageHashUtils.toTypedDataHash(
                                        _domainSeparator(),
                                        keccak256(
                                            abi.encode(
                                                PERMIT_TYPEHASH,
                                                owner,
                                                spender,
                                                value,
                                                _permitNonces[owner]++,
                                                deadline
                                            )
                                        )
                                    );
                                    require(
                                        SignatureChecker.isValidSignatureNow(
                                            owner,
                                            typedDataHash,
                                            signature
                                        ),
                                        "EIP2612: invalid signature"
                                    );
                                    _approve(owner, spender, value);
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { AbstractFiatTokenV1 } from "../v1/AbstractFiatTokenV1.sol";
                            abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 {
                                function _increaseAllowance(
                                    address owner,
                                    address spender,
                                    uint256 increment
                                ) internal virtual;
                                function _decreaseAllowance(
                                    address owner,
                                    address spender,
                                    uint256 decrement
                                ) internal virtual;
                            }
                            /**
                             * SPDX-License-Identifier: MIT
                             *
                             * Copyright (c) 2016 Smart Contract Solutions, Inc.
                             * Copyright (c) 2018-2020 CENTRE SECZ
                             *
                             * Permission is hereby granted, free of charge, to any person obtaining a copy
                             * of this software and associated documentation files (the "Software"), to deal
                             * in the Software without restriction, including without limitation the rights
                             * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
                             * copies of the Software, and to permit persons to whom the Software is
                             * furnished to do so, subject to the following conditions:
                             *
                             * The above copyright notice and this permission notice shall be included in
                             * copies or substantial portions of the Software.
                             *
                             * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
                             * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
                             * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
                             * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
                             * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
                             * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
                             * SOFTWARE.
                             */
                            pragma solidity 0.6.12;
                            import { Ownable } from "./Ownable.sol";
                            /**
                             * @notice Base contract which allows children to implement an emergency stop
                             * mechanism
                             * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/feb665136c0dae9912e08397c1a21c4af3651ef3/contracts/lifecycle/Pausable.sol
                             * Modifications:
                             * 1. Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018)
                             * 2. Removed whenNotPause/whenPaused from pause/unpause (6/14/2018)
                             * 3. Removed whenPaused (6/14/2018)
                             * 4. Switches ownable library to use ZeppelinOS (7/12/18)
                             * 5. Remove constructor (7/13/18)
                             * 6. Reformat, conform to Solidity 0.6 syntax and add error messages (5/13/20)
                             * 7. Make public functions external (5/27/20)
                             */
                            contract Pausable is Ownable {
                                event Pause();
                                event Unpause();
                                event PauserChanged(address indexed newAddress);
                                address public pauser;
                                bool public paused = false;
                                /**
                                 * @dev Modifier to make a function callable only when the contract is not paused.
                                 */
                                modifier whenNotPaused() {
                                    require(!paused, "Pausable: paused");
                                    _;
                                }
                                /**
                                 * @dev throws if called by any account other than the pauser
                                 */
                                modifier onlyPauser() {
                                    require(msg.sender == pauser, "Pausable: caller is not the pauser");
                                    _;
                                }
                                /**
                                 * @dev called by the owner to pause, triggers stopped state
                                 */
                                function pause() external onlyPauser {
                                    paused = true;
                                    emit Pause();
                                }
                                /**
                                 * @dev called by the owner to unpause, returns to normal state
                                 */
                                function unpause() external onlyPauser {
                                    paused = false;
                                    emit Unpause();
                                }
                                /**
                                 * @notice Updates the pauser address.
                                 * @param _newPauser The address of the new pauser.
                                 */
                                function updatePauser(address _newPauser) external onlyOwner {
                                    require(
                                        _newPauser != address(0),
                                        "Pausable: new pauser is the zero address"
                                    );
                                    pauser = _newPauser;
                                    emit PauserChanged(pauser);
                                }
                            }
                            /**
                             * SPDX-License-Identifier: MIT
                             *
                             * Copyright (c) 2018 zOS Global Limited.
                             * Copyright (c) 2018-2020 CENTRE SECZ
                             *
                             * Permission is hereby granted, free of charge, to any person obtaining a copy
                             * of this software and associated documentation files (the "Software"), to deal
                             * in the Software without restriction, including without limitation the rights
                             * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
                             * copies of the Software, and to permit persons to whom the Software is
                             * furnished to do so, subject to the following conditions:
                             *
                             * The above copyright notice and this permission notice shall be included in
                             * copies or substantial portions of the Software.
                             *
                             * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
                             * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
                             * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
                             * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
                             * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
                             * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
                             * SOFTWARE.
                             */
                            pragma solidity 0.6.12;
                            /**
                             * @notice The Ownable contract has an owner address, and provides basic
                             * authorization control functions
                             * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-labs/blob/3887ab77b8adafba4a26ace002f3a684c1a3388b/upgradeability_ownership/contracts/ownership/Ownable.sol
                             * Modifications:
                             * 1. Consolidate OwnableStorage into this contract (7/13/18)
                             * 2. Reformat, conform to Solidity 0.6 syntax, and add error messages (5/13/20)
                             * 3. Make public functions external (5/27/20)
                             */
                            contract Ownable {
                                // Owner of the contract
                                address private _owner;
                                /**
                                 * @dev Event to show ownership has been transferred
                                 * @param previousOwner representing the address of the previous owner
                                 * @param newOwner representing the address of the new owner
                                 */
                                event OwnershipTransferred(address previousOwner, address newOwner);
                                /**
                                 * @dev The constructor sets the original owner of the contract to the sender account.
                                 */
                                constructor() public {
                                    setOwner(msg.sender);
                                }
                                /**
                                 * @dev Tells the address of the owner
                                 * @return the address of the owner
                                 */
                                function owner() external view returns (address) {
                                    return _owner;
                                }
                                /**
                                 * @dev Sets a new owner address
                                 */
                                function setOwner(address newOwner) internal {
                                    _owner = newOwner;
                                }
                                /**
                                 * @dev Throws if called by any account other than the owner.
                                 */
                                modifier onlyOwner() {
                                    require(msg.sender == _owner, "Ownable: caller is not the owner");
                                    _;
                                }
                                /**
                                 * @dev Allows the current owner to transfer control of the contract to a newOwner.
                                 * @param newOwner The address to transfer ownership to.
                                 */
                                function transferOwnership(address newOwner) external onlyOwner {
                                    require(
                                        newOwner != address(0),
                                        "Ownable: new owner is the zero address"
                                    );
                                    emit OwnershipTransferred(_owner, newOwner);
                                    setOwner(newOwner);
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
                            import { AbstractFiatTokenV1 } from "./AbstractFiatTokenV1.sol";
                            import { Ownable } from "./Ownable.sol";
                            import { Pausable } from "./Pausable.sol";
                            import { Blacklistable } from "./Blacklistable.sol";
                            /**
                             * @title FiatToken
                             * @dev ERC20 Token backed by fiat reserves
                             */
                            contract FiatTokenV1 is AbstractFiatTokenV1, Ownable, Pausable, Blacklistable {
                                using SafeMath for uint256;
                                string public name;
                                string public symbol;
                                uint8 public decimals;
                                string public currency;
                                address public masterMinter;
                                bool internal initialized;
                                /// @dev A mapping that stores the balance and blacklist states for a given address.
                                /// The first bit defines whether the address is blacklisted (1 if blacklisted, 0 otherwise).
                                /// The last 255 bits define the balance for the address.
                                mapping(address => uint256) internal balanceAndBlacklistStates;
                                mapping(address => mapping(address => uint256)) internal allowed;
                                uint256 internal totalSupply_ = 0;
                                mapping(address => bool) internal minters;
                                mapping(address => uint256) internal minterAllowed;
                                event Mint(address indexed minter, address indexed to, uint256 amount);
                                event Burn(address indexed burner, uint256 amount);
                                event MinterConfigured(address indexed minter, uint256 minterAllowedAmount);
                                event MinterRemoved(address indexed oldMinter);
                                event MasterMinterChanged(address indexed newMasterMinter);
                                /**
                                 * @notice Initializes the fiat token contract.
                                 * @param tokenName       The name of the fiat token.
                                 * @param tokenSymbol     The symbol of the fiat token.
                                 * @param tokenCurrency   The fiat currency that the token represents.
                                 * @param tokenDecimals   The number of decimals that the token uses.
                                 * @param newMasterMinter The masterMinter address for the fiat token.
                                 * @param newPauser       The pauser address for the fiat token.
                                 * @param newBlacklister  The blacklister address for the fiat token.
                                 * @param newOwner        The owner of the fiat token.
                                 */
                                function initialize(
                                    string memory tokenName,
                                    string memory tokenSymbol,
                                    string memory tokenCurrency,
                                    uint8 tokenDecimals,
                                    address newMasterMinter,
                                    address newPauser,
                                    address newBlacklister,
                                    address newOwner
                                ) public {
                                    require(!initialized, "FiatToken: contract is already initialized");
                                    require(
                                        newMasterMinter != address(0),
                                        "FiatToken: new masterMinter is the zero address"
                                    );
                                    require(
                                        newPauser != address(0),
                                        "FiatToken: new pauser is the zero address"
                                    );
                                    require(
                                        newBlacklister != address(0),
                                        "FiatToken: new blacklister is the zero address"
                                    );
                                    require(
                                        newOwner != address(0),
                                        "FiatToken: new owner is the zero address"
                                    );
                                    name = tokenName;
                                    symbol = tokenSymbol;
                                    currency = tokenCurrency;
                                    decimals = tokenDecimals;
                                    masterMinter = newMasterMinter;
                                    pauser = newPauser;
                                    blacklister = newBlacklister;
                                    setOwner(newOwner);
                                    initialized = true;
                                }
                                /**
                                 * @dev Throws if called by any account other than a minter.
                                 */
                                modifier onlyMinters() {
                                    require(minters[msg.sender], "FiatToken: caller is not a minter");
                                    _;
                                }
                                /**
                                 * @notice Mints fiat tokens to an address.
                                 * @param _to The address that will receive the minted tokens.
                                 * @param _amount The amount of tokens to mint. Must be less than or equal
                                 * to the minterAllowance of the caller.
                                 * @return True if the operation was successful.
                                 */
                                function mint(address _to, uint256 _amount)
                                    external
                                    whenNotPaused
                                    onlyMinters
                                    notBlacklisted(msg.sender)
                                    notBlacklisted(_to)
                                    returns (bool)
                                {
                                    require(_to != address(0), "FiatToken: mint to the zero address");
                                    require(_amount > 0, "FiatToken: mint amount not greater than 0");
                                    uint256 mintingAllowedAmount = minterAllowed[msg.sender];
                                    require(
                                        _amount <= mintingAllowedAmount,
                                        "FiatToken: mint amount exceeds minterAllowance"
                                    );
                                    totalSupply_ = totalSupply_.add(_amount);
                                    _setBalance(_to, _balanceOf(_to).add(_amount));
                                    minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount);
                                    emit Mint(msg.sender, _to, _amount);
                                    emit Transfer(address(0), _to, _amount);
                                    return true;
                                }
                                /**
                                 * @dev Throws if called by any account other than the masterMinter
                                 */
                                modifier onlyMasterMinter() {
                                    require(
                                        msg.sender == masterMinter,
                                        "FiatToken: caller is not the masterMinter"
                                    );
                                    _;
                                }
                                /**
                                 * @notice Gets the minter allowance for an account.
                                 * @param minter The address to check.
                                 * @return The remaining minter allowance for the account.
                                 */
                                function minterAllowance(address minter) external view returns (uint256) {
                                    return minterAllowed[minter];
                                }
                                /**
                                 * @notice Checks if an account is a minter.
                                 * @param account The address to check.
                                 * @return True if the account is a minter, false if the account is not a minter.
                                 */
                                function isMinter(address account) external view returns (bool) {
                                    return minters[account];
                                }
                                /**
                                 * @notice Gets the remaining amount of fiat tokens a spender is allowed to transfer on
                                 * behalf of the token owner.
                                 * @param owner   The token owner's address.
                                 * @param spender The spender's address.
                                 * @return The remaining allowance.
                                 */
                                function allowance(address owner, address spender)
                                    external
                                    override
                                    view
                                    returns (uint256)
                                {
                                    return allowed[owner][spender];
                                }
                                /**
                                 * @notice Gets the totalSupply of the fiat token.
                                 * @return The totalSupply of the fiat token.
                                 */
                                function totalSupply() external override view returns (uint256) {
                                    return totalSupply_;
                                }
                                /**
                                 * @notice Gets the fiat token balance of an account.
                                 * @param account  The address to check.
                                 * @return balance The fiat token balance of the account.
                                 */
                                function balanceOf(address account)
                                    external
                                    override
                                    view
                                    returns (uint256)
                                {
                                    return _balanceOf(account);
                                }
                                /**
                                 * @notice Sets a fiat token allowance for a spender to spend on behalf of the caller.
                                 * @param spender The spender's address.
                                 * @param value   The allowance amount.
                                 * @return True if the operation was successful.
                                 */
                                function approve(address spender, uint256 value)
                                    external
                                    virtual
                                    override
                                    whenNotPaused
                                    notBlacklisted(msg.sender)
                                    notBlacklisted(spender)
                                    returns (bool)
                                {
                                    _approve(msg.sender, spender, value);
                                    return true;
                                }
                                /**
                                 * @dev Internal function to set allowance.
                                 * @param owner     Token owner's address.
                                 * @param spender   Spender's address.
                                 * @param value     Allowance amount.
                                 */
                                function _approve(
                                    address owner,
                                    address spender,
                                    uint256 value
                                ) internal override {
                                    require(owner != address(0), "ERC20: approve from the zero address");
                                    require(spender != address(0), "ERC20: approve to the zero address");
                                    allowed[owner][spender] = value;
                                    emit Approval(owner, spender, value);
                                }
                                /**
                                 * @notice Transfers tokens from an address to another by spending the caller's allowance.
                                 * @dev The caller must have some fiat token allowance on the payer's tokens.
                                 * @param from  Payer's address.
                                 * @param to    Payee's address.
                                 * @param value Transfer amount.
                                 * @return True if the operation was successful.
                                 */
                                function transferFrom(
                                    address from,
                                    address to,
                                    uint256 value
                                )
                                    external
                                    override
                                    whenNotPaused
                                    notBlacklisted(msg.sender)
                                    notBlacklisted(from)
                                    notBlacklisted(to)
                                    returns (bool)
                                {
                                    require(
                                        value <= allowed[from][msg.sender],
                                        "ERC20: transfer amount exceeds allowance"
                                    );
                                    _transfer(from, to, value);
                                    allowed[from][msg.sender] = allowed[from][msg.sender].sub(value);
                                    return true;
                                }
                                /**
                                 * @notice Transfers tokens from the caller.
                                 * @param to    Payee's address.
                                 * @param value Transfer amount.
                                 * @return True if the operation was successful.
                                 */
                                function transfer(address to, uint256 value)
                                    external
                                    override
                                    whenNotPaused
                                    notBlacklisted(msg.sender)
                                    notBlacklisted(to)
                                    returns (bool)
                                {
                                    _transfer(msg.sender, to, value);
                                    return true;
                                }
                                /**
                                 * @dev Internal function to process transfers.
                                 * @param from  Payer's address.
                                 * @param to    Payee's address.
                                 * @param value Transfer amount.
                                 */
                                function _transfer(
                                    address from,
                                    address to,
                                    uint256 value
                                ) internal override {
                                    require(from != address(0), "ERC20: transfer from the zero address");
                                    require(to != address(0), "ERC20: transfer to the zero address");
                                    require(
                                        value <= _balanceOf(from),
                                        "ERC20: transfer amount exceeds balance"
                                    );
                                    _setBalance(from, _balanceOf(from).sub(value));
                                    _setBalance(to, _balanceOf(to).add(value));
                                    emit Transfer(from, to, value);
                                }
                                /**
                                 * @notice Adds or updates a new minter with a mint allowance.
                                 * @param minter The address of the minter.
                                 * @param minterAllowedAmount The minting amount allowed for the minter.
                                 * @return True if the operation was successful.
                                 */
                                function configureMinter(address minter, uint256 minterAllowedAmount)
                                    external
                                    whenNotPaused
                                    onlyMasterMinter
                                    returns (bool)
                                {
                                    minters[minter] = true;
                                    minterAllowed[minter] = minterAllowedAmount;
                                    emit MinterConfigured(minter, minterAllowedAmount);
                                    return true;
                                }
                                /**
                                 * @notice Removes a minter.
                                 * @param minter The address of the minter to remove.
                                 * @return True if the operation was successful.
                                 */
                                function removeMinter(address minter)
                                    external
                                    onlyMasterMinter
                                    returns (bool)
                                {
                                    minters[minter] = false;
                                    minterAllowed[minter] = 0;
                                    emit MinterRemoved(minter);
                                    return true;
                                }
                                /**
                                 * @notice Allows a minter to burn some of its own tokens.
                                 * @dev The caller must be a minter, must not be blacklisted, and the amount to burn
                                 * should be less than or equal to the account's balance.
                                 * @param _amount the amount of tokens to be burned.
                                 */
                                function burn(uint256 _amount)
                                    external
                                    whenNotPaused
                                    onlyMinters
                                    notBlacklisted(msg.sender)
                                {
                                    uint256 balance = _balanceOf(msg.sender);
                                    require(_amount > 0, "FiatToken: burn amount not greater than 0");
                                    require(balance >= _amount, "FiatToken: burn amount exceeds balance");
                                    totalSupply_ = totalSupply_.sub(_amount);
                                    _setBalance(msg.sender, balance.sub(_amount));
                                    emit Burn(msg.sender, _amount);
                                    emit Transfer(msg.sender, address(0), _amount);
                                }
                                /**
                                 * @notice Updates the master minter address.
                                 * @param _newMasterMinter The address of the new master minter.
                                 */
                                function updateMasterMinter(address _newMasterMinter) external onlyOwner {
                                    require(
                                        _newMasterMinter != address(0),
                                        "FiatToken: new masterMinter is the zero address"
                                    );
                                    masterMinter = _newMasterMinter;
                                    emit MasterMinterChanged(masterMinter);
                                }
                                /**
                                 * @inheritdoc Blacklistable
                                 */
                                function _blacklist(address _account) internal override {
                                    _setBlacklistState(_account, true);
                                }
                                /**
                                 * @inheritdoc Blacklistable
                                 */
                                function _unBlacklist(address _account) internal override {
                                    _setBlacklistState(_account, false);
                                }
                                /**
                                 * @dev Helper method that sets the blacklist state of an account.
                                 * @param _account         The address of the account.
                                 * @param _shouldBlacklist True if the account should be blacklisted, false if the account should be unblacklisted.
                                 */
                                function _setBlacklistState(address _account, bool _shouldBlacklist)
                                    internal
                                    virtual
                                {
                                    _deprecatedBlacklisted[_account] = _shouldBlacklist;
                                }
                                /**
                                 * @dev Helper method that sets the balance of an account.
                                 * @param _account The address of the account.
                                 * @param _balance The new fiat token balance of the account.
                                 */
                                function _setBalance(address _account, uint256 _balance) internal virtual {
                                    balanceAndBlacklistStates[_account] = _balance;
                                }
                                /**
                                 * @inheritdoc Blacklistable
                                 */
                                function _isBlacklisted(address _account)
                                    internal
                                    virtual
                                    override
                                    view
                                    returns (bool)
                                {
                                    return _deprecatedBlacklisted[_account];
                                }
                                /**
                                 * @dev Helper method to obtain the balance of an account.
                                 * @param _account  The address of the account.
                                 * @return          The fiat token balance of the account.
                                 */
                                function _balanceOf(address _account)
                                    internal
                                    virtual
                                    view
                                    returns (uint256)
                                {
                                    return balanceAndBlacklistStates[_account];
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { Ownable } from "./Ownable.sol";
                            /**
                             * @title Blacklistable Token
                             * @dev Allows accounts to be blacklisted by a "blacklister" role
                             */
                            abstract contract Blacklistable is Ownable {
                                address public blacklister;
                                mapping(address => bool) internal _deprecatedBlacklisted;
                                event Blacklisted(address indexed _account);
                                event UnBlacklisted(address indexed _account);
                                event BlacklisterChanged(address indexed newBlacklister);
                                /**
                                 * @dev Throws if called by any account other than the blacklister.
                                 */
                                modifier onlyBlacklister() {
                                    require(
                                        msg.sender == blacklister,
                                        "Blacklistable: caller is not the blacklister"
                                    );
                                    _;
                                }
                                /**
                                 * @dev Throws if argument account is blacklisted.
                                 * @param _account The address to check.
                                 */
                                modifier notBlacklisted(address _account) {
                                    require(
                                        !_isBlacklisted(_account),
                                        "Blacklistable: account is blacklisted"
                                    );
                                    _;
                                }
                                /**
                                 * @notice Checks if account is blacklisted.
                                 * @param _account The address to check.
                                 * @return True if the account is blacklisted, false if the account is not blacklisted.
                                 */
                                function isBlacklisted(address _account) external view returns (bool) {
                                    return _isBlacklisted(_account);
                                }
                                /**
                                 * @notice Adds account to blacklist.
                                 * @param _account The address to blacklist.
                                 */
                                function blacklist(address _account) external onlyBlacklister {
                                    _blacklist(_account);
                                    emit Blacklisted(_account);
                                }
                                /**
                                 * @notice Removes account from blacklist.
                                 * @param _account The address to remove from the blacklist.
                                 */
                                function unBlacklist(address _account) external onlyBlacklister {
                                    _unBlacklist(_account);
                                    emit UnBlacklisted(_account);
                                }
                                /**
                                 * @notice Updates the blacklister address.
                                 * @param _newBlacklister The address of the new blacklister.
                                 */
                                function updateBlacklister(address _newBlacklister) external onlyOwner {
                                    require(
                                        _newBlacklister != address(0),
                                        "Blacklistable: new blacklister is the zero address"
                                    );
                                    blacklister = _newBlacklister;
                                    emit BlacklisterChanged(blacklister);
                                }
                                /**
                                 * @dev Checks if account is blacklisted.
                                 * @param _account The address to check.
                                 * @return true if the account is blacklisted, false otherwise.
                                 */
                                function _isBlacklisted(address _account)
                                    internal
                                    virtual
                                    view
                                    returns (bool);
                                /**
                                 * @dev Helper method that blacklists an account.
                                 * @param _account The address to blacklist.
                                 */
                                function _blacklist(address _account) internal virtual;
                                /**
                                 * @dev Helper method that unblacklists an account.
                                 * @param _account The address to unblacklist.
                                 */
                                function _unBlacklist(address _account) internal virtual;
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                            abstract contract AbstractFiatTokenV1 is IERC20 {
                                function _approve(
                                    address owner,
                                    address spender,
                                    uint256 value
                                ) internal virtual;
                                function _transfer(
                                    address from,
                                    address to,
                                    uint256 value
                                ) internal virtual;
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { Ownable } from "../v1/Ownable.sol";
                            import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                            import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
                            contract Rescuable is Ownable {
                                using SafeERC20 for IERC20;
                                address private _rescuer;
                                event RescuerChanged(address indexed newRescuer);
                                /**
                                 * @notice Returns current rescuer
                                 * @return Rescuer's address
                                 */
                                function rescuer() external view returns (address) {
                                    return _rescuer;
                                }
                                /**
                                 * @notice Revert if called by any account other than the rescuer.
                                 */
                                modifier onlyRescuer() {
                                    require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer");
                                    _;
                                }
                                /**
                                 * @notice Rescue ERC20 tokens locked up in this contract.
                                 * @param tokenContract ERC20 token contract address
                                 * @param to        Recipient address
                                 * @param amount    Amount to withdraw
                                 */
                                function rescueERC20(
                                    IERC20 tokenContract,
                                    address to,
                                    uint256 amount
                                ) external onlyRescuer {
                                    tokenContract.safeTransfer(to, amount);
                                }
                                /**
                                 * @notice Updates the rescuer address.
                                 * @param newRescuer The address of the new rescuer.
                                 */
                                function updateRescuer(address newRescuer) external onlyOwner {
                                    require(
                                        newRescuer != address(0),
                                        "Rescuable: new rescuer is the zero address"
                                    );
                                    _rescuer = newRescuer;
                                    emit RescuerChanged(newRescuer);
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { FiatTokenV1 } from "../v1/FiatTokenV1.sol";
                            import { Rescuable } from "./Rescuable.sol";
                            /**
                             * @title FiatTokenV1_1
                             * @dev ERC20 Token backed by fiat reserves
                             */
                            contract FiatTokenV1_1 is FiatTokenV1, Rescuable {
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            import { ECRecover } from "./ECRecover.sol";
                            import { IERC1271 } from "../interface/IERC1271.sol";
                            /**
                             * @dev Signature verification helper that can be used instead of `ECRecover.recover` to seamlessly support both ECDSA
                             * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets.
                             *
                             * Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/21bb89ef5bfc789b9333eb05e3ba2b7b284ac77c/contracts/utils/cryptography/SignatureChecker.sol
                             */
                            library SignatureChecker {
                                /**
                                 * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the
                                 * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECRecover.recover`.
                                 * @param signer        Address of the claimed signer
                                 * @param digest        Keccak-256 hash digest of the signed message
                                 * @param signature     Signature byte array associated with hash
                                 */
                                function isValidSignatureNow(
                                    address signer,
                                    bytes32 digest,
                                    bytes memory signature
                                ) external view returns (bool) {
                                    if (!isContract(signer)) {
                                        return ECRecover.recover(digest, signature) == signer;
                                    }
                                    return isValidERC1271SignatureNow(signer, digest, signature);
                                }
                                /**
                                 * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
                                 * against the signer smart contract using ERC1271.
                                 * @param signer        Address of the claimed signer
                                 * @param digest        Keccak-256 hash digest of the signed message
                                 * @param signature     Signature byte array associated with hash
                                 *
                                 * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
                                 * change through time. It could return true at block N and false at block N+1 (or the opposite).
                                 */
                                function isValidERC1271SignatureNow(
                                    address signer,
                                    bytes32 digest,
                                    bytes memory signature
                                ) internal view returns (bool) {
                                    (bool success, bytes memory result) = signer.staticcall(
                                        abi.encodeWithSelector(
                                            IERC1271.isValidSignature.selector,
                                            digest,
                                            signature
                                        )
                                    );
                                    return (success &&
                                        result.length >= 32 &&
                                        abi.decode(result, (bytes32)) ==
                                        bytes32(IERC1271.isValidSignature.selector));
                                }
                                /**
                                 * @dev Checks if the input address is a smart contract.
                                 */
                                function isContract(address addr) internal view returns (bool) {
                                    uint256 size;
                                    assembly {
                                        size := extcodesize(addr)
                                    }
                                    return size > 0;
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            /**
                             * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
                             *
                             * The library provides methods for generating a hash of a message that conforms to the
                             * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
                             * specifications.
                             */
                            library MessageHashUtils {
                                /**
                                 * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
                                 * Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/21bb89ef5bfc789b9333eb05e3ba2b7b284ac77c/contracts/utils/cryptography/MessageHashUtils.sol
                                 *
                                 * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
                                 * `\\x19\\x01` and hashing the result. It corresponds to the hash signed by the
                                 * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
                                 *
                                 * @param domainSeparator    Domain separator
                                 * @param structHash         Hashed EIP-712 data struct
                                 * @return digest            The keccak256 digest of an EIP-712 typed data
                                 */
                                function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash)
                                    internal
                                    pure
                                    returns (bytes32 digest)
                                {
                                    assembly {
                                        let ptr := mload(0x40)
                                        mstore(ptr, "\\x19\\x01")
                                        mstore(add(ptr, 0x02), domainSeparator)
                                        mstore(add(ptr, 0x22), structHash)
                                        digest := keccak256(ptr, 0x42)
                                    }
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            /**
                             * @title EIP712
                             * @notice A library that provides EIP712 helper functions
                             */
                            library EIP712 {
                                /**
                                 * @notice Make EIP712 domain separator
                                 * @param name      Contract name
                                 * @param version   Contract version
                                 * @param chainId   Blockchain ID
                                 * @return Domain separator
                                 */
                                function makeDomainSeparator(
                                    string memory name,
                                    string memory version,
                                    uint256 chainId
                                ) internal view returns (bytes32) {
                                    return
                                        keccak256(
                                            abi.encode(
                                                // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
                                                0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
                                                keccak256(bytes(name)),
                                                keccak256(bytes(version)),
                                                chainId,
                                                address(this)
                                            )
                                        );
                                }
                                /**
                                 * @notice Make EIP712 domain separator
                                 * @param name      Contract name
                                 * @param version   Contract version
                                 * @return Domain separator
                                 */
                                function makeDomainSeparator(string memory name, string memory version)
                                    internal
                                    view
                                    returns (bytes32)
                                {
                                    uint256 chainId;
                                    assembly {
                                        chainId := chainid()
                                    }
                                    return makeDomainSeparator(name, version, chainId);
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            /**
                             * @title ECRecover
                             * @notice A library that provides a safe ECDSA recovery function
                             */
                            library ECRecover {
                                /**
                                 * @notice Recover signer's address from a signed message
                                 * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol
                                 * Modifications: Accept v, r, and s as separate arguments
                                 * @param digest    Keccak-256 hash digest of the signed message
                                 * @param v         v of the signature
                                 * @param r         r of the signature
                                 * @param s         s of the signature
                                 * @return Signer address
                                 */
                                function recover(
                                    bytes32 digest,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                ) internal pure returns (address) {
                                    // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                                    // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                                    // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
                                    // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                                    //
                                    // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                                    // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                                    // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                                    // these malleable signatures as well.
                                    if (
                                        uint256(s) >
                                        0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
                                    ) {
                                        revert("ECRecover: invalid signature 's' value");
                                    }
                                    if (v != 27 && v != 28) {
                                        revert("ECRecover: invalid signature 'v' value");
                                    }
                                    // If the signature is valid (and not malleable), return the signer address
                                    address signer = ecrecover(digest, v, r, s);
                                    require(signer != address(0), "ECRecover: invalid signature");
                                    return signer;
                                }
                                /**
                                 * @notice Recover signer's address from a signed message
                                 * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0053ee040a7ff1dbc39691c9e67a69f564930a88/contracts/utils/cryptography/ECDSA.sol
                                 * @param digest    Keccak-256 hash digest of the signed message
                                 * @param signature Signature byte array associated with hash
                                 * @return Signer address
                                 */
                                function recover(bytes32 digest, bytes memory signature)
                                    internal
                                    pure
                                    returns (address)
                                {
                                    require(signature.length == 65, "ECRecover: invalid signature length");
                                    bytes32 r;
                                    bytes32 s;
                                    uint8 v;
                                    // ecrecover takes the signature parameters, and the only way to get them
                                    // currently is to use assembly.
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r := mload(add(signature, 0x20))
                                        s := mload(add(signature, 0x40))
                                        v := byte(0, mload(add(signature, 0x60)))
                                    }
                                    return recover(digest, v, r, s);
                                }
                            }
                            /**
                             * SPDX-License-Identifier: Apache-2.0
                             *
                             * Copyright (c) 2023, Circle Internet Financial, LLC.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             * http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            pragma solidity 0.6.12;
                            /**
                             * @dev Interface of the ERC1271 standard signature validation method for
                             * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
                             */
                            interface IERC1271 {
                                /**
                                 * @dev Should return whether the signature provided is valid for the provided data
                                 * @param hash          Hash of the data to be signed
                                 * @param signature     Signature byte array associated with the provided data hash
                                 * @return magicValue   bytes4 magic value 0x1626ba7e when function passes
                                 */
                                function isValidSignature(bytes32 hash, bytes memory signature)
                                    external
                                    view
                                    returns (bytes4 magicValue);
                            }
                            

                            File 16 of 17: PSMVariant1Actions
                            // SPDX-License-Identifier: AGPL-3.0-or-later
                            pragma solidity ^0.8.25;
                            import { IERC20 }   from "lib/forge-std/src/interfaces/IERC20.sol";
                            import { IERC4626 } from "lib/forge-std/src/interfaces/IERC4626.sol";
                            interface PSMVariant1Like {
                                function dai() external view returns (address);
                                function gemJoin() external view returns (GemJoinLike);
                                function buyGem(address usr, uint256 gemAmt) external;
                                function sellGem(address usr, uint256 gemAmt) external;
                                function tout() external view returns (uint256);
                            }
                            interface GemJoinLike {
                                function gem() external view returns (address);
                            }
                            /**
                             * @notice Actions for swapping in PSM and depositing in an ERC4626 token.
                             * @dev    This is for the first version of the PSM.
                             *         Code: https://github.com/makerdao/dss-psm/blob/222c96d4047e76680ed6803f07dd61aa2590e42b/src/psm.sol
                             */
                            contract PSMVariant1Actions {
                                uint256 private immutable GEM_CONVERSION_FACTOR;
                                PSMVariant1Like public immutable psm;
                                IERC20          public immutable dai;
                                IERC20          public immutable gem;
                                IERC4626        public immutable savingsToken;
                                constructor(address _psm, address _savingsToken) {
                                    psm          = PSMVariant1Like(_psm);
                                    dai          = IERC20(psm.dai());
                                    gem          = IERC20(psm.gemJoin().gem());
                                    savingsToken = IERC4626(_savingsToken);
                                    GEM_CONVERSION_FACTOR = 10 ** (dai.decimals() - gem.decimals());
                                    // Infinite approvals
                                    gem.approve(address(psm.gemJoin()), type(uint256).max);  // For psm.sellGem()
                                    dai.approve(address(psm),           type(uint256).max);  // For psm.buyGem()
                                    dai.approve(address(savingsToken),  type(uint256).max);  // For savingsToken.deposit()
                                }
                                /**
                                 * @notice Swap `gem` for `dai` in the PSM and deposit in the `savingsToken`.
                                 * @dev    Please note that `minAmountOut` is measured in `dai` due to increasing value of the `savingsToken`.
                                 *         `minAmountOut` is used to protect in the case PSM fees change.
                                 * @param  receiver     The receiver of the `savingsToken`.
                                 * @param  amountIn     The amount of the `gem` to swap.
                                 * @param  minAmountOut The minimum amount of `dai` to receive.
                                 * @return amountOut    The amount of `dai` deposited into the `savingsToken`.
                                 */
                                function swapAndDeposit(
                                    address receiver,
                                    uint256 amountIn,
                                    uint256 minAmountOut
                                ) external returns (uint256 amountOut) {
                                    gem.transferFrom(msg.sender, address(this), amountIn);
                                    // There may be a balance in this contract, so we determine the difference
                                    uint256 balanceBefore = dai.balanceOf(address(this));
                                    psm.sellGem(address(this), amountIn);
                                    amountOut = dai.balanceOf(address(this)) - balanceBefore;
                                    require(amountOut >= minAmountOut, "PSMVariant1Actions/amount-out-too-low");
                                    savingsToken.deposit(amountOut, receiver);
                                }
                                /**
                                 * @notice Withdraw a specified amount of output `gem` with a maximum limit of `savingsToken` (in DAI units).
                                 *         Use this if you want an exact amount of `gem` tokens out. IE pay someone 10k exactly.
                                 * @dev    Please note that `maxAmountIn` is measured in `dai` due to increasing value of the `savingsToken`.
                                 *         `maxAmountIn` is used to protect in the case PSM fees change.
                                 * @param  receiver    The receiver of the `gem`.
                                 * @param  amountOut   The amount of `gem` you want to receive.
                                 * @param  maxAmountIn The maximum amount of `dai` to pay for this swap.
                                 * @return amountIn    The amount of `dai` used for the swap.
                                 */
                                function withdrawAndSwap(
                                    address receiver,
                                    uint256 amountOut,
                                    uint256 maxAmountIn
                                ) external returns (uint256 amountIn) {
                                    // Calculate the exact amount of required dai based on the expected output
                                    // We are performing the calculation at https://github.com/makerdao/dss-psm/blob/222c96d4047e76680ed6803f07dd61aa2590e42b/src/psm.sol#L121
                                    uint256 amountOut18 = amountOut * GEM_CONVERSION_FACTOR;
                                    savingsToken.withdraw(amountOut18 + amountOut18 * psm.tout() / 1e18, address(this), msg.sender);
                                    // There may be a balance in this contract, so we determine the difference
                                    uint256 balanceBefore = dai.balanceOf(address(this));
                                    psm.buyGem(receiver, amountOut);
                                    amountIn = balanceBefore - dai.balanceOf(address(this));
                                    require(amountIn <= maxAmountIn, "PSMVariant1Actions/amount-in-too-high");
                                }
                                /**
                                 * @notice Redeem a specified amount of `savingsToken` from the `savingsToken` for `dai` and swap for `gem` in the PSM.
                                 *         Use this if you want to withdraw everything.
                                 * @dev    Please note that this will leave dust due to rounding error in this contract.
                                 * @param  receiver     The receiver of the `gem`.
                                 * @param  shares       The amount of shares to redeem.
                                 * @param  minAmountOut The minimum amount of `gem` to receive.
                                 * @return amountOut    The amount of `gem` tokens received.
                                 */
                                function redeemAndSwap(
                                    address receiver,
                                    uint256 shares,
                                    uint256 minAmountOut
                                ) external returns (uint256 amountOut) {
                                    uint256 assets = savingsToken.redeem(shares, address(this), msg.sender);
                                    // Calculate the exact amount of gems we expect to receive given this amount of assets
                                    // We are reversing the calculation at https://github.com/makerdao/dss-psm/blob/222c96d4047e76680ed6803f07dd61aa2590e42b/src/psm.sol#L121
                                    // Note: Due to rounding, this may leave dai dust in the contract
                                    amountOut = assets * 1e18 / (GEM_CONVERSION_FACTOR * (1e18 + psm.tout()));
                                    require(amountOut >= minAmountOut, "PSMVariant1Actions/amount-out-too-low");
                                    psm.buyGem(receiver, amountOut);
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            pragma solidity >=0.6.2;
                            /// @dev Interface of the ERC20 standard as defined in the EIP.
                            /// @dev This includes the optional name, symbol, and decimals metadata.
                            interface IERC20 {
                                /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
                                event Transfer(address indexed from, address indexed to, uint256 value);
                                /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
                                /// is the new allowance.
                                event Approval(address indexed owner, address indexed spender, uint256 value);
                                /// @notice Returns the amount of tokens in existence.
                                function totalSupply() external view returns (uint256);
                                /// @notice Returns the amount of tokens owned by `account`.
                                function balanceOf(address account) external view returns (uint256);
                                /// @notice Moves `amount` tokens from the caller's account to `to`.
                                function transfer(address to, uint256 amount) external returns (bool);
                                /// @notice Returns the remaining number of tokens that `spender` is allowed
                                /// to spend on behalf of `owner`
                                function allowance(address owner, address spender) external view returns (uint256);
                                /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
                                /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                function approve(address spender, uint256 amount) external returns (bool);
                                /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
                                /// `amount` is then deducted from the caller's allowance.
                                function transferFrom(address from, address to, uint256 amount) external returns (bool);
                                /// @notice Returns the name of the token.
                                function name() external view returns (string memory);
                                /// @notice Returns the symbol of the token.
                                function symbol() external view returns (string memory);
                                /// @notice Returns the decimals places of the token.
                                function decimals() external view returns (uint8);
                            }
                            // SPDX-License-Identifier: MIT
                            pragma solidity >=0.6.2;
                            import "./IERC20.sol";
                            /// @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
                            /// https://eips.ethereum.org/EIPS/eip-4626
                            interface IERC4626 is IERC20 {
                                event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
                                event Withdraw(
                                    address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
                                );
                                /// @notice Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
                                /// @dev
                                /// - MUST be an ERC-20 token contract.
                                /// - MUST NOT revert.
                                function asset() external view returns (address assetTokenAddress);
                                /// @notice Returns the total amount of the underlying asset that is “managed” by Vault.
                                /// @dev
                                /// - SHOULD include any compounding that occurs from yield.
                                /// - MUST be inclusive of any fees that are charged against assets in the Vault.
                                /// - MUST NOT revert.
                                function totalAssets() external view returns (uint256 totalManagedAssets);
                                /// @notice Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
                                /// scenario where all the conditions are met.
                                /// @dev
                                /// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
                                /// - MUST NOT show any variations depending on the caller.
                                /// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
                                /// - MUST NOT revert.
                                ///
                                /// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
                                /// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
                                /// from.
                                function convertToShares(uint256 assets) external view returns (uint256 shares);
                                /// @notice Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
                                /// scenario where all the conditions are met.
                                /// @dev
                                /// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
                                /// - MUST NOT show any variations depending on the caller.
                                /// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
                                /// - MUST NOT revert.
                                ///
                                /// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
                                /// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
                                /// from.
                                function convertToAssets(uint256 shares) external view returns (uint256 assets);
                                /// @notice Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
                                /// through a deposit call.
                                /// @dev
                                /// - MUST return a limited value if receiver is subject to some deposit limit.
                                /// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
                                /// - MUST NOT revert.
                                function maxDeposit(address receiver) external view returns (uint256 maxAssets);
                                /// @notice Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
                                /// current on-chain conditions.
                                /// @dev
                                /// - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
                                ///   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
                                ///   in the same transaction.
                                /// - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
                                ///   deposit would be accepted, regardless if the user has enough tokens approved, etc.
                                /// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
                                /// - MUST NOT revert.
                                ///
                                /// NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
                                /// share price or some other type of condition, meaning the depositor will lose assets by depositing.
                                function previewDeposit(uint256 assets) external view returns (uint256 shares);
                                /// @notice Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
                                /// @dev
                                /// - MUST emit the Deposit event.
                                /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
                                ///   deposit execution, and are accounted for during deposit.
                                /// - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
                                ///   approving enough underlying tokens to the Vault contract, etc).
                                ///
                                /// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
                                function deposit(uint256 assets, address receiver) external returns (uint256 shares);
                                /// @notice Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
                                /// @dev
                                /// - MUST return a limited value if receiver is subject to some mint limit.
                                /// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
                                /// - MUST NOT revert.
                                function maxMint(address receiver) external view returns (uint256 maxShares);
                                /// @notice Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
                                /// current on-chain conditions.
                                /// @dev
                                /// - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
                                ///   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
                                ///   same transaction.
                                /// - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
                                ///   would be accepted, regardless if the user has enough tokens approved, etc.
                                /// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
                                /// - MUST NOT revert.
                                ///
                                /// NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
                                /// share price or some other type of condition, meaning the depositor will lose assets by minting.
                                function previewMint(uint256 shares) external view returns (uint256 assets);
                                /// @notice Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
                                /// @dev
                                /// - MUST emit the Deposit event.
                                /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
                                ///   execution, and are accounted for during mint.
                                /// - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
                                ///   approving enough underlying tokens to the Vault contract, etc).
                                ///
                                /// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
                                function mint(uint256 shares, address receiver) external returns (uint256 assets);
                                /// @notice Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
                                /// Vault, through a withdraw call.
                                /// @dev
                                /// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
                                /// - MUST NOT revert.
                                function maxWithdraw(address owner) external view returns (uint256 maxAssets);
                                /// @notice Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
                                /// given current on-chain conditions.
                                /// @dev
                                /// - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
                                ///   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
                                ///   called
                                ///   in the same transaction.
                                /// - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
                                ///   the withdrawal would be accepted, regardless if the user has enough shares, etc.
                                /// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
                                /// - MUST NOT revert.
                                ///
                                /// NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
                                /// share price or some other type of condition, meaning the depositor will lose assets by depositing.
                                function previewWithdraw(uint256 assets) external view returns (uint256 shares);
                                /// @notice Burns shares from owner and sends exactly assets of underlying tokens to receiver.
                                /// @dev
                                /// - MUST emit the Withdraw event.
                                /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
                                ///   withdraw execution, and are accounted for during withdraw.
                                /// - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
                                ///   not having enough shares, etc).
                                ///
                                /// Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
                                /// Those methods should be performed separately.
                                function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
                                /// @notice Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
                                /// through a redeem call.
                                /// @dev
                                /// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
                                /// - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
                                /// - MUST NOT revert.
                                function maxRedeem(address owner) external view returns (uint256 maxShares);
                                /// @notice Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
                                /// given current on-chain conditions.
                                /// @dev
                                /// - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
                                ///   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
                                ///   same transaction.
                                /// - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
                                ///   redemption would be accepted, regardless if the user has enough shares, etc.
                                /// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
                                /// - MUST NOT revert.
                                ///
                                /// NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
                                /// share price or some other type of condition, meaning the depositor will lose assets by redeeming.
                                function previewRedeem(uint256 shares) external view returns (uint256 assets);
                                /// @notice Burns exactly shares from owner and sends assets of underlying tokens to receiver.
                                /// @dev
                                /// - MUST emit the Withdraw event.
                                /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
                                ///   redeem execution, and are accounted for during redeem.
                                /// - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
                                ///   not having enough shares, etc).
                                ///
                                /// NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
                                /// Those methods should be performed separately.
                                function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
                            }
                            

                            File 17 of 17: SUsds
                            // SPDX-License-Identifier: AGPL-3.0-or-later
                            /// SUsds.sol
                            // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico
                            // Copyright (C) 2021 Dai Foundation
                            //
                            // This program is free software: you can redistribute it and/or modify
                            // it under the terms of the GNU Affero General Public License as published by
                            // the Free Software Foundation, either version 3 of the License, or
                            // (at your option) any later version.
                            //
                            // This program is distributed in the hope that it will be useful,
                            // but WITHOUT ANY WARRANTY; without even the implied warranty of
                            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                            // GNU Affero General Public License for more details.
                            //
                            // You should have received a copy of the GNU Affero General Public License
                            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
                            pragma solidity ^0.8.21;
                            import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
                            interface IERC1271 {
                                function isValidSignature(
                                    bytes32,
                                    bytes memory
                                ) external view returns (bytes4);
                            }
                            interface VatLike {
                                function hope(address) external;
                                function suck(address, address, uint256) external;
                            }
                            interface UsdsJoinLike {
                                function vat() external view returns (address);
                                function usds() external view returns (address);
                                function exit(address, uint256) external;
                            }
                            interface UsdsLike {
                                function transfer(address, uint256) external;
                                function transferFrom(address, address, uint256) external;
                            }
                            contract SUsds is UUPSUpgradeable {
                                // --- Storage Variables ---
                                // Admin
                                mapping (address => uint256) public wards;
                                // ERC20
                                uint256                                           public totalSupply;
                                mapping (address => uint256)                      public balanceOf;
                                mapping (address => mapping (address => uint256)) public allowance;
                                mapping (address => uint256)                      public nonces;
                                // Savings yield
                                uint192 public chi;   // The Rate Accumulator  [ray]
                                uint64  public rho;   // Time of last drip     [unix epoch time]
                                uint256 public ssr;   // The USDS Savings Rate [ray]
                                // --- Constants ---
                                // ERC20
                                string  public constant name     = "Savings USDS";
                                string  public constant symbol   = "sUSDS";
                                string  public constant version  = "1";
                                uint8   public constant decimals = 18;
                                // Math
                                uint256 private constant RAY = 10 ** 27;
                                // --- Immutables ---
                                // EIP712
                                bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
                                // Savings yield
                                UsdsJoinLike public immutable usdsJoin;
                                VatLike      public immutable vat;
                                UsdsLike     public immutable usds;
                                address      public immutable vow;
                                // --- Events ---
                                // Admin
                                event Rely(address indexed usr);
                                event Deny(address indexed usr);
                                event File(bytes32 indexed what, uint256 data);
                                // ERC20
                                event Approval(address indexed owner, address indexed spender, uint256 value);
                                event Transfer(address indexed from, address indexed to, uint256 value);
                                // ERC4626
                                event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
                                event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
                                // Referral
                                event Referral(uint16 indexed referral, address indexed owner, uint256 assets, uint256 shares);
                                // Savings yield
                                event Drip(uint256 chi, uint256 diff);
                                // --- Modifiers ---
                                modifier auth {
                                    require(wards[msg.sender] == 1, "SUsds/not-authorized");
                                    _;
                                }
                                // --- Constructor ---
                                constructor(address usdsJoin_, address vow_) {
                                    _disableInitializers(); // Avoid initializing in the context of the implementation
                                    usdsJoin = UsdsJoinLike(usdsJoin_);
                                    vat = VatLike(UsdsJoinLike(usdsJoin_).vat());
                                    usds = UsdsLike(UsdsJoinLike(usdsJoin_).usds());
                                    vow = vow_;
                                }
                                // --- Upgradability ---
                                function initialize() initializer external {
                                    __UUPSUpgradeable_init();
                                    chi = uint192(RAY);
                                    rho = uint64(block.timestamp);
                                    ssr = RAY;
                                    vat.hope(address(usdsJoin));
                                    wards[msg.sender] = 1;
                                    emit Rely(msg.sender);
                                }
                                function _authorizeUpgrade(address newImplementation) internal override auth {}
                                function getImplementation() external view returns (address) {
                                    return ERC1967Utils.getImplementation();
                                }
                                // --- Internals ---
                                // EIP712
                                function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
                                    return keccak256(
                                        abi.encode(
                                            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                                            keccak256(bytes(name)),
                                            keccak256(bytes(version)),
                                            chainId,
                                            address(this)
                                        )
                                    );
                                }
                                function DOMAIN_SEPARATOR() external view returns (bytes32) {
                                    return _calculateDomainSeparator(block.chainid);
                                }
                                // Math
                                function _rpow(uint256 x, uint256 n) internal pure returns (uint256 z) {
                                    assembly {
                                        switch x case 0 {switch n case 0 {z := RAY} default {z := 0}}
                                        default {
                                            switch mod(n, 2) case 0 { z := RAY } default { z := x }
                                            let half := div(RAY, 2)  // for rounding.
                                            for { n := div(n, 2) } n { n := div(n,2) } {
                                                let xx := mul(x, x)
                                                if iszero(eq(div(xx, x), x)) { revert(0,0) }
                                                let xxRound := add(xx, half)
                                                if lt(xxRound, xx) { revert(0,0) }
                                                x := div(xxRound, RAY)
                                                if mod(n,2) {
                                                    let zx := mul(z, x)
                                                    if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) }
                                                    let zxRound := add(zx, half)
                                                    if lt(zxRound, zx) { revert(0,0) }
                                                    z := div(zxRound, RAY)
                                                }
                                            }
                                        }
                                    }
                                }
                                function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) {
                                    // Note: _divup(0,0) will return 0 differing from natural solidity division
                                    unchecked {
                                        z = x != 0 ? ((x - 1) / y) + 1 : 0;
                                    }
                                }
                                // --- Admin external functions ---
                                function rely(address usr) external auth {
                                    wards[usr] = 1;
                                    emit Rely(usr);
                                }
                                function deny(address usr) external auth {
                                    wards[usr] = 0;
                                    emit Deny(usr);
                                }
                                function file(bytes32 what, uint256 data) external auth {
                                    if (what == "ssr") {
                                        require(data >= RAY, "SUsds/wrong-ssr-value");
                                        require(rho == block.timestamp, "SUsds/chi-not-up-to-date");
                                        ssr = data;
                                    } else revert("SUsds/file-unrecognized-param");
                                    emit File(what, data);
                                }
                                // --- Savings Rate Accumulation external/internal function ---
                                function drip() public returns (uint256 nChi) {
                                    (uint256 chi_, uint256 rho_) = (chi, rho);
                                    uint256 diff;
                                    if (block.timestamp > rho_) {
                                        nChi = _rpow(ssr, block.timestamp - rho_) * chi_ / RAY;
                                        uint256 totalSupply_ = totalSupply;
                                        diff = totalSupply_ * nChi / RAY - totalSupply_ * chi_ / RAY;
                                        vat.suck(address(vow), address(this), diff * RAY);
                                        usdsJoin.exit(address(this), diff);
                                        chi = uint192(nChi); // safe as nChi is limited to maxUint256/RAY (which is < maxUint192)
                                        rho = uint64(block.timestamp);
                                    } else {
                                        nChi = chi_;
                                    }
                                    emit Drip(nChi, diff);
                                }
                                // --- ERC20 Mutations ---
                                function transfer(address to, uint256 value) external returns (bool) {
                                    require(to != address(0) && to != address(this), "SUsds/invalid-address");
                                    uint256 balance = balanceOf[msg.sender];
                                    require(balance >= value, "SUsds/insufficient-balance");
                                    unchecked {
                                        balanceOf[msg.sender] = balance - value;
                                        balanceOf[to] += value; // note: we don't need an overflow check here b/c sum of all balances == totalSupply
                                    }
                                    emit Transfer(msg.sender, to, value);
                                    return true;
                                }
                                function transferFrom(address from, address to, uint256 value) external returns (bool) {
                                    require(to != address(0) && to != address(this), "SUsds/invalid-address");
                                    uint256 balance = balanceOf[from];
                                    require(balance >= value, "SUsds/insufficient-balance");
                                    if (from != msg.sender) {
                                        uint256 allowed = allowance[from][msg.sender];
                                        if (allowed != type(uint256).max) {
                                            require(allowed >= value, "SUsds/insufficient-allowance");
                                            unchecked {
                                                allowance[from][msg.sender] = allowed - value;
                                            }
                                        }
                                    }
                                    unchecked {
                                        balanceOf[from] = balance - value;
                                        balanceOf[to] += value; // note: we don't need an overflow check here b/c sum of all balances == totalSupply
                                    }
                                    emit Transfer(from, to, value);
                                    return true;
                                }
                                function approve(address spender, uint256 value) external returns (bool) {
                                    allowance[msg.sender][spender] = value;
                                    emit Approval(msg.sender, spender, value);
                                    return true;
                                }
                                // --- Mint/Burn Internal ---
                                function _mint(uint256 assets, uint256 shares, address receiver) internal {
                                    require(receiver != address(0) && receiver != address(this), "SUsds/invalid-address");
                                    usds.transferFrom(msg.sender, address(this), assets);
                                    unchecked {
                                        balanceOf[receiver] = balanceOf[receiver] + shares; // note: we don't need an overflow check here b/c balanceOf[receiver] <= totalSupply
                                        totalSupply = totalSupply + shares; // note: we don't need an overflow check here b/c shares totalSupply will always be <= usds totalSupply
                                    }
                                    emit Deposit(msg.sender, receiver, assets, shares);
                                    emit Transfer(address(0), receiver, shares);
                                }
                                function _burn(uint256 assets, uint256 shares, address receiver, address owner) internal {
                                    uint256 balance = balanceOf[owner];
                                    require(balance >= shares, "SUsds/insufficient-balance");
                                    if (owner != msg.sender) {
                                        uint256 allowed = allowance[owner][msg.sender];
                                        if (allowed != type(uint256).max) {
                                            require(allowed >= shares, "SUsds/insufficient-allowance");
                                            unchecked {
                                                allowance[owner][msg.sender] = allowed - shares;
                                            }
                                        }
                                    }
                                    unchecked {
                                        balanceOf[owner] = balance - shares; // note: we don't need overflow checks b/c require(balance >= shares) and balance <= totalSupply
                                        totalSupply      = totalSupply - shares;
                                    }
                                    usds.transfer(receiver, assets);
                                    emit Transfer(owner, address(0), shares);
                                    emit Withdraw(msg.sender, receiver, owner, assets, shares);
                                }
                                // --- ERC-4626 ---
                                function asset() external view returns (address) {
                                    return address(usds);
                                }
                                function totalAssets() external view returns (uint256) {
                                    return convertToAssets(totalSupply);
                                }
                                function convertToShares(uint256 assets) public view returns (uint256) {
                                    uint256 chi_ = (block.timestamp > rho) ? _rpow(ssr, block.timestamp - rho) * chi / RAY : chi;
                                    return assets * RAY / chi_;
                                }
                                function convertToAssets(uint256 shares) public view returns (uint256) {
                                    uint256 chi_ = (block.timestamp > rho) ? _rpow(ssr, block.timestamp - rho) * chi / RAY : chi;
                                    return shares * chi_ / RAY;
                                }
                                function maxDeposit(address) external pure returns (uint256) {
                                    return type(uint256).max;
                                }
                                function previewDeposit(uint256 assets) external view returns (uint256) {
                                    return convertToShares(assets);
                                }
                                function deposit(uint256 assets, address receiver) public returns (uint256 shares) {
                                    shares = assets * RAY / drip();
                                    _mint(assets, shares, receiver);
                                }
                                function deposit(uint256 assets, address receiver, uint16 referral) external returns (uint256 shares) {
                                    shares = deposit(assets, receiver);
                                    emit Referral(referral, receiver, assets, shares);
                                }
                                function maxMint(address) external pure returns (uint256) {
                                    return type(uint256).max;
                                }
                                function previewMint(uint256 shares) external view returns (uint256) {
                                    uint256 chi_ = (block.timestamp > rho) ? _rpow(ssr, block.timestamp - rho) * chi / RAY : chi;
                                    return _divup(shares * chi_, RAY);
                                }
                                function mint(uint256 shares, address receiver) public returns (uint256 assets) {
                                    assets = _divup(shares * drip(), RAY);
                                    _mint(assets, shares, receiver);
                                }
                                function mint(uint256 shares, address receiver, uint16 referral) external returns (uint256 assets) {
                                    assets = mint(shares, receiver);
                                    emit Referral(referral, receiver, assets, shares);
                                }
                                function maxWithdraw(address owner) external view returns (uint256) {
                                    return convertToAssets(balanceOf[owner]);
                                }
                                function previewWithdraw(uint256 assets) external view returns (uint256) {
                                    uint256 chi_ = (block.timestamp > rho) ? _rpow(ssr, block.timestamp - rho) * chi / RAY : chi;
                                    return _divup(assets * RAY, chi_);
                                }
                                function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares) {
                                    shares = _divup(assets * RAY, drip());
                                    _burn(assets, shares, receiver, owner);
                                }
                                function maxRedeem(address owner) external view returns (uint256) {
                                    return balanceOf[owner];
                                }
                                function previewRedeem(uint256 shares) external view returns (uint256) {
                                    return convertToAssets(shares);
                                }
                                function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) {
                                    assets = shares * drip() / RAY;
                                    _burn(assets, shares, receiver, owner);
                                }
                                // --- Approve by signature ---
                                function _isValidSignature(
                                    address signer,
                                    bytes32 digest,
                                    bytes memory signature
                                ) internal view returns (bool valid) {
                                    if (signature.length == 65) {
                                        bytes32 r;
                                        bytes32 s;
                                        uint8 v;
                                        assembly {
                                            r := mload(add(signature, 0x20))
                                            s := mload(add(signature, 0x40))
                                            v := byte(0, mload(add(signature, 0x60)))
                                        }
                                        if (signer == ecrecover(digest, v, r, s)) {
                                            return true;
                                        }
                                    }
                                    if (signer.code.length > 0) {
                                        (bool success, bytes memory result) = signer.staticcall(
                                            abi.encodeCall(IERC1271.isValidSignature, (digest, signature))
                                        );
                                        valid = (success &&
                                            result.length == 32 &&
                                            abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);
                                    }
                                }
                                function permit(
                                    address owner,
                                    address spender,
                                    uint256 value,
                                    uint256 deadline,
                                    bytes memory signature
                                ) public {
                                    require(block.timestamp <= deadline, "SUsds/permit-expired");
                                    require(owner != address(0), "SUsds/invalid-owner");
                                    uint256 nonce;
                                    unchecked { nonce = nonces[owner]++; }
                                    bytes32 digest =
                                        keccak256(abi.encodePacked(
                                            "\\x19\\x01",
                                            _calculateDomainSeparator(block.chainid),
                                            keccak256(abi.encode(
                                                PERMIT_TYPEHASH,
                                                owner,
                                                spender,
                                                value,
                                                nonce,
                                                deadline
                                            ))
                                        ));
                                    require(_isValidSignature(owner, digest, signature), "SUsds/invalid-permit");
                                    allowance[owner][spender] = value;
                                    emit Approval(owner, spender, value);
                                }
                                function permit(
                                    address owner,
                                    address spender,
                                    uint256 value,
                                    uint256 deadline,
                                    uint8 v,
                                    bytes32 r,
                                    bytes32 s
                                ) external {
                                    permit(owner, spender, value, deadline, abi.encodePacked(r, s, v));
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol)
                            pragma solidity ^0.8.20;
                            import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
                            import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
                            import {Initializable} from "./Initializable.sol";
                            /**
                             * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
                             * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
                             *
                             * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
                             * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
                             * `UUPSUpgradeable` with a custom implementation of upgrades.
                             *
                             * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
                             */
                            abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
                                /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
                                address private immutable __self = address(this);
                                /**
                                 * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
                                 * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
                                 * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
                                 * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
                                 * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
                                 * during an upgrade.
                                 */
                                string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
                                /**
                                 * @dev The call is from an unauthorized context.
                                 */
                                error UUPSUnauthorizedCallContext();
                                /**
                                 * @dev The storage `slot` is unsupported as a UUID.
                                 */
                                error UUPSUnsupportedProxiableUUID(bytes32 slot);
                                /**
                                 * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
                                 * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
                                 * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
                                 * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
                                 * fail.
                                 */
                                modifier onlyProxy() {
                                    _checkProxy();
                                    _;
                                }
                                /**
                                 * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
                                 * callable on the implementing contract but not through proxies.
                                 */
                                modifier notDelegated() {
                                    _checkNotDelegated();
                                    _;
                                }
                                function __UUPSUpgradeable_init() internal onlyInitializing {
                                }
                                function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
                                }
                                /**
                                 * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
                                 * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
                                 *
                                 * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
                                 * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
                                 * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
                                 */
                                function proxiableUUID() external view virtual notDelegated returns (bytes32) {
                                    return ERC1967Utils.IMPLEMENTATION_SLOT;
                                }
                                /**
                                 * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
                                 * encoded in `data`.
                                 *
                                 * Calls {_authorizeUpgrade}.
                                 *
                                 * Emits an {Upgraded} event.
                                 *
                                 * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
                                 */
                                function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
                                    _authorizeUpgrade(newImplementation);
                                    _upgradeToAndCallUUPS(newImplementation, data);
                                }
                                /**
                                 * @dev Reverts if the execution is not performed via delegatecall or the execution
                                 * context is not of a proxy with an ERC1967-compliant implementation pointing to self.
                                 * See {_onlyProxy}.
                                 */
                                function _checkProxy() internal view virtual {
                                    if (
                                        address(this) == __self || // Must be called through delegatecall
                                        ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
                                    ) {
                                        revert UUPSUnauthorizedCallContext();
                                    }
                                }
                                /**
                                 * @dev Reverts if the execution is performed via delegatecall.
                                 * See {notDelegated}.
                                 */
                                function _checkNotDelegated() internal view virtual {
                                    if (address(this) != __self) {
                                        // Must not be called through delegatecall
                                        revert UUPSUnauthorizedCallContext();
                                    }
                                }
                                /**
                                 * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
                                 * {upgradeToAndCall}.
                                 *
                                 * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
                                 *
                                 * ```solidity
                                 * function _authorizeUpgrade(address) internal onlyOwner {}
                                 * ```
                                 */
                                function _authorizeUpgrade(address newImplementation) internal virtual;
                                /**
                                 * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
                                 *
                                 * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
                                 * is expected to be the implementation slot in ERC1967.
                                 *
                                 * Emits an {IERC1967-Upgraded} event.
                                 */
                                function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
                                    try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                                        if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
                                            revert UUPSUnsupportedProxiableUUID(slot);
                                        }
                                        ERC1967Utils.upgradeToAndCall(newImplementation, data);
                                    } catch {
                                        // The implementation is not UUPS
                                        revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
                                    }
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC1822.sol)
                            pragma solidity ^0.8.20;
                            /**
                             * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
                             * proxy whose upgrades are fully controlled by the current implementation.
                             */
                            interface IERC1822Proxiable {
                                /**
                                 * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
                                 * address.
                                 *
                                 * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
                                 * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
                                 * function revert if invoked through a proxy.
                                 */
                                function proxiableUUID() external view returns (bytes32);
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol)
                            pragma solidity ^0.8.20;
                            import {IBeacon} from "../beacon/IBeacon.sol";
                            import {Address} from "../../utils/Address.sol";
                            import {StorageSlot} from "../../utils/StorageSlot.sol";
                            /**
                             * @dev This abstract contract provides getters and event emitting update functions for
                             * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
                             */
                            library ERC1967Utils {
                                // We re-declare ERC-1967 events here because they can't be used directly from IERC1967.
                                // This will be fixed in Solidity 0.8.21. At that point we should remove these events.
                                /**
                                 * @dev Emitted when the implementation is upgraded.
                                 */
                                event Upgraded(address indexed implementation);
                                /**
                                 * @dev Emitted when the admin account has changed.
                                 */
                                event AdminChanged(address previousAdmin, address newAdmin);
                                /**
                                 * @dev Emitted when the beacon is changed.
                                 */
                                event BeaconUpgraded(address indexed beacon);
                                /**
                                 * @dev Storage slot with the address of the current implementation.
                                 * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
                                 */
                                // solhint-disable-next-line private-vars-leading-underscore
                                bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                                /**
                                 * @dev The `implementation` of the proxy is invalid.
                                 */
                                error ERC1967InvalidImplementation(address implementation);
                                /**
                                 * @dev The `admin` of the proxy is invalid.
                                 */
                                error ERC1967InvalidAdmin(address admin);
                                /**
                                 * @dev The `beacon` of the proxy is invalid.
                                 */
                                error ERC1967InvalidBeacon(address beacon);
                                /**
                                 * @dev An upgrade function sees `msg.value > 0` that may be lost.
                                 */
                                error ERC1967NonPayable();
                                /**
                                 * @dev Returns the current implementation address.
                                 */
                                function getImplementation() internal view returns (address) {
                                    return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
                                }
                                /**
                                 * @dev Stores a new address in the EIP1967 implementation slot.
                                 */
                                function _setImplementation(address newImplementation) private {
                                    if (newImplementation.code.length == 0) {
                                        revert ERC1967InvalidImplementation(newImplementation);
                                    }
                                    StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
                                }
                                /**
                                 * @dev Performs implementation upgrade with additional setup call if data is nonempty.
                                 * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
                                 * to avoid stuck value in the contract.
                                 *
                                 * Emits an {IERC1967-Upgraded} event.
                                 */
                                function upgradeToAndCall(address newImplementation, bytes memory data) internal {
                                    _setImplementation(newImplementation);
                                    emit Upgraded(newImplementation);
                                    if (data.length > 0) {
                                        Address.functionDelegateCall(newImplementation, data);
                                    } else {
                                        _checkNonPayable();
                                    }
                                }
                                /**
                                 * @dev Storage slot with the admin of the contract.
                                 * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
                                 */
                                // solhint-disable-next-line private-vars-leading-underscore
                                bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                                /**
                                 * @dev Returns the current admin.
                                 *
                                 * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
                                 * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                                 * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
                                 */
                                function getAdmin() internal view returns (address) {
                                    return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
                                }
                                /**
                                 * @dev Stores a new address in the EIP1967 admin slot.
                                 */
                                function _setAdmin(address newAdmin) private {
                                    if (newAdmin == address(0)) {
                                        revert ERC1967InvalidAdmin(address(0));
                                    }
                                    StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
                                }
                                /**
                                 * @dev Changes the admin of the proxy.
                                 *
                                 * Emits an {IERC1967-AdminChanged} event.
                                 */
                                function changeAdmin(address newAdmin) internal {
                                    emit AdminChanged(getAdmin(), newAdmin);
                                    _setAdmin(newAdmin);
                                }
                                /**
                                 * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
                                 * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
                                 */
                                // solhint-disable-next-line private-vars-leading-underscore
                                bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
                                /**
                                 * @dev Returns the current beacon.
                                 */
                                function getBeacon() internal view returns (address) {
                                    return StorageSlot.getAddressSlot(BEACON_SLOT).value;
                                }
                                /**
                                 * @dev Stores a new beacon in the EIP1967 beacon slot.
                                 */
                                function _setBeacon(address newBeacon) private {
                                    if (newBeacon.code.length == 0) {
                                        revert ERC1967InvalidBeacon(newBeacon);
                                    }
                                    StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
                                    address beaconImplementation = IBeacon(newBeacon).implementation();
                                    if (beaconImplementation.code.length == 0) {
                                        revert ERC1967InvalidImplementation(beaconImplementation);
                                    }
                                }
                                /**
                                 * @dev Change the beacon and trigger a setup call if data is nonempty.
                                 * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
                                 * to avoid stuck value in the contract.
                                 *
                                 * Emits an {IERC1967-BeaconUpgraded} event.
                                 *
                                 * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
                                 * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
                                 * efficiency.
                                 */
                                function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
                                    _setBeacon(newBeacon);
                                    emit BeaconUpgraded(newBeacon);
                                    if (data.length > 0) {
                                        Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                                    } else {
                                        _checkNonPayable();
                                    }
                                }
                                /**
                                 * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
                                 * if an upgrade doesn't perform an initialization call.
                                 */
                                function _checkNonPayable() private {
                                    if (msg.value > 0) {
                                        revert ERC1967NonPayable();
                                    }
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
                            pragma solidity ^0.8.20;
                            /**
                             * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
                             * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
                             * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
                             * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
                             *
                             * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
                             * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
                             * case an upgrade adds a module that needs to be initialized.
                             *
                             * For example:
                             *
                             * [.hljs-theme-light.nopadding]
                             * ```solidity
                             * contract MyToken is ERC20Upgradeable {
                             *     function initialize() initializer public {
                             *         __ERC20_init("MyToken", "MTK");
                             *     }
                             * }
                             *
                             * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
                             *     function initializeV2() reinitializer(2) public {
                             *         __ERC20Permit_init("MyToken");
                             *     }
                             * }
                             * ```
                             *
                             * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
                             * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
                             *
                             * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
                             * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
                             *
                             * [CAUTION]
                             * ====
                             * Avoid leaving a contract uninitialized.
                             *
                             * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
                             * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
                             * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
                             *
                             * [.hljs-theme-light.nopadding]
                             * ```
                             * /// @custom:oz-upgrades-unsafe-allow constructor
                             * constructor() {
                             *     _disableInitializers();
                             * }
                             * ```
                             * ====
                             */
                            abstract contract Initializable {
                                /**
                                 * @dev Storage of the initializable contract.
                                 *
                                 * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
                                 * when using with upgradeable contracts.
                                 *
                                 * @custom:storage-location erc7201:openzeppelin.storage.Initializable
                                 */
                                struct InitializableStorage {
                                    /**
                                     * @dev Indicates that the contract has been initialized.
                                     */
                                    uint64 _initialized;
                                    /**
                                     * @dev Indicates that the contract is in the process of being initialized.
                                     */
                                    bool _initializing;
                                }
                                // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
                                bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
                                /**
                                 * @dev The contract is already initialized.
                                 */
                                error InvalidInitialization();
                                /**
                                 * @dev The contract is not initializing.
                                 */
                                error NotInitializing();
                                /**
                                 * @dev Triggered when the contract has been initialized or reinitialized.
                                 */
                                event Initialized(uint64 version);
                                /**
                                 * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
                                 * `onlyInitializing` functions can be used to initialize parent contracts.
                                 *
                                 * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
                                 * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
                                 * production.
                                 *
                                 * Emits an {Initialized} event.
                                 */
                                modifier initializer() {
                                    // solhint-disable-next-line var-name-mixedcase
                                    InitializableStorage storage $ = _getInitializableStorage();
                                    // Cache values to avoid duplicated sloads
                                    bool isTopLevelCall = !$._initializing;
                                    uint64 initialized = $._initialized;
                                    // Allowed calls:
                                    // - initialSetup: the contract is not in the initializing state and no previous version was
                                    //                 initialized
                                    // - construction: the contract is initialized at version 1 (no reininitialization) and the
                                    //                 current contract is just being deployed
                                    bool initialSetup = initialized == 0 && isTopLevelCall;
                                    bool construction = initialized == 1 && address(this).code.length == 0;
                                    if (!initialSetup && !construction) {
                                        revert InvalidInitialization();
                                    }
                                    $._initialized = 1;
                                    if (isTopLevelCall) {
                                        $._initializing = true;
                                    }
                                    _;
                                    if (isTopLevelCall) {
                                        $._initializing = false;
                                        emit Initialized(1);
                                    }
                                }
                                /**
                                 * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
                                 * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
                                 * used to initialize parent contracts.
                                 *
                                 * A reinitializer may be used after the original initialization step. This is essential to configure modules that
                                 * are added through upgrades and that require initialization.
                                 *
                                 * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
                                 * cannot be nested. If one is invoked in the context of another, execution will revert.
                                 *
                                 * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
                                 * a contract, executing them in the right order is up to the developer or operator.
                                 *
                                 * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
                                 *
                                 * Emits an {Initialized} event.
                                 */
                                modifier reinitializer(uint64 version) {
                                    // solhint-disable-next-line var-name-mixedcase
                                    InitializableStorage storage $ = _getInitializableStorage();
                                    if ($._initializing || $._initialized >= version) {
                                        revert InvalidInitialization();
                                    }
                                    $._initialized = version;
                                    $._initializing = true;
                                    _;
                                    $._initializing = false;
                                    emit Initialized(version);
                                }
                                /**
                                 * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
                                 * {initializer} and {reinitializer} modifiers, directly or indirectly.
                                 */
                                modifier onlyInitializing() {
                                    _checkInitializing();
                                    _;
                                }
                                /**
                                 * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
                                 */
                                function _checkInitializing() internal view virtual {
                                    if (!_isInitializing()) {
                                        revert NotInitializing();
                                    }
                                }
                                /**
                                 * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
                                 * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
                                 * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
                                 * through proxies.
                                 *
                                 * Emits an {Initialized} event the first time it is successfully executed.
                                 */
                                function _disableInitializers() internal virtual {
                                    // solhint-disable-next-line var-name-mixedcase
                                    InitializableStorage storage $ = _getInitializableStorage();
                                    if ($._initializing) {
                                        revert InvalidInitialization();
                                    }
                                    if ($._initialized != type(uint64).max) {
                                        $._initialized = type(uint64).max;
                                        emit Initialized(type(uint64).max);
                                    }
                                }
                                /**
                                 * @dev Returns the highest version that has been initialized. See {reinitializer}.
                                 */
                                function _getInitializedVersion() internal view returns (uint64) {
                                    return _getInitializableStorage()._initialized;
                                }
                                /**
                                 * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
                                 */
                                function _isInitializing() internal view returns (bool) {
                                    return _getInitializableStorage()._initializing;
                                }
                                /**
                                 * @dev Returns a pointer to the storage namespace.
                                 */
                                // solhint-disable-next-line var-name-mixedcase
                                function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
                                    assembly {
                                        $.slot := INITIALIZABLE_STORAGE
                                    }
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)
                            pragma solidity ^0.8.20;
                            /**
                             * @dev This is the interface that {BeaconProxy} expects of its beacon.
                             */
                            interface IBeacon {
                                /**
                                 * @dev Must return an address that can be used as a delegate call target.
                                 *
                                 * {UpgradeableBeacon} will check that this address is a contract.
                                 */
                                function implementation() external view returns (address);
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
                            pragma solidity ^0.8.20;
                            /**
                             * @dev Collection of functions related to the address type
                             */
                            library Address {
                                /**
                                 * @dev The ETH balance of the account is not enough to perform the operation.
                                 */
                                error AddressInsufficientBalance(address account);
                                /**
                                 * @dev There's no code at `target` (it is not a contract).
                                 */
                                error AddressEmptyCode(address target);
                                /**
                                 * @dev A call to an address target failed. The target may have reverted.
                                 */
                                error FailedInnerCall();
                                /**
                                 * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                                 * `recipient`, forwarding all available gas and reverting on errors.
                                 *
                                 * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                                 * of certain opcodes, possibly making contracts go over the 2300 gas limit
                                 * imposed by `transfer`, making them unable to receive funds via
                                 * `transfer`. {sendValue} removes this limitation.
                                 *
                                 * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                                 *
                                 * IMPORTANT: because control is transferred to `recipient`, care must be
                                 * taken to not create reentrancy vulnerabilities. Consider using
                                 * {ReentrancyGuard} or the
                                 * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                                 */
                                function sendValue(address payable recipient, uint256 amount) internal {
                                    if (address(this).balance < amount) {
                                        revert AddressInsufficientBalance(address(this));
                                    }
                                    (bool success, ) = recipient.call{value: amount}("");
                                    if (!success) {
                                        revert FailedInnerCall();
                                    }
                                }
                                /**
                                 * @dev Performs a Solidity function call using a low level `call`. A
                                 * plain `call` is an unsafe replacement for a function call: use this
                                 * function instead.
                                 *
                                 * If `target` reverts with a revert reason or custom error, it is bubbled
                                 * up by this function (like regular Solidity function calls). However, if
                                 * the call reverted with no returned reason, this function reverts with a
                                 * {FailedInnerCall} error.
                                 *
                                 * Returns the raw returned data. To convert to the expected return value,
                                 * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                                 *
                                 * Requirements:
                                 *
                                 * - `target` must be a contract.
                                 * - calling `target` with `data` must not revert.
                                 */
                                function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                                    return functionCallWithValue(target, data, 0);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but also transferring `value` wei to `target`.
                                 *
                                 * Requirements:
                                 *
                                 * - the calling contract must have an ETH balance of at least `value`.
                                 * - the called Solidity function must be `payable`.
                                 */
                                function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                                    if (address(this).balance < value) {
                                        revert AddressInsufficientBalance(address(this));
                                    }
                                    (bool success, bytes memory returndata) = target.call{value: value}(data);
                                    return verifyCallResultFromTarget(target, success, returndata);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but performing a static call.
                                 */
                                function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                                    (bool success, bytes memory returndata) = target.staticcall(data);
                                    return verifyCallResultFromTarget(target, success, returndata);
                                }
                                /**
                                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                                 * but performing a delegate call.
                                 */
                                function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                                    (bool success, bytes memory returndata) = target.delegatecall(data);
                                    return verifyCallResultFromTarget(target, success, returndata);
                                }
                                /**
                                 * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
                                 * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
                                 * unsuccessful call.
                                 */
                                function verifyCallResultFromTarget(
                                    address target,
                                    bool success,
                                    bytes memory returndata
                                ) internal view returns (bytes memory) {
                                    if (!success) {
                                        _revert(returndata);
                                    } else {
                                        // only check if target is a contract if the call was successful and the return data is empty
                                        // otherwise we already know that it was a contract
                                        if (returndata.length == 0 && target.code.length == 0) {
                                            revert AddressEmptyCode(target);
                                        }
                                        return returndata;
                                    }
                                }
                                /**
                                 * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
                                 * revert reason or with a default {FailedInnerCall} error.
                                 */
                                function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
                                    if (!success) {
                                        _revert(returndata);
                                    } else {
                                        return returndata;
                                    }
                                }
                                /**
                                 * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
                                 */
                                function _revert(bytes memory returndata) private pure {
                                    // Look for revert reason and bubble it up if present
                                    if (returndata.length > 0) {
                                        // The easiest way to bubble the revert reason is using memory via assembly
                                        /// @solidity memory-safe-assembly
                                        assembly {
                                            let returndata_size := mload(returndata)
                                            revert(add(32, returndata), returndata_size)
                                        }
                                    } else {
                                        revert FailedInnerCall();
                                    }
                                }
                            }
                            // SPDX-License-Identifier: MIT
                            // OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
                            // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
                            pragma solidity ^0.8.20;
                            /**
                             * @dev Library for reading and writing primitive types to specific storage slots.
                             *
                             * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
                             * This library helps with reading and writing to such slots without the need for inline assembly.
                             *
                             * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
                             *
                             * Example usage to set ERC1967 implementation slot:
                             * ```solidity
                             * contract ERC1967 {
                             *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                             *
                             *     function _getImplementation() internal view returns (address) {
                             *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                             *     }
                             *
                             *     function _setImplementation(address newImplementation) internal {
                             *         require(newImplementation.code.length > 0);
                             *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                             *     }
                             * }
                             * ```
                             */
                            library StorageSlot {
                                struct AddressSlot {
                                    address value;
                                }
                                struct BooleanSlot {
                                    bool value;
                                }
                                struct Bytes32Slot {
                                    bytes32 value;
                                }
                                struct Uint256Slot {
                                    uint256 value;
                                }
                                struct StringSlot {
                                    string value;
                                }
                                struct BytesSlot {
                                    bytes value;
                                }
                                /**
                                 * @dev Returns an `AddressSlot` with member `value` located at `slot`.
                                 */
                                function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
                                 */
                                function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
                                 */
                                function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
                                 */
                                function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `StringSlot` with member `value` located at `slot`.
                                 */
                                function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
                                 */
                                function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := store.slot
                                    }
                                }
                                /**
                                 * @dev Returns an `BytesSlot` with member `value` located at `slot`.
                                 */
                                function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := slot
                                    }
                                }
                                /**
                                 * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
                                 */
                                function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        r.slot := store.slot
                                    }
                                }
                            }