RebalancerModule

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": {
    "@openzeppelin/contracts-upgradeable/interfaces/IERC20Upgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol)

pragma solidity ^0.8.0;

import "../token/ERC20/IERC20Upgradeable.sol";
"
    },
    "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20PermitUpgradeable {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
"
    },
    "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20Upgradeable {
    /**
     * @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);
}
"
    },
    "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";
import "../extensions/draft-IERC20PermitUpgradeable.sol";
import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable {
    using AddressUpgradeable for address;

    function safeTransfer(
        IERC20Upgradeable token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20Upgradeable 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(
        IERC20Upgradeable 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(
        IERC20Upgradeable 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(
        IERC20Upgradeable 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(
        IERC20PermitUpgradeable 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(IERC20Upgradeable 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");
        }
    }
}
"
    },
    "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": {
      "content": "// 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 AddressUpgradeable {
    /**
     * @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://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");

        (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 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);
        }
    }
}
"
    },
    "contracts/infinite-proxy/interfaces/IProxy.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IProxy {
    function setAdmin(address newAdmin_) external;

    function setDummyImplementation(address newDummyImplementation_) external;

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

    function removeImplementation(address implementation_) external;

    function getAdmin() external view returns (address);

    function getDummyImplementation() external view returns (address);

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

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

    function readFromStorage(
        bytes32 slot_
    ) external view returns (uint256 result_);
}
"
    },
    "contracts/vault/common/interfaces/IDSA.sol": {
      "content": "//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IDSA {
    function cast(
        string[] calldata _targetNames,
        bytes[] calldata _datas,
        address _origin
    ) external payable returns (bytes32);
}
"
    },
    "contracts/vault/common/interfaces/IToken.sol": {
      "content": "//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IToken {
    function approve(address, uint256) external;

    function transfer(address, uint) external;

    function transferFrom(address, address, uint) external;

    function deposit() external payable;

    function withdraw(uint) external;

    function balanceOf(address) external view returns (uint);

    function decimals() external view returns (uint);

    function totalSupply() external view returns (uint);

    function allowance(
        address owner,
        address spender
    ) external view returns (uint256);
}
"
    },
    "contracts/vault/common/interfaces/IVaultV3.sol": {
      "content": "//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IVaultV3 {
    function readFromStorage(bytes32 slot_) external view returns (uint256 result_);
    function getWithdrawFee(uint256 amount_) external view returns (uint256);
    function getProtocolRatio(uint8 protocolId_) external view returns (uint256 ratio_);
    function getNetAssets() external view returns (uint256 totalAssets_, uint256 totalDebt_, uint256 netAssets_, uint256 aggregatedRatio_);
    function getTokenExchangeRate(address tokenAddress_) external view returns (uint256 exchangeRate_);
}
"
    },
    "contracts/vault/common/variables/constants.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

contract Constants {
    address internal constant _TEAM_MULTISIG = 0x4F6F977aCDD1177DCD81aB83074855EcB9C2D49e;
    address internal constant _INSTA_INDEX_ADDRESS = 0x2971AdFa57b20E5a416aE5a708A8655A9c74f723;
    address internal constant _USDT_ADDRESS = 0xdAC17F958D2ee523a2206206994597C13D831ec7; // 6 decimals
}

"
    },
    "contracts/vault/common/variables/primaryHelpers.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {Constants} from "./constants.sol";
import {StorageVariables} from "./storageVariables.sol";
import {IProxy} from "../../../infinite-proxy/interfaces/IProxy.sol";
import {Structs} from "./structs.sol";
import {IVaultV3} from "../interfaces/IVaultV3.sol";
import {IToken} from "../interfaces/IToken.sol";

contract PrimaryHelpers is Constants, StorageVariables {
    using Structs for Structs.AuthTypes;

    /***********************************|
    |              ERRORS               |
    |__________________________________*/
    error Helpers__UnsupportedProtocolId();
    error Helpers__NotRebalancer();
    error Helpers__NotPrimaryRebalancer();
    error Helpers__Reentrant();
    error Helpers__NotAuth();
    error Helpers__InvalidAuthType();
    error Helpers__NotEnoughSwapLimit();

    function _auth(
        Structs.AuthTypes authType_,
        address account_
    ) internal view {
        address admin_ = IProxy(address(this)).getAdmin();
        if (authType_ == Structs.AuthTypes.Owner) {
            if (admin_ != account_) {
                revert Helpers__NotAuth();
            }
        } else if (authType_ == Structs.AuthTypes.SecondaryAuth) {
            if (
                secondaryAuth != account_ &&
                admin_ != account_
            ) {
                revert Helpers__NotAuth();
            }
        } else if (authType_ == Structs.AuthTypes.PrimaryRebalancer) {
            if (!isPrimaryRebalancer[account_] && admin_ != account_) {
                revert Helpers__NotAuth();
            }
        } else if (authType_ == Structs.AuthTypes.Rebalancer) {
            if (
                !isSecondaryRebalancer[account_] &&
                !isPrimaryRebalancer[account_] &&
                admin_ != account_
            ) {
                revert Helpers__NotAuth();
            }
        } else {
            revert Helpers__InvalidAuthType();
        }
    }

    /***********************************|
    |              MODIFIERS            |
    |__________________________________*/
    /// @notice reverts if msg.sender is not auth.
    modifier onlyAuth() {
        _auth(Structs.AuthTypes.Owner, msg.sender);
        _;
    }

    /// @notice reverts if msg.sender is not secondaryAuth or auth
    modifier onlySecondaryAuth() {
        _auth(Structs.AuthTypes.SecondaryAuth, msg.sender);
        _;
    }

    /// @notice reverts if msg.sender is not rebalancer or auth
    modifier onlyRebalancer() {
        _auth(Structs.AuthTypes.Rebalancer, msg.sender);
        _;
    }

    /// @notice reverts if msg.sender is not primaryRebalancer or auth
    modifier onlyPrimaryRebalancer() {
        _auth(Structs.AuthTypes.PrimaryRebalancer, msg.sender);
        _;
    }

    /**
     * @dev reentrancy gaurd.
     */
    modifier nonReentrant() {
        if (_status == 2) revert Helpers__Reentrant();
        _status = 2;
        _;
        _status = 1;
    }

    /// @notice Implements a method to read uint256 data from storage at a bytes32 storage slot key.
    function readFromStorage(
        bytes32 slot_
    ) public view returns (uint256 result_) {
        assembly {
            result_ := sload(slot_) // read value from the storage slot
        }
    }

    function _getAmountInUsd(
        address tokenAddress_,
        uint256 amount_,
        uint256 exchangeRate_
    ) internal view returns (uint256 amountInUsd_) {
        uint256 tokenDecimals_ = IToken(tokenAddress_).decimals();
        amountInUsd_ =
            (amount_ * exchangeRate_) /
            10 ** (2 * tokenDecimals_ - 6);
    }

    /// @notice Checks the available swap limit.
    /// @return availableSwapLimit_ The available swap limit.
    function checkAvailableSwapLimit()
        public
        view
        returns (uint256 availableSwapLimit_)
    {
        uint256 timeElapsed_ = block.timestamp - lastSwapTimestamp;
        availableSwapLimit_ = availableSwapLimit;

        /// @dev If time has elapsed, calculate the refill.
        if (timeElapsed_ > 0) {
            uint256 refill_ = (timeElapsed_ * maxDailySwapLimit) /
                (24 * 60 * 60);

            availableSwapLimit_ += refill_;

            availableSwapLimit_ = availableSwapLimit_ > maxDailySwapLimit
                ? maxDailySwapLimit
                : availableSwapLimit_;
        }
    }

    function _handleSwapLimitCheck(uint256 amount_) internal {
        availableSwapLimit = checkAvailableSwapLimit();

        if (availableSwapLimit < amount_) {
            revert Helpers__NotEnoughSwapLimit();
        }

        availableSwapLimit -= amount_;
        lastSwapTimestamp = block.timestamp;
    }
}
"
    },
    "contracts/vault/common/variables/storageVariables.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {IDSA} from "../../common/interfaces/IDSA.sol";
