Build Strategy By Example

Strategy Library

We've introduced a library featuring strategies for various DeFi protocols, these strategies are used in Unhosted wallet and might be useful in to get idea or even be built upon them. This library is consistently updated to incorporate new protocols. Each strategy within the library offers key functionalities specific to its associated protocol.

Installation

Hardhat, Truffle (npm)

npm install @unhosted/strategies  @openzeppelin/contracts

Foundry (git)

forge install Unhosted-Wallet/unhosted-strategies OpenZeppelin/openzeppelin-contracts

Also add to remappings.txt:

@unhosted/strategies/=lib/unhosted-strategies/src/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/

Swapping Collateral with Compound v3 Strategy

Explore this tutorial to construct a straightforward DeFi strategy for swapping provided collateral to another supported collateral within the Compound v3 protocol. The strategy includes four steps for executing the collateral swap:

  1. Initiate a flash loan for the exact amount of the current collateral from Aave v2
  2. Execute a swap for the precise amount to the targeted collateral token using Uniswap v3
  3. Deposit the new collateral into Compound v3 and withdraw the old collateral
  4. Repay the flash loan to Aave v2 with the withdrawn collateral

Strategy Contract

/contracts/CompV3CollateralSwap.sol:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import {IComet} from "@unhosted/strategies/compoundV3/CompoundV3Strategy.sol";
import {BaseStrategy, IERC20, SafeERC20} from "@unhosted/strategies/BaseStrategy.sol";
import {AaveV2Strategy, ILendingPoolAddressesProviderV2} from "@unhosted/strategies/aaveV2/AaveV2Strategy.sol";

/**
 * @title Compound v3 collateral swap strategy
 * @dev Compatible with Unhosted strategy module
 */
contract CompV3CollateralSwap is AaveV2Strategy {
    using SafeERC20 for IERC20;

    address public immutable fallbackHandler;

    constructor(address wethAddress, address aaveV2Provider, address fallbackHandler_, address dataProvider_, address quoter_)
        AaveV2Handler(wethAddress, aaveV2Provider, fallbackHandler_, dataProvider_, quoter_)
    {
        fallbackHandler = fallbackHandler_;
    }

    struct ReceiveData {
        address tokenOut;
        address comet;
    }

    /**
     * @dev Executes a collateral swap from the supplied token to another supported collateral token on Compound v3.
     * @param comet Address of the Compound contract used for collateral supply and withdrawal.
     * @param suppliedCollateralToken, Address of the currently supplied collateral token.
     * @param targetCollateralToken, Address of the new collateral token to be supplied.
     * @param collateralAmountToSwap, Amount of the current collateral token to be swapped.
     * @param debtMode, Flashloan mode for Aave (noDebt=0, stableDebt=1, variableDebt=2).
     */
    function collateralSwap(
        address comet,
        address suppliedCollateralToken,
        address targetCollateralToken,
        uint256 collateralAmountToSwap,
        uint256 debtMode
    ) public payable {
        uint256[] memory mode = new uint256[](1);
        uint256[] memory amount = new uint256[](1);
        address[] memory token = new address[](1);
        ReceiveData memory receiveData = ReceiveData(targetCollateralToken, comet);
        mode[0] = debtMode;
        amount[0] = collateralAmountToSwap;
        token[0] = suppliedCollateralToken;
        bytes memory data = abi.encode(receiveData);

        IComet(comet).allow(fallbackHandler, true);
        IERC20(suppliedCollateralToken).approve(fallbackHandler, collateralAmountToSwap);
        flashLoan(token, amount, mode, data);
        IComet(comet).allow(fallbackHandler, false);
        IERC20(suppliedCollateralToken).approve(fallbackHandler, 0);
    }

    function getContractName() public pure override(AaveV2Strategy) returns (string memory) {
        return "CollateralSwapStrategy";
    }
}

Parameters

  • comet: The address of the Compound protocol comet contract for supply/withdrawal
  • suppliedCollateralToken: Address of the currently supplied collateral
  • targetCollateralToken: Address of the new collateral token to supply
  • collateralAmountToSwap: The amount of supplied collateral
  • mode: Flashloan mode for Aave

Functions

The collateralSwap function operates in these steps:

  1. Prepares parameters for the flashloan
  2. Allows the fallback handler to withdraw and transfer
  3. Calls the flashloan on Aave
  4. Revokes allowances and approvals

Fallback Handler Contract

/contracts/FallbackHandler.sol:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

/* solhint-disable no-empty-blocks */
import {IFlashLoanReceiver} from "@unhosted/strategies/aaveV2/FallbackHandler.sol";
import {ISwapRouter} from "@unhosted/strategies/uniswapV3/UniswapV3Strategy.sol";
import {IERC20} from "@unhosted/strategies/BaseStrategy.sol";
import {IComet} from "@unhosted/strategies/compoundV3/CompoundV3Strategy.sol";

/**
 * @title Collateral swap flashloan fallback handler
 * @dev This contract temporarily replaces the default handler of SA during the flashloan process
 */
contract FallbackHandler is IFlashLoanReceiver {
    // prettier-ignore
    ISwapRouter public immutable router;

    struct ReceiveData {
        address tokenOut;
        address comet;
    }

    error InvalidInitiator();
    error SwapFailed();

    constructor(address router_) {
        router = ISwapRouter(router_);
    }

    /**
     * @dev Called by SA during the executeOperation of a flashloan
     * @dev Transfers the borrowed tokens from SA, swaps it for new collateral,
     * and supplies the new collateral, then withdraws the previous collateral to repay the loan
     */
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata,
        address initiator,
        bytes calldata data
    ) external virtual returns (bool) {
        if (initiator != msg.sender) {
            revert InvalidInitiator();
        }
        ReceiveData memory decodedData = abi.decode(data, (ReceiveData));
        {
            ISwapRouter.ExactInputSingleParams memory params;

            params.tokenIn = assets[0];
            params.tokenOut = decodedData.tokenOut;
            params.fee = 3000;
            params.recipient = address(this);
            params.amountIn = amounts[0];
            params.amountOutMinimum = 1;
            params.sqrtPriceLimitX96 = 0;
            params.deadline = block.timestamp;

            IERC20(assets[0]).transferFrom(msg.sender, address(this), amounts[0]);

            IERC20(assets[0]).approve(address(router), amounts[0]);
            try router.exactInputSingle(params) {}
            catch {
                revert SwapFailed();
            }
        }

        uint256 newBalance = IERC20(decodedData.tokenOut).balanceOf(address(this));
        IERC20(decodedData.tokenOut).approve(decodedData.comet, newBalance);
        IComet(decodedData.comet).supplyTo(msg.sender, decodedData.tokenOut, newBalance);

        IComet(decodedData.comet).withdrawFrom(
            msg.sender,
            msg.sender, // to
            assets[0],
            amounts[0]
        );

        return true;
    }
}

Functions

The executeOperation function handles:

  1. Verifying the initiator
  2. Decoding received data
  3. Setting up and executing the token swap
  4. Managing token approvals and transfers
  5. Supplying new collateral and withdrawing old collateral

Explore additional examples of strategies here to enhance your understanding of various scenarios. For faster development and efficient testing, we used Foundry - read their documentation for additional insights.

Next Steps

Now you are prepared to add your strategy to our strategy module and start accumulating fees for it.

Was this page helpful?