Merge branch 'main' into uniswapv3-router

This commit is contained in:
bhavik-m 2022-03-07 16:35:25 +05:30
commit 3628a291b1
31 changed files with 3087 additions and 15133 deletions

View File

@ -6,7 +6,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
permissions:
permissions:
contents: write
strategy:
matrix:
@ -32,12 +32,9 @@ jobs:
- name: Run status checks
id: status_check
run: |
mkdir -p pr
# Run status checks, Remove ANSI colors from the text
output=$(node ./status-checks | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g')
# Escape newlines so _all_ the output is included in the set-output
output="${output//'%'/'%25'}"
output="${output//$'\n'/'%0A'}"
output="${output//$'\r'/'%0D'}"
output=$(npx ts-node ./status-checks/index.ts | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g')
cat <<< "$output" > "./pr/status-check-output"
echo ${{ github.event.number }} > ./pr/number
- uses: actions/upload-artifact@v2

View File

@ -0,0 +1,12 @@
pragma solidity ^0.7.0;
contract Events {
event LogSwap(
address indexed buyToken,
address indexed sellToken,
uint256 buyAmt,
uint256 sellAmt,
uint256 getId,
uint256 setId
);
}

View File

@ -0,0 +1,80 @@
pragma solidity ^0.7.0;
import { TokenInterface } from "../../../common/interfaces.sol";
import { DSMath } from "../../../common/math.sol";
import { Basic } from "../../../common/basic.sol";
import { SwapData } from "./interface.sol";
abstract contract Helpers is DSMath, Basic {
/**
* @dev UniswapV3 Swap Router Address
*/
address internal constant V3_SWAP_ROUTER_ADDRESS =
0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45;
/**
* @dev UniswapV3 swapHelper
* @param swapData - Struct defined in interfaces.sol
*/
function _swapHelper(SwapData memory swapData)
internal
returns (uint256 buyAmt)
{
(uint256 _buyDec, uint256 _sellDec) = getTokensDec(
swapData.buyToken,
swapData.sellToken
);
uint256 _sellAmt18 = convertTo18(_sellDec, swapData._sellAmt);
uint256 _slippageAmt = convert18ToDec(
_buyDec,
wmul(swapData.unitAmt, _sellAmt18)
);
uint256 initalBal = getTokenBal(swapData.buyToken);
// solium-disable-next-line security/no-call-value
(bool success, ) = V3_SWAP_ROUTER_ADDRESS.call(swapData.callData);
if (!success) revert("uniswapV3-swap-failed");
uint256 finalBal = getTokenBal(swapData.buyToken);
buyAmt = sub(finalBal, initalBal);
require(_slippageAmt <= buyAmt, "Too much slippage");
}
/**
* @dev Gets the swapping data from auto router sdk
* @param swapData Struct with multiple swap data defined in interfaces.sol
* @param setId Set token amount at this ID in `InstaMemory` Contract.
*/
function _swap(SwapData memory swapData, uint256 setId)
internal
returns (SwapData memory)
{
bool isEthSellToken = address(swapData.sellToken) == ethAddr;
bool isEthBuyToken = address(swapData.buyToken) == ethAddr;
swapData.sellToken = isEthSellToken
? TokenInterface(wethAddr)
: swapData.sellToken;
swapData.buyToken = isEthBuyToken
? TokenInterface(wethAddr)
: swapData.buyToken;
convertEthToWeth(isEthSellToken, swapData.sellToken, swapData._sellAmt);
approve(
TokenInterface(swapData.sellToken),
V3_SWAP_ROUTER_ADDRESS,
swapData._sellAmt
);
swapData._buyAmt = _swapHelper(swapData);
convertWethToEth(isEthBuyToken, swapData.buyToken, swapData._buyAmt);
setUint(setId, swapData._buyAmt);
return swapData;
}
}

View File

@ -0,0 +1,12 @@
pragma solidity ^0.7.0;
import { TokenInterface } from "../../../common/interfaces.sol";
struct SwapData {
TokenInterface sellToken;
TokenInterface buyToken;
uint256 _sellAmt;
uint256 _buyAmt;
uint256 unitAmt;
bytes callData;
}

View File

@ -0,0 +1,64 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title UniswapV3_autoRouter.
* @dev DEX.
*/
// import files from common directory
import { TokenInterface, MemoryInterface } from "../../../common/interfaces.sol";
import { Stores } from "../../../common/stores.sol";
import { SwapData } from "./interface.sol";
import { Helpers } from "./helpers.sol";
import { Events } from "./events.sol";
abstract contract AutoRouter is Helpers, Events {
/**
* @dev Sell ETH/ERC20_Token using uniswap v3 auto router.
* @notice Swap tokens from getting an optimized trade routes
* @param buyAddr The address of the token to buy.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param sellAddr The address of the token to sell.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param sellAmt The amount of the token to sell.
* @param unitAmt The amount of buyAmt/sellAmt with slippage.
* @param callData Data from Uniswap V3 auto router SDK.
* @param setId ID stores the amount of token brought.
*/
function sell(
address buyAddr,
address sellAddr,
uint256 sellAmt,
uint256 unitAmt,
bytes calldata callData,
uint256 setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
SwapData memory swapData = SwapData({
buyToken: TokenInterface(buyAddr),
sellToken: TokenInterface(sellAddr),
unitAmt: unitAmt,
callData: callData,
_sellAmt: sellAmt,
_buyAmt: 0
});
swapData = _swap(swapData, setId);
_eventName = "LogSwap(address,address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(
buyAddr,
sellAddr,
swapData._buyAmt,
swapData._sellAmt,
0,
setId
);
}
}
contract ConnectV2UniswapV3AutoRouterArbitrum is AutoRouter {
string public name = "UniswapV3-Auto-Router-v1";
}

View File

@ -0,0 +1,18 @@
pragma solidity ^0.7.6;
contract Events {
event LogDeposit(address token, uint256 amount, address path, bool stake);
event LogWithdraw(
address token,
uint256 amount,
address path,
bool unstake
);
event LogClaimRewards(address token, uint256 amount);
event LogSwap(
address from,
address to,
uint256 amountIn,
uint256 amountOut
);
}

View File

@ -0,0 +1,109 @@
pragma solidity ^0.7.6;
import { DSMath } from "../../common/math.sol";
import { Basic } from "../../common/basic.sol";
import { TokenInterface } from "../../common/interfaces.sol";
import { ISavingsContractV2, IBoostedSavingsVault } from "./interface.sol";
abstract contract Helpers is DSMath, Basic {
address internal constant mUsdToken =
0xe2f2a5C287993345a840Db3B0845fbC70f5935a5;
address internal constant imUsdToken =
0x30647a72Dc82d7Fbb1123EA74716aB8A317Eac19;
address internal constant imUsdVault =
0x78BefCa7de27d07DC6e71da295Cc2946681A6c7B;
/***************************************
Internal
****************************************/
/**
* @dev Deposit to Save from any asset
* @notice Called internally from deposit functions
* @param _token Address of token to deposit
* @param _amount Amount of token to deposit
* @param _path Path to mint mUSD (only needed for Feeder Pool)
* @param _stake stake token in Vault?
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function _deposit(
address _token,
uint256 _amount,
address _path,
bool _stake
) internal returns (string memory _eventName, bytes memory _eventParam) {
// 1. Deposit mUSD to Save
approve(TokenInterface(mUsdToken), imUsdToken, _amount);
uint256 credits = ISavingsContractV2(imUsdToken).depositSavings(
_amount
);
if (_stake) {
// 2. Stake imUSD to Vault
approve(TokenInterface(imUsdToken), imUsdVault, credits);
IBoostedSavingsVault(imUsdVault).stake(credits);
}
// 3. Log Events
_eventName = "LogDeposit(address,uint256,address,bool)";
_eventParam = abi.encode(_token, _amount, _path, _stake);
}
/**
* @dev Withdraws from Save
* @notice Withdraws token supported by mStable from Save
* @param _credits Credits to withdraw
* @param _unstake unstake from Vault?
* @return amountWithdrawn Amount withdrawn in mUSD
*/
function _withdraw(uint256 _credits, bool _unstake)
internal
returns (uint256 amountWithdrawn)
{
uint256 credits;
// 1. Withdraw from Vault
if (_unstake) {
credits = _credits == uint256(-1)
? TokenInterface(imUsdVault).balanceOf(address(this))
: _credits;
IBoostedSavingsVault(imUsdVault).withdraw(credits);
}
// 2. Withdraw from Save
credits = _credits == uint256(-1)
? TokenInterface(imUsdToken).balanceOf(address(this))
: _credits;
approve(TokenInterface(imUsdToken), imUsdVault, _credits);
amountWithdrawn = ISavingsContractV2(imUsdToken).redeemCredits(credits);
}
/**
* @dev Returns the reward tokens
* @notice Gets the reward tokens from the vault contract
* @return rewardToken Address of reward token
*/
function _getRewardTokens() internal view returns (address rewardToken) {
rewardToken = address(
IBoostedSavingsVault(imUsdVault).getRewardToken()
);
}
/**
* @dev Returns the internal balances of the rewardToken and platformToken
* @notice Gets current balances of rewardToken and platformToken, used for calculating rewards accrued
* @param _rewardToken Address of reward token
* @return a Amount of reward token
*/
function _getRewardInternalBal(address _rewardToken)
internal
view
returns (uint256 a)
{
a = TokenInterface(_rewardToken).balanceOf(address(this));
}
}

View File

@ -0,0 +1,379 @@
pragma solidity ^0.7.6;
interface IMasset {
function mint(
address _input,
uint256 _inputQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 mintOutput);
function mintMulti(
address[] calldata _inputs,
uint256[] calldata _inputQuantities,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 mintOutput);
function getMintOutput(address _input, uint256 _inputQuantity)
external
view
returns (uint256 mintOutput);
function getMintMultiOutput(
address[] calldata _inputs,
uint256[] calldata _inputQuantities
) external view returns (uint256 mintOutput);
function swap(
address _input,
address _output,
uint256 _inputQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 swapOutput);
function getSwapOutput(
address _input,
address _output,
uint256 _inputQuantity
) external view returns (uint256 swapOutput);
function redeem(
address _output,
uint256 _mAssetQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 outputQuantity);
function redeemMasset(
uint256 _mAssetQuantity,
uint256[] calldata _minOutputQuantities,
address _recipient
) external returns (uint256[] memory outputQuantities);
function redeemExactBassets(
address[] calldata _outputs,
uint256[] calldata _outputQuantities,
uint256 _maxMassetQuantity,
address _recipient
) external returns (uint256 mAssetRedeemed);
function getRedeemOutput(address _output, uint256 _mAssetQuantity)
external
view
returns (uint256 bAssetOutput);
function getRedeemExactBassetsOutput(
address[] calldata _outputs,
uint256[] calldata _outputQuantities
) external view returns (uint256 mAssetAmount);
// Views
// This return an index, could be used to check if it's part of the basket
function bAssetIndexes(address) external view returns (uint8);
function getPrice() external view returns (uint256 price, uint256 k);
}
interface ISavingsContractV2 {
function depositInterest(uint256 _amount) external; // V1 & V2
function depositSavings(uint256 _amount)
external
returns (uint256 creditsIssued); // V1 & V2
function depositSavings(uint256 _amount, address _beneficiary)
external
returns (uint256 creditsIssued); // V2
function redeemCredits(uint256 _amount)
external
returns (uint256 underlyingReturned); // V2
function redeemUnderlying(uint256 _amount)
external
returns (uint256 creditsBurned); // V2
function exchangeRate() external view returns (uint256); // V1 & V2
function balanceOfUnderlying(address _user)
external
view
returns (uint256 balance); // V2
function underlyingToCredits(uint256 _credits)
external
view
returns (uint256 underlying); // V2
function creditsToUnderlying(uint256 _underlying)
external
view
returns (uint256 credits); // V2
}
interface IBoostedSavingsVault {
/**
* @dev Stakes a given amount of the StakingToken for the sender
* @param _amount Units of StakingToken
*/
function stake(uint256 _amount) external;
/**
* @dev Stakes a given amount of the StakingToken for a given beneficiary
* @param _beneficiary Staked tokens are credited to this address
* @param _amount Units of StakingToken
*/
function stake(address _beneficiary, uint256 _amount) external;
/**
* @dev Withdraws stake from pool and claims any unlocked rewards.
* Note, this function is costly - the args for _claimRewards
* should be determined off chain and then passed to other fn
*/
function exit() external;
/**
* @dev Withdraws stake from pool and claims any unlocked rewards.
* @param _first Index of the first array element to claim
* @param _last Index of the last array element to claim
*/
function exit(uint256 _first, uint256 _last) external;
/**
* @dev Withdraws given stake amount from the pool
* @param _amount Units of the staked token to withdraw
*/
function withdraw(uint256 _amount) external;
/**
* @dev Claims only the tokens that have been immediately unlocked, not including
* those that are in the lockers.
*/
function claimReward() external;
/**
* @dev Claims all unlocked rewards for sender.
* Note, this function is costly - the args for _claimRewards
* should be determined off chain and then passed to other fn
*/
function claimRewards() external;
/**
* @dev Claims all unlocked rewards for sender. Both immediately unlocked
* rewards and also locked rewards past their time lock.
* @param _first Index of the first array element to claim
* @param _last Index of the last array element to claim
*/
function claimRewards(uint256 _first, uint256 _last) external;
/**
* @dev Pokes a given account to reset the boost
*/
function pokeBoost(address _account) external;
/**
* @dev Gets the RewardsToken
*/
function getRewardToken() external view returns (IERC20);
/**
* @dev Gets the last applicable timestamp for this reward period
*/
function lastTimeRewardApplicable() external view returns (uint256);
/**
* @dev Calculates the amount of unclaimed rewards per token since last update,
* and sums with stored to give the new cumulative reward per token
* @return 'Reward' per staked token
*/
function rewardPerToken() external view returns (uint256);
/**
* @dev Returned the units of IMMEDIATELY claimable rewards a user has to receive. Note - this
* does NOT include the majority of rewards which will be locked up.
* @param _account User address
* @return Total reward amount earned
*/
function earned(address _account) external view returns (uint256);
/**
* @dev Calculates all unclaimed reward data, finding both immediately unlocked rewards
* and those that have passed their time lock.
* @param _account User address
* @return amount Total units of unclaimed rewards
* @return first Index of the first userReward that has unlocked
* @return last Index of the last userReward that has unlocked
*/
function unclaimedRewards(address _account)
external
view
returns (
uint256 amount,
uint256 first,
uint256 last
);
}
interface IFeederPool {
// Mint
function mint(
address _input,
uint256 _inputQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 mintOutput);
function mintMulti(
address[] calldata _inputs,
uint256[] calldata _inputQuantities,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 mintOutput);
function getMintOutput(address _input, uint256 _inputQuantity)
external
view
returns (uint256 mintOutput);
function getMintMultiOutput(
address[] calldata _inputs,
uint256[] calldata _inputQuantities
) external view returns (uint256 mintOutput);
// Swaps
function swap(
address _input,
address _output,
uint256 _inputQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 swapOutput);
function getSwapOutput(
address _input,
address _output,
uint256 _inputQuantity
) external view returns (uint256 swapOutput);
// Redemption
function redeem(
address _output,
uint256 _fpTokenQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 outputQuantity);
function redeemProportionately(
uint256 _fpTokenQuantity,
uint256[] calldata _minOutputQuantities,
address _recipient
) external returns (uint256[] memory outputQuantities);
function redeemExactBassets(
address[] calldata _outputs,
uint256[] calldata _outputQuantities,
uint256 _maxMassetQuantity,
address _recipient
) external returns (uint256 mAssetRedeemed);
function getRedeemOutput(address _output, uint256 _fpTokenQuantity)
external
view
returns (uint256 bAssetOutput);
function getRedeemExactBassetsOutput(
address[] calldata _outputs,
uint256[] calldata _outputQuantities
) external view returns (uint256 mAssetAmount);
// Views
function mAsset() external view returns (address);
function getPrice() external view returns (uint256 price, uint256 k);
}
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount)
external
returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender)
external
view
returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}

