ATokenVaultFactory

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/ATokenVaultFactory.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
// All Rights Reserved © AaveCo

pragma solidity ^0.8.10;

import {IERC20} from "@openzeppelin/interfaces/IERC20.sol";
import {IPoolAddressesProvider} from "@aave-v3-core/interfaces/IPoolAddressesProvider.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol";
import {ATokenVault} from "./ATokenVault.sol";
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {ProxyAdmin} from "@openzeppelin/proxy/transparent/ProxyAdmin.sol";
import {ATokenVaultRevenueSplitterOwner} from "./ATokenVaultRevenueSplitterOwner.sol";

/**
 * @title ATokenVaultImplDeploymentLib
 * @author Aave Labs
 * @notice Library that handles the deployment of the ATokenVault implementation contract
 * @dev This library is a helper to avoid holding the ATokenVault bytecode in the factory contract avoiding exceeding
 *      the contract size limit.
 */
library ATokenVaultImplDeploymentLib {
    function deployVaultImpl(
        address underlying,
        uint16 referralCode,
        IPoolAddressesProvider poolAddressesProvider
    ) external returns (address vault) {
        return address(new ATokenVault(
            underlying,
            referralCode,
            poolAddressesProvider
        ));
    }
}

/**
 * @title ATokenVaultFactory
 * @author Aave Labs
 * @notice Factory contract for deploying ATokenVault instances
 */
contract ATokenVaultFactory {
    using SafeERC20 for IERC20;

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Emitted when a new vault is deployed
     * @param vault The address of the deployed vault proxy
     * @param implementation The address of the vault implementation
     * @param underlying The underlying asset address
     * @param deployer The address that deployed the vault
     * @param params The parameters used to deploy the vault
     */
    event VaultDeployed(
        address indexed vault,
        address indexed implementation,
        address indexed underlying,
        address deployer,
        VaultParams params
    );

    /**
     * @dev Emitted when a new revenue splitter owner is deployed
     * @param revenueSplitterOwner The address of the deployed revenue splitter owner
     * @param vault The address of the vault to split the revenue from
     * @param owner The address of the owner of the revenue splitter, effective owner of the vault
     * @param revenueRecipients The recipients of the revenue
     */
    event RevenueSplitterOwnerDeployed(
        address indexed revenueSplitterOwner,
        address indexed vault,
        address indexed owner,
        ATokenVaultRevenueSplitterOwner.Recipient[] revenueRecipients
    );

    /*//////////////////////////////////////////////////////////////
                               CONSTANTS
    //////////////////////////////////////////////////////////////*/

    /// @notice Proxy admin address for all deployed vaults, with renounced ownership.
    /// @dev Future version will deploy a plain immutable vault without proxy.
    address internal immutable RENOUNCED_PROXY_ADMIN;

    /*//////////////////////////////////////////////////////////////
                                STRUCTS
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Struct containing constructor parameters for vault deployment
     */
    struct VaultParams {
        address underlying;
        uint16 referralCode;
        IPoolAddressesProvider poolAddressesProvider;
        address owner;
        uint256 initialFee;
        string shareName;
        string shareSymbol;
        uint256 initialLockDeposit;
        ATokenVaultRevenueSplitterOwner.Recipient[] revenueRecipients;
    }

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Constructor
     * @param proxyAdmin The address that will be the admin of all deployed proxies. Must have renounced ownership.
     */
    constructor(address proxyAdmin) {
        RENOUNCED_PROXY_ADMIN = proxyAdmin;
        require(ProxyAdmin(proxyAdmin).owner() == address(0), "PROXY_ADMIN_OWNERSHIP_NOT_RENOUNCED");
    }

    /*//////////////////////////////////////////////////////////////
                            EXTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Deploy a new ATokenVault with the given parameters
     * @param params All parameters needed for vault deployment and initialization
     * @return vault The address of the deployed vault proxy
     */
    function deployVault(VaultParams memory params) public returns (address vault) {
        require(params.underlying != address(0), "ZERO_ADDRESS_NOT_VALID");
        require(address(params.poolAddressesProvider) != address(0), "ZERO_ADDRESS_NOT_VALID");
        require(params.owner != address(0), "ZERO_ADDRESS_NOT_VALID");
        require(params.initialLockDeposit > 0, "ZERO_INITIAL_LOCK_DEPOSIT");
        require(bytes(params.shareName).length > 0, "EMPTY_SHARE_NAME");
        require(bytes(params.shareSymbol).length > 0, "EMPTY_SHARE_SYMBOL");

        IERC20(params.underlying).safeTransferFrom(
            msg.sender,
            address(this),
            params.initialLockDeposit
        );

        address implementation = ATokenVaultImplDeploymentLib.deployVaultImpl(
            params.underlying,
            params.referralCode,
            params.poolAddressesProvider
        );

        vault = address(new TransparentUpgradeableProxy(
            implementation,
            RENOUNCED_PROXY_ADMIN,
            ""
        ));

        address vaultOwner = params.owner;
        if (params.revenueRecipients.length > 0) {
            vaultOwner = _deployRevenueSplitterOwner(
                vault,
                params.owner,
                params.revenueRecipients
            );
        }

        IERC20(params.underlying).safeApprove(vault, params.initialLockDeposit);

        ATokenVault(vault).initialize(
            vaultOwner,
            params.initialFee,
            params.shareName,
            params.shareSymbol,
            params.initialLockDeposit
        );

        emit VaultDeployed(
            vault,
            implementation,
            params.underlying,
            msg.sender,
            params
        );
    }

    /**
     * @notice Deploys a new ATokenVaultRevenueSplitterOwner with the given parameters
     * @param vaultAddress The address of the vault to split the revenue from
     * @param owner The address of the owner of the revenue splitter, effective owner of the vault
     * @param revenueRecipients The recipients of the revenue
     * @return revenueSplitter The address of the deployed revenue splitter
     */
    function deployRevenueSplitterOwner(
        address vaultAddress,
        address owner,
        ATokenVaultRevenueSplitterOwner.Recipient[] memory revenueRecipients
    ) external returns (address) {
        return _deployRevenueSplitterOwner(vaultAddress, owner, revenueRecipients);
    }

    function _deployRevenueSplitterOwner(
        address vaultAddress,
        address owner,
        ATokenVaultRevenueSplitterOwner.Recipient[] memory revenueRecipients
    ) internal returns (address) {
        address revenueSplitter = address(new ATokenVaultRevenueSplitterOwner(vaultAddress, owner, revenueRecipients));
        emit RevenueSplitterOwnerDeployed(revenueSplitter, vaultAddress, owner, revenueRecipients);
        return revenueSplitter;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol)

pragma solidity ^0.8.0;

import "../token/ERC20/IERC20.sol";
"
    },
    "lib/aave-v3-core/contracts/interfaces/IPoolAddressesProvider.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;

