ETH Price: $1,799.80 (-0.72%)

Transaction Decoder

Block:
21809313 at Feb-09-2025 01:39:47 PM +UTC
Transaction Fee:
0.000098992664594976 ETH $0.18
Gas Used:
76,308 Gas / 1.297277672 Gwei

Emitted Events:

70 CurveStableSwapNG.Transfer( sender=[Sender] 0x944fb2f58ed2c2e8fd4d9bb7013f262875467da5, receiver=[Receiver] LevelUsdPointsFarm, value=18578025334554749201180 )
71 LevelUsdPointsFarm.Stake( user=[Sender] 0x944fb2f58ed2c2e8fd4d9bb7013f262875467da5, lpToken=CurveStableSwapNG, amount=18578025334554749201180 )

Account State Difference:

  Address   Before After State Difference Code
0x12208686...B08545ea4
3.336497095274174114 Eth3.336535249274174114 Eth0.000038154
0x80B73eF4...73559c00f
(Level Money : Level Usd Points Farm)
0x944fB2F5...875467da5
0.038046439370432637 Eth
Nonce: 444
0.037947446705837661 Eth
Nonce: 445
0.000098992664594976

Execution Trace

LevelUsdPointsFarm.stake( token=0x1220868672D5B10F3E1cB9Ab519E4d0B08545ea4, amount=18578025334554749201180 )
  • CurveStableSwapNG.transferFrom( _from=0x944fB2F58ED2c2e8fD4d9Bb7013F262875467da5, _to=0x80B73eF4534FE245300017A5197451973559c00f, _value=18578025334554749201180 ) => ( True )
  • CurveStableSwapNG.balanceOf( arg0=0x80B73eF4534FE245300017A5197451973559c00f ) => ( 5862975524789759612613048 )
    File 1 of 2: LevelUsdPointsFarm
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
    pragma solidity ^0.8.20;
    import {Context} from "../utils/Context.sol";
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * The initial owner is set to the address provided by the deployer. This can
     * later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract Ownable is Context {
        address private _owner;
        /**
         * @dev The caller account is not authorized to perform an operation.
         */
        error OwnableUnauthorizedAccount(address account);
        /**
         * @dev The owner is not a valid owner account. (eg. `address(0)`)
         */
        error OwnableInvalidOwner(address owner);
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
         */
        constructor(address initialOwner) {
            if (initialOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(initialOwner);
        }
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            _checkOwner();
            _;
        }
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
        /**
         * @dev Throws if the sender is not the owner.
         */
        function _checkOwner() internal view virtual {
            if (owner() != _msgSender()) {
                revert OwnableUnauthorizedAccount(_msgSender());
            }
        }
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby disabling any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            _transferOwnership(address(0));
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            if (newOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(newOwner);
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual {
            address oldOwner = _owner;
            _owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)
    pragma solidity ^0.8.20;
    import {Ownable} from "./Ownable.sol";
    /**
     * @dev Contract module which provides access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * The initial owner is specified at deployment time in the constructor for `Ownable`. This
     * can later be changed with {transferOwnership} and {acceptOwnership}.
     *
     * This module is used through inheritance. It will make available all functions
     * from parent (Ownable).
     */
    abstract contract Ownable2Step is Ownable {
        address private _pendingOwner;
        event OwnershipTransferStarted(
            address indexed previousOwner,
            address indexed newOwner
        );
        /**
         * @dev Returns the address of the pending owner.
         */
        function pendingOwner() public view virtual returns (address) {
            return _pendingOwner;
        }
        /**
         * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
         * Can only be called by the current owner.
         */
        function transferOwnership(
            address newOwner
        ) public virtual override onlyOwner {
            _pendingOwner = newOwner;
            emit OwnershipTransferStarted(owner(), newOwner);
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual override {
            delete _pendingOwner;
            super._transferOwnership(newOwner);
        }
        /**
         * @dev The new owner accepts the ownership transfer.
         */
        function acceptOwnership() public virtual {
            address sender = _msgSender();
            if (pendingOwner() != sender) {
                revert OwnableUnauthorizedAccount(sender);
            }
            _transferOwnership(sender);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
    pragma solidity ^0.8.20;
    /**
     * @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.
     *
     * ==== Security Considerations
     *
     * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
     * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
     * considered as an intention to spend the allowance in any specific way. The second is that because permits have
     * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
     * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
     * generally recommended is:
     *
     * ```solidity
     * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
     *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
     *     doThing(..., value);
     * }
     *
     * function doThing(..., uint256 value) public {
     *     token.safeTransferFrom(msg.sender, address(this), value);
     *     ...
     * }
     * ```
     *
     * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
     * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
     * {SafeERC20-safeTransferFrom}).
     *
     * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
     * contracts should have entry points that don't rely on permit.
     */
    interface IERC20Permit {
        /**
         * @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].
         *
         * CAUTION: See Security Considerations above.
         */
        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);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
    pragma solidity ^0.8.20;
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
        /**
         * @dev Returns the value of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the value of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
        /**
         * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
        /**
         * @dev Moves a `value` amount of tokens from `from` to `to` using the
         * allowance mechanism. `value` 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 value) external returns (bool);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
    pragma solidity ^0.8.20;
    import {IERC20} from "../IERC20.sol";
    import {IERC20Permit} from "../extensions/IERC20Permit.sol";
    import {Address} from "../../../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;
        /**
         * @dev An operation with an ERC20 token failed.
         */
        error SafeERC20FailedOperation(address token);
        /**
         * @dev Indicates a failed `decreaseAllowance` request.
         */
        error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
        /**
         * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
         * non-reverting calls are assumed to be successful.
         */
        function safeTransfer(IERC20 token, address to, uint256 value) internal {
            _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
        }
        /**
         * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
         * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
         */
        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
            _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
        }
        /**
         * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
         * non-reverting calls are assumed to be successful.
         */
        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
            uint256 oldAllowance = token.allowance(address(this), spender);
            forceApprove(token, spender, oldAllowance + value);
        }
        /**
         * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
         * value, non-reverting calls are assumed to be successful.
         */
        function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
            unchecked {
                uint256 currentAllowance = token.allowance(address(this), spender);
                if (currentAllowance < requestedDecrease) {
                    revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
                }
                forceApprove(token, spender, currentAllowance - requestedDecrease);
            }
        }
        /**
         * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
         * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
         * to be set to zero before setting it to a non-zero value, such as USDT.
         */
        function forceApprove(IERC20 token, address spender, uint256 value) internal {
            bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
            if (!_callOptionalReturnBool(token, approvalCall)) {
                _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
                _callOptionalReturn(token, approvalCall);
            }
        }
        /**
         * @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);
            if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
                revert SafeERC20FailedOperation(address(token));
            }
        }
        /**
         * @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).
         *
         * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
         */
        function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
            // 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 cannot use {Address-functionCall} here since this should return false
            // and not revert is the subcall reverts.
            (bool success, bytes memory returndata) = address(token).call(data);
            return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
    pragma solidity ^0.8.20;
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev The ETH balance of the account is not enough to perform the operation.
         */
        error AddressInsufficientBalance(address account);
        /**
         * @dev There's no code at `target` (it is not a contract).
         */
        error AddressEmptyCode(address target);
        /**
         * @dev A call to an address target failed. The target may have reverted.
         */
        error FailedInnerCall();
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            if (address(this).balance < amount) {
                revert AddressInsufficientBalance(address(this));
            }
            (bool success, ) = recipient.call{value: amount}("");
            if (!success) {
                revert FailedInnerCall();
            }
        }
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason or custom error, it is bubbled
         * up by this function (like regular Solidity function calls). However, if
         * the call reverted with no returned reason, this function reverts with a
         * {FailedInnerCall} error.
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            if (address(this).balance < value) {
                revert AddressInsufficientBalance(address(this));
            }
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
         * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
         * unsuccessful call.
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata
        ) internal view returns (bytes memory) {
            if (!success) {
                _revert(returndata);
            } else {
                // only check if target is a contract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                if (returndata.length == 0 && target.code.length == 0) {
                    revert AddressEmptyCode(target);
                }
                return returndata;
            }
        }
        /**
         * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
         * revert reason or with a default {FailedInnerCall} error.
         */
        function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
            if (!success) {
                _revert(returndata);
            } else {
                return returndata;
            }
        }
        /**
         * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
         */
        function _revert(bytes memory returndata) private pure {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert FailedInnerCall();
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
    pragma solidity ^0.8.20;
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
    pragma solidity ^0.8.20;
    /**
     * @dev Contract module that helps prevent reentrant calls to a function.
     *
     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
     * available, which can be applied to functions to make sure there are no nested
     * (reentrant) calls to them.
     *
     * Note that because there is a single `nonReentrant` guard, functions marked as
     * `nonReentrant` may not call one another. This can be worked around by making
     * those functions `private`, and then adding `external` `nonReentrant` entry
     * points to them.
     *
     * TIP: If you would like to learn more about reentrancy and alternative ways
     * to protect against it, check out our blog post
     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
     */
    abstract contract ReentrancyGuard {
        // Booleans are more expensive than uint256 or any type that takes up a full
        // word because each write operation emits an extra SLOAD to first read the
        // slot's contents, replace the bits taken up by the boolean, and then write
        // back. This is the compiler's defense against contract upgrades and
        // pointer aliasing, and it cannot be disabled.
        // The values being non-zero value makes deployment a bit more expensive,
        // but in exchange the refund on every call to nonReentrant will be lower in
        // amount. Since refunds are capped to a percentage of the total
        // transaction's gas, it is best to keep them low in cases like this one, to
        // increase the likelihood of the full refund coming into effect.
        uint256 private constant NOT_ENTERED = 1;
        uint256 private constant ENTERED = 2;
        uint256 private _status;
        /**
         * @dev Unauthorized reentrant call.
         */
        error ReentrancyGuardReentrantCall();
        constructor() {
            _status = NOT_ENTERED;
        }
        /**
         * @dev Prevents a contract from calling itself, directly or indirectly.
         * Calling a `nonReentrant` function from another `nonReentrant`
         * function is not supported. It is possible to prevent this from happening
         * by making the `nonReentrant` function external, and making it call a
         * `private` function that does the actual work.
         */
        modifier nonReentrant() {
            _nonReentrantBefore();
            _;
            _nonReentrantAfter();
        }
        function _nonReentrantBefore() private {
            // On the first call to nonReentrant, _status will be NOT_ENTERED
            if (_status == ENTERED) {
                revert ReentrancyGuardReentrantCall();
            }
            // Any calls to nonReentrant after this point will fail
            _status = ENTERED;
        }
        function _nonReentrantAfter() private {
            // By storing the original value once again, a refund is triggered (see
            // https://eips.ethereum.org/EIPS/eip-2200)
            _status = NOT_ENTERED;
        }
        /**
         * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
         * `nonReentrant` function in the call stack.
         */
        function _reentrancyGuardEntered() internal view returns (bool) {
            return _status == ENTERED;
        }
    }
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity 0.8.24;
    interface ILevelUsdPointsFarm {
        /// @notice information about staking for a particular LP token
        struct StakeParameters {
            uint8 epoch;
            uint248 stakeLimit;
            uint104 totalStaked; // total deposited and not in cooldown
            uint104 totalCoolingDown;
            uint48 cooldown;
        }
        /// @notice information about a particular stake by user and LP token
        struct StakeData {
            uint256 stakedAmount;
            uint152 coolingDownAmount;
            uint104 cooldownStartTimestamp;
        }
        /// @notice emitted when an epoch begins
        event NewEpoch(uint8 indexed newEpoch, uint8 indexed previousEpoch);
        /// @notice emitted when staking parameters are added/updated for an LP token
        event StakeParametersUpdated(
            address indexed lpToken,
            uint8 indexed epoch,
            uint248 stakeLimit,
            uint104 cooldown
        );
        /// @notice emitted when a user stakes
        event Stake(address indexed user, address indexed lpToken, uint256 amount);
        /// @notice emitted when a user unstakes
        event Unstake(
            address indexed user,
            address indexed lpToken,
            uint256 amount
        );
        /// @notice emitted when a user withdraws
        event Withdraw(
            address indexed user,
            address indexed lpToken,
            uint256 amount
        );
        /// @notice emitted when tokens are rescued by owner
        event TokensRescued(
            address indexed token,
            address indexed to,
            uint256 amount
        );
        /// @notice ownership cannot be renounced
        error CantRenounceOwnership();
        /// @notice Error returned when a user tries staking more than the limit for a given token
        error StakeLimitExceeded();
        /// @notice Error returned when staking LP token during wrong epoch
        error InvalidEpoch();
        /// @notice zero amount or amount greater than a max such as amount staked
        error InvalidAmount();
        /// @notice Error returned when native ETH transfer fails
        error TransferFailed();
        /// @notice Error returned when excess balance of an LP token is less than 0
        error InvariantBroken();
        /// @notice Error returned when owner sets cooldown > 1 year
        error MaxCooldownExceeded();
        /// @notice Error returned when user attempts to withdraw before cooldown period is over
        error CooldownNotOver();
        /// @notice This error is returned if the zero address is used
        error ZeroAddressException();
    }
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity 0.8.24;
    import "@openzeppelin/contracts/access/Ownable2Step.sol";
    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
    import "./interface/ILevelUsdPointsFarm.sol";
    /**
     * @title LevelUsdPointsFarm
     * Forked from EthenaLPStaking
     * @notice Allows users to deposit tokens (including LP tokens) to earn
     * Level XP; also includes a cooldown period set by the admin
     * to control for withdrawals when redemptions are gated.
     */
    contract LevelUsdPointsFarm is
        Ownable2Step,
        ILevelUsdPointsFarm,
        ReentrancyGuard
    {
        using SafeERC20 for IERC20;
        // ---------------------- Constants -----------------------
        /// @notice placeholder address for ETH
        address internal constant _ETH_ADDRESS =
            0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
        /// @notice the maximum cooldown period the owner can set for any LP token
        uint48 internal constant _MAX_COOLDOWN_PERIOD = 90 days;
        // ----------------------- Storage ------------------------
        /// @notice tracks the current epoch
        uint8 public currentEpoch;
        /// @notice tracks all stakes, indexed by user and LP token
        mapping(address => mapping(address => StakeData)) public stakes;
        /// @notice tracks stake parameters for each LP token, indexed by LP token address
        mapping(address => StakeParameters) public stakeParametersByToken;
        // --------------------- Constructor ----------------------
        constructor(address _initialOwner) Ownable(_initialOwner) {
            if (_initialOwner == address(0)) revert ZeroAddressException();
            _transferOwnership(_initialOwner);
        }
        // ---------------------- Modifiers -----------------------
        /**
         * @notice checks that the amount is not 0
         * @param amount the amount to check
         */
        modifier checkAmount(uint256 amount) {
            if (amount == 0) revert InvalidAmount();
            _;
        }
        // ------------------- Owner Functions --------------------
        /**
         * @notice owner can change epoch
         * @param newEpoch the new epoch
         */
        function setEpoch(uint8 newEpoch) external onlyOwner {
            if (newEpoch == currentEpoch) revert InvalidEpoch();
            emit NewEpoch(newEpoch, currentEpoch);
            currentEpoch = newEpoch;
        }
        /**
         * @notice owner can add/update stake parameters for a given LP token
         * @param token the LP token to update stake parameters for
         * @param epoch the epoch the token is eligible for staking
         * @param stakeLimit the maximum amount of LP tokens that can be staked
         * @param cooldown the cooldown period for withdrawing LP tokens
         */
        function updateStakeParameters(
            address token,
            uint8 epoch,
            uint248 stakeLimit,
            uint48 cooldown
        ) external onlyOwner {
            if (cooldown > _MAX_COOLDOWN_PERIOD) revert MaxCooldownExceeded();
            StakeParameters storage stakeParameters = stakeParametersByToken[token];
            // owner cannot modify total staked or cooling down
            stakeParameters.epoch = epoch;
            stakeParameters.stakeLimit = stakeLimit;
            stakeParameters.cooldown = cooldown;
            emit StakeParametersUpdated(token, epoch, stakeLimit, cooldown);
        }
        /**
         * @notice owner can rescue tokens that were accidentally sent to the contract
         * @param token the token to transfer
         * @param to the address to send the tokens to
         * @param amount the amount of tokens to send
         */
        function rescueTokens(
            address token,
            address to,
            uint256 amount
        ) external onlyOwner nonReentrant checkAmount(amount) {
            if (to == address(0)) revert ZeroAddressException();
            // contract should never hold ETH
            if (token == _ETH_ADDRESS) {
                (bool success, ) = to.call{value: amount}("");
                if (!success) revert TransferFailed();
            } else {
                IERC20(token).safeTransfer(to, amount);
                _checkInvariant(token);
            }
            emit TokensRescued(token, to, amount);
        }
        /// @notice Prevents the owner from renouncing ownership, must be transferred in 2 steps
        function renounceOwnership() public view override onlyOwner {
            revert CantRenounceOwnership();
        }
        // ----------------------- User Functions ------------------------
        /**
         * @notice users can stake LP tokens to earn shards toward airdrop
         * @param token the LP token to stake
         * @param amount the amount of LP tokens to stake
         */
        function stake(
            address token,
            uint104 amount
        ) external nonReentrant checkAmount(amount) {
            StakeParameters storage stakeParameters = stakeParametersByToken[token];
            // can only stake when it is the correct epoch
            if (currentEpoch != stakeParameters.epoch) revert InvalidEpoch();
            if (stakeParameters.totalStaked + amount > stakeParameters.stakeLimit)
                revert StakeLimitExceeded();
            stakeParameters.totalStaked += amount;
            stakes[msg.sender][token].stakedAmount += amount;
            IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
            _checkInvariant(token);
            emit Stake(msg.sender, token, amount);
        }
        /**
         * @notice users can unstake LP tokens to initiate the cooldown period.
         * They will not be able to withdraw until the cooldown period has passed and do not earn rewards during this period.
         * @param token the LP token to unstake
         * @param amount the amount of LP tokens to unstake
         */
        function unstake(
            address token,
            uint104 amount
        ) external nonReentrant checkAmount(amount) {
            StakeParameters storage stakeParameters = stakeParametersByToken[token];
            StakeData storage stakeData = stakes[msg.sender][token];
            if (stakeData.stakedAmount < amount) revert InvalidAmount();
            stakeData.stakedAmount -= amount;
            stakeData.coolingDownAmount += amount;
            stakeData.cooldownStartTimestamp = uint104(block.timestamp);
            stakeParameters.totalStaked -= amount;
            stakeParameters.totalCoolingDown += amount;
            _checkInvariant(token);
            emit Unstake(msg.sender, token, amount);
        }
        /**
         * @notice users can withdraw LP tokens after the cooldown period has passed
         * @param token the LP token to withdraw
         * @param amount the amount of LP tokens to withdraw
         */
        function withdraw(
            address token,
            uint104 amount
        ) external nonReentrant checkAmount(amount) {
            StakeParameters storage stakeParameters = stakeParametersByToken[token];
            StakeData storage stakeData = stakes[msg.sender][token];
            if (stakeData.coolingDownAmount < amount) revert InvalidAmount();
            if (
                block.timestamp <
                stakeData.cooldownStartTimestamp + stakeParameters.cooldown
            ) revert CooldownNotOver();
            stakeData.coolingDownAmount -= amount;
            stakeParameters.totalCoolingDown -= amount;
            IERC20(token).safeTransfer(msg.sender, amount);
            _checkInvariant(token);
            emit Withdraw(msg.sender, token, amount);
        }
        // ----------------------- Internal Functions ------------------------
        /**
         * @notice checks that the invariant is not broken
         * @param token the LP token to check
         * @dev the invariant is that the contract should never hold less of a token than the total staked and cooling down
         * @dev despite the higher gas cost of an extra sload here, we intentionally do not pass in the stake parameters
         * because we want to ensure that the invariant is checked against the current state of the contract
         */
        function _checkInvariant(address token) internal view {
            StakeParameters storage stakeParameters = stakeParametersByToken[token];
            uint256 balance = IERC20(token).balanceOf(address(this));
            if (
                balance <
                stakeParameters.totalStaked + stakeParameters.totalCoolingDown
            ) revert InvariantBroken();
        }
    }
    

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