Sturdy connector

This commit is contained in:
atozICT20 2022-10-18 10:11:25 -04:00
parent 7b63c1a075
commit bc1e4ea97b
5 changed files with 663 additions and 0 deletions

View File

@ -0,0 +1,19 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
contract Events {
event LogDeposit(address indexed token, uint256 tokenAmt, uint256 getId, uint256 setId);
event LogWithdraw(address indexed token, uint256 tokenAmt, uint256 getId, uint256 setId);
event LogBorrow(address indexed token, uint256 tokenAmt, uint256 indexed rateMode, uint256 getId, uint256 setId);
event LogRepay(address indexed token, uint256 tokenAmt, uint256 indexed rateMode, uint256 getId, uint256 setId);
event LogRepayOnBehalfOf(
address token,
uint256 amt,
uint256 rateMode,
address onBehalfOf,
uint256 getId,
uint256 setId
);
event LogDepositCollateral(address indexed token, uint256 tokenAmt, uint256 getId, uint256 setId);
event LogWithdrawCollateral(address indexed token, uint256 tokenAmt, uint256 getId, uint256 setId);
}

View File

@ -0,0 +1,59 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import { DSMath } from "../../common/math.sol";
import { Basic } from "../../common/basic.sol";
import { SturdyAddressesProviderInterface, SturdyDataProviderInterface } from "./interface.sol";
abstract contract Helpers is DSMath, Basic {
/**
* @dev Sturdy Address Provider
*/
SturdyAddressesProviderInterface constant internal sturdyAddressesProvider = SturdyAddressesProviderInterface(0xb7499a92fc36e9053a4324aFfae59d333635D9c3);
/**
* @dev Sturdy Protocol Data Provider
*/
SturdyDataProviderInterface constant internal sturdyData = SturdyDataProviderInterface(0x960993Cb6bA0E8244007a57544A55bDdb52db97e);
/**
* @dev Sturdy Lido Vault
*/
address constant internal sturdyLidoVault = 0x01c05337354aae5345d27d2A4A70B56a17aF2b4a;
/**
* @dev Sturdy Referral Code
*/
uint16 constant internal referralCode = 3333;
/**
* @dev Return Wrapped ETH address
*/
address constant internal stEthAddr = 0xDFe66B14D37C77F4E9b180cEb433d1b164f0281D;
/**
* @dev Get total debt balance & fee for an asset
* @param token token address of the debt.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param rateMode Borrow rate mode (Stable = 1, Variable = 2)
*/
function getRepayBalance(address token, uint rateMode) internal view returns (uint) {
(, uint stableDebt, uint variableDebt, , , , , , ) = sturdyData.getUserReserveData(token, address(this));
return rateMode == 1 ? stableDebt : variableDebt;
}
/**
* @dev Get OnBehalfOf user's total debt balance & fee for an asset
* @param token token address of the debt.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param rateMode Borrow rate mode (Stable = 1, Variable = 2)
*/
function getOnBehalfOfRepayBalance(address token, uint256 rateMode, address onBehalfOf)
internal
view
returns (uint256)
{
(, uint256 stableDebt, uint256 variableDebt, , , , , , ) = sturdyData
.getUserReserveData(token, onBehalfOf);
return rateMode == 1 ? stableDebt : variableDebt;
}
}

View File

@ -0,0 +1,82 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
interface SturdyAddressesProviderInterface {
function getLendingPool() external view returns (address);
function getAddress(bytes32 id) external view returns (address);
}
interface SturdyCollateralAdapterInterface {
function getAcceptableVault(address _externalAsset)
external
view
returns (address);
}
interface SturdyLendingPoolInterface {
function deposit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
function withdraw(
address asset,
uint256 amount,
address to
) external returns (uint256);
function borrow(
address asset,
uint256 amount,
uint256 interestRateMode,
uint16 referralCode,
address onBehalfOf
) external;
function repay(
address asset,
uint256 amount,
uint256 rateMode,
address onBehalfOf
) external returns (uint256);
}
interface SturdyVaultInterface {
function depositCollateralFrom(address asset, uint256 amount, address onBehalfOf) external payable;
function withdrawCollateral(
address asset,
uint256 amount,
uint256 slippage,
address to
) external;
}
interface SturdyDataProviderInterface {
function getReserveTokensAddresses(address _asset)
external
view
returns (
address aTokenAddress,
address stableDebtTokenAddress,
address variableDebtTokenAddress
);
function getUserReserveData(address _asset, address _user)
external
view
returns (
uint256 currentATokenBalance,
uint256 currentStableDebt,
uint256 currentVariableDebt,
uint256 principalStableDebt,
uint256 scaledVariableDebt,
uint256 stableBorrowRate,
uint256 liquidityRate,
uint40 stableRateLastUpdated,
bool usageAsCollateralEnabled
);
}

