From f651291852c7c3acd7fd72ab1808acd79f167ce1 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Sun, 4 Jul 2021 18:05:18 +0200 Subject: [PATCH] Add adapter for Stake DAO farm --- contracts/StakeDaoFarmAdapter.sol | 81 +++++++++++++++++++++ contracts/interfaces/StakeDaoInterfaces.sol | 6 ++ test/stakedao-test.js | 76 ++++++++++++++++++- 3 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 contracts/StakeDaoFarmAdapter.sol diff --git a/contracts/StakeDaoFarmAdapter.sol b/contracts/StakeDaoFarmAdapter.sol new file mode 100644 index 0000000..6f3ca53 --- /dev/null +++ b/contracts/StakeDaoFarmAdapter.sol @@ -0,0 +1,81 @@ +//SPDX-License-Identifier: GPLV3 +pragma solidity ^0.8.0; + +import "./IAdapter.sol"; +import "./Controller.sol"; +import "./interfaces/StakeDaoInterfaces.sol"; +import "./oz/token/ERC20/IERC20.sol"; +import "hardhat/console.sol"; + +contract StakeDaoFarmAdapter is IAdapter { + address private controllerAddress; + + constructor(address _controllerAddress) { + controllerAddress = _controllerAddress; + } + + mapping(address => uint256) public userToStakedLpTokens; + + function invest( + address onBehalfOf, + uint256[] calldata amounts, + address to, + address pullFrom, + address transferTo + ) external payable override returns (uint256[] memory amountsTransferred) { + address inputToken = Controller(controllerAddress).getMarketInputTokens( + to + )[0]; + + (address market, , ) = Controller(controllerAddress).markets(to); + + // transfer LP tokens to adapter + IERC20(inputToken).transferFrom(pullFrom, address(this), amounts[0]); + IERC20(inputToken).approve(market, amounts[0]); + + // stake LP tokens into farm to get rewards + userToStakedLpTokens[onBehalfOf] += amounts[0]; + IStakeDaoYieldFarm(market).deposit(0, amounts[0]); + + return amounts; + } + + function redeem( + address onBehalfOf, + uint256[] calldata amounts, + address from, + address pullFrom, + address transferTo + ) external override returns (uint256[] memory amountsTransferred) { + (address market, , ) = Controller(controllerAddress).markets(from); + + // unstake LP tokens from farm + require(userToStakedLpTokens[onBehalfOf] >= amounts[0]); + userToStakedLpTokens[onBehalfOf] -= amounts[0]; + IStakeDaoYieldFarm(market).withdraw(0, amounts[0]); + + // transfer LP tokens back to user + address inputToken = Controller(controllerAddress).getMarketInputTokens( + from + )[0]; + IERC20(inputToken).transfer(transferTo, amounts[0]); + + return amounts; + } + + function borrow( + address onBehalfOf, + uint256[] calldata amounts, + address from, + address pullFrom, + address transferTo + ) external override returns (uint256[] memory amountsTransferred) {} + + function repay( + address onBehalfOf, + uint256[] calldata amounts, + address to, + address pullFrom, + address transferTo + ) external payable override returns (uint256[] memory amountsTransferred) {} +} diff --git a/contracts/interfaces/StakeDaoInterfaces.sol b/contracts/interfaces/StakeDaoInterfaces.sol index a095e8e..d37e547 100644 --- a/contracts/interfaces/StakeDaoInterfaces.sol +++ b/contracts/interfaces/StakeDaoInterfaces.sol @@ -15,4 +15,10 @@ interface IStakeDaoYieldFarm { function poolLength() external view returns (uint256); function deposit(uint256 _pid, uint256 _amount) external; + + function withdraw(uint256 _pid, uint256 _amount) external; + + function userInfo(uint256 _pid, address _user) + external + returns (uint256, uint256); } diff --git a/test/stakedao-test.js b/test/stakedao-test.js index fd5b0fa..398a165 100644 --- a/test/stakedao-test.js +++ b/test/stakedao-test.js @@ -1,8 +1,9 @@ const { expect, assert } = require("chai"); const { ethers } = require("hardhat"); -let controller, stakeDaoAdapter, stakeDaoLpPool; +let controller, stakeDaoAdapter, stakeDaoFarmAdapter, stakeDaoLpPool; let testAccount, testSigner; +let amount_of_sdam3Crv_staked; const STAKE_DAO_LP = "0x7d60F21072b585351dFd5E8b17109458D97ec120"; const STAKE_DAO_YIELD_FARM = "0x68456B298c230415E2DE7aD4897A79Ee3f1A965a"; @@ -19,6 +20,12 @@ describe("StakeDaoAdapter", function () { // get contracts stakeDaoLpPool = await ethers.getContractAt("IStakeDaoLpPool", STAKE_DAO_LP, testSigner); + stakeDaoFarm = await ethers.getContractAt( + "IStakeDaoYieldFarm", + STAKE_DAO_YIELD_FARM, + testSigner + ); + curveLpToken = await ethers.getContractAt("IERC20", CURVE_AAVE_LP_TOKEN, testSigner); // deploy controller @@ -87,7 +94,7 @@ describe("StakeDaoAdapter", function () { const sdam3CrvBalanceBefore = await sdam3Crv.balanceOf(testAccount); // approve LP token for burning and unstake - const sdam3CrvToUnstake = sdam3CrvBalanceBefore / 2; + const sdam3CrvToUnstake = sdam3CrvBalanceBefore.div(2); await sdam3Crv.approve(stakeDaoAdapter.address, sdam3CrvToUnstake); await stakeDaoAdapter.redeem( testAccount, @@ -104,6 +111,45 @@ describe("StakeDaoAdapter", function () { assert(am3CrvBalanceBefore < am3CrvBalanceAfter); assert(sdam3CrvBalanceBefore > sdam3CrvBalanceAfter); }); + + it("should stake sdam3Crv to Stake Dao yield farm", async function () { + await initStakeDaoFarm(); + + const sdam3Crv = await ethers.getContractAt("IERC20", STAKE_DAO_LP, testSigner); + const sdam3CrvBalanceBefore = await sdam3Crv.balanceOf(testAccount); + + // approve sdam3Crv and stake + await sdam3Crv.approve(stakeDaoFarmAdapter.address, sdam3CrvBalanceBefore); + await stakeDaoFarmAdapter.invest( + testAccount, + [sdam3CrvBalanceBefore], + STAKE_DAO_YIELD_FARM, + testAccount, + testAccount + ); + + amount_of_sdam3Crv_staked = sdam3CrvBalanceBefore; + + const sdam3CrvBalanceAfter = await sdam3Crv.balanceOf(testAccount); + assert(sdam3CrvBalanceAfter < sdam3CrvBalanceBefore); + }); + + it("should unstake sdam3Crv from Stake Dao yield farm", async function () { + const sdam3Crv = await ethers.getContractAt("IERC20", STAKE_DAO_LP, testSigner); + const sdam3CrvBalanceBefore = await sdam3Crv.balanceOf(testAccount); + + // unstake sdam3Crv + await stakeDaoFarmAdapter.redeem( + testAccount, + [amount_of_sdam3Crv_staked.div(2)], + STAKE_DAO_YIELD_FARM, + testAccount, + testAccount + ); + + const sdam3CrvBalanceAfter = await sdam3Crv.balanceOf(testAccount); + assert(sdam3CrvBalanceAfter > sdam3CrvBalanceBefore); + }); }); /** @@ -127,6 +173,32 @@ async function initBalances(testAccount) { }); } +/** + * Deploy and register Stake Dao adapter + */ +async function initStakeDaoFarm() { + // deploy Stake Dao adapter + const StakeDaoFarmAdapter = await ethers.getContractFactory("StakeDaoFarmAdapter"); + tempAdapter = await StakeDaoFarmAdapter.deploy(controller.address); + await tempAdapter.deployed(); + stakeDaoFarmAdapter = await ethers.getContractAt("IAdapter", tempAdapter.address, testSigner); + + // add adapter and market data + await controller.addProtocolAdapter( + ethers.utils.formatBytes32String("Stake Dao farm adapter"), + stakeDaoAdapter.address + ); + await controller.addMarket( + ethers.utils.formatBytes32String("Stake Dao farm adapter"), + STAKE_DAO_YIELD_FARM, + STAKE_DAO_YIELD_FARM, + STAKE_DAO_YIELD_FARM, + wMATIC, + [STAKE_DAO_LP], + [STAKE_DAO_TOKEN] + ); +} + /** * Format to ETH */