ETH Price: $1,859.80 (-4.62%)

Contract Diff Checker

Contract Name:
VirtualPool

Contract Source Code:

File 1 of 1 : contracts/VirtualPool.vy

{{
  "language": "Vyper",
  "sources": {
    "contracts/VirtualPool.vy": {
      "content": "# @version 0.4.3\n\"\"\"\n@title VirtualPool\n@notice Virtual pool to swap LP in Yield Basis without touching the LP token\n@author Scientia Spectra AG\n@license Copyright (c) 2025\n\"\"\"\n\nfrom ethereum.ercs import IERC20 as ERC20\n\ninterface Flash:\n    def flashLoan(receiver: address, token: address, amount: uint256, data: Bytes[10**5]) -> bool: nonpayable\n    def supportedTokens(token: address) -> bool: view\n    def maxFlashLoan(token: address) -> uint256: view\n\ninterface Factory:\n    def flash() -> Flash: view\n    def virtual_pool_impl() -> address: view\n\ninterface Pool:\n    def approve(_spender: address, _value: uint256) -> bool: nonpayable\n    def coins(i: uint256) -> ERC20: view\n    def balances(i: uint256) -> uint256: view\n    def calc_token_amount(amounts: uint256[2], deposit: bool) -> uint256: view\n    def add_liquidity(amounts: uint256[2], min_mint_amount: uint256) -> uint256: nonpayable\n    def remove_liquidity(amount: uint256, min_amounts: uint256[2]) -> uint256[2]: nonpayable\n    def totalSupply() -> uint256: view\n\ninterface YbAMM:\n    def coins(i: uint256) -> ERC20: view\n    def get_dy(i: uint256, j: uint256, in_amount: uint256) -> uint256: view\n    def get_state() -> AMMState: view\n    def fee() -> uint256: view\n    def exchange(i: uint256, j: uint256, in_amount: uint256, min_out: uint256) -> uint256: nonpayable\n    def STABLECOIN() -> ERC20: view\n    def COLLATERAL() -> Pool: view\n\n\nevent TokenExchange:\n    buyer: indexed(address)\n    sold_id: uint256\n    tokens_sold: uint256\n    bought_id: uint256\n    tokens_bought: uint256\n\nstruct AMMState:\n    collateral: uint256\n    debt: uint256\n    x0: uint256\n\n\nFACTORY: public(immutable(Factory))\nAMM: public(immutable(YbAMM))\nPOOL: public(immutable(Pool))\nASSET_TOKEN: public(immutable(ERC20))\nSTABLECOIN: public(immutable(ERC20))\nROUNDING_DISCOUNT: public(constant(uint256)) = 10**18 // 10**8\nIMPL: public(immutable(address))\n\n\n@deploy\ndef __init__(amm: YbAMM):\n    AMM = amm\n    FACTORY = Factory(msg.sender)\n    IMPL = staticcall FACTORY.virtual_pool_impl()\n    POOL = staticcall amm.COLLATERAL()\n    STABLECOIN = staticcall amm.STABLECOIN()\n    assert staticcall POOL.coins(0) == STABLECOIN\n    ASSET_TOKEN = staticcall POOL.coins(1)\n    assert extcall STABLECOIN.approve(POOL.address, max_value(uint256), default_return_value=True)\n    assert extcall ASSET_TOKEN.approve(POOL.address, max_value(uint256), default_return_value=True)\n    assert extcall STABLECOIN.approve(AMM.address, max_value(uint256), default_return_value=True)\n    assert extcall POOL.approve(AMM.address, max_value(uint256), default_return_value=True)\n\n\n@external\n@view\ndef coins(i: uint256) -> ERC20:\n    \"\"\"\n    @notice Coins in the AMM: 0 - stablecoin, 1 - cryptoasset used to form collateral LP\n    \"\"\"\n    return [STABLECOIN, ASSET_TOKEN][i]\n\n\n@internal\n@view\ndef _calculate(i: uint256, in_amount: uint256, only_flash: bool) -> (uint256, uint256):\n    stables_in_pool: uint256 = staticcall POOL.balances(0)\n    out_amount: uint256 = 0\n\n    if i == 0:\n        state: AMMState = staticcall AMM.get_state()\n        pool_supply: uint256 = staticcall POOL.totalSupply()\n        fee: uint256 = staticcall AMM.fee()\n        r0fee: uint256 = stables_in_pool * (10**18 - fee) // pool_supply\n\n        # Solving quadratic eqn instead of calling the AMM b/c we have a special case\n        b: uint256 = state.x0 - state.debt + in_amount - r0fee * state.collateral // 10**18\n        D: uint256 = b**2 + 4 * state.collateral * r0fee // 10**18 * in_amount\n        flash_amount: uint256 = (isqrt(D) - b) // 2  # We received this withdrawing from the pool\n\n        if not only_flash:\n            crypto_in_pool: uint256 = staticcall POOL.balances(1)\n            out_amount = flash_amount * crypto_in_pool // stables_in_pool\n        # Withdrawal was ideally balanced\n        return out_amount, flash_amount\n\n    else:\n        crypto_in_pool: uint256 = staticcall POOL.balances(1)\n        flash_amount: uint256 = in_amount * stables_in_pool // crypto_in_pool\n        if not only_flash:\n            pool_supply: uint256 = staticcall POOL.totalSupply()\n            lp_amount: uint256 = pool_supply * in_amount // crypto_in_pool\n            out_amount = staticcall AMM.get_dy(1, 0, lp_amount) - flash_amount\n        return out_amount, flash_amount\n\n\n@external\n@view\ndef get_dy(i: uint256, j: uint256, in_amount: uint256) -> uint256:\n    \"\"\"\n    @notice Function to preview the result of exchange in the virtual AMM\n    @param i Index of input coin (0 = stablecoin, 1 = crypto)\n    @param j Index of output coin\n    @param in_amount Amount of coin i\n    @return Amount of coin j to be received\n    \"\"\"\n    assert (i == 0 and j == 1) or (i == 1 and j == 0)\n    _in_amount: uint256 = in_amount\n    if i == 0:\n        _in_amount = in_amount * (10**18 - ROUNDING_DISCOUNT) // 10**18\n    return self._calculate(i, _in_amount, False)[0]\n\n\n@external\ndef onFlashLoan(initiator: address, token: address, total_flash_amount: uint256, fee: uint256, data: Bytes[10**5]):\n    \"\"\"\n    @notice Receive a flash loan\n    @param initiator The initiator of the loan\n    @param token The loan currency\n    @param total_flash_amount The amount of tokens lent\n    @param fee The additional amount of tokens to repay\n    @param data Arbitrary data structure, intended to contain user-defined parameters\n    \"\"\"\n    assert initiator == self\n    assert token == STABLECOIN.address\n    assert msg.sender == (staticcall FACTORY.flash()).address, \"Wrong caller\"\n\n    # executor\n    i: uint256 = 0\n    in_amount: uint256 = 0\n    i, in_amount = abi_decode(data, (uint256, uint256))\n    flash_amount: uint256 = self._calculate(i, in_amount, True)[1]\n    in_coin: ERC20 = [STABLECOIN, ASSET_TOKEN][i]\n    out_coin: ERC20 = [STABLECOIN, ASSET_TOKEN][1-i]\n    repay_flash_amount: uint256 = total_flash_amount\n\n    if i == 0:\n        # stablecoin -> crypto exchange\n        # 1. Take flash loan\n        # 2. Use our stables + flash borrowed amount to swap to pool LP in AMM\n        # 3. Withdraw symmetrically from pool LP\n        # 4. Repay the flash loan\n        # 5. Send the crypto\n        lp_amount: uint256 = extcall AMM.exchange(0, 1, (in_amount * (10**18 - ROUNDING_DISCOUNT) // 10**18 + flash_amount), 0)\n        extcall POOL.remove_liquidity(lp_amount, [0, 0])\n        repay_flash_amount = staticcall STABLECOIN.balanceOf(self)\n\n    else:\n        # crypto -> stablecoin exchange\n        # 1. Take flash loan\n        # 2. Deposit taken stables + to Pool\n        # 3. Swap LP of the pool to stables\n        # 4. Repay flash loan\n        # 5. Send the rest to the user\n        lp_amount: uint256 = extcall POOL.add_liquidity([flash_amount, in_amount], 0)\n        extcall AMM.exchange(1, 0, lp_amount, 0)\n\n    assert extcall STABLECOIN.transfer(msg.sender, repay_flash_amount, default_return_value=True)\n\n@external\n@nonreentrant\ndef exchange(i: uint256, j: uint256, in_amount: uint256, min_out: uint256, _for: address = msg.sender) -> uint256:\n    \"\"\"\n    @notice Exchanges two coins, callable by anyone\n    @param i Index of input coin (0 = stablecoin, 1 = crypto)\n    @param j Output coin index\n    @param in_amount Amount of input coin to swap\n    @param min_out Minimal amount to get as output\n    @param _for Address to send coins to\n    @return Amount of coins given in/out\n    \"\"\"\n    assert (i == 0 and j == 1) or (i == 1 and j == 0)\n    flash: Flash = staticcall FACTORY.flash()\n\n    in_coin: ERC20 = [STABLECOIN, ASSET_TOKEN][i]\n    out_coin: ERC20 = [STABLECOIN, ASSET_TOKEN][j]\n\n    assert extcall in_coin.transferFrom(msg.sender, self, in_amount, default_return_value=True)\n\n    data: Bytes[128] = empty(Bytes[128])\n    data = abi_encode(i, in_amount)\n    extcall flash.flashLoan(self, STABLECOIN.address, staticcall flash.maxFlashLoan(STABLECOIN.address), data)\n\n    out_amount: uint256 = staticcall out_coin.balanceOf(self)\n    assert out_amount >= min_out, \"Slippage\"\n    assert extcall out_coin.transfer(_for, out_amount, default_return_value=True)\n\n    log TokenExchange(buyer=_for, sold_id=i, tokens_sold=in_amount, bought_id=j, tokens_bought=out_amount)\n    return out_amount\n\n\n# XXX include methods to determine max_in / max_out\n",
      "sha256sum": "f682f0375fcd6a38ea7c085db91cb62523299323e2b52bba0ff18cfcda26f630"
    }
  },
  "settings": {
    "outputSelection": {
      "contracts/VirtualPool.vy": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "abi"
      ]
    },
    "search_paths": [
      "."
    ]
  },
  "compiler_version": "v0.4.3+commit.bff19ea2",
  "integrity": "3290f2cfee2953e907480422c39ae737efdfc9a2a2dd457fb03a4e87ef5e7d3d"
}}

Please enter a contract address above to load the contract details and source code.

Context size (optional):