View File

@ -0,0 +1,298 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
/**
* @title Sturdy
* @dev Lending & Borrowing.
*/
import { TokenInterface } from "../../common/interfaces.sol";
import { Stores } from "../../common/stores.sol";
import { Helpers } from "./helpers.sol";
import { Events } from "./events.sol";
import { SturdyLendingPoolInterface, SturdyCollateralAdapterInterface, SturdyVaultInterface } from "./interface.sol";
import "hardhat/console.sol";
abstract contract SturdyResolver is Events, Helpers {
/**
* @dev Deposit Stable Coins
* @notice Deposit a token to Sturdy for farming.
* @param token The address of the token to deposit.
* @param amt The amount of the token to deposit. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens deposited.
*/
function deposit(
address token,
uint256 amt,
uint256 getId,
uint256 setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 _amt = getUint(getId, amt);
SturdyLendingPoolInterface sturdy = SturdyLendingPoolInterface(sturdyAddressesProvider.getLendingPool());
TokenInterface tokenContract = TokenInterface(token);
_amt = _amt == uint256(-1)
? tokenContract.balanceOf(address(this))
: _amt;
approve(tokenContract, address(sturdy), _amt);
sturdy.deposit(token, _amt, address(this), referralCode);
setUint(setId, _amt);
_eventName = "LogDeposit(address,uint256,uint256,uint256)";
_eventParam = abi.encode(token, _amt, getId, setId);
}
/**
* @dev Withdraw ETH/ERC20_Token.
* @notice Withdraw deposited token from Sturdy v2
* @param token The address of the token to withdraw.
* @param amt The amount of the token to withdraw. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens withdrawn.
*/
function withdraw(
address token,
uint256 amt,
uint256 getId,
uint256 setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 _amt = getUint(getId, amt);
SturdyLendingPoolInterface sturdy = SturdyLendingPoolInterface(sturdyAddressesProvider.getLendingPool());
TokenInterface tokenContract = TokenInterface(token);
sturdy.withdraw(token, _amt, address(this));
setUint(setId, _amt);
_eventName = "LogWithdraw(address,uint256,uint256,uint256)";
_eventParam = abi.encode(token, _amt, getId, setId);
}
/**
* @dev Borrow ETH/ERC20_Token.
* @notice Borrow a token using Sturdy v2
* @param token The address of the token to borrow.
* @param amt The amount of the token to borrow.
* @param rateMode The type of borrow debt. (For Stable: 1, Variable: 2)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens borrowed.
*/
function borrow(
address token,
uint256 amt,
uint256 rateMode,
uint256 getId,
uint256 setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 _amt = getUint(getId, amt);
SturdyLendingPoolInterface sturdy = SturdyLendingPoolInterface(sturdyAddressesProvider.getLendingPool());
sturdy.borrow(token, _amt, rateMode, referralCode, address(this));
setUint(setId, _amt);
_eventName = "LogBorrow(address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(token, _amt, rateMode, getId, setId);
}
/**
* @dev Repay borrowed ETH/ERC20_Token.
* @notice Repay debt owed.
* @param token The address of the token to payback.
* @param amt The amount of the token to payback. (For max: `uint256(-1)`)
* @param rateMode The type of debt paying back. (For Stable: 1, Variable: 2)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens paid back.
*/
function repay(
address token,
uint256 amt,
uint256 rateMode,
uint256 getId,
uint256 setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 _amt = getUint(getId, amt);
SturdyLendingPoolInterface sturdy = SturdyLendingPoolInterface(sturdyAddressesProvider.getLendingPool());
TokenInterface tokenContract = TokenInterface(token);
if (_amt == uint256(-1)) {
uint256 _amtDSA = tokenContract.balanceOf(address(this));
uint256 _amtDebt = getRepayBalance(token, rateMode);
_amt = _amtDSA <= _amtDebt ? _amtDSA : _amtDebt;
}
approve(tokenContract, address(sturdy), _amt);
sturdy.repay(token, _amt, rateMode, address(this));
setUint(setId, _amt);
_eventName = "LogRepay(address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(token, _amt, rateMode, getId, setId);
}
/**
* @dev Repay borrowed ETH/ERC20_Token on behalf of a user.
* @notice Repay debt owed on behalf os a user.
* @param token The address of the token to payback.
* @param amt The amount of the token to payback. (For max: `uint256(-1)`)
* @param rateMode The type of debt paying back. (For Stable: 1, Variable: 2)
* @param onBehalfOf Address of user who's debt to repay.
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens paid back.
*/
function repayOnBehalfOf(
address token,
uint256 amt,
uint256 rateMode,
address onBehalfOf,
uint256 getId,
uint256 setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 _amt = getUint(getId, amt);
SturdyLendingPoolInterface sturdy = SturdyLendingPoolInterface(sturdyAddressesProvider.getLendingPool());
TokenInterface tokenContract = TokenInterface(token);
if (_amt == uint256(-1)) {
uint256 _amtDSA = tokenContract.balanceOf(address(this));
uint256 _amtDebt = getOnBehalfOfRepayBalance(
token,
rateMode,
onBehalfOf
);
_amt = _amtDSA <= _amtDebt ? _amtDSA : _amtDebt;
}
approve(tokenContract, address(sturdy), _amt);
sturdy.repay(token, _amt, rateMode, onBehalfOf);
setUint(setId, _amt);
_eventName = "LogRepayOnBehalfOf(address,uint256,uint256,address,uint256,uint256)";
_eventParam = abi.encode(
token,
_amt,
rateMode,
onBehalfOf,
getId,
setId
);
}
/**
* @dev Deposit Collateral
* @notice Deposit a token to Sturdy for lending.
* @param token The address of the token to deposit.
* @param amt The amount of the token to deposit. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens deposited.
*/
function depositCollateral(
address token,
uint256 amt,
uint256 getId,
uint256 setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 _amt = getUint(getId, amt);
SturdyCollateralAdapterInterface sturdyCollateralAdapter = SturdyCollateralAdapterInterface(sturdyAddressesProvider.getAddress('COLLATERAL_ADAPTER'));
address vault = token == stEthAddr ? sturdyLidoVault : sturdyCollateralAdapter.getAcceptableVault(token);
require(vault != address(0), "Collateral is not supported");
SturdyVaultInterface sturdyVault = SturdyVaultInterface(vault);
TokenInterface tokenContract = TokenInterface(token);
_amt = _amt == uint256(-1)
? tokenContract.balanceOf(address(this))
: _amt;
approve(tokenContract, address(sturdyVault), _amt);
sturdyVault.depositCollateralFrom(token, _amt, address(this));
setUint(setId, _amt);
_eventName = "LogDepositCollateral(address,uint256,uint256,uint256)";
_eventParam = abi.encode(token, _amt, getId, setId);
}
/**
* @dev Withdraw ETH/ERC20_Token.
* @notice Withdraw deposited collateral from Sturdy
* @param token The address of the token to withdraw.
* @param amt The amount of the token to withdraw. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens withdrawn.
*/
function withdrawCollateral(
address token,
uint256 amt,
uint256 slippage,
uint256 getId,
uint256 setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 _amt = getUint(getId, amt);
SturdyCollateralAdapterInterface sturdyCollateralAdapter = SturdyCollateralAdapterInterface(sturdyAddressesProvider.getAddress('COLLATERAL_ADAPTER'));
address vault = token == stEthAddr ? sturdyLidoVault : sturdyCollateralAdapter.getAcceptableVault(token);
require(vault != address(0), "Collateral is not supported");
SturdyVaultInterface sturdyVault = SturdyVaultInterface(vault);
sturdyVault.withdrawCollateral(token, _amt, slippage, address(this));
setUint(setId, _amt);
_eventName = "LogWithdrawCollateral(address,uint256,uint256,uint256)";
_eventParam = abi.encode(token, _amt, getId, setId);
}
}
contract ConnectV2Sturdy is SturdyResolver {
string public constant name = "Sturdy-v1";
}

