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"
}}