ETH Price: $2,496.99 (-0.77%)

Transaction Decoder

Block:
19717960 at Apr-23-2024 12:01:11 PM +UTC
Transaction Fee:
0.0022009452507128 ETH $5.50
Gas Used:
194,050 Gas / 11.342155376 Gwei

Emitted Events:

303 DSProxy.0x1cff79cd00000000000000000000000000000000000000000000000000000000( 0x1cff79cd00000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000004dd3374addfb403ac7c641b21dab3541f6260d2c, 0x0000000000000000000000005dcd9dc0185a6ab07a31e5284d16ce9f0a79ce99, 0x0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000004000000000, 000000000000000000000000000000000000000000000000000001641cff79cd, 0000000000000000000000005dcd9dc0185a6ab07a31e5284d16ce9f0a79ce99, 0000000000000000000000000000000000000000000000000000000000000040, 00000000000000000000000000000000000000000000000000000000000000e4, 389f87ff00000000000000000000000000000000000000000000000000000000, 0000002000000000000000000000000000000000000000000000000000000000, 000000a000000000000000000000000000000000000000000000000000000000, 0000002000000000000000000000000000000000000000000000000000000000, 00000060ffffffffffffffffffffffffffffffffffffffffffffffffffffffff, ffffffff0000000000000000000000004dd3374addfb403ac7c641b21dab3541, f6260d2c00000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000 )
304 InitializableAdminUpgradeabilityProxy.0x5777ca300dfe5bead41006fbce4389794dbc0ed8d6cccebfaf94630aa04184bc( 0x5777ca300dfe5bead41006fbce4389794dbc0ed8d6cccebfaf94630aa04184bc, 0x0000000000000000000000004da27a545c0c5b758a6ba100e3a049001de870f5, 0000000000000000000000000000000000000000000000016a09a6c5e2548e51 )
305 InitializableAdminUpgradeabilityProxy.0xbb123b5c06d5408bbea3c4fef481578175cfb432e3b482c6186f02ed9086585b( 0xbb123b5c06d5408bbea3c4fef481578175cfb432e3b482c6186f02ed9086585b, 0x0000000000000000000000004dd3374addfb403ac7c641b21dab3541f6260d2c, 0x0000000000000000000000004da27a545c0c5b758a6ba100e3a049001de870f5, 0000000000000000000000000000000000000000000000016a09a6c5e2548e51 )
306 InitializableAdminUpgradeabilityProxy.0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925( 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, 0x000000000000000000000000d784927ff2f95ba542bfc824c8a8a98f3495f6b5, 0x0000000000000000000000004da27a545c0c5b758a6ba100e3a049001de870f5, ffffffffffffffffffffffffffffffffffffffffffff77918ac5241351ab2664 )
307 InitializableAdminUpgradeabilityProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000d784927ff2f95ba542bfc824c8a8a98f3495f6b5, 0x0000000000000000000000004da27a545c0c5b758a6ba100e3a049001de870f5, 0000000000000000000000000000000000000000000000006ba8bc0ee8226211 )
308 InitializableAdminUpgradeabilityProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000004dd3374addfb403ac7c641b21dab3541f6260d2c, 0000000000000000000000000000000000000000000000006ba8bc0ee8226211 )
309 InitializableAdminUpgradeabilityProxy.0x6c86f3fd5118b3aa8bb4f389a617046de0a3d3d477de1a1673d227f802f616dc( 0x6c86f3fd5118b3aa8bb4f389a617046de0a3d3d477de1a1673d227f802f616dc, 0x000000000000000000000000d784927ff2f95ba542bfc824c8a8a98f3495f6b5, 0x0000000000000000000000004dd3374addfb403ac7c641b21dab3541f6260d2c, 0000000000000000000000000000000000000000000000006ba8bc0ee8226211, 0000000000000000000000000000000000000000000000006ba8bc0ee8226211 )
310 InitializableImmutableAdminUpgradeabilityProxy.0x5637d7f962248a7f05a7ab69eec6446e31f3d0a299d997f135a65c62806e7891( 0x5637d7f962248a7f05a7ab69eec6446e31f3d0a299d997f135a65c62806e7891, 0x000000000000000000000000ad5ca20190463c276ef9f02d08c40a7c523aff13, 0x0000000000000000000000004dd3374addfb403ac7c641b21dab3541f6260d2c, 0x000000000000000000000000ad5ca20190463c276ef9f02d08c40a7c523aff13, 0000000000000000000000000000000000000000000000006ba8bc0ee8226211 )
311 DefisaverLogger.ActionDirectEvent( 0xf28c1e8e1a8c97027796e625e1ed041028c9642e14da6e7ad2c18838a59a2d8c, 0x000000000000000000000000ad5ca20190463c276ef9f02d08c40a7c523aff13, 0xd93d7e7fa20ffcee30e32fa65599072c2627c31ad298f5e50a24dea1e5ca652f, 0000000000000000000000000000000000000000000000000000000000000020, 00000000000000000000000000000000000000000000000000000000000000c0, 0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000006ba8bc0ee8226211, 0000000000000000000000000000000000000000000000000000000000000060, ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0000000000000000000000004dd3374addfb403ac7c641b21dab3541f6260d2c, 0000000000000000000000000000000000000000000000000000000000000000 )

Account State Difference:

  Address   Before After State Difference Code
3.141369973050559149 Eth3.141466998050559149 Eth0.000097025
0x4da27a54...01de870f5
0x4dD3374a...1F6260D2C
3.674333033475868125 Eth
Nonce: 201
3.672132088225155325 Eth
Nonce: 202
0.0022009452507128
0x7Fc66500...33E2DDaE9
0xd784927F...F3495f6b5
(Aave: Incentives Controller)

Execution Trace

