creation_sourcecode: # pragma version 0.3.10
  # pragma evm-version paris
  """
  @title SiloLlamaLendLoanMigrator(CRV)
  @author fiddyresearch
  """
  
  from vyper.interfaces import ERC20
  
  interface LlamaLendMarket:
      def create_loan_extended(
          collateral: uint256, 
          debt: uint256, 
          N: uint256, 
          callbacker: address, 
          callback_args: DynArray[uint256,5]
      ): nonpayable
  
  interface Silo:
      def assetStorage(_asset: address) -> AssetStorageData: view
      def repayFor(
          _asset: address,
          _borrower: address, 
          _amount: uint256
      ) -> (
          uint256,  # repaid amount 
          uint256,  # repaid share
      ): nonpayable
      def withdraw(
          _asset: address, 
          _amount: uint256, 
          _collateralOnly: bool
      ) -> (
          uint256,  # withdrawn amount 
          uint256,  # withdrawn share
      ): nonpayable
      def isSolvent(_user: address) -> bool: view
      def liquidity(_asset: address) -> uint256: view
  
  
  interface SiloLens:
      def getBorrowAmount(
          _silo: address,
          _asset: address,
          _user: address,
          _timestamp: uint256
      ) -> uint256: view
      def getDepositAmount(
          _silo: address,
          _asset: address,
          _user: address,
          _timestamp: uint256
      ) -> uint256: view
  
  
  struct AssetStorageData:
      collateral_token: address
      collateral_only_token: address
      debt_token: address
      total_deposits: uint256
      collateral_only_deposits: uint256
      total_borrow_amount: uint256
  
  
  CRV_crvUSD_LLAMALEND_CONTROLLER: constant(address) = 0xEdA215b7666936DEd834f76f3fBC6F323295110A
  CRV_crvUSD_SILO: constant(address) = 0x96eFdF95Cc47fe90e8f63D2f5Ef9FB8B180dAeB9
  SILO_ROUTER_V2: constant(address) = 0xF380F025675A868eD5614a1Dd77c6b05f4147004
  SILO_LENS: constant(address) = 0x32a4Bcd8DEa5E18a12a50584682f8E4B77fFF2DF
  
  CRV: constant(address) = 0xD533a949740bb3306d119CC777fa900bA034cd52
  crvUSD: constant(address) = 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E
  
  
  @external
  def __init__():
      ERC20(crvUSD).approve(CRV_crvUSD_SILO, max_value(uint256))
  
  
  @external
  def callback_deposit(
      user: address, 
      stablecoins: uint256, 
      collateral: uint256, 
      debt: uint256, 
      callback_args: DynArray[uint256, 5]
    ) -> uint256[2]:
      """
      @notice Migrate loan from Silo to Llamalend
      @param user Address whos loan gets migrated
      @param stablecoins Amount of flash-borrowed stablecoins from Controller. This
                         amount will be used to clear out the loan at Silo and create
                         a new loan at Curve LlamaLend. The amount must be larger than
                         or equal to the loan at Silo.
      @param collateral Amount of collateral. For the sake fo this contract, we do
                        not really care what the number is since we want to migrate
                        as much (if not all) of the collateral to Curve.
      @param debt Amount of debt. Again for the sake of this contract, it is not
                  important, since we want to clear out the entire loan position.
      @param callback_args An array of uint256 for any other optional args that the
                           callbacker (this contract) might need. Not needed for this
                           migrator contract.
      @returns A two-length array of uint256. This contract returns zeroes. If nonzero
               is returned, then it is simply added to input `collateral` in create_loan_extended
               in Controller.vy (caller of this contract) and then used to create a loan position.
               We don't need this since we mention the total collateral amount in the call args
               of create_loan_extended.
      @dev 1. Method needs to be called `callback_deposit` since that's hardcoded
              in Controller.vy. Also: call signature is hardcoded as well.
           2. If user wants to close their loan and inputs x as collateral in create_loan_extended
              and the migration process closes the loan, but does not necessarily withdraw all of the CRVs
              (since some of it might be loaned out), then it will take additional CRVs from the user's
              balance capped by the user's spending allowance of the collateral token by Controller.vy 
              (caller of this method via create_loan_extended); or else it will revert.
      """
      silo: Silo = Silo(CRV_crvUSD_SILO)
      silo_lens: SiloLens = SiloLens(SILO_LENS)
      user_borrowed_amount: uint256 = silo_lens.getBorrowAmount(
          CRV_crvUSD_SILO, crvUSD, user, block.timestamp
      )
  
      assert msg.sender == CRV_crvUSD_LLAMALEND_CONTROLLER                          # dev: controller-callable method
      assert silo.isSolvent(user)                                                   # dev: user is not solvent
      assert ERC20(crvUSD).balanceOf(self) >= user_borrowed_amount                  # dev: not enough crvUSD flashloaned to close Silo position
  
      # ---------------------------- REPAY SILO LOAN -----------------------------
  
      asset_data: AssetStorageData = silo.assetStorage(crvUSD)
      silo_debt_token: ERC20 = ERC20(asset_data.debt_token)
      silo.repayFor(crvUSD, user, ERC20(crvUSD).balanceOf(self))
  
      # ------------------ TRANSFER SILO COLLATERAL TO MIGRATOR ------------------
  
      asset_data = silo.assetStorage(CRV)
      collateral_token: ERC20 = ERC20(asset_data.collateral_token)
      collateral_only_token: ERC20 = ERC20(asset_data.collateral_only_token)
  
      user_deposit_before_transfer: uint256 = SiloLens(SILO_LENS).getDepositAmount(
          CRV_crvUSD_SILO, CRV, user, block.timestamp
      )
  
      # we need to transfer collateral tokens to migrator so they can withdraw
      # underlying on behalf of user:
      collateral_token.transferFrom(user, self, collateral_token.balanceOf(user))
      collateral_only_token.transferFrom(
          user, self, collateral_only_token.balanceOf(user)
      )
  
      # -------------------------- WITHDRAW COLLATERAL ---------------------------
  
      # we can always fully withdraw collateral only balances:
      silo.withdraw(CRV, max_value(uint256), True)
  
      # check if we can fully withdraw borrowable collateral:
      asset_data = silo.assetStorage(CRV)
      borrowable_collateral_balance: uint256 = (
          collateral_token.balanceOf(self) * asset_data.total_deposits / 
          collateral_token.totalSupply()
      )
  
      available_liquidity: uint256 = silo.liquidity(CRV)
      assert available_liquidity > 0                                                # dev: not enough free collateral in the Silo
  
      # we only withdraw whatever silo has available for withdrawing
      if available_liquidity > borrowable_collateral_balance:
        silo.withdraw(CRV, max_value(uint256), False)
      else:
        silo.withdraw(CRV, available_liquidity, False)
  
      # ---------------------- TRANSFER COLLATERAL TO USER -----------------------
  
      # transfer CRVs back to the user since controller needs to do a transferFrom
      # from the user to itself:
      ERC20(CRV).transfer(user, ERC20(CRV).balanceOf(self))
  
      # transfer any unwithdrawn borrowable silo collaeral tokens back to the user
      if collateral_token.balanceOf(self) > 0:
        collateral_token.transfer(user, collateral_token.balanceOf(self))
  
      # ---------------------------------- NOTE ----------------------------------
  
      # If not enough collateral has been removed, callback might succeed but
      # create_loan_extended (which calls this method) will not be able to create
      # a loan and revert the tx.
  
      # In the case that a user does not have enough collateral withdrawn from
      # Silo (say x amount has been withdrawn), and they called 
      # Controller.vy.`create_loan_extended(...)` with y as `collateral` input,
      # the remained (y - x) will be taken from the user's balance by Controller,
      # provided the user has given Controller.vy spending allowance >= y.
      
      return [0, 0]  # <--- we can return zeroes here safuly.
  
  
  @external
  @view
  def get_silo_crv_collateral_amount(user: address) -> uint256:
      """
      @notice Gets user's collateral amount deposited in Silo.
      @param user Address of the user
      @returns Deposited collateral: includes both borrowable and non borrowable
               collateral. Values are fetched for the current block.
      """
      return SiloLens(SILO_LENS).getDepositAmount(
          CRV_crvUSD_SILO, CRV, user, block.timestamp
      )
  
  
  @external
  @view
  def get_silo_crvusd_borrowed_amount(user: address) -> uint256:
      """
      @notice Gets user's debt amount in Silo.
      @param user Address of the user
      @returns Total debt of the user at current block.
      """
      return SiloLens(SILO_LENS).getBorrowAmount(
          CRV_crvUSD_SILO, crvUSD, user, block.timestamp
      )