/**
 * @title IPoolAddressesProvider
 * @author Aave
 * @notice Defines the basic interface for a Pool Addresses Provider.
 */
interface IPoolAddressesProvider {
  /**
   * @dev Emitted when the market identifier is updated.
   * @param oldMarketId The old id of the market
   * @param newMarketId The new id of the market
   */
  event MarketIdSet(string indexed oldMarketId, string indexed newMarketId);

  /**
   * @dev Emitted when the pool is updated.
   * @param oldAddress The old address of the Pool
   * @param newAddress The new address of the Pool
   */
  event PoolUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the pool configurator is updated.
   * @param oldAddress The old address of the PoolConfigurator
   * @param newAddress The new address of the PoolConfigurator
   */
  event PoolConfiguratorUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the price oracle is updated.
   * @param oldAddress The old address of the PriceOracle
   * @param newAddress The new address of the PriceOracle
   */
  event PriceOracleUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the ACL manager is updated.
   * @param oldAddress The old address of the ACLManager
   * @param newAddress The new address of the ACLManager
   */
  event ACLManagerUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the ACL admin is updated.
   * @param oldAddress The old address of the ACLAdmin
   * @param newAddress The new address of the ACLAdmin
   */
  event ACLAdminUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the price oracle sentinel is updated.
   * @param oldAddress The old address of the PriceOracleSentinel
   * @param newAddress The new address of the PriceOracleSentinel
   */
  event PriceOracleSentinelUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the pool data provider is updated.
   * @param oldAddress The old address of the PoolDataProvider
   * @param newAddress The new address of the PoolDataProvider
   */
  event PoolDataProviderUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when a new proxy is created.
   * @param id The identifier of the proxy
   * @param proxyAddress The address of the created proxy contract
   * @param implementationAddress The address of the implementation contract
   */
  event ProxyCreated(
    bytes32 indexed id,
    address indexed proxyAddress,
    address indexed implementationAddress
  );

  /**
   * @dev Emitted when a new non-proxied contract address is registered.
   * @param id The identifier of the contract
   * @param oldAddress The address of the old contract
   * @param newAddress The address of the new contract
   */
  event AddressSet(bytes32 indexed id, address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the implementation of the proxy registered with id is updated
   * @param id The identifier of the contract
   * @param proxyAddress The address of the proxy contract
   * @param oldImplementationAddress The address of the old implementation contract
   * @param newImplementationAddress The address of the new implementation contract
   */
  event AddressSetAsProxy(
    bytes32 indexed id,
    address indexed proxyAddress,
    address oldImplementationAddress,
    address indexed newImplementationAddress
  );

  /**
   * @notice Returns the id of the Aave market to which this contract points to.
   * @return The market id
   */
  function getMarketId() external view returns (string memory);

  /**
   * @notice Associates an id with a specific PoolAddressesProvider.
   * @dev This can be used to create an onchain registry of PoolAddressesProviders to
   * identify and validate multiple Aave markets.
   * @param newMarketId The market id
   */
  function setMarketId(string calldata newMarketId) external;

  /**
   * @notice Returns an address by its identifier.
   * @dev The returned address might be an EOA or a contract, potentially proxied
   * @dev It returns ZERO if there is no registered address with the given id
   * @param id The id
   * @return The address of the registered for the specified id
   */
  function getAddress(bytes32 id) external view returns (address);

  /**
   * @notice General function to update the implementation of a proxy registered with
   * certain `id`. If there is no proxy registered, it will instantiate one and
   * set as implementation the `newImplementationAddress`.
   * @dev IMPORTANT Use this function carefully, only for ids that don't have an explicit
   * setter function, in order to avoid unexpected consequences
   * @param id The id
   * @param newImplementationAddress The address of the new implementation
   */
  function setAddressAsProxy(bytes32 id, address newImplementationAddress) external;

  /**
   * @notice Sets an address for an id replacing the address saved in the addresses map.
   * @dev IMPORTANT Use this function carefully, as it will do a hard replacement
   * @param id The id
   * @param newAddress The address to set
   */
  function setAddress(bytes32 id, address newAddress) external;

  /**
   * @notice Returns the address of the Pool proxy.
   * @return The Pool proxy address
   */
  function getPool() external view returns (address);

  /**
   * @notice Updates the implementation of the Pool, or creates a proxy
   * setting the new `pool` implementation when the function is called for the first time.
   * @param newPoolImpl The new Pool implementation
   */
  function setPoolImpl(address newPoolImpl) external;

  /**
   * @notice Returns the address of the PoolConfigurator proxy.
   * @return The PoolConfigurator proxy address
   */
  function getPoolConfigurator() external view returns (address);

  /**
   * @notice Updates the implementation of the PoolConfigurator, or creates a proxy
   * setting the new `PoolConfigurator` implementation when the function is called for the first time.
   * @param newPoolConfiguratorImpl The new PoolConfigurator implementation
   */
  function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external;

  /**
   * @notice Returns the address of the price oracle.
   * @return The address of the PriceOracle
   */
  function getPriceOracle() external view returns (address);

  /**
   * @notice Updates the address of the price oracle.
   * @param newPriceOracle The address of the new PriceOracle
   */
  function setPriceOracle(address newPriceOracle) external;

  /**
   * @notice Returns the address of the ACL manager.
   * @return The address of the ACLManager
   */
  function getACLManager() external view returns (address);

  /**
   * @notice Updates the address of the ACL manager.
   * @param newAclManager The address of the new ACLManager
   */
  function setACLManager(address newAclManager) external;

  /**
   * @notice Returns the address of the ACL admin.
   * @return The address of the ACL admin
   */
  function getACLAdmin() external view returns (address);

  /**
   * @notice Updates the address of the ACL admin.
   * @param newAclAdmin The address of the new ACL admin
   */
  function setACLAdmin(address newAclAdmin) external;

  /**
   * @notice Returns the address of the price oracle sentinel.
   * @return The address of the PriceOracleSentinel
   */
  function getPriceOracleSentinel() external view returns (address);

  /**
   * @notice Updates the address of the price oracle sentinel.
   * @param newPriceOracleSentinel The address of the new PriceOracleSentinel
   */
  function setPriceOracleSentinel(address newPriceOracleSentinel) external;

  /**
   * @notice Returns the address of the data provider.
   * @return The address of the DataProvider
   */
  function getPoolDataProvider() external view returns (address);

  /**
   * @notice Updates the address of the data provider.
   * @param newDataProvider The address of the new DataProvider
   */
  function setPoolDataProvider(address newDataProvider) external;
}
"
    },
    "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/transparent/TransparentUpgradeableProxy.sol)

