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
Improperly implemented strategies may lead to wallet malfunctions or potential fund losses. During strategy development, feel free to reach out to our team for a thorough review.
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:
- Initiate a flash loan for the exact amount of the current collateral from Aave v2
- Execute a swap for the precise amount to the targeted collateral token using Uniswap v3
- Deposit the new collateral into Compound v3 and withdraw the old collateral
- 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/withdrawalsuppliedCollateralToken
: Address of the currently supplied collateraltargetCollateralToken
: Address of the new collateral token to supplycollateralAmountToSwap
: The amount of supplied collateralmode
: Flashloan mode for Aave
Functions
The collateralSwap
function operates in these steps:
- Prepares parameters for the flashloan
- Allows the fallback handler to withdraw and transfer
- Calls the flashloan on Aave
- 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:
- Verifying the initiator
- Decoding received data
- Setting up and executing the token swap
- Managing token approvals and transfers
- Supplying new collateral and withdrawing old collateral
Security considerations:
- The fallback handler initially verifies that the initiator of the flash loan is the SA address
- The fallback handler reinstates the default fallback handler of SA once the flash loan operations are completed
- Approvals granted by SA for repaying the loan, allowances given to the fallback handler for withdrawing collateral, and approvals for the fallback handler to transfer borrowed tokens are revoked at the conclusion of executed operations
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.