View File

@ -0,0 +1,383 @@
pragma solidity ^0.7.6;
/**
* @title mStable SAVE.
* @dev Depositing and withdrawing directly to Save
*/
import { Helpers } from "./helpers.sol";
import { Events } from "./events.sol";
import { TokenInterface } from "../../common/interfaces.sol";
import { IMasset, IBoostedSavingsVault, IFeederPool } from "./interface.sol";
abstract contract mStableResolver is Events, Helpers {
/***************************************
CORE
****************************************/
/**
* @dev Deposit to Save via mUSD or bAsset
* @notice Deposits token supported by mStable to Save
* @param _token Address of token to deposit
* @param _amount Amount of token to deposit
* @param _minOut Minimum amount of token to mint/deposit, equal to _amount if mUSD
* @param _stake stake token in Vault?
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens deposited
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function deposit(
address _token,
uint256 _amount,
uint256 _minOut,
bool _stake,
uint256 _setId,
uint256 _getId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 amount = getUint(_getId, _amount);
amount = amount == uint256(-1)
? TokenInterface(_token).balanceOf(address(this))
: amount;
uint256 mintedAmount;
address path;
// Check if needs to be minted first
if (IMasset(mUsdToken).bAssetIndexes(_token) != 0) {
// mint first
approve(TokenInterface(_token), mUsdToken, amount);
mintedAmount = IMasset(mUsdToken).mint(
_token,
amount,
_minOut,
address(this)
);
path = mUsdToken;
} else {
require(amount >= _minOut, "mintedAmount < _minOut");
mintedAmount = amount;
path = imUsdToken;
}
setUint(_setId, mintedAmount);
(_eventName, _eventParam) = _deposit(
_token,
mintedAmount,
path,
_stake
);
}
/**
* @dev Deposit to Save via feeder pool
* @notice Deposits token, requires _minOut for minting and _path
* @param _token Address of token to deposit
* @param _amount Amount of token to deposit
* @param _minOut Minimum amount of token to mint
* @param _path Feeder Pool address for _token
* @param _stake stake token in Vault?
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens deposited
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function depositViaSwap(
address _token,
uint256 _amount,
uint256 _minOut,
address _path,
bool _stake,
uint256 _setId,
uint256 _getId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
require(_path != address(0), "Path must be set");
require(
IMasset(mUsdToken).bAssetIndexes(_token) == 0,
"Token is bAsset"
);
uint256 amount = getUint(_getId, _amount);
amount = amount == uint256(-1)
? TokenInterface(_token).balanceOf(address(this))
: amount;
approve(TokenInterface(_token), _path, amount);
uint256 mintedAmount = IFeederPool(_path).swap(
_token,
mUsdToken,
amount,
_minOut,
address(this)
);
setUint(_setId, mintedAmount);
(_eventName, _eventParam) = _deposit(
_token,
mintedAmount,
_path,
_stake
);
}
/**
* @dev Withdraw from Save to mUSD or bAsset
* @notice Withdraws from Save Vault to mUSD
* @param _token Address of token to withdraw
* @param _credits Credits to withdraw
* @param _minOut Minimum amount of token to withdraw
* @param _unstake from the Vault first?
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens withdrawn
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function withdraw(
address _token,
uint256 _credits,
uint256 _minOut,
bool _unstake,
uint256 _getId,
uint256 _setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 credits = getUint(_getId, _credits);
uint256 amountWithdrawn = _withdraw(credits, _unstake);
// Check if needs to be redeemed
if (IMasset(mUsdToken).bAssetIndexes(_token) != 0) {
amountWithdrawn = IMasset(mUsdToken).redeem(
_token,
amountWithdrawn,
_minOut,
address(this)
);
} else {
require(amountWithdrawn >= _minOut, "amountWithdrawn < _minOut");
}
setUint(_setId, amountWithdrawn);
_eventName = "LogWithdraw(address,uint256,address,bool)";
_eventParam = abi.encode(
mUsdToken,
amountWithdrawn,
imUsdToken,
_unstake
);
}
/**
* @dev Withdraw from Save via Feeder Pool
* @notice Withdraws from Save Vault to asset via Feeder Pool
* @param _token bAsset to withdraw to
* @param _credits Credits to withdraw
* @param _minOut Minimum amount of token to mint
* @param _path Feeder Pool address for _token
* @param _unstake from the Vault first?
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens withdrawn
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function withdrawViaSwap(
address _token,
uint256 _credits,
uint256 _minOut,
address _path,
bool _unstake,
uint256 _getId,
uint256 _setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
require(_path != address(0), "Path must be set");
require(
IMasset(mUsdToken).bAssetIndexes(_token) == 0,
"Token is bAsset"
);
uint256 credits = getUint(_getId, _credits);
uint256 amountWithdrawn = _withdraw(credits, _unstake);
approve(TokenInterface(mUsdToken), _path, amountWithdrawn);
uint256 amountRedeemed = IFeederPool(_path).swap(
mUsdToken,
_token,
amountWithdrawn,
_minOut,
address(this)
);
setUint(_setId, amountRedeemed);
_eventName = "LogWithdraw(address,uint256,address,bool)";
_eventParam = abi.encode(_token, amountRedeemed, _path, _unstake);
}
/**
* @dev Claims Rewards
* @notice Claims accrued rewards from the Vault
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens withdrawn
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function claimRewards(uint256 _getId, uint256 _setId)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
address rewardToken = _getRewardTokens();
uint256 rewardAmount = _getRewardInternalBal(rewardToken);
IBoostedSavingsVault(imUsdVault).claimReward();
uint256 rewardAmountUpdated = _getRewardInternalBal(rewardToken);
uint256 claimedRewardToken = sub(rewardAmountUpdated, rewardAmount);
setUint(_setId, claimedRewardToken);
_eventName = "LogClaimRewards(address,uint256)";
_eventParam = abi.encode(rewardToken, claimedRewardToken);
}
/**
* @dev Swap tokens
* @notice Swaps tokens via Masset basket
* @param _input Token address to swap from
* @param _output Token address to swap to
* @param _amount Amount of tokens to swap
* @param _minOut Minimum amount of token to mint
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens swapped
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function swap(
address _input,
address _output,
uint256 _amount,
uint256 _minOut,
uint256 _getId,
uint256 _setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 amount = getUint(_getId, _amount);
amount = amount == uint256(-1)
? TokenInterface(_input).balanceOf(address(this))
: amount;
approve(TokenInterface(_input), mUsdToken, amount);
uint256 amountSwapped;
// Check the assets and swap accordingly
if (_output == mUsdToken) {
// bAsset to mUSD => mint
amountSwapped = IMasset(mUsdToken).mint(
_input,
amount,
_minOut,
address(this)
);
} else if (_input == mUsdToken) {
// mUSD to bAsset => redeem
amountSwapped = IMasset(mUsdToken).redeem(
_output,
amount,
_minOut,
address(this)
);
} else {
// bAsset to another bAsset => swap
amountSwapped = IMasset(mUsdToken).swap(
_input,
_output,
amount,
_minOut,
address(this)
);
}
setUint(_setId, amountSwapped);
_eventName = "LogSwap(address,address,uint256,uint256)";
_eventParam = abi.encode(_input, _output, amount, amountSwapped);
}
/**
* @dev Swap tokens via Feeder Pool
* @notice Swaps tokens via Feeder Pool
* @param _input Token address to swap from
* @param _output Token address to swap to
* @param _amount Amount of tokens to swap
* @param _minOut Minimum amount of token to mint
* @param _path Feeder Pool address to use
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens swapped
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function swapViaFeeder(
address _input,
address _output,
uint256 _amount,
uint256 _minOut,
address _path,
uint256 _getId,
uint256 _setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 amountSwapped;
uint256 amount = getUint(_getId, _amount);
amount = amount == uint256(-1)
? TokenInterface(_input).balanceOf(address(this))
: amount;
approve(TokenInterface(_input), _path, amount);
// swaps fAsset to mUSD via Feeder Pool
// swaps mUSD to fAsset via Feeder Pool
amountSwapped = IFeederPool(_path).swap(
_input,
_output,
amount,
_minOut,
address(this)
);
setUint(_setId, amountSwapped);
_eventName = "LogSwap(address,address,uint256,uint256)";
_eventParam = abi.encode(_input, _output, amount, amountSwapped);
}
}
contract ConnectV2mStable is mStableResolver {
string public constant name = "mStable-v1.0";
}

View File

@ -20,6 +20,7 @@ abstract contract SushipswapIncentiveResolver is Helpers, Events {
* @param amount amount of LP token
* @param getId ID to retrieve amount
* @param setId ID stores Pool ID
* @param data the metadata struct
*/
function deposit(
address token1,
@ -66,6 +67,7 @@ abstract contract SushipswapIncentiveResolver is Helpers, Events {
* @param amount amount of LP token
* @param getId ID to retrieve amount
* @param setId ID stores Pool ID
* @param data the metadata struct
*/
function withdraw(
address token1,
@ -104,6 +106,7 @@ abstract contract SushipswapIncentiveResolver is Helpers, Events {
* @param token1 token1 deposited of LP token
* @param token2 token2 deposited LP token
* @param setId ID stores Pool ID
* @param data the metadata struct
*/
function harvest(
address token1,
@ -143,6 +146,7 @@ abstract contract SushipswapIncentiveResolver is Helpers, Events {
* @param amount amount of LP token
* @param getId ID to retrieve amount
* @param setId ID stores Pool ID
* @param data the metadata struct
*/
function withdrawAndHarvest(
address token1,
@ -183,6 +187,7 @@ abstract contract SushipswapIncentiveResolver is Helpers, Events {
* @param token1 token1 deposited of LP token
* @param token2 token2 deposited LP token
* @param setId ID stores Pool ID
* @param data the metadata struct
*/
function emergencyWithdraw(
address token1,

View File

@ -0,0 +1,12 @@
pragma solidity ^0.7.0;
contract Events {
event LogSwap(
address indexed buyToken,
address indexed sellToken,
uint256 buyAmt,
uint256 sellAmt,
uint256 getId,
uint256 setId
);
}

View File

@ -0,0 +1,80 @@
pragma solidity ^0.7.0;
import { TokenInterface } from "../../../common/interfaces.sol";
import { DSMath } from "../../../common/math.sol";
import { Basic } from "../../../common/basic.sol";
import { SwapData } from "./interface.sol";
abstract contract Helpers is DSMath, Basic {
/**
* @dev UniswapV3 Swap Router Address
*/
address internal constant V3_SWAP_ROUTER_ADDRESS =
0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45;
/**
* @dev UniswapV3 swapHelper
* @param swapData - Struct defined in interfaces.sol
*/
function _swapHelper(SwapData memory swapData)
internal
returns (uint256 buyAmt)
{
(uint256 _buyDec, uint256 _sellDec) = getTokensDec(
swapData.buyToken,
swapData.sellToken
);
uint256 _sellAmt18 = convertTo18(_sellDec, swapData._sellAmt);
uint256 _slippageAmt = convert18ToDec(
_buyDec,
wmul(swapData.unitAmt, _sellAmt18)
);
uint256 initalBal = getTokenBal(swapData.buyToken);
// solium-disable-next-line security/no-call-value
(bool success, ) = V3_SWAP_ROUTER_ADDRESS.call(swapData.callData);
if (!success) revert("uniswapV3-swap-failed");
uint256 finalBal = getTokenBal(swapData.buyToken);
buyAmt = sub(finalBal, initalBal);
require(_slippageAmt <= buyAmt, "Too much slippage");
}
/**
* @dev Gets the swapping data from auto router sdk
* @param swapData Struct with multiple swap data defined in interfaces.sol
* @param setId Set token amount at this ID in `InstaMemory` Contract.
*/
function _swap(SwapData memory swapData, uint256 setId)
internal
returns (SwapData memory)
{
bool isEthSellToken = address(swapData.sellToken) == ethAddr;
bool isEthBuyToken = address(swapData.buyToken) == ethAddr;
swapData.sellToken = isEthSellToken
? TokenInterface(wethAddr)
: swapData.sellToken;
swapData.buyToken = isEthBuyToken
? TokenInterface(wethAddr)
: swapData.buyToken;
convertEthToWeth(isEthSellToken, swapData.sellToken, swapData._sellAmt);
approve(
TokenInterface(swapData.sellToken),
V3_SWAP_ROUTER_ADDRESS,
swapData._sellAmt
);
swapData._buyAmt = _swapHelper(swapData);
convertWethToEth(isEthBuyToken, swapData.buyToken, swapData._buyAmt);
setUint(setId, swapData._buyAmt);
return swapData;
}
}

View File

@ -0,0 +1,12 @@
pragma solidity ^0.7.0;
import { TokenInterface } from "../../../common/interfaces.sol";
struct SwapData {
TokenInterface sellToken;
TokenInterface buyToken;
uint256 _sellAmt;
uint256 _buyAmt;
uint256 unitAmt;
bytes callData;
}

View File

@ -0,0 +1,64 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title UniswapV3_autoRouter.
* @dev DEX.
*/
// import files from common directory
import { TokenInterface, MemoryInterface } from "../../../common/interfaces.sol";
import { Stores } from "../../../common/stores.sol";
import { SwapData } from "./interface.sol";
import { Helpers } from "./helpers.sol";
import { Events } from "./events.sol";
abstract contract AutoRouter is Helpers, Events {
/**
* @dev Sell ETH/ERC20_Token using uniswap v3 auto router.
* @notice Swap tokens from getting an optimized trade routes
* @param buyAddr The address of the token to buy.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param sellAddr The address of the token to sell.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param sellAmt The amount of the token to sell.
* @param unitAmt The amount of buyAmt/sellAmt with slippage.
* @param callData Data from Uniswap V3 auto router SDK.
* @param setId ID stores the amount of token brought.
*/
function sell(
address buyAddr,
address sellAddr,
uint256 sellAmt,
uint256 unitAmt,
bytes calldata callData,
uint256 setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
SwapData memory swapData = SwapData({
buyToken: TokenInterface(buyAddr),
sellToken: TokenInterface(sellAddr),
unitAmt: unitAmt,
callData: callData,
_sellAmt: sellAmt,
_buyAmt: 0
});
swapData = _swap(swapData, setId);
_eventName = "LogSwap(address,address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(
buyAddr,
sellAddr,
swapData._buyAmt,
swapData._sellAmt,
0,
setId
);
}
}
contract ConnectV2UniswapV3AutoRouterOptimism is AutoRouter {
string public name = "UniswapV3-Auto-Router-v1";
}

View File

@ -0,0 +1,23 @@
pragma solidity ^0.7.6;
contract Events {
event LogDeposit(address token, uint256 amount, address path, bool stake);
event LogWithdraw(
address token,
uint256 amount,
address path,
bool unstake
);
event LogClaimRewards(
address token,
uint256 amount,
address platformToken,
uint256 platformAmount
);
event LogSwap(
address from,
address to,
uint256 amountIn,
uint256 amountOut
);
}

View File

@ -0,0 +1,116 @@
pragma solidity ^0.7.6;
import { DSMath } from "../../common/math.sol";
import { Basic } from "../../common/basic.sol";
import { ISavingsContractV2, IStakingRewardsWithPlatformToken } from "./interface.sol";
import { TokenInterface } from "../../common/interfaces.sol";
abstract contract Helpers is DSMath, Basic {
address internal constant mUsdToken =
0xE840B73E5287865EEc17d250bFb1536704B43B21;
address internal constant imUsdToken =
0x5290Ad3d83476CA6A2b178Cd9727eE1EF72432af;
address internal constant imUsdVault =
0x32aBa856Dc5fFd5A56Bcd182b13380e5C855aa29;
/***************************************
Internal
****************************************/
/**
* @dev Deposit to Save from any asset
* @notice Called internally from deposit functions
* @param _token Address of token to deposit
* @param _amount Amount of token to deposit
* @param _path Path to mint mUSD (only needed for Feeder Pool)
* @param _stake stake token in Vault?
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function _deposit(
address _token,
uint256 _amount,
address _path,
bool _stake
) internal returns (string memory _eventName, bytes memory _eventParam) {
// 1. Deposit mUSD to Save
approve(TokenInterface(mUsdToken), imUsdToken, _amount);
uint256 credits = ISavingsContractV2(imUsdToken).depositSavings(
_amount
);
if (_stake) {
// 2. Stake imUSD to Vault
approve(TokenInterface(imUsdToken), imUsdVault, credits);
IStakingRewardsWithPlatformToken(imUsdVault).stake(credits);
}
// 3. Log Events
_eventName = "LogDeposit(address,uint256,address,bool)";
_eventParam = abi.encode(_token, _amount, _path, _stake);
}
/**
* @dev Withdraws from Save
* @notice Withdraws token supported by mStable from Save
* @param _credits Credits to withdraw
* @param _unstake unstake from Vault?
* @return amountWithdrawn Amount withdrawn in mUSD
*/
function _withdraw(uint256 _credits, bool _unstake)
internal
returns (uint256 amountWithdrawn)
{
uint256 credits;
// 1. Withdraw from Vault
if (_unstake) {
credits = _credits == uint256(-1)
? TokenInterface(imUsdVault).balanceOf(address(this))
: _credits;
IStakingRewardsWithPlatformToken(imUsdVault).withdraw(credits);
}
// 2. Withdraw from Save
credits = _credits == uint256(-1)
? TokenInterface(imUsdToken).balanceOf(address(this))
: _credits;
approve(TokenInterface(imUsdToken), imUsdVault, _credits);
amountWithdrawn = ISavingsContractV2(imUsdToken).redeemCredits(credits);
}
/**
* @dev Returns the reward tokens
* @notice Gets the reward tokens from the vault contract
* @return rewardToken Address of reward token
* @return platformToken Address of platform token
*/
function _getRewardTokens()
internal
returns (address rewardToken, address platformToken)
{
rewardToken = IStakingRewardsWithPlatformToken(imUsdVault)
.getRewardToken();
platformToken = IStakingRewardsWithPlatformToken(imUsdVault)
.getPlatformToken();
}
/**
* @dev Returns the internal balances of the rewardToken and platformToken
* @notice Gets current balances of rewardToken and platformToken, used for calculating rewards accrued
* @param _rewardToken Address of reward token
* @param _platformToken Address of platform token
* @return a Amount of reward token
* @return b Amount of platform token
*/
function _getRewardInternalBal(address _rewardToken, address _platformToken)
internal
view
returns (uint256 a, uint256 b)
{
a = TokenInterface(_rewardToken).balanceOf(address(this));
b = TokenInterface(_platformToken).balanceOf(address(this));
}
}

View File

@ -0,0 +1,234 @@
pragma solidity ^0.7.6;
interface IMasset {
// Mint
function mint(
address _input,
uint256 _inputQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 mintOutput);
function mintMulti(
address[] calldata _inputs,
uint256[] calldata _inputQuantities,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 mintOutput);
function getMintOutput(address _input, uint256 _inputQuantity)
external
view
returns (uint256 mintOutput);
function getMintMultiOutput(
address[] calldata _inputs,
uint256[] calldata _inputQuantities
) external view returns (uint256 mintOutput);
// Swaps
function swap(
address _input,
address _output,
uint256 _inputQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 swapOutput);
function getSwapOutput(
address _input,
address _output,
uint256 _inputQuantity
) external view returns (uint256 swapOutput);
// Redemption
function redeem(
address _output,
uint256 _mAssetQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 outputQuantity);
function redeemMasset(
uint256 _mAssetQuantity,
uint256[] calldata _minOutputQuantities,
address _recipient
) external returns (uint256[] memory outputQuantities);
function redeemExactBassets(
address[] calldata _outputs,
uint256[] calldata _outputQuantities,
uint256 _maxMassetQuantity,
address _recipient
) external returns (uint256 mAssetRedeemed);
function getRedeemOutput(address _output, uint256 _mAssetQuantity)
external
view
returns (uint256 bAssetOutput);
function getRedeemExactBassetsOutput(
address[] calldata _outputs,
uint256[] calldata _outputQuantities
) external view returns (uint256 mAssetAmount);
// Views
// This return an index, could be used to check if it's part of the basket
function bAssetIndexes(address) external view returns (uint8);
function getPrice() external view returns (uint256 price, uint256 k);
}
interface ISavingsContractV2 {
function depositInterest(uint256 _amount) external; // V1 & V2
function depositSavings(uint256 _amount)
external
returns (uint256 creditsIssued); // V1 & V2
function depositSavings(uint256 _amount, address _beneficiary)
external
returns (uint256 creditsIssued); // V2
function redeemCredits(uint256 _amount)
external
returns (uint256 underlyingReturned); // V2
function redeemUnderlying(uint256 _amount)
external
returns (uint256 creditsBurned); // V2
function exchangeRate() external view returns (uint256); // V1 & V2
function balanceOfUnderlying(address _user)
external
view
returns (uint256 balance); // V2
function underlyingToCredits(uint256 _credits)
external
view
returns (uint256 underlying); // V2
function creditsToUnderlying(uint256 _underlying)
external
view
returns (uint256 credits); // V2
}
interface IStakingRewardsWithPlatformToken {
/**
* @dev Stakes a given amount of the StakingToken for the sender
* @param _amount Units of StakingToken
*/
function stake(uint256 _amount) external;
/**
* @dev Stakes a given amount of the StakingToken for a given beneficiary
* @param _beneficiary Staked tokens are credited to this address
* @param _amount Units of StakingToken
*/
function stake(address _beneficiary, uint256 _amount) external;
function exit() external;
/**
* @dev Withdraws given stake amount from the pool
* @param _amount Units of the staked token to withdraw
*/
function withdraw(uint256 _amount) external;
/**
* @dev Claims outstanding rewards (both platform and native) for the sender.
* First updates outstanding reward allocation and then transfers.
*/
function claimReward() external;
/**
* @dev Claims outstanding rewards for the sender. Only the native
* rewards token, and not the platform rewards
*/
function claimRewardOnly() external;
function getRewardToken() external returns (address token);
function getPlatformToken() external returns (address token);
}
interface IFeederPool {
// Mint
function mint(
address _input,
uint256 _inputQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 mintOutput);
function mintMulti(
address[] calldata _inputs,
uint256[] calldata _inputQuantities,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 mintOutput);
function getMintOutput(address _input, uint256 _inputQuantity)
external
view
returns (uint256 mintOutput);
function getMintMultiOutput(
address[] calldata _inputs,
uint256[] calldata _inputQuantities
) external view returns (uint256 mintOutput);
// Swaps
function swap(
address _input,
address _output,
uint256 _inputQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 swapOutput);
function getSwapOutput(
address _input,
address _output,
uint256 _inputQuantity
) external view returns (uint256 swapOutput);
// Redemption
function redeem(
address _output,
uint256 _fpTokenQuantity,
uint256 _minOutputQuantity,
address _recipient
) external returns (uint256 outputQuantity);
function redeemProportionately(
uint256 _fpTokenQuantity,
uint256[] calldata _minOutputQuantities,
address _recipient
) external returns (uint256[] memory outputQuantities);
function redeemExactBassets(
address[] calldata _outputs,
uint256[] calldata _outputQuantities,
uint256 _maxMassetQuantity,
address _recipient
) external returns (uint256 mAssetRedeemed);
function getRedeemOutput(address _output, uint256 _fpTokenQuantity)
external
view
returns (uint256 bAssetOutput);
function getRedeemExactBassetsOutput(
address[] calldata _outputs,
uint256[] calldata _outputQuantities
) external view returns (uint256 mAssetAmount);
// Views
function mAsset() external view returns (address);
function getPrice() external view returns (uint256 price, uint256 k);
}

View File

@ -0,0 +1,398 @@
pragma solidity ^0.7.6;
/**
* @title mStable SAVE.
* @dev Depositing and withdrawing directly to Save
*/
import { Helpers } from "./helpers.sol";
import { Events } from "./events.sol";
import { IMasset, IStakingRewardsWithPlatformToken, IFeederPool } from "./interface.sol";
import { TokenInterface } from "../../common/interfaces.sol";
abstract contract PmStableResolver is Events, Helpers {
/***************************************
CORE
****************************************/
/**
* @dev Deposit to Save via mUSD or bAsset
* @notice Deposits token supported by mStable to Save
* @param _token Address of token to deposit
* @param _amount Amount of token to deposit
* @param _minOut Minimum amount of token to mint/deposit, equal to _amount if mUSD
* @param _stake stake token in Vault?
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens deposited
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function deposit(
address _token,
uint256 _amount,
uint256 _minOut,
bool _stake,
uint256 _getId,
uint256 _setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 amount = getUint(_getId, _amount);
amount = amount == uint256(-1)
? TokenInterface(_token).balanceOf(address(this))
: amount;
uint256 mintedAmount;
address path;
// Check if needs to be minted first
if (IMasset(mUsdToken).bAssetIndexes(_token) != 0) {
// mint first
approve(TokenInterface(_token), mUsdToken, amount);
mintedAmount = IMasset(mUsdToken).mint(
_token,
amount,
_minOut,
address(this)
);
path = mUsdToken;
} else {
require(amount >= _minOut, "mintedAmount < _minOut");
mintedAmount = amount;
path = imUsdToken;
}
setUint(_setId, mintedAmount);
(_eventName, _eventParam) = _deposit(
_token,
mintedAmount,
path,
_stake
);
}
/**
* @dev Deposit to Save via feeder pool
* @notice Deposits token, requires _minOut for minting and _path
* @param _token Address of token to deposit
* @param _amount Amount of token to deposit
* @param _minOut Minimum amount of token to mint
* @param _path Feeder Pool address for _token
* @param _stake stake token in Vault?
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens deposited
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function depositViaSwap(
address _token,
uint256 _amount,
uint256 _minOut,
address _path,
bool _stake,
uint256 _getId,
uint256 _setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
require(_path != address(0), "Path must be set");
require(
IMasset(mUsdToken).bAssetIndexes(_token) == 0,
"Token is bAsset"
);
uint256 amount = getUint(_getId, _amount);
amount = amount == uint256(-1)
? TokenInterface(_token).balanceOf(address(this))
: amount;
approve(TokenInterface(_token), _path, amount);
uint256 mintedAmount = IFeederPool(_path).swap(
_token,
mUsdToken,
amount,
_minOut,
address(this)
);
setUint(_setId, mintedAmount);
(_eventName, _eventParam) = _deposit(
_token,
mintedAmount,
_path,
_stake
);
}
/**
* @dev Withdraw from Save to mUSD or bAsset
* @notice Withdraws from Save Vault to mUSD
* @param _token Address of token to withdraw
* @param _credits Credits to withdraw
* @param _minOut Minimum amount of token to withdraw
* @param _unstake from the Vault first?
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens withdrawn
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function withdraw(
address _token,
uint256 _credits,
uint256 _minOut,
bool _unstake,
uint256 _getId,
uint256 _setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 credits = getUint(_getId, _credits);
uint256 amountWithdrawn = _withdraw(credits, _unstake);
// Check if needs to be redeemed
if (IMasset(mUsdToken).bAssetIndexes(_token) != 0) {
amountWithdrawn = IMasset(mUsdToken).redeem(
_token,
amountWithdrawn,
_minOut,
address(this)
);
} else {
require(amountWithdrawn >= _minOut, "amountWithdrawn < _minOut");
}
setUint(_setId, amountWithdrawn);
_eventName = "LogWithdraw(address,uint256,address,bool)";
_eventParam = abi.encode(
mUsdToken,
amountWithdrawn,
imUsdToken,
_unstake
);
}
/**
* @dev Withdraw from Save via Feeder Pool
* @notice Withdraws from Save Vault to asset via Feeder Pool
* @param _token bAsset to withdraw to
* @param _credits Credits to withdraw
* @param _minOut Minimum amount of token to mint
* @param _path Feeder Pool address for _token
* @param _unstake from the Vault first?
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens withdrawn
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function withdrawViaSwap(
address _token,
uint256 _credits,
uint256 _minOut,
address _path,
bool _unstake,
uint256 _getId,
uint256 _setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
require(_path != address(0), "Path must be set");
require(
IMasset(mUsdToken).bAssetIndexes(_token) == 0,
"Token is bAsset"
);
uint256 credits = getUint(_getId, _credits);
uint256 amountWithdrawn = _withdraw(credits, _unstake);
approve(TokenInterface(mUsdToken), _path, amountWithdrawn);
uint256 amountRedeemed = IFeederPool(_path).swap(
mUsdToken,
_token,
amountWithdrawn,
_minOut,
address(this)
);
setUint(_setId, amountRedeemed);
_eventName = "LogWithdraw(address,uint256,address,bool)";
_eventParam = abi.encode(_token, amountRedeemed, _path, _unstake);
}
/**
* @dev Claims Rewards
* @notice Claims accrued rewards from the Vault
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens withdrawn
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function claimRewards(uint256 _getId, uint256 _setId)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
(address rewardToken, address platformToken) = _getRewardTokens();
(uint256 rewardAmount, uint256 platformAmount) = _getRewardInternalBal(
rewardToken,
platformToken
);
IStakingRewardsWithPlatformToken(imUsdVault).claimReward();
(
uint256 rewardAmountUpdated,
uint256 platformAmountUpdated
) = _getRewardInternalBal(rewardToken, platformToken);
uint256 claimedRewardToken = sub(rewardAmountUpdated, rewardAmount);
uint256 claimedPlatformToken = sub(
platformAmountUpdated,
platformAmount
);
setUint(_setId, claimedRewardToken);
_eventName = "LogClaimRewards(address,uint256,address,uint256)";
_eventParam = abi.encode(
rewardToken,
claimedRewardToken,
platformToken,
claimedPlatformToken
);
}
/**
* @dev Swap tokens
* @notice Swaps tokens via Masset basket
* @param _input Token address to swap from
* @param _output Token address to swap to
* @param _amount Amount of tokens to swap
* @param _minOut Minimum amount of token to mint
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens swapped
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function swap(
address _input,
address _output,
uint256 _amount,
uint256 _minOut,
uint256 _getId,
uint256 _setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 amount = getUint(_getId, _amount);
amount = amount == uint256(-1)
? TokenInterface(_input).balanceOf(address(this))
: amount;
approve(TokenInterface(_input), mUsdToken, amount);
uint256 amountSwapped;
// Check the assets and swap accordingly
if (_output == mUsdToken) {
// bAsset to mUSD => mint
amountSwapped = IMasset(mUsdToken).mint(
_input,
amount,
_minOut,
address(this)
);
} else if (_input == mUsdToken) {
// mUSD to bAsset => redeem
amountSwapped = IMasset(mUsdToken).redeem(
_output,
amount,
_minOut,
address(this)
);
} else {
// bAsset to another bAsset => swap
amountSwapped = IMasset(mUsdToken).swap(
_input,
_output,
amount,
_minOut,
address(this)
);
}
setUint(_setId, amountSwapped);
_eventName = "LogSwap(address,address,uint256,uint256)";
_eventParam = abi.encode(_input, _output, amount, amountSwapped);
}
/**
* @dev Swap tokens via Feeder Pool
* @notice Swaps tokens via Feeder Pool
* @param _input Token address to swap from
* @param _output Token address to swap to
* @param _amount Amount of tokens to swap
* @param _minOut Minimum amount of token to mint
* @param _path Feeder Pool address to use
* @param _getId ID to retrieve amt
* @param _setId ID stores the amount of tokens swapped
* @return _eventName Event name
* @return _eventParam Event parameters
*/
function swapViaFeeder(
address _input,
address _output,
uint256 _amount,
uint256 _minOut,
address _path,
uint256 _getId,
uint256 _setId
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 amountSwapped;
uint256 amount = getUint(_getId, _amount);
amount = amount == uint256(-1)
? TokenInterface(_input).balanceOf(address(this))
: amount;
approve(TokenInterface(_input), _path, amount);
// swaps fAsset to mUSD via Feeder Pool
// swaps mUSD to fAsset via Feeder Pool
amountSwapped = IFeederPool(_path).swap(
_input,
_output,
amount,
_minOut,
address(this)
);
setUint(_setId, amountSwapped);
_eventName = "LogSwap(address,address,uint256,uint256)";
_eventParam = abi.encode(_input, _output, amount, amountSwapped);
}
}
contract ConnectV2PmStable is PmStableResolver {
string public constant name = "mStable-Polygon-v1.0";
}

View File

@ -13,6 +13,7 @@ import { HardhatUserConfig } from "hardhat/config";
import { NetworkUserConfig } from "hardhat/types";
import { utils } from "ethers";
import Web3 from "web3";
import { network } from "hardhat";
dotenvConfig({ path: resolve(__dirname, "./.env") });
@ -35,6 +36,7 @@ const PRIVATE_KEY = process.env.PRIVATE_KEY;
const ETHERSCAN_API = process.env.ETHERSCAN_API_KEY;
const POLYGONSCAN_API = process.env.POLYGON_API_KEY;
const ARBISCAN_API = process.env.ARBISCAN_API_KEY;
const OPTIMISM_API = process.env.OPTIMISM_API_KEY;
const SNOWTRACE_API = process.env.SNOWTRACE_API_KEY;
const mnemonic =
process.env.MNEMONIC ??
@ -51,7 +53,7 @@ function createConfig(network: string) {
return {
url: getNetworkUrl(network),
accounts: !!PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : { mnemonic },
// gasPrice: 1000000, // 0.0001 GWEI
gasPrice: 1000000, // 0.0001 GWEI
};
}
@ -71,6 +73,7 @@ function getScanApiKey(networkType: string) {
if (networkType === "avalanche") return SNOWTRACE_API;
else if (networkType === "polygon") return POLYGONSCAN_API;
else if (networkType === "arbitrum") return ARBISCAN_API;
else if (networkType === "optimism") return OPTIMISM_API;
else return ETHERSCAN_API;
}
@ -122,8 +125,8 @@ const config: HardhatUserConfig = {
sources: "./contracts",
tests: "./test",
},
etherscan: {
apiKey: getScanApiKey(String(process.env.networkType)),
etherscan: {
apiKey: getScanApiKey(String(process.env.networkType)),
},
typechain: {
outDir: "typechain",

2400
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,9 @@
"scripts": {
"test": "hardhat run scripts/tests/global-test.ts",
"coverage": "./node_modules/.bin/solidity-coverage",
"check": "node status-checks/huskyCheck.js",
"check-husky": "node status-checks/huskyCheck.js",
"deploy": "node scripts/deployConnectorsFromCmd.js",
"check": "ts-node status-checks/huskyCheck.ts",
"check-husky": "ts-node status-checks/huskyCheck.ts",
"deploy": "ts-node scripts/deployConnectorsFromCmd.ts",
"test:runner": "hardhat run scripts/tests/run-tests.ts",
"typechain": "hardhat typechain",
"compile": "hardhat compile",

View File

@ -92,7 +92,7 @@ const checkEvents = async (connector: {
const eventsPath = `${connector.path}/events.sol`;
const mainPath = `${connector.path}/main.sol`;
if (connector.events.length) {
const eventNames = [];
const eventNames:string[] = [];
for (let i1 = 0; i1 < connector.mainEvents.length; i1++) {
const mainEvent = connector.mainEvents[i1];
const name = mainEvent.split("(")[0];
@ -131,7 +131,7 @@ const checkEvents = async (connector: {
}
}
if (connector.mainEvents.length < connector.events.length) {
const deprecatedEvents = connector.events.filter((e) => {
const deprecatedEvents = connector.events.filter((e: string) => {
let used = false;
for (let index = 0; index < eventNames.length; index++) {
if (e.split("(")[0].split(" ")[1] === eventNames[index])
@ -157,7 +157,7 @@ const checkEvents = async (connector: {
const getCommments = async (strs: string | any[]) => {
try {
const comments = [];
let type: string;
let type: string = '';
for (let index = strs.length - 1; index >= 0; index--) {
const str = strs[index];
if (!type) {
@ -194,9 +194,9 @@ const parseCode = async (connector: { path: any; code?: any }) => {
const eventsFirstLines = [];
let func = [];
let funcs = [];
let event = [];
let mainEvents = [];
let firstLine: number;
let event: string[] = [];
let mainEvents: string[] = [];
let firstLine: number = -1;
let mainEventsLines = [];
for (let index = 0; index < strs.length; index++) {
const str = strs[index];
@ -297,7 +297,7 @@ const parseCode = async (connector: { path: any; code?: any }) => {
}
};
const checkComments = async (connector) => {
const checkComments = async (connector:any) => {
try {
const errors = [];
for (let i1 = 0; i1 < connector.funcs.length; i1++) {
@ -317,7 +317,7 @@ const checkComments = async (connector) => {
}
const reqs = ["@dev", "@notice"];
for (let i3 = 0; i3 < reqs.length; i3++) {
if (!func.comments.some((comment) => comment.startsWith(reqs[i3]))) {
if (!func.comments.some((comment: string) => comment.startsWith(reqs[i3]))) {
errors.push(
`no ${reqs[i3]} for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`
);

View File

@ -3,7 +3,7 @@ import fetch from "node-fetch";
import checks from "./checks";
const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/");
const [owner, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
function getCurrentCommitSha() {
return cp
@ -11,6 +11,7 @@ function getCurrentCommitSha() {
.toString()
.trim();
}
// The SHA provied by GITHUB_SHA is the merge (PR) commit.
// We need to get the current commit sha ourself.
const sha = getCurrentCommitSha();
@ -45,7 +46,7 @@ async function setStatus(context: any, state: string, description: string) {
try {
const response = await callback();
await setStatus(name, "success", response);
} catch (err) {
} catch (err: any) {
const message = err ? err.message : "Something went wrong";
await setStatus(name, "failure", message);
}

View File

@ -0,0 +1,160 @@
import hre, { ethers } from "hardhat";
import { IERC20Minimal__factory } from "../../../typechain";
import { BigNumber as BN } from "ethers";
export const DEAD_ADDRESS = "0x0000000000000000000000000000000000000001";
export const ZERO_ADDRESS = ethers.constants.AddressZero;
export const DEFAULT_DECIMALS = 18;
export const ZERO = BN.from(0);
export const ONE_MIN = BN.from(60);
export const TEN_MINS = BN.from(60 * 10);
export const ONE_HOUR = BN.from(60 * 60);
export const ONE_DAY = BN.from(60 * 60 * 24);
export const FIVE_DAYS = BN.from(60 * 60 * 24 * 5);
export const TEN_DAYS = BN.from(60 * 60 * 24 * 10);
export const ONE_WEEK = BN.from(60 * 60 * 24 * 7);
export const ONE_YEAR = BN.from(60 * 60 * 24 * 365);
export const connectorName = "MStable";
interface TokenData {
tokenAddress: string;
tokenWhaleAddress?: string;
feederPool?: string;
}
export const toEther = (amount: BN) => ethers.utils.formatEther(amount);
export const getToken = (tokenSymbol: string): TokenData => {
switch (tokenSymbol) {
case "MTA":
return {
tokenAddress: "0xa3BeD4E1c75D00fa6f4E5E6922DB7261B5E9AcD2"
};
case "mUSD":
return {
tokenAddress: "0xe2f2a5c287993345a840db3b0845fbc70f5935a5",
tokenWhaleAddress: "0x503828976D22510aad0201ac7EC88293211D23Da"
};
case "DAI":
return {
tokenAddress: "0x6b175474e89094c44da98b954eedeac495271d0f",
tokenWhaleAddress: "0xF977814e90dA44bFA03b6295A0616a897441aceC"
};
case "USDC":
return {
tokenAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
};
case "imUSD":
return {
tokenAddress: "0x30647a72dc82d7fbb1123ea74716ab8a317eac19"
};
case "imUSDVault":
return {
tokenAddress: "0x78BefCa7de27d07DC6e71da295Cc2946681A6c7B"
};
// Feeder Asset
case "alUSD":
return {
tokenAddress: "0xbc6da0fe9ad5f3b0d58160288917aa56653660e9",
tokenWhaleAddress: "0x115f95c00e8cf2f5C57250caA555A6B4e50B446b",
feederPool: "0x4eaa01974B6594C0Ee62fFd7FEE56CF11E6af936"
};
default:
throw new Error(`Token ${tokenSymbol} not supported`);
}
};
export const sendToken = async (token: string, amount: any, from: string, to: string): Promise<any> => {
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [from]
});
const [signer] = await ethers.getSigners();
const sender = hre.ethers.provider.getSigner(from);
await signer.sendTransaction({
to: from,
value: ethers.utils.parseEther("1")
});
return await IERC20Minimal__factory.connect(token, sender).transfer(to, amount);
};
export const fundWallet = async (token: string, amount: any, to: string) => {
const { tokenAddress, tokenWhaleAddress } = getToken(token);
await sendToken(tokenAddress, amount, tokenWhaleAddress!, to);
};
export const calcMinOut = (amount: BN, slippage: number): BN => {
const value = simpleToExactAmount(1 - slippage);
const minOut = amount.mul(value).div(ethers.BigNumber.from(10).pow(DEFAULT_DECIMALS));
return minOut;
};
export const simpleToExactAmount = (amount: number | string | BN, decimals: number | BN = DEFAULT_DECIMALS): BN => {
let amountString = amount.toString();
const decimalsBN = BN.from(decimals);
if (decimalsBN.gt(100)) {
throw new Error(`Invalid decimals amount`);
}
const scale = BN.from(10).pow(decimals);
const scaleString = scale.toString();
// Is it negative?
const negative = amountString.substring(0, 1) === "-";
if (negative) {
amountString = amountString.substring(1);
}
if (amountString === ".") {
throw new Error(`Error converting number ${amountString} to precise unit, invalid value`);
}
// Split it into a whole and fractional part
// eslint-disable-next-line prefer-const
let [whole, fraction, ...rest] = amountString.split(".");
if (rest.length > 0) {
throw new Error(`Error converting number ${amountString} to precise unit, too many decimal points`);
}
if (!whole) {
whole = "0";
}
if (!fraction) {
fraction = "0";
}
if (fraction.length > scaleString.length - 1) {
throw new Error(`Error converting number ${amountString} to precise unit, too many decimal places`);
}
while (fraction.length < scaleString.length - 1) {
fraction += "0";
}
const wholeBN = BN.from(whole);
const fractionBN = BN.from(fraction);
let result = wholeBN.mul(scale).add(fractionBN);
if (negative) {
result = result.mul("-1");
}
return result;
};
export const advanceBlock = async (): Promise<void> => ethers.provider.send("evm_mine", []);
export const increaseTime = async (length: BN | number): Promise<void> => {
await ethers.provider.send("evm_increaseTime", [BN.from(length).toNumber()]);
await advanceBlock();
};

View File

@ -0,0 +1,234 @@
import { expect } from "chai";
import hre from "hardhat";
const { waffle, ethers } = hre;
const { provider } = waffle;
import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector";
import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2";
import { encodeSpells } from "../../../scripts/tests/encodeSpells";
import { getMasterSigner } from "../../../scripts/tests/getMasterSigner";
import { addresses } from "../../../scripts/tests/mainnet/addresses";
import { abis } from "../../../scripts/constant/abis";
import type { Signer, Contract } from "ethers";
import { ConnectV2mStable__factory, IERC20Minimal__factory, IERC20Minimal } from "../../../typechain";
import { executeAndAssertDeposit, executeAndAssertSwap, executeAndAssertWithdraw } from "./mstable.utils";
import {
fundWallet,
getToken,
simpleToExactAmount,
DEAD_ADDRESS,
calcMinOut,
ONE_DAY,
increaseTime,
connectorName,
toEther
} from "./mstable.helpers";
describe("MStable", async () => {
let dsaWallet0: Contract;
let masterSigner: Signer;
let instaConnectorsV2: Contract;
let connector: Contract;
let mtaToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("MTA").tokenAddress, provider);
let mUsdToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("mUSD").tokenAddress, provider);
let imUsdToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("imUSD").tokenAddress, provider);
let imUsdVault: IERC20Minimal = IERC20Minimal__factory.connect(getToken("imUSDVault").tokenAddress, provider);
let daiToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("DAI").tokenAddress, provider);
let usdcToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("USDC").tokenAddress, provider);
let alusdToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("alUSD").tokenAddress, provider);
const wallets = provider.getWallets();
const [wallet0, wallet1, wallet2, wallet3] = wallets;
describe("DSA wallet", async () => {
const fundAmount = simpleToExactAmount(10000);
const setup = async () => {
await hre.network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
// @ts-ignore
jsonRpcUrl: hre.config.networks.hardhat.forking.url,
blockNumber: 13905885
}
}
]
});
masterSigner = await getMasterSigner();
instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2);
connector = await deployAndEnableConnector({
connectorName,
contractArtifact: ConnectV2mStable__factory,
signer: masterSigner,
connectors: instaConnectorsV2
});
console.log("Connector address", connector.address);
dsaWallet0 = await buildDSAv2(wallet0.address);
await wallet0.sendTransaction({
to: dsaWallet0.address,
value: simpleToExactAmount(10)
});
await fundWallet("mUSD", fundAmount, dsaWallet0.address);
await fundWallet("DAI", fundAmount, dsaWallet0.address);
await fundWallet("alUSD", fundAmount, dsaWallet0.address);
};
describe("Deploy", async () => {
before(async () => {
await setup();
});
it("Should deploy properly", async () => {
expect(instaConnectorsV2.address).to.be.properAddress;
expect(connector.address).to.be.properAddress;
expect(await masterSigner.getAddress()).to.be.properAddress;
expect(dsaWallet0.address).to.be.properAddress;
});
it("Should fund the wallet", async () => {
expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10"));
expect(await mUsdToken.balanceOf(dsaWallet0.address)).to.be.gte(fundAmount);
expect(await daiToken.balanceOf(dsaWallet0.address)).to.be.gte(fundAmount);
expect(await alusdToken.balanceOf(dsaWallet0.address)).to.be.gte(fundAmount);
});
it("Should not have vault tokens prior", async () => {
// No deposits prior
expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0);
expect(await imUsdVault.balanceOf(dsaWallet0.address)).to.be.eq(0);
});
});
describe("Main SAVE", async () => {
before(async () => {
await setup();
});
it("Should deposit mUSD to Vault successfully", async () => {
const depositAmount = simpleToExactAmount(100);
const minOut = depositAmount;
await executeAndAssertDeposit("deposit", mUsdToken, depositAmount, dsaWallet0, wallet0, [minOut, true]);
});
it("Should deposit DAI to Vault successfully (mUSD bAsset)", async () => {
const depositAmount = simpleToExactAmount(100);
const minOut = calcMinOut(depositAmount, 0.02);
await executeAndAssertDeposit("deposit", daiToken, depositAmount, dsaWallet0, wallet0, [minOut, true]);
});
it("Should deposit alUSD to Vault successfully (via Feeder Pool)", async () => {
const depositAmount = simpleToExactAmount(100);
const minOut = calcMinOut(depositAmount, 0.02);
const path = getToken("alUSD").feederPool;
await executeAndAssertDeposit("depositViaSwap", alusdToken, depositAmount, dsaWallet0, wallet0, [
minOut,
path,
true
]);
});
it("Should withdraw from Vault to mUSD", async () => {
const withdrawAmount = simpleToExactAmount(100);
const minOut = simpleToExactAmount(1);
await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [minOut, true]);
});
it("Should withdraw from Vault to DAI (mUSD bAsset)", async () => {
const withdrawAmount = simpleToExactAmount(100);
const minOut = simpleToExactAmount(1);
await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [minOut, true]);
});
it("Should withdraw from Vault to alUSD (via Feeder Pool)", async () => {
const withdrawAmount = simpleToExactAmount(100);
const minOut = simpleToExactAmount(1);
const path = getToken("alUSD").feederPool;
await executeAndAssertWithdraw("withdrawViaSwap", alusdToken, withdrawAmount, dsaWallet0, wallet0, [
minOut,
path,
true
]);
});
it("Should claim Rewards", async () => {
const mtaBalanceBefore = await mtaToken.balanceOf(dsaWallet0.address);
console.log("MTA balance before: ", toEther(mtaBalanceBefore));
// Wait a bit and let the rewards accumulate
await increaseTime(ONE_DAY);
const spells = [
{
connector: connectorName,
method: "claimRewards",
args: [0, 0]
}
];
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS);
const mtaBalanceAfter = await mtaToken.balanceOf(dsaWallet0.address);
console.log("MTA balance after: ", toEther(mtaBalanceAfter));
expect(mtaBalanceAfter).to.be.gt(mtaBalanceBefore);
});
});
describe("Main SWAP", async () => {
before(async () => {
await setup();
});
it("Should swap mUSD to bAsset (redeem)", async () => {
const swapAmount = simpleToExactAmount(100);
await executeAndAssertSwap("swap", mUsdToken, 18, daiToken, 18, swapAmount, dsaWallet0, wallet0);
});
it("Should swap mUSD to fAsset (via feeder pool)", async () => {
const swapAmount = simpleToExactAmount(100);
const path = getToken("alUSD").feederPool;
await executeAndAssertSwap("swapViaFeeder", mUsdToken, 18, alusdToken, 18, swapAmount, dsaWallet0, wallet0, [
path
]);
});
it("Should swap bAsset to mUSD (mint)", async () => {
const swapAmount = simpleToExactAmount(100);
await executeAndAssertSwap("swap", daiToken, 18, mUsdToken, 18, swapAmount, dsaWallet0, wallet0);
});
it("Should swap bAsset to bAsset (swap)", async () => {
const swapAmount = simpleToExactAmount(100);
await executeAndAssertSwap("swap", daiToken, 18, usdcToken, 6, swapAmount, dsaWallet0, wallet0);
});
it("Should swap bAsset to fAsset (via feeder)", async () => {
const swapAmount = simpleToExactAmount(100);
const path = getToken("alUSD").feederPool;
await executeAndAssertSwap("swapViaFeeder", daiToken, 18, alusdToken, 18, swapAmount, dsaWallet0, wallet0, [
path
]);
});
it("Should swap fAsset to bAsset (via feeder)", async () => {
const swapAmount = simpleToExactAmount(100);
const path = getToken("alUSD").feederPool;
await executeAndAssertSwap("swapViaFeeder", alusdToken, 18, daiToken, 18, swapAmount, dsaWallet0, wallet0, [
path
]);
});
it("Should swap fAsset to mUSD (via feeder)", async () => {
const swapAmount = simpleToExactAmount(100);
const path = getToken("alUSD").feederPool;
await executeAndAssertSwap("swapViaFeeder", alusdToken, 18, mUsdToken, 18, swapAmount, dsaWallet0, wallet0, [
path
]);
});
});
});
});

View File

@ -0,0 +1,137 @@
import hre from "hardhat";
import { ethers } from "hardhat";
import { assert, expect } from "chai";
import {
DEFAULT_DECIMALS,
DEAD_ADDRESS,
toEther,
connectorName,
simpleToExactAmount,
getToken
} from "./mstable.helpers";
import { IERC20Minimal, IERC20Minimal__factory } from "../../../typechain";
import { BigNumber, Contract, Wallet } from "ethers";
import { encodeSpells } from "../../../scripts/tests/encodeSpells";
const provider = hre.waffle.provider;
let imUsdToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("imUSD").tokenAddress, provider);
let imUsdVault: IERC20Minimal = IERC20Minimal__factory.connect(getToken("imUSDVault").tokenAddress, provider);
export const executeAndAssertSwap = async (
method: string,
tokenFrom: IERC20Minimal,
tokenFromDecimals: number,
tokenTo: IERC20Minimal,
tokenToDecimals: number,
swapAmount: BigNumber,
dsaWallet0: Contract,
wallet0: Wallet,
args?: any[]
) => {
const diffFrom = ethers.BigNumber.from(10).pow(DEFAULT_DECIMALS - tokenFromDecimals);
const diffTo = ethers.BigNumber.from(10).pow(DEFAULT_DECIMALS - tokenToDecimals);
const tokenFromBalanceBefore = (await tokenFrom.balanceOf(dsaWallet0.address)).mul(diffFrom);
console.log("Token From balance before: ", toEther(tokenFromBalanceBefore));
const tokenToBalanceBefore = (await tokenTo.balanceOf(dsaWallet0.address)).mul(diffTo);
console.log("Token To balance before: ", toEther(tokenToBalanceBefore));
const spells = [
{
connector: connectorName,
method,
args: [tokenFrom.address, tokenTo.address, swapAmount, 1, ...(args ? args : []), 0, 0]
}
];
console.log("Swapping...", toEther(swapAmount));
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS);
const tokenFromBalanceAfter = (await tokenFrom.balanceOf(dsaWallet0.address)).mul(diffFrom);
console.log("Token From balance after: ", toEther(tokenFromBalanceAfter));
const tokenToBalanceAfter = (await tokenTo.balanceOf(dsaWallet0.address)).mul(diffTo);
console.log("Token To balance after: ", toEther(tokenToBalanceAfter));
expect(tokenFromBalanceAfter).to.be.eq(tokenFromBalanceBefore.sub(swapAmount));
expect(tokenToBalanceAfter).to.be.gt(tokenToBalanceBefore);
};
export const executeAndAssertDeposit = async (
method: string,
tokenFrom: IERC20Minimal,
depositAmount: BigNumber,
dsaWallet0: Contract,
wallet0: Wallet,
args?: any[]
) => {
const FromBalanceBefore = await tokenFrom.balanceOf(dsaWallet0.address);
console.log("Balance before: ", toEther(FromBalanceBefore));
const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address);
console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore));
const spells = [
{
connector: connectorName,
method,
args: [tokenFrom.address, depositAmount, ...(args ? args : []), 0, 0]
}
];
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS);
const FromBalanceAfter = await tokenFrom.balanceOf(dsaWallet0.address);
console.log("Balance after: ", toEther(FromBalanceAfter));
const imUsdBalance = await imUsdToken.balanceOf(dsaWallet0.address);
console.log("imUSD balance: ", toEther(imUsdBalance));
const imUsdVaultBalance = await imUsdVault.balanceOf(dsaWallet0.address);
console.log("imUSD Vault balance: ", toEther(imUsdVaultBalance));
// Should have something in the vault but no imUSD
expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0);
expect(await imUsdVault.balanceOf(dsaWallet0.address)).to.be.gt(imUsdVaultBalanceBefore);
expect(FromBalanceAfter).to.eq(FromBalanceBefore.sub(depositAmount));
};
export const executeAndAssertWithdraw = async (
method: string,
tokenTo: IERC20Minimal,
withdrawAmount: BigNumber,
dsaWallet0: Contract,
wallet0: Wallet,
args: any[]
) => {
const tokenToBalanceBefore = await tokenTo.balanceOf(dsaWallet0.address);
console.log("Balance before: ", toEther(tokenToBalanceBefore));
const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address);
console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore));
const spells = [
{
connector: connectorName,
method,
args: [tokenTo.address, withdrawAmount, ...(args ? args : []), 0, 0]
}
];
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS);
const imUsdVaultBalanceAfter = await imUsdVault.balanceOf(dsaWallet0.address);
console.log("imUSD Vault balance after: ", toEther(imUsdVaultBalanceAfter));
const tokenToBalanceAfter = await tokenTo.balanceOf(dsaWallet0.address);
console.log("Balance after: ", toEther(tokenToBalanceAfter));
expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount));
expect(tokenToBalanceAfter).to.gt(tokenToBalanceBefore);
};

View File

@ -0,0 +1,159 @@
import hre, { ethers } from "hardhat";
import { IERC20Minimal__factory } from "../../../typechain";
import { BigNumber as BN } from "ethers";
export const DEAD_ADDRESS = "0x0000000000000000000000000000000000000001";
export const ZERO_ADDRESS = ethers.constants.AddressZero;
export const DEFAULT_DECIMALS = 18;
export const ZERO = BN.from(0);
export const ONE_MIN = BN.from(60);
export const TEN_MINS = BN.from(60 * 10);
export const ONE_HOUR = BN.from(60 * 60);
export const ONE_DAY = BN.from(60 * 60 * 24);
export const FIVE_DAYS = BN.from(60 * 60 * 24 * 5);
export const TEN_DAYS = BN.from(60 * 60 * 24 * 10);
export const ONE_WEEK = BN.from(60 * 60 * 24 * 7);
export const ONE_YEAR = BN.from(60 * 60 * 24 * 365);
export const connectorName = "MStable";
interface TokenData {
tokenAddress: string;
tokenWhaleAddress?: string;
feederPool?: string;
}
export const toEther = (amount: BN) => ethers.utils.formatEther(amount);
export const getToken = (tokenSymbol: string): TokenData => {
switch (tokenSymbol) {
case "MTA":
return {
tokenAddress: "0xf501dd45a1198c2e1b5aef5314a68b9006d842e0"
};
case "mUSD":
return {
tokenAddress: "0xe840b73e5287865eec17d250bfb1536704b43b21",
tokenWhaleAddress: "0x4393b9c542bf79e5235180d6da1915c0f9bc02c3"
};
case "DAI":
return {
tokenAddress: "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063",
tokenWhaleAddress: "0x49854708A8c42eEB837A97Dd97D597890CEb1334"
};
case "USDC":
return {
tokenAddress: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
};
case "imUSD":
return {
tokenAddress: "0x5290Ad3d83476CA6A2b178Cd9727eE1EF72432af"
};
case "imUSDVault":
return {
tokenAddress: "0x32aBa856Dc5fFd5A56Bcd182b13380e5C855aa29"
};
case "FRAX":
return {
tokenAddress: "0x104592a158490a9228070E0A8e5343B499e125D0",
tokenWhaleAddress: "0xAE0f77C239f72da36d4dA20a4bBdaAe4Ca48e03F",
feederPool: "0xb30a907084ac8a0d25dddab4e364827406fd09f0"
};
default:
throw new Error(`Token ${tokenSymbol} not supported`);
}
};
export const sendToken = async (token: string, amount: any, from: string, to: string): Promise<any> => {
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [from]
});
const [signer] = await ethers.getSigners();
const sender = hre.ethers.provider.getSigner(from);
await signer.sendTransaction({
to: from,
value: ethers.utils.parseEther("1")
});
return await IERC20Minimal__factory.connect(token, sender).transfer(to, amount);
};
export const fundWallet = async (token: string, amount: any, to: string) => {
const { tokenAddress, tokenWhaleAddress } = getToken(token);
await sendToken(tokenAddress, amount, tokenWhaleAddress!, to);
};
export const calcMinOut = (amount: BN, slippage: number): BN => {
const value = simpleToExactAmount(1 - slippage);
const minOut = amount.mul(value).div(ethers.BigNumber.from(10).pow(DEFAULT_DECIMALS));
return minOut;
};
export const simpleToExactAmount = (amount: number | string | BN, decimals: number | BN = DEFAULT_DECIMALS): BN => {
let amountString = amount.toString();
const decimalsBN = BN.from(decimals);
if (decimalsBN.gt(100)) {
throw new Error(`Invalid decimals amount`);
}
const scale = BN.from(10).pow(decimals);
const scaleString = scale.toString();
// Is it negative?
const negative = amountString.substring(0, 1) === "-";
if (negative) {
amountString = amountString.substring(1);
}
if (amountString === ".") {
throw new Error(`Error converting number ${amountString} to precise unit, invalid value`);
}
// Split it into a whole and fractional part
// eslint-disable-next-line prefer-const
let [whole, fraction, ...rest] = amountString.split(".");
if (rest.length > 0) {
throw new Error(`Error converting number ${amountString} to precise unit, too many decimal points`);
}
if (!whole) {
whole = "0";
}
if (!fraction) {
fraction = "0";
}
if (fraction.length > scaleString.length - 1) {
throw new Error(`Error converting number ${amountString} to precise unit, too many decimal places`);
}
while (fraction.length < scaleString.length - 1) {
fraction += "0";
}
const wholeBN = BN.from(whole);
const fractionBN = BN.from(fraction);
let result = wholeBN.mul(scale).add(fractionBN);
if (negative) {
result = result.mul("-1");
}
return result;
};
export const advanceBlock = async (): Promise<void> => ethers.provider.send("evm_mine", []);
export const increaseTime = async (length: BN | number): Promise<void> => {
await ethers.provider.send("evm_increaseTime", [BN.from(length).toNumber()]);
await advanceBlock();
};

View File

@ -0,0 +1,234 @@
import { expect } from "chai";
import hre from "hardhat";
const { waffle, ethers } = hre;
const { provider } = waffle;
import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector";
import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2";
import { encodeSpells } from "../../../scripts/tests/encodeSpells";
import { getMasterSigner } from "../../../scripts/tests/getMasterSigner";
import { addresses } from "../../../scripts/tests/polygon/addresses";
import { abis } from "../../../scripts/constant/abis";
import type { Signer, Contract } from "ethers";
import { ConnectV2PmStable__factory, IERC20Minimal__factory, IERC20Minimal } from "../../../typechain";
import { executeAndAssertDeposit, executeAndAssertSwap, executeAndAssertWithdraw } from "./mstable.utils";
import {
fundWallet,
getToken,
simpleToExactAmount,
DEAD_ADDRESS,
calcMinOut,
ONE_DAY,
increaseTime,
connectorName,
toEther
} from "./mstable.helpers";
describe("MStable", async () => {
let dsaWallet0: Contract;
let masterSigner: Signer;
let instaConnectorsV2: Contract;
let connector: Contract;
let mtaToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("MTA").tokenAddress, provider);
let mUsdToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("mUSD").tokenAddress, provider);
let imUsdToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("imUSD").tokenAddress, provider);
let imUsdVault: IERC20Minimal = IERC20Minimal__factory.connect(getToken("imUSDVault").tokenAddress, provider);
let daiToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("DAI").tokenAddress, provider);
let usdcToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("USDC").tokenAddress, provider);
let fraxToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("FRAX").tokenAddress, provider);
const wallets = provider.getWallets();
const [wallet0, wallet1, wallet2, wallet3] = wallets;
describe("DSA wallet", async () => {
const fundAmount = simpleToExactAmount(10000);
const setup = async () => {
await hre.network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
// @ts-ignore
jsonRpcUrl: hre.config.networks.hardhat.forking.url,
blockNumber: 23059414
}
}
]
});
masterSigner = await getMasterSigner();
instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2);
connector = await deployAndEnableConnector({
connectorName,
contractArtifact: ConnectV2PmStable__factory,
signer: masterSigner,
connectors: instaConnectorsV2
});
console.log("Connector address", connector.address);
dsaWallet0 = await buildDSAv2(wallet0.address);
await wallet0.sendTransaction({
to: dsaWallet0.address,
value: simpleToExactAmount(10)
});
await fundWallet("mUSD", fundAmount, dsaWallet0.address);
await fundWallet("DAI", fundAmount, dsaWallet0.address);
await fundWallet("FRAX", fundAmount, dsaWallet0.address);
};
describe("Deploy", async () => {
before(async () => {
await setup();
});
it("Should deploy properly", async () => {
expect(instaConnectorsV2.address).to.be.properAddress;
expect(connector.address).to.be.properAddress;
expect(await masterSigner.getAddress()).to.be.properAddress;
expect(dsaWallet0.address).to.be.properAddress;
});
it("Should fund the wallet", async () => {
expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10"));
expect(await mUsdToken.balanceOf(dsaWallet0.address)).to.be.gte(fundAmount);
expect(await daiToken.balanceOf(dsaWallet0.address)).to.be.gte(fundAmount);
expect(await fraxToken.balanceOf(dsaWallet0.address)).to.be.gte(fundAmount);
});
it("Should not have vault tokens prior", async () => {
// No deposits prior
expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0);
expect(await imUsdVault.balanceOf(dsaWallet0.address)).to.be.eq(0);
});
});
describe("Main SAVE", async () => {
before(async () => {
await setup();
});
it("Should deposit mUSD to Vault successfully", async () => {
const depositAmount = simpleToExactAmount(100);
const minOut = depositAmount;
await executeAndAssertDeposit("deposit", mUsdToken, depositAmount, dsaWallet0, wallet0, [minOut, true]);
});
it("Should deposit DAI to Vault successfully (mUSD bAsset)", async () => {
const depositAmount = simpleToExactAmount(100);
const minOut = calcMinOut(depositAmount, 0.02);
await executeAndAssertDeposit("deposit", daiToken, depositAmount, dsaWallet0, wallet0, [minOut, true]);
});
it("Should deposit FRAX to Vault successfully (via Feeder Pool)", async () => {
const depositAmount = simpleToExactAmount(100);
const minOut = calcMinOut(depositAmount, 0.02);
const path = getToken("FRAX").feederPool;
await executeAndAssertDeposit("depositViaSwap", fraxToken, depositAmount, dsaWallet0, wallet0, [
minOut,
path,
true
]);
});
it("Should withdraw from Vault to mUSD", async () => {
const withdrawAmount = simpleToExactAmount(100);
const minOut = simpleToExactAmount(1);
await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [minOut, true]);
});
it("Should withdraw from Vault to DAI (mUSD bAsset)", async () => {
const withdrawAmount = simpleToExactAmount(100);
const minOut = simpleToExactAmount(1);
await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [minOut, true]);
});
it("Should withdraw from Vault to FRAX (via Feeder Pool)", async () => {
const withdrawAmount = simpleToExactAmount(100);
const minOut = simpleToExactAmount(1);
const path = getToken("FRAX").feederPool;
await executeAndAssertWithdraw("withdrawViaSwap", fraxToken, withdrawAmount, dsaWallet0, wallet0, [
minOut,
path,
true
]);
});
it("Should claim Rewards", async () => {
const mtaBalanceBefore = await mtaToken.balanceOf(dsaWallet0.address);
console.log("MTA balance before: ", toEther(mtaBalanceBefore));
// Wait a bit and let the rewards accumulate
await increaseTime(ONE_DAY);
const spells = [
{
connector: connectorName,
method: "claimRewards",
args: [0, 0]
}
];
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS);
const mtaBalanceAfter = await mtaToken.balanceOf(dsaWallet0.address);
console.log("MTA balance after: ", toEther(mtaBalanceAfter));
expect(mtaBalanceAfter).to.be.gt(mtaBalanceBefore);
});
});
describe("Main SWAP", async () => {
before(async () => {
await setup();
});
it("Should swap mUSD to bAsset (redeem)", async () => {
const swapAmount = simpleToExactAmount(100);
await executeAndAssertSwap("swap", mUsdToken, 18, daiToken, 18, swapAmount, dsaWallet0, wallet0);
});
it("Should swap mUSD to fAsset (via feeder pool)", async () => {
const swapAmount = simpleToExactAmount(100);
const path = getToken("FRAX").feederPool;
await executeAndAssertSwap("swapViaFeeder", mUsdToken, 18, fraxToken, 18, swapAmount, dsaWallet0, wallet0, [
path
]);
});
it("Should swap bAsset to mUSD (mint)", async () => {
const swapAmount = simpleToExactAmount(100);
await executeAndAssertSwap("swap", daiToken, 18, mUsdToken, 18, swapAmount, dsaWallet0, wallet0);
});
it("Should swap bAsset to bAsset (swap)", async () => {
const swapAmount = simpleToExactAmount(100);
await executeAndAssertSwap("swap", daiToken, 18, usdcToken, 6, swapAmount, dsaWallet0, wallet0);
});
it("Should swap bAsset to fAsset (via feeder)", async () => {
const swapAmount = simpleToExactAmount(100);
const path = getToken("FRAX").feederPool;
await executeAndAssertSwap("swapViaFeeder", daiToken, 18, fraxToken, 18, swapAmount, dsaWallet0, wallet0, [
path
]);
});
it("Should swap fAsset to bAsset (via feeder)", async () => {
const swapAmount = simpleToExactAmount(100);
const path = getToken("FRAX").feederPool;
await executeAndAssertSwap("swapViaFeeder", fraxToken, 18, daiToken, 18, swapAmount, dsaWallet0, wallet0, [
path
]);
});
it("Should swap fAsset to mUSD (via feeder)", async () => {
const swapAmount = simpleToExactAmount(100);
const path = getToken("FRAX").feederPool;
await executeAndAssertSwap("swapViaFeeder", fraxToken, 18, mUsdToken, 18, swapAmount, dsaWallet0, wallet0, [
path
]);
});
});
});
});