View File

@ -0,0 +1,205 @@
import { expect } from "chai";
import hre from "hardhat";
import { abis } from "../../../scripts/constant/abis";
import { addresses } from "../../../scripts/tests/mainnet/addresses";
import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector";
import { getMasterSigner } from "../../../scripts/tests/getMasterSigner";
import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2";
import { ConnectV2Sturdy, ConnectV2Sturdy__factory } from "../../../typechain";
import { parseEther } from "@ethersproject/units";
import { encodeSpells } from "../../../scripts/tests/encodeSpells";
import { tokens } from "../../../scripts/tests/mainnet/tokens";
import { constants } from "../../../scripts/constant/constant";
import { addLiquidity } from "../../../scripts/tests/addLiquidity";
import {impersonateAccounts} from "../../../scripts/tests/impersonate";
const { ethers } = hre;
import type { Signer, Contract } from "ethers";
describe("Sturdy", function () {
const connectorName = "STURDY-TEST-A";
let connector: any;
let wallet0: Signer, wallet1:Signer;
let dsaWallet0: any;
let instaConnectorsV2: Contract;
let masterSigner: Signer;
const stETH = '0xDFe66B14D37C77F4E9b180cEb433d1b164f0281D';
const crvFRAX = '0x3175Df0976dFA876431C2E9eE6Bc45b65d3473CC';
const dsaDeposit = async (tokenName: string, address: string, amt: string) => {
const abi = ["function transfer(address to, uint value)"];
const tokenMapping = {
dai: {
impersonateSigner: '0x5d38b4e4783e34e2301a2a36c39a03c45798c4dd',
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
},
usdc: {
impersonateSigner: '0x72a53cdbbcc1b9efa39c834a540550e23463aacb',
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
},
usdt: {
impersonateSigner: '0x5a52e96bacdabb82fd05763e25335261b270efcb',
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
},
stETH: {
impersonateSigner: '0x3c79d7e8a5d6b49336f0fd2f3adde745954bc9f7',
address: stETH,
},
crvFRAX: {
impersonateSigner: '0xfd1d36995d76c0f75bbe4637c84c06e4a68bbb3a',
address: crvFRAX,
}
};
const token = tokenMapping[tokenName];
const [impersonatedSigner] = await impersonateAccounts([token.impersonateSigner]);
const contract = new ethers.Contract(token.address, abi, impersonatedSigner);
const tx = await contract.transfer(address, amt);
await tx.wait();
};
before(async () => {
await hre.network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
// @ts-ignore
jsonRpcUrl: hre.config.networks.hardhat.forking.url,
blockNumber: 15454171,
},
},
],
});
[wallet0, wallet1] = await ethers.getSigners();
masterSigner = await getMasterSigner();
instaConnectorsV2 = await ethers.getContractAt(
abis.core.connectorsV2,
addresses.core.connectorsV2
);
connector = await deployAndEnableConnector({
connectorName,
contractArtifact: ConnectV2Sturdy__factory,
signer: masterSigner,
connectors: instaConnectorsV2,
});
console.log("Connector address", connector.address);
});
it("should have contracts deployed", async () => {
expect(!!instaConnectorsV2.address).to.be.true;
expect(!!connector.address).to.be.true;
expect(!!(await masterSigner.getAddress())).to.be.true;
});
describe("DSA wallet setup", function () {
it("Should build DSA v2", async function () {
dsaWallet0 = await buildDSAv2(wallet0.getAddress());
expect(!!dsaWallet0.address).to.be.true;
});
it("Deposit ETH into DSA wallet", async function () {
await wallet0.sendTransaction({
to: dsaWallet0.address,
value: parseEther("10"),
});
expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(
parseEther("10")
);
});
});
describe("Main", function () {
it("should deposit DAI in Sturdy", async function () {
const amt = parseEther("100"); // 100 dai
await dsaDeposit("dai", dsaWallet0.address, amt.toString());
const spells = [
{
connector: connectorName,
method: "deposit",
args: [tokens.dai.address, amt, 0, 0],
},
];
const tx = await dsaWallet0
.connect(wallet0)
.cast(...encodeSpells(spells), wallet1.getAddress());
await tx.wait();
});
// it("should provide stETH in Sturdy", async function () {
// const amt = parseEther("100");
// await dsaDeposit("stETH", dsaWallet0.address, amt.toString());
// const spells = [
// {
// connector: connectorName,
// method: "depositCollateral",
// args: [stETH, amt, 0, 0],
// },
// ];
// const tx = await dsaWallet0
// .connect(wallet0)
// .cast(...encodeSpells(spells), wallet1.getAddress());
// await tx.wait();
// });
it("should provide crvFRAX in Sturdy", async function () {
const amt = parseEther("200");
await dsaDeposit("crvFRAX", dsaWallet0.address, amt.toString());
const spells = [
{
connector: connectorName,
method: "depositCollateral",
args: [crvFRAX, amt, 0, 0],
},
];
const tx = await dsaWallet0
.connect(wallet0)
.cast(...encodeSpells(spells), wallet1.getAddress());
await tx.wait();
});
it("Should borrow and repay DAI from Sturdy", async function () {
const amt = parseEther("100"); // 100 DAI
const setId = "83478237";
const spells = [
{
connector: connectorName,
method: "borrow",
args: [tokens.dai.address, amt, 2, 0, setId],
},
{
connector: connectorName,
method: "repay",
args: [tokens.dai.address, amt, 2, setId, 0],
},
];
const tx = await dsaWallet0
.connect(wallet0)
.cast(...encodeSpells(spells), wallet1.getAddress());
await tx.wait();
});
it("Should withdraw crvFRAX from Sturdy", async function () {
const slippage = '100'; // 1%
const spells = [
{
connector: connectorName,
method: "withdrawCollateral",
args: [crvFRAX, constants.max_value, slippage, 0, 0],
},
];
const tx = await dsaWallet0
.connect(wallet0)
.cast(...encodeSpells(spells), wallet1.getAddress());
await tx.wait();
});
});
});