import {Structs} from "../../common/variables/structs.sol";

contract StorageVariables {
    using Structs for Structs.FluidVaultDetails;
    /****************************************************************************|
    |   @notice Protocol IDs                                                     |
    |   // AAVE-V3 : 1  (SUSDe, USDe, USDC, USDT, GHO, USDS)                     |
    |   // FLUID-WSTUSR-USDC : 2  (wstUSR, USDC)                                 |
    |   // FLUID-WSTUSR-USDT : 3  (wstUSR, USDT)                                 |
    |   // FLUID-WSTUSR-GHO : 4  (wstUSR, GHO)                                   |
    |   // FLUID-SUSDE-USDC : 5  (SUSDe, USDC)                                   |
    |   // FLUID-SUSDE-USDT : 6  (SUSDe, USDT)                                   |
    |   // FLUID-SUSDE-GHO : 7  (SUSDe, GHO)                                     |        
    |   // FLUID-syrupUSDC-USDC : 8  (syrupUSDC, USDC)                           |
    |   // FLUID-syrupUSDC-USDT : 9  (syrupUSDC, USDT)                           |
    |   // FLUID-syrupUSDC-GHO : 10  (syrupUSDC, GHO)                            |
    |___________________________________________________________________________*/

    /***********************************|
    |           STATE VARIABLES         |
    |__________________________________*/
    // 1: open
    // 2: closed
    uint8 internal _status;

    IDSA public vaultDSA;

    /// @notice Secondary auth that only has the power to reduce max risk ratio.
    address public secondaryAuth;

    /// @notice Current exchange price.
    uint256 public exchangePrice;

    /// @notice Last timestamp the exchange price was updated
    /// @dev This is used to calculate the rate of the vault
    uint256 public lastExchangePriceUpdatedAt;

    /// @notice Mapping to store allowed primary rebalancers
    /// @dev Primary rebalancers are the ones that can perform swap related actions
    /// Modifiable by auth
    mapping(address => bool) public isPrimaryRebalancer;

    /// @notice Mapping to store allowed secondary rebalancers
    /// @dev Secondary rebalancers are the ones that can perform all rebalancer actions except swap related actions
    /// Modifiable by auth
    mapping(address => bool) public isSecondaryRebalancer;

    // Mapping of protocol id => max risk ratio, scaled to use basis points. i.e. 1e4 = 100%, 1e2 = 1%
    // 1: AAVE-V3
    // 2: FLUID-WSTUSR-USDC
    // 3: FLUID-WSTUSR-USDT
    // 4: FLUID-WSTUSR-GHO
    // 5: FLUID-SUSDE-USDC
    // 6: FLUID-SUSDE-USDT
    // 7: FLUID-SUSDE-GHO
    mapping(uint8 => uint256) public maxRiskRatio;

    // Max aggregated risk ratio of the vault that can be reached, scaled to use basis points. i.e. 1e4 = 100%, 1e2 = 1%
    // i.e. 1e4 = 100%, 1e2 = 1%
    uint256 public aggrMaxVaultRatio;

    /// @notice withdraw fee is either amount in percentage or absolute minimum.
    /// @dev This var defines the percentage in basis points. i.e. 1e4 = 100%, 1e2 = 1%
    /// Modifiable by owner
    uint256 public withdrawalFeePercentage;

    /// @notice withdraw fee is either amount in percentage or absolute minimum. This var defines the absolute minimum
    /// this number is given in decimals for the respective asset of the vault.
    /// Modifiable by owner
    uint256 public withdrawFeeAbsoluteMin; // in underlying base asset, i.e. USDT

    // charge from the profits, scaled to use basis points. i.e. 1e4 = 100%, 1e2 = 1%
    uint256 public revenueFeePercentage;

    /// @notice Stores reserves for the vault (previously revenue)
    /// @dev Reserves - also serve a purpose to cover unknown users losses
    /// @dev Reserves can be negative if there is not enough revenue to cover the losses
    int256 public reserves;

    /// @notice Min APR for the vault. This is the minimum APR the vault must yield.
    /// @dev Can be modified by the owner / secondary auth.
    uint256 public minRate;

    /// @notice Max APR for the vault. This is the maximum APR the vault can yield.
    /// @dev Can be modified by the owner / secondary auth.
    uint256 public maxRate;

    /// @notice Revenue will be transffered to this address upon collection.
    address public treasury;

    ///@notice Mapping to store fluid vault details
    /// @dev Protocol ID => Fluid Vault Details (VaultAddress, NFTId)
    /// 2: FLUID-WSTUSR-USDC
    /// 3: FLUID-WSTUSR-USDT
    /// 4: FLUID-WSTUSR-GHO
    /// 5: FLUID-SUSDE-USDC
    /// 6: FLUID-SUSDE-USDT
    /// 7: FLUID-SUSDE-GHO
    /// 8: FLUID-syrupUSDC-USDC
    /// 9: FLUID-syrupUSDC-USDT
    /// 10: FLUID-syrupUSDC-GHO
    mapping(uint8 => Structs.FluidVaultDetails) public fluidVaultDetails;

    /// @notice Daily swap limit of the vault.
    /// @dev This is used to prevent abuse of the swap functionality.
    /// @dev Team multisig can update this value.
    uint256 public maxDailySwapLimit;

    /// @notice Available swap limit of the vault.
    /// @dev This is used to track the available swap limit of the vault.
    uint256 public availableSwapLimit;

    /// @notice Last timestamp the swap limit was recalculated.
    uint256 public lastSwapTimestamp;

    /// @notice Maximum loss in USD that can be incurred during a swap.
    /// In Percentage, scaled to use the basis points. i.e. 1e4 = 100%, 1e2 = 1%
    uint256 public maxSwapLossPercentage;
}
"
    },
    "contracts/vault/common/variables/structs.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