pragma solidity ^0.8.0;

import "../ERC1967/ERC1967Proxy.sol";

/**
 * @dev This contract implements a proxy that is upgradeable by an admin.
 *
 * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
 * clashing], which can potentially be used in an attack, this contract uses the
 * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
 * things that go hand in hand:
 *
 * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
 * that call matches one of the admin functions exposed by the proxy itself.
 * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
 * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
 * "admin cannot fallback to proxy target".
 *
 * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
 * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
 * to sudden errors when trying to call a function from the proxy implementation.
 *
 * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
 * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
 */
contract TransparentUpgradeableProxy is ERC1967Proxy {
    /**
     * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
     * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
     */
    constructor(
        address _logic,
        address admin_,
        bytes memory _data
    ) payable ERC1967Proxy(_logic, _data) {
        _changeAdmin(admin_);
    }

    /**
     * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
     */
    modifier ifAdmin() {
        if (msg.sender == _getAdmin()) {
            _;
        } else {
            _fallback();
        }
    }

    /**
     * @dev Returns the current admin.
     *
     * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
     *
     * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
     * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
     * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
     */
    function admin() external ifAdmin returns (address admin_) {
        admin_ = _getAdmin();
    }

    /**
     * @dev Returns the current implementation.
     *
     * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
     *
     * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
     * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
     * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
     */
    function implementation() external ifAdmin returns (address implementation_) {
        implementation_ = _implementation();
    }

    /**
     * @dev Changes the admin of the proxy.
     *
     * Emits an {AdminChanged} event.
     *
     * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
     */
    function changeAdmin(address newAdmin) external virtual ifAdmin {
        _changeAdmin(newAdmin);
    }

    /**
     * @dev Upgrade the implementation of the proxy.
     *
     * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
     */
    function upgradeTo(address newImplementation) external ifAdmin {
        _upgradeToAndCall(newImplementation, bytes(""), false);
    }

    /**
     * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
     * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
     * proxied contract.
     *
     * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
     */
    function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
        _upgradeToAndCall(newImplementation, data, true);
    }

    /**
     * @dev Returns the current admin.
     */
    function _admin() internal view virtual returns (address) {
        return _getAdmin();
    }

    /**
     * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
     */
    function _beforeFallback() internal virtual override {
        require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
        super._beforeFallback();
    }
}
"
    },
    "src/ATokenVault.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
// All Rights Reserved © AaveCo

pragma solidity ^0.8.10;

import {ERC4626Upgradeable} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
import {SafeERC20Upgradeable} from "@openzeppelin-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {IERC20Upgradeable} from "@openzeppelin-upgradeable/interfaces/IERC20Upgradeable.sol";
import {EIP712Upgradeable} from "@openzeppelin-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {MathUpgradeable} from "@openzeppelin-upgradeable/utils/math/MathUpgradeable.sol";
import {IncentivizedERC20} from "@aave-v3-core/protocol/tokenization/base/IncentivizedERC20.sol";
import {IPoolAddressesProvider} from "@aave-v3-core/interfaces/IPoolAddressesProvider.sol";
import {IPool} from "@aave-v3-core/interfaces/IPool.sol";
import {IAToken} from "@aave-v3-core/interfaces/IAToken.sol";
import {DataTypes as AaveDataTypes} from "@aave-v3-core/protocol/libraries/types/DataTypes.sol";
import {WadRayMath} from "@aave-v3-core/protocol/libraries/math/WadRayMath.sol";
import {IRewardsController} from "@aave-v3-periphery/rewards/interfaces/IRewardsController.sol";
import {IATokenVault} from "./interfaces/IATokenVault.sol";
import {MetaTxHelpers} from "./libraries/MetaTxHelpers.sol";
import "./libraries/Constants.sol";
import {ATokenVaultStorage} from "./ATokenVaultStorage.sol";

/**
 * @title ATokenVault
 * @author Aave Protocol
 * @notice An ERC-4626 vault for Aave V3, with support to add a fee on yield earned.
 */
