ETH Price: $2,244.56 (-1.69%)

Transaction Decoder

Block:
21176601 at Nov-13-2024 05:12:47 AM +UTC
Transaction Fee:
0.001424788634854278 ETH $3.20
Gas Used:
54,666 Gas / 26.063524583 Gwei

Emitted Events:

144 InitializableAdminUpgradeabilityProxy.0x8a05f911d8ab7fc50fec37ef4ba7f9bfcb1a3c191c81dcd824ad0946c4e20d65( 0x8a05f911d8ab7fc50fec37ef4ba7f9bfcb1a3c191c81dcd824ad0946c4e20d65, 0x000000000000000000000000eebd6cc55186e3899719cb2c253113021699efaf, 00000000000000000000000000000000000000000000000093e69d7410104500 )

Account State Difference:

  Address   Before After State Difference Code
(Titan Builder)
12.088147154091916386 Eth12.088256468874094362 Eth0.000109314782177976
0x4da27a54...01de870f5
0xeeBD6Cc5...21699efaf
0.692046057413874268 Eth
Nonce: 350
0.69062126877901999 Eth
Nonce: 351
0.001424788634854278

Execution Trace

InitializableAdminUpgradeabilityProxy.CALL( )
  • StakedAaveV3.DELEGATECALL( )
    File 1 of 2: 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 2 of 2: 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;
        }
    }