library Structs {
    struct FluidVaultDetails {
        address vaultAddress;
        uint256 nftId;
    }

    enum AuthTypes {
        Owner,
        SecondaryAuth,
        PrimaryRebalancer,
        Rebalancer
    }
}
"
    },
    "contracts/vault/common/variables/variablesBuffer.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

/// @title      VariablesBuffer
/// @notice     Allocates space of 151 slots to maintain storage
///             consistency with imported variables in VariablesPrimaryHelper.

contract VariablesBuffer {
    uint[151] internal __buffergap;
}
"
    },
    "contracts/vault/common/variables/variablesBufferHelper.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

/// @title      VariablesBufferHelper
/// @notice     Buffer Helper for variables that imports all the primary
///             helpers from the storage slot 152.

import {VariablesBuffer} from "./variablesBuffer.sol";
import {PrimaryHelpers} from "./primaryHelpers.sol";

// Buffer & variables
contract VariablesBufferHelper is VariablesBuffer, PrimaryHelpers {}
"
    },
    "contracts/vault/modules/rebalancer-module/events.sol": {
      "content": "//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

contract Events{
    event LogVaultToProtocolDeposit(
        uint8 indexed protocolId,
        address indexed tokenAddress,
        uint256 depositAmount
    );

    event LogFillVaultAvailability(
        uint8 indexed protocolId,
        address tokenAddress,
        uint256 indexed withdrawAmount
    );

    event LogCollectRevenue(
        uint256 amount,
        address treasury
    );

    event LogUpdateExchangePrice(
        uint256 oldExchangePrice,
        uint256 newExchangePrice,
        uint256 newGrossExchangePrice,
        uint256 revenueFeePercentage,
        uint256 floorExchangePrice,
        uint256 ceilExchangePrice,
        uint256 hurdleExchangePrice
    );

    event LogToggleAaveV3EMode(
        uint256 emodeId
    );
}"
    },
    "contracts/vault/modules/rebalancer-module/main.sol": {
      "content": "//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {Events} from "./events.sol";
import {VariablesBufferHelper} from "../../common/variables/variablesBufferHelper.sol";
import {IVaultV3} from "../../common/interfaces/IVaultV3.sol";
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";

/// @title RebalancerModule
/// @dev Actions are executable by allowed rebalancers only

contract RebalancerModule is VariablesBufferHelper, Events {
    using SafeERC20Upgradeable for IERC20Upgradeable;
    /***********************************|
    |              ERRORS               |
    |__________________________________*/
    error RebalancerModule__NotValidCollectRevenueAmount();
    error RebalancerModule__CollectRevenueAmountIsHigh();
    error RebalancerModule__NotEnoughReserves();

    // @param protocolId_ - The protocol ID to which the position is being deposited.
    // @param tokenAddress_ - The address of the token being deposited.
    // @param depositAmount_ - The amount to be deposited into the protocol.
    // @param swapConnectors_ - The swap connectors to use, pass empty array if not needed.
    // @param swapCallDatas_ - The swap call datas to use, pass empty array if not needed.
    // @dev This function is used to deposit a position into a protocol.
    function vaultToProtocolDeposit(
        uint8 protocolId_,
        address depositTokenAddress_,
        uint256 depositAmount_,
        uint256 minBuyAmount_,
        string[] memory swapConnectors_,
        bytes[] memory swapCallDatas_
    ) public onlyPrimaryRebalancer nonReentrant {
        uint256 depositTokenExchangeRate_ = IVaultV3(address(this))
            .getTokenExchangeRate(depositTokenAddress_);

        uint256 depositAmountInUsdt_ = _getAmountInUsd(
            depositTokenAddress_,
            depositAmount_,
            depositTokenExchangeRate_
        );

        IERC20Upgradeable(_USDT_ADDRESS).approve(
            address(vaultDSA),
            depositAmountInUsdt_
        );

        uint256 spellCount_ = 3;
        uint256 spellIndex_ = 0;

        string[] memory targets_ = new string[](spellCount_);
        bytes[] memory calldatas_ = new bytes[](spellCount_);

        targets_[spellIndex_] = "BASIC-A";
        calldatas_[spellIndex_] = abi.encodeWithSignature(
            "deposit(address,uint256,uint256,uint256)",
            _USDT_ADDRESS,
            depositAmountInUsdt_,
            0,
            0
        );

        spellIndex_++;

        _handleSwapLimitCheck(depositAmountInUsdt_);

        targets_[spellIndex_] = "SWAP-AGGREGATOR-B";
        calldatas_[spellIndex_] = abi.encodeWithSignature(
            "swap(address,address,uint256,uint256,uin256,uint256,string[],bytes[])",
            _USDT_ADDRESS,
            depositTokenAddress_,
            minBuyAmount_,
            1e6, // Exchange rate of USDT is 1e6
            depositTokenExchangeRate_,
            maxSwapLossPercentage,
            swapConnectors_,
            swapCallDatas_
        );

        spellIndex_++;

        if (protocolId_ == 1) {
            targets_[spellIndex_] = "AAVE-V3-A";
            calldatas_[spellIndex_] = abi.encodeWithSignature(
                "deposit(address,uint256,uint256,uint256)",
                depositTokenAddress_,
                depositAmount_,
                0,
                0
            );
        } else if (
            protocolId_ == 2 ||
            protocolId_ == 3 ||
            protocolId_ == 4 ||
            protocolId_ == 5 ||
            protocolId_ == 6 ||
            protocolId_ == 7
        ) {
            targets_[spellIndex_] = "FLUID-A";
            calldatas_[spellIndex_] = abi.encodeWithSignature(
                "operate(address,uint256,int256,int256,uint256)",
                fluidVaultDetails[protocolId_].vaultAddress,
                fluidVaultDetails[protocolId_].nftId,
                depositAmount_,
                0,
                0
            );
        } else {
            revert Helpers__UnsupportedProtocolId();
        }

        spellIndex_++;

        vaultDSA.cast(targets_, calldatas_, address(this));
        emit LogVaultToProtocolDeposit(
            protocolId_,
            depositTokenAddress_,
            depositAmount_
        );
    }

    // @dev This function is used to fill the vault's balance enabling seamless user withdrawals.
    // @param protocolId_ - The protocol ID to which the position is being deposited.
    // @param withdrawTokenAddress_ - The address of the token being withdrawn.
    // @param withdrawAmount_ - The amount to be withdrawn from the protocol.
    // @param swapConnectors_ - The addresses of the swap connectors to be used, pass empty array if not needed.
    // @param swapCallDatas_ - The calldatas of the swap connectors to be used, pass empty array if not needed.
    function fillVaultAvailability(
        uint8 protocolId_,
        address withdrawTokenAddress_,
        uint256 withdrawAmount_,
        uint256 minBuyAmount_,
        string[] memory swapConnectors_,
        bytes[] memory swapCallDatas_
    ) public onlyPrimaryRebalancer nonReentrant {
        // Withdraw from the protocol
        uint256 spellCount_ = (withdrawTokenAddress_ == _USDT_ADDRESS) ? 2 : 3;
        uint256 spellIndex_ = 0;

        string[] memory targets_ = new string[](spellCount_);
        bytes[] memory calldatas_ = new bytes[](spellCount_);

        if (protocolId_ == 1) {
            targets_[spellIndex_] = "AAVE-V3-A";
            calldatas_[spellIndex_] = abi.encodeWithSignature(
                "withdraw(address,uint256,address,uint256,uint256)",
                withdrawTokenAddress_,
                withdrawAmount_,
                address(this),
                0,
                0
            );
        } else if (
            protocolId_ == 2 ||
            protocolId_ == 3 ||
            protocolId_ == 4 ||
            protocolId_ == 5 ||
            protocolId_ == 6 ||
            protocolId_ == 7
        ) {
            targets_[spellIndex_] = "FLUID-A";
            calldatas_[spellIndex_] = abi.encodeWithSignature(
                "operate(address,uint256,int256,int256,uint256)",
                fluidVaultDetails[protocolId_].vaultAddress,
                fluidVaultDetails[protocolId_].nftId,
                withdrawAmount_,
                0,
                0
            );
        } else {
            revert Helpers__UnsupportedProtocolId();
        }

        spellIndex_++;

        // Swap to USDT to support USDT ERC4626 withdrawals
        if (withdrawTokenAddress_ != _USDT_ADDRESS) {
            uint256 withdrawTokenExchangeRate_ = IVaultV3(address(this))
                .getTokenExchangeRate(withdrawTokenAddress_);
            uint256 withdrawAmountInUsd_ = _getAmountInUsd(
                withdrawTokenAddress_,
                withdrawAmount_,
                withdrawTokenExchangeRate_
            );

            _handleSwapLimitCheck(withdrawAmountInUsd_);

            targets_[spellIndex_] = "SWAP-AGGREGATOR-B";
            calldatas_[spellIndex_] = abi.encodeWithSignature(
                "swap(address,address,uint256,uint256,uin256,uint256,string[],bytes[])",
                withdrawTokenAddress_,
                _USDT_ADDRESS,
                minBuyAmount_,
                withdrawTokenExchangeRate_,
                1e6, // Exchange rate of USDT is 1e6
                maxSwapLossPercentage,
                swapConnectors_,
                swapCallDatas_
            );
            spellIndex_++;
        }

        // Withdraw the amount to vault
        targets_[spellIndex_] = "BASIC-A";
        calldatas_[spellIndex_] = abi.encodeWithSignature(
            "withdraw(address,uint256,address,uint256,uint256)",
            _USDT_ADDRESS,
            type(uint256).max,
            address(this),
            0,
            0
        );

        spellIndex_++;

        vaultDSA.cast(targets_, calldatas_, address(this));
        emit LogFillVaultAvailability(
            protocolId_,
            withdrawTokenAddress_,
            withdrawAmount_
        );
    }

    /// @notice Open function to collect the revenue stored.
    /// @param amount_ Amount of `USDC` revenue to collect.
    /// Note The amount will be transferred to the `treasury` address stored.
    function collectRevenue(
        uint256 amount_
    ) public nonReentrant onlyRebalancer {
        // If reserves are negative, it means collecting revenue is not possible.
        if (reserves < 0) {
            revert RebalancerModule__NotEnoughReserves();
        }

        // Collecting the entire revenue
        // forge-lint: disable-next-line(unsafe-typecast)
        if (amount_ == type(uint256).max) amount_ = uint256(reserves);

        // If amount is 0, it means collecting revenue is not possible.
        if (amount_ == 0)
            revert RebalancerModule__NotValidCollectRevenueAmount();

        // If amount is greater than reserves, it means collecting revenue is not possible.
        // forge-lint: disable-next-line(unsafe-typecast)
        if (amount_ > uint256(reserves))
            revert RebalancerModule__CollectRevenueAmountIsHigh();

        /// @dev Deducting the amount from revenue before transfering so that
        /// if any reentrancy happens, amount is deducted first and then the
        /// assets are transferred, which is a valid state.
        // forge-lint: disable-next-line(unsafe-typecast)
        reserves -= int256(amount_);

        /// @dev transferring to `treasury` address stored.
        IERC20Upgradeable(_USDT_ADDRESS).safeTransfer(treasury, amount_);

        emit LogCollectRevenue(amount_, treasury);
    }

    /// @notice Sets the exchange price and revenue based on current net assets(excluding revenue)
    /// @dev Revenue is only collected if there is a profit
    function updateExchangePrice() public nonReentrant onlyRebalancer {
        uint256 iTokenSupply_ = readFromStorage(
            0x0000000000000000000000000000000000000000000000000000000000000035
        );

        //  If iToken supply doesn't exist yet, the exchange rate will be 1e6 or last updated exchangePrice.
        if (iTokenSupply_ == 0) {
            return;
        }

        // Last exchange price based on old net assets before the update.
        uint256 oldExchangePrice_ = exchangePrice;

        // Calculating the new exchangePrice based on currentNetAssets(excluding reserves)
        (, , uint256 currentNetAssets_, ) = IVaultV3(address(this))
            .getNetAssets();
        uint256 newExchangePrice_ = (currentNetAssets_ * 1e6) / iTokenSupply_;

        // Calculate the floor and ceil exchangePrices for providing minRate and maxRate
        uint256 floorExchangePrice_ = (oldExchangePrice_ *
            (1e4 +
                (minRate * (block.timestamp - lastExchangePriceUpdatedAt)) /
                (31_536_000 * 1e4))) / 1e4;
        uint256 ceilExchangePrice_ = (oldExchangePrice_ *
            (1e4 +
                (maxRate * (block.timestamp - lastExchangePriceUpdatedAt)) /
                (31_536_000 * 1e4))) / 1e4;

        // If newExchangePrice is less than floorExchangePrice, set it to floorExchangePrice
        if (newExchangePrice_ < floorExchangePrice_) {
            // Calculate the difference in assets to be added from reserves
            uint256 reservesDelta_ = ((floorExchangePrice_ -
                newExchangePrice_) * iTokenSupply_) / 1e6;

            // forge-lint: disable-next-line(unsafe-typecast)
            reserves -= int256(reservesDelta_);

            // Update the exchangePrice
            newExchangePrice_ = floorExchangePrice_;
        }

        // If newExchangePrice is greater than ceilExchangePrice, set it to ceilExchangePrice
        if (newExchangePrice_ > ceilExchangePrice_) {
            // Calculate the difference in assets to be added to reserves
            uint256 reservesDelta_ = ((newExchangePrice_ - ceilExchangePrice_) *
                iTokenSupply_) / 1e6;

            // forge-lint: disable-next-line(unsafe-typecast)
            reserves += int256(reservesDelta_);

            // Update the exchangePrice
            newExchangePrice_ = ceilExchangePrice_;
        }

        // If newExchangePrice_ is between floorExchangePrice_ and ceilExchangePrice_, collect revenue
        // Hurdle exchange price refers to the exchange price at which after collecting revenue,
        // the new exchange price is at least floorExchangePrice_
        uint256 hurdleExchangePrice_ = oldExchangePrice_ +
            ((floorExchangePrice_ - oldExchangePrice_) * 1e4) /
            ((1e4 - revenueFeePercentage));

        uint256 revenueFeePercentage_;

        // If newExchangePrice_ is greater than hurdleExchangePrice_, then collect fixed revenue
        if (
            (newExchangePrice_ > hurdleExchangePrice_) &&
            (newExchangePrice_ < ceilExchangePrice_)
        ) {
            revenueFeePercentage_ = revenueFeePercentage;
        } else if (
            (newExchangePrice_ < hurdleExchangePrice_) &&
            (newExchangePrice_ > floorExchangePrice_)
        ) {
            revenueFeePercentage_ =
                ((newExchangePrice_ - floorExchangePrice_) * 1e4) /
                (newExchangePrice_ - oldExchangePrice_);
        } else if (newExchangePrice_ >= ceilExchangePrice_) {
            revenueFeePercentage_ = revenueFeePercentage;
        } else {
            revenueFeePercentage_ = 0;
        }

        uint256 revenue_ = ((newExchangePrice_ - oldExchangePrice_) *
            iTokenSupply_ *
            revenueFeePercentage_) / (1e4 * 1e6);

        // forge-lint: disable-next-line(unsafe-typecast)
        reserves += int256(revenue_);

        exchangePrice =
            ((((newExchangePrice_ * iTokenSupply_) / 1e6) - revenue_) * 1e6) /
            iTokenSupply_;

        lastExchangePriceUpdatedAt = block.timestamp;

        emit LogUpdateExchangePrice(
            oldExchangePrice_,
            exchangePrice,
            newExchangePrice_,
            revenueFeePercentage_,
            floorExchangePrice_,
            ceilExchangePrice_,
            hurdleExchangePrice_
        );
    }

    /// @notice Toggles the Aave V3 e-mode for the vault
    /// @param emodeId_ The e-mode ID to set
    function toggleAaveV3EMode(
        uint256 emodeId_
    ) public nonReentrant onlyRebalancer {
        string[] memory targets_ = new string[](1);
        bytes[] memory calldatas_ = new bytes[](1);

        targets_[0] = "AAVE-V3-A";
        calldatas_[0] = abi.encodeWithSignature(
            "setUserEMode(uint256)",
            emodeId_
        );

        vaultDSA.cast(targets_, calldatas_, address(this));
        emit LogToggleAaveV3EMode(emodeId_);
    }
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "metadata": {
      "useLiteralContent": true
    }
  }
}}

Tags:
ERC20, Multisig, Swap, Yield, Upgradeable, Multi-Signature, Factory|addr:0x6255beaac50520218ffcfe2d9d355eaeae6efa1f|verified:true|block:23689534|tx:0x3eae297c5e75249b9bca422463c92136d4ae14fe1af75d85b4fe3561c2f27af7|first_check:1761829700

Submitted on: 2025-10-30 14:08:23

Comments

Log in to comment.

No comments yet.