contract ATokenVault is ERC4626Upgradeable, OwnableUpgradeable, EIP712Upgradeable, ATokenVaultStorage, IATokenVault {
    using SafeERC20Upgradeable for IERC20Upgradeable;
    using MathUpgradeable for uint256;

    /// @inheritdoc IATokenVault
    IPoolAddressesProvider public immutable POOL_ADDRESSES_PROVIDER;

    /// @inheritdoc IATokenVault
    IPool public immutable AAVE_POOL;

    /// @inheritdoc IATokenVault
    IAToken public immutable ATOKEN;

    /// @inheritdoc IATokenVault
    IERC20Upgradeable public immutable UNDERLYING;

    /// @inheritdoc IATokenVault
    uint16 public immutable REFERRAL_CODE;

    /**
     * @dev Constructor.
     * @param underlying The underlying ERC20 asset which can be supplied to Aave
     * @param referralCode The Aave referral code to use for deposits from this vault
     * @param poolAddressesProvider The address of the Aave v3 Pool Addresses Provider
     */
    constructor(address underlying, uint16 referralCode, IPoolAddressesProvider poolAddressesProvider) {
        _disableInitializers();
        POOL_ADDRESSES_PROVIDER = poolAddressesProvider;
        AAVE_POOL = IPool(poolAddressesProvider.getPool());
        REFERRAL_CODE = referralCode;
        UNDERLYING = IERC20Upgradeable(underlying);

        address aTokenAddress = AAVE_POOL.getReserveData(address(underlying)).aTokenAddress;
        require(aTokenAddress != address(0), "ASSET_NOT_SUPPORTED");
        ATOKEN = IAToken(aTokenAddress);
    }

    /**
     * @notice Initializes the vault, setting the initial parameters and initializing inherited contracts.
     * @dev It requires an initial non-zero deposit to prevent a frontrunning attack (in underlying tokens). Note
     * that care should be taken to provide a non-trivial amount, but this depends on the underlying asset's decimals.
     * @dev It does not initialize the OwnableUpgradeable contract to avoid setting the proxy admin as the owner.
     * @param owner The owner to set
     * @param initialFee The initial fee to set, expressed in wad, where 1e18 is 100%
     * @param shareName The name to set for this vault
     * @param shareSymbol The symbol to set for this vault
     * @param initialLockDeposit The initial amount of underlying assets to deposit
     */
    function initialize(
        address owner,
        uint256 initialFee,
        string memory shareName,
        string memory shareSymbol,
        uint256 initialLockDeposit
    ) external initializer {
        require(owner != address(0), "ZERO_ADDRESS_NOT_VALID");
        require(initialLockDeposit != 0, "ZERO_INITIAL_LOCK_DEPOSIT");
        _transferOwnership(owner);
        __ERC4626_init(UNDERLYING);
        __ERC20_init(shareName, shareSymbol);
        __EIP712_init(shareName, "1");
        _setFee(initialFee);

        UNDERLYING.safeApprove(address(AAVE_POOL), type(uint256).max);

        _handleDeposit(initialLockDeposit, address(this), msg.sender, false);
    }

    /*//////////////////////////////////////////////////////////////
                        DEPOSIT/WITHDRAWAL LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IATokenVault
    function deposit(uint256 assets, address receiver) public override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        return _handleDeposit(assets, receiver, msg.sender, false);
    }

    /// @inheritdoc IATokenVault
    function depositATokens(uint256 assets, address receiver) public override returns (uint256) {
        return _handleDeposit(assets, receiver, msg.sender, true);
    }

    /// @inheritdoc IATokenVault
    function depositWithSig(
        uint256 assets,
        address receiver,
        address depositor,
        EIP712Signature calldata sig
    ) public override returns (uint256) {
        unchecked {
            MetaTxHelpers._validateRecoveredAddress(
                MetaTxHelpers._calculateDigest(
                    keccak256(
                        abi.encode(
                            DEPOSIT_WITH_SIG_TYPEHASH,
                            assets,
                            receiver,
                            depositor,
                            _sigNonces[depositor]++,
                            sig.deadline
                        )
                    ),
                    _domainSeparatorV4()
                ),
                depositor,
                sig
            );
        }
        return _handleDeposit(assets, receiver, depositor, false);
    }

    /// @inheritdoc IATokenVault
    function depositATokensWithSig(
        uint256 assets,
        address receiver,
        address depositor,
        EIP712Signature calldata sig
    ) public override returns (uint256) {
        unchecked {
            MetaTxHelpers._validateRecoveredAddress(
                MetaTxHelpers._calculateDigest(
                    keccak256(
                        abi.encode(
                            DEPOSIT_ATOKENS_WITH_SIG_TYPEHASH,
                            assets,
                            receiver,
                            depositor,
                            _sigNonces[depositor]++,
                            sig.deadline
                        )
                    ),
                    _domainSeparatorV4()
                ),
                depositor,
                sig
            );
        }
        return _handleDeposit(assets, receiver, depositor, true);
    }

    /// @inheritdoc IATokenVault
    function mint(uint256 shares, address receiver) public override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        return _handleMint(shares, receiver, msg.sender, false);
    }

    /// @inheritdoc IATokenVault
    function mintWithATokens(uint256 shares, address receiver) public override returns (uint256) {
        return _handleMint(shares, receiver, msg.sender, true);
    }

    /// @inheritdoc IATokenVault
    function mintWithSig(
        uint256 shares,
        address receiver,
        address depositor,
        EIP712Signature calldata sig
    ) public override returns (uint256) {
        unchecked {
            MetaTxHelpers._validateRecoveredAddress(
                MetaTxHelpers._calculateDigest(
                    keccak256(
                        abi.encode(MINT_WITH_SIG_TYPEHASH, shares, receiver, depositor, _sigNonces[depositor]++, sig.deadline)
                    ),
                    _domainSeparatorV4()
                ),
                depositor,
                sig
            );
        }
        return _handleMint(shares, receiver, depositor, false);
    }

    /// @inheritdoc IATokenVault
    function mintWithATokensWithSig(
        uint256 shares,
        address receiver,
        address depositor,
        EIP712Signature calldata sig
    ) public override returns (uint256) {
        unchecked {
            MetaTxHelpers._validateRecoveredAddress(
                MetaTxHelpers._calculateDigest(
                    keccak256(
                        abi.encode(
                            MINT_WITH_ATOKENS_WITH_SIG_TYPEHASH,
                            shares,
                            receiver,
                            depositor,
                            _sigNonces[depositor]++,
                            sig.deadline
                        )
                    ),
                    _domainSeparatorV4()
                ),
                depositor,
                sig
            );
        }
        return _handleMint(shares, receiver, depositor, true);
    }

    /// @inheritdoc IATokenVault
    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) public override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        return _handleWithdraw(assets, receiver, owner, msg.sender, false);
    }

    /// @inheritdoc IATokenVault
    function withdrawATokens(uint256 assets, address receiver, address owner) public override returns (uint256) {
        return _handleWithdraw(assets, receiver, owner, msg.sender, true);
    }

    /// @inheritdoc IATokenVault
    function withdrawWithSig(
        uint256 assets,
        address receiver,
        address owner,
        EIP712Signature calldata sig
    ) public override returns (uint256) {
        unchecked {
            MetaTxHelpers._validateRecoveredAddress(
                MetaTxHelpers._calculateDigest(
                    keccak256(
                        abi.encode(WITHDRAW_WITH_SIG_TYPEHASH, assets, receiver, owner, _sigNonces[owner]++, sig.deadline)
                    ),
                    _domainSeparatorV4()
                ),
                owner,
                sig
            );
        }
        return _handleWithdraw(assets, receiver, owner, owner, false);
    }

    /// @inheritdoc IATokenVault
    function withdrawATokensWithSig(
        uint256 assets,
        address receiver,
        address owner,
        EIP712Signature calldata sig
    ) public override returns (uint256) {
        unchecked {
            MetaTxHelpers._validateRecoveredAddress(
                MetaTxHelpers._calculateDigest(
                    keccak256(
                        abi.encode(
                            WITHDRAW_ATOKENS_WITH_SIG_TYPEHASH,
                            assets,
                            receiver,
                            owner,
                            _sigNonces[owner]++,
                            sig.deadline
                        )
                    ),
                    _domainSeparatorV4()
                ),
                owner,
                sig
            );
        }
        return _handleWithdraw(assets, receiver, owner, owner, true);
    }

    /// @inheritdoc IATokenVault
    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) public override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        return _handleRedeem(shares, receiver, owner, msg.sender, false);
    }

    /// @inheritdoc IATokenVault
    function redeemAsATokens(uint256 shares, address receiver, address owner) public override returns (uint256) {
        return _handleRedeem(shares, receiver, owner, msg.sender, true);
    }

    /// @inheritdoc IATokenVault
    function redeemWithSig(
        uint256 shares,
        address receiver,
        address owner,
        EIP712Signature calldata sig
    ) public override returns (uint256) {
        unchecked {
            MetaTxHelpers._validateRecoveredAddress(
                MetaTxHelpers._calculateDigest(
                    keccak256(abi.encode(REDEEM_WITH_SIG_TYPEHASH, shares, receiver, owner, _sigNonces[owner]++, sig.deadline)),
                    _domainSeparatorV4()
                ),
                owner,
                sig
            );
        }
        return _handleRedeem(shares, receiver, owner, owner, false);
    }

    /// @inheritdoc IATokenVault
    function redeemWithATokensWithSig(
        uint256 shares,
        address receiver,
        address owner,
        EIP712Signature calldata sig
    ) public override returns (uint256) {
        unchecked {
            MetaTxHelpers._validateRecoveredAddress(
                MetaTxHelpers._calculateDigest(
                    keccak256(
                        abi.encode(
                            REDEEM_WITH_ATOKENS_WITH_SIG_TYPEHASH,
                            shares,
                            receiver,
                            owner,
                            _sigNonces[owner]++,
                            sig.deadline
                        )
                    ),
                    _domainSeparatorV4()
                ),
                owner,
                sig
            );
        }
        return _handleRedeem(shares, receiver, owner, owner, true);
    }

    /// @inheritdoc IATokenVault
    function maxDeposit(address) public view override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        return _maxAssetsSuppliableToAave();
    }

    /// @inheritdoc IATokenVault
    function maxMint(address) public view override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        return _convertToShares(_maxAssetsSuppliableToAave(), MathUpgradeable.Rounding.Down);
    }

    /// @inheritdoc IATokenVault
    function maxWithdraw(address owner) public view override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        uint256 maxWithdrawable = _maxAssetsWithdrawableFromAave();
        return
            maxWithdrawable == 0 ? 0 : maxWithdrawable.min(_convertToAssets(balanceOf(owner), MathUpgradeable.Rounding.Down));
    }

    /// @inheritdoc IATokenVault
    function maxRedeem(address owner) public view override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        uint256 maxWithdrawable = _maxAssetsWithdrawableFromAave();
        return
            maxWithdrawable == 0 ? 0 : _convertToShares(maxWithdrawable, MathUpgradeable.Rounding.Down).min(balanceOf(owner));
    }

    /// @inheritdoc IATokenVault
    function previewDeposit(uint256 assets) public view override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        return _convertToShares(_maxAssetsSuppliableToAave().min(assets), MathUpgradeable.Rounding.Down);
    }

    /// @inheritdoc IATokenVault
    function previewMint(uint256 shares) public view override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        return _convertToAssets(shares, MathUpgradeable.Rounding.Up).min(_maxAssetsSuppliableToAave());
    }

    /// @inheritdoc IATokenVault
    function previewWithdraw(uint256 assets) public view override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        uint256 maxWithdrawable = _maxAssetsWithdrawableFromAave();
        return maxWithdrawable == 0 ? 0 : _convertToShares(maxWithdrawable.min(assets), MathUpgradeable.Rounding.Up);
    }

    /// @inheritdoc IATokenVault
    function previewRedeem(uint256 shares) public view override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        uint256 maxWithdrawable = _maxAssetsWithdrawableFromAave();
        return maxWithdrawable == 0 ? 0 : _convertToAssets(shares, MathUpgradeable.Rounding.Down).min(maxWithdrawable);
    }

    /// @inheritdoc IATokenVault
    function domainSeparator() public view override returns (bytes32) {
        return _domainSeparatorV4();
    }

    /*//////////////////////////////////////////////////////////////
                          ONLY OWNER FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IATokenVault
    function setFee(uint256 newFee) public override onlyOwner {
        _accrueYield();
        _setFee(newFee);
    }

    /// @inheritdoc IATokenVault
    function withdrawFees(address to, uint256 amount) public override onlyOwner {
        _accrueYield();
        require(amount <= _s.accumulatedFees, "INSUFFICIENT_FEES"); // will underflow below anyway, error msg for clarity

        _s.accumulatedFees -= uint128(amount);

        ATOKEN.transfer(to, amount);

        _s.lastVaultBalance = uint128(ATOKEN.balanceOf(address(this)));

        emit FeesWithdrawn(to, amount, _s.lastVaultBalance, _s.accumulatedFees);
    }

    /// @inheritdoc IATokenVault
    function claimRewards(address to) public override onlyOwner {
        require(to != address(0), "CANNOT_CLAIM_TO_ZERO_ADDRESS");

        address[] memory assets = new address[](1);
        assets[0] = address(ATOKEN);
        (address[] memory rewardsList, uint256[] memory claimedAmounts) = IRewardsController(
            address(IncentivizedERC20(address(ATOKEN)).getIncentivesController())
        ).claimAllRewards(assets, to);

        emit RewardsClaimed(to, rewardsList, claimedAmounts);
    }

    /// @inheritdoc IATokenVault
    function emergencyRescue(address token, address to, uint256 amount) public override onlyOwner {
        require(token != address(ATOKEN), "CANNOT_RESCUE_ATOKEN");

        IERC20Upgradeable(token).safeTransfer(to, amount);

        emit EmergencyRescue(token, to, amount);
    }

    /*//////////////////////////////////////////////////////////////
                          VIEW FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IATokenVault
    function totalAssets() public view override(ERC4626Upgradeable, IATokenVault) returns (uint256) {
        // Report only the total assets net of fees, for vault share logic
        return ATOKEN.balanceOf(address(this)) - getClaimableFees();
    }

    /// @inheritdoc IATokenVault
    function getClaimableFees() public view override returns (uint256) {
        uint256 newVaultBalance = ATOKEN.balanceOf(address(this));

        // Skip computation if there is no yield
        if (newVaultBalance <= _s.lastVaultBalance) {
            return _s.accumulatedFees;
        }

        uint256 newYield = newVaultBalance - _s.lastVaultBalance;
        uint256 newFees = newYield.mulDiv(_s.fee, SCALE, MathUpgradeable.Rounding.Down);

        return _s.accumulatedFees + newFees;
    }

    /// @inheritdoc IATokenVault
    function getSigNonce(address signer) public view override returns (uint256) {
        return _sigNonces[signer];
    }

    /// @inheritdoc IATokenVault
    function getLastVaultBalance() public view override returns (uint256) {
        return _s.lastVaultBalance;
    }

    /// @inheritdoc IATokenVault
    function getFee() public view override returns (uint256) {
        return _s.fee;
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    function _setFee(uint256 newFee) internal {
        require(newFee <= SCALE, "FEE_TOO_HIGH");

        uint256 oldFee = _s.fee;
        _s.fee = uint64(newFee);

        emit FeeUpdated(oldFee, newFee);
    }

    function _accrueYield() internal {
        uint256 newVaultBalance = ATOKEN.balanceOf(address(this));

        // Skip computation if there is no yield
        if (newVaultBalance <= _s.lastVaultBalance) {
            return;
        }

        uint256 newYield = newVaultBalance - _s.lastVaultBalance;
        uint256 newFeesEarned = newYield.mulDiv(_s.fee, SCALE, MathUpgradeable.Rounding.Down);

        _s.accumulatedFees += uint128(newFeesEarned);
        _s.lastVaultBalance = uint128(newVaultBalance);

        emit YieldAccrued(newYield, newFeesEarned, newVaultBalance);
    }

    function _handleDeposit(uint256 assets, address receiver, address depositor, bool asAToken) internal returns (uint256) {
        if (!asAToken) require(assets <= maxDeposit(receiver), "DEPOSIT_EXCEEDS_MAX");
        _accrueYield();
        uint256 shares = super.previewDeposit(assets);
        require(shares != 0, "ZERO_SHARES"); // Check for rounding error since we round down in previewDeposit.
        _baseDeposit(_convertToAssets(shares, MathUpgradeable.Rounding.Up), shares, depositor, receiver, asAToken);
        return shares;
    }

    function _handleMint(uint256 shares, address receiver, address depositor, bool asAToken) internal returns (uint256) {
        if (!asAToken) require(shares <= maxMint(receiver), "MINT_EXCEEDS_MAX");
        _accrueYield();
        uint256 assets = super.previewMint(shares); // No need to check for rounding error, previewMint rounds up.
        _baseDeposit(assets, shares, depositor, receiver, asAToken);
        return assets;
    }

    function _handleWithdraw(
        uint256 assets,
        address receiver,
        address owner,
        address allowanceTarget,
        bool asAToken
    ) internal returns (uint256) {
        _accrueYield();
        require(assets <= maxWithdraw(owner), "WITHDRAW_EXCEEDS_MAX");
        uint256 shares = super.previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
        _baseWithdraw(assets, shares, owner, receiver, allowanceTarget, asAToken);
        return shares;
    }

    function _handleRedeem(
        uint256 shares,
        address receiver,
        address owner,
        address allowanceTarget,
        bool asAToken
    ) internal returns (uint256) {
        _accrueYield();
        require(shares <= maxRedeem(owner), "REDEEM_EXCEEDS_MAX");
        uint256 assets = super.previewRedeem(shares);
        require(assets != 0, "ZERO_ASSETS"); // Check for rounding error since we round down in previewRedeem.
        _baseWithdraw(assets, shares, owner, receiver, allowanceTarget, asAToken);
        return assets;
    }

    function _maxAssetsSuppliableToAave() internal view returns (uint256) {
        // returns 0 if reserve is not active, frozen, or paused
        // returns max uint256 value if supply cap is 0 (not capped)
        // returns supply cap - current amount supplied as max suppliable if there is a supply cap for this reserve

        AaveDataTypes.ReserveData memory reserveData = AAVE_POOL.getReserveData(address(UNDERLYING));

        uint256 reserveConfigMap = reserveData.configuration.data;
        uint256 supplyCap = (reserveConfigMap & ~AAVE_SUPPLY_CAP_MASK) >> AAVE_SUPPLY_CAP_BIT_POSITION;

        if (
            (reserveConfigMap & ~AAVE_ACTIVE_MASK == 0) ||
            (reserveConfigMap & ~AAVE_FROZEN_MASK != 0) ||
            (reserveConfigMap & ~AAVE_PAUSED_MASK != 0)
        ) {
            return 0;
        } else if (supplyCap == 0) {
            return type(uint256).max;
        } else {
            // Reserve's supply cap - current amount supplied
            // See similar logic in Aave v3 ValidationLogic library, in the validateSupply function
            // https://github.com/aave/aave-v3-core/blob/a00f28e3ad7c0e4a369d8e06e0ac9fd0acabcab7/contracts/protocol/libraries/logic/ValidationLogic.sol#L71-L78
            uint256 currentSupply = WadRayMath.rayMul(
                (ATOKEN.scaledTotalSupply() + uint256(reserveData.accruedToTreasury)),
                reserveData.liquidityIndex
            );
            uint256 supplyCapWithDecimals = supplyCap * 10 ** decimals();
            return supplyCapWithDecimals > currentSupply ? supplyCapWithDecimals - currentSupply : 0;
        }
    }

    function _maxAssetsWithdrawableFromAave() internal view returns (uint256) {
        // returns 0 if reserve is not active, or paused
        // otherwise, returns available liquidity

        AaveDataTypes.ReserveData memory reserveData = AAVE_POOL.getReserveData(address(UNDERLYING));

        uint256 reserveConfigMap = reserveData.configuration.data;

        if ((reserveConfigMap & ~AAVE_ACTIVE_MASK == 0) || (reserveConfigMap & ~AAVE_PAUSED_MASK != 0)) {
            return 0;
        } else {
            return UNDERLYING.balanceOf(address(ATOKEN));
        }
    }

    function _baseDeposit(uint256 assets, uint256 shares, address depositor, address receiver, bool asAToken) private {
        // Need to transfer before minting or ERC777s could reenter.
        if (asAToken) {
            ATOKEN.transferFrom(depositor, address(this), assets);
        } else {
            UNDERLYING.safeTransferFrom(depositor, address(this), assets);
            AAVE_POOL.supply(address(UNDERLYING), assets, address(this), REFERRAL_CODE);
        }
        _s.lastVaultBalance = uint128(ATOKEN.balanceOf(address(this)));

        _mint(receiver, shares);

        emit Deposit(depositor, receiver, assets, shares);
    }

    function _baseWithdraw(
        uint256 assets,
        uint256 shares,
        address owner,
        address receiver,
        address allowanceTarget,
        bool asAToken
    ) private {
        if (allowanceTarget != owner) {
            _spendAllowance(owner, allowanceTarget, shares);
        }

        _burn(owner, shares);

        // Withdraw assets from Aave v3 and send to receiver
        if (asAToken) {
            ATOKEN.transfer(receiver, assets);
        } else {
            AAVE_POOL.withdraw(address(UNDERLYING), assets, receiver);
        }
        _s.lastVaultBalance = uint128(ATOKEN.balanceOf(address(this)));

        emit Withdraw(allowanceTarget, receiver, owner, assets, shares);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using 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));
        }
    }

    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @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");
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol)

pragma solidity ^0.8.0;

import "./TransparentUpgradeableProxy.sol";
import "../../access/Ownable.sol";

/**
 * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
 * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
 */
contract ProxyAdmin is Ownable {
    /**
     * @dev Returns the current implementation of `proxy`.
     *
     * Requirements:
     *
     * - This contract must be the admin of `proxy`.
     */
    function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
        // We need to manually run the static call since the getter cannot be flagged as view
        // bytes4(keccak256("implementation()")) == 0x5c60da1b
        (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
        require(success);
        return abi.decode(returndata, (address));
    }

    /**
     * @dev Returns the current admin of `proxy`.
     *
     * Requirements:
     *
     * - This contract must be the admin of `proxy`.
     */
    function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
        // We need to manually run the static call since the getter cannot be flagged as view
        // bytes4(keccak256("admin()")) == 0xf851a440
        (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
        require(success);
        return abi.decode(returndata, (address));
    }

    /**
     * @dev Changes the admin of `proxy` to `newAdmin`.
     *
     * Requirements:
     *
     * - This contract must be the current admin of `proxy`.
     */
    function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
        proxy.changeAdmin(newAdmin);
    }

    /**
     * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
     *
     * Requirements:
     *
     * - This contract must be the admin of `proxy`.
     */
    function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
        proxy.upgradeTo(implementation);
    }

    /**
     * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
     * {TransparentUpgradeableProxy-upgradeToAndCall}.
     *
     * Requirements:
     *
     * - This contract must be the admin of `proxy`.
     */
    function upgradeAndCall(
        TransparentUpgradeableProxy proxy,
        address implementation,
        bytes memory data
    ) public payable virtual onlyOwner {
        proxy.upgradeToAndCall{value: msg.value}(implementation, data);
    }
}
"
    },
    "src/ATokenVaultRevenueSplitterOwner.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
