From 76ebe50729cc8e67489afefcddd51501fccc4c2f Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Wed, 29 Dec 2021 17:37:38 +0700 Subject: [PATCH 01/21] wip: mStable save deposit --- .../polygon/connectors/mstable/events.sol | 13 + .../polygon/connectors/mstable/helpers.sol | 27 ++ .../polygon/connectors/mstable/interface.sol | 234 ++++++++++++++++++ contracts/polygon/connectors/mstable/main.sol | 161 ++++++++++++ 4 files changed, 435 insertions(+) create mode 100644 contracts/polygon/connectors/mstable/events.sol create mode 100644 contracts/polygon/connectors/mstable/helpers.sol create mode 100644 contracts/polygon/connectors/mstable/interface.sol create mode 100644 contracts/polygon/connectors/mstable/main.sol diff --git a/contracts/polygon/connectors/mstable/events.sol b/contracts/polygon/connectors/mstable/events.sol new file mode 100644 index 00000000..652c916d --- /dev/null +++ b/contracts/polygon/connectors/mstable/events.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.7.6; + +contract Events { + // TODO: Events go here + event LogDeposit(address token, uint256 amount, address path); + event LogWithdraw(address token, uint256 amount, address origin); + event LogClaimReward( + address token, + uint256 amount, + address platformToken, + uint256 platformAmount + ); +} diff --git a/contracts/polygon/connectors/mstable/helpers.sol b/contracts/polygon/connectors/mstable/helpers.sol new file mode 100644 index 00000000..4e174128 --- /dev/null +++ b/contracts/polygon/connectors/mstable/helpers.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.7.6; + +import { DSMath } from "../../common/math.sol"; +import { Basic } from "../../common/basic.sol"; + +// import { SaveWrapper } from "./interface.sol"; + +// interfaces here +// import { AaveLendingPoolProviderInterface, AaveDataProviderInterface } from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + // Helpers go here + /* + * @dev SaveWrapper address + */ + // SaveWrapper internal constant saveWrapper = + // SaveWrapper(0x299081f52738A4204C3D58264ff44f6F333C6c88); + + // Addresses that will be important for the contract + + address internal constant mUsdToken = + 0xE840B73E5287865EEc17d250bFb1536704B43B21; + address internal constant imUsdToken = + 0x5290Ad3d83476CA6A2b178Cd9727eE1EF72432af; + address internal constant imUsdVault = + 0x32aBa856Dc5fFd5A56Bcd182b13380e5C855aa29; +} diff --git a/contracts/polygon/connectors/mstable/interface.sol b/contracts/polygon/connectors/mstable/interface.sol new file mode 100644 index 00000000..cc551921 --- /dev/null +++ b/contracts/polygon/connectors/mstable/interface.sol @@ -0,0 +1,234 @@ +pragma solidity ^0.7.6; + +// TODO: Interfaces go here +// https://polygonscan.com/address/0xca9cf48ad534f1efa2b0f6923457f2953df86e0b#code +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; +} + +abstract contract IFeederPool { + // Mint + function mint( + address _input, + uint256 _inputQuantity, + uint256 _minOutputQuantity, + address _recipient + ) external virtual returns (uint256 mintOutput); + + function mintMulti( + address[] calldata _inputs, + uint256[] calldata _inputQuantities, + uint256 _minOutputQuantity, + address _recipient + ) external virtual returns (uint256 mintOutput); + + function getMintOutput(address _input, uint256 _inputQuantity) + external + view + virtual + returns (uint256 mintOutput); + + function getMintMultiOutput( + address[] calldata _inputs, + uint256[] calldata _inputQuantities + ) external view virtual returns (uint256 mintOutput); + + // Swaps + function swap( + address _input, + address _output, + uint256 _inputQuantity, + uint256 _minOutputQuantity, + address _recipient + ) external virtual returns (uint256 swapOutput); + + function getSwapOutput( + address _input, + address _output, + uint256 _inputQuantity + ) external view virtual returns (uint256 swapOutput); + + // Redemption + function redeem( + address _output, + uint256 _fpTokenQuantity, + uint256 _minOutputQuantity, + address _recipient + ) external virtual returns (uint256 outputQuantity); + + function redeemProportionately( + uint256 _fpTokenQuantity, + uint256[] calldata _minOutputQuantities, + address _recipient + ) external virtual returns (uint256[] memory outputQuantities); + + function redeemExactBassets( + address[] calldata _outputs, + uint256[] calldata _outputQuantities, + uint256 _maxMassetQuantity, + address _recipient + ) external virtual returns (uint256 mAssetRedeemed); + + function getRedeemOutput(address _output, uint256 _fpTokenQuantity) + external + view + virtual + returns (uint256 bAssetOutput); + + function getRedeemExactBassetsOutput( + address[] calldata _outputs, + uint256[] calldata _outputQuantities + ) external view virtual returns (uint256 mAssetAmount); + + // Views + function mAsset() external view virtual returns (address); + + function getPrice() public view virtual returns (uint256 price, uint256 k); +} diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol new file mode 100644 index 00000000..5f2b204f --- /dev/null +++ b/contracts/polygon/connectors/mstable/main.sol @@ -0,0 +1,161 @@ +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, ISavingsContractV2, IStakingRewardsWithPlatformToken, IFeederPool } from "./interface.sol"; +import { TokenInterface } from "../../common/interfaces.sol"; + +import "hardhat/console.sol"; + +abstract contract mStableResolver is Events, Helpers { + /*************************************** + CORE + ****************************************/ + + /** + * @dev Deposit to Save via mUSD + * @notice Deposits token supported by mStable to Save + * @param _token Address of token to deposit + * @param _amount Amount of token to deposit + */ + + function deposit(address _token, uint256 _amount) + external + returns (string memory _eventName, bytes memory _eventParam) + { + return _deposit(_token, _amount, imUsdToken); + } + + /** + * @dev Deposit to Save via bAsset + * @notice Deposits token, requires _minOut for minting + * @param _token Address of token to deposit + * @param _amount Amount of token to deposit + * @param _minOut Minimum amount of token to mint + */ + + function deposit( + address _token, + uint256 _amount, + uint256 _minOut + ) external returns (string memory _eventName, bytes memory _eventParam) { + require( + IMasset(mUsdToken).bAssetIndexes(_token) != 0, + "Token not a bAsset" + ); + + approve(TokenInterface(_token), mUsdToken, _amount); + uint256 mintedAmount = IMasset(mUsdToken).mint( + _token, + _amount, + _minOut, + address(this) + ); + + return _deposit(_token, mintedAmount, mUsdToken); + } + + // /** + // * @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 + // */ + + // function deposit( + // address _token, + // uint256 _amount, + // uint256 _minOut, + // address _path + // ) external returns (string memory _eventName, bytes memory _eventParam) { + // require(_path != address(0), "Path must be set"); + // require( + // IMasset(mUsdToken).bAssetIndexes(_token) == 0, + // "Token is bAsset" + // ); + + // approve(TokenInterface(_token), _path, _amount); + // uint256 mintedAmount = IFeederPool(_path).swap( + // _token, + // mUsdToken, + // _amount, + // _minOut, + // address(this) + // ); + // return _deposit(_token, mintedAmount, _path); + // } + + /*************************************** + 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) + */ + + function _deposit( + address _token, + uint256 _amount, + address _path + ) internal returns (string memory _eventName, bytes memory _eventParam) { + // 1. Deposit mUSD to Save + approve(TokenInterface(mUsdToken), imUsdToken, _amount); + uint256 credits = ISavingsContractV2(imUsdToken).depositSavings( + _amount + ); + + // 2. Stake imUSD to Vault + approve(TokenInterface(imUsdToken), imUsdVault, credits); + IStakingRewardsWithPlatformToken(imUsdVault).stake(credits); + + // 3. Log Events + _eventName = "LogDeposit()"; + _eventParam = abi.encode(_token, _amount, _path); + } + + /** + * @dev Withdraw from Save + * @notice Withdraws token supported by mStable from Save + * @param _token Address of token to withdraw + * @param _amount Amount of token to withdraw + */ + + // function withdraw(address _token, uint256 _amount) + // external + // returns (string memory _eventName, bytes memory _eventParam); + + // TODO + // function to support via Feeders or separate function? + // blocked by new SaveUnwrapper upgrade + + /** + * @dev Swaps token supported by mStable for another token + * @notice Swaps token supported by mStable for another token + * @param _token Address of token to swap + * @param _amount Amount of token to swap + * @param _minOutput Minimum amount of token to swap + */ + + // function swap( + // address _token, + // uint256 _amount, + // uint256 _minOutput + // ) external returns (string memory _eventName, bytes memory _eventParam); + // TODO + // function to support via Feeders or separate function? +} + +contract ConnectV2mStable is mStableResolver { + string public constant name = "mStable-Polygon-Connector-v1"; +} From 67004ca16d103421e75632695fdb3962c4dcf746 Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Wed, 29 Dec 2021 17:37:54 +0700 Subject: [PATCH 02/21] wip: deposit tests --- test/polygon/mstable/mstable.helpers.ts | 133 ++++++++++++++++ test/polygon/mstable/mstable.test.ts | 197 ++++++++++++++++++++++++ 2 files changed, 330 insertions(+) create mode 100644 test/polygon/mstable/mstable.helpers.ts create mode 100644 test/polygon/mstable/mstable.test.ts diff --git a/test/polygon/mstable/mstable.helpers.ts b/test/polygon/mstable/mstable.helpers.ts new file mode 100644 index 00000000..9378fa57 --- /dev/null +++ b/test/polygon/mstable/mstable.helpers.ts @@ -0,0 +1,133 @@ +import hre, { ethers } from "hardhat"; +import { IERC20Minimal__factory } from "../../../typechain"; +import { BigNumber as BN } from "ethers"; + +const DEAD_ADDRESS = "0x0000000000000000000000000000000000000001"; +const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; +const DEFAULT_DECIMALS = 18; + +interface TokenData { + tokenAddress: string; + tokenWhaleAddress?: string; + feederPool?: string; +} + +const getToken = (tokenSymbol: string): TokenData => { + switch (tokenSymbol) { + case "mUSD": + return { + tokenAddress: "0xe840b73e5287865eec17d250bfb1536704b43b21", + tokenWhaleAddress: "0x4393b9c542bf79e5235180d6da1915c0f9bc02c3" + }; + + case "DAI": + return { + tokenAddress: "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", + tokenWhaleAddress: "0x49854708A8c42eEB837A97Dd97D597890CEb1334" + }; + 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`); + } +}; + +const sendToken = async (token: string, amount: any, from: string, to: string): Promise => { + 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); +}; + +const fundWallet = async (token: string, amount: any, to: string) => { + const { tokenAddress, tokenWhaleAddress } = getToken(token); + await sendToken(tokenAddress, amount, tokenWhaleAddress!, to); +}; + +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; +}; + +const simpleToExactAmount = (amount: number | string | BN, decimals: number | BN = DEFAULT_DECIMALS): BN => { + // Code is largely lifted from the guts of web3 toWei here: + // https://github.com/ethjs/ethjs-unit/blob/master/src/index.js + 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 { fundWallet, getToken, simpleToExactAmount, DEAD_ADDRESS, ZERO_ADDRESS, calcMinOut }; diff --git a/test/polygon/mstable/mstable.test.ts b/test/polygon/mstable/mstable.test.ts new file mode 100644 index 00000000..3a134ebf --- /dev/null +++ b/test/polygon/mstable/mstable.test.ts @@ -0,0 +1,197 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = 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 { addLiquidity } from "../../../scripts/tests/addLiquidity"; + +import { addresses } from "../../../scripts/tests/polygon/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { tokens } from "../../../scripts/tests/polygon/tokens"; +import type { Signer, Contract, BigNumber } from "ethers"; + +import { ConnectV2mStable__factory, IERC20Minimal__factory, IERC20Minimal } from "../../../typechain"; + +import { fundWallet, getToken, simpleToExactAmount, DEAD_ADDRESS, calcMinOut } from "./mstable.helpers"; + +describe("MStable", async () => { + const connectorName = "MStable"; + + let dsaWallet0: Contract; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: Contract; + + let mUsdToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("mUSD").tokenAddress, provider); + let daiToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("DAI").tokenAddress, provider); + let fraxToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("FRAX").tokenAddress, provider); + let imUsdToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("imUSD").tokenAddress, provider); + let imUsdVault: IERC20Minimal = IERC20Minimal__factory.connect(getToken("imUSDVault").tokenAddress, provider); + + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + + const toEther = (amount: BigNumber) => ethers.utils.formatEther(amount); + + before(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: ConnectV2mStable__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + + console.log("Connector address", connector.address); + }); + it("should deploy", async () => { + expect(instaConnectorsV2.address).to.be.properAddress; + expect(connector.address).to.be.properAddress; + expect(await masterSigner.getAddress()).to.be.properAddress; + }); + describe("DSA wallet", async () => { + it("Should build DSA v2", async () => { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(dsaWallet0.address).to.be.properAddress; + }); + it("Deposit ETH and tokens into DSA Wallet", async () => { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: simpleToExactAmount(10) + }); + + const fundAmount = simpleToExactAmount(10000); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + + await fundWallet("mUSD", fundAmount, dsaWallet0.address); + await fundWallet("DAI", fundAmount, dsaWallet0.address); + await fundWallet("FRAX", fundAmount, dsaWallet0.address); + + 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); + + // 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", async () => { + it("Should deposit mUSD to Vault successfully", async () => { + const depositAmount = simpleToExactAmount(100); + + console.log(dsaWallet0.address); + + const mUsdBalanceBefore = await mUsdToken.balanceOf(dsaWallet0.address); + console.log("mUSD balance before: ", toEther(mUsdBalanceBefore)); + + const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [mUsdToken.address, depositAmount] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS); + + const mUsdBalanceAfter = await mUsdToken.balanceOf(dsaWallet0.address); + console.log("mUSD balance after: ", toEther(mUsdBalanceAfter)); + + 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(0); + expect(mUsdBalanceAfter).to.eq(mUsdBalanceBefore.sub(depositAmount)); + }); + it("Should deposit DAI to Vault successfully (mUSD bAsset)", async () => { + const depositAmount = simpleToExactAmount(100); + const minOut = calcMinOut(depositAmount, 0.02); + + const daiBalanceBefore = await daiToken.balanceOf(dsaWallet0.address); + console.log("DAI balance before: ", toEther(daiBalanceBefore)); + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [daiToken.address, depositAmount, minOut] + } + ]; + const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS); + + const daiBalanceAfter = await daiToken.balanceOf(dsaWallet0.address); + console.log("DAI balance after: ", toEther(daiBalanceAfter)); + + const imUsdVaultBalanceAfter = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance after: ", toEther(imUsdVaultBalanceAfter)); + + expect(imUsdVaultBalanceAfter).to.be.gt(imUsdVaultBalanceBefore); + expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0); + expect(daiBalanceAfter).to.eq(daiBalanceBefore.sub(depositAmount)); + }); + it.skip("Should deposit FRAX to Vault successfully (via Feeder Pool)", async () => { + const depositAmount = simpleToExactAmount(100); + const minOut = calcMinOut(depositAmount, 0.02); + + const fraxBalanceBefore = await fraxToken.balanceOf(dsaWallet0.address); + console.log("FRAX balance before: ", toEther(fraxBalanceBefore)); + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [fraxToken.address, depositAmount, minOut, getToken("FRAX").feederPool] + } + ]; + + const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS); + + const fraxBalanceAfter = await fraxToken.balanceOf(dsaWallet0.address); + console.log("FRAX balance after: ", toEther(fraxBalanceAfter)); + + const imUsdVaultBalanceAfter = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance after: ", toEther(imUsdVaultBalanceAfter)); + + expect(imUsdVaultBalanceAfter).to.be.gt(imUsdVaultBalanceBefore); + expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0); + expect(fraxBalanceAfter).to.eq(fraxBalanceBefore.sub(depositAmount)); + }); + it.skip("Should withdraw from Vault to mUSD", async () => {}); + it.skip("Should withdraw from Vault to DAI (mUSD bAsset)", async () => {}); + it.skip("Should withdraw from Vault to FRAX (via Feeder Pool)", async () => {}); + }); + }); +}); From e89222002024295ba97589103f410e9e5daecac7 Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Wed, 29 Dec 2021 20:06:43 +0700 Subject: [PATCH 03/21] wip: withdraw tests --- test/polygon/mstable/mstable.helpers.ts | 4 + test/polygon/mstable/mstable.test.ts | 119 ++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 8 deletions(-) diff --git a/test/polygon/mstable/mstable.helpers.ts b/test/polygon/mstable/mstable.helpers.ts index 9378fa57..21cacaf2 100644 --- a/test/polygon/mstable/mstable.helpers.ts +++ b/test/polygon/mstable/mstable.helpers.ts @@ -14,6 +14,10 @@ interface TokenData { const getToken = (tokenSymbol: string): TokenData => { switch (tokenSymbol) { + case "MTA": + return { + tokenAddress: "0xf501dd45a1198c2e1b5aef5314a68b9006d842e0" + }; case "mUSD": return { tokenAddress: "0xe840b73e5287865eec17d250bfb1536704b43b21", diff --git a/test/polygon/mstable/mstable.test.ts b/test/polygon/mstable/mstable.test.ts index 3a134ebf..6fb31698 100644 --- a/test/polygon/mstable/mstable.test.ts +++ b/test/polygon/mstable/mstable.test.ts @@ -26,6 +26,7 @@ describe("MStable", async () => { 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 daiToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("DAI").tokenAddress, provider); let fraxToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("FRAX").tokenAddress, provider); @@ -99,8 +100,6 @@ describe("MStable", async () => { it("Should deposit mUSD to Vault successfully", async () => { const depositAmount = simpleToExactAmount(100); - console.log(dsaWallet0.address); - const mUsdBalanceBefore = await mUsdToken.balanceOf(dsaWallet0.address); console.log("mUSD balance before: ", toEther(mUsdBalanceBefore)); @@ -140,7 +139,7 @@ describe("MStable", async () => { const spells = [ { connector: connectorName, - method: "deposit", + method: "depositViaMint", args: [daiToken.address, depositAmount, minOut] } ]; @@ -159,7 +158,7 @@ describe("MStable", async () => { expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0); expect(daiBalanceAfter).to.eq(daiBalanceBefore.sub(depositAmount)); }); - it.skip("Should deposit FRAX to Vault successfully (via Feeder Pool)", async () => { + it("Should deposit FRAX to Vault successfully (via Feeder Pool)", async () => { const depositAmount = simpleToExactAmount(100); const minOut = calcMinOut(depositAmount, 0.02); @@ -169,7 +168,7 @@ describe("MStable", async () => { const spells = [ { connector: connectorName, - method: "deposit", + method: "depositViaSwap", args: [fraxToken.address, depositAmount, minOut, getToken("FRAX").feederPool] } ]; @@ -189,9 +188,113 @@ describe("MStable", async () => { expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0); expect(fraxBalanceAfter).to.eq(fraxBalanceBefore.sub(depositAmount)); }); - it.skip("Should withdraw from Vault to mUSD", async () => {}); - it.skip("Should withdraw from Vault to DAI (mUSD bAsset)", async () => {}); - it.skip("Should withdraw from Vault to FRAX (via Feeder Pool)", async () => {}); + it("Should withdraw from Vault to mUSD", async () => { + const withdrawAmount = simpleToExactAmount(100); + + const mUsdBalanceBefore = await mUsdToken.balanceOf(dsaWallet0.address); + console.log("mUSD balance before: ", toEther(mUsdBalanceBefore)); + + const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); + + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: [withdrawAmount] + } + ]; + + 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 mUsdBalanceAfter = await mUsdToken.balanceOf(dsaWallet0.address); + console.log("mUSD balance after: ", toEther(mUsdBalanceAfter)); + + expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); + expect(mUsdBalanceAfter).to.gt(mUsdBalanceBefore); + }); + it("Should withdraw from Vault to DAI (mUSD bAsset)", async () => { + const withdrawAmount = simpleToExactAmount(100); + const minOut = simpleToExactAmount(1); + + const daiBalanceBefore = await daiToken.balanceOf(dsaWallet0.address); + console.log("DAI balance before: ", toEther(daiBalanceBefore)); + + const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); + + const spells = [ + { + connector: connectorName, + method: "withdrawViaRedeem", + args: [daiToken.address, withdrawAmount, minOut] + } + ]; + + 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 daiBalanceAfter = await daiToken.balanceOf(dsaWallet0.address); + console.log("DAI balance after: ", toEther(daiBalanceAfter)); + + expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); + expect(daiBalanceAfter).to.gt(daiBalanceBefore); + }); + it("Should withdraw from Vault to FRAX (via Feeder Pool)", async () => { + const withdrawAmount = simpleToExactAmount(100); + const minOut = simpleToExactAmount(1); + + const fraxBalanceBefore = await fraxToken.balanceOf(dsaWallet0.address); + console.log("FRAX balance before: ", toEther(fraxBalanceBefore)); + + const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); + + const spells = [ + { + connector: connectorName, + method: "withdrawViaSwap", + args: [fraxToken.address, withdrawAmount, minOut, getToken("FRAX").feederPool] + } + ]; + + 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 fraxBalanceAfter = await fraxToken.balanceOf(dsaWallet0.address); + console.log("FRAX balance after: ", toEther(fraxBalanceAfter)); + + expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); + expect(fraxBalanceAfter).to.gt(fraxBalanceBefore); + }); + it("Should claim Rewards", async () => { + const mtaBalanceBefore = await mtaToken.balanceOf(dsaWallet0.address); + console.log("MTA balance before: ", toEther(mtaBalanceBefore)); + + // Wait a day and let the rewards accumulate + await provider.send("evm_increaseTime", [600]); + + const spells = [ + { + connector: connectorName, + method: "claimRewards" + } + ]; + + 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); + }); }); }); }); From 093d7b30c6b551bf23f6ac69c2d7f81b95b8ef2d Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Wed, 29 Dec 2021 20:07:27 +0700 Subject: [PATCH 04/21] wip: withdraw from vautl --- .../polygon/connectors/mstable/events.sol | 2 +- contracts/polygon/connectors/mstable/main.sol | 174 ++++++++++++++---- 2 files changed, 136 insertions(+), 40 deletions(-) diff --git a/contracts/polygon/connectors/mstable/events.sol b/contracts/polygon/connectors/mstable/events.sol index 652c916d..52e31fa4 100644 --- a/contracts/polygon/connectors/mstable/events.sol +++ b/contracts/polygon/connectors/mstable/events.sol @@ -3,7 +3,7 @@ pragma solidity ^0.7.6; contract Events { // TODO: Events go here event LogDeposit(address token, uint256 amount, address path); - event LogWithdraw(address token, uint256 amount, address origin); + event LogWithdraw(address token, uint256 amount, address path); event LogClaimReward( address token, uint256 amount, diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index 5f2b204f..3f57ad1a 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -39,7 +39,7 @@ abstract contract mStableResolver is Events, Helpers { * @param _minOut Minimum amount of token to mint */ - function deposit( + function depositViaMint( address _token, uint256 _amount, uint256 _minOut @@ -60,37 +60,127 @@ abstract contract mStableResolver is Events, Helpers { return _deposit(_token, mintedAmount, mUsdToken); } - // /** - // * @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 - // */ + /** + * @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 + */ - // function deposit( - // address _token, - // uint256 _amount, - // uint256 _minOut, - // address _path - // ) external returns (string memory _eventName, bytes memory _eventParam) { - // require(_path != address(0), "Path must be set"); - // require( - // IMasset(mUsdToken).bAssetIndexes(_token) == 0, - // "Token is bAsset" - // ); + function depositViaSwap( + address _token, + uint256 _amount, + uint256 _minOut, + address _path + ) external returns (string memory _eventName, bytes memory _eventParam) { + require(_path != address(0), "Path must be set"); + require( + IMasset(mUsdToken).bAssetIndexes(_token) == 0, + "Token is bAsset" + ); - // approve(TokenInterface(_token), _path, _amount); - // uint256 mintedAmount = IFeederPool(_path).swap( - // _token, - // mUsdToken, - // _amount, - // _minOut, - // address(this) - // ); - // return _deposit(_token, mintedAmount, _path); - // } + approve(TokenInterface(_token), _path, _amount); + uint256 mintedAmount = IFeederPool(_path).swap( + _token, + mUsdToken, + _amount, + _minOut, + address(this) + ); + return _deposit(_token, mintedAmount, _path); + } + + /** + * @dev Withdraw from Save to mUSD + * @notice Withdraws from Save Vault to mUSD + * @param _credits Credits to withdraw + */ + function withdraw(uint256 _credits) + external + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 amountWithdrawn = _withdraw(_credits); + + _eventName = "LogWithdraw()"; + _eventParam = abi.encode(mUsdToken, amountWithdrawn, imUsdToken); + } + + /** + * @dev Withdraw from Save to bAsset + * @notice Withdraws from Save Vault to bAsset + * @param _token bAsset to withdraw to + * @param _credits Credits to withdraw + * @param _minOut Minimum amount of token to mint + */ + + function withdrawViaRedeem( + address _token, + uint256 _credits, + uint256 _minOut + ) external returns (string memory _eventName, bytes memory _eventParam) { + require( + IMasset(mUsdToken).bAssetIndexes(_token) != 0, + "Token not a bAsset" + ); + + uint256 amountWithdrawn = _withdraw(_credits); + uint256 amountRedeemed = IMasset(mUsdToken).redeem( + _token, + amountWithdrawn, + _minOut, + address(this) + ); + + _eventName = "LogRedeem()"; + _eventParam = abi.encode(mUsdToken, amountRedeemed, _token); + } + + /** + * @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 + */ + + function withdrawViaSwap( + address _token, + uint256 _credits, + uint256 _minOut, + address _path + ) external 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 amountWithdrawn = _withdraw(_credits); + + approve(TokenInterface(mUsdToken), _path, amountWithdrawn); + uint256 amountRedeemed = IFeederPool(_path).swap( + mUsdToken, + _token, + amountWithdrawn, + _minOut, + address(this) + ); + + _eventName = "LogRedeem()"; + _eventParam = abi.encode(_token, amountRedeemed, _path); + } + + /** + * @dev Claims Rewards + * @notice Claims accrued rewards from the Vault + */ + + function claimRewards() external { + IStakingRewardsWithPlatformToken(imUsdVault).claimReward(); + } /*************************************** Internal @@ -125,19 +215,25 @@ abstract contract mStableResolver is Events, Helpers { } /** - * @dev Withdraw from Save + * @dev Withdraws from Save * @notice Withdraws token supported by mStable from Save - * @param _token Address of token to withdraw - * @param _amount Amount of token to withdraw + * @param _credits Credits to withdraw */ - // function withdraw(address _token, uint256 _amount) - // external - // returns (string memory _eventName, bytes memory _eventParam); + function _withdraw(uint256 _credits) + internal + returns (uint256 amountWithdrawn) + { + // 1. Withdraw from Vault + // approve(TokenInterface(imUsdVault), imUsdToken, _credits); + IStakingRewardsWithPlatformToken(imUsdVault).withdraw(_credits); - // TODO - // function to support via Feeders or separate function? - // blocked by new SaveUnwrapper upgrade + // 2. Withdraw from Save + approve(TokenInterface(imUsdToken), imUsdVault, _credits); + amountWithdrawn = ISavingsContractV2(imUsdToken).redeemCredits( + _credits + ); + } /** * @dev Swaps token supported by mStable for another token From 5e58eda2ed2e391c894ab6e9827ade024b6cc898 Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 30 Dec 2021 12:05:31 +0700 Subject: [PATCH 05/21] wip: reward claim --- .../polygon/connectors/mstable/events.sol | 2 +- .../polygon/connectors/mstable/interface.sol | 4 + contracts/polygon/connectors/mstable/main.sol | 83 ++++++++++++++++++- 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/contracts/polygon/connectors/mstable/events.sol b/contracts/polygon/connectors/mstable/events.sol index 52e31fa4..58823740 100644 --- a/contracts/polygon/connectors/mstable/events.sol +++ b/contracts/polygon/connectors/mstable/events.sol @@ -4,7 +4,7 @@ contract Events { // TODO: Events go here event LogDeposit(address token, uint256 amount, address path); event LogWithdraw(address token, uint256 amount, address path); - event LogClaimReward( + event LogClaimRewards( address token, uint256 amount, address platformToken, diff --git a/contracts/polygon/connectors/mstable/interface.sol b/contracts/polygon/connectors/mstable/interface.sol index cc551921..9efdc5ef 100644 --- a/contracts/polygon/connectors/mstable/interface.sol +++ b/contracts/polygon/connectors/mstable/interface.sol @@ -151,6 +151,10 @@ interface IStakingRewardsWithPlatformToken { * rewards token, and not the platform rewards */ function claimRewardOnly() external; + + function getRewardToken() external returns (address token); + + function getPlatformToken() external returns (address token); } abstract contract IFeederPool { diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index 3f57ad1a..bc5e07aa 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -22,6 +22,8 @@ abstract contract mStableResolver is Events, Helpers { * @notice Deposits token supported by mStable to Save * @param _token Address of token to deposit * @param _amount Amount of token to deposit + * @return _eventName Event name + * @return _eventParam Event parameters */ function deposit(address _token, uint256 _amount) @@ -37,6 +39,8 @@ abstract contract mStableResolver is Events, Helpers { * @param _token Address of token to deposit * @param _amount Amount of token to deposit * @param _minOut Minimum amount of token to mint + * @return _eventName Event name + * @return _eventParam Event parameters */ function depositViaMint( @@ -67,6 +71,8 @@ abstract contract mStableResolver is Events, Helpers { * @param _amount Amount of token to deposit * @param _minOut Minimum amount of token to mint * @param _path Feeder Pool address for _token + * @return _eventName Event name + * @return _eventParam Event parameters */ function depositViaSwap( @@ -96,6 +102,8 @@ abstract contract mStableResolver is Events, Helpers { * @dev Withdraw from Save to mUSD * @notice Withdraws from Save Vault to mUSD * @param _credits Credits to withdraw + * @return _eventName Event name + * @return _eventParam Event parameters */ function withdraw(uint256 _credits) external @@ -113,6 +121,8 @@ abstract contract mStableResolver is Events, Helpers { * @param _token bAsset to withdraw to * @param _credits Credits to withdraw * @param _minOut Minimum amount of token to mint + * @return _eventName Event name + * @return _eventParam Event parameters */ function withdrawViaRedeem( @@ -144,6 +154,8 @@ abstract contract mStableResolver is Events, Helpers { * @param _credits Credits to withdraw * @param _minOut Minimum amount of token to mint * @param _path Feeder Pool address for _token + * @return _eventName Event name + * @return _eventParam Event parameters */ function withdrawViaSwap( @@ -176,10 +188,41 @@ abstract contract mStableResolver is Events, Helpers { /** * @dev Claims Rewards * @notice Claims accrued rewards from the Vault + * @return _eventName Event name + * @return _eventParam Event parameters */ - function claimRewards() external { + function claimRewards() + external + 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 + ); + + _eventName = "LogClaimRewards()"; + _eventParam = abi.encode( + rewardToken, + claimedRewardToken, + platformToken, + claimedPlatformToken + ); } /*************************************** @@ -192,6 +235,8 @@ abstract contract mStableResolver is Events, Helpers { * @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) + * @return _eventName Event name + * @return _eventParam Event parameters */ function _deposit( @@ -218,6 +263,7 @@ abstract contract mStableResolver is Events, Helpers { * @dev Withdraws from Save * @notice Withdraws token supported by mStable from Save * @param _credits Credits to withdraw + * @return amountWithdrawn Amount withdrawn in mUSD */ function _withdraw(uint256 _credits) @@ -235,6 +281,41 @@ abstract contract mStableResolver is Events, Helpers { ); } + /** + * @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)); + } + /** * @dev Swaps token supported by mStable for another token * @notice Swaps token supported by mStable for another token From 14008b3bf90aab140b4a23ff75073ba283ee5b7b Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 30 Dec 2021 12:05:46 +0700 Subject: [PATCH 06/21] wip: reward claim tests --- test/polygon/mstable/mstable.helpers.ts | 36 +++++++++++++++++-------- test/polygon/mstable/mstable.test.ts | 17 +++++++++--- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/test/polygon/mstable/mstable.helpers.ts b/test/polygon/mstable/mstable.helpers.ts index 21cacaf2..f1435d12 100644 --- a/test/polygon/mstable/mstable.helpers.ts +++ b/test/polygon/mstable/mstable.helpers.ts @@ -2,9 +2,20 @@ import hre, { ethers } from "hardhat"; import { IERC20Minimal__factory } from "../../../typechain"; import { BigNumber as BN } from "ethers"; -const DEAD_ADDRESS = "0x0000000000000000000000000000000000000001"; -const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; -const DEFAULT_DECIMALS = 18; +export const DEAD_ADDRESS = "0x0000000000000000000000000000000000000001"; +export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; + +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); interface TokenData { tokenAddress: string; @@ -12,7 +23,7 @@ interface TokenData { feederPool?: string; } -const getToken = (tokenSymbol: string): TokenData => { +export const getToken = (tokenSymbol: string): TokenData => { switch (tokenSymbol) { case "MTA": return { @@ -51,7 +62,7 @@ const getToken = (tokenSymbol: string): TokenData => { } }; -const sendToken = async (token: string, amount: any, from: string, to: string): Promise => { +export const sendToken = async (token: string, amount: any, from: string, to: string): Promise => { await hre.network.provider.request({ method: "hardhat_impersonateAccount", params: [from] @@ -67,20 +78,18 @@ const sendToken = async (token: string, amount: any, from: string, to: string): return await IERC20Minimal__factory.connect(token, sender).transfer(to, amount); }; -const fundWallet = async (token: string, amount: any, to: string) => { +export const fundWallet = async (token: string, amount: any, to: string) => { const { tokenAddress, tokenWhaleAddress } = getToken(token); await sendToken(tokenAddress, amount, tokenWhaleAddress!, to); }; -const calcMinOut = (amount: BN, slippage: number): BN => { +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; }; -const simpleToExactAmount = (amount: number | string | BN, decimals: number | BN = DEFAULT_DECIMALS): BN => { - // Code is largely lifted from the guts of web3 toWei here: - // https://github.com/ethjs/ethjs-unit/blob/master/src/index.js +export const simpleToExactAmount = (amount: number | string | BN, decimals: number | BN = DEFAULT_DECIMALS): BN => { let amountString = amount.toString(); const decimalsBN = BN.from(decimals); @@ -134,4 +143,9 @@ const simpleToExactAmount = (amount: number | string | BN, decimals: number | BN return result; }; -export { fundWallet, getToken, simpleToExactAmount, DEAD_ADDRESS, ZERO_ADDRESS, calcMinOut }; +export const advanceBlock = async (): Promise => ethers.provider.send("evm_mine", []); + +export const increaseTime = async (length: BN | number): Promise => { + await ethers.provider.send("evm_increaseTime", [BN.from(length).toNumber()]); + await advanceBlock(); +}; diff --git a/test/polygon/mstable/mstable.test.ts b/test/polygon/mstable/mstable.test.ts index 6fb31698..5b6efb36 100644 --- a/test/polygon/mstable/mstable.test.ts +++ b/test/polygon/mstable/mstable.test.ts @@ -16,7 +16,15 @@ import type { Signer, Contract, BigNumber } from "ethers"; import { ConnectV2mStable__factory, IERC20Minimal__factory, IERC20Minimal } from "../../../typechain"; -import { fundWallet, getToken, simpleToExactAmount, DEAD_ADDRESS, calcMinOut } from "./mstable.helpers"; +import { + fundWallet, + getToken, + simpleToExactAmount, + DEAD_ADDRESS, + calcMinOut, + ONE_DAY, + increaseTime +} from "./mstable.helpers"; describe("MStable", async () => { const connectorName = "MStable"; @@ -278,13 +286,14 @@ describe("MStable", async () => { const mtaBalanceBefore = await mtaToken.balanceOf(dsaWallet0.address); console.log("MTA balance before: ", toEther(mtaBalanceBefore)); - // Wait a day and let the rewards accumulate - await provider.send("evm_increaseTime", [600]); + // Wait a bit and let the rewards accumulate + await increaseTime(ONE_DAY); const spells = [ { connector: connectorName, - method: "claimRewards" + method: "claimRewards", + args: [] } ]; From ceb90406cd896a04b81d28137df7e193c798d772 Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 30 Dec 2021 16:21:54 +0700 Subject: [PATCH 07/21] chore: add standadized functions --- test/polygon/mstable/mstable.test.ts | 269 +++++++++++--------------- test/polygon/mstable/mstable.utils.ts | 137 +++++++++++++ 2 files changed, 253 insertions(+), 153 deletions(-) create mode 100644 test/polygon/mstable/mstable.utils.ts diff --git a/test/polygon/mstable/mstable.test.ts b/test/polygon/mstable/mstable.test.ts index 5b6efb36..5bf87f22 100644 --- a/test/polygon/mstable/mstable.test.ts +++ b/test/polygon/mstable/mstable.test.ts @@ -16,6 +16,8 @@ import type { Signer, Contract, BigNumber } from "ethers"; import { ConnectV2mStable__factory, IERC20Minimal__factory, IERC20Minimal } from "../../../typechain"; +import { executeAndAssertDeposit, executeAndAssertSwap, executeAndAssertWithdraw } from "./mstable.utils"; + import { fundWallet, getToken, @@ -23,12 +25,13 @@ import { DEAD_ADDRESS, calcMinOut, ONE_DAY, - increaseTime + increaseTime, + DEFAULT_DECIMALS, + connectorName, + toEther } from "./mstable.helpers"; describe("MStable", async () => { - const connectorName = "MStable"; - let dsaWallet0: Contract; let masterSigner: Signer; let instaConnectorsV2: Contract; @@ -36,193 +39,108 @@ describe("MStable", async () => { let mtaToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("MTA").tokenAddress, provider); let mUsdToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("mUSD").tokenAddress, provider); - let daiToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("DAI").tokenAddress, provider); - let fraxToken: IERC20Minimal = IERC20Minimal__factory.connect(getToken("FRAX").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; - const toEther = (amount: BigNumber) => ethers.utils.formatEther(amount); - - before(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: ConnectV2mStable__factory, - signer: masterSigner, - connectors: instaConnectorsV2 - }); - - console.log("Connector address", connector.address); - }); - it("should deploy", async () => { - expect(instaConnectorsV2.address).to.be.properAddress; - expect(connector.address).to.be.properAddress; - expect(await masterSigner.getAddress()).to.be.properAddress; - }); describe("DSA wallet", async () => { - it("Should build DSA v2", 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: ConnectV2mStable__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + + console.log("Connector address", connector.address); + dsaWallet0 = await buildDSAv2(wallet0.address); - expect(dsaWallet0.address).to.be.properAddress; - }); - it("Deposit ETH and tokens into DSA Wallet", async () => { + await wallet0.sendTransaction({ to: dsaWallet0.address, value: simpleToExactAmount(10) }); - const fundAmount = simpleToExactAmount(10000); - - expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); - await fundWallet("mUSD", fundAmount, dsaWallet0.address); await fundWallet("DAI", fundAmount, dsaWallet0.address); await fundWallet("FRAX", fundAmount, dsaWallet0.address); + }; - 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); + describe("Deploy", async () => { + before(async () => { + await setup(); + }); - // No deposits prior - expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0); - expect(await imUsdVault.balanceOf(dsaWallet0.address)).to.be.eq(0); + 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", async () => { + describe("Main SAVE", async () => { + before(async () => { + await setup(); + }); it("Should deposit mUSD to Vault successfully", async () => { const depositAmount = simpleToExactAmount(100); - const mUsdBalanceBefore = await mUsdToken.balanceOf(dsaWallet0.address); - console.log("mUSD balance before: ", toEther(mUsdBalanceBefore)); - - const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); - console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); - - const spells = [ - { - connector: connectorName, - method: "deposit", - args: [mUsdToken.address, depositAmount] - } - ]; - - const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS); - - const mUsdBalanceAfter = await mUsdToken.balanceOf(dsaWallet0.address); - console.log("mUSD balance after: ", toEther(mUsdBalanceAfter)); - - 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(0); - expect(mUsdBalanceAfter).to.eq(mUsdBalanceBefore.sub(depositAmount)); + await executeAndAssertDeposit("deposit", mUsdToken, depositAmount, dsaWallet0, wallet0); }); it("Should deposit DAI to Vault successfully (mUSD bAsset)", async () => { const depositAmount = simpleToExactAmount(100); const minOut = calcMinOut(depositAmount, 0.02); - const daiBalanceBefore = await daiToken.balanceOf(dsaWallet0.address); - console.log("DAI balance before: ", toEther(daiBalanceBefore)); - const spells = [ - { - connector: connectorName, - method: "depositViaMint", - args: [daiToken.address, depositAmount, minOut] - } - ]; - const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); - console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); - - const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS); - - const daiBalanceAfter = await daiToken.balanceOf(dsaWallet0.address); - console.log("DAI balance after: ", toEther(daiBalanceAfter)); - - const imUsdVaultBalanceAfter = await imUsdVault.balanceOf(dsaWallet0.address); - console.log("imUSD Vault balance after: ", toEther(imUsdVaultBalanceAfter)); - - expect(imUsdVaultBalanceAfter).to.be.gt(imUsdVaultBalanceBefore); - expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0); - expect(daiBalanceAfter).to.eq(daiBalanceBefore.sub(depositAmount)); + await executeAndAssertDeposit("depositViaMint", daiToken, depositAmount, dsaWallet0, wallet0, [minOut]); }); 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; - const fraxBalanceBefore = await fraxToken.balanceOf(dsaWallet0.address); - console.log("FRAX balance before: ", toEther(fraxBalanceBefore)); - - const spells = [ - { - connector: connectorName, - method: "depositViaSwap", - args: [fraxToken.address, depositAmount, minOut, getToken("FRAX").feederPool] - } - ]; - - const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); - console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); - - const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), DEAD_ADDRESS); - - const fraxBalanceAfter = await fraxToken.balanceOf(dsaWallet0.address); - console.log("FRAX balance after: ", toEther(fraxBalanceAfter)); - - const imUsdVaultBalanceAfter = await imUsdVault.balanceOf(dsaWallet0.address); - console.log("imUSD Vault balance after: ", toEther(imUsdVaultBalanceAfter)); - - expect(imUsdVaultBalanceAfter).to.be.gt(imUsdVaultBalanceBefore); - expect(await imUsdToken.balanceOf(dsaWallet0.address)).to.be.eq(0); - expect(fraxBalanceAfter).to.eq(fraxBalanceBefore.sub(depositAmount)); + await executeAndAssertDeposit("depositViaSwap", fraxToken, depositAmount, dsaWallet0, wallet0, [minOut, path]); }); it("Should withdraw from Vault to mUSD", async () => { const withdrawAmount = simpleToExactAmount(100); - const mUsdBalanceBefore = await mUsdToken.balanceOf(dsaWallet0.address); - console.log("mUSD balance before: ", toEther(mUsdBalanceBefore)); - - const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); - console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); - - const spells = [ - { - connector: connectorName, - method: "withdraw", - args: [withdrawAmount] - } - ]; - - 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 mUsdBalanceAfter = await mUsdToken.balanceOf(dsaWallet0.address); - console.log("mUSD balance after: ", toEther(mUsdBalanceAfter)); - - expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); - expect(mUsdBalanceAfter).to.gt(mUsdBalanceBefore); + await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [withdrawAmount]); }); it("Should withdraw from Vault to DAI (mUSD bAsset)", async () => { const withdrawAmount = simpleToExactAmount(100); @@ -305,5 +223,50 @@ describe("MStable", async () => { 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 + ]); + }); + }); }); }); diff --git a/test/polygon/mstable/mstable.utils.ts b/test/polygon/mstable/mstable.utils.ts new file mode 100644 index 00000000..6f51be22 --- /dev/null +++ b/test/polygon/mstable/mstable.utils.ts @@ -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 : [])] + } + ]; + + 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 : [])] + } + ]; + + 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, + tokenFrom: IERC20Minimal, + withdrawAmount: BigNumber, + dsaWallet0: Contract, + wallet0: Wallet, + args: any[] +) => { + const mUsdBalanceBefore = await tokenFrom.balanceOf(dsaWallet0.address); + console.log("Balance before: ", toEther(mUsdBalanceBefore)); + + const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); + + const spells = [ + { + connector: connectorName, + method, + args + } + ]; + + 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 mUsdBalanceAfter = await tokenFrom.balanceOf(dsaWallet0.address); + console.log("Balance after: ", toEther(mUsdBalanceAfter)); + + expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); + expect(mUsdBalanceAfter).to.gt(mUsdBalanceBefore); +}; From 972b8a8ebb638ba6c96a5eaeeebcf592fcc507dd Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 30 Dec 2021 16:22:24 +0700 Subject: [PATCH 08/21] wip: add swap tests --- .../polygon/connectors/mstable/events.sol | 6 + contracts/polygon/connectors/mstable/main.sol | 114 +++++++++++++++--- test/polygon/mstable/mstable.helpers.ts | 8 ++ 3 files changed, 112 insertions(+), 16 deletions(-) diff --git a/contracts/polygon/connectors/mstable/events.sol b/contracts/polygon/connectors/mstable/events.sol index 58823740..14e8bd7c 100644 --- a/contracts/polygon/connectors/mstable/events.sol +++ b/contracts/polygon/connectors/mstable/events.sol @@ -10,4 +10,10 @@ contract Events { address platformToken, uint256 platformAmount ); + event LogSwap( + address from, + address to, + uint256 amountIn, + uint256 amountOut + ); } diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index bc5e07aa..cf952804 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -13,6 +13,7 @@ import { TokenInterface } from "../../common/interfaces.sol"; import "hardhat/console.sol"; abstract contract mStableResolver is Events, Helpers { + // /*************************************** CORE ****************************************/ @@ -48,6 +49,7 @@ abstract contract mStableResolver is Events, Helpers { uint256 _amount, uint256 _minOut ) external returns (string memory _eventName, bytes memory _eventParam) { + // require( IMasset(mUsdToken).bAssetIndexes(_token) != 0, "Token not a bAsset" @@ -81,6 +83,7 @@ abstract contract mStableResolver is Events, Helpers { uint256 _minOut, address _path ) external returns (string memory _eventName, bytes memory _eventParam) { + // require(_path != address(0), "Path must be set"); require( IMasset(mUsdToken).bAssetIndexes(_token) == 0, @@ -105,6 +108,7 @@ abstract contract mStableResolver is Events, Helpers { * @return _eventName Event name * @return _eventParam Event parameters */ + function withdraw(uint256 _credits) external returns (string memory _eventName, bytes memory _eventParam) @@ -130,6 +134,7 @@ abstract contract mStableResolver is Events, Helpers { uint256 _credits, uint256 _minOut ) external returns (string memory _eventName, bytes memory _eventParam) { + // require( IMasset(mUsdToken).bAssetIndexes(_token) != 0, "Token not a bAsset" @@ -164,6 +169,7 @@ abstract contract mStableResolver is Events, Helpers { uint256 _minOut, address _path ) external returns (string memory _eventName, bytes memory _eventParam) { + // require(_path != address(0), "Path must be set"); require( IMasset(mUsdToken).bAssetIndexes(_token) == 0, @@ -225,6 +231,97 @@ abstract contract mStableResolver is Events, Helpers { ); } + /** + * @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 + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function swap( + address _input, + address _output, + uint256 _amount, + uint256 _minOut + ) external returns (string memory _eventName, bytes memory _eventParam) { + // + 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) + ); + } + + _eventName = "LogSwap()"; + _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 + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function swapViaFeeder( + address _input, + address _output, + uint256 _amount, + uint256 _minOut, + address _path + ) external returns (string memory _eventName, bytes memory _eventParam) { + // + uint256 amountSwapped; + + 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) + ); + + _eventName = "LogSwap()"; + _eventParam = abi.encode(_input, _output, _amount, amountSwapped); + } + /*************************************** Internal ****************************************/ @@ -244,6 +341,7 @@ abstract contract mStableResolver is Events, Helpers { uint256 _amount, address _path ) internal returns (string memory _eventName, bytes memory _eventParam) { + // // 1. Deposit mUSD to Save approve(TokenInterface(mUsdToken), imUsdToken, _amount); uint256 credits = ISavingsContractV2(imUsdToken).depositSavings( @@ -315,22 +413,6 @@ abstract contract mStableResolver is Events, Helpers { a = TokenInterface(_rewardToken).balanceOf(address(this)); b = TokenInterface(_platformToken).balanceOf(address(this)); } - - /** - * @dev Swaps token supported by mStable for another token - * @notice Swaps token supported by mStable for another token - * @param _token Address of token to swap - * @param _amount Amount of token to swap - * @param _minOutput Minimum amount of token to swap - */ - - // function swap( - // address _token, - // uint256 _amount, - // uint256 _minOutput - // ) external returns (string memory _eventName, bytes memory _eventParam); - // TODO - // function to support via Feeders or separate function? } contract ConnectV2mStable is mStableResolver { diff --git a/test/polygon/mstable/mstable.helpers.ts b/test/polygon/mstable/mstable.helpers.ts index f1435d12..781559f8 100644 --- a/test/polygon/mstable/mstable.helpers.ts +++ b/test/polygon/mstable/mstable.helpers.ts @@ -17,12 +17,16 @@ 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": @@ -40,6 +44,10 @@ export const getToken = (tokenSymbol: string): TokenData => { tokenAddress: "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", tokenWhaleAddress: "0x49854708A8c42eEB837A97Dd97D597890CEb1334" }; + case "USDC": + return { + tokenAddress: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174" + }; case "imUSD": return { tokenAddress: "0x5290Ad3d83476CA6A2b178Cd9727eE1EF72432af" From d6231fff0d993bdd5ff7b25b924933fc68d8e426 Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 30 Dec 2021 17:42:44 +0700 Subject: [PATCH 09/21] feat: add mainnet connector --- contracts/mainnet/mstable/events.sol | 13 + contracts/mainnet/mstable/helpers.sol | 13 + contracts/mainnet/mstable/interface.sol | 381 +++++++++++++++++++++++ contracts/mainnet/mstable/main.sol | 394 ++++++++++++++++++++++++ 4 files changed, 801 insertions(+) create mode 100644 contracts/mainnet/mstable/events.sol create mode 100644 contracts/mainnet/mstable/helpers.sol create mode 100644 contracts/mainnet/mstable/interface.sol create mode 100644 contracts/mainnet/mstable/main.sol diff --git a/contracts/mainnet/mstable/events.sol b/contracts/mainnet/mstable/events.sol new file mode 100644 index 00000000..7cabfddc --- /dev/null +++ b/contracts/mainnet/mstable/events.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.7.6; + +contract Events { + event LogDeposit(address token, uint256 amount, address path); + event LogWithdraw(address token, uint256 amount, address path); + event LogClaimRewards(address token, uint256 amount); + event LogSwap( + address from, + address to, + uint256 amountIn, + uint256 amountOut + ); +} diff --git a/contracts/mainnet/mstable/helpers.sol b/contracts/mainnet/mstable/helpers.sol new file mode 100644 index 00000000..f98c9688 --- /dev/null +++ b/contracts/mainnet/mstable/helpers.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.7.6; + +import { DSMath } from "../common/math.sol"; +import { Basic } from "../common/basic.sol"; + +abstract contract Helpers is DSMath, Basic { + address internal constant mUsdToken = + 0xe2f2a5C287993345a840Db3B0845fbC70f5935a5; + address internal constant imUsdToken = + 0x30647a72Dc82d7Fbb1123EA74716aB8A317Eac19; + address internal constant imUsdVault = + 0x78BefCa7de27d07DC6e71da295Cc2946681A6c7B; +} diff --git a/contracts/mainnet/mstable/interface.sol b/contracts/mainnet/mstable/interface.sol new file mode 100644 index 00000000..a4e3531e --- /dev/null +++ b/contracts/mainnet/mstable/interface.sol @@ -0,0 +1,381 @@ +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 + ); +} + +abstract contract IFeederPool { + // Mint + function mint( + address _input, + uint256 _inputQuantity, + uint256 _minOutputQuantity, + address _recipient + ) external virtual returns (uint256 mintOutput); + + function mintMulti( + address[] calldata _inputs, + uint256[] calldata _inputQuantities, + uint256 _minOutputQuantity, + address _recipient + ) external virtual returns (uint256 mintOutput); + + function getMintOutput(address _input, uint256 _inputQuantity) + external + view + virtual + returns (uint256 mintOutput); + + function getMintMultiOutput( + address[] calldata _inputs, + uint256[] calldata _inputQuantities + ) external view virtual returns (uint256 mintOutput); + + // Swaps + function swap( + address _input, + address _output, + uint256 _inputQuantity, + uint256 _minOutputQuantity, + address _recipient + ) external virtual returns (uint256 swapOutput); + + function getSwapOutput( + address _input, + address _output, + uint256 _inputQuantity + ) external view virtual returns (uint256 swapOutput); + + // Redemption + function redeem( + address _output, + uint256 _fpTokenQuantity, + uint256 _minOutputQuantity, + address _recipient + ) external virtual returns (uint256 outputQuantity); + + function redeemProportionately( + uint256 _fpTokenQuantity, + uint256[] calldata _minOutputQuantities, + address _recipient + ) external virtual returns (uint256[] memory outputQuantities); + + function redeemExactBassets( + address[] calldata _outputs, + uint256[] calldata _outputQuantities, + uint256 _maxMassetQuantity, + address _recipient + ) external virtual returns (uint256 mAssetRedeemed); + + function getRedeemOutput(address _output, uint256 _fpTokenQuantity) + external + view + virtual + returns (uint256 bAssetOutput); + + function getRedeemExactBassetsOutput( + address[] calldata _outputs, + uint256[] calldata _outputQuantities + ) external view virtual returns (uint256 mAssetAmount); + + // Views + function mAsset() external view virtual returns (address); + + function getPrice() public view virtual 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 + ); +} diff --git a/contracts/mainnet/mstable/main.sol b/contracts/mainnet/mstable/main.sol new file mode 100644 index 00000000..5e938501 --- /dev/null +++ b/contracts/mainnet/mstable/main.sol @@ -0,0 +1,394 @@ +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, ISavingsContractV2, IBoostedSavingsVault, IFeederPool } from "./interface.sol"; +import { TokenInterface } from "../common/interfaces.sol"; + +abstract contract mStableResolver is Events, Helpers { + // + /*************************************** + CORE + ****************************************/ + + /** + * @dev Deposit to Save via mUSD + * @notice Deposits token supported by mStable to Save + * @param _token Address of token to deposit + * @param _amount Amount of token to deposit + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function deposit(address _token, uint256 _amount) + external + returns (string memory _eventName, bytes memory _eventParam) + { + return _deposit(_token, _amount, imUsdToken); + } + + /** + * @dev Deposit to Save via bAsset + * @notice Deposits token, requires _minOut for minting + * @param _token Address of token to deposit + * @param _amount Amount of token to deposit + * @param _minOut Minimum amount of token to mint + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function depositViaMint( + address _token, + uint256 _amount, + uint256 _minOut + ) external returns (string memory _eventName, bytes memory _eventParam) { + // + require( + IMasset(mUsdToken).bAssetIndexes(_token) != 0, + "Token not a bAsset" + ); + + approve(TokenInterface(_token), mUsdToken, _amount); + uint256 mintedAmount = IMasset(mUsdToken).mint( + _token, + _amount, + _minOut, + address(this) + ); + + return _deposit(_token, mintedAmount, mUsdToken); + } + + /** + * @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 + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function depositViaSwap( + address _token, + uint256 _amount, + uint256 _minOut, + address _path + ) external returns (string memory _eventName, bytes memory _eventParam) { + // + require(_path != address(0), "Path must be set"); + require( + IMasset(mUsdToken).bAssetIndexes(_token) == 0, + "Token is bAsset" + ); + + approve(TokenInterface(_token), _path, _amount); + uint256 mintedAmount = IFeederPool(_path).swap( + _token, + mUsdToken, + _amount, + _minOut, + address(this) + ); + return _deposit(_token, mintedAmount, _path); + } + + /** + * @dev Withdraw from Save to mUSD + * @notice Withdraws from Save Vault to mUSD + * @param _credits Credits to withdraw + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function withdraw(uint256 _credits) + external + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 amountWithdrawn = _withdraw(_credits); + + _eventName = "LogWithdraw()"; + _eventParam = abi.encode(mUsdToken, amountWithdrawn, imUsdToken); + } + + /** + * @dev Withdraw from Save to bAsset + * @notice Withdraws from Save Vault to bAsset + * @param _token bAsset to withdraw to + * @param _credits Credits to withdraw + * @param _minOut Minimum amount of token to mint + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function withdrawViaRedeem( + address _token, + uint256 _credits, + uint256 _minOut + ) external returns (string memory _eventName, bytes memory _eventParam) { + // + require( + IMasset(mUsdToken).bAssetIndexes(_token) != 0, + "Token not a bAsset" + ); + + uint256 amountWithdrawn = _withdraw(_credits); + uint256 amountRedeemed = IMasset(mUsdToken).redeem( + _token, + amountWithdrawn, + _minOut, + address(this) + ); + + _eventName = "LogRedeem()"; + _eventParam = abi.encode(mUsdToken, amountRedeemed, _token); + } + + /** + * @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 + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function withdrawViaSwap( + address _token, + uint256 _credits, + uint256 _minOut, + address _path + ) external 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 amountWithdrawn = _withdraw(_credits); + + approve(TokenInterface(mUsdToken), _path, amountWithdrawn); + uint256 amountRedeemed = IFeederPool(_path).swap( + mUsdToken, + _token, + amountWithdrawn, + _minOut, + address(this) + ); + + _eventName = "LogRedeem()"; + _eventParam = abi.encode(_token, amountRedeemed, _path); + } + + /** + * @dev Claims Rewards + * @notice Claims accrued rewards from the Vault + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function claimRewards() + external + 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); + + _eventName = "LogClaimRewards()"; + _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 + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function swap( + address _input, + address _output, + uint256 _amount, + uint256 _minOut + ) external returns (string memory _eventName, bytes memory _eventParam) { + // + 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) + ); + } + + _eventName = "LogSwap()"; + _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 + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function swapViaFeeder( + address _input, + address _output, + uint256 _amount, + uint256 _minOut, + address _path + ) external returns (string memory _eventName, bytes memory _eventParam) { + // + uint256 amountSwapped; + + 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) + ); + + _eventName = "LogSwap()"; + _eventParam = abi.encode(_input, _output, _amount, amountSwapped); + } + + /*************************************** + 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) + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function _deposit( + address _token, + uint256 _amount, + address _path + ) internal returns (string memory _eventName, bytes memory _eventParam) { + // + // 1. Deposit mUSD to Save + approve(TokenInterface(mUsdToken), imUsdToken, _amount); + uint256 credits = ISavingsContractV2(imUsdToken).depositSavings( + _amount + ); + + // 2. Stake imUSD to Vault + approve(TokenInterface(imUsdToken), imUsdVault, credits); + IBoostedSavingsVault(imUsdVault).stake(credits); + + // 3. Log Events + _eventName = "LogDeposit()"; + _eventParam = abi.encode(_token, _amount, _path); + } + + /** + * @dev Withdraws from Save + * @notice Withdraws token supported by mStable from Save + * @param _credits Credits to withdraw + * @return amountWithdrawn Amount withdrawn in mUSD + */ + + function _withdraw(uint256 _credits) + internal + returns (uint256 amountWithdrawn) + { + // 1. Withdraw from Vault + // approve(TokenInterface(imUsdVault), imUsdToken, _credits); + IBoostedSavingsVault(imUsdVault).withdraw(_credits); + + // 2. Withdraw from Save + 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)); + } +} + +contract ConnectV2mStable is mStableResolver { + string public constant name = "mStable-Mainnet-Connector-v1"; +} From b221c92d46e1deba381c2c36726eca5d6ee7cd2c Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 30 Dec 2021 17:43:01 +0700 Subject: [PATCH 10/21] chore: cleanup --- contracts/polygon/connectors/mstable/events.sol | 1 - contracts/polygon/connectors/mstable/main.sol | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/contracts/polygon/connectors/mstable/events.sol b/contracts/polygon/connectors/mstable/events.sol index 14e8bd7c..dcbbbe68 100644 --- a/contracts/polygon/connectors/mstable/events.sol +++ b/contracts/polygon/connectors/mstable/events.sol @@ -1,7 +1,6 @@ pragma solidity ^0.7.6; contract Events { - // TODO: Events go here event LogDeposit(address token, uint256 amount, address path); event LogWithdraw(address token, uint256 amount, address path); event LogClaimRewards( diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index cf952804..532a98e9 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -10,9 +10,7 @@ import { Events } from "./events.sol"; import { IMasset, ISavingsContractV2, IStakingRewardsWithPlatformToken, IFeederPool } from "./interface.sol"; import { TokenInterface } from "../../common/interfaces.sol"; -import "hardhat/console.sol"; - -abstract contract mStableResolver is Events, Helpers { +abstract contract PmStableResolver is Events, Helpers { // /*************************************** CORE @@ -415,6 +413,6 @@ abstract contract mStableResolver is Events, Helpers { } } -contract ConnectV2mStable is mStableResolver { +contract ConnectV2PmStable is PmStableResolver { string public constant name = "mStable-Polygon-Connector-v1"; } From f354f2112afb7a45834877c357469a6a7cd340ea Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 30 Dec 2021 17:43:22 +0700 Subject: [PATCH 11/21] chore: add mainnet tests --- test/mainnet/mstable/mstable.helpers.ts | 160 ++++++++++++++ test/mainnet/mstable/mstable.test.ts | 269 ++++++++++++++++++++++++ test/mainnet/mstable/mstable.utils.ts | 137 ++++++++++++ 3 files changed, 566 insertions(+) create mode 100644 test/mainnet/mstable/mstable.helpers.ts create mode 100644 test/mainnet/mstable/mstable.test.ts create mode 100644 test/mainnet/mstable/mstable.utils.ts diff --git a/test/mainnet/mstable/mstable.helpers.ts b/test/mainnet/mstable/mstable.helpers.ts new file mode 100644 index 00000000..3558b648 --- /dev/null +++ b/test/mainnet/mstable/mstable.helpers.ts @@ -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 = "0x0000000000000000000000000000000000000000"; + +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 => { + 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 => ethers.provider.send("evm_mine", []); + +export const increaseTime = async (length: BN | number): Promise => { + await ethers.provider.send("evm_increaseTime", [BN.from(length).toNumber()]); + await advanceBlock(); +}; diff --git a/test/mainnet/mstable/mstable.test.ts b/test/mainnet/mstable/mstable.test.ts new file mode 100644 index 00000000..99222b42 --- /dev/null +++ b/test/mainnet/mstable/mstable.test.ts @@ -0,0 +1,269 @@ +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); + + await executeAndAssertDeposit("deposit", mUsdToken, depositAmount, dsaWallet0, wallet0); + }); + it("Should deposit DAI to Vault successfully (mUSD bAsset)", async () => { + const depositAmount = simpleToExactAmount(100); + const minOut = calcMinOut(depositAmount, 0.02); + + await executeAndAssertDeposit("depositViaMint", daiToken, depositAmount, dsaWallet0, wallet0, [minOut]); + }); + 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]); + }); + it("Should withdraw from Vault to mUSD", async () => { + const withdrawAmount = simpleToExactAmount(100); + + await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [withdrawAmount]); + }); + it("Should withdraw from Vault to DAI (mUSD bAsset)", async () => { + const withdrawAmount = simpleToExactAmount(100); + const minOut = simpleToExactAmount(1); + + const daiBalanceBefore = await daiToken.balanceOf(dsaWallet0.address); + console.log("DAI balance before: ", toEther(daiBalanceBefore)); + + const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); + + const spells = [ + { + connector: connectorName, + method: "withdrawViaRedeem", + args: [daiToken.address, withdrawAmount, minOut] + } + ]; + + 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 daiBalanceAfter = await daiToken.balanceOf(dsaWallet0.address); + console.log("DAI balance after: ", toEther(daiBalanceAfter)); + + expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); + expect(daiBalanceAfter).to.gt(daiBalanceBefore); + }); + it("Should withdraw from Vault to alUSD (via Feeder Pool)", async () => { + const withdrawAmount = simpleToExactAmount(100); + const minOut = simpleToExactAmount(1); + + const alusdBalanceBefore = await alusdToken.balanceOf(dsaWallet0.address); + console.log("Balance before: ", toEther(alusdBalanceBefore)); + + const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); + + const spells = [ + { + connector: connectorName, + method: "withdrawViaSwap", + args: [alusdToken.address, withdrawAmount, minOut, getToken("alUSD").feederPool] + } + ]; + + 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 alusdBalanceAfter = await alusdToken.balanceOf(dsaWallet0.address); + console.log("alUSD balance after: ", toEther(alusdBalanceAfter)); + + expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); + expect(alusdBalanceAfter).to.gt(alusdBalanceBefore); + }); + 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: [] + } + ]; + + 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 + ]); + }); + }); + }); +}); diff --git a/test/mainnet/mstable/mstable.utils.ts b/test/mainnet/mstable/mstable.utils.ts new file mode 100644 index 00000000..6f51be22 --- /dev/null +++ b/test/mainnet/mstable/mstable.utils.ts @@ -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 : [])] + } + ]; + + 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 : [])] + } + ]; + + 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, + tokenFrom: IERC20Minimal, + withdrawAmount: BigNumber, + dsaWallet0: Contract, + wallet0: Wallet, + args: any[] +) => { + const mUsdBalanceBefore = await tokenFrom.balanceOf(dsaWallet0.address); + console.log("Balance before: ", toEther(mUsdBalanceBefore)); + + const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); + console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); + + const spells = [ + { + connector: connectorName, + method, + args + } + ]; + + 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 mUsdBalanceAfter = await tokenFrom.balanceOf(dsaWallet0.address); + console.log("Balance after: ", toEther(mUsdBalanceAfter)); + + expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); + expect(mUsdBalanceAfter).to.gt(mUsdBalanceBefore); +}; From b8fc97f6c017fde105c9e71254f6b274052574fc Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 30 Dec 2021 17:43:37 +0700 Subject: [PATCH 12/21] chore: cleanup --- test/polygon/mstable/mstable.test.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/polygon/mstable/mstable.test.ts b/test/polygon/mstable/mstable.test.ts index 5bf87f22..adf56dbb 100644 --- a/test/polygon/mstable/mstable.test.ts +++ b/test/polygon/mstable/mstable.test.ts @@ -1,20 +1,18 @@ import { expect } from "chai"; import hre from "hardhat"; -const { web3, deployments, waffle, ethers } = hre; -const { provider, deployContract } = waffle; +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 { addLiquidity } from "../../../scripts/tests/addLiquidity"; import { addresses } from "../../../scripts/tests/polygon/addresses"; import { abis } from "../../../scripts/constant/abis"; -import { tokens } from "../../../scripts/tests/polygon/tokens"; -import type { Signer, Contract, BigNumber } from "ethers"; +import type { Signer, Contract } from "ethers"; -import { ConnectV2mStable__factory, IERC20Minimal__factory, IERC20Minimal } from "../../../typechain"; +import { ConnectV2PmStable__factory, IERC20Minimal__factory, IERC20Minimal } from "../../../typechain"; import { executeAndAssertDeposit, executeAndAssertSwap, executeAndAssertWithdraw } from "./mstable.utils"; @@ -26,7 +24,6 @@ import { calcMinOut, ONE_DAY, increaseTime, - DEFAULT_DECIMALS, connectorName, toEther } from "./mstable.helpers"; @@ -70,7 +67,7 @@ describe("MStable", async () => { instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); connector = await deployAndEnableConnector({ connectorName, - contractArtifact: ConnectV2mStable__factory, + contractArtifact: ConnectV2PmStable__factory, signer: masterSigner, connectors: instaConnectorsV2 }); From 531ac64ba50e845d8de71d48d24d0a6e9db9ed33 Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Sun, 2 Jan 2022 15:28:40 +0700 Subject: [PATCH 13/21] chore: consolidate functions to deposit and swap --- .../{ => connectors}/mstable/events.sol | 0 .../{ => connectors}/mstable/helpers.sol | 4 +- .../{ => connectors}/mstable/interface.sol | 0 .../mainnet/{ => connectors}/mstable/main.sol | 105 ++++++------------ contracts/polygon/connectors/mstable/main.sol | 104 ++++++----------- test/mainnet/mstable/mstable.test.ts | 62 ++--------- test/mainnet/mstable/mstable.utils.ts | 14 +-- test/polygon/mstable/mstable.test.ts | 62 ++--------- test/polygon/mstable/mstable.utils.ts | 14 +-- 9 files changed, 111 insertions(+), 254 deletions(-) rename contracts/mainnet/{ => connectors}/mstable/events.sol (100%) rename contracts/mainnet/{ => connectors}/mstable/helpers.sol (77%) rename contracts/mainnet/{ => connectors}/mstable/interface.sol (100%) rename contracts/mainnet/{ => connectors}/mstable/main.sol (82%) diff --git a/contracts/mainnet/mstable/events.sol b/contracts/mainnet/connectors/mstable/events.sol similarity index 100% rename from contracts/mainnet/mstable/events.sol rename to contracts/mainnet/connectors/mstable/events.sol diff --git a/contracts/mainnet/mstable/helpers.sol b/contracts/mainnet/connectors/mstable/helpers.sol similarity index 77% rename from contracts/mainnet/mstable/helpers.sol rename to contracts/mainnet/connectors/mstable/helpers.sol index f98c9688..8100d0a1 100644 --- a/contracts/mainnet/mstable/helpers.sol +++ b/contracts/mainnet/connectors/mstable/helpers.sol @@ -1,7 +1,7 @@ pragma solidity ^0.7.6; -import { DSMath } from "../common/math.sol"; -import { Basic } from "../common/basic.sol"; +import { DSMath } from "../../common/math.sol"; +import { Basic } from "../../common/basic.sol"; abstract contract Helpers is DSMath, Basic { address internal constant mUsdToken = diff --git a/contracts/mainnet/mstable/interface.sol b/contracts/mainnet/connectors/mstable/interface.sol similarity index 100% rename from contracts/mainnet/mstable/interface.sol rename to contracts/mainnet/connectors/mstable/interface.sol diff --git a/contracts/mainnet/mstable/main.sol b/contracts/mainnet/connectors/mstable/main.sol similarity index 82% rename from contracts/mainnet/mstable/main.sol rename to contracts/mainnet/connectors/mstable/main.sol index 5e938501..05080c3d 100644 --- a/contracts/mainnet/mstable/main.sol +++ b/contracts/mainnet/connectors/mstable/main.sol @@ -8,7 +8,7 @@ pragma solidity ^0.7.6; import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; import { IMasset, ISavingsContractV2, IBoostedSavingsVault, IFeederPool } from "./interface.sol"; -import { TokenInterface } from "../common/interfaces.sol"; +import { TokenInterface } from "../../common/interfaces.sol"; abstract contract mStableResolver is Events, Helpers { // @@ -17,51 +17,38 @@ abstract contract mStableResolver is Events, Helpers { ****************************************/ /** - * @dev Deposit to Save via mUSD + * @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 * @return _eventName Event name * @return _eventParam Event parameters */ - function deposit(address _token, uint256 _amount) - external - returns (string memory _eventName, bytes memory _eventParam) - { - return _deposit(_token, _amount, imUsdToken); - } - - /** - * @dev Deposit to Save via bAsset - * @notice Deposits token, requires _minOut for minting - * @param _token Address of token to deposit - * @param _amount Amount of token to deposit - * @param _minOut Minimum amount of token to mint - * @return _eventName Event name - * @return _eventParam Event parameters - */ - - function depositViaMint( + function deposit( address _token, uint256 _amount, uint256 _minOut ) external returns (string memory _eventName, bytes memory _eventParam) { // - require( - IMasset(mUsdToken).bAssetIndexes(_token) != 0, - "Token not a bAsset" - ); + uint256 mintedAmount = _amount; - approve(TokenInterface(_token), mUsdToken, _amount); - uint256 mintedAmount = IMasset(mUsdToken).mint( - _token, - _amount, - _minOut, - address(this) - ); + // 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) + ); + } else { + require(mintedAmount >= _minOut, "mintedAmount < _minOut"); + } - return _deposit(_token, mintedAmount, mUsdToken); + return _deposit(_token, mintedAmount, imUsdToken); } /** @@ -100,54 +87,36 @@ abstract contract mStableResolver is Events, Helpers { } /** - * @dev Withdraw from Save to mUSD + * @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 * @return _eventName Event name * @return _eventParam Event parameters */ - function withdraw(uint256 _credits) - external - returns (string memory _eventName, bytes memory _eventParam) - { - uint256 amountWithdrawn = _withdraw(_credits); - - _eventName = "LogWithdraw()"; - _eventParam = abi.encode(mUsdToken, amountWithdrawn, imUsdToken); - } - - /** - * @dev Withdraw from Save to bAsset - * @notice Withdraws from Save Vault to bAsset - * @param _token bAsset to withdraw to - * @param _credits Credits to withdraw - * @param _minOut Minimum amount of token to mint - * @return _eventName Event name - * @return _eventParam Event parameters - */ - - function withdrawViaRedeem( + function withdraw( address _token, uint256 _credits, uint256 _minOut ) external returns (string memory _eventName, bytes memory _eventParam) { - // - require( - IMasset(mUsdToken).bAssetIndexes(_token) != 0, - "Token not a bAsset" - ); - uint256 amountWithdrawn = _withdraw(_credits); - uint256 amountRedeemed = IMasset(mUsdToken).redeem( - _token, - amountWithdrawn, - _minOut, - address(this) - ); - _eventName = "LogRedeem()"; - _eventParam = abi.encode(mUsdToken, amountRedeemed, _token); + // 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"); + } + + _eventName = "LogWithdraw()"; + _eventParam = abi.encode(mUsdToken, amountWithdrawn, imUsdToken); } /** diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index 532a98e9..4e69d843 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -17,51 +17,38 @@ abstract contract PmStableResolver is Events, Helpers { ****************************************/ /** - * @dev Deposit to Save via mUSD + * @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 * @return _eventName Event name * @return _eventParam Event parameters */ - function deposit(address _token, uint256 _amount) - external - returns (string memory _eventName, bytes memory _eventParam) - { - return _deposit(_token, _amount, imUsdToken); - } - - /** - * @dev Deposit to Save via bAsset - * @notice Deposits token, requires _minOut for minting - * @param _token Address of token to deposit - * @param _amount Amount of token to deposit - * @param _minOut Minimum amount of token to mint - * @return _eventName Event name - * @return _eventParam Event parameters - */ - - function depositViaMint( + function deposit( address _token, uint256 _amount, uint256 _minOut ) external returns (string memory _eventName, bytes memory _eventParam) { // - require( - IMasset(mUsdToken).bAssetIndexes(_token) != 0, - "Token not a bAsset" - ); + uint256 mintedAmount = _amount; - approve(TokenInterface(_token), mUsdToken, _amount); - uint256 mintedAmount = IMasset(mUsdToken).mint( - _token, - _amount, - _minOut, - address(this) - ); + // 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) + ); + } else { + require(mintedAmount >= _minOut, "mintedAmount < _minOut"); + } - return _deposit(_token, mintedAmount, mUsdToken); + return _deposit(_token, mintedAmount, imUsdToken); } /** @@ -100,54 +87,36 @@ abstract contract PmStableResolver is Events, Helpers { } /** - * @dev Withdraw from Save to mUSD + * @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 * @return _eventName Event name * @return _eventParam Event parameters */ - function withdraw(uint256 _credits) - external - returns (string memory _eventName, bytes memory _eventParam) - { - uint256 amountWithdrawn = _withdraw(_credits); - - _eventName = "LogWithdraw()"; - _eventParam = abi.encode(mUsdToken, amountWithdrawn, imUsdToken); - } - - /** - * @dev Withdraw from Save to bAsset - * @notice Withdraws from Save Vault to bAsset - * @param _token bAsset to withdraw to - * @param _credits Credits to withdraw - * @param _minOut Minimum amount of token to mint - * @return _eventName Event name - * @return _eventParam Event parameters - */ - - function withdrawViaRedeem( + function withdraw( address _token, uint256 _credits, uint256 _minOut ) external returns (string memory _eventName, bytes memory _eventParam) { - // - require( - IMasset(mUsdToken).bAssetIndexes(_token) != 0, - "Token not a bAsset" - ); - uint256 amountWithdrawn = _withdraw(_credits); - uint256 amountRedeemed = IMasset(mUsdToken).redeem( - _token, - amountWithdrawn, - _minOut, - address(this) - ); - _eventName = "LogRedeem()"; - _eventParam = abi.encode(mUsdToken, amountRedeemed, _token); + // 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"); + } + + _eventName = "LogWithdraw()"; + _eventParam = abi.encode(mUsdToken, amountWithdrawn, imUsdToken); } /** @@ -367,7 +336,6 @@ abstract contract PmStableResolver is Events, Helpers { returns (uint256 amountWithdrawn) { // 1. Withdraw from Vault - // approve(TokenInterface(imUsdVault), imUsdToken, _credits); IStakingRewardsWithPlatformToken(imUsdVault).withdraw(_credits); // 2. Withdraw from Save diff --git a/test/mainnet/mstable/mstable.test.ts b/test/mainnet/mstable/mstable.test.ts index 99222b42..53bb6dfc 100644 --- a/test/mainnet/mstable/mstable.test.ts +++ b/test/mainnet/mstable/mstable.test.ts @@ -118,14 +118,15 @@ describe("MStable", async () => { }); it("Should deposit mUSD to Vault successfully", async () => { const depositAmount = simpleToExactAmount(100); + const minOut = depositAmount; - await executeAndAssertDeposit("deposit", mUsdToken, depositAmount, dsaWallet0, wallet0); + await executeAndAssertDeposit("deposit", mUsdToken, depositAmount, dsaWallet0, wallet0, [minOut]); }); it("Should deposit DAI to Vault successfully (mUSD bAsset)", async () => { const depositAmount = simpleToExactAmount(100); const minOut = calcMinOut(depositAmount, 0.02); - await executeAndAssertDeposit("depositViaMint", daiToken, depositAmount, dsaWallet0, wallet0, [minOut]); + await executeAndAssertDeposit("deposit", daiToken, depositAmount, dsaWallet0, wallet0, [minOut]); }); it("Should deposit alUSD to Vault successfully (via Feeder Pool)", async () => { const depositAmount = simpleToExactAmount(100); @@ -136,66 +137,25 @@ describe("MStable", async () => { }); it("Should withdraw from Vault to mUSD", async () => { const withdrawAmount = simpleToExactAmount(100); + const minOut = simpleToExactAmount(1); - await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [withdrawAmount]); + await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [minOut]); }); it("Should withdraw from Vault to DAI (mUSD bAsset)", async () => { const withdrawAmount = simpleToExactAmount(100); const minOut = simpleToExactAmount(1); - const daiBalanceBefore = await daiToken.balanceOf(dsaWallet0.address); - console.log("DAI balance before: ", toEther(daiBalanceBefore)); - - const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); - console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); - - const spells = [ - { - connector: connectorName, - method: "withdrawViaRedeem", - args: [daiToken.address, withdrawAmount, minOut] - } - ]; - - 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 daiBalanceAfter = await daiToken.balanceOf(dsaWallet0.address); - console.log("DAI balance after: ", toEther(daiBalanceAfter)); - - expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); - expect(daiBalanceAfter).to.gt(daiBalanceBefore); + await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [minOut]); }); it("Should withdraw from Vault to alUSD (via Feeder Pool)", async () => { const withdrawAmount = simpleToExactAmount(100); const minOut = simpleToExactAmount(1); + const path = getToken("alUSD").feederPool; - const alusdBalanceBefore = await alusdToken.balanceOf(dsaWallet0.address); - console.log("Balance before: ", toEther(alusdBalanceBefore)); - - const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); - console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); - - const spells = [ - { - connector: connectorName, - method: "withdrawViaSwap", - args: [alusdToken.address, withdrawAmount, minOut, getToken("alUSD").feederPool] - } - ]; - - 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 alusdBalanceAfter = await alusdToken.balanceOf(dsaWallet0.address); - console.log("alUSD balance after: ", toEther(alusdBalanceAfter)); - - expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); - expect(alusdBalanceAfter).to.gt(alusdBalanceBefore); + await executeAndAssertWithdraw("withdrawViaSwap", alusdToken, withdrawAmount, dsaWallet0, wallet0, [ + minOut, + path + ]); }); it("Should claim Rewards", async () => { const mtaBalanceBefore = await mtaToken.balanceOf(dsaWallet0.address); diff --git a/test/mainnet/mstable/mstable.utils.ts b/test/mainnet/mstable/mstable.utils.ts index 6f51be22..2614bff8 100644 --- a/test/mainnet/mstable/mstable.utils.ts +++ b/test/mainnet/mstable/mstable.utils.ts @@ -104,14 +104,14 @@ export const executeAndAssertDeposit = async ( export const executeAndAssertWithdraw = async ( method: string, - tokenFrom: IERC20Minimal, + tokenTo: IERC20Minimal, withdrawAmount: BigNumber, dsaWallet0: Contract, wallet0: Wallet, args: any[] ) => { - const mUsdBalanceBefore = await tokenFrom.balanceOf(dsaWallet0.address); - console.log("Balance before: ", toEther(mUsdBalanceBefore)); + 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)); @@ -120,7 +120,7 @@ export const executeAndAssertWithdraw = async ( { connector: connectorName, method, - args + args: [tokenTo.address, withdrawAmount, ...(args ? args : [])] } ]; @@ -129,9 +129,9 @@ export const executeAndAssertWithdraw = async ( const imUsdVaultBalanceAfter = await imUsdVault.balanceOf(dsaWallet0.address); console.log("imUSD Vault balance after: ", toEther(imUsdVaultBalanceAfter)); - const mUsdBalanceAfter = await tokenFrom.balanceOf(dsaWallet0.address); - console.log("Balance after: ", toEther(mUsdBalanceAfter)); + const tokenToBalanceAfter = await tokenTo.balanceOf(dsaWallet0.address); + console.log("Balance after: ", toEther(tokenToBalanceAfter)); expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); - expect(mUsdBalanceAfter).to.gt(mUsdBalanceBefore); + expect(tokenToBalanceAfter).to.gt(tokenToBalanceBefore); }; diff --git a/test/polygon/mstable/mstable.test.ts b/test/polygon/mstable/mstable.test.ts index adf56dbb..2198e288 100644 --- a/test/polygon/mstable/mstable.test.ts +++ b/test/polygon/mstable/mstable.test.ts @@ -118,14 +118,15 @@ describe("MStable", async () => { }); it("Should deposit mUSD to Vault successfully", async () => { const depositAmount = simpleToExactAmount(100); + const minOut = depositAmount; - await executeAndAssertDeposit("deposit", mUsdToken, depositAmount, dsaWallet0, wallet0); + await executeAndAssertDeposit("deposit", mUsdToken, depositAmount, dsaWallet0, wallet0, [minOut]); }); it("Should deposit DAI to Vault successfully (mUSD bAsset)", async () => { const depositAmount = simpleToExactAmount(100); const minOut = calcMinOut(depositAmount, 0.02); - await executeAndAssertDeposit("depositViaMint", daiToken, depositAmount, dsaWallet0, wallet0, [minOut]); + await executeAndAssertDeposit("deposit", daiToken, depositAmount, dsaWallet0, wallet0, [minOut]); }); it("Should deposit FRAX to Vault successfully (via Feeder Pool)", async () => { const depositAmount = simpleToExactAmount(100); @@ -136,66 +137,25 @@ describe("MStable", async () => { }); it("Should withdraw from Vault to mUSD", async () => { const withdrawAmount = simpleToExactAmount(100); + const minOut = simpleToExactAmount(1); - await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [withdrawAmount]); + await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [minOut]); }); it("Should withdraw from Vault to DAI (mUSD bAsset)", async () => { const withdrawAmount = simpleToExactAmount(100); const minOut = simpleToExactAmount(1); - const daiBalanceBefore = await daiToken.balanceOf(dsaWallet0.address); - console.log("DAI balance before: ", toEther(daiBalanceBefore)); - - const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); - console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); - - const spells = [ - { - connector: connectorName, - method: "withdrawViaRedeem", - args: [daiToken.address, withdrawAmount, minOut] - } - ]; - - 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 daiBalanceAfter = await daiToken.balanceOf(dsaWallet0.address); - console.log("DAI balance after: ", toEther(daiBalanceAfter)); - - expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); - expect(daiBalanceAfter).to.gt(daiBalanceBefore); + await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [minOut]); }); it("Should withdraw from Vault to FRAX (via Feeder Pool)", async () => { const withdrawAmount = simpleToExactAmount(100); const minOut = simpleToExactAmount(1); + const path = getToken("FRAX").feederPool; - const fraxBalanceBefore = await fraxToken.balanceOf(dsaWallet0.address); - console.log("FRAX balance before: ", toEther(fraxBalanceBefore)); - - const imUsdVaultBalanceBefore = await imUsdVault.balanceOf(dsaWallet0.address); - console.log("imUSD Vault balance before: ", toEther(imUsdVaultBalanceBefore)); - - const spells = [ - { - connector: connectorName, - method: "withdrawViaSwap", - args: [fraxToken.address, withdrawAmount, minOut, getToken("FRAX").feederPool] - } - ]; - - 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 fraxBalanceAfter = await fraxToken.balanceOf(dsaWallet0.address); - console.log("FRAX balance after: ", toEther(fraxBalanceAfter)); - - expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); - expect(fraxBalanceAfter).to.gt(fraxBalanceBefore); + await executeAndAssertWithdraw("withdrawViaSwap", fraxToken, withdrawAmount, dsaWallet0, wallet0, [ + minOut, + path + ]); }); it("Should claim Rewards", async () => { const mtaBalanceBefore = await mtaToken.balanceOf(dsaWallet0.address); diff --git a/test/polygon/mstable/mstable.utils.ts b/test/polygon/mstable/mstable.utils.ts index 6f51be22..2614bff8 100644 --- a/test/polygon/mstable/mstable.utils.ts +++ b/test/polygon/mstable/mstable.utils.ts @@ -104,14 +104,14 @@ export const executeAndAssertDeposit = async ( export const executeAndAssertWithdraw = async ( method: string, - tokenFrom: IERC20Minimal, + tokenTo: IERC20Minimal, withdrawAmount: BigNumber, dsaWallet0: Contract, wallet0: Wallet, args: any[] ) => { - const mUsdBalanceBefore = await tokenFrom.balanceOf(dsaWallet0.address); - console.log("Balance before: ", toEther(mUsdBalanceBefore)); + 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)); @@ -120,7 +120,7 @@ export const executeAndAssertWithdraw = async ( { connector: connectorName, method, - args + args: [tokenTo.address, withdrawAmount, ...(args ? args : [])] } ]; @@ -129,9 +129,9 @@ export const executeAndAssertWithdraw = async ( const imUsdVaultBalanceAfter = await imUsdVault.balanceOf(dsaWallet0.address); console.log("imUSD Vault balance after: ", toEther(imUsdVaultBalanceAfter)); - const mUsdBalanceAfter = await tokenFrom.balanceOf(dsaWallet0.address); - console.log("Balance after: ", toEther(mUsdBalanceAfter)); + const tokenToBalanceAfter = await tokenTo.balanceOf(dsaWallet0.address); + console.log("Balance after: ", toEther(tokenToBalanceAfter)); expect(imUsdVaultBalanceAfter).to.be.eq(imUsdVaultBalanceBefore.sub(withdrawAmount)); - expect(mUsdBalanceAfter).to.gt(mUsdBalanceBefore); + expect(tokenToBalanceAfter).to.gt(tokenToBalanceBefore); }; From fe76acb4278601b9d6cbec6fae2a26f4e431a3e2 Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Sun, 2 Jan 2022 15:39:18 +0700 Subject: [PATCH 14/21] chore: clean comments --- contracts/polygon/connectors/mstable/interface.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/polygon/connectors/mstable/interface.sol b/contracts/polygon/connectors/mstable/interface.sol index 9efdc5ef..61137ea9 100644 --- a/contracts/polygon/connectors/mstable/interface.sol +++ b/contracts/polygon/connectors/mstable/interface.sol @@ -1,7 +1,5 @@ pragma solidity ^0.7.6; -// TODO: Interfaces go here -// https://polygonscan.com/address/0xca9cf48ad534f1efa2b0f6923457f2953df86e0b#code interface IMasset { // Mint function mint( From b5a67e5b49f52875c77982175ce1e526a762a1ff Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Sun, 2 Jan 2022 15:44:36 +0700 Subject: [PATCH 15/21] fix: interface declaration --- .../mainnet/connectors/mstable/interface.sol | 26 +++++++++---------- .../polygon/connectors/mstable/interface.sol | 26 +++++++++---------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/contracts/mainnet/connectors/mstable/interface.sol b/contracts/mainnet/connectors/mstable/interface.sol index a4e3531e..cb58ea0e 100644 --- a/contracts/mainnet/connectors/mstable/interface.sol +++ b/contracts/mainnet/connectors/mstable/interface.sol @@ -216,32 +216,31 @@ interface IBoostedSavingsVault { ); } -abstract contract IFeederPool { +interface IFeederPool { // Mint function mint( address _input, uint256 _inputQuantity, uint256 _minOutputQuantity, address _recipient - ) external virtual returns (uint256 mintOutput); + ) external returns (uint256 mintOutput); function mintMulti( address[] calldata _inputs, uint256[] calldata _inputQuantities, uint256 _minOutputQuantity, address _recipient - ) external virtual returns (uint256 mintOutput); + ) external returns (uint256 mintOutput); function getMintOutput(address _input, uint256 _inputQuantity) external view - virtual returns (uint256 mintOutput); function getMintMultiOutput( address[] calldata _inputs, uint256[] calldata _inputQuantities - ) external view virtual returns (uint256 mintOutput); + ) external view returns (uint256 mintOutput); // Swaps function swap( @@ -250,13 +249,13 @@ abstract contract IFeederPool { uint256 _inputQuantity, uint256 _minOutputQuantity, address _recipient - ) external virtual returns (uint256 swapOutput); + ) external returns (uint256 swapOutput); function getSwapOutput( address _input, address _output, uint256 _inputQuantity - ) external view virtual returns (uint256 swapOutput); + ) external view returns (uint256 swapOutput); // Redemption function redeem( @@ -264,36 +263,35 @@ abstract contract IFeederPool { uint256 _fpTokenQuantity, uint256 _minOutputQuantity, address _recipient - ) external virtual returns (uint256 outputQuantity); + ) external returns (uint256 outputQuantity); function redeemProportionately( uint256 _fpTokenQuantity, uint256[] calldata _minOutputQuantities, address _recipient - ) external virtual returns (uint256[] memory outputQuantities); + ) external returns (uint256[] memory outputQuantities); function redeemExactBassets( address[] calldata _outputs, uint256[] calldata _outputQuantities, uint256 _maxMassetQuantity, address _recipient - ) external virtual returns (uint256 mAssetRedeemed); + ) external returns (uint256 mAssetRedeemed); function getRedeemOutput(address _output, uint256 _fpTokenQuantity) external view - virtual returns (uint256 bAssetOutput); function getRedeemExactBassetsOutput( address[] calldata _outputs, uint256[] calldata _outputQuantities - ) external view virtual returns (uint256 mAssetAmount); + ) external view returns (uint256 mAssetAmount); // Views - function mAsset() external view virtual returns (address); + function mAsset() external view returns (address); - function getPrice() public view virtual returns (uint256 price, uint256 k); + function getPrice() external view returns (uint256 price, uint256 k); } interface IERC20 { diff --git a/contracts/polygon/connectors/mstable/interface.sol b/contracts/polygon/connectors/mstable/interface.sol index 61137ea9..028b728f 100644 --- a/contracts/polygon/connectors/mstable/interface.sol +++ b/contracts/polygon/connectors/mstable/interface.sol @@ -155,32 +155,31 @@ interface IStakingRewardsWithPlatformToken { function getPlatformToken() external returns (address token); } -abstract contract IFeederPool { +interface IFeederPool { // Mint function mint( address _input, uint256 _inputQuantity, uint256 _minOutputQuantity, address _recipient - ) external virtual returns (uint256 mintOutput); + ) external returns (uint256 mintOutput); function mintMulti( address[] calldata _inputs, uint256[] calldata _inputQuantities, uint256 _minOutputQuantity, address _recipient - ) external virtual returns (uint256 mintOutput); + ) external returns (uint256 mintOutput); function getMintOutput(address _input, uint256 _inputQuantity) external view - virtual returns (uint256 mintOutput); function getMintMultiOutput( address[] calldata _inputs, uint256[] calldata _inputQuantities - ) external view virtual returns (uint256 mintOutput); + ) external view returns (uint256 mintOutput); // Swaps function swap( @@ -189,13 +188,13 @@ abstract contract IFeederPool { uint256 _inputQuantity, uint256 _minOutputQuantity, address _recipient - ) external virtual returns (uint256 swapOutput); + ) external returns (uint256 swapOutput); function getSwapOutput( address _input, address _output, uint256 _inputQuantity - ) external view virtual returns (uint256 swapOutput); + ) external view returns (uint256 swapOutput); // Redemption function redeem( @@ -203,34 +202,33 @@ abstract contract IFeederPool { uint256 _fpTokenQuantity, uint256 _minOutputQuantity, address _recipient - ) external virtual returns (uint256 outputQuantity); + ) external returns (uint256 outputQuantity); function redeemProportionately( uint256 _fpTokenQuantity, uint256[] calldata _minOutputQuantities, address _recipient - ) external virtual returns (uint256[] memory outputQuantities); + ) external returns (uint256[] memory outputQuantities); function redeemExactBassets( address[] calldata _outputs, uint256[] calldata _outputQuantities, uint256 _maxMassetQuantity, address _recipient - ) external virtual returns (uint256 mAssetRedeemed); + ) external returns (uint256 mAssetRedeemed); function getRedeemOutput(address _output, uint256 _fpTokenQuantity) external view - virtual returns (uint256 bAssetOutput); function getRedeemExactBassetsOutput( address[] calldata _outputs, uint256[] calldata _outputQuantities - ) external view virtual returns (uint256 mAssetAmount); + ) external view returns (uint256 mAssetAmount); // Views - function mAsset() external view virtual returns (address); + function mAsset() external view returns (address); - function getPrice() public view virtual returns (uint256 price, uint256 k); + function getPrice() external view returns (uint256 price, uint256 k); } From d1519c56fd822dde55ab27a9504920567acf40c1 Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 20 Jan 2022 12:31:42 +0700 Subject: [PATCH 16/21] fix: eventsParams --- contracts/mainnet/connectors/mstable/main.sol | 19 +++++++++++-------- contracts/polygon/connectors/mstable/main.sol | 19 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/contracts/mainnet/connectors/mstable/main.sol b/contracts/mainnet/connectors/mstable/main.sol index 05080c3d..7f4d3cab 100644 --- a/contracts/mainnet/connectors/mstable/main.sol +++ b/contracts/mainnet/connectors/mstable/main.sol @@ -33,6 +33,7 @@ abstract contract mStableResolver is Events, Helpers { ) external returns (string memory _eventName, bytes memory _eventParam) { // uint256 mintedAmount = _amount; + address path; // Check if needs to be minted first if (IMasset(mUsdToken).bAssetIndexes(_token) != 0) { @@ -44,11 +45,13 @@ abstract contract mStableResolver is Events, Helpers { _minOut, address(this) ); + path = mUsdToken; } else { require(mintedAmount >= _minOut, "mintedAmount < _minOut"); + path = imUsdToken } - return _deposit(_token, mintedAmount, imUsdToken); + return _deposit(_token, mintedAmount, path); } /** @@ -115,7 +118,7 @@ abstract contract mStableResolver is Events, Helpers { require(amountWithdrawn >= _minOut, "amountWithdrawn < _minOut"); } - _eventName = "LogWithdraw()"; + _eventName = "LogWithdraw(address,uint256,address)"; _eventParam = abi.encode(mUsdToken, amountWithdrawn, imUsdToken); } @@ -154,7 +157,7 @@ abstract contract mStableResolver is Events, Helpers { address(this) ); - _eventName = "LogRedeem()"; + _eventName = "LogWithdraw(address,uint256,address)"; _eventParam = abi.encode(_token, amountRedeemed, _path); } @@ -178,7 +181,7 @@ abstract contract mStableResolver is Events, Helpers { uint256 claimedRewardToken = sub(rewardAmountUpdated, rewardAmount); - _eventName = "LogClaimRewards()"; + _eventName = "LogClaimRewards(address,uint256)"; _eventParam = abi.encode(rewardToken, claimedRewardToken); } @@ -231,7 +234,7 @@ abstract contract mStableResolver is Events, Helpers { ); } - _eventName = "LogSwap()"; + _eventName = "LogSwap(address,address,uint256,uint256)"; _eventParam = abi.encode(_input, _output, _amount, amountSwapped); } @@ -269,7 +272,7 @@ abstract contract mStableResolver is Events, Helpers { address(this) ); - _eventName = "LogSwap()"; + _eventName = "LogSwap(address,address,uint256,uint256)"; _eventParam = abi.encode(_input, _output, _amount, amountSwapped); } @@ -304,7 +307,7 @@ abstract contract mStableResolver is Events, Helpers { IBoostedSavingsVault(imUsdVault).stake(credits); // 3. Log Events - _eventName = "LogDeposit()"; + _eventName = "LogDeposit(address,uint256,address)"; _eventParam = abi.encode(_token, _amount, _path); } @@ -359,5 +362,5 @@ abstract contract mStableResolver is Events, Helpers { } contract ConnectV2mStable is mStableResolver { - string public constant name = "mStable-Mainnet-Connector-v1"; + string public constant name = "mStable-v1.0"; } diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index 4e69d843..69b63e48 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -33,6 +33,7 @@ abstract contract PmStableResolver is Events, Helpers { ) external returns (string memory _eventName, bytes memory _eventParam) { // uint256 mintedAmount = _amount; + address path; // Check if needs to be minted first if (IMasset(mUsdToken).bAssetIndexes(_token) != 0) { @@ -44,11 +45,13 @@ abstract contract PmStableResolver is Events, Helpers { _minOut, address(this) ); + path = mUsdToken; } else { require(mintedAmount >= _minOut, "mintedAmount < _minOut"); + path = imUsdToken } - return _deposit(_token, mintedAmount, imUsdToken); + return _deposit(_token, mintedAmount, path); } /** @@ -115,7 +118,7 @@ abstract contract PmStableResolver is Events, Helpers { require(amountWithdrawn >= _minOut, "amountWithdrawn < _minOut"); } - _eventName = "LogWithdraw()"; + _eventName = "LogWithdraw(address,uint256,address)"; _eventParam = abi.encode(mUsdToken, amountWithdrawn, imUsdToken); } @@ -154,7 +157,7 @@ abstract contract PmStableResolver is Events, Helpers { address(this) ); - _eventName = "LogRedeem()"; + _eventName = "LogWithdraw(address,uint256,address)"; _eventParam = abi.encode(_token, amountRedeemed, _path); } @@ -189,7 +192,7 @@ abstract contract PmStableResolver is Events, Helpers { platformAmount ); - _eventName = "LogClaimRewards()"; + _eventName = "LogClaimRewards(address,uint256,address,uint256)"; _eventParam = abi.encode( rewardToken, claimedRewardToken, @@ -247,7 +250,7 @@ abstract contract PmStableResolver is Events, Helpers { ); } - _eventName = "LogSwap()"; + _eventName = "LogSwap(address,address,uint256,uint256)"; _eventParam = abi.encode(_input, _output, _amount, amountSwapped); } @@ -285,7 +288,7 @@ abstract contract PmStableResolver is Events, Helpers { address(this) ); - _eventName = "LogSwap()"; + _eventName = "LogSwap(address,address,uint256,uint256)"; _eventParam = abi.encode(_input, _output, _amount, amountSwapped); } @@ -320,7 +323,7 @@ abstract contract PmStableResolver is Events, Helpers { IStakingRewardsWithPlatformToken(imUsdVault).stake(credits); // 3. Log Events - _eventName = "LogDeposit()"; + _eventName = "LogDeposit(address,uint256,address)"; _eventParam = abi.encode(_token, _amount, _path); } @@ -382,5 +385,5 @@ abstract contract PmStableResolver is Events, Helpers { } contract ConnectV2PmStable is PmStableResolver { - string public constant name = "mStable-Polygon-Connector-v1"; + string public constant name = "mStable-Polygon-v1.0"; } From faf608d8259ede8b4a156b42ee25448961b39e5f Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 20 Jan 2022 12:43:06 +0700 Subject: [PATCH 17/21] fix: event returns --- contracts/mainnet/connectors/mstable/main.sol | 5 +++-- contracts/polygon/connectors/mstable/main.sol | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/mainnet/connectors/mstable/main.sol b/contracts/mainnet/connectors/mstable/main.sol index 7f4d3cab..659ca535 100644 --- a/contracts/mainnet/connectors/mstable/main.sol +++ b/contracts/mainnet/connectors/mstable/main.sol @@ -51,7 +51,7 @@ abstract contract mStableResolver is Events, Helpers { path = imUsdToken } - return _deposit(_token, mintedAmount, path); + (_eventName, _eventParam) = _deposit(_token, mintedAmount, path); } /** @@ -86,7 +86,8 @@ abstract contract mStableResolver is Events, Helpers { _minOut, address(this) ); - return _deposit(_token, mintedAmount, _path); + + (_eventName, _eventParam) = _deposit(_token, mintedAmount, _path); } /** diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index 69b63e48..a0f88de5 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -51,7 +51,7 @@ abstract contract PmStableResolver is Events, Helpers { path = imUsdToken } - return _deposit(_token, mintedAmount, path); + (_eventName, _eventParam) = _deposit(_token, mintedAmount, path); } /** @@ -86,7 +86,8 @@ abstract contract PmStableResolver is Events, Helpers { _minOut, address(this) ); - return _deposit(_token, mintedAmount, _path); + + (_eventName, _eventParam) = _deposit(_token, mintedAmount, _path); } /** From cc7f53860c2890e6200b1fb8bbdeb15577c1bdef Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 20 Jan 2022 13:56:13 +0700 Subject: [PATCH 18/21] fix: typo --- contracts/mainnet/connectors/mstable/main.sol | 2 +- contracts/polygon/connectors/mstable/main.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/mainnet/connectors/mstable/main.sol b/contracts/mainnet/connectors/mstable/main.sol index 659ca535..7e300e30 100644 --- a/contracts/mainnet/connectors/mstable/main.sol +++ b/contracts/mainnet/connectors/mstable/main.sol @@ -48,7 +48,7 @@ abstract contract mStableResolver is Events, Helpers { path = mUsdToken; } else { require(mintedAmount >= _minOut, "mintedAmount < _minOut"); - path = imUsdToken + path = imUsdToken; } (_eventName, _eventParam) = _deposit(_token, mintedAmount, path); diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index a0f88de5..1a63a698 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -48,7 +48,7 @@ abstract contract PmStableResolver is Events, Helpers { path = mUsdToken; } else { require(mintedAmount >= _minOut, "mintedAmount < _minOut"); - path = imUsdToken + path = imUsdToken; } (_eventName, _eventParam) = _deposit(_token, mintedAmount, path); @@ -86,7 +86,7 @@ abstract contract PmStableResolver is Events, Helpers { _minOut, address(this) ); - + (_eventName, _eventParam) = _deposit(_token, mintedAmount, _path); } From 9c83fd0924615e30ddab429c011181601490c8a2 Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Mon, 24 Jan 2022 22:55:44 +0700 Subject: [PATCH 19/21] chore: clean up --- .../mainnet/connectors/mstable/helpers.sol | 86 ++++++++++++++ contracts/mainnet/connectors/mstable/main.sol | 93 +--------------- .../polygon/connectors/mstable/helpers.sol | 105 +++++++++++++++--- contracts/polygon/connectors/mstable/main.sol | 99 +---------------- test/mainnet/mstable/mstable.helpers.ts | 2 +- test/polygon/mstable/mstable.helpers.ts | 2 +- 6 files changed, 183 insertions(+), 204 deletions(-) diff --git a/contracts/mainnet/connectors/mstable/helpers.sol b/contracts/mainnet/connectors/mstable/helpers.sol index 8100d0a1..dce9024f 100644 --- a/contracts/mainnet/connectors/mstable/helpers.sol +++ b/contracts/mainnet/connectors/mstable/helpers.sol @@ -3,6 +3,9 @@ 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; @@ -10,4 +13,87 @@ abstract contract Helpers is DSMath, Basic { 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) + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function _deposit( + address _token, + uint256 _amount, + address _path + ) internal returns (string memory _eventName, bytes memory _eventParam) { + // 1. Deposit mUSD to Save + approve(TokenInterface(mUsdToken), imUsdToken, _amount); + uint256 credits = ISavingsContractV2(imUsdToken).depositSavings( + _amount + ); + + // 2. Stake imUSD to Vault + approve(TokenInterface(imUsdToken), imUsdVault, credits); + IBoostedSavingsVault(imUsdVault).stake(credits); + + // 3. Log Events + _eventName = "LogDeposit(address,uint256,address)"; + _eventParam = abi.encode(_token, _amount, _path); + } + + /** + * @dev Withdraws from Save + * @notice Withdraws token supported by mStable from Save + * @param _credits Credits to withdraw + * @return amountWithdrawn Amount withdrawn in mUSD + */ + + function _withdraw(uint256 _credits) + internal + returns (uint256 amountWithdrawn) + { + // 1. Withdraw from Vault + // approve(TokenInterface(imUsdVault), imUsdToken, _credits); + IBoostedSavingsVault(imUsdVault).withdraw(_credits); + + // 2. Withdraw from Save + 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)); + } } diff --git a/contracts/mainnet/connectors/mstable/main.sol b/contracts/mainnet/connectors/mstable/main.sol index 7e300e30..f42abf42 100644 --- a/contracts/mainnet/connectors/mstable/main.sol +++ b/contracts/mainnet/connectors/mstable/main.sol @@ -7,11 +7,11 @@ pragma solidity ^0.7.6; import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; -import { IMasset, ISavingsContractV2, IBoostedSavingsVault, IFeederPool } from "./interface.sol"; + import { TokenInterface } from "../../common/interfaces.sol"; +import { IMasset, IBoostedSavingsVault, IFeederPool } from "./interface.sol"; abstract contract mStableResolver is Events, Helpers { - // /*************************************** CORE ****************************************/ @@ -31,7 +31,6 @@ abstract contract mStableResolver is Events, Helpers { uint256 _amount, uint256 _minOut ) external returns (string memory _eventName, bytes memory _eventParam) { - // uint256 mintedAmount = _amount; address path; @@ -71,7 +70,6 @@ abstract contract mStableResolver is Events, Helpers { uint256 _minOut, address _path ) external returns (string memory _eventName, bytes memory _eventParam) { - // require(_path != address(0), "Path must be set"); require( IMasset(mUsdToken).bAssetIndexes(_token) == 0, @@ -140,7 +138,6 @@ abstract contract mStableResolver is Events, Helpers { uint256 _minOut, address _path ) external returns (string memory _eventName, bytes memory _eventParam) { - // require(_path != address(0), "Path must be set"); require( IMasset(mUsdToken).bAssetIndexes(_token) == 0, @@ -203,7 +200,6 @@ abstract contract mStableResolver is Events, Helpers { uint256 _amount, uint256 _minOut ) external returns (string memory _eventName, bytes memory _eventParam) { - // approve(TokenInterface(_input), mUsdToken, _amount); uint256 amountSwapped; @@ -258,7 +254,6 @@ abstract contract mStableResolver is Events, Helpers { uint256 _minOut, address _path ) external returns (string memory _eventName, bytes memory _eventParam) { - // uint256 amountSwapped; approve(TokenInterface(_input), _path, _amount); @@ -276,90 +271,6 @@ abstract contract mStableResolver is Events, Helpers { _eventName = "LogSwap(address,address,uint256,uint256)"; _eventParam = abi.encode(_input, _output, _amount, amountSwapped); } - - /*************************************** - 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) - * @return _eventName Event name - * @return _eventParam Event parameters - */ - - function _deposit( - address _token, - uint256 _amount, - address _path - ) internal returns (string memory _eventName, bytes memory _eventParam) { - // - // 1. Deposit mUSD to Save - approve(TokenInterface(mUsdToken), imUsdToken, _amount); - uint256 credits = ISavingsContractV2(imUsdToken).depositSavings( - _amount - ); - - // 2. Stake imUSD to Vault - approve(TokenInterface(imUsdToken), imUsdVault, credits); - IBoostedSavingsVault(imUsdVault).stake(credits); - - // 3. Log Events - _eventName = "LogDeposit(address,uint256,address)"; - _eventParam = abi.encode(_token, _amount, _path); - } - - /** - * @dev Withdraws from Save - * @notice Withdraws token supported by mStable from Save - * @param _credits Credits to withdraw - * @return amountWithdrawn Amount withdrawn in mUSD - */ - - function _withdraw(uint256 _credits) - internal - returns (uint256 amountWithdrawn) - { - // 1. Withdraw from Vault - // approve(TokenInterface(imUsdVault), imUsdToken, _credits); - IBoostedSavingsVault(imUsdVault).withdraw(_credits); - - // 2. Withdraw from Save - 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)); - } } contract ConnectV2mStable is mStableResolver { diff --git a/contracts/polygon/connectors/mstable/helpers.sol b/contracts/polygon/connectors/mstable/helpers.sol index 4e174128..54e3d0da 100644 --- a/contracts/polygon/connectors/mstable/helpers.sol +++ b/contracts/polygon/connectors/mstable/helpers.sol @@ -3,25 +3,104 @@ pragma solidity ^0.7.6; import { DSMath } from "../../common/math.sol"; import { Basic } from "../../common/basic.sol"; -// import { SaveWrapper } from "./interface.sol"; - -// interfaces here -// import { AaveLendingPoolProviderInterface, AaveDataProviderInterface } from "./interface.sol"; +import { ISavingsContractV2, IStakingRewardsWithPlatformToken } from "./interface.sol"; +import { TokenInterface } from "../../common/interfaces.sol"; abstract contract Helpers is DSMath, Basic { - // Helpers go here - /* - * @dev SaveWrapper address - */ - // SaveWrapper internal constant saveWrapper = - // SaveWrapper(0x299081f52738A4204C3D58264ff44f6F333C6c88); - - // Addresses that will be important for the contract - 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) + * @return _eventName Event name + * @return _eventParam Event parameters + */ + + function _deposit( + address _token, + uint256 _amount, + address _path + ) internal returns (string memory _eventName, bytes memory _eventParam) { + // 1. Deposit mUSD to Save + approve(TokenInterface(mUsdToken), imUsdToken, _amount); + uint256 credits = ISavingsContractV2(imUsdToken).depositSavings( + _amount + ); + + // 2. Stake imUSD to Vault + approve(TokenInterface(imUsdToken), imUsdVault, credits); + IStakingRewardsWithPlatformToken(imUsdVault).stake(credits); + + // 3. Log Events + _eventName = "LogDeposit(address,uint256,address)"; + _eventParam = abi.encode(_token, _amount, _path); + } + + /** + * @dev Withdraws from Save + * @notice Withdraws token supported by mStable from Save + * @param _credits Credits to withdraw + * @return amountWithdrawn Amount withdrawn in mUSD + */ + + function _withdraw(uint256 _credits) + internal + returns (uint256 amountWithdrawn) + { + // 1. Withdraw from Vault + IStakingRewardsWithPlatformToken(imUsdVault).withdraw(_credits); + + // 2. Withdraw from Save + 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)); + } } diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index 1a63a698..b247fb4b 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -7,11 +7,10 @@ pragma solidity ^0.7.6; import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; -import { IMasset, ISavingsContractV2, IStakingRewardsWithPlatformToken, IFeederPool } from "./interface.sol"; +import { IMasset, IStakingRewardsWithPlatformToken, IFeederPool } from "./interface.sol"; import { TokenInterface } from "../../common/interfaces.sol"; abstract contract PmStableResolver is Events, Helpers { - // /*************************************** CORE ****************************************/ @@ -31,7 +30,6 @@ abstract contract PmStableResolver is Events, Helpers { uint256 _amount, uint256 _minOut ) external returns (string memory _eventName, bytes memory _eventParam) { - // uint256 mintedAmount = _amount; address path; @@ -71,7 +69,6 @@ abstract contract PmStableResolver is Events, Helpers { uint256 _minOut, address _path ) external returns (string memory _eventName, bytes memory _eventParam) { - // require(_path != address(0), "Path must be set"); require( IMasset(mUsdToken).bAssetIndexes(_token) == 0, @@ -140,7 +137,6 @@ abstract contract PmStableResolver is Events, Helpers { uint256 _minOut, address _path ) external returns (string memory _eventName, bytes memory _eventParam) { - // require(_path != address(0), "Path must be set"); require( IMasset(mUsdToken).bAssetIndexes(_token) == 0, @@ -219,7 +215,6 @@ abstract contract PmStableResolver is Events, Helpers { uint256 _amount, uint256 _minOut ) external returns (string memory _eventName, bytes memory _eventParam) { - // approve(TokenInterface(_input), mUsdToken, _amount); uint256 amountSwapped; @@ -274,7 +269,6 @@ abstract contract PmStableResolver is Events, Helpers { uint256 _minOut, address _path ) external returns (string memory _eventName, bytes memory _eventParam) { - // uint256 amountSwapped; approve(TokenInterface(_input), _path, _amount); @@ -292,97 +286,6 @@ abstract contract PmStableResolver is Events, Helpers { _eventName = "LogSwap(address,address,uint256,uint256)"; _eventParam = abi.encode(_input, _output, _amount, amountSwapped); } - - /*************************************** - 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) - * @return _eventName Event name - * @return _eventParam Event parameters - */ - - function _deposit( - address _token, - uint256 _amount, - address _path - ) internal returns (string memory _eventName, bytes memory _eventParam) { - // - // 1. Deposit mUSD to Save - approve(TokenInterface(mUsdToken), imUsdToken, _amount); - uint256 credits = ISavingsContractV2(imUsdToken).depositSavings( - _amount - ); - - // 2. Stake imUSD to Vault - approve(TokenInterface(imUsdToken), imUsdVault, credits); - IStakingRewardsWithPlatformToken(imUsdVault).stake(credits); - - // 3. Log Events - _eventName = "LogDeposit(address,uint256,address)"; - _eventParam = abi.encode(_token, _amount, _path); - } - - /** - * @dev Withdraws from Save - * @notice Withdraws token supported by mStable from Save - * @param _credits Credits to withdraw - * @return amountWithdrawn Amount withdrawn in mUSD - */ - - function _withdraw(uint256 _credits) - internal - returns (uint256 amountWithdrawn) - { - // 1. Withdraw from Vault - IStakingRewardsWithPlatformToken(imUsdVault).withdraw(_credits); - - // 2. Withdraw from Save - 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)); - } } contract ConnectV2PmStable is PmStableResolver { diff --git a/test/mainnet/mstable/mstable.helpers.ts b/test/mainnet/mstable/mstable.helpers.ts index 3558b648..1dde1637 100644 --- a/test/mainnet/mstable/mstable.helpers.ts +++ b/test/mainnet/mstable/mstable.helpers.ts @@ -3,7 +3,7 @@ import { IERC20Minimal__factory } from "../../../typechain"; import { BigNumber as BN } from "ethers"; export const DEAD_ADDRESS = "0x0000000000000000000000000000000000000001"; -export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; +export const ZERO_ADDRESS = ethers.constants.AddressZero; export const DEFAULT_DECIMALS = 18; diff --git a/test/polygon/mstable/mstable.helpers.ts b/test/polygon/mstable/mstable.helpers.ts index 781559f8..f717efb3 100644 --- a/test/polygon/mstable/mstable.helpers.ts +++ b/test/polygon/mstable/mstable.helpers.ts @@ -3,7 +3,7 @@ import { IERC20Minimal__factory } from "../../../typechain"; import { BigNumber as BN } from "ethers"; export const DEAD_ADDRESS = "0x0000000000000000000000000000000000000001"; -export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; +export const ZERO_ADDRESS = ethers.constants.AddressZero; export const DEFAULT_DECIMALS = 18; From 571a69ea3c73ce747b2c7f0822417ff676739c43 Mon Sep 17 00:00:00 2001 From: Dimitri <69167058+dimsome@users.noreply.github.com> Date: Thu, 27 Jan 2022 19:34:47 +0700 Subject: [PATCH 20/21] chore: add stores --- .../mainnet/connectors/mstable/events.sol | 9 +- .../mainnet/connectors/mstable/helpers.sol | 36 +++-- contracts/mainnet/connectors/mstable/main.sol | 138 ++++++++++++++---- .../polygon/connectors/mstable/events.sol | 9 +- .../polygon/connectors/mstable/helpers.sol | 36 +++-- contracts/polygon/connectors/mstable/main.sol | 138 ++++++++++++++---- test/mainnet/mstable/mstable.test.ts | 19 ++- test/mainnet/mstable/mstable.utils.ts | 6 +- test/polygon/mstable/mstable.test.ts | 19 ++- test/polygon/mstable/mstable.utils.ts | 6 +- 10 files changed, 308 insertions(+), 108 deletions(-) diff --git a/contracts/mainnet/connectors/mstable/events.sol b/contracts/mainnet/connectors/mstable/events.sol index 7cabfddc..11497875 100644 --- a/contracts/mainnet/connectors/mstable/events.sol +++ b/contracts/mainnet/connectors/mstable/events.sol @@ -1,8 +1,13 @@ pragma solidity ^0.7.6; contract Events { - event LogDeposit(address token, uint256 amount, address path); - event LogWithdraw(address token, uint256 amount, address path); + 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, diff --git a/contracts/mainnet/connectors/mstable/helpers.sol b/contracts/mainnet/connectors/mstable/helpers.sol index dce9024f..9d569963 100644 --- a/contracts/mainnet/connectors/mstable/helpers.sol +++ b/contracts/mainnet/connectors/mstable/helpers.sol @@ -24,6 +24,7 @@ abstract contract Helpers is DSMath, Basic { * @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 */ @@ -31,7 +32,8 @@ abstract contract Helpers is DSMath, Basic { function _deposit( address _token, uint256 _amount, - address _path + address _path, + bool _stake ) internal returns (string memory _eventName, bytes memory _eventParam) { // 1. Deposit mUSD to Save approve(TokenInterface(mUsdToken), imUsdToken, _amount); @@ -39,35 +41,43 @@ abstract contract Helpers is DSMath, Basic { _amount ); - // 2. Stake imUSD to Vault - approve(TokenInterface(imUsdToken), imUsdVault, credits); - IBoostedSavingsVault(imUsdVault).stake(credits); - + if (_stake) { + // 2. Stake imUSD to Vault + approve(TokenInterface(imUsdToken), imUsdVault, credits); + IBoostedSavingsVault(imUsdVault).stake(credits); + } // 3. Log Events - _eventName = "LogDeposit(address,uint256,address)"; - _eventParam = abi.encode(_token, _amount, _path); + _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) + function _withdraw(uint256 _credits, bool _unstake) internal returns (uint256 amountWithdrawn) { + uint256 credits; // 1. Withdraw from Vault - // approve(TokenInterface(imUsdVault), imUsdToken, _credits); - IBoostedSavingsVault(imUsdVault).withdraw(_credits); + 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 - ); + amountWithdrawn = ISavingsContractV2(imUsdToken).redeemCredits(credits); } /** diff --git a/contracts/mainnet/connectors/mstable/main.sol b/contracts/mainnet/connectors/mstable/main.sol index f42abf42..30107346 100644 --- a/contracts/mainnet/connectors/mstable/main.sol +++ b/contracts/mainnet/connectors/mstable/main.sol @@ -22,6 +22,9 @@ abstract contract mStableResolver is Events, Helpers { * @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 */ @@ -29,28 +32,42 @@ abstract contract mStableResolver is Events, Helpers { function deposit( address _token, uint256 _amount, - uint256 _minOut + uint256 _minOut, + bool _stake, + uint256 _setId, + uint256 _getId ) external returns (string memory _eventName, bytes memory _eventParam) { - uint256 mintedAmount = _amount; + 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); + approve(TokenInterface(_token), mUsdToken, amount); mintedAmount = IMasset(mUsdToken).mint( _token, - _amount, + amount, _minOut, address(this) ); path = mUsdToken; } else { - require(mintedAmount >= _minOut, "mintedAmount < _minOut"); + require(amount >= _minOut, "mintedAmount < _minOut"); + mintedAmount = amount; path = imUsdToken; } - (_eventName, _eventParam) = _deposit(_token, mintedAmount, path); + setUint(_setId, mintedAmount); + (_eventName, _eventParam) = _deposit( + _token, + mintedAmount, + path, + _stake + ); } /** @@ -60,6 +77,9 @@ abstract contract mStableResolver is Events, Helpers { * @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 */ @@ -68,7 +88,10 @@ abstract contract mStableResolver is Events, Helpers { address _token, uint256 _amount, uint256 _minOut, - address _path + address _path, + bool _stake, + uint256 _setId, + uint256 _getId ) external returns (string memory _eventName, bytes memory _eventParam) { require(_path != address(0), "Path must be set"); require( @@ -76,16 +99,27 @@ abstract contract mStableResolver is Events, Helpers { "Token is bAsset" ); - approve(TokenInterface(_token), _path, _amount); + 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, + amount, _minOut, address(this) ); - (_eventName, _eventParam) = _deposit(_token, mintedAmount, _path); + setUint(_setId, mintedAmount); + (_eventName, _eventParam) = _deposit( + _token, + mintedAmount, + _path, + _stake + ); } /** @@ -94,6 +128,9 @@ abstract contract mStableResolver is Events, Helpers { * @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 */ @@ -101,9 +138,13 @@ abstract contract mStableResolver is Events, Helpers { function withdraw( address _token, uint256 _credits, - uint256 _minOut + uint256 _minOut, + bool _unstake, + uint256 _getId, + uint256 _setId ) external returns (string memory _eventName, bytes memory _eventParam) { - uint256 amountWithdrawn = _withdraw(_credits); + uint256 credits = getUint(_getId, _credits); + uint256 amountWithdrawn = _withdraw(credits, _unstake); // Check if needs to be redeemed if (IMasset(mUsdToken).bAssetIndexes(_token) != 0) { @@ -117,8 +158,14 @@ abstract contract mStableResolver is Events, Helpers { require(amountWithdrawn >= _minOut, "amountWithdrawn < _minOut"); } - _eventName = "LogWithdraw(address,uint256,address)"; - _eventParam = abi.encode(mUsdToken, amountWithdrawn, imUsdToken); + setUint(_setId, amountWithdrawn); + _eventName = "LogWithdraw(address,uint256,address,bool)"; + _eventParam = abi.encode( + mUsdToken, + amountWithdrawn, + imUsdToken, + _unstake + ); } /** @@ -128,6 +175,9 @@ abstract contract mStableResolver is Events, Helpers { * @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 */ @@ -136,7 +186,10 @@ abstract contract mStableResolver is Events, Helpers { address _token, uint256 _credits, uint256 _minOut, - address _path + address _path, + bool _unstake, + uint256 _getId, + uint256 _setId ) external returns (string memory _eventName, bytes memory _eventParam) { require(_path != address(0), "Path must be set"); require( @@ -144,7 +197,9 @@ abstract contract mStableResolver is Events, Helpers { "Token is bAsset" ); - uint256 amountWithdrawn = _withdraw(_credits); + uint256 credits = getUint(_getId, _credits); + + uint256 amountWithdrawn = _withdraw(credits, _unstake); approve(TokenInterface(mUsdToken), _path, amountWithdrawn); uint256 amountRedeemed = IFeederPool(_path).swap( @@ -155,18 +210,22 @@ abstract contract mStableResolver is Events, Helpers { address(this) ); - _eventName = "LogWithdraw(address,uint256,address)"; - _eventParam = abi.encode(_token, amountRedeemed, _path); + 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() + function claimRewards(uint256 _getId, uint256 _setId) external returns (string memory _eventName, bytes memory _eventParam) { @@ -179,6 +238,8 @@ abstract contract mStableResolver is Events, Helpers { uint256 claimedRewardToken = sub(rewardAmountUpdated, rewardAmount); + setUint(_setId, claimedRewardToken); + _eventName = "LogClaimRewards(address,uint256)"; _eventParam = abi.encode(rewardToken, claimedRewardToken); } @@ -190,6 +251,8 @@ abstract contract mStableResolver is Events, Helpers { * @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 */ @@ -198,9 +261,15 @@ abstract contract mStableResolver is Events, Helpers { address _input, address _output, uint256 _amount, - uint256 _minOut + uint256 _minOut, + uint256 _getId, + uint256 _setId ) external returns (string memory _eventName, bytes memory _eventParam) { - approve(TokenInterface(_input), mUsdToken, _amount); + 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 @@ -208,7 +277,7 @@ abstract contract mStableResolver is Events, Helpers { // bAsset to mUSD => mint amountSwapped = IMasset(mUsdToken).mint( _input, - _amount, + amount, _minOut, address(this) ); @@ -216,7 +285,7 @@ abstract contract mStableResolver is Events, Helpers { // mUSD to bAsset => redeem amountSwapped = IMasset(mUsdToken).redeem( _output, - _amount, + amount, _minOut, address(this) ); @@ -225,14 +294,15 @@ abstract contract mStableResolver is Events, Helpers { amountSwapped = IMasset(mUsdToken).swap( _input, _output, - _amount, + amount, _minOut, address(this) ); } + setUint(_setId, amountSwapped); _eventName = "LogSwap(address,address,uint256,uint256)"; - _eventParam = abi.encode(_input, _output, _amount, amountSwapped); + _eventParam = abi.encode(_input, _output, amount, amountSwapped); } /** @@ -243,6 +313,8 @@ abstract contract mStableResolver is Events, Helpers { * @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 */ @@ -252,24 +324,32 @@ abstract contract mStableResolver is Events, Helpers { address _output, uint256 _amount, uint256 _minOut, - address _path + address _path, + uint256 _getId, + uint256 _setId ) external 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); + 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, + amount, _minOut, address(this) ); + setUint(_setId, amountSwapped); + _eventName = "LogSwap(address,address,uint256,uint256)"; - _eventParam = abi.encode(_input, _output, _amount, amountSwapped); + _eventParam = abi.encode(_input, _output, amount, amountSwapped); } } diff --git a/contracts/polygon/connectors/mstable/events.sol b/contracts/polygon/connectors/mstable/events.sol index dcbbbe68..c463b75d 100644 --- a/contracts/polygon/connectors/mstable/events.sol +++ b/contracts/polygon/connectors/mstable/events.sol @@ -1,8 +1,13 @@ pragma solidity ^0.7.6; contract Events { - event LogDeposit(address token, uint256 amount, address path); - event LogWithdraw(address token, uint256 amount, address path); + 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, diff --git a/contracts/polygon/connectors/mstable/helpers.sol b/contracts/polygon/connectors/mstable/helpers.sol index 54e3d0da..d4330fb6 100644 --- a/contracts/polygon/connectors/mstable/helpers.sol +++ b/contracts/polygon/connectors/mstable/helpers.sol @@ -24,6 +24,7 @@ abstract contract Helpers is DSMath, Basic { * @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 */ @@ -31,42 +32,51 @@ abstract contract Helpers is DSMath, Basic { function _deposit( address _token, uint256 _amount, - address _path + 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 ); - - // 2. Stake imUSD to Vault - approve(TokenInterface(imUsdToken), imUsdVault, credits); - IStakingRewardsWithPlatformToken(imUsdVault).stake(credits); - + if (_stake) { + // 2. Stake imUSD to Vault + approve(TokenInterface(imUsdToken), imUsdVault, credits); + IStakingRewardsWithPlatformToken(imUsdVault).stake(credits); + } // 3. Log Events - _eventName = "LogDeposit(address,uint256,address)"; - _eventParam = abi.encode(_token, _amount, _path); + _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) + function _withdraw(uint256 _credits, bool _unstake) internal returns (uint256 amountWithdrawn) { + uint256 credits; // 1. Withdraw from Vault - IStakingRewardsWithPlatformToken(imUsdVault).withdraw(_credits); + 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 - ); + amountWithdrawn = ISavingsContractV2(imUsdToken).redeemCredits(credits); } /** diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index b247fb4b..2f116322 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -21,6 +21,9 @@ abstract contract PmStableResolver is Events, Helpers { * @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 */ @@ -28,28 +31,43 @@ abstract contract PmStableResolver is Events, Helpers { function deposit( address _token, uint256 _amount, - uint256 _minOut + uint256 _minOut, + bool _stake, + uint256 _getId, + uint256 _setId ) external returns (string memory _eventName, bytes memory _eventParam) { - uint256 mintedAmount = _amount; + 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); + approve(TokenInterface(_token), mUsdToken, amount); mintedAmount = IMasset(mUsdToken).mint( _token, - _amount, + amount, _minOut, address(this) ); path = mUsdToken; } else { - require(mintedAmount >= _minOut, "mintedAmount < _minOut"); + require(amount >= _minOut, "mintedAmount < _minOut"); + mintedAmount = amount; path = imUsdToken; } - (_eventName, _eventParam) = _deposit(_token, mintedAmount, path); + setUint(_setId, mintedAmount); + + (_eventName, _eventParam) = _deposit( + _token, + mintedAmount, + path, + _stake + ); } /** @@ -59,6 +77,9 @@ abstract contract PmStableResolver is Events, Helpers { * @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 */ @@ -67,7 +88,10 @@ abstract contract PmStableResolver is Events, Helpers { address _token, uint256 _amount, uint256 _minOut, - address _path + address _path, + bool _stake, + uint256 _getId, + uint256 _setId ) external returns (string memory _eventName, bytes memory _eventParam) { require(_path != address(0), "Path must be set"); require( @@ -75,16 +99,27 @@ abstract contract PmStableResolver is Events, Helpers { "Token is bAsset" ); - approve(TokenInterface(_token), _path, _amount); + 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, + amount, _minOut, address(this) ); - (_eventName, _eventParam) = _deposit(_token, mintedAmount, _path); + setUint(_setId, mintedAmount); + (_eventName, _eventParam) = _deposit( + _token, + mintedAmount, + _path, + _stake + ); } /** @@ -93,6 +128,9 @@ abstract contract PmStableResolver is Events, Helpers { * @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 */ @@ -100,9 +138,13 @@ abstract contract PmStableResolver is Events, Helpers { function withdraw( address _token, uint256 _credits, - uint256 _minOut + uint256 _minOut, + bool _unstake, + uint256 _getId, + uint256 _setId ) external returns (string memory _eventName, bytes memory _eventParam) { - uint256 amountWithdrawn = _withdraw(_credits); + uint256 credits = getUint(_getId, _credits); + uint256 amountWithdrawn = _withdraw(credits, _unstake); // Check if needs to be redeemed if (IMasset(mUsdToken).bAssetIndexes(_token) != 0) { @@ -116,8 +158,14 @@ abstract contract PmStableResolver is Events, Helpers { require(amountWithdrawn >= _minOut, "amountWithdrawn < _minOut"); } - _eventName = "LogWithdraw(address,uint256,address)"; - _eventParam = abi.encode(mUsdToken, amountWithdrawn, imUsdToken); + setUint(_setId, amountWithdrawn); + _eventName = "LogWithdraw(address,uint256,address,bool)"; + _eventParam = abi.encode( + mUsdToken, + amountWithdrawn, + imUsdToken, + _unstake + ); } /** @@ -127,6 +175,9 @@ abstract contract PmStableResolver is Events, Helpers { * @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 */ @@ -135,7 +186,10 @@ abstract contract PmStableResolver is Events, Helpers { address _token, uint256 _credits, uint256 _minOut, - address _path + address _path, + bool _unstake, + uint256 _getId, + uint256 _setId ) external returns (string memory _eventName, bytes memory _eventParam) { require(_path != address(0), "Path must be set"); require( @@ -143,7 +197,8 @@ abstract contract PmStableResolver is Events, Helpers { "Token is bAsset" ); - uint256 amountWithdrawn = _withdraw(_credits); + uint256 credits = getUint(_getId, _credits); + uint256 amountWithdrawn = _withdraw(credits, _unstake); approve(TokenInterface(mUsdToken), _path, amountWithdrawn); uint256 amountRedeemed = IFeederPool(_path).swap( @@ -154,18 +209,21 @@ abstract contract PmStableResolver is Events, Helpers { address(this) ); - _eventName = "LogWithdraw(address,uint256,address)"; - _eventParam = abi.encode(_token, amountRedeemed, _path); + 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() + function claimRewards(uint256 _getId, uint256 _setId) external returns (string memory _eventName, bytes memory _eventParam) { @@ -189,6 +247,7 @@ abstract contract PmStableResolver is Events, Helpers { platformAmount ); + setUint(_setId, claimedRewardToken); _eventName = "LogClaimRewards(address,uint256,address,uint256)"; _eventParam = abi.encode( rewardToken, @@ -205,6 +264,8 @@ abstract contract PmStableResolver is Events, Helpers { * @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 */ @@ -213,9 +274,16 @@ abstract contract PmStableResolver is Events, Helpers { address _input, address _output, uint256 _amount, - uint256 _minOut + uint256 _minOut, + uint256 _getId, + uint256 _setId ) external returns (string memory _eventName, bytes memory _eventParam) { - approve(TokenInterface(_input), mUsdToken, _amount); + 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 @@ -223,7 +291,7 @@ abstract contract PmStableResolver is Events, Helpers { // bAsset to mUSD => mint amountSwapped = IMasset(mUsdToken).mint( _input, - _amount, + amount, _minOut, address(this) ); @@ -231,7 +299,7 @@ abstract contract PmStableResolver is Events, Helpers { // mUSD to bAsset => redeem amountSwapped = IMasset(mUsdToken).redeem( _output, - _amount, + amount, _minOut, address(this) ); @@ -240,14 +308,16 @@ abstract contract PmStableResolver is Events, Helpers { amountSwapped = IMasset(mUsdToken).swap( _input, _output, - _amount, + amount, _minOut, address(this) ); } + setUint(_setId, amountSwapped); + _eventName = "LogSwap(address,address,uint256,uint256)"; - _eventParam = abi.encode(_input, _output, _amount, amountSwapped); + _eventParam = abi.encode(_input, _output, amount, amountSwapped); } /** @@ -258,6 +328,8 @@ abstract contract PmStableResolver is Events, Helpers { * @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 */ @@ -267,24 +339,32 @@ abstract contract PmStableResolver is Events, Helpers { address _output, uint256 _amount, uint256 _minOut, - address _path + address _path, + uint256 _getId, + uint256 _setId ) external 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); + 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, + amount, _minOut, address(this) ); + setUint(_setId, amountSwapped); + _eventName = "LogSwap(address,address,uint256,uint256)"; - _eventParam = abi.encode(_input, _output, _amount, amountSwapped); + _eventParam = abi.encode(_input, _output, amount, amountSwapped); } } diff --git a/test/mainnet/mstable/mstable.test.ts b/test/mainnet/mstable/mstable.test.ts index 53bb6dfc..8ca09a8c 100644 --- a/test/mainnet/mstable/mstable.test.ts +++ b/test/mainnet/mstable/mstable.test.ts @@ -120,32 +120,36 @@ describe("MStable", async () => { const depositAmount = simpleToExactAmount(100); const minOut = depositAmount; - await executeAndAssertDeposit("deposit", mUsdToken, depositAmount, dsaWallet0, wallet0, [minOut]); + 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]); + 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]); + 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]); + 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]); + await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [minOut, true]); }); it("Should withdraw from Vault to alUSD (via Feeder Pool)", async () => { const withdrawAmount = simpleToExactAmount(100); @@ -154,7 +158,8 @@ describe("MStable", async () => { await executeAndAssertWithdraw("withdrawViaSwap", alusdToken, withdrawAmount, dsaWallet0, wallet0, [ minOut, - path + path, + true ]); }); it("Should claim Rewards", async () => { @@ -168,7 +173,7 @@ describe("MStable", async () => { { connector: connectorName, method: "claimRewards", - args: [] + args: [0, 0] } ]; diff --git a/test/mainnet/mstable/mstable.utils.ts b/test/mainnet/mstable/mstable.utils.ts index 2614bff8..88bb21ea 100644 --- a/test/mainnet/mstable/mstable.utils.ts +++ b/test/mainnet/mstable/mstable.utils.ts @@ -45,7 +45,7 @@ export const executeAndAssertSwap = async ( { connector: connectorName, method, - args: [tokenFrom.address, tokenTo.address, swapAmount, 1, ...(args ? args : [])] + args: [tokenFrom.address, tokenTo.address, swapAmount, 1, ...(args ? args : []), 0, 0] } ]; @@ -81,7 +81,7 @@ export const executeAndAssertDeposit = async ( { connector: connectorName, method, - args: [tokenFrom.address, depositAmount, ...(args ? args : [])] + args: [tokenFrom.address, depositAmount, ...(args ? args : []), 0, 0] } ]; @@ -120,7 +120,7 @@ export const executeAndAssertWithdraw = async ( { connector: connectorName, method, - args: [tokenTo.address, withdrawAmount, ...(args ? args : [])] + args: [tokenTo.address, withdrawAmount, ...(args ? args : []), 0, 0] } ]; diff --git a/test/polygon/mstable/mstable.test.ts b/test/polygon/mstable/mstable.test.ts index 2198e288..1015f8dd 100644 --- a/test/polygon/mstable/mstable.test.ts +++ b/test/polygon/mstable/mstable.test.ts @@ -120,32 +120,36 @@ describe("MStable", async () => { const depositAmount = simpleToExactAmount(100); const minOut = depositAmount; - await executeAndAssertDeposit("deposit", mUsdToken, depositAmount, dsaWallet0, wallet0, [minOut]); + 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]); + 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]); + 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]); + 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]); + await executeAndAssertWithdraw("withdraw", mUsdToken, withdrawAmount, dsaWallet0, wallet0, [minOut, true]); }); it("Should withdraw from Vault to FRAX (via Feeder Pool)", async () => { const withdrawAmount = simpleToExactAmount(100); @@ -154,7 +158,8 @@ describe("MStable", async () => { await executeAndAssertWithdraw("withdrawViaSwap", fraxToken, withdrawAmount, dsaWallet0, wallet0, [ minOut, - path + path, + true ]); }); it("Should claim Rewards", async () => { @@ -168,7 +173,7 @@ describe("MStable", async () => { { connector: connectorName, method: "claimRewards", - args: [] + args: [0, 0] } ]; diff --git a/test/polygon/mstable/mstable.utils.ts b/test/polygon/mstable/mstable.utils.ts index 2614bff8..88bb21ea 100644 --- a/test/polygon/mstable/mstable.utils.ts +++ b/test/polygon/mstable/mstable.utils.ts @@ -45,7 +45,7 @@ export const executeAndAssertSwap = async ( { connector: connectorName, method, - args: [tokenFrom.address, tokenTo.address, swapAmount, 1, ...(args ? args : [])] + args: [tokenFrom.address, tokenTo.address, swapAmount, 1, ...(args ? args : []), 0, 0] } ]; @@ -81,7 +81,7 @@ export const executeAndAssertDeposit = async ( { connector: connectorName, method, - args: [tokenFrom.address, depositAmount, ...(args ? args : [])] + args: [tokenFrom.address, depositAmount, ...(args ? args : []), 0, 0] } ]; @@ -120,7 +120,7 @@ export const executeAndAssertWithdraw = async ( { connector: connectorName, method, - args: [tokenTo.address, withdrawAmount, ...(args ? args : [])] + args: [tokenTo.address, withdrawAmount, ...(args ? args : []), 0, 0] } ]; From a51ad6fd4569a697135c9de9527497c32fe526f8 Mon Sep 17 00:00:00 2001 From: Dimitri Golecko Date: Tue, 1 Feb 2022 16:41:20 +0100 Subject: [PATCH 21/21] chore: add payable --- contracts/mainnet/connectors/mstable/main.sol | 37 ++++++++++++++++--- contracts/polygon/connectors/mstable/main.sol | 37 ++++++++++++++++--- 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/contracts/mainnet/connectors/mstable/main.sol b/contracts/mainnet/connectors/mstable/main.sol index 30107346..fcbbc690 100644 --- a/contracts/mainnet/connectors/mstable/main.sol +++ b/contracts/mainnet/connectors/mstable/main.sol @@ -36,7 +36,11 @@ abstract contract mStableResolver is Events, Helpers { bool _stake, uint256 _setId, uint256 _getId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { uint256 amount = getUint(_getId, _amount); amount = amount == uint256(-1) ? TokenInterface(_token).balanceOf(address(this)) @@ -92,7 +96,11 @@ abstract contract mStableResolver is Events, Helpers { bool _stake, uint256 _setId, uint256 _getId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { require(_path != address(0), "Path must be set"); require( IMasset(mUsdToken).bAssetIndexes(_token) == 0, @@ -142,7 +150,11 @@ abstract contract mStableResolver is Events, Helpers { bool _unstake, uint256 _getId, uint256 _setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { uint256 credits = getUint(_getId, _credits); uint256 amountWithdrawn = _withdraw(credits, _unstake); @@ -190,7 +202,11 @@ abstract contract mStableResolver is Events, Helpers { bool _unstake, uint256 _getId, uint256 _setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { require(_path != address(0), "Path must be set"); require( IMasset(mUsdToken).bAssetIndexes(_token) == 0, @@ -227,6 +243,7 @@ abstract contract mStableResolver is Events, Helpers { function claimRewards(uint256 _getId, uint256 _setId) external + payable returns (string memory _eventName, bytes memory _eventParam) { address rewardToken = _getRewardTokens(); @@ -264,7 +281,11 @@ abstract contract mStableResolver is Events, Helpers { uint256 _minOut, uint256 _getId, uint256 _setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { uint256 amount = getUint(_getId, _amount); amount = amount == uint256(-1) ? TokenInterface(_input).balanceOf(address(this)) @@ -327,7 +348,11 @@ abstract contract mStableResolver is Events, Helpers { address _path, uint256 _getId, uint256 _setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { uint256 amountSwapped; uint256 amount = getUint(_getId, _amount); amount = amount == uint256(-1) diff --git a/contracts/polygon/connectors/mstable/main.sol b/contracts/polygon/connectors/mstable/main.sol index 2f116322..d2939fb8 100644 --- a/contracts/polygon/connectors/mstable/main.sol +++ b/contracts/polygon/connectors/mstable/main.sol @@ -35,7 +35,11 @@ abstract contract PmStableResolver is Events, Helpers { bool _stake, uint256 _getId, uint256 _setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { uint256 amount = getUint(_getId, _amount); amount = amount == uint256(-1) ? TokenInterface(_token).balanceOf(address(this)) @@ -92,7 +96,11 @@ abstract contract PmStableResolver is Events, Helpers { bool _stake, uint256 _getId, uint256 _setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { require(_path != address(0), "Path must be set"); require( IMasset(mUsdToken).bAssetIndexes(_token) == 0, @@ -142,7 +150,11 @@ abstract contract PmStableResolver is Events, Helpers { bool _unstake, uint256 _getId, uint256 _setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { uint256 credits = getUint(_getId, _credits); uint256 amountWithdrawn = _withdraw(credits, _unstake); @@ -190,7 +202,11 @@ abstract contract PmStableResolver is Events, Helpers { bool _unstake, uint256 _getId, uint256 _setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { require(_path != address(0), "Path must be set"); require( IMasset(mUsdToken).bAssetIndexes(_token) == 0, @@ -225,6 +241,7 @@ abstract contract PmStableResolver is Events, Helpers { function claimRewards(uint256 _getId, uint256 _setId) external + payable returns (string memory _eventName, bytes memory _eventParam) { (address rewardToken, address platformToken) = _getRewardTokens(); @@ -277,7 +294,11 @@ abstract contract PmStableResolver is Events, Helpers { uint256 _minOut, uint256 _getId, uint256 _setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { uint256 amount = getUint(_getId, _amount); amount = amount == uint256(-1) ? TokenInterface(_input).balanceOf(address(this)) @@ -342,7 +363,11 @@ abstract contract PmStableResolver is Events, Helpers { address _path, uint256 _getId, uint256 _setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { uint256 amountSwapped; uint256 amount = getUint(_getId, _amount); amount = amount == uint256(-1)