View File

@ -0,0 +1,137 @@
import hre from "hardhat";
import { ethers } from "hardhat";
import { assert, expect } from "chai";
import {
DEFAULT_DECIMALS,
DEAD_ADDRESS,
toEther,
connectorName,
simpleToExactAmount,
getToken
} from "./mstable.helpers";
import { IERC20Minimal, IERC20Minimal__factory } from "../../../typechain";
import { BigNumber, Contract, Wallet } from "ethers";
import { encodeSpells } from "../../../scripts/tests/encodeSpells";
const provider = hre.waffle.provider;
let imUsdToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("imUSD").tokenAddress, provider);
let imUsdVault: IERC20Minimal = IERC20Minimal__factory.connect(getToken("imUSDVault").tokenAddress, provider);
export const executeAndAssertSwap = async (
method: string,
tokenFrom: IERC20Minimal,
tokenFromDecimals: number,
tokenTo: IERC20Minimal,
tokenToDecimals: number,
swapAmount: BigNumber,
dsaWallet0: Contract,
wallet0: Wallet,
args?: any[]
) => {
const diffFrom = ethers.BigNumber.from(10).pow(DEFAULT_DECIMALS - tokenFromDecimals);
const diffTo = ethers.BigNumber.from(10).pow(DEFAULT_DECIMALS - tokenToDecimals);
const tokenFromBalanceBefore = (await tokenFrom.balanceOf(dsaWallet0.address)).mul(diffFrom);
console.log("Token From balance before: ", toEther(tokenFromBalanceBefore));
const tokenToBalanceBefore = (await tokenTo.balanceOf(dsaWallet0.address)).mul(diffTo);
console.log("Token To balance before: ", toEther(tokenToBalanceBefore));
const spells = [
{
connector: connectorName,
method,
args: [tokenFrom.address, tokenTo.address, swapAmount, 1, ...(args ? args : []), 0, 0]
}
];
console.log("Swapping...", toEther(swapAmount));
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS);
const tokenFromBalanceAfter = (await tokenFrom.balanceOf(dsaWallet0.address)).mul(diffFrom);
console.log("Token From balance after: ", toEther(tokenFromBalanceAfter));
const tokenToBalanceAfter = (await tokenTo.balanceOf(dsaWallet0.address)).mul(diffTo);
console.log("Token To balance after: ", toEther(tokenToBalanceAfter));
expect(tokenFromBalanceAfter).to.be.eq(tokenFromBalanceBefore.sub(swapAmount));
expect(tokenToBalanceAfter).to.be.gt(tokenToBalanceBefore);
};
export const executeAndAssertDeposit = async (
method: string,
tokenFrom: IERC20Minimal,
depositAmount: BigNumber,
dsaWallet0: Contract,
wallet0: Wallet,
args?: any[]
) => {
const FromBalanceBefore = await tokenFrom.balanceOf(dsaWallet0.address);
console.log("Balance before: ", toEther(FromBalanceBefore));
const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address);
console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore));
const spells = [
{
connector: connectorName,
method,
args: [tokenFrom.address, depositAmount, ...(args ? args : []), 0, 0]
}
];
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS);
const FromBalanceAfter = await tokenFrom.balanceOf(dsaWallet0.address);
console.log("Balance after: ", toEther(FromBalanceAfter));
const imUsdBalance = await imUsdToken.balanceOf(dsaWallet0.address);
console.log("imUSD balance: ", toEther(imUsdBalance));
const imUsdVaultBalance = await imUsdVault.balanceOf(dsaWallet0.address);
console.log("imUSD Vault balance: ", toEther(imUsdVaultBalance));
// Should have something in the vault but no imUSD
expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0);
expect(await imUsdVault.balanceOf(dsaWallet0.address)).to.be.gt(imUsdVaultBalanceBefore);
expect(FromBalanceAfter).to.eq(FromBalanceBefore.sub(depositAmount));
};
export const executeAndAssertWithdraw = async (
method: string,
tokenTo: IERC20Minimal,
withdrawAmount: BigNumber,
dsaWallet0: Contract,
wallet0: Wallet,
args: any[]
) => {
const tokenToBalanceBefore = await tokenTo.balanceOf(dsaWallet0.address);
console.log("Balance before: ", toEther(tokenToBalanceBefore));
const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address);
console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore));
const spells = [
{
connector: connectorName,
method,
args: [tokenTo.address, withdrawAmount, ...(args ? args : []), 0, 0]
}
];
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS);
const imUsdVaultBalanceAfter = await imUsdVault.balanceOf(dsaWallet0.address);
console.log("imUSD Vault balance after: ", toEther(imUsdVaultBalanceAfter));
const tokenToBalanceAfter = await tokenTo.balanceOf(dsaWallet0.address);
console.log("Balance after: ", toEther(tokenToBalanceAfter));
expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount));
expect(tokenToBalanceAfter).to.gt(tokenToBalanceBefore);
};

View File

@ -21,6 +21,7 @@
"tasks/**/*",
"test/**/*",
"typechain/**/*",
"types/**/*"
"types/**/*",
"status-checks/*"
]
}

12710
yarn.lock

File diff suppressed because it is too large Load Diff