// All Rights Reserved © AaveCo

pragma solidity ^0.8.10;

import {Ownable} from "@openzeppelin/access/Ownable.sol";
import {IATokenVault} from "./interfaces/IATokenVault.sol";
import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";

/**
 * @title ATokenVaultRevenueSplitterOwner
 * @author Aave Labs
 * @notice ATokenVault owner with revenue split capabilities.
 */
contract ATokenVaultRevenueSplitterOwner is Ownable {
    using SafeERC20 for IERC20;

    /**
     * @dev Emitted at construction time for each recipient set.
     * @param recipient The address of the recipient set.
     * @param shareInBps The recipient's share of the revenue in basis points.
     */
    event RecipientSet(address indexed recipient, uint16 shareInBps);

    /**
     * @dev Emitted when revenue is split for each recipient and asset.
     * @param recipient The address of the recipient receiving the revenue.
     * @param asset The asset being split.
     * @param amount The amount of revenue sent to the recipient in the split asset.
     */
    event RevenueSplitTransferred(address indexed recipient, address indexed asset, uint256 amount);

    /**
     * @dev The sum of all recipients' shares in basis points, represents 100.00%. Each basis point is 0.01%.
     */
    uint256 public constant TOTAL_SHARE_IN_BPS = 10_000;

    /**
     * @dev The aToken Vault to own, whose revenue is split.
     */
    IATokenVault public immutable VAULT;
    
    /**
     * @dev A struct to represent a recipient and its share of the revenue in basis points.
     * @param addr The address of the recipient.
     * @param shareInBps The recipient's share of the revenue in basis points.
     */
    struct Recipient {
        address addr;
        uint16 shareInBps;
    }

    /**
     * @dev The recipients set for this revenue splitter. Set at construction time only, cannot be modified afterwards.
     */
    Recipient[] internal _recipients;

    /**
     * @dev Total historical amount held for a given asset in this contract.
     */
    mapping(address => uint256) internal _previousAccumulatedBalance;

    /**
     * @dev Amount already transferred for a given asset to a given recipient.
     */
    mapping(address => mapping(address => uint256)) internal _amountAlreadyTransferred;

    /**
     * @dev Constructor.
     * @param vault The address of the aToken Vault to own, whose revenue is split.
     * @param owner The address owning this contract, the effective owner of the vault.
     * @param recipients The recipients to set for the revenue split. Duplicates are not allowed. The recipients
     * configuration cannot be modified afterwards.
     */
    constructor(address vault, address owner, Recipient[] memory recipients) {
        VAULT = IATokenVault(vault);
        require(recipients.length > 0, "MISSING_RECIPIENTS");
        _setRecipients(recipients);
        _transferOwnership(owner);
    }

    /**
     * @dev Rejects native currency transfers.
     */
    receive() external payable {
        revert("NATIVE_CURRENCY_NOT_SUPPORTED");
    }

    /**
     * @dev Transfers the ownership of the aToken vault to a new owner. Claims all fees and rewards prior to transfer,
     * to secure already accrued fees and rewards for the configured split recipients.
     * @dev Only callable by the owner of this contract.
     * @dev DO NOT confuse with `transferOwnership` which transfers the ownership of this contract instead.
     * @param newVaultOwner The address of the new aToken vault owner.
     */
    function transferVaultOwnership(address newVaultOwner) public onlyOwner {
        _claimRewards();
        _withdrawFees();
        Ownable(address(VAULT)).transferOwnership(newVaultOwner);
    }

    /**
     * @dev Withdraws all vault fees to this contract, so they can be split among the configured recipients.
     */
    function withdrawFees() public {
        _withdrawFees();
    }

    /**
     * @dev Claims all vault rewards to this contract, so they can be split among the configured recipients.
     */
    function claimRewards() external {
        _claimRewards();
    }

    /**
     * @dev Splits the revenue from the given assets among the configured recipients. Assets must follow the ERC-20
     * standard and be held by this contract.
     * @param assets The assets to split the revenue from.
     */
    function splitRevenue(address[] calldata assets) public {
        Recipient[] memory recipients = _recipients;
        for (uint256 i = 0; i < assets.length; i++) {
            uint256 assetBalance = IERC20(assets[i]).balanceOf(address(this));
            require(assetBalance > 0, "ASSET_NOT_HELD_BY_SPLITTER");
            // Decrease balance by one unit to ensure aToken transfers will not fail due to scaled balance rounding.
            assetBalance--;
            uint256 accumulatedAssetBalance = _previousAccumulatedBalance[assets[i]] + assetBalance;
            _previousAccumulatedBalance[assets[i]] = accumulatedAssetBalance;
            uint256 undistributedAmount = assetBalance;
            for (uint256 j = 0; j < recipients.length; j++) {
                /**
                 * The `assetBalance` adjustment previously done by decrementing one unit will leave that unit of the
                 * asset undistributed in this contract's balance.
                 * However, due to floor-rounding in integer division, the sum of the amounts transferred may be less
                 * than the intended total amount to split, leaving a few more units of the asset undistributed.
                 * These units (also known as 'dust') may be distributed in the next `splitRevenue` call.
                 */
                uint256 amountForRecipient = accumulatedAssetBalance * recipients[j].shareInBps / TOTAL_SHARE_IN_BPS
                    - _amountAlreadyTransferred[assets[i]][recipients[j].addr];
                if (amountForRecipient > 0) {
                    _amountAlreadyTransferred[assets[i]][recipients[j].addr] += amountForRecipient;
                    IERC20(assets[i]).safeTransfer(recipients[j].addr, amountForRecipient);
                    undistributedAmount -= amountForRecipient;
                }
                emit RevenueSplitTransferred(recipients[j].addr, assets[i], amountForRecipient);
            }
            if (undistributedAmount > 0) {
                _previousAccumulatedBalance[assets[i]] -= undistributedAmount;
            }
        }
    }

    /**
     * @dev Rescues assets that may have accidentally been transferred to the vault.
     * @dev Only callable by the owner of this contract.
     * @dev The asset to rescue cannot be the vault's aToken.
     * @dev Fees cannot be "rescued" as they are accrued in the vault's aToken. Rewards cannot be "rescued" as they are 
     * not held by the vault contract. Thus, already accrued fees and rewards cannot be taken from split recipients.
     * @param asset The asset to rescue from the vault.
     * @param to The address to send the rescued assets to.
     * @param amount The amount of assets to rescue from the vault.
     */
    function emergencyRescue(address asset, address to, uint256 amount) public onlyOwner {
        VAULT.emergencyRescue(asset, to, amount);
    }

    /**
     * @dev Sets the fee for the vault.
     * @dev Only callable by the owner of this contract.
     * @param newFee The new fee for the vault.
     */
    function setFee(uint256 newFee) public onlyOwner {
        VAULT.setFee(newFee);
    }

    /**
     * @dev Getter for the revenue split configured recipients.
     * @return The configured recipients with their corresponding share in basis points.
     */
    function getRecipients() public view returns (Recipient[] memory) {
        return _recipients;
    }

    function _claimRewards() internal {
        VAULT.claimRewards(address(this));
    }

    function _withdrawFees() internal {
        uint256 feesToWithdraw = VAULT.getClaimableFees();
        if (feesToWithdraw > 0) {
            VAULT.withdrawFees(address(this), feesToWithdraw);
        }
    }

    /**
     * @dev Sum of shares must represent 100.00% in basis points.
     */
    function _setRecipients(Recipient[] memory recipients) internal {
        uint256 accumulatedShareInBps = 0;
        for (uint256 i = 0; i < recipients.length; i++) {
            require(recipients[i].addr != address(0), "RECIPIENT_CANNOT_BE_ZERO_ADDRESS");
            require(recipients[i].shareInBps > 0, "BPS_SHARE_CANNOT_BE_ZERO");
            accumulatedShareInBps += recipients[i].share

Tags:
ERC20, Multisig, Mintable, Burnable, Swap, Liquidity, Staking, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x218fcf793fa1e4fda220fb7e55adf9d8fe0b8c96|verified:true|block:23462636|tx:0x4a3268badb84d08210ba4d60ac3346b0e5736f1d6cc1aafe9f4dc1f822dc02de|first_check:1759076986

Submitted on: 2025-09-28 18:29:47

Comments

Log in to comment.

No comments yet.