DSProxy.execute( _target=0x5Dcd9Dc0185a6Ab07a31e5284D16Ce9f0A79Ce99, _data=0x389F87FF000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000A000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000004DD3374ADDFB403AC7C641B21DAB3541F6260D2C0000000000000000000000000000000000000000000000000000000000000000 ) => ( response=0000000000000000000000000000000000000000000000000000000000000000 )
  • AaveClaimStkAave.executeActionDirect( _callData=0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000004DD3374ADDFB403AC7C641B21DAB3541F6260D2C0000000000000000000000000000000000000000000000000000000000000000 )
    • InitializableImmutableAdminUpgradeabilityProxy.3111e7b3( )
      • StakedTokenIncentivesController.claimRewards( assets=[], amount=115792089237316195423570985008687907853269984665640564039457584007913129639935, to=0x4dD3374aDDFb403AC7c641b21daB3541F6260D2C ) => ( 7757657130355810833 )
        • InitializableAdminUpgradeabilityProxy.adc9772e( )
          • StakedAaveV3.stake( to=0x4dD3374aDDFb403AC7c641b21daB3541F6260D2C, amount=7757657130355810833 )
            • InitializableAdminUpgradeabilityProxy.23b872dd( )
            • InitializableImmutableAdminUpgradeabilityProxy.5b9c4cf1( )
            • DefisaverLogger.logActionDirectEvent( _logName=AaveClaimStkAave, _data=0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000006BA8BC0EE82262110000000000000000000000000000000000000000000000000000000000000060FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000004DD3374ADDFB403AC7C641B21DAB3541F6260D2C0000000000000000000000000000000000000000000000000000000000000000 )
              File 1 of 9: DSProxy
              // proxy.sol - execute actions atomically through the proxy's identity
              
              // Copyright (C) 2017  DappHub, LLC
              
              // 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.4.23;
              
              contract DSAuthority {
                  function canCall(
                      address src, address dst, bytes4 sig
                  ) public view returns (bool);
              }
              
              contract DSAuthEvents {
                  event LogSetAuthority (address indexed authority);
                  event LogSetOwner     (address indexed owner);
              }
              
              contract DSAuth is DSAuthEvents {
                  DSAuthority  public  authority;
                  address      public  owner;
              
                  constructor() public {
                      owner = msg.sender;
                      emit LogSetOwner(msg.sender);
                  }
              
                  function setOwner(address owner_)
                      public
                      auth
                  {
                      owner = owner_;
                      emit LogSetOwner(owner);
                  }
              
                  function setAuthority(DSAuthority authority_)
                      public
                      auth
                  {
                      authority = authority_;
                      emit LogSetAuthority(authority);
                  }
              
                  modifier auth {
                      require(isAuthorized(msg.sender, msg.sig));
                      _;
                  }
              
                  function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
                      if (src == address(this)) {
                          return true;
                      } else if (src == owner) {
                          return true;
                      } else if (authority == DSAuthority(0)) {
                          return false;
                      } else {
                          return authority.canCall(src, this, sig);
                      }
                  }
              }
              
              contract DSNote {
                  event LogNote(
                      bytes4   indexed  sig,
                      address  indexed  guy,
                      bytes32  indexed  foo,
                      bytes32  indexed  bar,
                      uint              wad,
                      bytes             fax
                  ) anonymous;
              
                  modifier note {
                      bytes32 foo;
                      bytes32 bar;
              
                      assembly {
                          foo := calldataload(4)
                          bar := calldataload(36)
                      }
              
                      emit LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data);
              
                      _;
                  }
              }
              
              // DSProxy
              // Allows code execution using a persistant identity This can be very
              // useful to execute a sequence of atomic actions. Since the owner of
              // the proxy can be changed, this allows for dynamic ownership models
              // i.e. a multisig
              contract DSProxy is DSAuth, DSNote {
                  DSProxyCache public cache;  // global cache for contracts
              
                  constructor(address _cacheAddr) public {
                      require(setCache(_cacheAddr));
                  }
              
                  function() public payable {
                  }
              
                  // use the proxy to execute calldata _data on contract _code
                  function execute(bytes _code, bytes _data)
                      public
                      payable
                      returns (address target, bytes32 response)
                  {
                      target = cache.read(_code);
                      if (target == 0x0) {
                          // deploy contract & store its address in cache
                          target = cache.write(_code);
                      }
              
                      response = execute(target, _data);
                  }
              
                  function execute(address _target, bytes _data)
                      public
                      auth
                      note
                      payable
                      returns (bytes32 response)
                  {
                      require(_target != 0x0);
              
                      // call contract in current context
                      assembly {
                          let succeeded := delegatecall(sub(gas, 5000), _target, add(_data, 0x20), mload(_data), 0, 32)
                          response := mload(0)      // load delegatecall output
                          switch iszero(succeeded)
                          case 1 {
                              // throw if delegatecall failed
                              revert(0, 0)
                          }
                      }
                  }
              
                  //set new cache
                  function setCache(address _cacheAddr)
                      public
                      auth
                      note
                      returns (bool)
                  {
                      require(_cacheAddr != 0x0);        // invalid cache address
                      cache = DSProxyCache(_cacheAddr);  // overwrite cache
                      return true;
                  }
              }
              
              // DSProxyFactory
              // This factory deploys new proxy instances through build()
              // Deployed proxy addresses are logged
              contract DSProxyFactory {
                  event Created(address indexed sender, address indexed owner, address proxy, address cache);
                  mapping(address=>bool) public isProxy;
                  DSProxyCache public cache = new DSProxyCache();
              
                  // deploys a new proxy instance
                  // sets owner of proxy to caller
                  function build() public returns (DSProxy proxy) {
                      proxy = build(msg.sender);
                  }
              
                  // deploys a new proxy instance
                  // sets custom owner of proxy
                  function build(address owner) public returns (DSProxy proxy) {
                      proxy = new DSProxy(cache);
                      emit Created(msg.sender, owner, address(proxy), address(cache));
                      proxy.setOwner(owner);
                      isProxy[proxy] = true;
                  }
              }
              
              // DSProxyCache
              // This global cache stores addresses of contracts previously deployed
              // by a proxy. This saves gas from repeat deployment of the same
              // contracts and eliminates blockchain bloat.
              
              // By default, all proxies deployed from the same factory store
              // contracts in the same cache. The cache a proxy instance uses can be
              // changed.  The cache uses the sha3 hash of a contract's bytecode to
              // lookup the address
              contract DSProxyCache {
                  mapping(bytes32 => address) cache;
              
                  function read(bytes _code) public view returns (address) {
                      bytes32 hash = keccak256(_code);
                      return cache[hash];
                  }
              
                  function write(bytes _code) public returns (address target) {
                      assembly {
                          target := create(0, add(_code, 0x20), mload(_code))
                          switch iszero(extcodesize(target))
                          case 1 {
                              // throw if contract failed to deploy
                              revert(0, 0)
                          }
                      }
                      bytes32 hash = keccak256(_code);
                      cache[hash] = target;
                  }
              }

              File 2 of 9: InitializableAdminUpgradeabilityProxy
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              pragma experimental ABIEncoderV2;
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              interface IAaveDistributionManager {
                function configureAssets(DistributionTypes.AssetConfigInput[] calldata assetsConfigInput) external;
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              pragma experimental ABIEncoderV2;
              library DistributionTypes {
                struct AssetConfigInput {
                  uint128 emissionPerSecond;
                  uint256 totalStaked;
                  address underlyingAsset;
                }
                struct UserStakeInput {
                  address underlyingAsset;
                  uint256 stakedByUser;
                  uint256 totalStaked;
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              pragma experimental ABIEncoderV2;
              interface IAaveIncentivesController {
                function handleAction(
                  address asset,
                  uint256 userBalance,
                  uint256 totalSupply
                ) external;
                function getRewardsBalance(address[] calldata assets, address user)
                  external
                  view
                  returns (uint256);
                function claimRewards(
                  address[] calldata assets,
                  uint256 amount,
                  address to,
                  bool stake
                ) external returns (uint256);
              }
              pragma solidity ^0.6.12;
              interface IAToken {
                function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.6.12;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               * From https://github.com/OpenZeppelin/openzeppelin-contracts
               */
              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: agpl-3.0
              pragma solidity 0.6.12;
              import {IERC20} from './IERC20.sol';
              /**
               * @dev Interface for ERC20 including metadata
               **/
              interface IERC20Detailed is IERC20 {
                  function name() external view returns (string memory);
                  function symbol() external view returns (string memory);
                  function decimals() external view returns (uint8);
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              interface IStakedAave {
                function stake(address to, uint256 amount) external;
                function redeem(address to, uint256 amount) external;
                function cooldown() external;
                function claimRewards(address to, uint256 amount) external;
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              interface ITransferHook {
                  function onTransfer(address from, address to, uint256 amount) external;
              }// SPDX-License-Identifier: MIT
              pragma solidity 0.6.12;
              /**
               * @dev Collection of functions related to the address type
               * From https://github.com/OpenZeppelin/openzeppelin-contracts
               */
              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) {
                      // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                      // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                      // for accounts without code, i.e. `keccak256('')`
                      bytes32 codehash;
                      bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          codehash := extcodehash(account)
                      }
                      return (codehash != accountHash && codehash != 0x0);
                  }
                  /**
                   * @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');
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.6.12;
              import './UpgradeabilityProxy.sol';
              /**
               * @title BaseAdminUpgradeabilityProxy
               * @dev From https://github.com/OpenZeppelin/openzeppelin-sdk/tree/solc-0.6/packages/lib/contracts/upgradeability 
               * 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 BaseAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
                  /**
                   * @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 "eip1967.proxy.admin" subtracted by 1, and is
                   * validated in the constructor.
                   */
                  bytes32
                      internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                  /**
                   * @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();
                      }
                  }
                  /**
                   * @return The address of the proxy admin.
                   */
                  function admin() external ifAdmin returns (address) {
                      return _admin();
                  }
                  /**
                   * @return The address of the implementation.
                   */
                  function implementation() external 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/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                   */
                  function upgradeToAndCall(address newImplementation, bytes calldata data)
                      external
                      payable
                      ifAdmin
                  {
                      _upgradeTo(newImplementation);
                      (bool success, ) = newImplementation.delegatecall(data);
                      require(success);
                  }
                  /**
                   * @return adm 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 virtual override {
                      require(msg.sender != _admin(), 'Cannot call fallback function from the proxy admin');
                      super._willFallback();
                  }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              import './BaseUpgradeabilityProxy.sol';
              /**
               * @title UpgradeabilityProxy
               * @dev From https://github.com/OpenZeppelin/openzeppelin-sdk/tree/solc-0.6/packages/lib/contracts/upgradeability
               * Extends BaseUpgradeabilityProxy with a constructor for initializing
               * implementation and init data.
               */
              contract UpgradeabilityProxy is BaseUpgradeabilityProxy {
                  /**
                   * @dev Contract constructor.
                   * @param _logic Address of the initial implementation.
                   * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                   * It should include the signature and the parameters of the function to be called, as described in
                   * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                   * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                   */
                  constructor(address _logic, bytes memory _data) public payable {
                      assert(
                          IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
                      );
                      _setImplementation(_logic);
                      if (_data.length > 0) {
                          (bool success, ) = _logic.delegatecall(_data);
                          require(success);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.6.12;
              import './Proxy.sol';
              import './Address.sol';
              /**
               * @title BaseUpgradeabilityProxy
               * @dev From https://github.com/OpenZeppelin/openzeppelin-sdk/tree/solc-0.6/packages/lib/contracts/upgradeability
               * 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 BaseUpgradeabilityProxy is Proxy {
                  /**
                   * @dev Emitted when the implementation is upgraded.
                   * @param implementation Address of the new implementation.
                   */
                  event Upgraded(address indexed implementation);
                  /**
                   * @dev Storage slot with the address of the current implementation.
                   * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                   * validated in the constructor.
                   */
                  bytes32
                      internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                  /**
                   * @dev Returns the current implementation.
                   * @return impl Address of the current implementation
                   */
                  function _implementation() internal override 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) internal {
                      require(
                          Address.isContract(newImplementation),
                          'Cannot set a proxy implementation to a non-contract address'
                      );
                      bytes32 slot = IMPLEMENTATION_SLOT;
                      assembly {
                          sstore(slot, newImplementation)
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.6.12;
              /**
               * @title Proxy
               * @dev From https://github.com/OpenZeppelin/openzeppelin-sdk/tree/solc-0.6/packages/lib/contracts/upgradeability
               * 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.
               */
              abstract contract Proxy {
                  /**
                   * @dev Fallback function.
                   * Implemented entirely in `_fallback`.
                   */
                  fallback() external payable {
                      _fallback();
                  }
                  /**
                   * @return The Address of the implementation.
                   */
                  function _implementation() internal virtual 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 virtual {}
                  /**
                   * @dev fallback implementation.
                   * Extracted to enable manual triggering.
                   */
                  function _fallback() internal {
                      _willFallback();
                      _delegate(_implementation());
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.6.12;
              /**
               * @dev From https://github.com/OpenZeppelin/openzeppelin-contracts
               * Provides information about the current execution context, including the
               * sender of the transaction and its data. While these are generally available
               * via msg.sender and msg.data, they should not be accessed in such a direct
               * manner, since when dealing with GSN meta-transactions the account sending and
               * paying for execution may not be the actual sender (as far as an application
               * is concerned).
               *
               * This contract is only required for intermediate, library-like contracts.
               */
              abstract contract Context {
                  function _msgSender() internal virtual view returns (address payable) {
                      return msg.sender;
                  }
                  function _msgData() internal virtual view returns (bytes memory) {
                      this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                      return msg.data;
                  }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              import {Context} from './Context.sol';
              import {IERC20} from '../interfaces/IERC20.sol';
              import {IERC20Detailed} from '../interfaces/IERC20Detailed.sol';
              import {SafeMath} from './SafeMath.sol';
              /**
               * @title ERC20
               * @notice Basic ERC20 implementation
               * @author Aave
               **/
              contract ERC20 is Context, IERC20, IERC20Detailed {
                using SafeMath for uint256;
                mapping(address => uint256) private _balances;
                mapping(address => mapping(address => uint256)) private _allowances;
                uint256 private _totalSupply;
                string private _name;
                string private _symbol;
                uint8 private _decimals;
                constructor(
                  string memory name,
                  string memory symbol,
                  uint8 decimals
                ) public {
                  _name = name;
                  _symbol = symbol;
                  _decimals = decimals;
                }
                /**
                 * @return the name of the token
                 **/
                function name() public override view returns (string memory) {
                  return _name;
                }
                /**
                 * @return the symbol of the token
                 **/
                function symbol() public override view returns (string memory) {
                  return _symbol;
                }
                /**
                 * @return the decimals of the token
                 **/
                function decimals() public override view returns (uint8) {
                  return _decimals;
                }
                /**
                 * @return the total supply of the token
                 **/
                function totalSupply() public override view returns (uint256) {
                  return _totalSupply;
                }
                /**
                 * @return the balance of the token
                 **/
                function balanceOf(address account) public override view returns (uint256) {
                  return _balances[account];
                }
                /**
                 * @dev executes a transfer of tokens from msg.sender to recipient
                 * @param recipient the recipient of the tokens
                 * @param amount the amount of tokens being transferred
                 * @return true if the transfer succeeds, false otherwise
                 **/
                function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
                  _transfer(_msgSender(), recipient, amount);
                  return true;
                }
                /**
                 * @dev returns the allowance of spender on the tokens owned by owner
                 * @param owner the owner of the tokens
                 * @param spender the user allowed to spend the owner's tokens
                 * @return the amount of owner's tokens spender is allowed to spend
                 **/
                function allowance(address owner, address spender)
                  public
                  virtual
                  override
                  view
                  returns (uint256)
                {
                  return _allowances[owner][spender];
                }
                /**
                 * @dev allows spender to spend the tokens owned by msg.sender
                 * @param spender the user allowed to spend msg.sender tokens
                 * @return true
                 **/
                function approve(address spender, uint256 amount) public virtual override returns (bool) {
                  _approve(_msgSender(), spender, amount);
                  return true;
                }
                /**
                 * @dev executes a transfer of token from sender to recipient, if msg.sender is allowed to do so
                 * @param sender the owner of the tokens
                 * @param recipient the recipient of the tokens
                 * @param amount the amount of tokens being transferred
                 * @return true if the transfer succeeds, false otherwise
                 **/
                function transferFrom(
                  address sender,
                  address recipient,
                  uint256 amount
                ) public virtual override returns (bool) {
                  _transfer(sender, recipient, amount);
                  _approve(
                    sender,
                    _msgSender(),
                    _allowances[sender][_msgSender()].sub(amount, 'ERC20: transfer amount exceeds allowance')
                  );
                  return true;
                }
                /**
                 * @dev increases the allowance of spender to spend msg.sender tokens
                 * @param spender the user allowed to spend on behalf of msg.sender
                 * @param addedValue the amount being added to the allowance
                 * @return true
                 **/
                function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                  _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
                  return true;
                }
                /**
                 * @dev decreases the allowance of spender to spend msg.sender tokens
                 * @param spender the user allowed to spend on behalf of msg.sender
                 * @param subtractedValue the amount being subtracted to the allowance
                 * @return true
                 **/
                function decreaseAllowance(address spender, uint256 subtractedValue)
                  public
                  virtual
                  returns (bool)
                {
                  _approve(
                    _msgSender(),
                    spender,
                    _allowances[_msgSender()][spender].sub(
                      subtractedValue,
                      'ERC20: decreased allowance below zero'
                    )
                  );
                  return true;
                }
                function _transfer(
                  address sender,
                  address recipient,
                  uint256 amount
                ) internal virtual {
                  require(sender != address(0), 'ERC20: transfer from the zero address');
                  require(recipient != address(0), 'ERC20: transfer to the zero address');
                  _beforeTokenTransfer(sender, recipient, amount);
                  _balances[sender] = _balances[sender].sub(amount, 'ERC20: transfer amount exceeds balance');
                  _balances[recipient] = _balances[recipient].add(amount);
                  emit Transfer(sender, recipient, amount);
                }
                function _mint(address account, uint256 amount) internal virtual {
                  require(account != address(0), 'ERC20: mint to the zero address');
                  _beforeTokenTransfer(address(0), account, amount);
                  _totalSupply = _totalSupply.add(amount);
                  _balances[account] = _balances[account].add(amount);
                  emit Transfer(address(0), account, amount);
                }
                function _burn(address account, uint256 amount) internal virtual {
                  require(account != address(0), 'ERC20: burn from the zero address');
                  _beforeTokenTransfer(account, address(0), amount);
                  _balances[account] = _balances[account].sub(amount, 'ERC20: burn amount exceeds balance');
                  _totalSupply = _totalSupply.sub(amount);
                  emit Transfer(account, address(0), amount);
                }
                function _approve(
                  address owner,
                  address spender,
                  uint256 amount
                ) internal virtual {
                  require(owner != address(0), 'ERC20: approve from the zero address');
                  require(spender != address(0), 'ERC20: approve to the zero address');
                  _allowances[owner][spender] = amount;
                  emit Approval(owner, spender, amount);
                }
                function _setName(string memory newName) internal {
                  _name = newName;
                }
                function _setSymbol(string memory newSymbol) internal {
                  _symbol = newSymbol;
                }
                function _setDecimals(uint8 newDecimals) internal {
                  _decimals = newDecimals;
                }
                function _beforeTokenTransfer(
                  address from,
                  address to,
                  uint256 amount
                ) internal virtual {}
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              /**
               * @dev From https://github.com/OpenZeppelin/openzeppelin-contracts
               * 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, 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) {
                      return sub(a, b, 'SafeMath: subtraction overflow');
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                   * overflow (when the result is negative).
                   *
                   * 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);
                      uint256 c = a - b;
                      return c;
                  }
                  /**
                   * @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) {
                      // 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 0;
                      }
                      uint256 c = a * b;
                      require(c / a == b, 'SafeMath: multiplication overflow');
                      return c;
                  }
                  /**
                   * @dev Returns the integer division of two unsigned integers. Reverts 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) {
                      return div(a, b, 'SafeMath: division by zero');
                  }
                  /**
                   * @dev Returns the integer division of two unsigned integers. Reverts with custom message 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,
                      string memory errorMessage
                  ) internal pure returns (uint256) {
                      // Solidity only automatically asserts when dividing by 0
                      require(b > 0, errorMessage);
                      uint256 c = a / b;
                      // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                      return c;
                  }
                  /**
                   * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                   * Reverts 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) {
                      return mod(a, b, 'SafeMath: modulo by zero');
                  }
                  /**
                   * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                   * Reverts with custom message 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,
                      string memory errorMessage
                  ) internal pure returns (uint256) {
                      require(b != 0, errorMessage);
                      return a % b;
                  }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              import {ERC20} from '../lib/ERC20.sol';
              import {ITransferHook} from '../interfaces/ITransferHook.sol';
              /**
               * @title ERC20WithSnapshot
               * @notice ERC20 including snapshots of balances on transfer-related actions
               * @author Aave
               **/
              contract ERC20WithSnapshot is ERC20 {
                  /// @dev snapshot of a value on a specific block, used for balances
                  struct Snapshot {
                      uint128 blockNumber;
                      uint128 value;
                  }
                  mapping (address => mapping (uint256 => Snapshot)) public _snapshots;
                  mapping (address => uint256) public _countsSnapshots;
                  /// @dev reference to the Aave governance contract to call (if initialized) on _beforeTokenTransfer
                  /// !!! IMPORTANT The Aave governance is considered a trustable contract, being its responsibility
                  /// to control all potential reentrancies by calling back the this contract
                  ITransferHook public _aaveGovernance;
                  event SnapshotDone(address owner, uint128 oldValue, uint128 newValue);
                  constructor(string memory name, string memory symbol, uint8 decimals) public ERC20(name, symbol, decimals) {}
                  function _setAaveGovernance(ITransferHook aaveGovernance) internal virtual {
                      _aaveGovernance = aaveGovernance;
                  }
                  /**
                  * @dev Writes a snapshot for an owner of tokens
                  * @param owner The owner of the tokens
                  * @param oldValue The value before the operation that is gonna be executed after the snapshot
                  * @param newValue The value after the operation
                  */
                  function _writeSnapshot(address owner, uint128 oldValue, uint128 newValue) internal virtual {
                      uint128 currentBlock = uint128(block.number);
                      uint256 ownerCountOfSnapshots = _countsSnapshots[owner];
                      mapping (uint256 => Snapshot) storage snapshotsOwner = _snapshots[owner];
                      // Doing multiple operations in the same block
                      if (ownerCountOfSnapshots != 0 && snapshotsOwner[ownerCountOfSnapshots.sub(1)].blockNumber == currentBlock) {
                          snapshotsOwner[ownerCountOfSnapshots.sub(1)].value = newValue;
                      } else {
                          snapshotsOwner[ownerCountOfSnapshots] = Snapshot(currentBlock, newValue);
                          _countsSnapshots[owner] = ownerCountOfSnapshots.add(1);
                      }
                      emit SnapshotDone(owner, oldValue, newValue);
                  }
                  /**
                  * @dev Writes a snapshot before any operation involving transfer of value: _transfer, _mint and _burn
                  * - On _transfer, it writes snapshots for both "from" and "to"
                  * - On _mint, only for _to
                  * - On _burn, only for _from
                  * @param from the from address
                  * @param to the to address
                  * @param amount the amount to transfer
                  */
                  function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
                      if (from == to) {
                          return;
                      }
                      if (from != address(0)) {
                          uint256 fromBalance = balanceOf(from);
                          _writeSnapshot(from, uint128(fromBalance), uint128(fromBalance.sub(amount)));
                      }
                      if (to != address(0)) {
                          uint256 toBalance = balanceOf(to);
                          _writeSnapshot(to, uint128(toBalance), uint128(toBalance.add(amount)));
                      }
                      // caching the aave governance address to avoid multiple state loads
                      ITransferHook aaveGovernance = _aaveGovernance;
                      if (aaveGovernance != ITransferHook(0)) {
                          aaveGovernance.onTransfer(from, to, amount);
                      }
                  }
              }// SPDX-License-Identifier: MIT
              pragma solidity 0.6.12;
              import './BaseAdminUpgradeabilityProxy.sol';
              import './InitializableUpgradeabilityProxy.sol';
              /**
               * @title InitializableAdminUpgradeabilityProxy
               * @dev From https://github.com/OpenZeppelin/openzeppelin-sdk/tree/solc-0.6/packages/lib/contracts/upgradeability 
               * Extends from BaseAdminUpgradeabilityProxy with an initializer for
               * initializing the implementation, admin, and init data.
               */
              contract InitializableAdminUpgradeabilityProxy is
                  BaseAdminUpgradeabilityProxy,
                  InitializableUpgradeabilityProxy
              {
                  /**
                   * Contract initializer.
                   * @param _logic address of the initial implementation.
                   * @param _admin Address of the proxy administrator.
                   * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                   * It should include the signature and the parameters of the function to be called, as described in
                   * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                   * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                   */
                  function initialize(
                      address _logic,
                      address _admin,
                      bytes memory _data
                  ) public payable {
                      require(_implementation() == address(0));
                      InitializableUpgradeabilityProxy.initialize(_logic, _data);
                      assert(ADMIN_SLOT == bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1));
                      _setAdmin(_admin);
                  }
                  /**
                   * @dev Only fall back when the sender is not the admin.
                   */
                  function _willFallback() internal override(BaseAdminUpgradeabilityProxy, Proxy) {
                      BaseAdminUpgradeabilityProxy._willFallback();
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.6.12;
              import './BaseUpgradeabilityProxy.sol';
              /**
               * @title InitializableUpgradeabilityProxy
               * @dev From https://github.com/OpenZeppelin/openzeppelin-sdk/tree/solc-0.6/packages/lib/contracts/upgradeability
               * Extends BaseUpgradeabilityProxy with an initializer for initializing
               * implementation and init data.
               */
              contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy {
                  /**
                   * @dev Contract initializer.
                   * @param _logic Address of the initial implementation.
                   * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                   * It should include the signature and the parameters of the function to be called, as described in
                   * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                   * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                   */
                  function initialize(address _logic, bytes memory _data) public payable {
                      require(_implementation() == address(0));
                      assert(
                          IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
                      );
                      _setImplementation(_logic);
                      if (_data.length > 0) {
                          (bool success, ) = _logic.delegatecall(_data);
                          require(success);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.6.12;
              import {IERC20} from "../interfaces/IERC20.sol";
              import {SafeMath} from "./SafeMath.sol";
              import {Address} from "./Address.sol";
              /**
               * @title SafeERC20
               * @dev From https://github.com/OpenZeppelin/openzeppelin-contracts
               * 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));
                  }
                  function safeApprove(IERC20 token, address spender, uint256 value) internal {
                      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 callOptionalReturn(IERC20 token, bytes memory data) private {
                      require(address(token).isContract(), "SafeERC20: call to non-contract");
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = address(token).call(data);
                      require(success, "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: agpl-3.0
              pragma solidity 0.6.12;
              pragma experimental ABIEncoderV2;
              import {IAaveIncentivesController} from '../interfaces/IAaveIncentivesController.sol';
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              import {IAToken} from '../interfaces/IAToken.sol';
              contract ATokenMock is IAToken {
                IAaveIncentivesController public _aic;
                uint256 internal _userBalance;
                uint256 internal _totalSupply;
                // hack to be able to test event from EI properly
                event RewardsAccrued(address indexed user, uint256 amount);
                // hack to be able to test event from Distribution manager properly
                event AssetConfigUpdated(address indexed asset, uint256 emission);
                event AssetIndexUpdated(address indexed asset, uint256 index);
                event UserIndexUpdated(address indexed user, address indexed asset, uint256 index);
                constructor(IAaveIncentivesController aic) public {
                  _aic = aic;
                }
                function handleActionOnAic(
                  address user,
                  uint256 userBalance,
                  uint256 totalSupply
                ) external {
                  _aic.handleAction(user, userBalance, totalSupply);
                }
                function setUserBalanceAndSupply(uint256 userBalance, uint256 totalSupply) public {
                  _userBalance = userBalance;
                  _totalSupply = totalSupply;
                }
                function getScaledUserBalanceAndSupply(address user)
                  external
                  override
                  view
                  returns (uint256, uint256)
                {
                  return (_userBalance, _totalSupply);
                }
                function cleanUserState() external {
                  _userBalance = 0;
                  _totalSupply = 0;
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              import {ITransferHook} from "../interfaces/ITransferHook.sol";
              contract MockTransferHook is ITransferHook {
                  event MockHookEvent();
                  function onTransfer(address from, address to, uint256 amount) external override {
                      emit MockHookEvent();
                  }
              }// SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              pragma experimental ABIEncoderV2;
              import {SafeMath} from '../lib/SafeMath.sol';
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              import {IAaveDistributionManager} from '../interfaces/IAaveDistributionManager.sol';
              /**
               * @title AaveDistributionManager
               * @notice Accounting contract to manage multiple staking distributions
               * @author Aave
               **/
              contract AaveDistributionManager is IAaveDistributionManager {
                using SafeMath for uint256;
                struct AssetData {
                  uint128 emissionPerSecond;
                  uint128 lastUpdateTimestamp;
                  uint256 index;
                  mapping(address => uint256) users;
                }
                uint256 public immutable DISTRIBUTION_END;
                address public immutable EMISSION_MANAGER;
                uint8 public constant PRECISION = 18;
                mapping(address => AssetData) public assets;
                event AssetConfigUpdated(address indexed asset, uint256 emission);
                event AssetIndexUpdated(address indexed asset, uint256 index);
                event UserIndexUpdated(address indexed user, address indexed asset, uint256 index);
                constructor(address emissionManager, uint256 distributionDuration) public {
                  DISTRIBUTION_END = block.timestamp.add(distributionDuration);
                  EMISSION_MANAGER = emissionManager;
                }
                /**
                 * @dev Configures the distribution of rewards for a list of assets
                 * @param assetsConfigInput The list of configurations to apply
                 **/
                function configureAssets(DistributionTypes.AssetConfigInput[] calldata assetsConfigInput) external override {
                  require(msg.sender == EMISSION_MANAGER, 'ONLY_EMISSION_MANAGER');
                  for (uint256 i = 0; i < assetsConfigInput.length; i++) {
                    AssetData storage assetConfig = assets[assetsConfigInput[i].underlyingAsset];
                    _updateAssetStateInternal(
                      assetsConfigInput[i].underlyingAsset,
                      assetConfig,
                      assetsConfigInput[i].totalStaked
                    );
                    assetConfig.emissionPerSecond = assetsConfigInput[i].emissionPerSecond;
                    emit AssetConfigUpdated(
                      assetsConfigInput[i].underlyingAsset,
                      assetsConfigInput[i].emissionPerSecond
                    );
                  }
                }
                /**
                 * @dev Updates the state of one distribution, mainly rewards index and timestamp
                 * @param underlyingAsset The address used as key in the distribution, for example sAAVE or the aTokens addresses on Aave
                 * @param assetConfig Storage pointer to the distribution's config
                 * @param totalStaked Current total of staked assets for this distribution
                 * @return The new distribution index
                 **/
                function _updateAssetStateInternal(
                  address underlyingAsset,
                  AssetData storage assetConfig,
                  uint256 totalStaked
                ) internal returns (uint256) {
                  uint256 oldIndex = assetConfig.index;
                  uint128 lastUpdateTimestamp = assetConfig.lastUpdateTimestamp;
                  if (block.timestamp == lastUpdateTimestamp) {
                    return oldIndex;
                  }
                  uint256 newIndex = _getAssetIndex(
                    oldIndex,
                    assetConfig.emissionPerSecond,
                    lastUpdateTimestamp,
                    totalStaked
                  );
                  if (newIndex != oldIndex) {
                    assetConfig.index = newIndex;
                    emit AssetIndexUpdated(underlyingAsset, newIndex);
                  }
                  assetConfig.lastUpdateTimestamp = uint128(block.timestamp);
                  return newIndex;
                }
                /**
                 * @dev Updates the state of an user in a distribution
                 * @param user The user's address
                 * @param asset The address of the reference asset of the distribution
                 * @param stakedByUser Amount of tokens staked by the user in the distribution at the moment
                 * @param totalStaked Total tokens staked in the distribution
                 * @return The accrued rewards for the user until the moment
                 **/
                function _updateUserAssetInternal(
                  address user,
                  address asset,
                  uint256 stakedByUser,
                  uint256 totalStaked
                ) internal returns (uint256) {
                  AssetData storage assetData = assets[asset];
                  uint256 userIndex = assetData.users[user];
                  uint256 accruedRewards = 0;
                  uint256 newIndex = _updateAssetStateInternal(asset, assetData, totalStaked);
                  if (userIndex != newIndex) {
                    if (stakedByUser != 0) {
                      accruedRewards = _getRewards(stakedByUser, newIndex, userIndex);
                    }
                    assetData.users[user] = newIndex;
                    emit UserIndexUpdated(user, asset, newIndex);
                  }
                  return accruedRewards;
                }
                /**
                 * @dev Used by "frontend" stake contracts to update the data of an user when claiming rewards from there
                 * @param user The address of the user
                 * @param stakes List of structs of the user data related with his stake
                 * @return The accrued rewards for the user until the moment
                 **/
                function _claimRewards(address user, DistributionTypes.UserStakeInput[] memory stakes)
                  internal
                  returns (uint256)
                {
                  uint256 accruedRewards = 0;
                  for (uint256 i = 0; i < stakes.length; i++) {
                    accruedRewards = accruedRewards.add(
                      _updateUserAssetInternal(
                        user,
                        stakes[i].underlyingAsset,
                        stakes[i].stakedByUser,
                        stakes[i].totalStaked
                      )
                    );
                  }
                  return accruedRewards;
                }
                /**
                 * @dev Return the accrued rewards for an user over a list of distribution
                 * @param user The address of the user
                 * @param stakes List of structs of the user data related with his stake
                 * @return The accrued rewards for the user until the moment
                 **/
                function _getUnclaimedRewards(address user, DistributionTypes.UserStakeInput[] memory stakes)
                  internal
                  view
                  returns (uint256)
                {
                  uint256 accruedRewards = 0;
                  for (uint256 i = 0; i < stakes.length; i++) {
                    AssetData storage assetConfig = assets[stakes[i].underlyingAsset];
                    uint256 assetIndex = _getAssetIndex(
                      assetConfig.index,
                      assetConfig.emissionPerSecond,
                      assetConfig.lastUpdateTimestamp,
                      stakes[i].totalStaked
                    );
                    accruedRewards = accruedRewards.add(
                      _getRewards(stakes[i].stakedByUser, assetIndex, assetConfig.users[user])
                    );
                  }
                  return accruedRewards;
                }
                /**
                 * @dev Internal function for the calculation of user's rewards on a distribution
                 * @param principalUserBalance Amount staked by the user on a distribution
                 * @param reserveIndex Current index of the distribution
                 * @param userIndex Index stored for the user, representation his staking moment
                 * @return The rewards
                 **/
                function _getRewards(
                  uint256 principalUserBalance,
                  uint256 reserveIndex,
                  uint256 userIndex
                ) internal pure returns (uint256) {
                  return principalUserBalance.mul(reserveIndex.sub(userIndex)).div(10**uint256(PRECISION));
                }
                /**
                 * @dev Calculates the next value of an specific distribution index, with validations
                 * @param currentIndex Current index of the distribution
                 * @param emissionPerSecond Representing the total rewards distributed per second per asset unit, on the distribution
                 * @param lastUpdateTimestamp Last moment this distribution was updated
                 * @param totalBalance of tokens considered for the distribution
                 * @return The new index.
                 **/
                function _getAssetIndex(
                  uint256 currentIndex,
                  uint256 emissionPerSecond,
                  uint128 lastUpdateTimestamp,
                  uint256 totalBalance
                ) internal view returns (uint256) {
                  if (
                    emissionPerSecond == 0 ||
                    totalBalance == 0 ||
                    lastUpdateTimestamp == block.timestamp ||
                    lastUpdateTimestamp >= DISTRIBUTION_END
                  ) {
                    return currentIndex;
                  }
                  uint256 currentTimestamp = block.timestamp > DISTRIBUTION_END
                    ? DISTRIBUTION_END
                    : block.timestamp;
                  uint256 timeDelta = currentTimestamp.sub(lastUpdateTimestamp);
                  return
                    emissionPerSecond.mul(timeDelta).mul(10**uint256(PRECISION)).div(totalBalance).add(
                      currentIndex
                    );
                }
                /**
                 * @dev Returns the data of an user on a distribution
                 * @param user Address of the user
                 * @param asset The address of the reference asset of the distribution
                 * @return The new index
                 **/
                function getUserAssetData(address user, address asset) public view returns (uint256) {
                  return assets[asset].users[user];
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              pragma experimental ABIEncoderV2;
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              import {IERC20} from '../interfaces/IERC20.sol';
              import {IAToken} from '../interfaces/IAToken.sol';
              import {IAaveIncentivesController} from '../interfaces/IAaveIncentivesController.sol';
              import {IStakedAave} from '../interfaces/IStakedAave.sol';
              import {VersionedInitializable} from '../utils/VersionedInitializable.sol';
              import {AaveDistributionManager} from './AaveDistributionManager.sol';
              /**
               * @title AaveIncentivesController
               * @notice Distributor contract for rewards to the Aave protocol
               * @author Aave
               **/
              contract AaveIncentivesController is
                IAaveIncentivesController,
                VersionedInitializable,
                AaveDistributionManager
              {
                uint256 public constant REVISION = 1;
                IStakedAave public immutable PSM;
                IERC20 public immutable REWARD_TOKEN;
                address public immutable REWARDS_VAULT;
                uint256 public immutable EXTRA_PSM_REWARD;
                mapping(address => uint256) internal _usersUnclaimedRewards;
                event RewardsAccrued(address indexed user, uint256 amount);
                event RewardsClaimed(address indexed user, address indexed to, uint256 amount);
                constructor(
                  IERC20 rewardToken,
                  address rewardsVault,
                  IStakedAave psm,
                  uint256 extraPsmReward,
                  address emissionManager,
                  uint128 distributionDuration
                ) public AaveDistributionManager(emissionManager, distributionDuration) {
                  REWARD_TOKEN = rewardToken;
                  REWARDS_VAULT = rewardsVault;
                  PSM = psm;
                  EXTRA_PSM_REWARD = extraPsmReward;
                }
                /**
                 * @dev Called by the proxy contract. Not used at the moment, but for the future
                 **/
                function initialize() external initializer {
                  // to unlock possibility to stake on behalf of the user
                  REWARD_TOKEN.approve(address(PSM), type(uint256).max);
                }
                /**
                 * @dev Called by the corresponding asset on any update that affects the rewards distribution
                 * @param user The address of the user
                 * @param userBalance The balance of the user of the asset in the lending pool
                 * @param totalSupply The total supply of the asset in the lending pool
                 **/
                function handleAction(
                  address user,
                  uint256 userBalance,
                  uint256 totalSupply
                ) external override {
                  uint256 accruedRewards = _updateUserAssetInternal(user, msg.sender, userBalance, totalSupply);
                  if (accruedRewards != 0) {
                    _usersUnclaimedRewards[user] = _usersUnclaimedRewards[user].add(accruedRewards);
                    emit RewardsAccrued(user, accruedRewards);
                  }
                }
                /**
                 * @dev Returns the total of rewards of an user, already accrued + not yet accrued
                 * @param user The address of the user
                 * @return The rewards
                 **/
                function getRewardsBalance(address[] calldata assets, address user)
                  external
                  override
                  view
                  returns (uint256)
                {
                  uint256 unclaimedRewards = _usersUnclaimedRewards[user];
                  DistributionTypes.UserStakeInput[] memory userState = new DistributionTypes.UserStakeInput[](
                    assets.length
                  );
                  for (uint256 i = 0; i < assets.length; i++) {
                    userState[i].underlyingAsset = assets[i];
                    (userState[i].stakedByUser, userState[i].totalStaked) = IAToken(assets[i])
                      .getScaledUserBalanceAndSupply(user);
                  }
                  unclaimedRewards = unclaimedRewards.add(_getUnclaimedRewards(user, userState));
                  return unclaimedRewards;
                }
                /**
                 * @dev Claims reward for an user, on all the assets of the lending pool, accumulating the pending rewards
                 * @param amount Amount of rewards to claim
                 * @param to Address that will be receiving the rewards
                 * @param stake Boolean flag to determined if the claimed rewards should be staked in the Safety Module or not
                 * @return Rewards claimed
                 **/
                function claimRewards(
                  address[] calldata assets,
                  uint256 amount,
                  address to,
                  bool stake
                ) external override returns (uint256) {
                  if (amount == 0) {
                    return 0;
                  }
                  address user = msg.sender;
                  uint256 unclaimedRewards = _usersUnclaimedRewards[user];
                  DistributionTypes.UserStakeInput[] memory userState = new DistributionTypes.UserStakeInput[](
                    assets.length
                  );
                  for (uint256 i = 0; i < assets.length; i++) {
                    userState[i].underlyingAsset = assets[i];
                    (userState[i].stakedByUser, userState[i].totalStaked) = IAToken(assets[i])
                      .getScaledUserBalanceAndSupply(user);
                  }
                  uint256 accruedRewards = _claimRewards(user, userState);
                  if (accruedRewards != 0) {
                    unclaimedRewards = unclaimedRewards.add(accruedRewards);
                    emit RewardsAccrued(user, accruedRewards);
                  }
                  if (unclaimedRewards == 0) {
                    return 0;
                  }
                  uint256 amountToClaim = amount > unclaimedRewards ? unclaimedRewards : amount;
                  _usersUnclaimedRewards[user] = unclaimedRewards - amountToClaim; // Safe due to the previous line
                  if (stake) {
                    amountToClaim = amountToClaim.add(amountToClaim.mul(EXTRA_PSM_REWARD).div(100));
                    REWARD_TOKEN.transferFrom(REWARDS_VAULT, address(this), amountToClaim);
                    PSM.stake(to, amountToClaim);
                  } else {
                    REWARD_TOKEN.transferFrom(REWARDS_VAULT, to, amountToClaim);
                  }
                  emit RewardsClaimed(msg.sender, to, amountToClaim);
                  return amountToClaim;
                }
                /**
                 * @dev returns the unclaimed rewards of the user
                 * @param _user the address of the user
                 * @return the unclaimed user rewards
                 */
                function getUserUnclaimedRewards(address _user) external view returns (uint256) {
                  return _usersUnclaimedRewards[_user];
                }
                /**
                 * @dev returns the revision of the implementation contract
                 */
                function getRevision() internal override pure returns (uint256) {
                  return REVISION;
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              /**
               * @title VersionedInitializable
               *
               * @dev Helper contract to support initializer functions. To use it, replace
               * the constructor with a function that has the `initializer` modifier.
               * WARNING: Unlike constructors, initializer functions must be manually
               * invoked. This applies both to deploying an Initializable contract, as well
               * as extending an Initializable contract via inheritance.
               * WARNING: When used with inheritance, manual care must be taken to not invoke
               * a parent initializer twice, or ensure that all initializers are idempotent,
               * because this is not dealt with automatically as with constructors.
               *
               * @author Aave, inspired by the OpenZeppelin Initializable contract
               */
              abstract contract VersionedInitializable {
                  /**
                   * @dev Indicates that the contract has been initialized.
                   */
                  uint256 internal lastInitializedRevision = 0;
                  /**
                   * @dev Modifier to use in the initializer function of a contract.
                   */
                  modifier initializer() {
                      uint256 revision = getRevision();
                      require(
                          revision > lastInitializedRevision,
                          'Contract instance has already been initialized'
                      );
                      lastInitializedRevision = revision;
                      _;
                  }
                  /// @dev returns the revision number of the contract.
                  /// Needs to be defined in the inherited class as a constant.
                  function getRevision() internal virtual pure returns (uint256);
                  // Reserved storage space to allow for layout changes in the future.
                  uint256[50] private ______gap;
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              pragma experimental ABIEncoderV2;
              import {IERC20} from '../interfaces/IERC20.sol';
              import {StakedToken} from './StakedToken.sol';
              /**
               * @title StakedAave
               * @notice StakedToken with AAVE token as staked token
               * @author Aave
               **/
              contract StakedAave is StakedToken {
                string internal constant NAME = 'Staked Aave';
                string internal constant SYMBOL = 'stkAAVE';
                uint8 internal constant DECIMALS = 18;
                
                constructor(
                  IERC20 stakedToken,
                  IERC20 rewardToken,
                  uint256 cooldownSeconds,
                  uint256 unstakeWindow,
                  address rewardsVault,
                  address emissionManager,
                  uint128 distributionDuration
                ) public StakedToken(
                  stakedToken,
                  rewardToken,
                  cooldownSeconds,
                  unstakeWindow,
                  rewardsVault,
                  emissionManager,
                  distributionDuration,
                  NAME,
                  SYMBOL,
                  DECIMALS) {}
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              pragma experimental ABIEncoderV2;
              import {IERC20} from '../interfaces/IERC20.sol';
              import {IStakedAave} from '../interfaces/IStakedAave.sol';
              import {ITransferHook} from '../interfaces/ITransferHook.sol';
              import {ERC20WithSnapshot} from '../lib/ERC20WithSnapshot.sol';
              import {SafeERC20} from '../lib/SafeERC20.sol';
              import {VersionedInitializable} from '../utils/VersionedInitializable.sol';
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              import {AaveDistributionManager} from './AaveDistributionManager.sol';
              /**
               * @title StakedToken
               * @notice Contract to stake Aave token, tokenize the position and get rewards, inheriting from a distribution manager contract
               * @author Aave
               **/
              contract StakedToken is IStakedAave, ERC20WithSnapshot, VersionedInitializable, AaveDistributionManager {
                using SafeERC20 for IERC20;
                uint256 public constant REVISION = 1;
                IERC20 public immutable STAKED_TOKEN;
                IERC20 public immutable REWARD_TOKEN;
                uint256 public immutable COOLDOWN_SECONDS;
                /// @notice Seconds available to redeem once the cooldown period is fullfilled
                uint256 public immutable UNSTAKE_WINDOW;
                /// @notice Address to pull from the rewards, needs to have approved this contract
                address public immutable REWARDS_VAULT;
                mapping(address => uint256) public stakerRewardsToClaim;
                mapping(address => uint256) public stakersCooldowns;
                event Staked(address indexed from, address indexed onBehalfOf, uint256 amount);
                event Redeem(address indexed from, address indexed to, uint256 amount);
                event RewardsAccrued(address user, uint256 amount);
                event RewardsClaimed(address indexed from, address indexed to, uint256 amount);
                event Cooldown(address indexed user);
                constructor(
                  IERC20 stakedToken,
                  IERC20 rewardToken,
                  uint256 cooldownSeconds,
                  uint256 unstakeWindow,
                  address rewardsVault,
                  address emissionManager,
                  uint128 distributionDuration,
                  string memory name,
                  string memory symbol,
                  uint8 decimals
                ) public ERC20WithSnapshot(name, symbol, decimals) AaveDistributionManager(emissionManager, distributionDuration) {
                  STAKED_TOKEN = stakedToken;
                  REWARD_TOKEN = rewardToken;
                  COOLDOWN_SECONDS = cooldownSeconds;
                  UNSTAKE_WINDOW = unstakeWindow;
                  REWARDS_VAULT = rewardsVault;
                }
                /**
                 * @dev Called by the proxy contract
                 **/
                function initialize(ITransferHook aaveGovernance, string calldata name, string calldata symbol, uint8 decimals) external initializer {
                  _setName(name);
                  _setSymbol(symbol);
                  _setDecimals(decimals);
                  _setAaveGovernance(aaveGovernance);
                }
                function stake(address onBehalfOf, uint256 amount) external override {
                  require(amount != 0, 'INVALID_ZERO_AMOUNT');
                  uint256 balanceOfUser = balanceOf(onBehalfOf);
                  uint256 accruedRewards = _updateUserAssetInternal(
                    onBehalfOf,
                    address(this),
                    balanceOfUser,
                    totalSupply()
                  );
                  if (accruedRewards != 0) {
                    emit RewardsAccrued(onBehalfOf, accruedRewards);
                    stakerRewardsToClaim[onBehalfOf] = stakerRewardsToClaim[onBehalfOf].add(accruedRewards);
                  }
                  stakersCooldowns[onBehalfOf] = getNextCooldownTimestamp(0, amount, onBehalfOf, balanceOfUser);
                  _mint(onBehalfOf, amount);
                  IERC20(STAKED_TOKEN).safeTransferFrom(msg.sender, address(this), amount);
                  emit Staked(msg.sender, onBehalfOf, amount);
                }
                /**
                 * @dev Redeems staked tokens, and stop earning rewards
                 * @param to Address to redeem to
                 * @param amount Amount to redeem
                 **/
                function redeem(address to, uint256 amount) external override {
                  require(amount != 0, 'INVALID_ZERO_AMOUNT');
                  //solium-disable-next-line
                  uint256 cooldownStartTimestamp = stakersCooldowns[msg.sender];
                  require(
                    block.timestamp > cooldownStartTimestamp.add(COOLDOWN_SECONDS),
                    'INSUFFICIENT_COOLDOWN'
                  );
                  require(
                    block.timestamp.sub(cooldownStartTimestamp.add(COOLDOWN_SECONDS)) <= UNSTAKE_WINDOW,
                    'UNSTAKE_WINDOW_FINISHED'
                  );
                  uint256 balanceOfMessageSender = balanceOf(msg.sender);
                  uint256 amountToRedeem = (amount > balanceOfMessageSender) ? balanceOfMessageSender : amount;
                  _updateCurrentUnclaimedRewards(msg.sender, balanceOfMessageSender, true);
                  _burn(msg.sender, amountToRedeem);
                  if (balanceOfMessageSender.sub(amountToRedeem) == 0) {
                    stakersCooldowns[msg.sender] = 0;
                  }
                  IERC20(STAKED_TOKEN).safeTransfer(to, amountToRedeem);
                  emit Redeem(msg.sender, to, amountToRedeem);
                }
                /**
                 * @dev Activates the cooldown period to unstake
                 * - It can't be called if the user is not staking
                 **/
                function cooldown() external override {
                  require(balanceOf(msg.sender) != 0, "INVALID_BALANCE_ON_COOLDOWN");
                  //solium-disable-next-line
                  stakersCooldowns[msg.sender] = block.timestamp;
                  emit Cooldown(msg.sender);
                }
                /**
                 * @dev Claims an `amount` of `REWARD_TOKEN` to the address `to`
                 * @param to Address to stake for
                 * @param amount Amount to stake
                 **/
                function claimRewards(address to, uint256 amount) external override {
                  uint256 newTotalRewards = _updateCurrentUnclaimedRewards(
                    msg.sender,
                    balanceOf(msg.sender),
                    false
                  );
                  uint256 amountToClaim = (amount == type(uint256).max) ? newTotalRewards : amount;
                  stakerRewardsToClaim[msg.sender] = newTotalRewards.sub(amountToClaim, "INVALID_AMOUNT");
                  REWARD_TOKEN.safeTransferFrom(REWARDS_VAULT, to, amountToClaim);
                  emit RewardsClaimed(msg.sender, to, amountToClaim);
                }
                /**
                 * @dev Internal ERC20 _transfer of the tokenized staked tokens
                 * @param from Address to transfer from
                 * @param to Address to transfer to
                 * @param amount Amount to transfer
                 **/
                function _transfer(
                  address from,
                  address to,
                  uint256 amount
                ) internal override {
                  uint256 balanceOfFrom = balanceOf(from);
                  // Sender
                  _updateCurrentUnclaimedRewards(from, balanceOfFrom, true);
                  // Recipient
                  if (from != to) {
                    uint256 balanceOfTo = balanceOf(to);
                    _updateCurrentUnclaimedRewards(to, balanceOfTo, true);
                    uint256 previousSenderCooldown = stakersCooldowns[from];
                    stakersCooldowns[to] = getNextCooldownTimestamp(previousSenderCooldown, amount, to, balanceOfTo);
                    // if cooldown was set and whole balance of sender was transferred - clear cooldown
                    if (balanceOfFrom == amount && previousSenderCooldown != 0) {
                      stakersCooldowns[from] = 0;
                    }
                  }
                  super._transfer(from, to, amount);
                }
                /**
                 * @dev Updates the user state related with his accrued rewards
                 * @param user Address of the user
                 * @param userBalance The current balance of the user
                 * @param updateStorage Boolean flag used to update or not the stakerRewardsToClaim of the user
                 * @return The unclaimed rewards that were added to the total accrued
                 **/
                function _updateCurrentUnclaimedRewards(
                  address user,
                  uint256 userBalance,
                  bool updateStorage
                ) internal returns (uint256) {
                  uint256 accruedRewards = _updateUserAssetInternal(
                    user,
                    address(this),
                    userBalance,
                    totalSupply()
                  );
                  uint256 unclaimedRewards = stakerRewardsToClaim[user].add(accruedRewards);
                  if (accruedRewards != 0) {
                    if (updateStorage) {
                      stakerRewardsToClaim[user] = unclaimedRewards;
                    }
                    emit RewardsAccrued(user, accruedRewards);
                  }
                  return unclaimedRewards;
                }
                /**
                 * @dev Calculates the how is gonna be a new cooldown timestamp depending on the sender/receiver situation
                 *  - If the timestamp of the sender is "better" or the timestamp of the recipient is 0, we take the one of the recipient
                 *  - Weighted average of from/to cooldown timestamps if:
                 *    # The sender doesn't have the cooldown activated (timestamp 0).
                 *    # The sender timestamp is expired
                 *    # The sender has a "worse" timestamp
                 *  - If the receiver's cooldown timestamp expired (too old), the next is 0
                 * @param fromCooldownTimestamp Cooldown timestamp of the sender
                 * @param amountToReceive Amount
                 * @param toAddress Address of the recipient
                 * @param toBalance Current balance of the receiver
                 * @return The new cooldown timestamp
                 **/
                function getNextCooldownTimestamp(
                  uint256 fromCooldownTimestamp,
                  uint256 amountToReceive,
                  address toAddress,
                  uint256 toBalance
                ) public returns (uint256) {
                  uint256 toCooldownTimestamp = stakersCooldowns[toAddress];
                  if (toCooldownTimestamp == 0) {
                    return 0;
                  }
                  uint256 minimalValidCooldownTimestamp = block.timestamp.sub(COOLDOWN_SECONDS).sub(
                    UNSTAKE_WINDOW
                  );
                  if (minimalValidCooldownTimestamp > toCooldownTimestamp) {
                    toCooldownTimestamp = 0;
                  } else {
                    uint256 fromCooldownTimestamp = (minimalValidCooldownTimestamp > fromCooldownTimestamp)
                      ? block.timestamp
                      : fromCooldownTimestamp;
                    if (fromCooldownTimestamp < toCooldownTimestamp) {
                      return toCooldownTimestamp;
                    } else {
                      toCooldownTimestamp = (
                        amountToReceive.mul(fromCooldownTimestamp).add(toBalance.mul(toCooldownTimestamp))
                      )
                        .div(amountToReceive.add(toBalance));
                    }
                  }
                  stakersCooldowns[toAddress] = toCooldownTimestamp;
                  return toCooldownTimestamp;
                }
                /**
                 * @dev Return the total rewards pending to claim by an staker
                 * @param staker The staker address
                 * @return The rewards
                 */
                function getTotalRewardsBalance(address staker) external view returns (uint256) {
                    DistributionTypes.UserStakeInput[] memory userStakeInputs
                   = new DistributionTypes.UserStakeInput[](1);
                  userStakeInputs[0] = DistributionTypes.UserStakeInput({
                    underlyingAsset: address(this),
                    stakedByUser: balanceOf(staker),
                    totalStaked: totalSupply()
                  });
                  return stakerRewardsToClaim[staker].add(_getUnclaimedRewards(staker, userStakeInputs));
                }
                /**
                 * @dev returns the revision of the implementation contract
                 * @return The revision
                 */
                function getRevision() internal override pure returns (uint256) {
                  return REVISION;
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              import '../lib/ERC20.sol';
              /**
               * @title ERC20Mintable
               * @dev ERC20 minting logic
               */
              contract MintableErc20 is ERC20 {
                  constructor(
                      string memory name,
                      string memory symbol,
                      uint8 decimals
                  ) public ERC20(name, symbol, decimals) {}
                  /**
                   * @dev Function to mint tokens
                   * @param value The amount of tokens to mint.
                   * @return A boolean that indicates if the operation was successful.
                   */
                  function mint(uint256 value) public returns (bool) {
                      _mint(msg.sender, value);
                      return true;
                  }
              }
              

              File 3 of 9: InitializableAdminUpgradeabilityProxy
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.10;
              /**
               * @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: agpl-3.0
              pragma solidity 0.6.10;
              import {IERC20} from "./IERC20.sol";
              interface IERC20Detailed is IERC20 {
                  function name() external view returns(string memory);
                  function symbol() external view returns(string memory);
                  function decimals() external view returns(uint8);
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.10;
              interface ITransferHook {
                  function onTransfer(address from, address to, uint256 amount) external;
              }pragma solidity ^0.6.2;
              /**
               * @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) {
                      // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                      // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                      // for accounts without code, i.e. `keccak256('')`
                      bytes32 codehash;
                      bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                      // solhint-disable-next-line no-inline-assembly
                      assembly { codehash := extcodehash(account) }
                      return (codehash != accountHash && codehash != 0x0);
                  }
                  /**
                   * @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");
                  }
              }pragma solidity ^0.6.0;
              import './UpgradeabilityProxy.sol';
              /**
               * @title BaseAdminUpgradeabilityProxy
               * @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 BaseAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
                /**
                 * @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 "eip1967.proxy.admin" subtracted by 1, and is
                 * validated in the constructor.
                 */
                bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                /**
                 * @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();
                  }
                }
                /**
                 * @return The address of the proxy admin.
                 */
                function admin() external ifAdmin returns (address) {
                  return _admin();
                }
                /**
                 * @return The address of the implementation.
                 */
                function implementation() external 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/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                 */
                function upgradeToAndCall(address newImplementation, bytes calldata data) payable external ifAdmin {
                  _upgradeTo(newImplementation);
                  (bool success,) = newImplementation.delegatecall(data);
                  require(success);
                }
                /**
                 * @return adm 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 override virtual {
                  require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
                  super._willFallback();
                }
              }pragma solidity ^0.6.0;
              import './BaseUpgradeabilityProxy.sol';
              /**
               * @title UpgradeabilityProxy
               * @dev Extends BaseUpgradeabilityProxy with a constructor for initializing
               * implementation and init data.
               */
              contract UpgradeabilityProxy is BaseUpgradeabilityProxy {
                /**
                 * @dev Contract constructor.
                 * @param _logic Address of the initial implementation.
                 * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                 * It should include the signature and the parameters of the function to be called, as described in
                 * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                 * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                 */
                constructor(address _logic, bytes memory _data) public payable {
                  assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
                  _setImplementation(_logic);
                  if(_data.length > 0) {
                    (bool success,) = _logic.delegatecall(_data);
                    require(success);
                  }
                }  
              }pragma solidity ^0.6.0;
              import './Proxy.sol';
              import './Address.sol';
              /**
               * @title BaseUpgradeabilityProxy
               * @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 BaseUpgradeabilityProxy is Proxy {
                /**
                 * @dev Emitted when the implementation is upgraded.
                 * @param implementation Address of the new implementation.
                 */
                event Upgraded(address indexed implementation);
                /**
                 * @dev Storage slot with the address of the current implementation.
                 * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                 * validated in the constructor.
                 */
                bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                /**
                 * @dev Returns the current implementation.
                 * @return impl Address of the current implementation
                 */
                function _implementation() internal override 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) internal {
                  require(Address.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
                  bytes32 slot = IMPLEMENTATION_SLOT;
                  assembly {
                    sstore(slot, newImplementation)
                  }
                }
              }pragma solidity ^0.6.0;
              /**
               * @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.
               */
              abstract contract Proxy {
                /**
                 * @dev Fallback function.
                 * Implemented entirely in `_fallback`.
                 */
                fallback () payable external {
                  _fallback();
                }
                /**
                 * @return The Address of the implementation.
                 */
                function _implementation() internal virtual 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 virtual {
                }
                /**
                 * @dev fallback implementation.
                 * Extracted to enable manual triggering.
                 */
                function _fallback() internal {
                  _willFallback();
                  _delegate(_implementation());
                }
              }// SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              /*
               * @dev Provides information about the current execution context, including the
               * sender of the transaction and its data. While these are generally available
               * via msg.sender and msg.data, they should not be accessed in such a direct
               * manner, since when dealing with GSN meta-transactions the account sending and
               * paying for execution may not be the actual sender (as far as an application
               * is concerned).
               *
               * This contract is only required for intermediate, library-like contracts.
               */
              abstract contract Context {
                  function _msgSender() internal view virtual returns (address payable) {
                      return msg.sender;
                  }
                  function _msgData() internal view virtual returns (bytes memory) {
                      this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                      return msg.data;
                  }
              }// SPDX-License-Identifier: MIT
              pragma solidity ^0.6.0;
              import "./Context.sol";
              import "../interfaces/IERC20.sol";
              import "./SafeMath.sol";
              import "./Address.sol";
              /**
               * @dev Implementation of the {IERC20} interface.
               *
               * This implementation is agnostic to the way tokens are created. This means
               * that a supply mechanism has to be added in a derived contract using {_mint}.
               * For a generic mechanism see {ERC20PresetMinterPauser}.
               *
               * TIP: For a detailed writeup see our guide
               * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
               * to implement supply mechanisms].
               *
               * We have followed general OpenZeppelin guidelines: functions revert instead
               * of returning `false` on failure. This behavior is nonetheless conventional
               * and does not conflict with the expectations of ERC20 applications.
               *
               * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
               * This allows applications to reconstruct the allowance for all accounts just
               * by listening to said events. Other implementations of the EIP may not emit
               * these events, as it isn't required by the specification.
               *
               * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
               * functions have been added to mitigate the well-known issues around setting
               * allowances. See {IERC20-approve}.
               */
              contract ERC20 is Context, IERC20 {
                  using SafeMath for uint256;
                  using Address for address;
                  mapping (address => uint256) private _balances;
                  mapping (address => mapping (address => uint256)) private _allowances;
                  uint256 private _totalSupply;
                  string internal _name;
                  string internal _symbol;
                  uint8 private _decimals;
                  /**
                   * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
                   * a default value of 18.
                   *
                   * To select a different value for {decimals}, use {_setupDecimals}.
                   *
                   * All three of these values are immutable: they can only be set once during
                   * construction.
                   */
                  constructor (string memory name, string memory symbol) public {
                      _name = name;
                      _symbol = symbol;
                      _decimals = 18;
                  }
                  /**
                   * @dev Returns the name of the token.
                   */
                  function name() public view returns (string memory) {
                      return _name;
                  }
                  /**
                   * @dev Returns the symbol of the token, usually a shorter version of the
                   * name.
                   */
                  function symbol() public view returns (string memory) {
                      return _symbol;
                  }
                  /**
                   * @dev Returns the number of decimals used to get its user representation.
                   * For example, if `decimals` equals `2`, a balance of `505` tokens should
                   * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                   *
                   * Tokens usually opt for a value of 18, imitating the relationship between
                   * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
                   * called.
                   *
                   * NOTE: This information is only used for _display_ purposes: it in
                   * no way affects any of the arithmetic of the contract, including
                   * {IERC20-balanceOf} and {IERC20-transfer}.
                   */
                  function decimals() public view returns (uint8) {
                      return _decimals;
                  }
                  /**
                   * @dev See {IERC20-totalSupply}.
                   */
                  function totalSupply() public view override returns (uint256) {
                      return _totalSupply;
                  }
                  /**
                   * @dev See {IERC20-balanceOf}.
                   */
                  function balanceOf(address account) public view override returns (uint256) {
                      return _balances[account];
                  }
                  /**
                   * @dev See {IERC20-transfer}.
                   *
                   * Requirements:
                   *
                   * - `recipient` cannot be the zero address.
                   * - the caller must have a balance of at least `amount`.
                   */
                  function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
                      _transfer(_msgSender(), recipient, amount);
                      return true;
                  }
                  /**
                   * @dev See {IERC20-allowance}.
                   */
                  function allowance(address owner, address spender) public view virtual override returns (uint256) {
                      return _allowances[owner][spender];
                  }
                  /**
                   * @dev See {IERC20-approve}.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   */
                  function approve(address spender, uint256 amount) public virtual override returns (bool) {
                      _approve(_msgSender(), spender, amount);
                      return true;
                  }
                  /**
                   * @dev See {IERC20-transferFrom}.
                   *
                   * Emits an {Approval} event indicating the updated allowance. This is not
                   * required by the EIP. See the note at the beginning of {ERC20};
                   *
                   * Requirements:
                   * - `sender` and `recipient` cannot be the zero address.
                   * - `sender` must have a balance of at least `amount`.
                   * - the caller must have allowance for ``sender``'s tokens of at least
                   * `amount`.
                   */
                  function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
                      _transfer(sender, recipient, amount);
                      _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
                      return true;
                  }
                  /**
                   * @dev Atomically increases the allowance granted to `spender` by the caller.
                   *
                   * This is an alternative to {approve} that can be used as a mitigation for
                   * problems described in {IERC20-approve}.
                   *
                   * Emits an {Approval} event indicating the updated allowance.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   */
                  function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                      _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
                      return true;
                  }
                  /**
                   * @dev Atomically decreases the allowance granted to `spender` by the caller.
                   *
                   * This is an alternative to {approve} that can be used as a mitigation for
                   * problems described in {IERC20-approve}.
                   *
                   * Emits an {Approval} event indicating the updated allowance.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   * - `spender` must have allowance for the caller of at least
                   * `subtractedValue`.
                   */
                  function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
                      _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
                      return true;
                  }
                  /**
                   * @dev Moves tokens `amount` from `sender` to `recipient`.
                   *
                   * This is internal function is equivalent to {transfer}, and can be used to
                   * e.g. implement automatic token fees, slashing mechanisms, etc.
                   *
                   * Emits a {Transfer} event.
                   *
                   * Requirements:
                   *
                   * - `sender` cannot be the zero address.
                   * - `recipient` cannot be the zero address.
                   * - `sender` must have a balance of at least `amount`.
                   */
                  function _transfer(address sender, address recipient, uint256 amount) internal virtual {
                      require(sender != address(0), "ERC20: transfer from the zero address");
                      require(recipient != address(0), "ERC20: transfer to the zero address");
                      _beforeTokenTransfer(sender, recipient, amount);
                      _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
                      _balances[recipient] = _balances[recipient].add(amount);
                      emit Transfer(sender, recipient, amount);
                  }
                  /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                   * the total supply.
                   *
                   * Emits a {Transfer} event with `from` set to the zero address.
                   *
                   * Requirements
                   *
                   * - `to` cannot be the zero address.
                   */
                  function _mint(address account, uint256 amount) internal virtual {
                      require(account != address(0), "ERC20: mint to the zero address");
                      _beforeTokenTransfer(address(0), account, amount);
                      _totalSupply = _totalSupply.add(amount);
                      _balances[account] = _balances[account].add(amount);
                      emit Transfer(address(0), account, amount);
                  }
                  /**
                   * @dev Destroys `amount` tokens from `account`, reducing the
                   * total supply.
                   *
                   * Emits a {Transfer} event with `to` set to the zero address.
                   *
                   * Requirements
                   *
                   * - `account` cannot be the zero address.
                   * - `account` must have at least `amount` tokens.
                   */
                  function _burn(address account, uint256 amount) internal virtual {
                      require(account != address(0), "ERC20: burn from the zero address");
                      _beforeTokenTransfer(account, address(0), amount);
                      _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
                      _totalSupply = _totalSupply.sub(amount);
                      emit Transfer(account, address(0), amount);
                  }
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
                   *
                   * This is internal function is equivalent to `approve`, and can be used to
                   * e.g. set automatic allowances for certain subsystems, etc.
                   *
                   * Emits an {Approval} event.
                   *
                   * Requirements:
                   *
                   * - `owner` cannot be the zero address.
                   * - `spender` cannot be the zero address.
                   */
                  function _approve(address owner, address spender, uint256 amount) internal virtual {
                      require(owner != address(0), "ERC20: approve from the zero address");
                      require(spender != address(0), "ERC20: approve to the zero address");
                      _allowances[owner][spender] = amount;
                      emit Approval(owner, spender, amount);
                  }
                  /**
                   * @dev Sets {decimals} to a value other than the default one of 18.
                   *
                   * WARNING: This function should only be called from the constructor. Most
                   * applications that interact with token contracts will not expect
                   * {decimals} to ever change, and may work incorrectly if it does.
                   */
                  function _setupDecimals(uint8 decimals_) internal {
                      _decimals = decimals_;
                  }
                  /**
                   * @dev Hook that is called before any transfer of tokens. This includes
                   * minting and burning.
                   *
                   * Calling conditions:
                   *
                   * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
                   * will be to transferred to `to`.
                   * - when `from` is zero, `amount` tokens will be minted for `to`.
                   * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
                   * - `from` and `to` are never both zero.
                   *
                   * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                   */
                  function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
              }pragma solidity ^0.6.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, 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) {
                      return sub(a, b, "SafeMath: subtraction overflow");
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                   * overflow (when the result is negative).
                   *
                   * 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);
                      uint256 c = a - b;
                      return c;
                  }
                  /**
                   * @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) {
                      // 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 0;
                      }
                      uint256 c = a * b;
                      require(c / a == b, "SafeMath: multiplication overflow");
                      return c;
                  }
                  /**
                   * @dev Returns the integer division of two unsigned integers. Reverts 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) {
                      return div(a, b, "SafeMath: division by zero");
                  }
                  /**
                   * @dev Returns the integer division of two unsigned integers. Reverts with custom message 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, string memory errorMessage) internal pure returns (uint256) {
                      // Solidity only automatically asserts when dividing by 0
                      require(b > 0, errorMessage);
                      uint256 c = a / b;
                      // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                      return c;
                  }
                  /**
                   * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                   * Reverts 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) {
                      return mod(a, b, "SafeMath: modulo by zero");
                  }
                  /**
                   * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                   * Reverts with custom message 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, string memory errorMessage) internal pure returns (uint256) {
                      require(b != 0, errorMessage);
                      return a % b;
                  }
              }// SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.6.10;
              import "./BaseAdminUpgradeabilityProxy.sol";
              import "./InitializableUpgradeabilityProxy.sol";
              /**
               * @title InitializableAdminUpgradeabilityProxy
               * @dev Extends from BaseAdminUpgradeabilityProxy with an initializer for 
               * initializing the implementation, admin, and init data.
               */
              contract InitializableAdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, InitializableUpgradeabilityProxy {
                  /**
                 * Contract initializer.
                 * @param _logic address of the initial implementation.
                 * @param _admin Address of the proxy administrator.
                 * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                 * It should include the signature and the parameters of the function to be called, as described in
                 * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                 * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                 */
                  function initialize(address _logic, address _admin, bytes memory _data) public payable {
                      require(_implementation() == address(0));
                      InitializableUpgradeabilityProxy.initialize(_logic, _data);
                      assert(ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                      _setAdmin(_admin);
                  }
                  /**
                  * @dev Only fall back when the sender is not the admin.
                  */
                  function _willFallback() internal override(BaseAdminUpgradeabilityProxy, Proxy) {
                      BaseAdminUpgradeabilityProxy._willFallback();
                  }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.6.10;
              import "./BaseUpgradeabilityProxy.sol";
              /**
               * @title InitializableUpgradeabilityProxy
               * @dev Extends BaseUpgradeabilityProxy with an initializer for initializing
               * implementation and init data.
               */
              contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy {
                  /**
                 * @dev Contract initializer.
                 * @param _logic Address of the initial implementation.
                 * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                 * It should include the signature and the parameters of the function to be called, as described in
                 * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                 * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                 */
                  function initialize(address _logic, bytes memory _data) public payable {
                      require(_implementation() == address(0));
                      assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                      _setImplementation(_logic);
                      if (_data.length > 0) {
                          (bool success, ) = _logic.delegatecall(_data);
                          require(success);
                      }
                  }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.10;
              import {ERC20} from "../open-zeppelin/ERC20.sol";
              import {ITransferHook} from "../interfaces/ITransferHook.sol";
              import {VersionedInitializable} from "../utils/VersionedInitializable.sol";
              /**
              * @notice implementation of the AAVE token contract
              * @author Aave
              */
              contract AaveToken is ERC20, VersionedInitializable {
                  /// @dev snapshot of a value on a specific block, used for balances
                  struct Snapshot {
                      uint128 blockNumber;
                      uint128 value;
                  }
                  string internal constant NAME = "Aave Token";
                  string internal constant SYMBOL = "AAVE";
                  uint8 internal constant DECIMALS = 18;
                  /// @dev the amount being distributed for the LEND -> AAVE migration
                  uint256 internal constant MIGRATION_AMOUNT = 13000000 ether;
                  /// @dev the amount being distributed for the PSI and PEI
                  uint256 internal constant DISTRIBUTION_AMOUNT = 3000000 ether;
                  uint256 public constant REVISION = 1;
                  /// @dev owner => next valid nonce to submit with permit()
                  mapping (address => uint256) public _nonces;
                  mapping (address => mapping (uint256 => Snapshot)) public _snapshots;
                  mapping (address => uint256) public _countsSnapshots;
                  /// @dev reference to the Aave governance contract to call (if initialized) on _beforeTokenTransfer
                  /// !!! IMPORTANT The Aave governance is considered a trustable contract, being its responsibility
                  /// to control all potential reentrancies by calling back the AaveToken
                  ITransferHook public _aaveGovernance;
                  bytes32 public DOMAIN_SEPARATOR;
                  bytes public constant EIP712_REVISION = bytes("1");
                  bytes32 internal constant EIP712_DOMAIN = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
                  bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
                  event SnapshotDone(address owner, uint128 oldValue, uint128 newValue);
                  constructor() ERC20(NAME, SYMBOL) public {}
                  /**
                  * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy
                  * @param migrator the address of the LEND -> AAVE migration contract
                  * @param distributor the address of the AAVE distribution contract
                  */
                  function initialize(
                      address migrator,
                      address distributor,
                      ITransferHook aaveGovernance
                  ) external initializer {
                      uint256 chainId;
                      //solium-disable-next-line
                      assembly {
                          chainId := chainid()
                      }
                      DOMAIN_SEPARATOR = keccak256(abi.encode(
                          EIP712_DOMAIN,
                          keccak256(bytes(NAME)),
                          keccak256(EIP712_REVISION),
                          chainId,
                          address(this)
                      ));
                      _name = NAME;
                      _symbol = SYMBOL;
                      _setupDecimals(DECIMALS);
                      _aaveGovernance = aaveGovernance;
                      _mint(migrator, MIGRATION_AMOUNT);
                      _mint(distributor, DISTRIBUTION_AMOUNT);
                  }
                  /**
                  * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md
                  * @param owner the owner of the funds
                  * @param spender the spender
                  * @param value the amount
                  * @param deadline the deadline timestamp, type(uint256).max for no deadline
                  * @param v signature param
                  * @param s signature param
                  * @param r signature param
                  */
                  function permit(
                      address owner,
                      address spender,
                      uint256 value,
                      uint256 deadline,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) external {
                      require(owner != address(0), "INVALID_OWNER");
                      //solium-disable-next-line
                      require(block.timestamp <= deadline, "INVALID_EXPIRATION");
                      uint256 currentValidNonce = _nonces[owner];
                      bytes32 digest = keccak256(
                              abi.encodePacked(
                                  "\\x19\\x01",
                                  DOMAIN_SEPARATOR,
                                  keccak256(
                                      abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline))
                                  )
                      );
                      require(owner == ecrecover(digest, v, r, s), "INVALID_SIGNATURE");
                      _nonces[owner] = currentValidNonce.add(1);
                      _approve(owner, spender, value);
                  }
                  /**
                  * @dev returns the revision of the implementation contract
                  */
                  function getRevision() internal pure override returns (uint256) {
                      return REVISION;
                  }
                  /**
                  * @dev Writes a snapshot for an owner of tokens
                  * @param owner The owner of the tokens
                  * @param oldValue The value before the operation that is gonna be executed after the snapshot
                  * @param newValue The value after the operation
                  */
                  function _writeSnapshot(address owner, uint128 oldValue, uint128 newValue) internal {
                      uint128 currentBlock = uint128(block.number);
                      uint256 ownerCountOfSnapshots = _countsSnapshots[owner];
                      mapping (uint256 => Snapshot) storage snapshotsOwner = _snapshots[owner];
                      // Doing multiple operations in the same block
                      if (ownerCountOfSnapshots != 0 && snapshotsOwner[ownerCountOfSnapshots.sub(1)].blockNumber == currentBlock) {
                          snapshotsOwner[ownerCountOfSnapshots.sub(1)].value = newValue;
                      } else {
                          snapshotsOwner[ownerCountOfSnapshots] = Snapshot(currentBlock, newValue);
                          _countsSnapshots[owner] = ownerCountOfSnapshots.add(1);
                      }
                      emit SnapshotDone(owner, oldValue, newValue);
                  }
                  /**
                  * @dev Writes a snapshot before any operation involving transfer of value: _transfer, _mint and _burn
                  * - On _transfer, it writes snapshots for both "from" and "to"
                  * - On _mint, only for _to
                  * - On _burn, only for _from
                  * @param from the from address
                  * @param to the to address
                  * @param amount the amount to transfer
                  */
                  function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
                      if (from == to) {
                          return;
                      }
                      if (from != address(0)) {
                          uint256 fromBalance = balanceOf(from);
                          _writeSnapshot(from, uint128(fromBalance), uint128(fromBalance.sub(amount)));
                      }
                      if (to != address(0)) {
                          uint256 toBalance = balanceOf(to);
                          _writeSnapshot(to, uint128(toBalance), uint128(toBalance.add(amount)));
                      }
                      // caching the aave governance address to avoid multiple state loads
                      ITransferHook aaveGovernance = _aaveGovernance;
                      if (aaveGovernance != ITransferHook(0)) {
                          aaveGovernance.onTransfer(from, to, amount);
                      }
                  }
              }// SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.10;
              /**
               * @title VersionedInitializable
               *
               * @dev Helper contract to support initializer functions. To use it, replace
               * the constructor with a function that has the `initializer` modifier.
               * WARNING: Unlike constructors, initializer functions must be manually
               * invoked. This applies both to deploying an Initializable contract, as well
               * as extending an Initializable contract via inheritance.
               * WARNING: When used with inheritance, manual care must be taken to not invoke
               * a parent initializer twice, or ensure that all initializers are idempotent,
               * because this is not dealt with automatically as with constructors.
               *
               * @author Aave, inspired by the OpenZeppelin Initializable contract
               */
              abstract contract VersionedInitializable {
                  /**
                 * @dev Indicates that the contract has been initialized.
                 */
                  uint256 internal lastInitializedRevision = 0;
                 /**
                 * @dev Modifier to use in the initializer function of a contract.
                 */
                  modifier initializer() {
                      uint256 revision = getRevision();
                      require(revision > lastInitializedRevision, "Contract instance has already been initialized");
                      lastInitializedRevision = revision;
                      _;
                  }
                  /// @dev returns the revision number of the contract.
                  /// Needs to be defined in the inherited class as a constant.
                  function getRevision() internal pure virtual returns(uint256);
                  // Reserved storage space to allow for layout changes in the future.
                  uint256[50] private ______gap;
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.10;
              import {IERC20} from "../interfaces/IERC20.sol";
              import {SafeMath} from "../open-zeppelin/SafeMath.sol";
              import {VersionedInitializable} from "../utils/VersionedInitializable.sol";
              /**
              * @title LendToAaveMigrator
              * @notice This contract implements the migration from LEND to AAVE token
              * @author Aave 
              */
              contract LendToAaveMigrator is VersionedInitializable {
                  using SafeMath for uint256;
                  IERC20 public immutable AAVE;
                  IERC20 public immutable LEND;
                  uint256 public immutable LEND_AAVE_RATIO;
                  uint256 public constant REVISION = 1;
                  
                  uint256 public _totalLendMigrated;
                  /**
                  * @dev emitted on migration
                  * @param sender the caller of the migration
                  * @param amount the amount being migrated
                  */
                  event LendMigrated(address indexed sender, uint256 indexed amount);
                  /**
                  * @param aave the address of the AAVE token
                  * @param lend the address of the LEND token
                  * @param lendAaveRatio the exchange rate between LEND and AAVE 
                   */
                  constructor(IERC20 aave, IERC20 lend, uint256 lendAaveRatio) public {
                      AAVE = aave;
                      LEND = lend;
                      LEND_AAVE_RATIO = lendAaveRatio;
                  }
                  /**
                  * @dev initializes the implementation
                  */
                  function initialize() public initializer {
                  }
                  /**
                  * @dev returns true if the migration started
                  */
                  function migrationStarted() external view returns(bool) {
                      return lastInitializedRevision != 0;
                  }
                  /**
                  * @dev executes the migration from LEND to AAVE. Users need to give allowance to this contract to transfer LEND before executing
                  * this transaction.
                  * @param amount the amount of LEND to be migrated
                  */
                  function migrateFromLEND(uint256 amount) external {
                      require(lastInitializedRevision != 0, "MIGRATION_NOT_STARTED");
                      _totalLendMigrated = _totalLendMigrated.add(amount);
                      LEND.transferFrom(msg.sender, address(this), amount);
                      AAVE.transfer(msg.sender, amount.div(LEND_AAVE_RATIO));
                      emit LendMigrated(msg.sender, amount);
                  }
                  /**
                  * @dev returns the implementation revision
                  * @return the implementation revision
                  */
                  function getRevision() internal pure override returns (uint256) {
                      return REVISION;
                  }
              }// SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.10;
              import "../interfaces/IERC20.sol";
              contract DoubleTransferHelper {
                  IERC20 public immutable AAVE;
                  constructor(IERC20 aave) public {
                      AAVE = aave;
                  }
                  function doubleSend(address to, uint256 amount1, uint256 amount2) external {
                      AAVE.transfer(to, amount1);
                      AAVE.transfer(to, amount2);
                  }
              }// SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.10;
              import "../open-zeppelin/ERC20.sol";
              /**
               * @title ERC20Mintable
               * @dev ERC20 minting logic
               */
              contract MintableErc20 is ERC20 {
                  constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol) public {
                      _setupDecimals(decimals);
                  }
                  /**
                   * @dev Function to mint tokens
                   * @param value The amount of tokens to mint.
                   * @return A boolean that indicates if the operation was successful.
                   */
                  function mint(uint256 value) public returns (bool) {
                      _mint(msg.sender, value);
                      return true;
                  }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.10;
              import {ITransferHook} from "../interfaces/ITransferHook.sol";
              contract MockTransferHook is ITransferHook {
                  event MockHookEvent();
                  function onTransfer(address from, address to, uint256 amount) external override {
                      emit MockHookEvent();
                  }
              }

              File 4 of 9: InitializableImmutableAdminUpgradeabilityProxy
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.6.12;
              
              /**
               * @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) {
                  // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                  // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                  // for accounts without code, i.e. `keccak256('')`
                  bytes32 codehash;
                  bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                  // solhint-disable-next-line no-inline-assembly
                  assembly {
                    codehash := extcodehash(account)
                  }
                  return (codehash != accountHash && codehash != 0x0);
                }
              
                /**
                 * @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');
                }
              }
              
              /**
               * @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.
               */
              abstract contract Proxy {
                /**
                 * @dev Fallback function.
                 * Implemented entirely in `_fallback`.
                 */
                fallback() external payable {
                  _fallback();
                }
              
                /**
                 * @return The Address of the implementation.
                 */
                function _implementation() internal view virtual 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 {
                  //solium-disable-next-line
                  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 virtual {}
              
                /**
                 * @dev fallback implementation.
                 * Extracted to enable manual triggering.
                 */
                function _fallback() internal {
                  _willFallback();
                  _delegate(_implementation());
                }
              }
              
              /**
               * @title BaseUpgradeabilityProxy
               * @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 BaseUpgradeabilityProxy is Proxy {
                /**
                 * @dev Emitted when the implementation is upgraded.
                 * @param implementation Address of the new implementation.
                 */
                event Upgraded(address indexed implementation);
              
                /**
                 * @dev Storage slot with the address of the current implementation.
                 * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                 * validated in the constructor.
                 */
                bytes32 internal constant IMPLEMENTATION_SLOT =
                  0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
              
                /**
                 * @dev Returns the current implementation.
                 * @return impl Address of the current implementation
                 */
                function _implementation() internal view override returns (address impl) {
                  bytes32 slot = IMPLEMENTATION_SLOT;
                  //solium-disable-next-line
                  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) internal {
                  require(
                    Address.isContract(newImplementation),
                    'Cannot set a proxy implementation to a non-contract address'
                  );
              
                  bytes32 slot = IMPLEMENTATION_SLOT;
              
                  //solium-disable-next-line
                  assembly {
                    sstore(slot, newImplementation)
                  }
                }
              }
              
              /**
               * @title InitializableUpgradeabilityProxy
               * @dev Extends BaseUpgradeabilityProxy with an initializer for initializing
               * implementation and init data.
               */
              contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy {
                /**
                 * @dev Contract initializer.
                 * @param _logic Address of the initial implementation.
                 * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                 * It should include the signature and the parameters of the function to be called, as described in
                 * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                 * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                 */
                function initialize(address _logic, bytes memory _data) public payable {
                  require(_implementation() == address(0));
                  assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
                  _setImplementation(_logic);
                  if (_data.length > 0) {
                    (bool success, ) = _logic.delegatecall(_data);
                    require(success);
                  }
                }
              }
              
              /**
               * @title BaseImmutableAdminUpgradeabilityProxy
               * @author Aave, inspired by the OpenZeppelin upgradeability proxy pattern
               * @dev This contract combines an upgradeability proxy with an authorization
               * mechanism for administrative tasks. The admin role is stored in an immutable, which
               * helps saving transactions costs
               * 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 BaseImmutableAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
                address immutable ADMIN;
              
                constructor(address admin) public {
                  ADMIN = admin;
                }
              
                modifier ifAdmin() {
                  if (msg.sender == ADMIN) {
                    _;
                  } else {
                    _fallback();
                  }
                }
              
                /**
                 * @return The address of the proxy admin.
                 */
                function admin() external ifAdmin returns (address) {
                  return ADMIN;
                }
              
                /**
                 * @return The address of the implementation.
                 */
                function implementation() external ifAdmin returns (address) {
                  return _implementation();
                }
              
                /**
                 * @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/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                 */
                function upgradeToAndCall(address newImplementation, bytes calldata data)
                  external
                  payable
                  ifAdmin
                {
                  _upgradeTo(newImplementation);
                  (bool success, ) = newImplementation.delegatecall(data);
                  require(success);
                }
              
                /**
                 * @dev Only fall back when the sender is not the admin.
                 */
                function _willFallback() internal virtual override {
                  require(msg.sender != ADMIN, 'Cannot call fallback function from the proxy admin');
                  super._willFallback();
                }
              }
              
              /**
               * @title InitializableAdminUpgradeabilityProxy
               * @dev Extends BaseAdminUpgradeabilityProxy with an initializer function
               */
              contract InitializableImmutableAdminUpgradeabilityProxy is
                BaseImmutableAdminUpgradeabilityProxy,
                InitializableUpgradeabilityProxy
              {
                constructor(address admin) public BaseImmutableAdminUpgradeabilityProxy(admin) {}
              
                /**
                 * @dev Only fall back when the sender is not the admin.
                 */
                function _willFallback() internal override(BaseImmutableAdminUpgradeabilityProxy, Proxy) {
                  BaseImmutableAdminUpgradeabilityProxy._willFallback();
                }
              }

              File 5 of 9: DefisaverLogger
              // SPDX-License-Identifier: MIT
              
              pragma solidity =0.8.10;
              
              contract DefisaverLogger {
                  event RecipeEvent(
                      address indexed caller,
                      string indexed logName
                  );
              
                  event ActionDirectEvent(
                      address indexed caller,
                      string indexed logName,
                      bytes data
                  );
              
                  function logRecipeEvent(
                      string memory _logName
                  ) public {
                      emit RecipeEvent(msg.sender, _logName);
                  }
              
                  function logActionDirectEvent(
                      string memory _logName,
                      bytes memory _data
                  ) public {
                      emit ActionDirectEvent(msg.sender, _logName, _data);
                  }
              }

              File 6 of 9: AaveClaimStkAave
              // SPDX-License-Identifier: MIT
              
              pragma solidity =0.8.10;
              
              pragma experimental ABIEncoderV2;
              
              
              
              
              
              abstract contract IDFSRegistry {
               
                  function getAddr(bytes4 _id) public view virtual returns (address);
              
                  function addNewContract(
                      bytes32 _id,
                      address _contractAddr,
                      uint256 _waitPeriod
                  ) public virtual;
              
                  function startContractChange(bytes32 _id, address _newContractAddr) public virtual;
              
                  function approveContractChange(bytes32 _id) public virtual;
              
                  function cancelContractChange(bytes32 _id) public virtual;
              
                  function changeWaitPeriod(bytes32 _id, uint256 _newWaitPeriod) public virtual;
              }
              
              
              
              
              
              interface IERC20 {
                  function name() external view returns (string memory);
                  function symbol() external view returns (string memory);
                  function decimals() external view returns (uint256 digits);
                  function totalSupply() external view returns (uint256 supply);
              
                  function balanceOf(address _owner) external view returns (uint256 balance);
              
                  function transfer(address _to, uint256 _value) external returns (bool success);
              
                  function transferFrom(
                      address _from,
                      address _to,
                      uint256 _value
                  ) external returns (bool success);
              
                  function approve(address _spender, uint256 _value) external returns (bool success);
              
                  function allowance(address _owner, address _spender) external view returns (uint256 remaining);
              
                  event Approval(address indexed _owner, address indexed _spender, uint256 _value);
              }
              
              
              
              
              
              library Address {
                  //insufficient balance
                  error InsufficientBalance(uint256 available, uint256 required);
                  //unable to send value, recipient may have reverted
                  error SendingValueFail();
                  //insufficient balance for call
                  error InsufficientBalanceForCall(uint256 available, uint256 required);
                  //call to non-contract
                  error NonContractCall();
                  
                  function isContract(address account) internal view returns (bool) {
                      // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                      // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                      // for accounts without code, i.e. `keccak256('')`
                      bytes32 codehash;
                      bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          codehash := extcodehash(account)
                      }
                      return (codehash != accountHash && codehash != 0x0);
                  }
              
                  function sendValue(address payable recipient, uint256 amount) internal {
                      uint256 balance = address(this).balance;
                      if (balance < amount){
                          revert InsufficientBalance(balance, amount);
                      }
              
                      // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                      (bool success, ) = recipient.call{value: amount}("");
                      if (!(success)){
                          revert SendingValueFail();
                      }
                  }
              
                  function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                      return functionCall(target, data, "Address: low-level call failed");
                  }
              
                  function functionCall(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      return _functionCallWithValue(target, data, 0, errorMessage);
                  }
              
                  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");
                  }
              
                  function functionCallWithValue(
                      address target,
                      bytes memory data,
                      uint256 value,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      uint256 balance = address(this).balance;
                      if (balance < value){
                          revert InsufficientBalanceForCall(balance, value);
                      }
                      return _functionCallWithValue(target, data, value, errorMessage);
                  }
              
                  function _functionCallWithValue(
                      address target,
                      bytes memory data,
                      uint256 weiValue,
                      string memory errorMessage
                  ) private returns (bytes memory) {
                      if (!(isContract(target))){
                          revert NonContractCall();
                      }
              
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = target.call{value: weiValue}(data);
                      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);
                          }
                      }
                  }
              }
              
              
              
              
              library SafeMath {
                  function add(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 c = a + b;
                      require(c >= a, "SafeMath: addition overflow");
              
                      return c;
                  }
              
                  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                      return sub(a, b, "SafeMath: subtraction overflow");
                  }
              
                  function sub(
                      uint256 a,
                      uint256 b,
                      string memory errorMessage
                  ) internal pure returns (uint256) {
                      require(b <= a, errorMessage);
                      uint256 c = a - b;
              
                      return c;
                  }
              
                  function mul(uint256 a, uint256 b) internal pure returns (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 0;
                      }
              
                      uint256 c = a * b;
                      require(c / a == b, "SafeMath: multiplication overflow");
              
                      return c;
                  }
              
                  function div(uint256 a, uint256 b) internal pure returns (uint256) {
                      return div(a, b, "SafeMath: division by zero");
                  }
              
                  function div(
                      uint256 a,
                      uint256 b,
                      string memory errorMessage
                  ) internal pure returns (uint256) {
                      require(b > 0, errorMessage);
                      uint256 c = a / b;
                      // assert(a == b * c + a % b); // There is no case in which this doesn't hold
              
                      return c;
                  }
              
                  function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                      return mod(a, b, "SafeMath: modulo by zero");
                  }
              
                  function mod(
                      uint256 a,
                      uint256 b,
                      string memory errorMessage
                  ) internal pure returns (uint256) {
                      require(b != 0, errorMessage);
                      return a % b;
                  }
              }
              
              
              
              
              
              
              
              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 Edited so it always first approves 0 and then the value, because of non standard tokens
                  function safeApprove(
                      IERC20 token,
                      address spender,
                      uint256 value
                  ) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
                      _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)
                      );
                  }
              
                  function _callOptionalReturn(IERC20 token, bytes memory data) private {
                      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");
                      }
                  }
              }
              
              
              
              
              
              contract MainnetAuthAddresses {
                  address internal constant ADMIN_VAULT_ADDR = 0xCCf3d848e08b94478Ed8f46fFead3008faF581fD;
                  address internal constant FACTORY_ADDRESS = 0x5a15566417e6C1c9546523066500bDDBc53F88C7;
                  address internal constant ADMIN_ADDR = 0x25eFA336886C74eA8E282ac466BdCd0199f85BB9; // USED IN ADMIN VAULT CONSTRUCTOR
              }
              
              
              
              
              
              contract AuthHelper is MainnetAuthAddresses {
              }
              
              
              
              
              
              contract AdminVault is AuthHelper {
                  address public owner;
                  address public admin;
              
                  error SenderNotAdmin();
              
                  constructor() {
                      owner = msg.sender;
                      admin = ADMIN_ADDR;
                  }
              
                  /// @notice Admin is able to change owner
                  /// @param _owner Address of new owner
                  function changeOwner(address _owner) public {
                      if (admin != msg.sender){
                          revert SenderNotAdmin();
                      }
                      owner = _owner;
                  }
              
                  /// @notice Admin is able to set new admin
                  /// @param _admin Address of multisig that becomes new admin
                  function changeAdmin(address _admin) public {
                      if (admin != msg.sender){
                          revert SenderNotAdmin();
                      }
                      admin = _admin;
                  }
              
              }
              
              
              
              
              
              
              
              
              contract AdminAuth is AuthHelper {
                  using SafeERC20 for IERC20;
              
                  AdminVault public constant adminVault = AdminVault(ADMIN_VAULT_ADDR);
              
                  error SenderNotOwner();
                  error SenderNotAdmin();
              
                  modifier onlyOwner() {
                      if (adminVault.owner() != msg.sender){
                          revert SenderNotOwner();
                      }
                      _;
                  }
              
                  modifier onlyAdmin() {
                      if (adminVault.admin() != msg.sender){
                          revert SenderNotAdmin();
                      }
                      _;
                  }
              
                  /// @notice withdraw stuck funds
                  function withdrawStuckFunds(address _token, address _receiver, uint256 _amount) public onlyOwner {
                      if (_token == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) {
                          payable(_receiver).transfer(_amount);
                      } else {
                          IERC20(_token).safeTransfer(_receiver, _amount);
                      }
                  }
              
                  /// @notice Destroy the contract
                  function kill() public onlyAdmin {
                      selfdestruct(payable(msg.sender));
                  }
              }
              
              
              
              
              
              contract DFSRegistry is AdminAuth {
                  error EntryAlreadyExistsError(bytes4);
                  error EntryNonExistentError(bytes4);
                  error EntryNotInChangeError(bytes4);
                  error ChangeNotReadyError(uint256,uint256);
                  error EmptyPrevAddrError(bytes4);
                  error AlreadyInContractChangeError(bytes4);
                  error AlreadyInWaitPeriodChangeError(bytes4);
              
                  event AddNewContract(address,bytes4,address,uint256);
                  event RevertToPreviousAddress(address,bytes4,address,address);
                  event StartContractChange(address,bytes4,address,address);
                  event ApproveContractChange(address,bytes4,address,address);
                  event CancelContractChange(address,bytes4,address,address);
                  event StartWaitPeriodChange(address,bytes4,uint256);
                  event ApproveWaitPeriodChange(address,bytes4,uint256,uint256);
                  event CancelWaitPeriodChange(address,bytes4,uint256,uint256);
              
                  struct Entry {
                      address contractAddr;
                      uint256 waitPeriod;
                      uint256 changeStartTime;
                      bool inContractChange;
                      bool inWaitPeriodChange;
                      bool exists;
                  }
              
                  mapping(bytes4 => Entry) public entries;
                  mapping(bytes4 => address) public previousAddresses;
              
                  mapping(bytes4 => address) public pendingAddresses;
                  mapping(bytes4 => uint256) public pendingWaitTimes;
              
                  /// @notice Given an contract id returns the registered address
                  /// @dev Id is keccak256 of the contract name
                  /// @param _id Id of contract
                  function getAddr(bytes4 _id) public view returns (address) {
                      return entries[_id].contractAddr;
                  }
              
                  /// @notice Helper function to easily query if id is registered
                  /// @param _id Id of contract
                  function isRegistered(bytes4 _id) public view returns (bool) {
                      return entries[_id].exists;
                  }
              
                  /////////////////////////// OWNER ONLY FUNCTIONS ///////////////////////////
              
                  /// @notice Adds a new contract to the registry
                  /// @param _id Id of contract
                  /// @param _contractAddr Address of the contract
                  /// @param _waitPeriod Amount of time to wait before a contract address can be changed
                  function addNewContract(
                      bytes4 _id,
                      address _contractAddr,
                      uint256 _waitPeriod
                  ) public onlyOwner {
                      if (entries[_id].exists){
                          revert EntryAlreadyExistsError(_id);
                      }
              
                      entries[_id] = Entry({
                          contractAddr: _contractAddr,
                          waitPeriod: _waitPeriod,
                          changeStartTime: 0,
                          inContractChange: false,
                          inWaitPeriodChange: false,
                          exists: true
                      });
              
                      emit AddNewContract(msg.sender, _id, _contractAddr, _waitPeriod);
                  }
              
                  /// @notice Reverts to the previous address immediately
                  /// @dev In case the new version has a fault, a quick way to fallback to the old contract
                  /// @param _id Id of contract
                  function revertToPreviousAddress(bytes4 _id) public onlyOwner {
                      if (!(entries[_id].exists)){
                          revert EntryNonExistentError(_id);
                      }
                      if (previousAddresses[_id] == address(0)){
                          revert EmptyPrevAddrError(_id);
                      }
              
                      address currentAddr = entries[_id].contractAddr;
                      entries[_id].contractAddr = previousAddresses[_id];
              
                      emit RevertToPreviousAddress(msg.sender, _id, currentAddr, previousAddresses[_id]);
                  }
              
                  /// @notice Starts an address change for an existing entry
                  /// @dev Can override a change that is currently in progress
                  /// @param _id Id of contract
                  /// @param _newContractAddr Address of the new contract
                  function startContractChange(bytes4 _id, address _newContractAddr) public onlyOwner {
                      if (!entries[_id].exists){
                          revert EntryNonExistentError(_id);
                      }
                      if (entries[_id].inWaitPeriodChange){
                          revert AlreadyInWaitPeriodChangeError(_id);
                      }
              
                      entries[_id].changeStartTime = block.timestamp; // solhint-disable-line
                      entries[_id].inContractChange = true;
              
                      pendingAddresses[_id] = _newContractAddr;
              
                      emit StartContractChange(msg.sender, _id, entries[_id].contractAddr, _newContractAddr);
                  }
              
                  /// @notice Changes new contract address, correct time must have passed
                  /// @param _id Id of contract
                  function approveContractChange(bytes4 _id) public onlyOwner {
                      if (!entries[_id].exists){
                          revert EntryNonExistentError(_id);
                      }
                      if (!entries[_id].inContractChange){
                          revert EntryNotInChangeError(_id);
                      }
                      if (block.timestamp < (entries[_id].changeStartTime + entries[_id].waitPeriod)){// solhint-disable-line
                          revert ChangeNotReadyError(block.timestamp, (entries[_id].changeStartTime + entries[_id].waitPeriod));
                      }
              
                      address oldContractAddr = entries[_id].contractAddr;
                      entries[_id].contractAddr = pendingAddresses[_id];
                      entries[_id].inContractChange = false;
                      entries[_id].changeStartTime = 0;
              
                      pendingAddresses[_id] = address(0);
                      previousAddresses[_id] = oldContractAddr;
              
                      emit ApproveContractChange(msg.sender, _id, oldContractAddr, entries[_id].contractAddr);
                  }
              
                  /// @notice Cancel pending change
                  /// @param _id Id of contract
                  function cancelContractChange(bytes4 _id) public onlyOwner {
                      if (!entries[_id].exists){
                          revert EntryNonExistentError(_id);
                      }
                      if (!entries[_id].inContractChange){
                          revert EntryNotInChangeError(_id);
                      }
              
                      address oldContractAddr = pendingAddresses[_id];
              
                      pendingAddresses[_id] = address(0);
                      entries[_id].inContractChange = false;
                      entries[_id].changeStartTime = 0;
              
                      emit CancelContractChange(msg.sender, _id, oldContractAddr, entries[_id].contractAddr);
                  }
              
                  /// @notice Starts the change for waitPeriod
                  /// @param _id Id of contract
                  /// @param _newWaitPeriod New wait time
                  function startWaitPeriodChange(bytes4 _id, uint256 _newWaitPeriod) public onlyOwner {
                      if (!entries[_id].exists){
                          revert EntryNonExistentError(_id);
                      }
                      if (entries[_id].inContractChange){
                          revert AlreadyInContractChangeError(_id);
                      }
              
                      pendingWaitTimes[_id] = _newWaitPeriod;
              
                      entries[_id].changeStartTime = block.timestamp; // solhint-disable-line
                      entries[_id].inWaitPeriodChange = true;
              
                      emit StartWaitPeriodChange(msg.sender, _id, _newWaitPeriod);
                  }
              
                  /// @notice Changes new wait period, correct time must have passed
                  /// @param _id Id of contract
                  function approveWaitPeriodChange(bytes4 _id) public onlyOwner {
                      if (!entries[_id].exists){
                          revert EntryNonExistentError(_id);
                      }
                      if (!entries[_id].inWaitPeriodChange){
                          revert EntryNotInChangeError(_id);
                      }
                      if (block.timestamp < (entries[_id].changeStartTime + entries[_id].waitPeriod)){ // solhint-disable-line
                          revert ChangeNotReadyError(block.timestamp, (entries[_id].changeStartTime + entries[_id].waitPeriod));
                      }
              
                      uint256 oldWaitTime = entries[_id].waitPeriod;
                      entries[_id].waitPeriod = pendingWaitTimes[_id];
                      
                      entries[_id].inWaitPeriodChange = false;
                      entries[_id].changeStartTime = 0;
              
                      pendingWaitTimes[_id] = 0;
              
                      emit ApproveWaitPeriodChange(msg.sender, _id, oldWaitTime, entries[_id].waitPeriod);
                  }
              
                  /// @notice Cancel wait period change
                  /// @param _id Id of contract
                  function cancelWaitPeriodChange(bytes4 _id) public onlyOwner {
                      if (!entries[_id].exists){
                          revert EntryNonExistentError(_id);
                      }
                      if (!entries[_id].inWaitPeriodChange){
                          revert EntryNotInChangeError(_id);
                      }
              
                      uint256 oldWaitPeriod = pendingWaitTimes[_id];
              
                      pendingWaitTimes[_id] = 0;
                      entries[_id].inWaitPeriodChange = false;
                      entries[_id].changeStartTime = 0;
              
                      emit CancelWaitPeriodChange(msg.sender, _id, oldWaitPeriod, entries[_id].waitPeriod);
                  }
              }
              
              
              
              
              
              abstract contract DSAuthority {
                  function canCall(
                      address src,
                      address dst,
                      bytes4 sig
                  ) public view virtual returns (bool);
              }
              
              
              
              
              
              contract DSAuthEvents {
                  event LogSetAuthority(address indexed authority);
                  event LogSetOwner(address indexed owner);
              }
              
              contract DSAuth is DSAuthEvents {
                  DSAuthority public authority;
                  address public owner;
              
                  constructor() {
                      owner = msg.sender;
                      emit LogSetOwner(msg.sender);
                  }
              
                  function setOwner(address owner_) public auth {
                      owner = owner_;
                      emit LogSetOwner(owner);
                  }
              
                  function setAuthority(DSAuthority authority_) public auth {
                      authority = authority_;
                      emit LogSetAuthority(address(authority));
                  }
              
                  modifier auth {
                      require(isAuthorized(msg.sender, msg.sig), "Not authorized");
                      _;
                  }
              
                  function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
                      if (src == address(this)) {
                          return true;
                      } else if (src == owner) {
                          return true;
                      } else if (authority == DSAuthority(address(0))) {
                          return false;
                      } else {
                          return authority.canCall(src, address(this), sig);
                      }
                  }
              }
              
              
              
              
              
              contract DSNote {
                  event LogNote(
                      bytes4 indexed sig,
                      address indexed guy,
                      bytes32 indexed foo,
                      bytes32 indexed bar,
                      uint256 wad,
                      bytes fax
                  ) anonymous;
              
                  modifier note {
                      bytes32 foo;
                      bytes32 bar;
              
                      assembly {
                          foo := calldataload(4)
                          bar := calldataload(36)
                      }
              
                      emit LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data);
              
                      _;
                  }
              }
              
              
              
              
              
              
              abstract contract DSProxy is DSAuth, DSNote {
                  DSProxyCache public cache; // global cache for contracts
              
                  constructor(address _cacheAddr) {
                      if (!(setCache(_cacheAddr))){
                          require(isAuthorized(msg.sender, msg.sig), "Not authorized");
                      }
                  }
              
                  // solhint-disable-next-line no-empty-blocks
                  receive() external payable {}
              
                  // use the proxy to execute calldata _data on contract _code
                  function execute(bytes memory _code, bytes memory _data)
                      public
                      payable
                      virtual
                      returns (address target, bytes32 response);
              
                  function execute(address _target, bytes memory _data)
                      public
                      payable
                      virtual
                      returns (bytes32 response);
              
                  //set new cache
                  function setCache(address _cacheAddr) public payable virtual returns (bool);
              }
              
              contract DSProxyCache {
                  mapping(bytes32 => address) cache;
              
                  function read(bytes memory _code) public view returns (address) {
                      bytes32 hash = keccak256(_code);
                      return cache[hash];
                  }
              
                  function write(bytes memory _code) public returns (address target) {
                      assembly {
                          target := create(0, add(_code, 0x20), mload(_code))
                          switch iszero(extcodesize(target))
                              case 1 {
                                  // throw if contract failed to deploy
                                  revert(0, 0)
                              }
                      }
                      bytes32 hash = keccak256(_code);
                      cache[hash] = target;
                  }
              }
              
              
              
              
              
              contract DefisaverLogger {
                  event RecipeEvent(
                      address indexed caller,
                      string indexed logName
                  );
              
                  event ActionDirectEvent(
                      address indexed caller,
                      string indexed logName,
                      bytes data
                  );
              
                  function logRecipeEvent(
                      string memory _logName
                  ) public {
                      emit RecipeEvent(msg.sender, _logName);
                  }
              
                  function logActionDirectEvent(
                      string memory _logName,
                      bytes memory _data
                  ) public {
                      emit ActionDirectEvent(msg.sender, _logName, _data);
                  }
              }
              
              
              
              
              
              contract MainnetActionsUtilAddresses {
                  address internal constant DFS_REG_CONTROLLER_ADDR = 0xF8f8B3C98Cf2E63Df3041b73f80F362a4cf3A576;
                  address internal constant REGISTRY_ADDR = 0x287778F121F134C66212FB16c9b53eC991D32f5b;
                  address internal constant DFS_LOGGER_ADDR = 0xcE7a977Cac4a481bc84AC06b2Da0df614e621cf3;
              }
              
              
              
              
              
              contract ActionsUtilHelper is MainnetActionsUtilAddresses {
              }
              
              
              
              
              
              
              
              
              abstract contract ActionBase is AdminAuth, ActionsUtilHelper {
                  event ActionEvent(
                      string indexed logName,
                      bytes data
                  );
              
                  DFSRegistry public constant registry = DFSRegistry(REGISTRY_ADDR);
              
                  DefisaverLogger public constant logger = DefisaverLogger(
                      DFS_LOGGER_ADDR
                  );
              
                  //Wrong sub index value
                  error SubIndexValueError();
                  //Wrong return index value
                  error ReturnIndexValueError();
              
                  /// @dev Subscription params index range [128, 255]
                  uint8 public constant SUB_MIN_INDEX_VALUE = 128;
                  uint8 public constant SUB_MAX_INDEX_VALUE = 255;
              
                  /// @dev Return params index range [1, 127]
                  uint8 public constant RETURN_MIN_INDEX_VALUE = 1;
                  uint8 public constant RETURN_MAX_INDEX_VALUE = 127;
              
                  /// @dev If the input value should not be replaced
                  uint8 public constant NO_PARAM_MAPPING = 0;
              
                  /// @dev We need to parse Flash loan actions in a different way
                  enum ActionType { FL_ACTION, STANDARD_ACTION, FEE_ACTION, CHECK_ACTION, CUSTOM_ACTION }
              
                  /// @notice Parses inputs and runs the implemented action through a proxy
                  /// @dev Is called by the RecipeExecutor chaining actions together
                  /// @param _callData Array of input values each value encoded as bytes
                  /// @param _subData Array of subscribed vales, replaces input values if specified
                  /// @param _paramMapping Array that specifies how return and subscribed values are mapped in input
                  /// @param _returnValues Returns values from actions before, which can be injected in inputs
                  /// @return Returns a bytes32 value through DSProxy, each actions implements what that value is
                  function executeAction(
                      bytes memory _callData,
                      bytes32[] memory _subData,
                      uint8[] memory _paramMapping,
                      bytes32[] memory _returnValues
                  ) public payable virtual returns (bytes32);
              
                  /// @notice Parses inputs and runs the single implemented action through a proxy
                  /// @dev Used to save gas when executing a single action directly
                  function executeActionDirect(bytes memory _callData) public virtual payable;
              
                  /// @notice Returns the type of action we are implementing
                  function actionType() public pure virtual returns (uint8);
              
              
                  //////////////////////////// HELPER METHODS ////////////////////////////
              
                  /// @notice Given an uint256 input, injects return/sub values if specified
                  /// @param _param The original input value
                  /// @param _mapType Indicated the type of the input in paramMapping
                  /// @param _subData Array of subscription data we can replace the input value with
                  /// @param _returnValues Array of subscription data we can replace the input value with
                  function _parseParamUint(
                      uint _param,
                      uint8 _mapType,
                      bytes32[] memory _subData,
                      bytes32[] memory _returnValues
                  ) internal pure returns (uint) {
                      if (isReplaceable(_mapType)) {
                          if (isReturnInjection(_mapType)) {
                              _param = uint(_returnValues[getReturnIndex(_mapType)]);
                          } else {
                              _param = uint256(_subData[getSubIndex(_mapType)]);
                          }
                      }
              
                      return _param;
                  }
              
              
                  /// @notice Given an addr input, injects return/sub values if specified
                  /// @param _param The original input value
                  /// @param _mapType Indicated the type of the input in paramMapping
                  /// @param _subData Array of subscription data we can replace the input value with
                  /// @param _returnValues Array of subscription data we can replace the input value with
                  function _parseParamAddr(
                      address _param,
                      uint8 _mapType,
                      bytes32[] memory _subData,
                      bytes32[] memory _returnValues
                  ) internal view returns (address) {
                      if (isReplaceable(_mapType)) {
                          if (isReturnInjection(_mapType)) {
                              _param = address(bytes20((_returnValues[getReturnIndex(_mapType)])));
                          } else {
                              /// @dev The last two values are specially reserved for proxy addr and owner addr
                              if (_mapType == 254) return address(this); //DSProxy address
                              if (_mapType == 255) return DSProxy(payable(address(this))).owner(); // owner of DSProxy
              
                              _param = address(uint160(uint256(_subData[getSubIndex(_mapType)])));
                          }
                      }
              
                      return _param;
                  }
              
                  /// @notice Given an bytes32 input, injects return/sub values if specified
                  /// @param _param The original input value
                  /// @param _mapType Indicated the type of the input in paramMapping
                  /// @param _subData Array of subscription data we can replace the input value with
                  /// @param _returnValues Array of subscription data we can replace the input value with
                  function _parseParamABytes32(
                      bytes32 _param,
                      uint8 _mapType,
                      bytes32[] memory _subData,
                      bytes32[] memory _returnValues
                  ) internal pure returns (bytes32) {
                      if (isReplaceable(_mapType)) {
                          if (isReturnInjection(_mapType)) {
                              _param = (_returnValues[getReturnIndex(_mapType)]);
                          } else {
                              _param = _subData[getSubIndex(_mapType)];
                          }
                      }
              
                      return _param;
                  }
              
                  /// @notice Checks if the paramMapping value indicated that we need to inject values
                  /// @param _type Indicated the type of the input
                  function isReplaceable(uint8 _type) internal pure returns (bool) {
                      return _type != NO_PARAM_MAPPING;
                  }
              
                  /// @notice Checks if the paramMapping value is in the return value range
                  /// @param _type Indicated the type of the input
                  function isReturnInjection(uint8 _type) internal pure returns (bool) {
                      return (_type >= RETURN_MIN_INDEX_VALUE) && (_type <= RETURN_MAX_INDEX_VALUE);
                  }
              
                  /// @notice Transforms the paramMapping value to the index in return array value
                  /// @param _type Indicated the type of the input
                  function getReturnIndex(uint8 _type) internal pure returns (uint8) {
                      if (!(isReturnInjection(_type))){
                          revert SubIndexValueError();
                      }
              
                      return (_type - RETURN_MIN_INDEX_VALUE);
                  }
              
                  /// @notice Transforms the paramMapping value to the index in sub array value
                  /// @param _type Indicated the type of the input
                  function getSubIndex(uint8 _type) internal pure returns (uint8) {
                      if (_type < SUB_MIN_INDEX_VALUE){
                          revert ReturnIndexValueError();
                      }
                      return (_type - SUB_MIN_INDEX_VALUE);
                  }
              }
              
              
              
              
              
              interface ILendingPoolAddressesProviderV2 {
                event LendingPoolUpdated(address indexed newAddress);
                event ConfigurationAdminUpdated(address indexed newAddress);
                event EmergencyAdminUpdated(address indexed newAddress);
                event LendingPoolConfiguratorUpdated(address indexed newAddress);
                event LendingPoolCollateralManagerUpdated(address indexed newAddress);
                event PriceOracleUpdated(address indexed newAddress);
                event LendingRateOracleUpdated(address indexed newAddress);
                event ProxyCreated(bytes32 id, address indexed newAddress);
                event AddressSet(bytes32 id, address indexed newAddress, bool hasProxy);
              
                function setAddress(bytes32 id, address newAddress) external;
              
                function setAddressAsProxy(bytes32 id, address impl) external;
              
                function getAddress(bytes32 id) external view returns (address);
              
                function getLendingPool() external view returns (address);
              
                function setLendingPoolImpl(address pool) external;
              
                function getLendingPoolConfigurator() external view returns (address);
              
                function setLendingPoolConfiguratorImpl(address configurator) external;
              
                function getLendingPoolCollateralManager() external view returns (address);
              
                function setLendingPoolCollateralManager(address manager) external;
              
                function getPoolAdmin() external view returns (address);
              
                function setPoolAdmin(address admin) external;
              
                function getEmergencyAdmin() external view returns (address);
              
                function setEmergencyAdmin(address admin) external;
              
                function getPriceOracle() external view returns (address);
              
                function setPriceOracle(address priceOracle) external;
              
                function getLendingRateOracle() external view returns (address);
              
                function setLendingRateOracle(address lendingRateOracle) external;
              }
              
              
              
              
              library DataTypes {
                // refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties.
                struct ReserveData {
                  //stores the reserve configuration
                  ReserveConfigurationMap configuration;
                  //the liquidity index. Expressed in ray
                  uint128 liquidityIndex;
                  //variable borrow index. Expressed in ray
                  uint128 variableBorrowIndex;
                  //the current supply rate. Expressed in ray
                  uint128 currentLiquidityRate;
                  //the current variable borrow rate. Expressed in ray
                  uint128 currentVariableBorrowRate;
                  //the current stable borrow rate. Expressed in ray
                  uint128 currentStableBorrowRate;
                  uint40 lastUpdateTimestamp;
                  //tokens addresses
                  address aTokenAddress;
                  address stableDebtTokenAddress;
                  address variableDebtTokenAddress;
                  //address of the interest rate strategy
                  address interestRateStrategyAddress;
                  //the id of the reserve. Represents the position in the list of the active reserves
                  uint8 id;
                }
              
                struct ReserveConfigurationMap {
                  //bit 0-15: LTV
                  //bit 16-31: Liq. threshold
                  //bit 32-47: Liq. bonus
                  //bit 48-55: Decimals
                  //bit 56: Reserve is active
                  //bit 57: reserve is frozen
                  //bit 58: borrowing is enabled
                  //bit 59: stable rate borrowing enabled
                  //bit 60-63: reserved
                  //bit 64-79: reserve factor
                  uint256 data;
                }
              
                struct UserConfigurationMap {
                  uint256 data;
                }
              
                enum InterestRateMode {NONE, STABLE, VARIABLE}
              }
              
              interface ILendingPoolV2 {
                /**
                 * @dev Emitted on deposit()
                 * @param reserve The address of the underlying asset of the reserve
                 * @param user The address initiating the deposit
                 * @param onBehalfOf The beneficiary of the deposit, receiving the aTokens
                 * @param amount The amount deposited
                 * @param referral The referral code used
                 **/
                event Deposit(
                  address indexed reserve,
                  address user,
                  address indexed onBehalfOf,
                  uint256 amount,
                  uint16 indexed referral
                );
              
                /**
                 * @dev Emitted on withdraw()
                 * @param reserve The address of the underlyng asset being withdrawn
                 * @param user The address initiating the withdrawal, owner of aTokens
                 * @param to Address that will receive the underlying
                 * @param amount The amount to be withdrawn
                 **/
                event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount);
              
                /**
                 * @dev Emitted on borrow() and flashLoan() when debt needs to be opened
                 * @param reserve The address of the underlying asset being borrowed
                 * @param user The address of the user initiating the borrow(), receiving the funds on borrow() or just
                 * initiator of the transaction on flashLoan()
                 * @param onBehalfOf The address that will be getting the debt
                 * @param amount The amount borrowed out
                 * @param borrowRateMode The rate mode: 1 for Stable, 2 for Variable
                 * @param borrowRate The numeric rate at which the user has borrowed
                 * @param referral The referral code used
                 **/
                event Borrow(
                  address indexed reserve,
                  address user,
                  address indexed onBehalfOf,
                  uint256 amount,
                  uint256 borrowRateMode,
                  uint256 borrowRate,
                  uint16 indexed referral
                );
              
                /**
                 * @dev Emitted on repay()
                 * @param reserve The address of the underlying asset of the reserve
                 * @param user The beneficiary of the repayment, getting his debt reduced
                 * @param repayer The address of the user initiating the repay(), providing the funds
                 * @param amount The amount repaid
                 **/
                event Repay(
                  address indexed reserve,
                  address indexed user,
                  address indexed repayer,
                  uint256 amount
                );
              
                /**
                 * @dev Emitted on swapBorrowRateMode()
                 * @param reserve The address of the underlying asset of the reserve
                 * @param user The address of the user swapping his rate mode
                 * @param rateMode The rate mode that the user wants to swap to
                 **/
                event Swap(address indexed reserve, address indexed user, uint256 rateMode);
              
                /**
                 * @dev Emitted on setUserUseReserveAsCollateral()
                 * @param reserve The address of the underlying asset of the reserve
                 * @param user The address of the user enabling the usage as collateral
                 **/
                event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user);
              
                /**
                 * @dev Emitted on setUserUseReserveAsCollateral()
                 * @param reserve The address of the underlying asset of the reserve
                 * @param user The address of the user enabling the usage as collateral
                 **/
                event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user);
              
                /**
                 * @dev Emitted on rebalanceStableBorrowRate()
                 * @param reserve The address of the underlying asset of the reserve
                 * @param user The address of the user for which the rebalance has been executed
                 **/
                event RebalanceStableBorrowRate(address indexed reserve, address indexed user);
              
                /**
                 * @dev Emitted on flashLoan()
                 * @param target The address of the flash loan receiver contract
                 * @param initiator The address initiating the flash loan
                 * @param asset The address of the asset being flash borrowed
                 * @param amount The amount flash borrowed
                 * @param premium The fee flash borrowed
                 * @param referralCode The referral code used
                 **/
                event FlashLoan(
                  address indexed target,
                  address indexed initiator,
                  address indexed asset,
                  uint256 amount,
                  uint256 premium,
                  uint16 referralCode
                );
              
                /**
                 * @dev Emitted when the pause is triggered.
                 */
                event Paused();
              
                /**
                 * @dev Emitted when the pause is lifted.
                 */
                event Unpaused();
              
                /**
                 * @dev Emitted when a borrower is liquidated. This event is emitted by the LendingPool via
                 * LendingPoolCollateral manager using a DELEGATECALL
                 * This allows to have the events in the generated ABI for LendingPool.
                 * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
                 * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
                 * @param user The address of the borrower getting liquidated
                 * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
                 * @param liquidatedCollateralAmount The amount of collateral received by the liiquidator
                 * @param liquidator The address of the liquidator
                 * @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants
                 * to receive the underlying collateral asset directly
                 **/
                event LiquidationCall(
                  address indexed collateralAsset,
                  address indexed debtAsset,
                  address indexed user,
                  uint256 debtToCover,
                  uint256 liquidatedCollateralAmount,
                  address liquidator,
                  bool receiveAToken
                );
              
                /**
                 * @dev Emitted when the state of a reserve is updated. NOTE: This event is actually declared
                 * in the ReserveLogic library and emitted in the updateInterestRates() function. Since the function is internal,
                 * the event will actually be fired by the LendingPool contract. The event is therefore replicated here so it
                 * gets added to the LendingPool ABI
                 * @param reserve The address of the underlying asset of the reserve
                 * @param liquidityRate The new liquidity rate
                 * @param stableBorrowRate The new stable borrow rate
                 * @param variableBorrowRate The new variable borrow rate
                 * @param liquidityIndex The new liquidity index
                 * @param variableBorrowIndex The new variable borrow index
                 **/
                event ReserveDataUpdated(
                  address indexed reserve,
                  uint256 liquidityRate,
                  uint256 stableBorrowRate,
                  uint256 variableBorrowRate,
                  uint256 liquidityIndex,
                  uint256 variableBorrowIndex
                );
              
                /**
                 * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
                 * - E.g. User deposits 100 USDC and gets in return 100 aUSDC
                 * @param asset The address of the underlying asset to deposit
                 * @param amount The amount to be deposited
                 * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
                 *   wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
                 *   is a different wallet
                 * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
                 *   0 if the action is executed directly by the user, without any middle-man
                 **/
                function deposit(
                  address asset,
                  uint256 amount,
                  address onBehalfOf,
                  uint16 referralCode
                ) external;
              
                /**
                 * @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
                 * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
                 * @param asset The address of the underlying asset to withdraw
                 * @param amount The underlying amount to be withdrawn
                 *   - Send the value type(uint256).max in order to withdraw the whole aToken balance
                 * @param to Address that will receive the underlying, same as msg.sender if the user
                 *   wants to receive it on his own wallet, or a different address if the beneficiary is a
                 *   different wallet
                 **/
                function withdraw(
                  address asset,
                  uint256 amount,
                  address to
                ) external;
              
                /**
                 * @dev Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower
                 * already deposited enough collateral, or he was given enough allowance by a credit delegator on the
                 * corresponding debt token (StableDebtToken or VariableDebtToken)
                 * - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet
                 *   and 100 stable/variable debt tokens, depending on the `interestRateMode`
                 * @param asset The address of the underlying asset to borrow
                 * @param amount The amount to be borrowed
                 * @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable
                 * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
                 *   0 if the action is executed directly by the user, without any middle-man
                 * @param onBehalfOf Address of the user who will receive the debt. Should be the address of the borrower itself
                 * calling the function if he wants to borrow against his own collateral, or the address of the credit delegator
                 * if he has been given credit delegation allowance
                 **/
                function borrow(
                  address asset,
                  uint256 amount,
                  uint256 interestRateMode,
                  uint16 referralCode,
                  address onBehalfOf
                ) external;
              
                /**
                 * @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned
                 * - E.g. User repays 100 USDC, burning 100 variable/stable debt tokens of the `onBehalfOf` address
                 * @param asset The address of the borrowed underlying asset previously borrowed
                 * @param amount The amount to repay
                 * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
                 * @param rateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable
                 * @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the
                 * user calling the function if he wants to reduce/remove his own debt, or the address of any other
                 * other borrower whose debt should be removed
                 **/
                function repay(
                  address asset,
                  uint256 amount,
                  uint256 rateMode,
                  address onBehalfOf
                ) external;
              
                /**
                 * @dev Allows a borrower to swap his debt between stable and variable mode, or viceversa
                 * @param asset The address of the underlying asset borrowed
                 * @param rateMode The rate mode that the user wants to swap to
                 **/
                function swapBorrowRateMode(address asset, uint256 rateMode) external;
              
                /**
                 * @dev Rebalances the stable interest rate of a user to the current stable rate defined on the reserve.
                 * - Users can be rebalanced if the following conditions are satisfied:
                 *     1. Usage ratio is above 95%
                 *     2. the current deposit APY is below REBALANCE_UP_THRESHOLD * maxVariableBorrowRate, which means that too much has been
                 *        borrowed at a stable rate and depositors are not earning enough
                 * @param asset The address of the underlying asset borrowed
                 * @param user The address of the user to be rebalanced
                 **/
                function rebalanceStableBorrowRate(address asset, address user) external;
              
                /**
                 * @dev Allows depositors to enable/disable a specific deposited asset as collateral
                 * @param asset The address of the underlying asset deposited
                 * @param useAsCollateral `true` if the user wants to use the deposit as collateral, `false` otherwise
                 **/
                function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;
              
                /**
                 * @dev Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1
                 * - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives
                 *   a proportionally amount of the `collateralAsset` plus a bonus to cover market risk
                 * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
                 * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
                 * @param user The address of the borrower getting liquidated
                 * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
                 * @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants
                 * to receive the underlying collateral asset directly
                 **/
                function liquidationCall(
                  address collateralAsset,
                  address debtAsset,
                  address user,
                  uint256 debtToCover,
                  bool receiveAToken
                ) external;
              
                /**
                 * @dev Allows smartcontracts to access the liquidity of the pool within one transaction,
                 * as long as the amount taken plus a fee is returned.
                 * IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept into consideration.
                 * For further details please visit https://developers.aave.com
                 * @param receiverAddress The address of the contract receiving the funds, implementing the IFlashLoanReceiver interface
                 * @param assets The addresses of the assets being flash-borrowed
                 * @param amounts The amounts amounts being flash-borrowed
                 * @param modes Types of the debt to open if the flash loan is not returned:
                 *   0 -> Don't open any debt, just revert if funds can't be transferred from the receiver
                 *   1 -> Open debt at stable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
                 *   2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
                 * @param onBehalfOf The address  that will receive the debt in the case of using on `modes` 1 or 2
                 * @param params Variadic packed params to pass to the receiver as extra information
                 * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
                 *   0 if the action is executed directly by the user, without any middle-man
                 **/
                function flashLoan(
                  address receiverAddress,
                  address[] calldata assets,
                  uint256[] calldata amounts,
                  uint256[] calldata modes,
                  address onBehalfOf,
                  bytes calldata params,
                  uint16 referralCode
                ) external;
              
                /**
                 * @dev Returns the user account data across all the reserves
                 * @param user The address of the user
                 * @return totalCollateralETH the total collateral in ETH of the user
                 * @return totalDebtETH the total debt in ETH of the user
                 * @return availableBorrowsETH the borrowing power left of the user
                 * @return currentLiquidationThreshold the liquidation threshold of the user
                 * @return ltv the loan to value of the user
                 * @return healthFactor the current health factor of the user
                 **/
                function getUserAccountData(address user)
                  external
                  view
                  returns (
                    uint256 totalCollateralETH,
                    uint256 totalDebtETH,
                    uint256 availableBorrowsETH,
                    uint256 currentLiquidationThreshold,
                    uint256 ltv,
                    uint256 healthFactor
                  );
              
                function initReserve(
                  address reserve,
                  address aTokenAddress,
                  address stableDebtAddress,
                  address variableDebtAddress,
                  address interestRateStrategyAddress
                ) external;
              
                function setReserveInterestRateStrategyAddress(address reserve, address rateStrategyAddress)
                  external;
              
                function setConfiguration(address reserve, uint256 configuration) external;
              
                /**
                 * @dev Returns the configuration of the reserve
                 * @param asset The address of the underlying asset of the reserve
                 * @return The configuration of the reserve
                 **/
                function getConfiguration(address asset) external view returns (DataTypes.ReserveConfigurationMap memory);
              
                /**
                 * @dev Returns the configuration of the user across all the reserves
                 * @param user The user address
                 * @return The configuration of the user
                 **/
                function getUserConfiguration(address user) external view returns (DataTypes.UserConfigurationMap memory);
              
                /**
                 * @dev Returns the normalized income normalized income of the reserve
                 * @param asset The address of the underlying asset of the reserve
                 * @return The reserve's normalized income
                 */
                function getReserveNormalizedIncome(address asset) external view returns (uint256);
              
                /**
                 * @dev Returns the normalized variable debt per unit of asset
                 * @param asset The address of the underlying asset of the reserve
                 * @return The reserve normalized variable debt
                 */
                function getReserveNormalizedVariableDebt(address asset) external view returns (uint256);
              
                /**
                 * @dev Returns the state and configuration of the reserve
                 * @param asset The address of the underlying asset of the reserve
                 * @return The state of the reserve
                 **/
                function getReserveData(address asset) external view returns (DataTypes.ReserveData memory);
              
                function finalizeTransfer(
                  address asset,
                  address from,
                  address to,
                  uint256 amount,
                  uint256 balanceFromAfter,
                  uint256 balanceToBefore
                ) external;
              
                function getReservesList() external view returns (address[] memory);
              
                function getAddressesProvider() external view returns (ILendingPoolAddressesProviderV2);
              
                function setPause(bool val) external;
              
                function paused() external view returns (bool);
              }
              
              
              
              
              
              abstract contract IAaveProtocolDataProviderV2 {
              
                struct TokenData {
                  string symbol;
                  address tokenAddress;
                }
              
                function getAllReservesTokens() external virtual view returns (TokenData[] memory);
              
                function getAllATokens() external virtual view returns (TokenData[] memory);
              
                function getReserveConfigurationData(address asset)
                  external virtual
                  view
                  returns (
                    uint256 decimals,
                    uint256 ltv,
                    uint256 liquidationThreshold,
                    uint256 liquidationBonus,
                    uint256 reserveFactor,
                    bool usageAsCollateralEnabled,
                    bool borrowingEnabled,
                    bool stableBorrowRateEnabled,
                    bool isActive,
                    bool isFrozen
                  );
              
                function getReserveData(address asset)
                  external virtual
                  view
                  returns (
                    uint256 availableLiquidity,
                    uint256 totalStableDebt,
                    uint256 totalVariableDebt,
                    uint256 liquidityRate,
                    uint256 variableBorrowRate,
                    uint256 stableBorrowRate,
                    uint256 averageStableBorrowRate,
                    uint256 liquidityIndex,
                    uint256 variableBorrowIndex,
                    uint40 lastUpdateTimestamp
                  );
              
                function getUserReserveData(address asset, address user)
                  external virtual
                  view
                  returns (
                    uint256 currentATokenBalance,
                    uint256 currentStableDebt,
                    uint256 currentVariableDebt,
                    uint256 principalStableDebt,
                    uint256 scaledVariableDebt,
                    uint256 stableBorrowRate,
                    uint256 liquidityRate,
                    uint40 stableRateLastUpdated,
                    bool usageAsCollateralEnabled
                  );
              
                function getReserveTokensAddresses(address asset)
                  external virtual
                  view
                  returns (
                    address aTokenAddress,
                    address stableDebtTokenAddress,
                    address variableDebtTokenAddress
                  );
              }
              
              
              
              
              interface IAaveIncentivesController {
                
                event RewardsAccrued(address indexed user, uint256 amount);
                
                event RewardsClaimed(
                  address indexed user,
                  address indexed to,
                  address indexed claimer,
                  uint256 amount
                );
              
                event ClaimerSet(address indexed user, address indexed claimer);
              
                /**
                 * @dev Whitelists an address to claim the rewards on behalf of another address
                 * @param user The address of the user
                 * @param claimer The address of the claimer
                 */
                function setClaimer(address user, address claimer) external;
              
                /**
                 * @dev Returns the whitelisted claimer for a certain address (0x0 if not set)
                 * @param user The address of the user
                 * @return The claimer address
                 */
                function getClaimer(address user) external view returns (address);
              
                /**
                 * @dev Configure assets for a certain rewards emission
                 * @param assets The assets to incentivize
                 * @param emissionsPerSecond The emission for each asset
                 */
                function configureAssets(address[] calldata assets, uint256[] calldata emissionsPerSecond)
                  external;
              
              
                /**
                 * @dev Called by the corresponding asset on any update that affects the rewards distribution
                 * @param asset The address of the user
                 * @param userBalance The balance of the user of the asset in the lending pool
                 * @param totalSupply The total supply of the asset in the lending pool
                 **/
                function handleAction(
                  address asset,
                  uint256 userBalance,
                  uint256 totalSupply
                ) external;
              
                /**
                 * @dev Returns the total of rewards of an user, already accrued + not yet accrued
                 * @param user The address of the user
                 * @return The rewards
                 **/
                function getRewardsBalance(address[] calldata assets, address user)
                  external
                  view
                  returns (uint256);
              
                /**
                 * @dev Claims reward for an user, on all the assets of the lending pool, accumulating the pending rewards
                 * @param amount Amount of rewards to claim
                 * @param to Address that will be receiving the rewards
                 * @return Rewards claimed
                 **/
                function claimRewards(
                  address[] calldata assets,
                  uint256 amount,
                  address to
                ) external returns (uint256);
              
                /**
                 * @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards. The caller must
                 * be whitelisted via "allowClaimOnBehalf" function by the RewardsAdmin role manager
                 * @param amount Amount of rewards to claim
                 * @param user Address to check and claim rewards
                 * @param to Address that will be receiving the rewards
                 * @return Rewards claimed
                 **/
                function claimRewardsOnBehalf(
                  address[] calldata assets,
                  uint256 amount,
                  address user,
                  address to
                ) external returns (uint256);
              
                /**
                 * @dev returns the unclaimed rewards of the user
                 * @param user the address of the user
                 * @return the unclaimed user rewards
                 */
                function getUserUnclaimedRewards(address user) external view returns (uint256);
              
                /**
                * @dev for backward compatibility with previous implementation of the Incentives controller
                */
                function REWARD_TOKEN() external view returns (address);
              }
              
              
              
              
              
              contract MainnetAaveAddresses {
                  address internal constant STAKED_CONTROLLER_ADDR = 0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5;
                  address internal constant STAKED_TOKEN_ADDR = 0x4da27a545c0c5B758a6BA100e3a049001de870f5;
              }
              
              
              
              
              interface IStakedToken {
                  function getTotalRewardsBalance(address) external view returns (uint256);
              }
              
              
              
              
              
              
              
              
              contract AaveHelper is MainnetAaveAddresses {
                  uint16 public constant AAVE_REFERRAL_CODE = 64;
              
                  uint256 public constant STABLE_ID = 1;
                  uint256 public constant VARIABLE_ID = 2;
              
                  bytes32 public constant DATA_PROVIDER_ID =
                      0x0100000000000000000000000000000000000000000000000000000000000000;
                  
                  IAaveIncentivesController constant public AaveIncentivesController = IAaveIncentivesController(STAKED_CONTROLLER_ADDR);
              
                  IStakedToken constant public StakedToken = IStakedToken(STAKED_TOKEN_ADDR);
              
                  /// @notice Enable/Disable a token as collateral for the specified Aave market
                  function enableAsCollateral(
                      address _market,
                      address _tokenAddr,
                      bool _useAsCollateral
                  ) public {
                      address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool();
              
                      ILendingPoolV2(lendingPool).setUserUseReserveAsCollateral(_tokenAddr, _useAsCollateral);
                  }
              
                  /// @notice Switches the borrowing rate mode (stable/variable) for the user
                  function switchRateMode(
                      address _market,
                      address _tokenAddr,
                      uint256 _rateMode
                  ) public {
                      address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool();
              
                      ILendingPoolV2(lendingPool).swapBorrowRateMode(_tokenAddr, _rateMode);
                  }
              
                  /// @notice Fetch the data provider for the specified market
                  function getDataProvider(address _market) internal view returns (IAaveProtocolDataProviderV2) {
                      return
                          IAaveProtocolDataProviderV2(
                              ILendingPoolAddressesProviderV2(_market).getAddress(DATA_PROVIDER_ID)
                          );
                  }
              
                  /// @notice Returns the lending pool contract of the specified market
                  function getLendingPool(address _market) internal view returns (ILendingPoolV2) {
                      return ILendingPoolV2(ILendingPoolAddressesProviderV2(_market).getLendingPool());
                  }
              }
              
              
              
              
              
              
              contract AaveClaimStkAave is ActionBase, AaveHelper {
              
                  struct Params {
                      address[] assets;
                      uint256 amount;     // Amount of rewards to claim
                      address to;         // Address that will be receiving the rewards
                  }
              
                  /// @inheritdoc ActionBase
                  function executeAction(
                      bytes memory _callData,
                      bytes32[] memory _subData,
                      uint8[] memory _paramMapping,
                      bytes32[] memory _returnValues
                  ) public payable virtual override returns (bytes32) {
                      Params memory params = parseInputs(_callData);
              
                      params.amount = _parseParamUint(params.amount, _paramMapping[0], _subData, _returnValues);
                      params.to = _parseParamAddr(params.to, _paramMapping[1], _subData, _returnValues);
              
                      (uint256 claimedAmount, bytes memory logData) = _aaveClaimStkAave(params);
                      emit ActionEvent("AaveClaimStkAave", logData);
                      return bytes32(claimedAmount);
                  }
              
                  /// @inheritdoc ActionBase
                  function executeActionDirect(bytes memory _callData) public payable override {
                      Params memory params = parseInputs(_callData);
                      (, bytes memory logData) = _aaveClaimStkAave(params);
                      logger.logActionDirectEvent("AaveClaimStkAave", logData);
                  }
              
                  /// @inheritdoc ActionBase
                  function actionType() public pure virtual override returns (uint8) {
                      return uint8(ActionType.STANDARD_ACTION);
                  }
              
                  //////////////////////////// ACTION LOGIC ////////////////////////////
              
                  /// @notice Claims stkAave rewards on the assets of the lending pool
                  function _aaveClaimStkAave(Params memory _params) internal returns (uint256 claimedAmount, bytes memory logData) {
                      // amount 0 is safe
                      // amount > unclaimedRewards is safe
                      claimedAmount = AaveIncentivesController.claimRewards(
                          _params.assets,
                          _params.amount,
                          _params.to
                      );
              
                      logData = abi.encode(_params, claimedAmount);
                  }
              
                  function parseInputs(bytes memory _callData) internal pure returns (Params memory params)
                  {
                      params = abi.decode(_callData, (Params));
                  }
              }
              

              File 7 of 9: StakedTokenIncentivesController
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.7.5;
              pragma experimental ABIEncoderV2;
              import {IAaveDistributionManager} from '../interfaces/IAaveDistributionManager.sol';
              import {SafeMath} from '../lib/SafeMath.sol';
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              /**
               * @title DistributionManager
               * @notice Accounting contract to manage multiple staking distributions
               * @author Aave
               **/
              contract DistributionManager is IAaveDistributionManager {
                using SafeMath for uint256;
                struct AssetData {
                  uint104 emissionPerSecond;
                  uint104 index;
                  uint40 lastUpdateTimestamp;
                  mapping(address => uint256) users;
                }
                address public immutable EMISSION_MANAGER;
                uint8 public constant PRECISION = 18;
                mapping(address => AssetData) public assets;
                uint256 internal _distributionEnd;
                modifier onlyEmissionManager() {
                  require(msg.sender == EMISSION_MANAGER, 'ONLY_EMISSION_MANAGER');
                  _;
                }
                constructor(address emissionManager) {
                  EMISSION_MANAGER = emissionManager;
                }
                /// @inheritdoc IAaveDistributionManager
                function setDistributionEnd(uint256 distributionEnd) external override onlyEmissionManager {
                  _distributionEnd = distributionEnd;
                  emit DistributionEndUpdated(distributionEnd);
                }
                /// @inheritdoc IAaveDistributionManager
                function getDistributionEnd() external view override returns (uint256) {
                  return _distributionEnd;
                }
                /// @inheritdoc IAaveDistributionManager
                function DISTRIBUTION_END() external view override returns (uint256) {
                  return _distributionEnd;
                }
                /// @inheritdoc IAaveDistributionManager
                function getUserAssetData(address user, address asset) public view override returns (uint256) {
                  return assets[asset].users[user];
                }
                /// @inheritdoc IAaveDistributionManager
                function getAssetData(address asset) public view override returns (uint256, uint256, uint256) {
                  return (assets[asset].index, assets[asset].emissionPerSecond, assets[asset].lastUpdateTimestamp);
                }
                /**
                 * @dev Configure the assets for a specific emission
                 * @param assetsConfigInput The array of each asset configuration
                 **/
                function _configureAssets(DistributionTypes.AssetConfigInput[] memory assetsConfigInput)
                  internal
                {
                  for (uint256 i = 0; i < assetsConfigInput.length; i++) {
                    AssetData storage assetConfig = assets[assetsConfigInput[i].underlyingAsset];
                    _updateAssetStateInternal(
                      assetsConfigInput[i].underlyingAsset,
                      assetConfig,
                      assetsConfigInput[i].totalStaked
                    );
                    assetConfig.emissionPerSecond = assetsConfigInput[i].emissionPerSecond;
                    emit AssetConfigUpdated(
                      assetsConfigInput[i].underlyingAsset,
                      assetsConfigInput[i].emissionPerSecond
                    );
                  }
                }
                /**
                 * @dev Updates the state of one distribution, mainly rewards index and timestamp
                 * @param asset The address of the asset being updated
                 * @param assetConfig Storage pointer to the distribution's config
                 * @param totalStaked Current total of staked assets for this distribution
                 * @return The new distribution index
                 **/
                function _updateAssetStateInternal(
                  address asset,
                  AssetData storage assetConfig,
                  uint256 totalStaked
                ) internal returns (uint256) {
                  uint256 oldIndex = assetConfig.index;
                  uint256 emissionPerSecond = assetConfig.emissionPerSecond;
                  uint128 lastUpdateTimestamp = assetConfig.lastUpdateTimestamp;
                  if (block.timestamp == lastUpdateTimestamp) {
                    return oldIndex;
                  }
                  uint256 newIndex =
                    _getAssetIndex(oldIndex, emissionPerSecond, lastUpdateTimestamp, totalStaked);
                  if (newIndex != oldIndex) {
                    require(uint104(newIndex) == newIndex, 'Index overflow');
                    //optimization: storing one after another saves one SSTORE
                    assetConfig.index = uint104(newIndex);
                    assetConfig.lastUpdateTimestamp = uint40(block.timestamp);
                    emit AssetIndexUpdated(asset, newIndex);
                  } else {
                    assetConfig.lastUpdateTimestamp = uint40(block.timestamp);
                  }
                  return newIndex;
                }
                /**
                 * @dev Updates the state of an user in a distribution
                 * @param user The user's address
                 * @param asset The address of the reference asset of the distribution
                 * @param stakedByUser Amount of tokens staked by the user in the distribution at the moment
                 * @param totalStaked Total tokens staked in the distribution
                 * @return The accrued rewards for the user until the moment
                 **/
                function _updateUserAssetInternal(
                  address user,
                  address asset,
                  uint256 stakedByUser,
                  uint256 totalStaked
                ) internal returns (uint256) {
                  AssetData storage assetData = assets[asset];
                  uint256 userIndex = assetData.users[user];
                  uint256 accruedRewards = 0;
                  uint256 newIndex = _updateAssetStateInternal(asset, assetData, totalStaked);
                  if (userIndex != newIndex) {
                    if (stakedByUser != 0) {
                      accruedRewards = _getRewards(stakedByUser, newIndex, userIndex);
                    }
                    assetData.users[user] = newIndex;
                    emit UserIndexUpdated(user, asset, newIndex);
                  }
                  return accruedRewards;
                }
                /**
                 * @dev Used by "frontend" stake contracts to update the data of an user when claiming rewards from there
                 * @param user The address of the user
                 * @param stakes List of structs of the user data related with his stake
                 * @return The accrued rewards for the user until the moment
                 **/
                function _claimRewards(address user, DistributionTypes.UserStakeInput[] memory stakes)
                  internal
                  returns (uint256)
                {
                  uint256 accruedRewards = 0;
                  for (uint256 i = 0; i < stakes.length; i++) {
                    accruedRewards = accruedRewards.add(
                      _updateUserAssetInternal(
                        user,
                        stakes[i].underlyingAsset,
                        stakes[i].stakedByUser,
                        stakes[i].totalStaked
                      )
                    );
                  }
                  return accruedRewards;
                }
                /**
                 * @dev Return the accrued rewards for an user over a list of distribution
                 * @param user The address of the user
                 * @param stakes List of structs of the user data related with his stake
                 * @return The accrued rewards for the user until the moment
                 **/
                function _getUnclaimedRewards(address user, DistributionTypes.UserStakeInput[] memory stakes)
                  internal
                  view
                  returns (uint256)
                {
                  uint256 accruedRewards = 0;
                  for (uint256 i = 0; i < stakes.length; i++) {
                    AssetData storage assetConfig = assets[stakes[i].underlyingAsset];
                    uint256 assetIndex =
                      _getAssetIndex(
                        assetConfig.index,
                        assetConfig.emissionPerSecond,
                        assetConfig.lastUpdateTimestamp,
                        stakes[i].totalStaked
                      );
                    accruedRewards = accruedRewards.add(
                      _getRewards(stakes[i].stakedByUser, assetIndex, assetConfig.users[user])
                    );
                  }
                  return accruedRewards;
                }
                /**
                 * @dev Internal function for the calculation of user's rewards on a distribution
                 * @param principalUserBalance Amount staked by the user on a distribution
                 * @param reserveIndex Current index of the distribution
                 * @param userIndex Index stored for the user, representation his staking moment
                 * @return The rewards
                 **/
                function _getRewards(
                  uint256 principalUserBalance,
                  uint256 reserveIndex,
                  uint256 userIndex
                ) internal pure returns (uint256) {
                  return principalUserBalance.mul(reserveIndex.sub(userIndex)) / 10**uint256(PRECISION);
                }
                /**
                 * @dev Calculates the next value of an specific distribution index, with validations
                 * @param currentIndex Current index of the distribution
                 * @param emissionPerSecond Representing the total rewards distributed per second per asset unit, on the distribution
                 * @param lastUpdateTimestamp Last moment this distribution was updated
                 * @param totalBalance of tokens considered for the distribution
                 * @return The new index.
                 **/
                function _getAssetIndex(
                  uint256 currentIndex,
                  uint256 emissionPerSecond,
                  uint128 lastUpdateTimestamp,
                  uint256 totalBalance
                ) internal view returns (uint256) {
                  uint256 distributionEnd = _distributionEnd;
                  if (
                    emissionPerSecond == 0 ||
                    totalBalance == 0 ||
                    lastUpdateTimestamp == block.timestamp ||
                    lastUpdateTimestamp >= distributionEnd
                  ) {
                    return currentIndex;
                  }
                  uint256 currentTimestamp =
                    block.timestamp > distributionEnd ? distributionEnd : block.timestamp;
                  uint256 timeDelta = currentTimestamp.sub(lastUpdateTimestamp);
                  return
                    emissionPerSecond.mul(timeDelta).mul(10**uint256(PRECISION)).div(totalBalance).add(
                      currentIndex
                    );
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.7.5;
              pragma experimental ABIEncoderV2;
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              interface IAaveDistributionManager {
                
                event AssetConfigUpdated(address indexed asset, uint256 emission);
                event AssetIndexUpdated(address indexed asset, uint256 index);
                event UserIndexUpdated(address indexed user, address indexed asset, uint256 index);
                event DistributionEndUpdated(uint256 newDistributionEnd);
                /**
                * @dev Sets the end date for the distribution
                * @param distributionEnd The end date timestamp
                **/
                function setDistributionEnd(uint256 distributionEnd) external;
                /**
                * @dev Gets the end date for the distribution
                * @return The end of the distribution
                **/
                function getDistributionEnd() external view returns (uint256);
                /**
                * @dev for backwards compatibility with the previous DistributionManager used
                * @return The end of the distribution
                **/
                function DISTRIBUTION_END() external view returns(uint256);
                 /**
                 * @dev Returns the data of an user on a distribution
                 * @param user Address of the user
                 * @param asset The address of the reference asset of the distribution
                 * @return The new index
                 **/
                 function getUserAssetData(address user, address asset) external view returns (uint256);
                 /**
                 * @dev Returns the configuration of the distribution for a certain asset
                 * @param asset The address of the reference asset of the distribution
                 * @return The asset index, the emission per second and the last updated timestamp
                 **/
                 function getAssetData(address asset) external view returns (uint256, uint256, uint256);
              }
              
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.7.5;
              /// @title Optimized overflow and underflow safe math operations
              /// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost
              /// inspired by uniswap V3
              library SafeMath {
                  /// @notice Returns x + y, reverts if sum overflows uint256
                  /// @param x The augend
                  /// @param y The addend
                  /// @return z The sum of x and y
                  function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
                      require((z = x + y) >= x);
                  }
                  /// @notice Returns x - y, reverts if underflows
                  /// @param x The minuend
                  /// @param y The subtrahend
                  /// @return z The difference of x and y
                  function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
                      require((z = x - y) <= x);
                  }
                  /// @notice Returns x * y, reverts if overflows
                  /// @param x The multiplicand
                  /// @param y The multiplier
                  /// @return z The product of x and y
                  function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
                      require(x == 0 || (z = x * y) / x == y);
                  }
                  function div(uint256 x, uint256 y) internal pure returns(uint256) {
                      // no need to check for division by zero - solidity already reverts
                      return x / y;
                  }
              }// SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.7.5;
              pragma experimental ABIEncoderV2;
              library DistributionTypes {
                struct AssetConfigInput {
                  uint104 emissionPerSecond;
                  uint256 totalStaked;
                  address underlyingAsset;
                }
                struct UserStakeInput {
                  address underlyingAsset;
                  uint256 stakedByUser;
                  uint256 totalStaked;
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.7.5;
              pragma experimental ABIEncoderV2;
              import {SafeERC20} from '@aave/aave-stake/contracts/lib/SafeERC20.sol';
              import {SafeMath} from '../lib/SafeMath.sol';
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              import {VersionedInitializable} from '@aave/aave-stake/contracts/utils/VersionedInitializable.sol';
              import {DistributionManager} from './DistributionManager.sol';
              import {IStakedTokenWithConfig} from '../interfaces/IStakedTokenWithConfig.sol';
              import {IERC20} from '@aave/aave-stake/contracts/interfaces/IERC20.sol';
              import {IScaledBalanceToken} from '../interfaces/IScaledBalanceToken.sol';
              import {IAaveIncentivesController} from '../interfaces/IAaveIncentivesController.sol';
              /**
               * @title StakedTokenIncentivesController
               * @notice Distributor contract for rewards to the Aave protocol, using a staked token as rewards asset.
               * The contract stakes the rewards before redistributing them to the Aave protocol participants.
               * The reference staked token implementation is at https://github.com/aave/aave-stake-v2
               * @author Aave
               **/
              contract StakedTokenIncentivesController is
                IAaveIncentivesController,
                VersionedInitializable,
                DistributionManager
              {
                using SafeMath for uint256;
                using SafeERC20 for IERC20;
                uint256 public constant REVISION = 2;
                IStakedTokenWithConfig public immutable STAKE_TOKEN;
                mapping(address => uint256) internal _usersUnclaimedRewards;
                // this mapping allows whitelisted addresses to claim on behalf of others
                // useful for contracts that hold tokens to be rewarded but don't have any native logic to claim Liquidity Mining rewards
                mapping(address => address) internal _authorizedClaimers;
                modifier onlyAuthorizedClaimers(address claimer, address user) {
                  require(_authorizedClaimers[user] == claimer, 'CLAIMER_UNAUTHORIZED');
                  _;
                }
                constructor(IStakedTokenWithConfig stakeToken, address emissionManager)
                  DistributionManager(emissionManager)
                {
                  STAKE_TOKEN = stakeToken;
                }
                /**
                 * @dev Initialize IStakedTokenIncentivesController. Empty after REVISION 1, but maintains the expected interface.
                 **/
                function initialize(address) external initializer {}
                /// @inheritdoc IAaveIncentivesController
                function configureAssets(address[] calldata assets, uint256[] calldata emissionsPerSecond)
                  external
                  override
                  onlyEmissionManager
                {
                  require(assets.length == emissionsPerSecond.length, 'INVALID_CONFIGURATION');
                  DistributionTypes.AssetConfigInput[] memory assetsConfig =
                    new DistributionTypes.AssetConfigInput[](assets.length);
                  for (uint256 i = 0; i < assets.length; i++) {
                    assetsConfig[i].underlyingAsset = assets[i];
                    assetsConfig[i].emissionPerSecond = uint104(emissionsPerSecond[i]);
                    require(assetsConfig[i].emissionPerSecond == emissionsPerSecond[i], 'INVALID_CONFIGURATION');
                    assetsConfig[i].totalStaked = IScaledBalanceToken(assets[i]).scaledTotalSupply();
                  }
                  _configureAssets(assetsConfig);
                }
                /// @inheritdoc IAaveIncentivesController
                function handleAction(
                  address user,
                  uint256 totalSupply,
                  uint256 userBalance
                ) external override {
                  uint256 accruedRewards = _updateUserAssetInternal(user, msg.sender, userBalance, totalSupply);
                  if (accruedRewards != 0) {
                    _usersUnclaimedRewards[user] = _usersUnclaimedRewards[user].add(accruedRewards);
                    emit RewardsAccrued(user, accruedRewards);
                  }
                }
                /// @inheritdoc IAaveIncentivesController
                function getRewardsBalance(address[] calldata assets, address user)
                  external
                  view
                  override
                  returns (uint256)
                {
                  uint256 unclaimedRewards = _usersUnclaimedRewards[user];
                  DistributionTypes.UserStakeInput[] memory userState =
                    new DistributionTypes.UserStakeInput[](assets.length);
                  for (uint256 i = 0; i < assets.length; i++) {
                    userState[i].underlyingAsset = assets[i];
                    (userState[i].stakedByUser, userState[i].totalStaked) = IScaledBalanceToken(assets[i])
                      .getScaledUserBalanceAndSupply(user);
                  }
                  unclaimedRewards = unclaimedRewards.add(_getUnclaimedRewards(user, userState));
                  return unclaimedRewards;
                }
                /// @inheritdoc IAaveIncentivesController
                function claimRewards(
                  address[] calldata assets,
                  uint256 amount,
                  address to
                ) external override returns (uint256) {
                  require(to != address(0), 'INVALID_TO_ADDRESS');
                  return _claimRewards(assets, amount, msg.sender, msg.sender, to);
                }
                /// @inheritdoc IAaveIncentivesController
                function claimRewardsOnBehalf(
                  address[] calldata assets,
                  uint256 amount,
                  address user,
                  address to
                ) external override onlyAuthorizedClaimers(msg.sender, user) returns (uint256) {
                  require(user != address(0), 'INVALID_USER_ADDRESS');
                  require(to != address(0), 'INVALID_TO_ADDRESS');
                  return _claimRewards(assets, amount, msg.sender, user, to);
                }
                /// @inheritdoc IAaveIncentivesController
                function claimRewardsToSelf(address[] calldata assets, uint256 amount)
                  external
                  override
                  returns (uint256)
                {
                  return _claimRewards(assets, amount, msg.sender, msg.sender, msg.sender);
                }
                /**
                 * @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards.
                 * @param amount Amount of rewards to claim
                 * @param user Address to check and claim rewards
                 * @param to Address that will be receiving the rewards
                 * @return Rewards claimed
                 **/
                /// @inheritdoc IAaveIncentivesController
                function setClaimer(address user, address caller) external override onlyEmissionManager {
                  _authorizedClaimers[user] = caller;
                  emit ClaimerSet(user, caller);
                }
                /// @inheritdoc IAaveIncentivesController
                function getClaimer(address user) external view override returns (address) {
                  return _authorizedClaimers[user];
                }
                /// @inheritdoc IAaveIncentivesController
                function getUserUnclaimedRewards(address _user) external view override returns (uint256) {
                  return _usersUnclaimedRewards[_user];
                }
                /// @inheritdoc IAaveIncentivesController
                function REWARD_TOKEN() external view override returns (address) {
                  return address(STAKE_TOKEN);
                }
                /**
                 * @dev returns the revision of the implementation contract
                 */
                function getRevision() internal pure override returns (uint256) {
                  return REVISION;
                }
                /**
                 * @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards.
                 * @param amount Amount of rewards to claim
                 * @param user Address to check and claim rewards
                 * @param to Address that will be receiving the rewards
                 * @return Rewards claimed
                 **/
                function _claimRewards(
                  address[] calldata assets,
                  uint256 amount,
                  address claimer,
                  address user,
                  address to
                ) internal returns (uint256) {
                  if (amount == 0) {
                    return 0;
                  }
                  uint256 unclaimedRewards = _usersUnclaimedRewards[user];
                  DistributionTypes.UserStakeInput[] memory userState =
                    new DistributionTypes.UserStakeInput[](assets.length);
                  for (uint256 i = 0; i < assets.length; i++) {
                    userState[i].underlyingAsset = assets[i];
                    (userState[i].stakedByUser, userState[i].totalStaked) = IScaledBalanceToken(assets[i])
                      .getScaledUserBalanceAndSupply(user);
                  }
                  uint256 accruedRewards = _claimRewards(user, userState);
                  if (accruedRewards != 0) {
                    unclaimedRewards = unclaimedRewards.add(accruedRewards);
                    emit RewardsAccrued(user, accruedRewards);
                  }
                  if (unclaimedRewards == 0) {
                    return 0;
                  }
                  uint256 amountToClaim = amount > unclaimedRewards ? unclaimedRewards : amount;
                  _usersUnclaimedRewards[user] = unclaimedRewards - amountToClaim; // Safe due to the previous line
                  STAKE_TOKEN.stake(to, amountToClaim);
                  emit RewardsClaimed(user, to, claimer, amountToClaim);
                  return amountToClaim;
                }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.7.5;
              import {IERC20} from '../interfaces/IERC20.sol';
              import {SafeMath} from './SafeMath.sol';
              import {Address} from './Address.sol';
              /**
               * @title SafeERC20
               * @dev From https://github.com/OpenZeppelin/openzeppelin-contracts
               * 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));
                }
                function safeApprove(
                  IERC20 token,
                  address spender,
                  uint256 value
                ) internal {
                  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 callOptionalReturn(IERC20 token, bytes memory data) private {
                  require(address(token).isContract(), 'SafeERC20: call to non-contract');
                  // solhint-disable-next-line avoid-low-level-calls
                  (bool success, bytes memory returndata) = address(token).call(data);
                  require(success, '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: agpl-3.0
              pragma solidity 0.7.5;
              /**
               * @title VersionedInitializable
               *
               * @dev Helper contract to support initializer functions. To use it, replace
               * the constructor with a function that has the `initializer` modifier.
               * WARNING: Unlike constructors, initializer functions must be manually
               * invoked. This applies both to deploying an Initializable contract, as well
               * as extending an Initializable contract via inheritance.
               * WARNING: When used with inheritance, manual care must be taken to not invoke
               * a parent initializer twice, or ensure that all initializers are idempotent,
               * because this is not dealt with automatically as with constructors.
               *
               * @author Aave, inspired by the OpenZeppelin Initializable contract
               */
              abstract contract VersionedInitializable {
                /**
                 * @dev Indicates that the contract has been initialized.
                 */
                uint256 internal lastInitializedRevision = 0;
                /**
                 * @dev Modifier to use in the initializer function of a contract.
                 */
                modifier initializer() {
                  uint256 revision = getRevision();
                  require(revision > lastInitializedRevision, 'Contract instance has already been initialized');
                  lastInitializedRevision = revision;
                  _;
                }
                /// @dev returns the revision number of the contract.
                /// Needs to be defined in the inherited class as a constant.
                function getRevision() internal pure virtual returns (uint256);
                // Reserved storage space to allow for layout changes in the future.
                uint256[50] private ______gap;
              }
              // SPDX-License-Identifier: AGPL-3.0
              pragma solidity 0.7.5;
              import {IStakedToken} from '@aave/aave-stake/contracts/interfaces/IStakedToken.sol';
              interface IStakedTokenWithConfig is IStakedToken {
                function STAKED_TOKEN() external view returns(address);
              }// SPDX-License-Identifier: MIT
              pragma solidity 0.7.5;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               * From https://github.com/OpenZeppelin/openzeppelin-contracts
               */
              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: agpl-3.0
              pragma solidity 0.7.5;
              interface IScaledBalanceToken {
                /**
                 * @dev Returns the scaled balance of the user. The scaled balance is the sum of all the
                 * updated stored balance divided by the reserve's liquidity index at the moment of the update
                 * @param user The user whose balance is calculated
                 * @return The scaled balance of the user
                 **/
                function scaledBalanceOf(address user) external view returns (uint256);
                /**
                 * @dev Returns the scaled balance of the user and the scaled total supply.
                 * @param user The address of the user
                 * @return The scaled balance of the user
                 * @return The scaled balance and the scaled total supply
                 **/
                function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256);
                /**
                 * @dev Returns the scaled total supply of the token. Represents sum(debt/index)
                 * @return The scaled total supply
                 **/
                function scaledTotalSupply() external view returns (uint256);
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.7.5;
              pragma experimental ABIEncoderV2;
              import {IAaveDistributionManager} from '../interfaces/IAaveDistributionManager.sol';
              interface IAaveIncentivesController is IAaveDistributionManager {
                
                event RewardsAccrued(address indexed user, uint256 amount);
                
                event RewardsClaimed(
                  address indexed user,
                  address indexed to,
                  address indexed claimer,
                  uint256 amount
                );
                event ClaimerSet(address indexed user, address indexed claimer);
                /**
                 * @dev Whitelists an address to claim the rewards on behalf of another address
                 * @param user The address of the user
                 * @param claimer The address of the claimer
                 */
                function setClaimer(address user, address claimer) external;
                /**
                 * @dev Returns the whitelisted claimer for a certain address (0x0 if not set)
                 * @param user The address of the user
                 * @return The claimer address
                 */
                function getClaimer(address user) external view returns (address);
                /**
                 * @dev Configure assets for a certain rewards emission
                 * @param assets The assets to incentivize
                 * @param emissionsPerSecond The emission for each asset
                 */
                function configureAssets(address[] calldata assets, uint256[] calldata emissionsPerSecond)
                  external;
                /**
                 * @dev Called by the corresponding asset on any update that affects the rewards distribution
                 * @param asset The address of the user
                 * @param userBalance The balance of the user of the asset in the lending pool
                 * @param totalSupply The total supply of the asset in the lending pool
                 **/
                function handleAction(
                  address asset,
                  uint256 userBalance,
                  uint256 totalSupply
                ) external;
                /**
                 * @dev Returns the total of rewards of an user, already accrued + not yet accrued
                 * @param user The address of the user
                 * @return The rewards
                 **/
                function getRewardsBalance(address[] calldata assets, address user)
                  external
                  view
                  returns (uint256);
                /**
                 * @dev Claims reward for an user, on all the assets of the lending pool, accumulating the pending rewards
                 * @param amount Amount of rewards to claim
                 * @param to Address that will be receiving the rewards
                 * @return Rewards claimed
                 **/
                function claimRewards(
                  address[] calldata assets,
                  uint256 amount,
                  address to
                ) external returns (uint256);
                /**
                 * @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards. The caller must
                 * be whitelisted via "allowClaimOnBehalf" function by the RewardsAdmin role manager
                 * @param amount Amount of rewards to claim
                 * @param user Address to check and claim rewards
                 * @param to Address that will be receiving the rewards
                 * @return Rewards claimed
                 **/
                function claimRewardsOnBehalf(
                  address[] calldata assets,
                  uint256 amount,
                  address user,
                  address to
                ) external returns (uint256);
                /**
                 * @dev Claims rewards for a user, on the specified assets of the lending pool, distributing the pending rewards to self
                 * @param assets Incentivized assets on which to claim rewards
                 * @param amount Amount of rewards to claim
                 * @return Rewards claimed
                 **/
                function claimRewardsToSelf(address[] calldata assets, uint256 amount) external returns (uint256);
                /**
                 * @dev returns the unclaimed rewards of the user
                 * @param user the address of the user
                 * @return the unclaimed user rewards
                 */
                function getUserUnclaimedRewards(address user) external view returns (uint256);
                /**
                * @dev for backward compatibility with previous implementation of the Incentives controller
                */
                function REWARD_TOKEN() external view returns (address);
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.7.5;
              /**
               * @dev From https://github.com/OpenZeppelin/openzeppelin-contracts
               * 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, 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) {
                  return sub(a, b, 'SafeMath: subtraction overflow');
                }
                /**
                 * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                 * overflow (when the result is negative).
                 *
                 * 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);
                  uint256 c = a - b;
                  return c;
                }
                /**
                 * @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) {
                  // 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 0;
                  }
                  uint256 c = a * b;
                  require(c / a == b, 'SafeMath: multiplication overflow');
                  return c;
                }
                /**
                 * @dev Returns the integer division of two unsigned integers. Reverts 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) {
                  return div(a, b, 'SafeMath: division by zero');
                }
                /**
                 * @dev Returns the integer division of two unsigned integers. Reverts with custom message 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,
                  string memory errorMessage
                ) internal pure returns (uint256) {
                  // Solidity only automatically asserts when dividing by 0
                  require(b > 0, errorMessage);
                  uint256 c = a / b;
                  // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                  return c;
                }
                /**
                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                 * Reverts 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) {
                  return mod(a, b, 'SafeMath: modulo by zero');
                }
                /**
                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                 * Reverts with custom message 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,
                  string memory errorMessage
                ) internal pure returns (uint256) {
                  require(b != 0, errorMessage);
                  return a % b;
                }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.7.5;
              /**
               * @dev Collection of functions related to the address type
               * From https://github.com/OpenZeppelin/openzeppelin-contracts
               */
              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) {
                  // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                  // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                  // for accounts without code, i.e. `keccak256('')`
                  bytes32 codehash;
                  bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                  // solhint-disable-next-line no-inline-assembly
                  assembly {
                    codehash := extcodehash(account)
                  }
                  return (codehash != accountHash && codehash != 0x0);
                }
                /**
                 * @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');
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity 0.7.5;
              interface IStakedToken {
                
                function stake(address to, uint256 amount) external;
                function redeem(address to, uint256 amount) external;
                function cooldown() external;
                function claimRewards(address to, uint256 amount) external;
              }
              

              File 8 of 9: StakedAaveV3
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol';
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              import {StakedTokenV3} from './StakedTokenV3.sol';
              import {IGhoVariableDebtTokenTransferHook} from '../interfaces/IGhoVariableDebtTokenTransferHook.sol';
              import {SafeCast} from '../lib/SafeCast.sol';
              import {IStakedAaveV3} from '../interfaces/IStakedAaveV3.sol';
              /**
               * @title StakedAaveV3
               * @notice StakedTokenV3 with AAVE token as staked token
               * @author BGD Labs
               */
              contract StakedAaveV3 is StakedTokenV3, IStakedAaveV3 {
                using SafeCast for uint256;
                uint256[1] private ______DEPRECATED_FROM_STK_AAVE_V3;
                /// @notice GHO debt token to be used in the _beforeTokenTransfer hook
                IGhoVariableDebtTokenTransferHook public ghoDebtToken;
                function REVISION() public pure virtual override returns (uint256) {
                  return 6;
                }
                constructor(
                  IERC20 stakedToken,
                  IERC20 rewardToken,
                  uint256 unstakeWindow,
                  address rewardsVault,
                  address emissionManager,
                  uint128 distributionDuration
                )
                  StakedTokenV3(
                    stakedToken,
                    rewardToken,
                    unstakeWindow,
                    rewardsVault,
                    emissionManager,
                    distributionDuration
                  )
                {
                  // brick initialize
                  lastInitializedRevision = REVISION();
                }
                /**
                 * @dev Called by the proxy contract
                 */
                function initialize() external override initializer {}
                /// @inheritdoc IStakedAaveV3
                function claimRewardsAndStake(
                  address to,
                  uint256 amount
                ) external override returns (uint256) {
                  return _claimRewardsAndStakeOnBehalf(msg.sender, to, amount);
                }
                /// @inheritdoc IStakedAaveV3
                function claimRewardsAndStakeOnBehalf(
                  address from,
                  address to,
                  uint256 amount
                ) external override onlyClaimHelper returns (uint256) {
                  return _claimRewardsAndStakeOnBehalf(from, to, amount);
                }
                /**
                 * - On _transfer, it updates discount, rewards & delegation for both "from" and "to"
                 * - On _mint, only for _to
                 * - On _burn, only for _from
                 * @param from token sender
                 * @param to token recipient
                 * @param fromBalanceBefore balance of the sender before transfer
                 * @param toBalanceBefore balance of the recipient before transfer
                 * @param amount amount of tokens sent
                 */
                function _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 fromBalanceBefore,
                  uint256 toBalanceBefore,
                  uint256 amount
                ) internal override {
                  super._afterTokenTransfer(
                    from,
                    to,
                    fromBalanceBefore,
                    toBalanceBefore,
                    amount
                  );
                  address cachedGhoDebtToken = address(ghoDebtToken);
                  if (cachedGhoDebtToken != address(0)) {
                    _updateDiscountDistribution(
                      cachedGhoDebtToken,
                      from,
                      to,
                      fromBalanceBefore,
                      toBalanceBefore,
                      amount
                    );
                  }
                }
                /// @notice Assembly implementation of the gas limited call to avoid return gas bomb,
                /// moreover call would also revert even inside try-catch block in Solidity 0.8.17
                function _updateDiscountDistribution(
                  address cachedGhoDebtToken,
                  address from,
                  address to,
                  uint256 fromBalanceBefore,
                  uint256 toBalanceBefore,
                  uint256 amount
                ) internal {
                  bytes4 selector = IGhoVariableDebtTokenTransferHook
                    .updateDiscountDistribution
                    .selector;
                  uint256 gasLimit = 220_000;
                  /// @solidity memory-safe-assembly
                  assembly {
                    // solhint-disable-line no-inline-assembly
                    let ptr := mload(0x40)
                    mstore(ptr, selector)
                    mstore(add(ptr, 0x04), from)
                    mstore(add(ptr, 0x24), to)
                    mstore(add(ptr, 0x44), fromBalanceBefore)
                    mstore(add(ptr, 0x64), toBalanceBefore)
                    mstore(add(ptr, 0x84), amount)
                    let gasLeft := gas()
                    if iszero(call(gasLimit, cachedGhoDebtToken, 0, ptr, 0xA4, 0, 0)) {
                      if lt(div(mul(gasLeft, 63), 64), gasLimit) {
                        returndatacopy(ptr, 0, returndatasize())
                        revert(ptr, returndatasize())
                      }
                    }
                  }
                }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
              pragma solidity ^0.8.19;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  /**
                   * @dev Emitted when `value` tokens are moved from one account (`from`) to
                   * another (`to`).
                   *
                   * Note that `value` may be zero.
                   */
                  event Transfer(address indexed from, address indexed to, uint256 value);
                  /**
                   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                   * a call to {approve}. `value` is the new allowance.
                   */
                  event Approval(address indexed owner, address indexed spender, uint256 value);
                  /**
                   * @dev Returns the 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 `to`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address to, 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 `from` to `to` 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 from, address to, uint256 amount) external returns (bool);
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              pragma experimental ABIEncoderV2;
              library DistributionTypes {
                  struct AssetConfigInput {
                      uint128 emissionPerSecond;
                      uint256 totalStaked;
                      address underlyingAsset;
                  }
                  struct UserStakeInput {
                      address underlyingAsset;
                      uint256 stakedByUser;
                      uint256 totalStaked;
                  }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol';
              import {IERC20Metadata} from 'openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol';
              import {BaseDelegation} from 'aave-token-v3/BaseDelegation.sol';
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              import {SafeERC20} from '../lib/SafeERC20.sol';
              import {IAaveDistributionManager} from '../interfaces/IAaveDistributionManager.sol';
              import {IStakedTokenV2} from '../interfaces/IStakedTokenV2.sol';
              import {StakedTokenV2} from './StakedTokenV2.sol';
              import {IStakedTokenV3} from '../interfaces/IStakedTokenV3.sol';
              import {PercentageMath} from '../lib/PercentageMath.sol';
              import {RoleManager} from '../utils/RoleManager.sol';
              import {SafeCast} from '../lib/SafeCast.sol';
              import {IERC20WithPermit} from '../interfaces/IERC20WithPermit.sol';
              /**
               * @title StakedTokenV3
               * @notice Contract to stake Aave token, tokenize the position and get rewards, inheriting from a distribution manager contract
               * @author BGD Labs
               */
              contract StakedTokenV3 is
                StakedTokenV2,
                IStakedTokenV3,
                RoleManager,
                IAaveDistributionManager,
                BaseDelegation
              {
                using SafeERC20 for IERC20;
                using PercentageMath for uint256;
                using SafeCast for uint256;
                using SafeCast for uint104;
                uint256 public constant SLASH_ADMIN_ROLE = 0;
                uint256 public constant COOLDOWN_ADMIN_ROLE = 1;
                uint256 public constant CLAIM_HELPER_ROLE = 2;
                uint216 public constant INITIAL_EXCHANGE_RATE = 1e18;
                uint256 public constant EXCHANGE_RATE_UNIT = 1e18;
                /// @notice lower bound to prevent spam & avoid exchangeRate issues
                // as returnFunds can be called permissionless an attacker could spam returnFunds(1) to produce exchangeRate snapshots making voting expensive
                uint256 public immutable LOWER_BOUND;
                // Reserved storage space to allow for layout changes in the future.
                uint256[6] private ______gap;
                /// @notice Seconds between starting cooldown and being able to withdraw
                uint256 internal _cooldownSeconds;
                /// @notice The maximum amount of funds that can be slashed at any given time
                uint256 internal _maxSlashablePercentage;
                /// @notice Mirror of latest snapshot value for cheaper access
                uint216 internal _currentExchangeRate;
                /// @notice Flag determining if there's an ongoing slashing event that needs to be settled
                bool public inPostSlashingPeriod;
                modifier onlySlashingAdmin() {
                  require(
                    msg.sender == getAdmin(SLASH_ADMIN_ROLE),
                    'CALLER_NOT_SLASHING_ADMIN'
                  );
                  _;
                }
                modifier onlyCooldownAdmin() {
                  require(
                    msg.sender == getAdmin(COOLDOWN_ADMIN_ROLE),
                    'CALLER_NOT_COOLDOWN_ADMIN'
                  );
                  _;
                }
                modifier onlyClaimHelper() {
                  require(
                    msg.sender == getAdmin(CLAIM_HELPER_ROLE),
                    'CALLER_NOT_CLAIM_HELPER'
                  );
                  _;
                }
                constructor(
                  IERC20 stakedToken,
                  IERC20 rewardToken,
                  uint256 unstakeWindow,
                  address rewardsVault,
                  address emissionManager,
                  uint128 distributionDuration
                )
                  StakedTokenV2(
                    stakedToken,
                    rewardToken,
                    unstakeWindow,
                    rewardsVault,
                    emissionManager,
                    distributionDuration
                  )
                {
                  // brick initialize
                  lastInitializedRevision = REVISION();
                  uint256 decimals = IERC20Metadata(address(stakedToken)).decimals();
                  LOWER_BOUND = 10 ** decimals;
                }
                /**
                 * @dev returns the revision of the implementation contract
                 * @return The revision
                 */
                function REVISION() public pure virtual returns (uint256) {
                  return 4;
                }
                /**
                 * @dev returns the revision of the implementation contract
                 * @return The revision
                 */
                function getRevision() internal pure virtual override returns (uint256) {
                  return REVISION();
                }
                /**
                 * @dev Called by the proxy contract
                 */
                function initialize() external virtual initializer {}
                function _initialize(
                  address slashingAdmin,
                  address cooldownPauseAdmin,
                  address claimHelper,
                  uint256 maxSlashablePercentage,
                  uint256 cooldownSeconds
                ) internal {
                  InitAdmin[] memory initAdmins = new InitAdmin[](3);
                  initAdmins[0] = InitAdmin(SLASH_ADMIN_ROLE, slashingAdmin);
                  initAdmins[1] = InitAdmin(COOLDOWN_ADMIN_ROLE, cooldownPauseAdmin);
                  initAdmins[2] = InitAdmin(CLAIM_HELPER_ROLE, claimHelper);
                  _initAdmins(initAdmins);
                  _setMaxSlashablePercentage(maxSlashablePercentage);
                  _setCooldownSeconds(cooldownSeconds);
                  _updateExchangeRate(INITIAL_EXCHANGE_RATE);
                }
                /// @inheritdoc IAaveDistributionManager
                function configureAssets(
                  DistributionTypes.AssetConfigInput[] memory assetsConfigInput
                ) external override {
                  require(msg.sender == EMISSION_MANAGER, 'ONLY_EMISSION_MANAGER');
                  for (uint256 i = 0; i < assetsConfigInput.length; i++) {
                    assetsConfigInput[i].totalStaked = totalSupply();
                  }
                  _configureAssets(assetsConfigInput);
                }
                /// @inheritdoc IStakedTokenV3
                function previewStake(uint256 assets) public view returns (uint256) {
                  return (assets * _currentExchangeRate) / EXCHANGE_RATE_UNIT;
                }
                /// @inheritdoc IStakedTokenV2
                function stake(
                  address to,
                  uint256 amount
                ) external override(IStakedTokenV2, StakedTokenV2) {
                  _stake(msg.sender, to, amount);
                }
                /// @inheritdoc IStakedTokenV3
                function stakeWithPermit(
                  uint256 amount,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
                ) external override {
                  try
                    IERC20WithPermit(address(STAKED_TOKEN)).permit(
                      msg.sender,
                      address(this),
                      amount,
                      deadline,
                      v,
                      r,
                      s
                    )
                  {
                    // do nothing
                  } catch (bytes memory) {
                    // do nothing
                  }
                  _stake(msg.sender, msg.sender, amount);
                }
                /// @inheritdoc IStakedTokenV2
                function cooldown() external override(IStakedTokenV2, StakedTokenV2) {
                  _cooldown(msg.sender);
                }
                /// @inheritdoc IStakedTokenV3
                function cooldownOnBehalfOf(address from) external override onlyClaimHelper {
                  _cooldown(from);
                }
                function _cooldown(address from) internal {
                  uint256 amount = balanceOf(from);
                  require(amount != 0, 'INVALID_BALANCE_ON_COOLDOWN');
                  stakersCooldowns[from] = CooldownSnapshot({
                    timestamp: uint40(block.timestamp),
                    amount: uint216(amount)
                  });
                  emit Cooldown(from, amount);
                }
                /// @inheritdoc IStakedTokenV2
                function redeem(
                  address to,
                  uint256 amount
                ) external override(IStakedTokenV2, StakedTokenV2) {
                  _redeem(msg.sender, to, amount.toUint104());
                }
                /// @inheritdoc IStakedTokenV3
                function redeemOnBehalf(
                  address from,
                  address to,
                  uint256 amount
                ) external override onlyClaimHelper {
                  _redeem(from, to, amount.toUint104());
                }
                /// @inheritdoc IStakedTokenV2
                function claimRewards(
                  address to,
                  uint256 amount
                ) external override(IStakedTokenV2, StakedTokenV2) {
                  _claimRewards(msg.sender, to, amount);
                }
                /// @inheritdoc IStakedTokenV3
                function claimRewardsOnBehalf(
                  address from,
                  address to,
                  uint256 amount
                ) external override onlyClaimHelper returns (uint256) {
                  return _claimRewards(from, to, amount);
                }
                /// @inheritdoc IStakedTokenV3
                function claimRewardsAndRedeem(
                  address to,
                  uint256 claimAmount,
                  uint256 redeemAmount
                ) external override {
                  _claimRewards(msg.sender, to, claimAmount);
                  _redeem(msg.sender, to, redeemAmount.toUint104());
                }
                /// @inheritdoc IStakedTokenV3
                function claimRewardsAndRedeemOnBehalf(
                  address from,
                  address to,
                  uint256 claimAmount,
                  uint256 redeemAmount
                ) external override onlyClaimHelper {
                  _claimRewards(from, to, claimAmount);
                  _redeem(from, to, redeemAmount.toUint104());
                }
                /// @inheritdoc IStakedTokenV3
                function getExchangeRate() public view override returns (uint216) {
                  return _currentExchangeRate;
                }
                /// @inheritdoc IStakedTokenV3
                function previewRedeem(
                  uint256 shares
                ) public view override returns (uint256) {
                  return (EXCHANGE_RATE_UNIT * shares) / _currentExchangeRate;
                }
                /// @inheritdoc IStakedTokenV3
                function slash(
                  address destination,
                  uint256 amount
                ) external override onlySlashingAdmin returns (uint256) {
                  require(!inPostSlashingPeriod, 'PREVIOUS_SLASHING_NOT_SETTLED');
                  require(amount > 0, 'ZERO_AMOUNT');
                  uint256 currentShares = totalSupply();
                  uint256 balance = previewRedeem(currentShares);
                  uint256 maxSlashable = balance.percentMul(_maxSlashablePercentage);
                  if (amount > maxSlashable) {
                    amount = maxSlashable;
                  }
                  require(balance - amount >= LOWER_BOUND, 'REMAINING_LT_MINIMUM');
                  inPostSlashingPeriod = true;
                  _updateExchangeRate(_getExchangeRate(balance - amount, currentShares));
                  STAKED_TOKEN.safeTransfer(destination, amount);
                  emit Slashed(destination, amount);
                  return amount;
                }
                /// @inheritdoc IStakedTokenV3
                function returnFunds(uint256 amount) external override {
                  require(amount >= LOWER_BOUND, 'AMOUNT_LT_MINIMUM');
                  uint256 currentShares = totalSupply();
                  require(currentShares >= LOWER_BOUND, 'SHARES_LT_MINIMUM');
                  uint256 assets = previewRedeem(currentShares);
                  _updateExchangeRate(_getExchangeRate(assets + amount, currentShares));
                  STAKED_TOKEN.safeTransferFrom(msg.sender, address(this), amount);
                  emit FundsReturned(amount);
                }
                /// @inheritdoc IStakedTokenV3
                function settleSlashing() external override onlySlashingAdmin {
                  inPostSlashingPeriod = false;
                  emit SlashingSettled();
                }
                /// @inheritdoc IStakedTokenV3
                function setMaxSlashablePercentage(
                  uint256 percentage
                ) external override onlySlashingAdmin {
                  _setMaxSlashablePercentage(percentage);
                }
                /// @inheritdoc IStakedTokenV3
                function getMaxSlashablePercentage()
                  external
                  view
                  override
                  returns (uint256)
                {
                  return _maxSlashablePercentage;
                }
                /// @inheritdoc IStakedTokenV3
                function setCooldownSeconds(
                  uint256 cooldownSeconds
                ) external onlyCooldownAdmin {
                  _setCooldownSeconds(cooldownSeconds);
                }
                /// @inheritdoc IStakedTokenV3
                function getCooldownSeconds() external view returns (uint256) {
                  return _cooldownSeconds;
                }
                /// @inheritdoc IStakedTokenV3
                function COOLDOWN_SECONDS() external view returns (uint256) {
                  return _cooldownSeconds;
                }
                /**
                 * @dev sets the max slashable percentage
                 * @param percentage must be strictly lower 100% as otherwise the exchange rate calculation would result in 0 division
                 */
                function _setMaxSlashablePercentage(uint256 percentage) internal {
                  require(
                    percentage < PercentageMath.PERCENTAGE_FACTOR,
                    'INVALID_SLASHING_PERCENTAGE'
                  );
                  _maxSlashablePercentage = percentage;
                  emit MaxSlashablePercentageChanged(percentage);
                }
                /**
                 * @dev sets the cooldown seconds
                 * @param cooldownSeconds the new amount of cooldown seconds
                 */
                function _setCooldownSeconds(uint256 cooldownSeconds) internal {
                  _cooldownSeconds = cooldownSeconds;
                  emit CooldownSecondsChanged(cooldownSeconds);
                }
                /**
                 * @dev claims the rewards for a specified address to a specified address
                 * @param from The address of the from from which to claim
                 * @param to Address to receive the rewards
                 * @param amount Amount to claim
                 * @return amount claimed
                 */
                function _claimRewards(
                  address from,
                  address to,
                  uint256 amount
                ) internal returns (uint256) {
                  require(amount != 0, 'INVALID_ZERO_AMOUNT');
                  uint256 newTotalRewards = _updateCurrentUnclaimedRewards(
                    from,
                    balanceOf(from),
                    false
                  );
                  uint256 amountToClaim = (amount > newTotalRewards)
                    ? newTotalRewards
                    : amount;
                  require(amountToClaim != 0, 'INVALID_ZERO_AMOUNT');
                  stakerRewardsToClaim[from] = newTotalRewards - amountToClaim;
                  REWARD_TOKEN.safeTransferFrom(REWARDS_VAULT, to, amountToClaim);
                  emit RewardsClaimed(from, to, amountToClaim);
                  return amountToClaim;
                }
                /**
                 * @dev Claims an `amount` of `REWARD_TOKEN` and stakes.
                 * @param from The address of the from from which to claim
                 * @param to Address to stake to
                 * @param amount Amount to claim
                 * @return amount claimed
                 */
                function _claimRewardsAndStakeOnBehalf(
                  address from,
                  address to,
                  uint256 amount
                ) internal returns (uint256) {
                  require(REWARD_TOKEN == STAKED_TOKEN, 'REWARD_TOKEN_IS_NOT_STAKED_TOKEN');
                  uint256 userUpdatedRewards = _updateCurrentUnclaimedRewards(
                    from,
                    balanceOf(from),
                    true
                  );
                  uint256 amountToClaim = (amount > userUpdatedRewards)
                    ? userUpdatedRewards
                    : amount;
                  if (amountToClaim != 0) {
                    _claimRewards(from, address(this), amountToClaim);
                    _stake(address(this), to, amountToClaim);
                  }
                  return amountToClaim;
                }
                /**
                 * @dev Allows staking a specified amount of STAKED_TOKEN
                 * @param to The address to receiving the shares
                 * @param amount The amount of assets to be staked
                 */
                function _stake(address from, address to, uint256 amount) internal {
                  require(!inPostSlashingPeriod, 'SLASHING_ONGOING');
                  require(amount != 0, 'INVALID_ZERO_AMOUNT');
                  uint256 balanceOfTo = balanceOf(to);
                  uint256 accruedRewards = _updateUserAssetInternal(
                    to,
                    address(this),
                    balanceOfTo,
                    totalSupply()
                  );
                  if (accruedRewards != 0) {
                    stakerRewardsToClaim[to] = stakerRewardsToClaim[to] + accruedRewards;
                    emit RewardsAccrued(to, accruedRewards);
                  }
                  uint256 sharesToMint = previewStake(amount);
                  STAKED_TOKEN.safeTransferFrom(from, address(this), amount);
                  _mint(to, sharesToMint.toUint104());
                  emit Staked(from, to, amount, sharesToMint);
                }
                /**
                 * @dev Redeems staked tokens, and stop earning rewards
                 * @param from Address to redeem from
                 * @param to Address to redeem to
                 * @param amount Amount to redeem
                 */
                function _redeem(address from, address to, uint104 amount) internal {
                  require(amount != 0, 'INVALID_ZERO_AMOUNT');
                  CooldownSnapshot memory cooldownSnapshot = stakersCooldowns[from];
                  if (!inPostSlashingPeriod) {
                    require(
                      (block.timestamp >= cooldownSnapshot.timestamp + _cooldownSeconds),
                      'INSUFFICIENT_COOLDOWN'
                    );
                    require(
                      (block.timestamp - (cooldownSnapshot.timestamp + _cooldownSeconds) <=
                        UNSTAKE_WINDOW),
                      'UNSTAKE_WINDOW_FINISHED'
                    );
                  }
                  uint256 balanceOfFrom = balanceOf(from);
                  uint256 maxRedeemable = inPostSlashingPeriod
                    ? balanceOfFrom
                    : cooldownSnapshot.amount;
                  require(maxRedeemable != 0, 'INVALID_ZERO_MAX_REDEEMABLE');
                  uint256 amountToRedeem = (amount > maxRedeemable) ? maxRedeemable : amount;
                  _updateCurrentUnclaimedRewards(from, balanceOfFrom, true);
                  uint256 underlyingToRedeem = previewRedeem(amountToRedeem);
                  _burn(from, amountToRedeem.toUint104());
                  if (cooldownSnapshot.timestamp != 0) {
                    if (cooldownSnapshot.amount - amountToRedeem == 0) {
                      delete stakersCooldowns[from];
                    } else {
                      stakersCooldowns[from].amount =
                        stakersCooldowns[from].amount -
                        amountToRedeem.toUint184();
                    }
                  }
                  IERC20(STAKED_TOKEN).safeTransfer(to, underlyingToRedeem);
                  emit Redeem(from, to, underlyingToRedeem, amountToRedeem);
                }
                /**
                 * @dev Updates the exchangeRate and emits events accordingly
                 * @param newExchangeRate the new exchange rate
                 */
                function _updateExchangeRate(uint216 newExchangeRate) internal virtual {
                  require(newExchangeRate != 0, 'ZERO_EXCHANGE_RATE');
                  _currentExchangeRate = newExchangeRate;
                  emit ExchangeRateChanged(newExchangeRate);
                }
                /**
                 * @dev calculates the exchange rate based on totalAssets and totalShares
                 * @dev always rounds up to ensure 100% backing of shares by rounding in favor of the contract
                 * @param totalAssets The total amount of assets staked
                 * @param totalShares The total amount of shares
                 * @return exchangeRate as 18 decimal precision uint216
                 */
                function _getExchangeRate(
                  uint256 totalAssets,
                  uint256 totalShares
                ) internal pure returns (uint216) {
                  return
                    (((totalShares * EXCHANGE_RATE_UNIT) + totalAssets - 1) / totalAssets)
                      .toUint216();
                }
                function _transfer(
                  address from,
                  address to,
                  uint256 amount
                ) internal override {
                  uint256 balanceOfFrom = balanceOf(from);
                  // Sender
                  _updateCurrentUnclaimedRewards(from, balanceOfFrom, true);
                  // Recipient
                  if (from != to) {
                    uint256 balanceOfTo = balanceOf(to);
                    _updateCurrentUnclaimedRewards(to, balanceOfTo, true);
                    CooldownSnapshot memory previousSenderCooldown = stakersCooldowns[from];
                    if (previousSenderCooldown.timestamp != 0) {
                      // if cooldown was set and whole balance of sender was transferred - clear cooldown
                      if (balanceOfFrom == amount) {
                        delete stakersCooldowns[from];
                      } else if (balanceOfFrom - amount < previousSenderCooldown.amount) {
                        stakersCooldowns[from].amount = uint216(balanceOfFrom - amount);
                      }
                    }
                  }
                  super._transfer(from, to, amount);
                }
                function _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 fromBalanceBefore,
                  uint256 toBalanceBefore,
                  uint256 amount
                ) internal virtual override {
                  _delegationChangeOnTransfer(
                    from,
                    to,
                    fromBalanceBefore,
                    toBalanceBefore,
                    amount
                  );
                }
                function _getDelegationState(
                  address user
                ) internal view override returns (DelegationState memory) {
                  DelegationAwareBalance memory userState = _balances[user];
                  return
                    DelegationState({
                      delegatedPropositionBalance: userState.delegatedPropositionBalance,
                      delegatedVotingBalance: userState.delegatedVotingBalance,
                      delegationMode: userState.delegationMode
                    });
                }
                function _getBalance(address user) internal view override returns (uint256) {
                  return balanceOf(user);
                }
                function getPowerCurrent(
                  address user,
                  GovernancePowerType delegationType
                ) public view override returns (uint256) {
                  return
                    (super.getPowerCurrent(user, delegationType) * EXCHANGE_RATE_UNIT) /
                    getExchangeRate();
                }
                function _setDelegationState(
                  address user,
                  DelegationState memory delegationState
                ) internal override {
                  DelegationAwareBalance storage userState = _balances[user];
                  userState.delegatedPropositionBalance = delegationState
                    .delegatedPropositionBalance;
                  userState.delegatedVotingBalance = delegationState.delegatedVotingBalance;
                  userState.delegationMode = delegationState.delegationMode;
                }
                function _incrementNonces(address user) internal override returns (uint256) {
                  unchecked {
                    // Does not make sense to check because it's not realistic to reach uint256.max in nonce
                    return _nonces[user]++;
                  }
                }
                function _getDomainSeparator() internal view override returns (bytes32) {
                  return DOMAIN_SEPARATOR();
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              interface IGhoVariableDebtTokenTransferHook {
                /**
                 * @dev updates the discount when discount token is transferred
                 * @dev Only callable by discount token
                 * @param sender address of sender
                 * @param recipient address of recipient
                 * @param senderDiscountTokenBalance sender discount token balance
                 * @param recipientDiscountTokenBalance recipient discount token balance
                 * @param amount amount of discount token being transferred
                 **/
                function updateDiscountDistribution(
                  address sender,
                  address recipient,
                  uint256 senderDiscountTokenBalance,
                  uint256 recipientDiscountTokenBalance,
                  uint256 amount
                ) external;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol)
              // This file was procedurally generated from scripts/generate/templates/SafeCast.js.
              pragma solidity ^0.8.0;
              /**
               * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
               * checks.
               *
               * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
               * easily result in undesired exploitation or bugs, since developers usually
               * assume that overflows raise errors. `SafeCast` restores this intuition by
               * reverting the transaction when such an operation overflows.
               *
               * Using this library instead of the unchecked operations eliminates an entire
               * class of bugs, so it's recommended to use it always.
               *
               * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
               * all math on `uint256` and `int256` and then downcasting.
               */
              library SafeCast {
                  /**
                   * @dev Returns the downcasted uint248 from uint256, reverting on
                   * overflow (when the input is greater than largest uint248).
                   *
                   * Counterpart to Solidity's `uint248` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 248 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint248(uint256 value) internal pure returns (uint248) {
                      require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
                      return uint248(value);
                  }
                  /**
                   * @dev Returns the downcasted uint240 from uint256, reverting on
                   * overflow (when the input is greater than largest uint240).
                   *
                   * Counterpart to Solidity's `uint240` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 240 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint240(uint256 value) internal pure returns (uint240) {
                      require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
                      return uint240(value);
                  }
                  /**
                   * @dev Returns the downcasted uint232 from uint256, reverting on
                   * overflow (when the input is greater than largest uint232).
                   *
                   * Counterpart to Solidity's `uint232` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 232 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint232(uint256 value) internal pure returns (uint232) {
                      require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
                      return uint232(value);
                  }
                  /**
                   * @dev Returns the downcasted uint224 from uint256, reverting on
                   * overflow (when the input is greater than largest uint224).
                   *
                   * Counterpart to Solidity's `uint224` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 224 bits
                   *
                   * _Available since v4.2._
                   */
                  function toUint224(uint256 value) internal pure returns (uint224) {
                      require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
                      return uint224(value);
                  }
                  /**
                   * @dev Returns the downcasted uint216 from uint256, reverting on
                   * overflow (when the input is greater than largest uint216).
                   *
                   * Counterpart to Solidity's `uint216` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 216 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint216(uint256 value) internal pure returns (uint216) {
                      require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
                      return uint216(value);
                  }
                  /**
                   * @dev Returns the downcasted uint208 from uint256, reverting on
                   * overflow (when the input is greater than largest uint208).
                   *
                   * Counterpart to Solidity's `uint208` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 208 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint208(uint256 value) internal pure returns (uint208) {
                      require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
                      return uint208(value);
                  }
                  /**
                   * @dev Returns the downcasted uint200 from uint256, reverting on
                   * overflow (when the input is greater than largest uint200).
                   *
                   * Counterpart to Solidity's `uint200` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 200 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint200(uint256 value) internal pure returns (uint200) {
                      require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
                      return uint200(value);
                  }
                  /**
                   * @dev Returns the downcasted uint192 from uint256, reverting on
                   * overflow (when the input is greater than largest uint192).
                   *
                   * Counterpart to Solidity's `uint192` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 192 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint192(uint256 value) internal pure returns (uint192) {
                      require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
                      return uint192(value);
                  }
                  /**
                   * @dev Returns the downcasted uint184 from uint256, reverting on
                   * overflow (when the input is greater than largest uint184).
                   *
                   * Counterpart to Solidity's `uint184` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 184 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint184(uint256 value) internal pure returns (uint184) {
                      require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
                      return uint184(value);
                  }
                  /**
                   * @dev Returns the downcasted uint176 from uint256, reverting on
                   * overflow (when the input is greater than largest uint176).
                   *
                   * Counterpart to Solidity's `uint176` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 176 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint176(uint256 value) internal pure returns (uint176) {
                      require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
                      return uint176(value);
                  }
                  /**
                   * @dev Returns the downcasted uint168 from uint256, reverting on
                   * overflow (when the input is greater than largest uint168).
                   *
                   * Counterpart to Solidity's `uint168` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 168 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint168(uint256 value) internal pure returns (uint168) {
                      require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
                      return uint168(value);
                  }
                  /**
                   * @dev Returns the downcasted uint160 from uint256, reverting on
                   * overflow (when the input is greater than largest uint160).
                   *
                   * Counterpart to Solidity's `uint160` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 160 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint160(uint256 value) internal pure returns (uint160) {
                      require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
                      return uint160(value);
                  }
                  /**
                   * @dev Returns the downcasted uint152 from uint256, reverting on
                   * overflow (when the input is greater than largest uint152).
                   *
                   * Counterpart to Solidity's `uint152` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 152 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint152(uint256 value) internal pure returns (uint152) {
                      require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
                      return uint152(value);
                  }
                  /**
                   * @dev Returns the downcasted uint144 from uint256, reverting on
                   * overflow (when the input is greater than largest uint144).
                   *
                   * Counterpart to Solidity's `uint144` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 144 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint144(uint256 value) internal pure returns (uint144) {
                      require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
                      return uint144(value);
                  }
                  /**
                   * @dev Returns the downcasted uint136 from uint256, reverting on
                   * overflow (when the input is greater than largest uint136).
                   *
                   * Counterpart to Solidity's `uint136` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 136 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint136(uint256 value) internal pure returns (uint136) {
                      require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
                      return uint136(value);
                  }
                  /**
                   * @dev Returns the downcasted uint128 from uint256, reverting on
                   * overflow (when the input is greater than largest uint128).
                   *
                   * Counterpart to Solidity's `uint128` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 128 bits
                   *
                   * _Available since v2.5._
                   */
                  function toUint128(uint256 value) internal pure returns (uint128) {
                      require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
                      return uint128(value);
                  }
                  /**
                   * @dev Returns the downcasted uint120 from uint256, reverting on
                   * overflow (when the input is greater than largest uint120).
                   *
                   * Counterpart to Solidity's `uint120` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 120 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint120(uint256 value) internal pure returns (uint120) {
                      require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
                      return uint120(value);
                  }
                  /**
                   * @dev Returns the downcasted uint112 from uint256, reverting on
                   * overflow (when the input is greater than largest uint112).
                   *
                   * Counterpart to Solidity's `uint112` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 112 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint112(uint256 value) internal pure returns (uint112) {
                      require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
                      return uint112(value);
                  }
                  /**
                   * @dev Returns the downcasted uint104 from uint256, reverting on
                   * overflow (when the input is greater than largest uint104).
                   *
                   * Counterpart to Solidity's `uint104` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 104 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint104(uint256 value) internal pure returns (uint104) {
                      require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
                      return uint104(value);
                  }
                  /**
                   * @dev Returns the downcasted uint96 from uint256, reverting on
                   * overflow (when the input is greater than largest uint96).
                   *
                   * Counterpart to Solidity's `uint96` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 96 bits
                   *
                   * _Available since v4.2._
                   */
                  function toUint96(uint256 value) internal pure returns (uint96) {
                      require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
                      return uint96(value);
                  }
                  /**
                   * @dev Returns the downcasted uint88 from uint256, reverting on
                   * overflow (when the input is greater than largest uint88).
                   *
                   * Counterpart to Solidity's `uint88` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 88 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint88(uint256 value) internal pure returns (uint88) {
                      require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
                      return uint88(value);
                  }
                  /**
                   * @dev Returns the downcasted uint80 from uint256, reverting on
                   * overflow (when the input is greater than largest uint80).
                   *
                   * Counterpart to Solidity's `uint80` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 80 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint80(uint256 value) internal pure returns (uint80) {
                      require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
                      return uint80(value);
                  }
                  /**
                   * @dev Returns the downcasted uint72 from uint256, reverting on
                   * overflow (when the input is greater than largest uint72).
                   *
                   * Counterpart to Solidity's `uint72` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 72 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint72(uint256 value) internal pure returns (uint72) {
                      require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
                      return uint72(value);
                  }
                  /**
                   * @dev Returns the downcasted uint64 from uint256, reverting on
                   * overflow (when the input is greater than largest uint64).
                   *
                   * Counterpart to Solidity's `uint64` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 64 bits
                   *
                   * _Available since v2.5._
                   */
                  function toUint64(uint256 value) internal pure returns (uint64) {
                      require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
                      return uint64(value);
                  }
                  /**
                   * @dev Returns the downcasted uint56 from uint256, reverting on
                   * overflow (when the input is greater than largest uint56).
                   *
                   * Counterpart to Solidity's `uint56` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 56 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint56(uint256 value) internal pure returns (uint56) {
                      require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
                      return uint56(value);
                  }
                  /**
                   * @dev Returns the downcasted uint48 from uint256, reverting on
                   * overflow (when the input is greater than largest uint48).
                   *
                   * Counterpart to Solidity's `uint48` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 48 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint48(uint256 value) internal pure returns (uint48) {
                      require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
                      return uint48(value);
                  }
                  /**
                   * @dev Returns the downcasted uint40 from uint256, reverting on
                   * overflow (when the input is greater than largest uint40).
                   *
                   * Counterpart to Solidity's `uint40` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 40 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint40(uint256 value) internal pure returns (uint40) {
                      require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
                      return uint40(value);
                  }
                  /**
                   * @dev Returns the downcasted uint32 from uint256, reverting on
                   * overflow (when the input is greater than largest uint32).
                   *
                   * Counterpart to Solidity's `uint32` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 32 bits
                   *
                   * _Available since v2.5._
                   */
                  function toUint32(uint256 value) internal pure returns (uint32) {
                      require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
                      return uint32(value);
                  }
                  /**
                   * @dev Returns the downcasted uint24 from uint256, reverting on
                   * overflow (when the input is greater than largest uint24).
                   *
                   * Counterpart to Solidity's `uint24` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 24 bits
                   *
                   * _Available since v4.7._
                   */
                  function toUint24(uint256 value) internal pure returns (uint24) {
                      require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
                      return uint24(value);
                  }
                  /**
                   * @dev Returns the downcasted uint16 from uint256, reverting on
                   * overflow (when the input is greater than largest uint16).
                   *
                   * Counterpart to Solidity's `uint16` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 16 bits
                   *
                   * _Available since v2.5._
                   */
                  function toUint16(uint256 value) internal pure returns (uint16) {
                      require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
                      return uint16(value);
                  }
                  /**
                   * @dev Returns the downcasted uint8 from uint256, reverting on
                   * overflow (when the input is greater than largest uint8).
                   *
                   * Counterpart to Solidity's `uint8` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 8 bits
                   *
                   * _Available since v2.5._
                   */
                  function toUint8(uint256 value) internal pure returns (uint8) {
                      require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
                      return uint8(value);
                  }
                  /**
                   * @dev Converts a signed int256 into an unsigned uint256.
                   *
                   * Requirements:
                   *
                   * - input must be greater than or equal to 0.
                   *
                   * _Available since v3.0._
                   */
                  function toUint256(int256 value) internal pure returns (uint256) {
                      require(value >= 0, "SafeCast: value must be positive");
                      return uint256(value);
                  }
                  /**
                   * @dev Returns the downcasted int248 from int256, reverting on
                   * overflow (when the input is less than smallest int248 or
                   * greater than largest int248).
                   *
                   * Counterpart to Solidity's `int248` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 248 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt248(int256 value) internal pure returns (int248 downcasted) {
                      downcasted = int248(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
                  }
                  /**
                   * @dev Returns the downcasted int240 from int256, reverting on
                   * overflow (when the input is less than smallest int240 or
                   * greater than largest int240).
                   *
                   * Counterpart to Solidity's `int240` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 240 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt240(int256 value) internal pure returns (int240 downcasted) {
                      downcasted = int240(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
                  }
                  /**
                   * @dev Returns the downcasted int232 from int256, reverting on
                   * overflow (when the input is less than smallest int232 or
                   * greater than largest int232).
                   *
                   * Counterpart to Solidity's `int232` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 232 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt232(int256 value) internal pure returns (int232 downcasted) {
                      downcasted = int232(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
                  }
                  /**
                   * @dev Returns the downcasted int224 from int256, reverting on
                   * overflow (when the input is less than smallest int224 or
                   * greater than largest int224).
                   *
                   * Counterpart to Solidity's `int224` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 224 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt224(int256 value) internal pure returns (int224 downcasted) {
                      downcasted = int224(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
                  }
                  /**
                   * @dev Returns the downcasted int216 from int256, reverting on
                   * overflow (when the input is less than smallest int216 or
                   * greater than largest int216).
                   *
                   * Counterpart to Solidity's `int216` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 216 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt216(int256 value) internal pure returns (int216 downcasted) {
                      downcasted = int216(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
                  }
                  /**
                   * @dev Returns the downcasted int208 from int256, reverting on
                   * overflow (when the input is less than smallest int208 or
                   * greater than largest int208).
                   *
                   * Counterpart to Solidity's `int208` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 208 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt208(int256 value) internal pure returns (int208 downcasted) {
                      downcasted = int208(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
                  }
                  /**
                   * @dev Returns the downcasted int200 from int256, reverting on
                   * overflow (when the input is less than smallest int200 or
                   * greater than largest int200).
                   *
                   * Counterpart to Solidity's `int200` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 200 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt200(int256 value) internal pure returns (int200 downcasted) {
                      downcasted = int200(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
                  }
                  /**
                   * @dev Returns the downcasted int192 from int256, reverting on
                   * overflow (when the input is less than smallest int192 or
                   * greater than largest int192).
                   *
                   * Counterpart to Solidity's `int192` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 192 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt192(int256 value) internal pure returns (int192 downcasted) {
                      downcasted = int192(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
                  }
                  /**
                   * @dev Returns the downcasted int184 from int256, reverting on
                   * overflow (when the input is less than smallest int184 or
                   * greater than largest int184).
                   *
                   * Counterpart to Solidity's `int184` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 184 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt184(int256 value) internal pure returns (int184 downcasted) {
                      downcasted = int184(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
                  }
                  /**
                   * @dev Returns the downcasted int176 from int256, reverting on
                   * overflow (when the input is less than smallest int176 or
                   * greater than largest int176).
                   *
                   * Counterpart to Solidity's `int176` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 176 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt176(int256 value) internal pure returns (int176 downcasted) {
                      downcasted = int176(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
                  }
                  /**
                   * @dev Returns the downcasted int168 from int256, reverting on
                   * overflow (when the input is less than smallest int168 or
                   * greater than largest int168).
                   *
                   * Counterpart to Solidity's `int168` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 168 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt168(int256 value) internal pure returns (int168 downcasted) {
                      downcasted = int168(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
                  }
                  /**
                   * @dev Returns the downcasted int160 from int256, reverting on
                   * overflow (when the input is less than smallest int160 or
                   * greater than largest int160).
                   *
                   * Counterpart to Solidity's `int160` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 160 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt160(int256 value) internal pure returns (int160 downcasted) {
                      downcasted = int160(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
                  }
                  /**
                   * @dev Returns the downcasted int152 from int256, reverting on
                   * overflow (when the input is less than smallest int152 or
                   * greater than largest int152).
                   *
                   * Counterpart to Solidity's `int152` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 152 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt152(int256 value) internal pure returns (int152 downcasted) {
                      downcasted = int152(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
                  }
                  /**
                   * @dev Returns the downcasted int144 from int256, reverting on
                   * overflow (when the input is less than smallest int144 or
                   * greater than largest int144).
                   *
                   * Counterpart to Solidity's `int144` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 144 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt144(int256 value) internal pure returns (int144 downcasted) {
                      downcasted = int144(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
                  }
                  /**
                   * @dev Returns the downcasted int136 from int256, reverting on
                   * overflow (when the input is less than smallest int136 or
                   * greater than largest int136).
                   *
                   * Counterpart to Solidity's `int136` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 136 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt136(int256 value) internal pure returns (int136 downcasted) {
                      downcasted = int136(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
                  }
                  /**
                   * @dev Returns the downcasted int128 from int256, reverting on
                   * overflow (when the input is less than smallest int128 or
                   * greater than largest int128).
                   *
                   * Counterpart to Solidity's `int128` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 128 bits
                   *
                   * _Available since v3.1._
                   */
                  function toInt128(int256 value) internal pure returns (int128 downcasted) {
                      downcasted = int128(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
                  }
                  /**
                   * @dev Returns the downcasted int120 from int256, reverting on
                   * overflow (when the input is less than smallest int120 or
                   * greater than largest int120).
                   *
                   * Counterpart to Solidity's `int120` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 120 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt120(int256 value) internal pure returns (int120 downcasted) {
                      downcasted = int120(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
                  }
                  /**
                   * @dev Returns the downcasted int112 from int256, reverting on
                   * overflow (when the input is less than smallest int112 or
                   * greater than largest int112).
                   *
                   * Counterpart to Solidity's `int112` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 112 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt112(int256 value) internal pure returns (int112 downcasted) {
                      downcasted = int112(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
                  }
                  /**
                   * @dev Returns the downcasted int104 from int256, reverting on
                   * overflow (when the input is less than smallest int104 or
                   * greater than largest int104).
                   *
                   * Counterpart to Solidity's `int104` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 104 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt104(int256 value) internal pure returns (int104 downcasted) {
                      downcasted = int104(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
                  }
                  /**
                   * @dev Returns the downcasted int96 from int256, reverting on
                   * overflow (when the input is less than smallest int96 or
                   * greater than largest int96).
                   *
                   * Counterpart to Solidity's `int96` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 96 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt96(int256 value) internal pure returns (int96 downcasted) {
                      downcasted = int96(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
                  }
                  /**
                   * @dev Returns the downcasted int88 from int256, reverting on
                   * overflow (when the input is less than smallest int88 or
                   * greater than largest int88).
                   *
                   * Counterpart to Solidity's `int88` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 88 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt88(int256 value) internal pure returns (int88 downcasted) {
                      downcasted = int88(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
                  }
                  /**
                   * @dev Returns the downcasted int80 from int256, reverting on
                   * overflow (when the input is less than smallest int80 or
                   * greater than largest int80).
                   *
                   * Counterpart to Solidity's `int80` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 80 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt80(int256 value) internal pure returns (int80 downcasted) {
                      downcasted = int80(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
                  }
                  /**
                   * @dev Returns the downcasted int72 from int256, reverting on
                   * overflow (when the input is less than smallest int72 or
                   * greater than largest int72).
                   *
                   * Counterpart to Solidity's `int72` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 72 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt72(int256 value) internal pure returns (int72 downcasted) {
                      downcasted = int72(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
                  }
                  /**
                   * @dev Returns the downcasted int64 from int256, reverting on
                   * overflow (when the input is less than smallest int64 or
                   * greater than largest int64).
                   *
                   * Counterpart to Solidity's `int64` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 64 bits
                   *
                   * _Available since v3.1._
                   */
                  function toInt64(int256 value) internal pure returns (int64 downcasted) {
                      downcasted = int64(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
                  }
                  /**
                   * @dev Returns the downcasted int56 from int256, reverting on
                   * overflow (when the input is less than smallest int56 or
                   * greater than largest int56).
                   *
                   * Counterpart to Solidity's `int56` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 56 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt56(int256 value) internal pure returns (int56 downcasted) {
                      downcasted = int56(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
                  }
                  /**
                   * @dev Returns the downcasted int48 from int256, reverting on
                   * overflow (when the input is less than smallest int48 or
                   * greater than largest int48).
                   *
                   * Counterpart to Solidity's `int48` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 48 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt48(int256 value) internal pure returns (int48 downcasted) {
                      downcasted = int48(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
                  }
                  /**
                   * @dev Returns the downcasted int40 from int256, reverting on
                   * overflow (when the input is less than smallest int40 or
                   * greater than largest int40).
                   *
                   * Counterpart to Solidity's `int40` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 40 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt40(int256 value) internal pure returns (int40 downcasted) {
                      downcasted = int40(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
                  }
                  /**
                   * @dev Returns the downcasted int32 from int256, reverting on
                   * overflow (when the input is less than smallest int32 or
                   * greater than largest int32).
                   *
                   * Counterpart to Solidity's `int32` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 32 bits
                   *
                   * _Available since v3.1._
                   */
                  function toInt32(int256 value) internal pure returns (int32 downcasted) {
                      downcasted = int32(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
                  }
                  /**
                   * @dev Returns the downcasted int24 from int256, reverting on
                   * overflow (when the input is less than smallest int24 or
                   * greater than largest int24).
                   *
                   * Counterpart to Solidity's `int24` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 24 bits
                   *
                   * _Available since v4.7._
                   */
                  function toInt24(int256 value) internal pure returns (int24 downcasted) {
                      downcasted = int24(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
                  }
                  /**
                   * @dev Returns the downcasted int16 from int256, reverting on
                   * overflow (when the input is less than smallest int16 or
                   * greater than largest int16).
                   *
                   * Counterpart to Solidity's `int16` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 16 bits
                   *
                   * _Available since v3.1._
                   */
                  function toInt16(int256 value) internal pure returns (int16 downcasted) {
                      downcasted = int16(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
                  }
                  /**
                   * @dev Returns the downcasted int8 from int256, reverting on
                   * overflow (when the input is less than smallest int8 or
                   * greater than largest int8).
                   *
                   * Counterpart to Solidity's `int8` operator.
                   *
                   * Requirements:
                   *
                   * - input must fit into 8 bits
                   *
                   * _Available since v3.1._
                   */
                  function toInt8(int256 value) internal pure returns (int8 downcasted) {
                      downcasted = int8(value);
                      require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
                  }
                  /**
                   * @dev Converts an unsigned uint256 into a signed int256.
                   *
                   * Requirements:
                   *
                   * - input must be less than or equal to maxInt256.
                   *
                   * _Available since v3.0._
                   */
                  function toInt256(uint256 value) internal pure returns (int256) {
                      // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
                      require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
                      return int256(value);
                  }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              import {IStakedTokenV3} from './IStakedTokenV3.sol';
              import {IGhoVariableDebtTokenTransferHook} from './IGhoVariableDebtTokenTransferHook.sol';
              interface IStakedAaveV3 is IStakedTokenV3 {
                /**
                 * @dev Claims an `amount` of `REWARD_TOKEN` and stakes.
                 * @param to Address to stake to
                 * @param amount Amount to claim
                 */
                function claimRewardsAndStake(
                  address to,
                  uint256 amount
                ) external returns (uint256);
                /**
                 * @dev Claims an `amount` of `REWARD_TOKEN` and stakes. Only the claim helper contract is allowed to call this function
                 * @param from The address of the from from which to claim
                 * @param to Address to stake to
                 * @param amount Amount to claim
                 */
                function claimRewardsAndStakeOnBehalf(
                  address from,
                  address to,
                  uint256 amount
                ) external returns (uint256);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
              pragma solidity ^0.8.19;
              import "../IERC20.sol";
              /**
               * @dev Interface for the optional metadata functions from the ERC20 standard.
               *
               * _Available since v4.1._
               */
              interface IERC20Metadata is IERC20 {
                  /**
                   * @dev Returns the name of the token.
                   */
                  function name() external view returns (string memory);
                  /**
                   * @dev Returns the symbol of the token.
                   */
                  function symbol() external view returns (string memory);
                  /**
                   * @dev Returns the decimals places of the token.
                   */
                  function decimals() external view returns (uint8);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {ECDSA} from 'openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol';
              import {SafeCast72} from './utils/SafeCast72.sol';
              import {IGovernancePowerDelegationToken} from './interfaces/IGovernancePowerDelegationToken.sol';
              import {DelegationMode} from './DelegationAwareBalance.sol';
              /**
               * @notice The contract implements generic delegation functionality for the upcoming governance v3
               * @author BGD Labs
               * @dev to make it's pluggable to any exising token it has a set of virtual functions
               *   for simple access to balances and permit functionality
               * @dev ************ IMPORTANT SECURITY CONSIDERATION ************
               *   current version of the token can be used only with asset which has 18 decimals
               *   and possible totalSupply lower then 4722366482869645213696,
               *   otherwise at least POWER_SCALE_FACTOR should be adjusted !!!
               *   *************************************************************
               */
              abstract contract BaseDelegation is IGovernancePowerDelegationToken {
                struct DelegationState {
                  uint72 delegatedPropositionBalance;
                  uint72 delegatedVotingBalance;
                  DelegationMode delegationMode;
                }
                mapping(address => address) internal _votingDelegatee;
                mapping(address => address) internal _propositionDelegatee;
                /** @dev we assume that for the governance system delegation with 18 decimals of precision is not needed,
                 *   by this constant we reduce it by 10, to 8 decimals.
                 *   In case of Aave token this will allow to work with up to 47'223'664'828'696,45213696 total supply
                 *   If your token already have less then 10 decimals, please change it to appropriate.
                 */
                uint256 public constant POWER_SCALE_FACTOR = 1e10;
                bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH =
                  keccak256(
                    'DelegateByType(address delegator,address delegatee,uint8 delegationType,uint256 nonce,uint256 deadline)'
                  );
                bytes32 public constant DELEGATE_TYPEHASH =
                  keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)');
                /**
                 * @notice returns eip-2612 compatible domain separator
                 * @dev we expect that existing tokens, ie Aave, already have, so we want to reuse
                 * @return domain separator
                 */
                function _getDomainSeparator() internal view virtual returns (bytes32);
                /**
                 * @notice gets the delegation state of a user
                 * @param user address
                 * @return state of a user's delegation
                 */
                function _getDelegationState(address user) internal view virtual returns (DelegationState memory);
                /**
                 * @notice returns the token balance of a user
                 * @param user address
                 * @return current nonce before increase
                 */
                function _getBalance(address user) internal view virtual returns (uint256);
                /**
                 * @notice increases and return the current nonce of a user
                 * @dev should use `return nonce++;` pattern
                 * @param user address
                 * @return current nonce before increase
                 */
                function _incrementNonces(address user) internal virtual returns (uint256);
                /**
                 * @notice sets the delegation state of a user
                 * @param user address
                 * @param delegationState state of a user's delegation
                 */
                function _setDelegationState(address user, DelegationState memory delegationState)
                  internal
                  virtual;
                /// @inheritdoc IGovernancePowerDelegationToken
                function delegateByType(address delegatee, GovernancePowerType delegationType)
                  external
                  virtual
                  override
                {
                  _delegateByType(msg.sender, delegatee, delegationType);
                }
                /// @inheritdoc IGovernancePowerDelegationToken
                function delegate(address delegatee) external override {
                  _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING);
                  _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION);
                }
                /// @inheritdoc IGovernancePowerDelegationToken
                function getDelegateeByType(address delegator, GovernancePowerType delegationType)
                  external
                  view
                  override
                  returns (address)
                {
                  return _getDelegateeByType(delegator, _getDelegationState(delegator), delegationType);
                }
                /// @inheritdoc IGovernancePowerDelegationToken
                function getDelegates(address delegator) external view override returns (address, address) {
                  DelegationState memory delegatorBalance = _getDelegationState(delegator);
                  return (
                    _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.VOTING),
                    _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.PROPOSITION)
                  );
                }
                /// @inheritdoc IGovernancePowerDelegationToken
                function getPowerCurrent(address user, GovernancePowerType delegationType)
                  public
                  view
                  virtual
                  override
                  returns (uint256)
                {
                  DelegationState memory userState = _getDelegationState(user);
                  uint256 userOwnPower = uint8(userState.delegationMode) & (uint8(delegationType) + 1) == 0
                    ? _getBalance(user)
                    : 0;
                  uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType);
                  return userOwnPower + userDelegatedPower;
                }
                /// @inheritdoc IGovernancePowerDelegationToken
                function getPowersCurrent(address user) external view override returns (uint256, uint256) {
                  return (
                    getPowerCurrent(user, GovernancePowerType.VOTING),
                    getPowerCurrent(user, GovernancePowerType.PROPOSITION)
                  );
                }
                /// @inheritdoc IGovernancePowerDelegationToken
                function metaDelegateByType(
                  address delegator,
                  address delegatee,
                  GovernancePowerType delegationType,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
                ) external override {
                  require(delegator != address(0), 'INVALID_OWNER');
                  //solium-disable-next-line
                  require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
                  bytes32 digest = ECDSA.toTypedDataHash(
                    _getDomainSeparator(),
                    keccak256(
                      abi.encode(
                        DELEGATE_BY_TYPE_TYPEHASH,
                        delegator,
                        delegatee,
                        delegationType,
                        _incrementNonces(delegator),
                        deadline
                      )
                    )
                  );
                  require(delegator == ECDSA.recover(digest, v, r, s), 'INVALID_SIGNATURE');
                  _delegateByType(delegator, delegatee, delegationType);
                }
                /// @inheritdoc IGovernancePowerDelegationToken
                function metaDelegate(
                  address delegator,
                  address delegatee,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
                ) external override {
                  require(delegator != address(0), 'INVALID_OWNER');
                  //solium-disable-next-line
                  require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
                  bytes32 digest = ECDSA.toTypedDataHash(
                    _getDomainSeparator(),
                    keccak256(
                      abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, _incrementNonces(delegator), deadline)
                    )
                  );
                  require(delegator == ECDSA.recover(digest, v, r, s), 'INVALID_SIGNATURE');
                  _delegateByType(delegator, delegatee, GovernancePowerType.VOTING);
                  _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION);
                }
                /**
                 * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION).
                 * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose
                 * any precision.
                 * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee`
                 * before an action.
                 * For example, if the action is a delegation from one account to another, the impact before the action will be 0.
                 * @param impactOnDelegationAfter how much impact a balance of another account will have  over the delegation of a `delegatee`
                 * after an action.
                 * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance
                 * of the account changing the delegatee.
                 * @param delegatee the user whom delegated governance power will be changed
                 * @param delegationType the type of governance power delegation (VOTING, PROPOSITION)
                 **/
                function _governancePowerTransferByType(
                  uint256 impactOnDelegationBefore,
                  uint256 impactOnDelegationAfter,
                  address delegatee,
                  GovernancePowerType delegationType
                ) internal {
                  if (delegatee == address(0)) return;
                  if (impactOnDelegationBefore == impactOnDelegationAfter) return;
                  // we use uint72, because this is the most optimal for AaveTokenV3
                  // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR
                  uint72 impactOnDelegationBefore72 = SafeCast72.toUint72(
                    impactOnDelegationBefore / POWER_SCALE_FACTOR
                  );
                  uint72 impactOnDelegationAfter72 = SafeCast72.toUint72(
                    impactOnDelegationAfter / POWER_SCALE_FACTOR
                  );
                  DelegationState memory delegateeState = _getDelegationState(delegatee);
                  if (delegationType == GovernancePowerType.VOTING) {
                    delegateeState.delegatedVotingBalance =
                      delegateeState.delegatedVotingBalance -
                      impactOnDelegationBefore72 +
                      impactOnDelegationAfter72;
                  } else {
                    delegateeState.delegatedPropositionBalance =
                      delegateeState.delegatedPropositionBalance -
                      impactOnDelegationBefore72 +
                      impactOnDelegationAfter72;
                  }
                  _setDelegationState(delegatee, delegateeState);
                }
                /**
                 * @dev performs all state changes related delegation changes on transfer
                 * @param from token sender
                 * @param to token recipient
                 * @param fromBalanceBefore balance of the sender before transfer
                 * @param toBalanceBefore balance of the recipient before transfer
                 * @param amount amount of tokens sent
                 **/
                function _delegationChangeOnTransfer(
                  address from,
                  address to,
                  uint256 fromBalanceBefore,
                  uint256 toBalanceBefore,
                  uint256 amount
                ) internal {
                  if (from == to) {
                    return;
                  }
                  if (from != address(0)) {
                    DelegationState memory fromUserState = _getDelegationState(from);
                    uint256 fromBalanceAfter = fromBalanceBefore - amount;
                    if (fromUserState.delegationMode != DelegationMode.NO_DELEGATION) {
                      _governancePowerTransferByType(
                        fromBalanceBefore,
                        fromBalanceAfter,
                        _getDelegateeByType(from, fromUserState, GovernancePowerType.VOTING),
                        GovernancePowerType.VOTING
                      );
                      _governancePowerTransferByType(
                        fromBalanceBefore,
                        fromBalanceAfter,
                        _getDelegateeByType(from, fromUserState, GovernancePowerType.PROPOSITION),
                        GovernancePowerType.PROPOSITION
                      );
                    }
                  }
                  if (to != address(0)) {
                    DelegationState memory toUserState = _getDelegationState(to);
                    uint256 toBalanceAfter = toBalanceBefore + amount;
                    if (toUserState.delegationMode != DelegationMode.NO_DELEGATION) {
                      _governancePowerTransferByType(
                        toBalanceBefore,
                        toBalanceAfter,
                        _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING),
                        GovernancePowerType.VOTING
                      );
                      _governancePowerTransferByType(
                        toBalanceBefore,
                        toBalanceAfter,
                        _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION),
                        GovernancePowerType.PROPOSITION
                      );
                    }
                  }
                }
                /**
                 * @dev Extracts from state and returns delegated governance power (Voting, Proposition)
                 * @param userState the current state of a user
                 * @param delegationType the type of governance power delegation (VOTING, PROPOSITION)
                 **/
                function _getDelegatedPowerByType(
                  DelegationState memory userState,
                  GovernancePowerType delegationType
                ) internal pure returns (uint256) {
                  return
                    POWER_SCALE_FACTOR *
                    (
                      delegationType == GovernancePowerType.VOTING
                        ? userState.delegatedVotingBalance
                        : userState.delegatedPropositionBalance
                    );
                }
                /**
                 * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition)
                 * - If the delegator doesn't have any delegatee, returns address(0)
                 * @param delegator delegator
                 * @param userState the current state of a user
                 * @param delegationType the type of governance power delegation (VOTING, PROPOSITION)
                 **/
                function _getDelegateeByType(
                  address delegator,
                  DelegationState memory userState,
                  GovernancePowerType delegationType
                ) internal view returns (address) {
                  if (delegationType == GovernancePowerType.VOTING) {
                    return
                      /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED
                      /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11
                      (uint8(userState.delegationMode) & uint8(DelegationMode.VOTING_DELEGATED)) != 0
                        ? _votingDelegatee[delegator]
                        : address(0);
                  }
                  return
                    userState.delegationMode >= DelegationMode.PROPOSITION_DELEGATED
                      ? _propositionDelegatee[delegator]
                      : address(0);
                }
                /**
                 * @dev Changes user's delegatee address by type of governance power (Voting, Proposition)
                 * @param delegator delegator
                 * @param delegationType the type of governance power delegation (VOTING, PROPOSITION)
                 * @param _newDelegatee the new delegatee
                 **/
                function _updateDelegateeByType(
                  address delegator,
                  GovernancePowerType delegationType,
                  address _newDelegatee
                ) internal {
                  address newDelegatee = _newDelegatee == delegator ? address(0) : _newDelegatee;
                  if (delegationType == GovernancePowerType.VOTING) {
                    _votingDelegatee[delegator] = newDelegatee;
                  } else {
                    _propositionDelegatee[delegator] = newDelegatee;
                  }
                }
                /**
                 * @dev Updates the specific flag which signaling about existence of delegation of governance power (Voting, Proposition)
                 * @param userState a user state to change
                 * @param delegationType the type of governance power delegation (VOTING, PROPOSITION)
                 * @param willDelegate next state of delegation
                 **/
                function _updateDelegationModeByType(
                  DelegationState memory userState,
                  GovernancePowerType delegationType,
                  bool willDelegate
                ) internal pure returns (DelegationState memory) {
                  if (willDelegate) {
                    // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR
                    userState.delegationMode = DelegationMode(
                      uint8(userState.delegationMode) | (uint8(delegationType) + 1)
                    );
                  } else {
                    // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10,
                    // then bitwise AND, which means it will keep only another delegation type if it exists
                    userState.delegationMode = DelegationMode(
                      uint8(userState.delegationMode) &
                        ((uint8(delegationType) + 1) ^ uint8(DelegationMode.FULL_POWER_DELEGATED))
                    );
                  }
                  return userState;
                }
                /**
                 * @dev This is the equivalent of an ERC20 transfer(), but for a power type: an atomic transfer of a balance (power).
                 * When needed, it decreases the power of the `delegator` and when needed, it increases the power of the `delegatee`
                 * @param delegator delegator
                 * @param _delegatee the user which delegated power will change
                 * @param delegationType the type of delegation (VOTING, PROPOSITION)
                 **/
                function _delegateByType(
                  address delegator,
                  address _delegatee,
                  GovernancePowerType delegationType
                ) internal {
                  // Here we unify the property that delegating power to address(0) == delegating power to yourself == no delegation
                  // So from now on, not being delegating is (exclusively) that delegatee == address(0)
                  address delegatee = _delegatee == delegator ? address(0) : _delegatee;
                  // We read the whole struct before validating delegatee, because in the optimistic case
                  // (_delegatee != currentDelegatee) we will reuse userState in the rest of the function
                  DelegationState memory delegatorState = _getDelegationState(delegator);
                  address currentDelegatee = _getDelegateeByType(delegator, delegatorState, delegationType);
                  if (delegatee == currentDelegatee) return;
                  bool delegatingNow = currentDelegatee != address(0);
                  bool willDelegateAfter = delegatee != address(0);
                  uint256 delegatorBalance = _getBalance(delegator);
                  if (delegatingNow) {
                    _governancePowerTransferByType(delegatorBalance, 0, currentDelegatee, delegationType);
                  }
                  if (willDelegateAfter) {
                    _governancePowerTransferByType(0, delegatorBalance, delegatee, delegationType);
                  }
                  _updateDelegateeByType(delegator, delegationType, delegatee);
                  if (willDelegateAfter != delegatingNow) {
                    _setDelegationState(
                      delegator,
                      _updateDelegationModeByType(delegatorState, delegationType, willDelegateAfter)
                    );
                  }
                  emit DelegateChanged(delegator, delegatee, delegationType);
                }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
              pragma solidity ^0.8.0;
              import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol';
              import "./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 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'
                      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) + value;
                      _callOptionalReturn(
                          token,
                          abi.encodeWithSelector(
                              token.approve.selector,
                              spender,
                              newAllowance
                          )
                      );
                  }
                  function safeDecreaseAllowance(
                      IERC20 token,
                      address spender,
                      uint256 value
                  ) internal {
                      unchecked {
                          uint256 oldAllowance = token.allowance(address(this), spender);
                          require(
                              oldAllowance >= value,
                              "SafeERC20: decreased allowance below zero"
                          );
                          uint256 newAllowance = oldAllowance - value;
                          _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
                          require(
                              abi.decode(returndata, (bool)),
                              "SafeERC20: ERC20 operation did not succeed"
                          );
                      }
                  }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              interface IAaveDistributionManager {
                function configureAssets(
                  DistributionTypes.AssetConfigInput[] memory assetsConfigInput
                ) external;
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              interface IStakedTokenV2 {
                struct CooldownSnapshot {
                  uint40 timestamp;
                  uint216 amount;
                }
                event RewardsAccrued(address user, uint256 amount);
                event RewardsClaimed(
                  address indexed from,
                  address indexed to,
                  uint256 amount
                );
                event Cooldown(address indexed user, uint256 amount);
                /**
                 * @dev Allows staking a specified amount of STAKED_TOKEN
                 * @param to The address to receiving the shares
                 * @param amount The amount of assets to be staked
                 */
                function stake(address to, uint256 amount) external;
                /**
                 * @dev Redeems shares, and stop earning rewards
                 * @param to Address to redeem to
                 * @param amount Amount of shares to redeem
                 */
                function redeem(address to, uint256 amount) external;
                /**
                 * @dev Activates the cooldown period to unstake
                 * - It can't be called if the user is not staking
                 */
                function cooldown() external;
                /**
                 * @dev Claims an `amount` of `REWARD_TOKEN` to the address `to`
                 * @param to Address to send the claimed rewards
                 * @param amount Amount to stake
                 */
                function claimRewards(address to, uint256 amount) external;
                /**
                 * @dev Return the total rewards pending to claim by an staker
                 * @param staker The staker address
                 * @return The rewards
                 */
                function getTotalRewardsBalance(
                  address staker
                ) external view returns (uint256);
                /**
                 * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md
                 * @param owner the owner of the funds
                 * @param spender the spender
                 * @param value the amount
                 * @param deadline the deadline timestamp, type(uint256).max for no deadline
                 * @param v signature param
                 * @param s signature param
                 * @param r signature param
                 */
                function permit(
                  address owner,
                  address spender,
                  uint256 value,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
                ) external;
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol';
              import {EIP712, ECDSA} from 'aave-token-v3/utils/EIP712.sol';
              import {IStakedTokenV2} from '../interfaces/IStakedTokenV2.sol';
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              import {SafeERC20} from '../lib/SafeERC20.sol';
              import {VersionedInitializable} from '../utils/VersionedInitializable.sol';
              import {AaveDistributionManager} from './AaveDistributionManager.sol';
              import {GovernancePowerWithSnapshot} from '../lib/GovernancePowerWithSnapshot.sol';
              import {BaseMintableAaveToken} from './BaseMintableAaveToken.sol';
              /**
               * @title StakedTokenV2
               * @notice Contract to stake Aave token, tokenize the position and get rewards, inheriting from a distribution manager contract
               * @author BGD Labs
               */
              abstract contract StakedTokenV2 is
                IStakedTokenV2,
                BaseMintableAaveToken,
                GovernancePowerWithSnapshot,
                VersionedInitializable,
                AaveDistributionManager,
                EIP712
              {
                using SafeERC20 for IERC20;
                IERC20 public immutable STAKED_TOKEN;
                IERC20 public immutable REWARD_TOKEN;
                /// @notice Seconds available to redeem once the cooldown period is fulfilled
                uint256 public immutable UNSTAKE_WINDOW;
                /// @notice Address to pull from the rewards, needs to have approved this contract
                address public immutable REWARDS_VAULT;
                mapping(address => uint256) public stakerRewardsToClaim;
                mapping(address => CooldownSnapshot) public stakersCooldowns;
                /// @dev End of Storage layout from StakedToken v1
                uint256[5] private ______DEPRECATED_FROM_STK_AAVE_V2;
                bytes32 public constant PERMIT_TYPEHASH =
                  keccak256(
                    'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'
                  );
                /// @dev owner => next valid nonce to submit with permit()
                mapping(address => uint256) public _nonces;
                constructor(
                  IERC20 stakedToken,
                  IERC20 rewardToken,
                  uint256 unstakeWindow,
                  address rewardsVault,
                  address emissionManager,
                  uint128 distributionDuration
                )
                  AaveDistributionManager(emissionManager, distributionDuration)
                  EIP712('Staked Aave', '2')
                {
                  STAKED_TOKEN = stakedToken;
                  REWARD_TOKEN = rewardToken;
                  UNSTAKE_WINDOW = unstakeWindow;
                  REWARDS_VAULT = rewardsVault;
                }
                /**
                 * @notice Get the domain separator for the token
                 * @dev Return cached value if chainId matches cache, otherwise recomputes separator
                 * @return The domain separator of the token at current chain
                 */
                function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
                  return _domainSeparatorV4();
                }
                /// @dev maintained for backwards compatibility. See EIP712 _EIP712Version
                function EIP712_REVISION() external view returns (bytes memory) {
                  return bytes(_EIP712Version());
                }
                /// @inheritdoc IStakedTokenV2
                function stake(address onBehalfOf, uint256 amount) external virtual override;
                /// @inheritdoc IStakedTokenV2
                function redeem(address to, uint256 amount) external virtual override;
                /// @inheritdoc IStakedTokenV2
                function cooldown() external virtual override;
                /// @inheritdoc IStakedTokenV2
                function claimRewards(address to, uint256 amount) external virtual override;
                /// @inheritdoc IStakedTokenV2
                function getTotalRewardsBalance(
                  address staker
                ) external view returns (uint256) {
                  DistributionTypes.UserStakeInput[]
                    memory userStakeInputs = new DistributionTypes.UserStakeInput[](1);
                  userStakeInputs[0] = DistributionTypes.UserStakeInput({
                    underlyingAsset: address(this),
                    stakedByUser: balanceOf(staker),
                    totalStaked: totalSupply()
                  });
                  return
                    stakerRewardsToClaim[staker] +
                    _getUnclaimedRewards(staker, userStakeInputs);
                }
                /// @inheritdoc IStakedTokenV2
                function permit(
                  address owner,
                  address spender,
                  uint256 value,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
                ) external {
                  require(owner != address(0), 'INVALID_OWNER');
                  //solium-disable-next-line
                  require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
                  uint256 currentValidNonce = _nonces[owner];
                  bytes32 digest = _hashTypedDataV4(
                    keccak256(
                      abi.encode(
                        PERMIT_TYPEHASH,
                        owner,
                        spender,
                        value,
                        currentValidNonce,
                        deadline
                      )
                    )
                  );
                  require(owner == ECDSA.recover(digest, v, r, s), 'INVALID_SIGNATURE');
                  unchecked {
                    _nonces[owner] = currentValidNonce + 1;
                  }
                  _approve(owner, spender, value);
                }
                /**
                 * @dev Updates the user state related with his accrued rewards
                 * @param user Address of the user
                 * @param userBalance The current balance of the user
                 * @param updateStorage Boolean flag used to update or not the stakerRewardsToClaim of the user
                 * @return The unclaimed rewards that were added to the total accrued
                 */
                function _updateCurrentUnclaimedRewards(
                  address user,
                  uint256 userBalance,
                  bool updateStorage
                ) internal returns (uint256) {
                  uint256 accruedRewards = _updateUserAssetInternal(
                    user,
                    address(this),
                    userBalance,
                    totalSupply()
                  );
                  uint256 unclaimedRewards = stakerRewardsToClaim[user] + accruedRewards;
                  if (accruedRewards != 0) {
                    if (updateStorage) {
                      stakerRewardsToClaim[user] = unclaimedRewards;
                    }
                    emit RewardsAccrued(user, accruedRewards);
                  }
                  return unclaimedRewards;
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              import {IStakedTokenV2} from './IStakedTokenV2.sol';
              interface IStakedTokenV3 is IStakedTokenV2 {
                event Staked(
                  address indexed from,
                  address indexed to,
                  uint256 assets,
                  uint256 shares
                );
                event Redeem(
                  address indexed from,
                  address indexed to,
                  uint256 assets,
                  uint256 shares
                );
                event MaxSlashablePercentageChanged(uint256 newPercentage);
                event Slashed(address indexed destination, uint256 amount);
                event SlashingExitWindowDurationChanged(uint256 windowSeconds);
                event CooldownSecondsChanged(uint256 cooldownSeconds);
                event ExchangeRateChanged(uint216 exchangeRate);
                event FundsReturned(uint256 amount);
                event SlashingSettled();
                /**
                 * @dev Allows staking a certain amount of STAKED_TOKEN with gasless approvals (permit)
                 * @param amount The amount to be staked
                 * @param deadline The permit execution deadline
                 * @param v The v component of the signed message
                 * @param r The r component of the signed message
                 * @param s The s component of the signed message
                 */
                function stakeWithPermit(
                  uint256 amount,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
                ) external;
                /**
                 * @dev Returns the current exchange rate
                 * @return exchangeRate as 18 decimal precision uint216
                 */
                function getExchangeRate() external view returns (uint216);
                /**
                 * @dev Executes a slashing of the underlying of a certain amount, transferring the seized funds
                 * to destination. Decreasing the amount of underlying will automatically adjust the exchange rate.
                 * A call to `slash` will start a slashing event which has to be settled via `settleSlashing`.
                 * As long as the slashing event is ongoing, stake and slash are deactivated.
                 * - MUST NOT be called when a previous slashing is still ongoing
                 * @param destination the address where seized funds will be transferred
                 * @param amount the amount to be slashed
                 * - if the amount bigger than maximum allowed, the maximum will be slashed instead.
                 * @return amount the amount slashed
                 */
                function slash(
                  address destination,
                  uint256 amount
                ) external returns (uint256);
                /**
                 * @dev Settles an ongoing slashing event
                 */
                function settleSlashing() external;
                /**
                 * @dev Pulls STAKE_TOKEN and distributes them amongst current stakers by altering the exchange rate.
                 * This method is permissionless and intended to be used after a slashing event to return potential excess funds.
                 * @param amount amount of STAKE_TOKEN to pull.
                 */
                function returnFunds(uint256 amount) external;
                /**
                 * @dev Getter of the cooldown seconds
                 * @return cooldownSeconds the amount of seconds between starting the cooldown and being able to redeem
                 */
                function getCooldownSeconds() external view returns (uint256);
                /**
                 * @dev Getter of the cooldown seconds
                 * @return cooldownSeconds the amount of seconds between starting the cooldown and being able to redeem
                 */
                function COOLDOWN_SECONDS() external view returns (uint256); // @deprecated
                /**
                 * @dev Setter of cooldown seconds
                 * Can only be called by the cooldown admin
                 * @param cooldownSeconds the new amount of seconds you have to wait between starting the cooldown and being able to redeem
                 */
                function setCooldownSeconds(uint256 cooldownSeconds) external;
                /**
                 * @dev Getter of the max slashable percentage of the total staked amount.
                 * @return percentage the maximum slashable percentage
                 */
                function getMaxSlashablePercentage() external view returns (uint256);
                /**
                 * @dev Setter of max slashable percentage of the total staked amount.
                 * Can only be called by the slashing admin
                 * @param percentage the new maximum slashable percentage
                 */
                function setMaxSlashablePercentage(uint256 percentage) external;
                /**
                 * @dev returns the exact amount of shares that would be received for the provided number of assets
                 * @param assets the number of assets to stake
                 * @return uint256 shares the number of shares that would be received
                 */
                function previewStake(uint256 assets) external view returns (uint256);
                /**
                 * @dev Activates the cooldown period to unstake
                 * - It can't be called if the user is not staking
                 */
                function cooldownOnBehalfOf(address from) external;
                /**
                 * @dev Claims an `amount` of `REWARD_TOKEN` to the address `to` on behalf of the user. Only the claim helper contract is allowed to call this function
                 * @param from The address of the user from to claim
                 * @param to Address to send the claimed rewards
                 * @param amount Amount to claim
                 */
                function claimRewardsOnBehalf(
                  address from,
                  address to,
                  uint256 amount
                ) external returns (uint256);
                /**
                 * @dev returns the exact amount of assets that would be redeemed for the provided number of shares
                 * @param shares the number of shares to redeem
                 * @return uint256 assets the number of assets that would be redeemed
                 */
                function previewRedeem(uint256 shares) external view returns (uint256);
                /**
                 * @dev Redeems shares for a user. Only the claim helper contract is allowed to call this function
                 * @param from Address to redeem from
                 * @param to Address to redeem to
                 * @param amount Amount of shares to redeem
                 */
                function redeemOnBehalf(address from, address to, uint256 amount) external;
                /**
                 * @dev Claims an `amount` of `REWARD_TOKEN` and redeems to the provided address
                 * @param to Address to claim and redeem to
                 * @param claimAmount Amount to claim
                 * @param redeemAmount Amount to redeem
                 */
                function claimRewardsAndRedeem(
                  address to,
                  uint256 claimAmount,
                  uint256 redeemAmount
                ) external;
                /**
                 * @dev Claims an `amount` of `REWARD_TOKEN` and redeems the `redeemAmount` to an address. Only the claim helper contract is allowed to call this function
                 * @param from The address of the from
                 * @param to Address to claim and redeem to
                 * @param claimAmount Amount to claim
                 * @param redeemAmount Amount to redeem
                 */
                function claimRewardsAndRedeemOnBehalf(
                  address from,
                  address to,
                  uint256 claimAmount,
                  uint256 redeemAmount
                ) external;
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              /**
               * @title PercentageMath library
               * @author Aave
               * @notice Provides functions to perform percentage calculations
               * @dev Percentages are defined by default with 2 decimals of precision (100.00). The precision is indicated by PERCENTAGE_FACTOR
               * @dev Operations are rounded half up
               **/
              library PercentageMath {
                  uint256 constant PERCENTAGE_FACTOR = 1e4; //percentage plus two decimals
                  uint256 constant HALF_PERCENT = PERCENTAGE_FACTOR / 2;
                  /**
                   * @dev Executes a percentage multiplication
                   * @param value The value of which the percentage needs to be calculated
                   * @param percentage The percentage of the value to be calculated
                   * @return The percentage of value
                   **/
                  function percentMul(uint256 value, uint256 percentage)
                      internal
                      pure
                      returns (uint256)
                  {
                      if (value == 0 || percentage == 0) {
                          return 0;
                      }
                      require(
                          value <= (type(uint256).max) / percentage,
                          "MATH_MULTIPLICATION_OVERFLOW"
                      );
                      return (value * percentage) / PERCENTAGE_FACTOR;
                  }
                  /**
                   * @dev Executes a percentage division
                   * @param value The value of which the percentage needs to be calculated
                   * @param percentage The percentage of the value to be calculated
                   * @return The value divided the percentage
                   **/
                  function percentDiv(uint256 value, uint256 percentage)
                      internal
                      pure
                      returns (uint256)
                  {
                      require(percentage != 0, "MATH_DIVISION_BY_ZERO");
                      require(
                          value <= type(uint256).max / PERCENTAGE_FACTOR,
                          "MATH_MULTIPLICATION_OVERFLOW"
                      );
                      return (value * PERCENTAGE_FACTOR) / percentage;
                  }
              }
              pragma solidity ^0.8.0;
              /**
               * @title RoleManager
               * @notice Generic role manager to manage slashing and cooldown admin in StakedAaveV3.
               *         It implements a claim admin role pattern to safely migrate between different admin addresses
               * @author Aave
               **/
              contract RoleManager {
                struct InitAdmin {
                  uint256 role;
                  address admin;
                }
                mapping(uint256 => address) private _admins;
                mapping(uint256 => address) private _pendingAdmins;
                event PendingAdminChanged(address indexed newPendingAdmin, uint256 role);
                event RoleClaimed(address indexed newAdmin, uint256 role);
                modifier onlyRoleAdmin(uint256 role) {
                  require(_admins[role] == msg.sender, 'CALLER_NOT_ROLE_ADMIN');
                  _;
                }
                modifier onlyPendingRoleAdmin(uint256 role) {
                  require(
                    _pendingAdmins[role] == msg.sender,
                    'CALLER_NOT_PENDING_ROLE_ADMIN'
                  );
                  _;
                }
                /**
                 * @dev returns the admin associated with the specific role
                 * @param role the role associated with the admin being returned
                 **/
                function getAdmin(uint256 role) public view returns (address) {
                  return _admins[role];
                }
                /**
                 * @dev returns the pending admin associated with the specific role
                 * @param role the role associated with the pending admin being returned
                 **/
                function getPendingAdmin(uint256 role) public view returns (address) {
                  return _pendingAdmins[role];
                }
                /**
                 * @dev sets the pending admin for a specific role
                 * @param role the role associated with the new pending admin being set
                 * @param newPendingAdmin the address of the new pending admin
                 **/
                function setPendingAdmin(
                  uint256 role,
                  address newPendingAdmin
                ) public onlyRoleAdmin(role) {
                  _pendingAdmins[role] = newPendingAdmin;
                  emit PendingAdminChanged(newPendingAdmin, role);
                }
                /**
                 * @dev allows the caller to become a specific role admin
                 * @param role the role associated with the admin claiming the new role
                 **/
                function claimRoleAdmin(uint256 role) external onlyPendingRoleAdmin(role) {
                  _admins[role] = msg.sender;
                  _pendingAdmins[role] = address(0);
                  emit RoleClaimed(msg.sender, role);
                }
                function _initAdmins(InitAdmin[] memory initAdmins) internal {
                  for (uint256 i = 0; i < initAdmins.length; i++) {
                    require(
                      _admins[initAdmins[i].role] == address(0) &&
                        initAdmins[i].admin != address(0),
                      'ADMIN_CANNOT_BE_INITIALIZED'
                    );
                    _admins[initAdmins[i].role] = initAdmins[i].admin;
                    emit RoleClaimed(initAdmins[i].admin, initAdmins[i].role);
                  }
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol';
              interface IERC20WithPermit is IERC20 {
                function permit(
                  address owner,
                  address spender,
                  uint256 value,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
                ) external;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
              pragma solidity ^0.8.19;
              import "../Strings.sol";
              /**
               * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
               *
               * These functions can be used to verify that a message was signed by the holder
               * of the private keys of a given address.
               */
              library ECDSA {
                  enum RecoverError {
                      NoError,
                      InvalidSignature,
                      InvalidSignatureLength,
                      InvalidSignatureS
                  }
                  /**
                   * @dev The signature derives the `address(0)`.
                   */
                  error ECDSAInvalidSignature();
                  /**
                   * @dev The signature has an invalid length.
                   */
                  error ECDSAInvalidSignatureLength(uint256 length);
                  /**
                   * @dev The signature has an S value that is in the upper half order.
                   */
                  error ECDSAInvalidSignatureS(bytes32 s);
                  function _throwError(RecoverError error, bytes32 errorArg) private pure {
                      if (error == RecoverError.NoError) {
                          return; // no error: do nothing
                      } else if (error == RecoverError.InvalidSignature) {
                          revert ECDSAInvalidSignature();
                      } else if (error == RecoverError.InvalidSignatureLength) {
                          revert ECDSAInvalidSignatureLength(uint256(errorArg));
                      } else if (error == RecoverError.InvalidSignatureS) {
                          revert ECDSAInvalidSignatureS(errorArg);
                      }
                  }
                  /**
                   * @dev Returns the address that signed a hashed message (`hash`) with
                   * `signature` or error string. This address can then be used for verification purposes.
                   *
                   * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
                   * this function rejects them by requiring the `s` value to be in the lower
                   * half order, and the `v` value to be either 27 or 28.
                   *
                   * IMPORTANT: `hash` _must_ be the result of a hash operation for the
                   * verification to be secure: it is possible to craft signatures that
                   * recover to arbitrary addresses for non-hashed data. A safe way to ensure
                   * this is by receiving a hash of the original message (which may otherwise
                   * be too long), and then calling {toEthSignedMessageHash} on it.
                   *
                   * Documentation for signature generation:
                   * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
                   * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
                   *
                   * _Available since v4.3._
                   */
                  function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
                      if (signature.length == 65) {
                          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 tryRecover(hash, v, r, s);
                      } else {
                          return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
                      }
                  }
                  /**
                   * @dev Returns the address that signed a hashed message (`hash`) with
                   * `signature`. This address can then be used for verification purposes.
                   *
                   * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
                   * this function rejects them by requiring the `s` value to be in the lower
                   * half order, and the `v` value to be either 27 or 28.
                   *
                   * IMPORTANT: `hash` _must_ be the result of a hash operation for the
                   * verification to be secure: it is possible to craft signatures that
                   * recover to arbitrary addresses for non-hashed data. A safe way to ensure
                   * this is by receiving a hash of the original message (which may otherwise
                   * be too long), and then calling {toEthSignedMessageHash} on it.
                   */
                  function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                      (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
                      _throwError(error, errorArg);
                      return recovered;
                  }
                  /**
                   * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
                   *
                   * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
                   *
                   * _Available since v4.3._
                   */
                  function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
                      unchecked {
                          bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
                          // We do not check for an overflow here since the shift operation results in 0 or 1.
                          uint8 v = uint8((uint256(vs) >> 255) + 27);
                          return tryRecover(hash, v, r, s);
                      }
                  }
                  /**
                   * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
                   *
                   * _Available since v4.2._
                   */
                  function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
                      (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
                      _throwError(error, errorArg);
                      return recovered;
                  }
                  /**
                   * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
                   * `r` and `s` signature fields separately.
                   *
                   * _Available since v4.3._
                   */
                  function tryRecover(
                      bytes32 hash,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) internal pure returns (address, RecoverError, bytes32) {
                      // 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 (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): 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) {
                          return (address(0), RecoverError.InvalidSignatureS, s);
                      }
                      // If the signature is valid (and not malleable), return the signer address
                      address signer = ecrecover(hash, v, r, s);
                      if (signer == address(0)) {
                          return (address(0), RecoverError.InvalidSignature, bytes32(0));
                      }
                      return (signer, RecoverError.NoError, bytes32(0));
                  }
                  /**
                   * @dev Overload of {ECDSA-recover} that receives the `v`,
                   * `r` and `s` signature fields separately.
                   */
                  function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
                      (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
                      _throwError(error, errorArg);
                      return recovered;
                  }
                  /**
                   * @dev Returns an Ethereum Signed Message, created from a `hash`. This
                   * produces hash corresponding to the one signed with the
                   * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
                   * JSON-RPC method as part of EIP-191.
                   *
                   * See {recover}.
                   */
                  function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
                      // 32 is the length in bytes of hash,
                      // enforced by the type signature above
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(0x00, "\\x19Ethereum Signed Message:\
              32")
                          mstore(0x1c, hash)
                          message := keccak256(0x00, 0x3c)
                      }
                  }
                  /**
                   * @dev Returns an Ethereum Signed Message, created from `s`. This
                   * produces hash corresponding to the one signed with the
                   * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
                   * JSON-RPC method as part of EIP-191.
                   *
                   * See {recover}.
                   */
                  function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
                      return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
              ", Strings.toString(s.length), s));
                  }
                  /**
                   * @dev Returns an Ethereum Signed Typed Data, created from a
                   * `domainSeparator` and a `structHash`. This produces hash corresponding
                   * to the one signed with the
                   * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
                   * JSON-RPC method as part of EIP-712.
                   *
                   * See {recover}.
                   */
                  function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          let ptr := mload(0x40)
                          mstore(ptr, hex"19_01")
                          mstore(add(ptr, 0x02), domainSeparator)
                          mstore(add(ptr, 0x22), structHash)
                          data := keccak256(ptr, 0x42)
                      }
                  }
                  /**
                   * @dev Returns an Ethereum Signed Data with intended validator, created from a
                   * `validator` and `data` according to the version 0 of EIP-191.
                   *
                   * See {recover}.
                   */
                  function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
                      return keccak256(abi.encodePacked(hex"19_00", validator, data));
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /** @notice influenced by OpenZeppelin SafeCast lib, which is missing to uint72 cast
               * @author BGD Labs
               */
              library SafeCast72 {
                /**
                 * @dev Returns the downcasted uint72 from uint256, reverting on
                 * overflow (when the input is greater than largest uint72).
                 *
                 * Counterpart to Solidity's `uint16` operator.
                 *
                 * Requirements:
                 *
                 * - input must fit into 72 bits
                 */
                function toUint72(uint256 value) internal pure returns (uint72) {
                  require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
                  return uint72(value);
                }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              interface IGovernancePowerDelegationToken {
                enum GovernancePowerType {
                  VOTING,
                  PROPOSITION
                }
                /**
                 * @dev emitted when a user delegates to another
                 * @param delegator the user which delegated governance power
                 * @param delegatee the delegatee
                 * @param delegationType the type of delegation (VOTING, PROPOSITION)
                 **/
                event DelegateChanged(
                  address indexed delegator,
                  address indexed delegatee,
                  GovernancePowerType delegationType
                );
                // @dev we removed DelegatedPowerChanged event because to reconstruct the full state of the system,
                // is enough to have Transfer and DelegateChanged TODO: document it
                /**
                 * @dev delegates the specific power to a delegatee
                 * @param delegatee the user which delegated power will change
                 * @param delegationType the type of delegation (VOTING, PROPOSITION)
                 **/
                function delegateByType(address delegatee, GovernancePowerType delegationType) external;
                /**
                 * @dev delegates all the governance powers to a specific user
                 * @param delegatee the user to which the powers will be delegated
                 **/
                function delegate(address delegatee) external;
                /**
                 * @dev returns the delegatee of an user
                 * @param delegator the address of the delegator
                 * @param delegationType the type of delegation (VOTING, PROPOSITION)
                 * @return address of the specified delegatee
                 **/
                function getDelegateeByType(address delegator, GovernancePowerType delegationType)
                  external
                  view
                  returns (address);
                /**
                 * @dev returns delegates of an user
                 * @param delegator the address of the delegator
                 * @return a tuple of addresses the VOTING and PROPOSITION delegatee
                 **/
                function getDelegates(address delegator)
                  external
                  view
                  returns (address, address);
                /**
                 * @dev returns the current voting or proposition power of a user.
                 * @param user the user
                 * @param delegationType the type of delegation (VOTING, PROPOSITION)
                 * @return the current voting or proposition power of a user
                 **/
                function getPowerCurrent(address user, GovernancePowerType delegationType)
                  external
                  view
                  returns (uint256);
                /**
                 * @dev returns the current voting or proposition power of a user.
                 * @param user the user
                 * @return the current voting and proposition power of a user
                 **/
                function getPowersCurrent(address user)
                  external
                  view
                  returns (uint256, uint256);
                /**
                 * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md
                 * @param delegator the owner of the funds
                 * @param delegatee the user to who owner delegates his governance power
                 * @param delegationType the type of governance power delegation (VOTING, PROPOSITION)
                 * @param deadline the deadline timestamp, type(uint256).max for no deadline
                 * @param v signature param
                 * @param s signature param
                 * @param r signature param
                 */
                function metaDelegateByType(
                  address delegator,
                  address delegatee,
                  GovernancePowerType delegationType,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
                ) external;
                /**
                 * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md
                 * @param delegator the owner of the funds
                 * @param delegatee the user to who delegator delegates his voting and proposition governance power
                 * @param deadline the deadline timestamp, type(uint256).max for no deadline
                 * @param v signature param
                 * @param s signature param
                 * @param r signature param
                 */
                function metaDelegate(
                  address delegator,
                  address delegatee,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
                ) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              enum DelegationMode {
                NO_DELEGATION,
                VOTING_DELEGATED,
                PROPOSITION_DELEGATED,
                FULL_POWER_DELEGATED
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
              pragma solidity ^0.8.1;
              /**
               * @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
                   * ====
                   *
                   * [IMPORTANT]
                   * ====
                   * You shouldn't rely on `isContract` to protect against flash loan attacks!
                   *
                   * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                   * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                   * constructor.
                   * ====
                   */
                  function isContract(address account) internal view returns (bool) {
                      // This method relies on extcodesize/address.code.length, which returns 0
                      // for contracts in construction, since the code is only stored at the end
                      // of the constructor execution.
                      return account.code.length > 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://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.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");
                      (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 functionCallWithValue(target, data, 0, "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");
                      (bool success, bytes memory returndata) = target.call{value: value}(data);
                      return verifyCallResultFromTarget(target, 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) {
                      (bool success, bytes memory returndata) = target.staticcall(data);
                      return verifyCallResultFromTarget(target, 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) {
                      (bool success, bytes memory returndata) = target.delegatecall(data);
                      return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                  }
                  /**
                   * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
                   * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
                   *
                   * _Available since v4.8._
                   */
                  function verifyCallResultFromTarget(
                      address target,
                      bool success,
                      bytes memory returndata,
                      string memory errorMessage
                  ) internal view returns (bytes memory) {
                      if (success) {
                          if (returndata.length == 0) {
                              // only check isContract if the call was successful and the return data is empty
                              // otherwise we already know that it was a contract
                              require(isContract(target), "Address: call to non-contract");
                          }
                          return returndata;
                      } else {
                          _revert(returndata, errorMessage);
                      }
                  }
                  /**
                   * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
                   * revert reason or using the provided one.
                   *
                   * _Available since v4.3._
                   */
                  function verifyCallResult(
                      bool success,
                      bytes memory returndata,
                      string memory errorMessage
                  ) internal pure returns (bytes memory) {
                      if (success) {
                          return returndata;
                      } else {
                          _revert(returndata, errorMessage);
                      }
                  }
                  function _revert(bytes memory returndata, string memory errorMessage) 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(errorMessage);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // Contract modified from OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol) to remove local
              // fallback storage variables, so contract does not affect on existing storage layout. This works as its used on contracts
              // that have name and revision < 32 bytes
              pragma solidity ^0.8.10;
              import {ECDSA} from 'openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol';
              import {ShortStrings, ShortString} from 'openzeppelin-contracts/contracts/utils/ShortStrings.sol';
              import {IERC5267} from 'openzeppelin-contracts/contracts/interfaces/IERC5267.sol';
              /**
               * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
               *
               * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
               * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
               * they need in their contracts using a combination of `abi.encode` and `keccak256`.
               *
               * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
               * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
               * ({_hashTypedDataV4}).
               *
               * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
               * the chain id to protect against replay attacks on an eventual fork of the chain.
               *
               * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
               * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
               *
               * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
               * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the
               * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
               *
               * _Available since v3.4._
               *
               * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
               */
              abstract contract EIP712 is IERC5267 {
                using ShortStrings for *;
                bytes32 private constant _TYPE_HASH =
                  keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)');
                // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
                // invalidate the cached domain separator if the chain id changes.
                bytes32 private immutable _cachedDomainSeparator;
                uint256 private immutable _cachedChainId;
                address private immutable _cachedThis;
                bytes32 private immutable _hashedName;
                bytes32 private immutable _hashedVersion;
                ShortString private immutable _name;
                ShortString private immutable _version;
                /**
                 * @dev Initializes the domain separator and parameter caches.
                 *
                 * The meaning of `name` and `version` is specified in
                 * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
                 *
                 * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
                 * - `version`: the current major version of the signing domain.
                 *
                 * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
                 * contract upgrade].
                 */
                /// @dev BGD: removed usage of fallback variables to not modify previous storage layout. As we know that the length of
                ///           name and version will not be bigger than 32 bytes we use toShortString as there is no need to use the fallback system.
                constructor(string memory name, string memory version) {
                  _name = name.toShortString();
                  _version = version.toShortString();
                  _hashedName = keccak256(bytes(name));
                  _hashedVersion = keccak256(bytes(version));
                  _cachedChainId = block.chainid;
                  _cachedDomainSeparator = _buildDomainSeparator();
                  _cachedThis = address(this);
                }
                /**
                 * @dev Returns the domain separator for the current chain.
                 */
                function _domainSeparatorV4() internal view returns (bytes32) {
                  if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
                    return _cachedDomainSeparator;
                  } else {
                    return _buildDomainSeparator();
                  }
                }
                function _buildDomainSeparator() private view returns (bytes32) {
                  return
                    keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
                }
                /**
                 * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
                 * function returns the hash of the fully encoded EIP712 message for this domain.
                 *
                 * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
                 *
                 * ```solidity
                 * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
                 *     keccak256("Mail(address to,string contents)"),
                 *     mailTo,
                 *     keccak256(bytes(mailContents))
                 * )));
                 * address signer = ECDSA.recover(digest, signature);
                 * ```
                 */
                function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
                  return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
                }
                /**
                 * @dev See {EIP-5267}.
                 *
                 * _Available since v4.9._
                 */
                function eip712Domain()
                  public
                  view
                  virtual
                  returns (
                    bytes1 fields,
                    string memory name,
                    string memory version,
                    uint256 chainId,
                    address verifyingContract,
                    bytes32 salt,
                    uint256[] memory extensions
                  )
                {
                  return (
                    hex'0f', // 01111
                    _EIP712Name(),
                    _EIP712Version(),
                    block.chainid,
                    address(this),
                    bytes32(0),
                    new uint256[](0)
                  );
                }
                /**
                 * @dev The name parameter for the EIP712 domain.
                 *
                 * NOTE: By default this function reads _name which is an immutable value.
                 * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
                 *
                 * _Available since v5.0._
                 */
                /// @dev BGD: we use toString instead of toStringWithFallback as we dont have fallback, to not modify previous storage layout
                // solhint-disable-next-line func-name-mixedcase
                function _EIP712Name() internal view returns (string memory) {
                  return _name.toString(); // _name.toStringWithFallback(_nameFallback);
                }
                /**
                 * @dev The version parameter for the EIP712 domain.
                 *
                 * NOTE: By default this function reads _version which is an immutable value.
                 * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
                 *
                 * _Available since v5.0._
                 */
                /// @dev BGD: we use toString instead of toStringWithFallback as we dont have fallback, to not modify previous storage layout
                // solhint-disable-next-line func-name-mixedcase
                function _EIP712Version() internal view returns (string memory) {
                  return _version.toString();
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              /**
               * @title VersionedInitializable
               *
               * @dev Helper contract to support initializer functions. To use it, replace
               * the constructor with a function that has the `initializer` modifier.
               * WARNING: Unlike constructors, initializer functions must be manually
               * invoked. This applies both to deploying an Initializable contract, as well
               * as extending an Initializable contract via inheritance.
               * WARNING: When used with inheritance, manual care must be taken to not invoke
               * a parent initializer twice, or ensure that all initializers are idempotent,
               * because this is not dealt with automatically as with constructors.
               *
               * @author Aave, inspired by the OpenZeppelin Initializable contract
               */
              abstract contract VersionedInitializable {
                /**
                 * @dev Indicates that the contract has been initialized.
                 */
                uint256 internal lastInitializedRevision = 0;
                /**
                 * @dev Modifier to use in the initializer function of a contract.
                 */
                modifier initializer() {
                  uint256 revision = getRevision();
                  require(
                    revision > lastInitializedRevision,
                    'Contract instance has already been initialized'
                  );
                  lastInitializedRevision = revision;
                  _;
                }
                /// @dev returns the revision number of the contract.
                /// Needs to be defined in the inherited class as a constant.
                function getRevision() internal pure virtual returns (uint256);
                // Reserved storage space to allow for layout changes in the future.
                uint256[50] private ______gap;
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              import {DistributionTypes} from '../lib/DistributionTypes.sol';
              /**
               * @title AaveDistributionManager
               * @notice Accounting contract to manage multiple staking distributions
               * @author Aave
               */
              contract AaveDistributionManager {
                struct AssetData {
                  uint128 emissionPerSecond;
                  uint128 lastUpdateTimestamp;
                  uint256 index;
                  mapping(address => uint256) users;
                }
                uint256 public immutable DISTRIBUTION_END;
                address public immutable EMISSION_MANAGER;
                uint8 public constant PRECISION = 18;
                mapping(address => AssetData) public assets;
                event AssetConfigUpdated(address indexed asset, uint256 emission);
                event AssetIndexUpdated(address indexed asset, uint256 index);
                event UserIndexUpdated(
                  address indexed user,
                  address indexed asset,
                  uint256 index
                );
                constructor(address emissionManager, uint256 distributionDuration) {
                  DISTRIBUTION_END = block.timestamp + distributionDuration;
                  EMISSION_MANAGER = emissionManager;
                }
                /**
                 * @dev Configures the distribution of rewards for a list of assets
                 * @param assetsConfigInput The list of configurations to apply
                 */
                function _configureAssets(
                  DistributionTypes.AssetConfigInput[] memory assetsConfigInput
                ) internal {
                  for (uint256 i = 0; i < assetsConfigInput.length; i++) {
                    AssetData storage assetConfig = assets[
                      assetsConfigInput[i].underlyingAsset
                    ];
                    _updateAssetStateInternal(
                      assetsConfigInput[i].underlyingAsset,
                      assetConfig,
                      assetsConfigInput[i].totalStaked
                    );
                    assetConfig.emissionPerSecond = assetsConfigInput[i].emissionPerSecond;
                    emit AssetConfigUpdated(
                      assetsConfigInput[i].underlyingAsset,
                      assetsConfigInput[i].emissionPerSecond
                    );
                  }
                }
                /**
                 * @dev Updates the state of one distribution, mainly rewards index and timestamp
                 * @param underlyingAsset The address used as key in the distribution, for example sAAVE or the aTokens addresses on Aave
                 * @param assetConfig Storage pointer to the distribution's config
                 * @param totalStaked Current total of staked assets for this distribution
                 * @return The new distribution index
                 */
                function _updateAssetStateInternal(
                  address underlyingAsset,
                  AssetData storage assetConfig,
                  uint256 totalStaked
                ) internal returns (uint256) {
                  uint256 oldIndex = assetConfig.index;
                  uint128 lastUpdateTimestamp = assetConfig.lastUpdateTimestamp;
                  if (block.timestamp == lastUpdateTimestamp) {
                    return oldIndex;
                  }
                  uint256 newIndex = _getAssetIndex(
                    oldIndex,
                    assetConfig.emissionPerSecond,
                    lastUpdateTimestamp,
                    totalStaked
                  );
                  if (newIndex != oldIndex) {
                    assetConfig.index = newIndex;
                    emit AssetIndexUpdated(underlyingAsset, newIndex);
                  }
                  assetConfig.lastUpdateTimestamp = uint128(block.timestamp);
                  return newIndex;
                }
                /**
                 * @dev Updates the state of an user in a distribution
                 * @param user The user's address
                 * @param asset The address of the reference asset of the distribution
                 * @param stakedByUser Amount of tokens staked by the user in the distribution at the moment
                 * @param totalStaked Total tokens staked in the distribution
                 * @return The accrued rewards for the user until the moment
                 */
                function _updateUserAssetInternal(
                  address user,
                  address asset,
                  uint256 stakedByUser,
                  uint256 totalStaked
                ) internal returns (uint256) {
                  AssetData storage assetData = assets[asset];
                  uint256 userIndex = assetData.users[user];
                  uint256 accruedRewards = 0;
                  uint256 newIndex = _updateAssetStateInternal(asset, assetData, totalStaked);
                  if (userIndex != newIndex) {
                    if (stakedByUser != 0) {
                      accruedRewards = _getRewards(stakedByUser, newIndex, userIndex);
                    }
                    assetData.users[user] = newIndex;
                    emit UserIndexUpdated(user, asset, newIndex);
                  }
                  return accruedRewards;
                }
                /**
                 * @dev Used by "frontend" stake contracts to update the data of an user when claiming rewards from there
                 * @param user The address of the user
                 * @param stakes List of structs of the user data related with his stake
                 * @return The accrued rewards for the user until the moment
                 */
                function _claimRewards(
                  address user,
                  DistributionTypes.UserStakeInput[] memory stakes
                ) internal returns (uint256) {
                  uint256 accruedRewards = 0;
                  for (uint256 i = 0; i < stakes.length; i++) {
                    accruedRewards =
                      accruedRewards +
                      _updateUserAssetInternal(
                        user,
                        stakes[i].underlyingAsset,
                        stakes[i].stakedByUser,
                        stakes[i].totalStaked
                      );
                  }
                  return accruedRewards;
                }
                /**
                 * @dev Return the accrued rewards for an user over a list of distribution
                 * @param user The address of the user
                 * @param stakes List of structs of the user data related with his stake
                 * @return The accrued rewards for the user until the moment
                 */
                function _getUnclaimedRewards(
                  address user,
                  DistributionTypes.UserStakeInput[] memory stakes
                ) internal view returns (uint256) {
                  uint256 accruedRewards = 0;
                  for (uint256 i = 0; i < stakes.length; i++) {
                    AssetData storage assetConfig = assets[stakes[i].underlyingAsset];
                    uint256 assetIndex = _getAssetIndex(
                      assetConfig.index,
                      assetConfig.emissionPerSecond,
                      assetConfig.lastUpdateTimestamp,
                      stakes[i].totalStaked
                    );
                    accruedRewards =
                      accruedRewards +
                      _getRewards(
                        stakes[i].stakedByUser,
                        assetIndex,
                        assetConfig.users[user]
                      );
                  }
                  return accruedRewards;
                }
                /**
                 * @dev Internal function for the calculation of user's rewards on a distribution
                 * @param principalUserBalance Amount staked by the user on a distribution
                 * @param reserveIndex Current index of the distribution
                 * @param userIndex Index stored for the user, representation his staking moment
                 * @return The rewards
                 */
                function _getRewards(
                  uint256 principalUserBalance,
                  uint256 reserveIndex,
                  uint256 userIndex
                ) internal pure returns (uint256) {
                  return
                    (principalUserBalance * (reserveIndex - userIndex)) /
                    (10 ** uint256(PRECISION));
                }
                /**
                 * @dev Calculates the next value of an specific distribution index, with validations
                 * @param currentIndex Current index of the distribution
                 * @param emissionPerSecond Representing the total rewards distributed per second per asset unit, on the distribution
                 * @param lastUpdateTimestamp Last moment this distribution was updated
                 * @param totalBalance of tokens considered for the distribution
                 * @return The new index.
                 */
                function _getAssetIndex(
                  uint256 currentIndex,
                  uint256 emissionPerSecond,
                  uint128 lastUpdateTimestamp,
                  uint256 totalBalance
                ) internal view returns (uint256) {
                  if (
                    emissionPerSecond == 0 ||
                    totalBalance == 0 ||
                    lastUpdateTimestamp == block.timestamp ||
                    lastUpdateTimestamp >= DISTRIBUTION_END
                  ) {
                    return currentIndex;
                  }
                  uint256 currentTimestamp = block.timestamp > DISTRIBUTION_END
                    ? DISTRIBUTION_END
                    : block.timestamp;
                  uint256 timeDelta = currentTimestamp - lastUpdateTimestamp;
                  return
                    ((emissionPerSecond * timeDelta * (10 ** uint256(PRECISION))) /
                      totalBalance) + currentIndex;
                }
                /**
                 * @dev Returns the data of an user on a distribution
                 * @param user Address of the user
                 * @param asset The address of the reference asset of the distribution
                 * @return The new index
                 */
                function getUserAssetData(
                  address user,
                  address asset
                ) public view returns (uint256) {
                  return assets[asset].users[user];
                }
              }
              // SPDX-License-Identifier: agpl-3.0
              pragma solidity ^0.8.0;
              /**
               * @title MOCK CONTRACT TO KEEP VALID STORAGE LAYOUT
               * @dev WAS including snapshots of balances on transfer-related actions
               * @author BGD Labs
               **/
              abstract contract GovernancePowerWithSnapshot {
                uint256[3] private __________DEPRECATED_GOV_V2_PART;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {BaseAaveToken} from 'aave-token-v3/BaseAaveToken.sol';
              /**
               * @title BaseMintableAaveToken
               * @author BGD labs
               * @notice extension for BaseAaveToken adding mint/burn and transfer hooks
               */
              contract BaseMintableAaveToken is BaseAaveToken {
                /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                 * the total supply.
                 *
                 * Emits a {Transfer} event with `from` set to the zero address.
                 *
                 * Requirements:
                 *
                 * - `account` cannot be the zero address.
                 */
                function _mint(address account, uint104 amount) internal virtual {
                  require(account != address(0), 'ERC20: mint to the zero address');
                  uint104 balanceBefore = _balances[account].balance;
                  _totalSupply += amount;
                  _balances[account].balance += amount;
                  emit Transfer(address(0), account, amount);
                  _afterTokenTransfer(address(0), account, 0, balanceBefore, amount);
                }
                /**
                 * @dev Destroys `amount` tokens from `account`, reducing the
                 * total supply.
                 *
                 * Emits a {Transfer} event with `to` set to the zero address.
                 *
                 * Requirements:
                 *
                 * - `account` cannot be the zero address.
                 * - `account` must have at least `amount` tokens.
                 */
                function _burn(address account, uint104 amount) internal virtual {
                  require(account != address(0), 'ERC20: burn from the zero address');
                  uint104 accountBalance = _balances[account].balance;
                  require(accountBalance >= amount, 'ERC20: burn amount exceeds balance');
                  unchecked {
                    _balances[account].balance = accountBalance - amount;
                    // Overflow not possible: amount <= accountBalance <= totalSupply.
                    _totalSupply -= amount;
                  }
                  emit Transfer(account, address(0), amount);
                  _afterTokenTransfer(account, address(0), accountBalance, 0, amount);
                }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
              pragma solidity ^0.8.19;
              import "./math/Math.sol";
              import "./math/SignedMath.sol";
              /**
               * @dev String operations.
               */
              library Strings {
                  bytes16 private constant _SYMBOLS = "0123456789abcdef";
                  uint8 private constant _ADDRESS_LENGTH = 20;
                  /**
                   * @dev The `value` string doesn't fit in the specified `length`.
                   */
                  error StringsInsufficientHexLength(uint256 value, uint256 length);
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` decimal representation.
                   */
                  function toString(uint256 value) internal pure returns (string memory) {
                      unchecked {
                          uint256 length = Math.log10(value) + 1;
                          string memory buffer = new string(length);
                          uint256 ptr;
                          /// @solidity memory-safe-assembly
                          assembly {
                              ptr := add(buffer, add(32, length))
                          }
                          while (true) {
                              ptr--;
                              /// @solidity memory-safe-assembly
                              assembly {
                                  mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                              }
                              value /= 10;
                              if (value == 0) break;
                          }
                          return buffer;
                      }
                  }
                  /**
                   * @dev Converts a `int256` to its ASCII `string` decimal representation.
                   */
                  function toStringSigned(int256 value) internal pure returns (string memory) {
                      return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
                  }
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
                   */
                  function toHexString(uint256 value) internal pure returns (string memory) {
                      unchecked {
                          return toHexString(value, Math.log256(value) + 1);
                      }
                  }
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
                   */
                  function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                      uint256 localValue = value;
                      bytes memory buffer = new bytes(2 * length + 2);
                      buffer[0] = "0";
                      buffer[1] = "x";
                      for (uint256 i = 2 * length + 1; i > 1; --i) {
                          buffer[i] = _SYMBOLS[localValue & 0xf];
                          localValue >>= 4;
                      }
                      if (localValue != 0) {
                          revert StringsInsufficientHexLength(value, length);
                      }
                      return string(buffer);
                  }
                  /**
                   * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
                   */
                  function toHexString(address addr) internal pure returns (string memory) {
                      return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
                  }
                  /**
                   * @dev Returns true if the two strings are equal.
                   */
                  function equal(string memory a, string memory b) internal pure returns (bool) {
                      return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/ShortStrings.sol)
              pragma solidity ^0.8.19;
              import "./StorageSlot.sol";
              // | string  | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |
              // | length  | 0x                                                              BB |
              type ShortString is bytes32;
              /**
               * @dev This library provides functions to convert short memory strings
               * into a `ShortString` type that can be used as an immutable variable.
               *
               * Strings of arbitrary length can be optimized using this library if
               * they are short enough (up to 31 bytes) by packing them with their
               * length (1 byte) in a single EVM word (32 bytes). Additionally, a
               * fallback mechanism can be used for every other case.
               *
               * Usage example:
               *
               * ```solidity
               * contract Named {
               *     using ShortStrings for *;
               *
               *     ShortString private immutable _name;
               *     string private _nameFallback;
               *
               *     constructor(string memory contractName) {
               *         _name = contractName.toShortStringWithFallback(_nameFallback);
               *     }
               *
               *     function name() external view returns (string memory) {
               *         return _name.toStringWithFallback(_nameFallback);
               *     }
               * }
               * ```
               */
              library ShortStrings {
                  // Used as an identifier for strings longer than 31 bytes.
                  bytes32 private constant _FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;
                  error StringTooLong(string str);
                  error InvalidShortString();
                  /**
                   * @dev Encode a string of at most 31 chars into a `ShortString`.
                   *
                   * This will trigger a `StringTooLong` error is the input string is too long.
                   */
                  function toShortString(string memory str) internal pure returns (ShortString) {
                      bytes memory bstr = bytes(str);
                      if (bstr.length > 31) {
                          revert StringTooLong(str);
                      }
                      return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
                  }
                  /**
                   * @dev Decode a `ShortString` back to a "normal" string.
                   */
                  function toString(ShortString sstr) internal pure returns (string memory) {
                      uint256 len = byteLength(sstr);
                      // using `new string(len)` would work locally but is not memory safe.
                      string memory str = new string(32);
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore(str, len)
                          mstore(add(str, 0x20), sstr)
                      }
                      return str;
                  }
                  /**
                   * @dev Return the length of a `ShortString`.
                   */
                  function byteLength(ShortString sstr) internal pure returns (uint256) {
                      uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
                      if (result > 31) {
                          revert InvalidShortString();
                      }
                      return result;
                  }
                  /**
                   * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
                   */
                  function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
                      if (bytes(value).length < 32) {
                          return toShortString(value);
                      } else {
                          StorageSlot.getStringSlot(store).value = value;
                          return ShortString.wrap(_FALLBACK_SENTINEL);
                      }
                  }
                  /**
                   * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
                   */
                  function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
                      if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) {
                          return toString(value);
                      } else {
                          return store;
                      }
                  }
                  /**
                   * @dev Return the length of a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
                   *
                   * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
                   * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
                   */
                  function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
                      if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) {
                          return byteLength(value);
                      } else {
                          return bytes(store).length;
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol)
              pragma solidity ^0.8.19;
              interface IERC5267 {
                  /**
                   * @dev MAY be emitted to signal that the domain could have changed.
                   */
                  event EIP712DomainChanged();
                  /**
                   * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
                   * signature.
                   */
                  function eip712Domain()
                      external
                      view
                      returns (
                          bytes1 fields,
                          string memory name,
                          string memory version,
                          uint256 chainId,
                          address verifyingContract,
                          bytes32 salt,
                          uint256[] memory extensions
                      );
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import {Context} from 'openzeppelin-contracts/contracts/utils/Context.sol';
              import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol';
              import {IERC20Metadata} from 'openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol';
              import {DelegationMode} from './DelegationAwareBalance.sol';
              // Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol)
              abstract contract BaseAaveToken is Context, IERC20Metadata {
                struct DelegationAwareBalance {
                  uint104 balance;
                  uint72 delegatedPropositionBalance;
                  uint72 delegatedVotingBalance;
                  DelegationMode delegationMode;
                }
                mapping(address => DelegationAwareBalance) internal _balances;
                mapping(address => mapping(address => uint256)) internal _allowances;
                uint256 internal _totalSupply;
                string internal _name;
                string internal _symbol;
                // @dev DEPRECATED
                // kept for backwards compatibility with old storage layout
                uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS;
                /**
                 * @dev Returns the name of the token.
                 */
                function name() public view virtual override returns (string memory) {
                  return _name;
                }
                /**
                 * @dev Returns the symbol of the token, usually a shorter version of the
                 * name.
                 */
                function symbol() public view virtual override returns (string memory) {
                  return _symbol;
                }
                function decimals() public view virtual override returns (uint8) {
                  return 18;
                }
                function totalSupply() public view virtual override returns (uint256) {
                  return _totalSupply;
                }
                function balanceOf(address account) public view virtual override returns (uint256) {
                  return _balances[account].balance;
                }
                function transfer(address to, uint256 amount) public virtual override returns (bool) {
                  address owner = _msgSender();
                  _transfer(owner, to, amount);
                  return true;
                }
                function allowance(address owner, address spender)
                  public
                  view
                  virtual
                  override
                  returns (uint256)
                {
                  return _allowances[owner][spender];
                }
                function approve(address spender, uint256 amount) public virtual override returns (bool) {
                  address owner = _msgSender();
                  _approve(owner, spender, amount);
                  return true;
                }
                function transferFrom(
                  address from,
                  address to,
                  uint256 amount
                ) public virtual override returns (bool) {
                  address spender = _msgSender();
                  _spendAllowance(from, spender, amount);
                  _transfer(from, to, amount);
                  return true;
                }
                function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                  address owner = _msgSender();
                  _approve(owner, spender, _allowances[owner][spender] + addedValue);
                  return true;
                }
                function decreaseAllowance(address spender, uint256 subtractedValue)
                  public
                  virtual
                  returns (bool)
                {
                  address owner = _msgSender();
                  uint256 currentAllowance = _allowances[owner][spender];
                  require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero');
                  unchecked {
                    _approve(owner, spender, currentAllowance - subtractedValue);
                  }
                  return true;
                }
                function _transfer(
                  address from,
                  address to,
                  uint256 amount
                ) internal virtual {
                  require(from != address(0), 'ERC20: transfer from the zero address');
                  require(to != address(0), 'ERC20: transfer to the zero address');
                  if (from != to) {
                    uint104 fromBalanceBefore = _balances[from].balance;
                    uint104 toBalanceBefore = _balances[to].balance;
                    require(fromBalanceBefore >= amount, 'ERC20: transfer amount exceeds balance');
                    unchecked {
                      _balances[from].balance = fromBalanceBefore - uint104(amount);
                    }
                    _balances[to].balance = toBalanceBefore + uint104(amount);
                    _afterTokenTransfer(from, to, fromBalanceBefore, toBalanceBefore, amount);
                  }
                  emit Transfer(from, to, amount);
                }
                function _approve(
                  address owner,
                  address spender,
                  uint256 amount
                ) internal virtual {
                  require(owner != address(0), 'ERC20: approve from the zero address');
                  require(spender != address(0), 'ERC20: approve to the zero address');
                  _allowances[owner][spender] = amount;
                  emit Approval(owner, spender, amount);
                }
                function _spendAllowance(
                  address owner,
                  address spender,
                  uint256 amount
                ) internal virtual {
                  uint256 currentAllowance = allowance(owner, spender);
                  if (currentAllowance != type(uint256).max) {
                    require(currentAllowance >= amount, 'ERC20: insufficient allowance');
                    unchecked {
                      _approve(owner, spender, currentAllowance - amount);
                    }
                  }
                }
                /**
                 * @dev after token transfer hook, added for delegation system
                 * @param from token sender
                 * @param to token recipient
                 * @param fromBalanceBefore balance of the sender before transfer
                 * @param toBalanceBefore balance of the recipient before transfer
                 * @param amount amount of tokens sent
                 **/
                function _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 fromBalanceBefore,
                  uint256 toBalanceBefore,
                  uint256 amount
                ) internal virtual {}
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
              pragma solidity ^0.8.19;
              /**
               * @dev Standard math utilities missing in the Solidity language.
               */
              library Math {
                  /**
                   * @dev Muldiv operation overflow.
                   */
                  error MathOverflowedMulDiv();
                  enum Rounding {
                      Down, // Toward negative infinity
                      Up, // Toward infinity
                      Zero // Toward zero
                  }
                  /**
                   * @dev Returns the addition of two unsigned integers, with an overflow flag.
                   *
                   * _Available since v5.0._
                   */
                  function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                      unchecked {
                          uint256 c = a + b;
                          if (c < a) return (false, 0);
                          return (true, c);
                      }
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
                   *
                   * _Available since v5.0._
                   */
                  function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                      unchecked {
                          if (b > a) return (false, 0);
                          return (true, a - b);
                      }
                  }
                  /**
                   * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
                   *
                   * _Available since v5.0._
                   */
                  function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                      unchecked {
                          // 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 v5.0._
                   */
                  function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                      unchecked {
                          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 v5.0._
                   */
                  function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                      unchecked {
                          if (b == 0) return (false, 0);
                          return (true, a % b);
                      }
                  }
                  /**
                   * @dev Returns the largest of two numbers.
                   */
                  function max(uint256 a, uint256 b) internal pure returns (uint256) {
                      return a > b ? a : b;
                  }
                  /**
                   * @dev Returns the smallest of two numbers.
                   */
                  function min(uint256 a, uint256 b) internal pure returns (uint256) {
                      return a < b ? a : b;
                  }
                  /**
                   * @dev Returns the average of two numbers. The result is rounded towards
                   * zero.
                   */
                  function average(uint256 a, uint256 b) internal pure returns (uint256) {
                      // (a + b) / 2 can overflow.
                      return (a & b) + (a ^ b) / 2;
                  }
                  /**
                   * @dev Returns the ceiling of the division of two numbers.
                   *
                   * This differs from standard division with `/` in that it rounds up instead
                   * of rounding down.
                   */
                  function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                      if (b == 0) {
                          // Guarantee the same behavior as in a regular Solidity division.
                          return a / b;
                      }
                      // (a + b - 1) / b can overflow on addition, so we distribute.
                      return a == 0 ? 0 : (a - 1) / b + 1;
                  }
                  /**
                   * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
                   * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
                   * with further edits by Uniswap Labs also under MIT license.
                   */
                  function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
                      unchecked {
                          // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                          // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                          // variables such that product = prod1 * 2^256 + prod0.
                          uint256 prod0; // Least significant 256 bits of the product
                          uint256 prod1; // Most significant 256 bits of the product
                          assembly {
                              let mm := mulmod(x, y, not(0))
                              prod0 := mul(x, y)
                              prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                          }
                          // Handle non-overflow cases, 256 by 256 division.
                          if (prod1 == 0) {
                              // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                              // The surrounding unchecked block does not change this fact.
                              // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                              return prod0 / denominator;
                          }
                          // Make sure the result is less than 2^256. Also prevents denominator == 0.
                          if (denominator <= prod1) {
                              revert MathOverflowedMulDiv();
                          }
                          ///////////////////////////////////////////////
                          // 512 by 256 division.
                          ///////////////////////////////////////////////
                          // Make division exact by subtracting the remainder from [prod1 prod0].
                          uint256 remainder;
                          assembly {
                              // Compute remainder using mulmod.
                              remainder := mulmod(x, y, denominator)
                              // Subtract 256 bit number from 512 bit number.
                              prod1 := sub(prod1, gt(remainder, prod0))
                              prod0 := sub(prod0, remainder)
                          }
                          // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                          // See https://cs.stackexchange.com/q/138556/92363.
                          // Does not overflow because the denominator cannot be zero at this stage in the function.
                          uint256 twos = denominator & (~denominator + 1);
                          assembly {
                              // Divide denominator by twos.
                              denominator := div(denominator, twos)
                              // Divide [prod1 prod0] by twos.
                              prod0 := div(prod0, twos)
                              // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                              twos := add(div(sub(0, twos), twos), 1)
                          }
                          // Shift in bits from prod1 into prod0.
                          prod0 |= prod1 * twos;
                          // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                          // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                          // four bits. That is, denominator * inv = 1 mod 2^4.
                          uint256 inverse = (3 * denominator) ^ 2;
                          // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                          // in modular arithmetic, doubling the correct bits in each step.
                          inverse *= 2 - denominator * inverse; // inverse mod 2^8
                          inverse *= 2 - denominator * inverse; // inverse mod 2^16
                          inverse *= 2 - denominator * inverse; // inverse mod 2^32
                          inverse *= 2 - denominator * inverse; // inverse mod 2^64
                          inverse *= 2 - denominator * inverse; // inverse mod 2^128
                          inverse *= 2 - denominator * inverse; // inverse mod 2^256
                          // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                          // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                          // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                          // is no longer required.
                          result = prod0 * inverse;
                          return result;
                      }
                  }
                  /**
                   * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
                   */
                  function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
                      uint256 result = mulDiv(x, y, denominator);
                      if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                          result += 1;
                      }
                      return result;
                  }
                  /**
                   * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
                   *
                   * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
                   */
                  function sqrt(uint256 a) internal pure returns (uint256) {
                      if (a == 0) {
                          return 0;
                      }
                      // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                      //
                      // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                      // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                      //
                      // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                      // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                      // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                      //
                      // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                      uint256 result = 1 << (log2(a) >> 1);
                      // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                      // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                      // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                      // into the expected uint128 result.
                      unchecked {
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          return min(result, a / result);
                      }
                  }
                  /**
                   * @notice Calculates sqrt(a), following the selected rounding direction.
                   */
                  function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = sqrt(a);
                          return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 2, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log2(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >> 128 > 0) {
                              value >>= 128;
                              result += 128;
                          }
                          if (value >> 64 > 0) {
                              value >>= 64;
                              result += 64;
                          }
                          if (value >> 32 > 0) {
                              value >>= 32;
                              result += 32;
                          }
                          if (value >> 16 > 0) {
                              value >>= 16;
                              result += 16;
                          }
                          if (value >> 8 > 0) {
                              value >>= 8;
                              result += 8;
                          }
                          if (value >> 4 > 0) {
                              value >>= 4;
                              result += 4;
                          }
                          if (value >> 2 > 0) {
                              value >>= 2;
                              result += 2;
                          }
                          if (value >> 1 > 0) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log2(value);
                          return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 10, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log10(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >= 10 ** 64) {
                              value /= 10 ** 64;
                              result += 64;
                          }
                          if (value >= 10 ** 32) {
                              value /= 10 ** 32;
                              result += 32;
                          }
                          if (value >= 10 ** 16) {
                              value /= 10 ** 16;
                              result += 16;
                          }
                          if (value >= 10 ** 8) {
                              value /= 10 ** 8;
                              result += 8;
                          }
                          if (value >= 10 ** 4) {
                              value /= 10 ** 4;
                              result += 4;
                          }
                          if (value >= 10 ** 2) {
                              value /= 10 ** 2;
                              result += 2;
                          }
                          if (value >= 10 ** 1) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log10(value);
                          return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 256, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   *
                   * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
                   */
                  function log256(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >> 128 > 0) {
                              value >>= 128;
                              result += 16;
                          }
                          if (value >> 64 > 0) {
                              value >>= 64;
                              result += 8;
                          }
                          if (value >> 32 > 0) {
                              value >>= 32;
                              result += 4;
                          }
                          if (value >> 16 > 0) {
                              value >>= 16;
                              result += 2;
                          }
                          if (value >> 8 > 0) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log256(value);
                          return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
              pragma solidity ^0.8.19;
              /**
               * @dev Standard signed math utilities missing in the Solidity language.
               */
              library SignedMath {
                  /**
                   * @dev Returns the largest of two signed numbers.
                   */
                  function max(int256 a, int256 b) internal pure returns (int256) {
                      return a > b ? a : b;
                  }
                  /**
                   * @dev Returns the smallest of two signed numbers.
                   */
                  function min(int256 a, int256 b) internal pure returns (int256) {
                      return a < b ? a : b;
                  }
                  /**
                   * @dev Returns the average of two signed numbers without overflow.
                   * The result is rounded towards zero.
                   */
                  function average(int256 a, int256 b) internal pure returns (int256) {
                      // Formula from the book "Hacker's Delight"
                      int256 x = (a & b) + ((a ^ b) >> 1);
                      return x + (int256(uint256(x) >> 255) & (a ^ b));
                  }
                  /**
                   * @dev Returns the absolute unsigned value of a signed value.
                   */
                  function abs(int256 n) internal pure returns (uint256) {
                      unchecked {
                          // must be unchecked in order to support `n = type(int256).min`
                          return uint256(n >= 0 ? n : -n);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
              // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
              pragma solidity ^0.8.19;
              /**
               * @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;
               *     }
               * }
               * ```
               *
               * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
               * _Available since v4.9 for `string`, `bytes`._
               */
              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
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
              pragma solidity ^0.8.19;
              /**
               * @dev Provides information about the current execution context, including the
               * sender of the transaction and its data. While these are generally available
               * via msg.sender and msg.data, they should not be accessed in such a direct
               * manner, since when dealing with meta-transactions the account sending and
               * paying for execution may not be the actual sender (as far as an application
               * is concerned).
               *
               * This contract is only required for intermediate, library-like contracts.
               */
              abstract contract Context {
                  function _msgSender() internal view virtual returns (address) {
                      return msg.sender;
                  }
                  function _msgData() internal view virtual returns (bytes calldata) {
                      return msg.data;
                  }
              }
              

              File 9 of 9: InitializableImmutableAdminUpgradeabilityProxy
              // SPDX-License-Identifier: AGPL-3.0
              pragma solidity 0.8.10;
              /**
               * @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) {
                  // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                  // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                  // for accounts without code, i.e. `keccak256('')`
                  bytes32 codehash;
                  bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                  // solhint-disable-next-line no-inline-assembly
                  assembly {
                    codehash := extcodehash(account)
                  }
                  return (codehash != accountHash && codehash != 0x0);
                }
                /**
                 * @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');
                }
              }
              // SPDX-License-Identifier: AGPL-3.0
              pragma solidity 0.8.10;
              import './Proxy.sol';
              import '../contracts/Address.sol';
              /**
               * @title BaseUpgradeabilityProxy
               * @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 BaseUpgradeabilityProxy is Proxy {
                /**
                 * @dev Emitted when the implementation is upgraded.
                 * @param implementation Address of the new implementation.
                 */
                event Upgraded(address indexed implementation);
                /**
                 * @dev Storage slot with the address of the current implementation.
                 * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                 * validated in the constructor.
                 */
                bytes32 internal constant IMPLEMENTATION_SLOT =
                  0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                /**
                 * @dev Returns the current implementation.
                 * @return impl Address of the current implementation
                 */
                function _implementation() internal view override returns (address impl) {
                  bytes32 slot = IMPLEMENTATION_SLOT;
                  //solium-disable-next-line
                  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) internal {
                  require(
                    Address.isContract(newImplementation),
                    'Cannot set a proxy implementation to a non-contract address'
                  );
                  bytes32 slot = IMPLEMENTATION_SLOT;
                  //solium-disable-next-line
                  assembly {
                    sstore(slot, newImplementation)
                  }
                }
              }
              // SPDX-License-Identifier: AGPL-3.0
              pragma solidity 0.8.10;
              import './BaseUpgradeabilityProxy.sol';
              /**
               * @title InitializableUpgradeabilityProxy
               * @dev Extends BaseUpgradeabilityProxy with an initializer for initializing
               * implementation and init data.
               */
              contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy {
                /**
                 * @dev Contract initializer.
                 * @param _logic Address of the initial implementation.
                 * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                 * It should include the signature and the parameters of the function to be called, as described in
                 * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                 * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                 */
                function initialize(address _logic, bytes memory _data) public payable {
                  require(_implementation() == address(0));
                  assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
                  _setImplementation(_logic);
                  if (_data.length > 0) {
                    (bool success, ) = _logic.delegatecall(_data);
                    require(success);
                  }
                }
              }
              // SPDX-License-Identifier: AGPL-3.0
              pragma solidity 0.8.10;
              /**
               * @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.
               */
              abstract contract Proxy {
                /**
                 * @dev Fallback function.
                 * Will run if no other function in the contract matches the call data.
                 * Implemented entirely in `_fallback`.
                 */
                fallback() external payable {
                  _fallback();
                }
                /**
                 * @return The Address of the implementation.
                 */
                function _implementation() internal view virtual 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 {
                  //solium-disable-next-line
                  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 virtual {}
                /**
                 * @dev fallback implementation.
                 * Extracted to enable manual triggering.
                 */
                function _fallback() internal {
                  _willFallback();
                  _delegate(_implementation());
                }
              }
              // SPDX-License-Identifier: AGPL-3.0
              pragma solidity 0.8.10;
              import {BaseUpgradeabilityProxy} from '../../../dependencies/openzeppelin/upgradeability/BaseUpgradeabilityProxy.sol';
              /**
               * @title BaseImmutableAdminUpgradeabilityProxy
               * @author Aave, inspired by the OpenZeppelin upgradeability proxy pattern
               * @notice This contract combines an upgradeability proxy with an authorization
               * mechanism for administrative tasks.
               * @dev The admin role is stored in an immutable, which helps saving transactions costs
               * 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 BaseImmutableAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
                address internal immutable _admin;
                /**
                 * @dev Constructor.
                 * @param admin The address of the admin
                 */
                constructor(address admin) {
                  _admin = admin;
                }
                modifier ifAdmin() {
                  if (msg.sender == _admin) {
                    _;
                  } else {
                    _fallback();
                  }
                }
                /**
                 * @notice Return the admin address
                 * @return The address of the proxy admin.
                 */
                function admin() external ifAdmin returns (address) {
                  return _admin;
                }
                /**
                 * @notice Return the implementation address
                 * @return The address of the implementation.
                 */
                function implementation() external ifAdmin returns (address) {
                  return _implementation();
                }
                /**
                 * @notice Upgrade the backing implementation of the proxy.
                 * @dev Only the admin can call this function.
                 * @param newImplementation The address of the new implementation.
                 */
                function upgradeTo(address newImplementation) external ifAdmin {
                  _upgradeTo(newImplementation);
                }
                /**
                 * @notice Upgrade the backing implementation of the proxy and call a function
                 * on the new implementation.
                 * @dev This is useful to initialize the proxied contract.
                 * @param newImplementation The 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/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                 */
                function upgradeToAndCall(address newImplementation, bytes calldata data)
                  external
                  payable
                  ifAdmin
                {
                  _upgradeTo(newImplementation);
                  (bool success, ) = newImplementation.delegatecall(data);
                  require(success);
                }
                /**
                 * @notice Only fall back when the sender is not the admin.
                 */
                function _willFallback() internal virtual override {
                  require(msg.sender != _admin, 'Cannot call fallback function from the proxy admin');
                  super._willFallback();
                }
              }
              // SPDX-License-Identifier: AGPL-3.0
              pragma solidity 0.8.10;
              import {InitializableUpgradeabilityProxy} from '../../../dependencies/openzeppelin/upgradeability/InitializableUpgradeabilityProxy.sol';
              import {Proxy} from '../../../dependencies/openzeppelin/upgradeability/Proxy.sol';
              import {BaseImmutableAdminUpgradeabilityProxy} from './BaseImmutableAdminUpgradeabilityProxy.sol';
              /**
               * @title InitializableAdminUpgradeabilityProxy
               * @author Aave
               * @dev Extends BaseAdminUpgradeabilityProxy with an initializer function
               */
              contract InitializableImmutableAdminUpgradeabilityProxy is
                BaseImmutableAdminUpgradeabilityProxy,
                InitializableUpgradeabilityProxy
              {
                /**
                 * @dev Constructor.
                 * @param admin The address of the admin
                 */
                constructor(address admin) BaseImmutableAdminUpgradeabilityProxy(admin) {
                  // Intentionally left blank
                }
                /// @inheritdoc BaseImmutableAdminUpgradeabilityProxy
                function _willFallback() internal override(BaseImmutableAdminUpgradeabilityProxy, Proxy) {
                  BaseImmutableAdminUpgradeabilityProxy._willFallback();
                }
              }