From abb9c60e1d1c3c2f252d3d244c3a872229fc1bf6 Mon Sep 17 00:00:00 2001 From: alsco77 Date: Wed, 17 Feb 2021 16:51:38 +0000 Subject: [PATCH] Basic mint and swap test cases implemented --- contracts/feeders/FeederPool.sol | 232 +++++++++++++----------- contracts/masset/InvariantValidator.sol | 10 +- contracts/masset/Manager.sol | 5 + contracts/shared/MassetHelpers.sol | 5 + test-utils/machines/feederMachine.ts | 218 ++++++++++++++++++++++ test-utils/machines/index.ts | 1 + test-utils/machines/mAssetMachine.ts | 25 ++- test/feeders/basic-fns.spec.ts | 73 ++++++++ test/masset/admin.spec.ts | 30 +-- 9 files changed, 470 insertions(+), 129 deletions(-) create mode 100644 test-utils/machines/feederMachine.ts diff --git a/contracts/feeders/FeederPool.sol b/contracts/feeders/FeederPool.sol index 74f9a232..2855ed39 100644 --- a/contracts/feeders/FeederPool.sol +++ b/contracts/feeders/FeederPool.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.0; pragma abicoder v2; +import "hardhat/console.sol"; + // External import { IInvariantValidator } from "../masset/IInvariantValidator.sol"; @@ -16,6 +18,8 @@ import { IMasset } from "../interfaces/IMasset.sol"; // Libs import { SafeCast } from "@openzeppelin/contracts-sol8/contracts/utils/SafeCast.sol"; +import { IERC20 } from "@openzeppelin/contracts-sol8/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts-sol8/contracts/token/ERC20/SafeERC20.sol"; import { StableMath } from "../shared/StableMath.sol"; import { Manager } from "../masset/Manager.sol"; @@ -27,6 +31,7 @@ contract FeederPool is InitializableReentrancyGuard { using StableMath for uint256; + using SafeERC20 for IERC20; // Forging Events event Minted( @@ -36,13 +41,13 @@ contract FeederPool is address input, uint256 inputQuantity ); - // event MintedMulti( - // address indexed minter, - // address recipient, - // uint256 output, - // address[] inputs, - // uint256[] inputQuantities - // ); + event MintedMulti( + address indexed minter, + address recipient, + uint256 output, + address[] inputs, + uint256[] inputQuantities + ); event Swapped( address indexed swapper, address input, @@ -88,6 +93,7 @@ contract FeederPool is // [1] = fAsset BassetPersonal[] public bAssetPersonal; BassetData[] public bAssetData; + // TODO - also store fAsset addr here? address public immutable mAsset; uint256 private constant A_PRECISION = 100; @@ -110,6 +116,7 @@ contract FeederPool is address _validator, BassetPersonal calldata _mAsset, BassetPersonal calldata _fAsset, + address[] calldata _mpAssets, InvariantConfig memory _config ) public initializer { InitializableToken._initialize(_nameArg, _symbolArg); @@ -117,7 +124,7 @@ contract FeederPool is _initializeReentrancyGuard(); validator = IInvariantValidator(_validator); - + console.log("a"); // TODO - consider how to store fAsset vs mAsset. Atm we do 3 extra SLOADs per asset // ----- prop ---- fAsset ---- mAsset // addr immutable immutable @@ -127,23 +134,24 @@ contract FeederPool is // vBalance mutable mutable // status outdated outdated require(_mAsset.addr == mAsset, "mAsset incorrect"); - bAssetPersonal[0] = BassetPersonal( - _mAsset.addr, - _mAsset.integrator, - false, - BassetStatus.Normal + console.log("b"); + bAssetPersonal.push( + BassetPersonal(_mAsset.addr, _mAsset.integrator, false, BassetStatus.Normal) ); - bAssetData[0] = BassetData(1e8, 0); - bAssetPersonal[1] = BassetPersonal( - _fAsset.addr, - _fAsset.integrator, - _fAsset.hasTxFee, - BassetStatus.Normal + console.log("c"); + bAssetData.push(BassetData(1e8, 0)); + console.log("d"); + bAssetPersonal.push( + BassetPersonal(_fAsset.addr, _fAsset.integrator, _fAsset.hasTxFee, BassetStatus.Normal) ); - bAssetData[1] = BassetData( - SafeCast.toUint128(10**(18 - IBasicToken(_fAsset.addr).decimals())), - 0 + console.log("e"); + bAssetData.push( + BassetData(SafeCast.toUint128(10**(26 - IBasicToken(_fAsset.addr).decimals())), 0) ); + for(uint i = 0; i < _mpAssets.length; i++){ + IERC20(_mpAssets[i]).approve(_mAsset.addr, 2**255); + } + console.log("f"); uint64 startA = SafeCast.toUint64(_config.a * A_PRECISION); ampData = AmpData(startA, startA, 0, 0); @@ -193,31 +201,23 @@ contract FeederPool is (bool exists, uint8 idx) = _getAsset(_input); if (exists) { - mintOutput = _mintLocal(idx, _inputQuantity, _minOutputQuantity, _recipient); + mintOutput = _mintLocal(idx, _inputQuantity, _minOutputQuantity, false, _recipient); } else { + // TODO - consider having this as part of the wrapper fn too + IERC20(_input).safeTransferFrom(msg.sender, address(this), _inputQuantity); uint256 mAssetMinted = IMasset(mAsset).mint(_input, _inputQuantity, 0, address(this)); - mintOutput = _mintLocal(0, mAssetMinted, _minOutputQuantity, _recipient); + mintOutput = _mintLocal(0, mAssetMinted, _minOutputQuantity, true, _recipient); } } - // /** - // * @dev Mint with multiple bAssets, at a 1:1 ratio to mAsset. This contract - // * must have approval to spend the senders bAssets - // * @param _inputs Non-duplicate address array of bASset addresses to deposit for the minted mAsset tokens. - // * @param _inputQuantities Quantity of each bAsset to deposit for the minted mAsset. - // * Order of array should mirror the above bAsset addresses. - // * @param _minOutputQuantity Minimum mAsset quanity to be minted. This protects against slippage. - // * @param _recipient Address to receive the newly minted mAsset tokens - // * @return mintOutput Quantity of newly minted mAssets for the deposited bAssets. - // */ - // function mintMulti( - // address[] calldata _inputs, - // uint256[] calldata _inputQuantities, - // uint256 _minOutputQuantity, - // address _recipient - // ) external nonReentrant whenInOperation returns (uint256 mintOutput) { - // mintOutput = _mintMulti(_inputs, _inputQuantities, _minOutputQuantity, _recipient); - // } + function mintMulti( + address[] calldata _inputs, + uint256[] calldata _inputQuantities, + uint256 _minOutputQuantity, + address _recipient + ) external nonReentrant whenInOperation returns (uint256 mintOutput) { + mintOutput = _mintMulti(_inputs, _inputQuantities, _minOutputQuantity, _recipient); + } /** * @dev Get the projected output of a given mint @@ -268,14 +268,20 @@ contract FeederPool is uint8 _idx, uint256 _inputQuantity, uint256 _minOutputQuantity, + bool _skipTransfer, address _recipient ) internal returns (uint256 tokensMinted) { BassetData[] memory allBassets = bAssetData; - Cache memory cache = _getCacheDetails(); // Transfer collateral to the platform integration address and call deposit BassetPersonal memory personal = bAssetPersonal[_idx]; - uint256 quantityDeposited = - Manager.depositTokens(personal, allBassets[_idx].ratio, _inputQuantity, cache.maxCache); + uint256 quantityDeposited; + if(_skipTransfer) { + // TODO - cleanup and gas minimise + quantityDeposited = IERC20(personal.addr).balanceOf(address(this)); + } else { + Cache memory cache = _getCacheDetails(); + quantityDeposited = Manager.depositTokens(personal, allBassets[_idx].ratio, _inputQuantity, cache.maxCache); + } // Validation should be after token transfer, as bAssetQty is unknown before tokensMinted = validator.computeMint(allBassets, _idx, quantityDeposited, _getConfig()); require(tokensMinted >= _minOutputQuantity, "Mint quantity < min qty"); @@ -288,54 +294,77 @@ contract FeederPool is emit Minted(msg.sender, _recipient, tokensMinted, personal.addr, quantityDeposited); } - // /** @dev Mint Multi */ - // function _mintMulti( - // address[] memory _inputs, - // uint256[] memory _inputQuantities, - // uint256 _minOutputQuantity, - // address _recipient - // ) internal returns (uint256 tokensMinted) { - // require(_recipient != address(0), "Invalid recipient"); - // uint256 len = _inputQuantities.length; - // require(len > 0 && len == _inputs.length, "Input array mismatch"); - // // Load bAssets from storage into memory - // (uint8[] memory indexes, BassetPersonal[] memory personals) = _getBassets(_inputs); - // BassetData[] memory allBassets = bAssetData; - // Cache memory cache = _getCacheDetails(); - // uint256[] memory quantitiesDeposited = new uint256[](len); - // // Transfer the Bassets to the integrator, update storage and calc MassetQ - // for (uint256 i = 0; i < len; i++) { - // uint256 bAssetQuantity = _inputQuantities[i]; - // if (bAssetQuantity > 0) { - // uint8 idx = indexes[i]; - // BassetData memory data = allBassets[idx]; - // BassetPersonal memory personal = personals[i]; - // uint256 quantityDeposited = - // Manager.depositTokens(personal, data.ratio, bAssetQuantity, cache.maxCache); - // quantitiesDeposited[i] = quantityDeposited; - // bAssetData[idx].vaultBalance = - // data.vaultBalance + - // SafeCast.toUint128(quantityDeposited); - // } - // } - // // Validate the proposed mint, after token transfer - // tokensMinted = validator.computeMintMulti( - // allBassets, - // indexes, - // quantitiesDeposited, - // _getConfig() - // ); - // require(tokensMinted >= _minOutputQuantity, "Mint quantity < min qty"); - // require(tokensMinted > 0, "Zero mAsset quantity"); - - // // Mint the LP Token - // _mint(_recipient, tokensMinted); - // emit MintedMulti(msg.sender, _recipient, tokensMinted, _inputs, _inputQuantities); - // } + // TODO - do we support complex combinations like this? + // If want to mint with mpAsset, maybe use a wrapper instead? + function _mintMulti( + address[] memory _inputs, + uint256[] memory _inputQuantities, + uint256 _minOutputQuantity, + address _recipient + ) internal returns (uint256 tokensMinted) { + require(_recipient != address(0), "Invalid recipient"); + uint256 len = _inputQuantities.length; + require(len > 0 && len == _inputs.length, "Input array mismatch"); + + bool exists; + uint8[] memory indexes = new uint8[](len); + for (uint256 i = 0; i < len; i++) { + (exists, indexes[i]) = _getAsset(_inputs[i]); + console.log('asset: ', _inputs[i], exists, indexes[i]); + require(exists, "Invalid asset"); + } + console.log('two'); + // Load bAssets from storage into memory + BassetData[] memory allBassets = bAssetData; + Cache memory cache = _getCacheDetails(); + uint256[] memory quantitiesDeposited = new uint256[](len); + // Transfer the Bassets to the integrator, update storage and calc MassetQ + for (uint256 i = 0; i < len; i++) { + console.log('three'); + uint256 bAssetQuantity = _inputQuantities[i]; + if (bAssetQuantity > 0) { + uint8 idx = indexes[i]; + BassetData memory data = allBassets[idx]; + console.log('three.1', address(Manager)); + BassetPersonal memory personal = bAssetPersonal[idx]; + console.log('three.2', bAssetPersonal[idx].addr); + uint256 quantityDeposited = + Manager.depositTokens( + personal, + data.ratio, + bAssetQuantity, + cache.maxCache + ); + + console.log('three.3', quantityDeposited); + quantitiesDeposited[i] = quantityDeposited; + bAssetData[idx].vaultBalance = + data.vaultBalance + + SafeCast.toUint128(quantityDeposited); + } + } + console.log('four', address(validator), quantitiesDeposited[0], quantitiesDeposited[1]); + console.log('four', address(validator), indexes[0], indexes[1]); + console.log('four', address(validator), allBassets[0].ratio, allBassets[1].ratio); + // Validate the proposed mint, after token transfer + tokensMinted = validator.computeMintMulti( + allBassets, + indexes, + quantitiesDeposited, + _getConfig() + ); + console.log('four.1'); + require(tokensMinted >= _minOutputQuantity, "Mint quantity < min qty"); + require(tokensMinted > 0, "Zero mAsset quantity"); + console.log('five'); + // Mint the LP Token + _mint(_recipient, tokensMinted); + emit MintedMulti(msg.sender, _recipient, tokensMinted, _inputs, _inputQuantities); + } - // /*************************************** - // SWAP (PUBLIC) - // ****************************************/ + /*************************************** + SWAP (PUBLIC) + ****************************************/ /** * @dev Swaps one bAsset for another bAsset using the bAsset addresses. @@ -378,10 +407,8 @@ contract FeederPool is } // Else we are swapping into fAsset // Mint mAsset from mp > Swap into fAsset here - else { - uint256 mAssetQuantity = IMasset(mAsset).mint(_input, _inputQuantity, 0, address(this)); - return _swapLocal(0, outputIdx, mAssetQuantity, _minOutputQuantity, _recipient); - } + uint256 mAssetQuantity = IMasset(mAsset).mint(_input, _inputQuantity, 0, address(this)); + return _swapLocal(0, outputIdx, mAssetQuantity, _minOutputQuantity, _recipient); } /** @@ -859,11 +886,6 @@ contract FeederPool is return Cache(supply, supply.mulTruncate(cacheSize)); } - struct Asset { - uint8 idx; - BassetPersonal personal; - } - function _getAsset(address _asset) internal view returns (bool exists, uint8 idx) { // if input is mAsset then we know the position if (_asset == mAsset) return (true, 0); @@ -872,13 +894,7 @@ contract FeederPool is return (bAssetPersonal[1].addr == _asset, 1); } - // /** - // * @dev Gets a an array of bAssets from storage and protects against duplicates - // * @param _bAssets Addresses of the assets - // * @return indexes Indexes of the assets - // * @return personal Personal details for the assets - // */ - // function _getBassets(address[] memory _bAssets) + // function _getAssets(address[] memory _assets) // internal // view // returns (uint8[] memory indexes, BassetPersonal[] memory personal) diff --git a/contracts/masset/InvariantValidator.sol b/contracts/masset/InvariantValidator.sol index 91b3bba8..48f2ece6 100644 --- a/contracts/masset/InvariantValidator.sol +++ b/contracts/masset/InvariantValidator.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity 0.8.0; +import "hardhat/console.sol"; + import { IInvariantValidator } from "./IInvariantValidator.sol"; import { Root } from "../shared/Root.sol"; @@ -76,8 +78,10 @@ contract InvariantValidator is IInvariantValidator { uint256[] calldata _rawInputs, InvariantConfig memory _config ) external view override returns (uint256 mintAmount) { + console.log("Validator: mintMulti"); // 1. Get raw reserves (uint256[] memory x, uint256 sum) = _getReserves(_bAssets); + console.log("Validator: mintMulti 1"); // 2. Get value of reserves according to invariant uint256 k0 = _invariant(x, sum, _config.a); @@ -91,8 +95,10 @@ contract InvariantValidator is IInvariantValidator { x[idx] += scaledInput; sum += scaledInput; } + console.log("Validator: mintMulti 2"); // 4. Finalise mint require(_inBounds(x, sum, _config.limits), "Exceeds weight limits"); + console.log("Validator: mintMulti 3"); mintAmount = _computeMintOutput(x, sum, k0, _config.a); } @@ -268,12 +274,14 @@ contract InvariantValidator is IInvariantValidator { uint256[] memory _x, uint256 _sum, WeightLimits memory _limits - ) internal pure returns (bool inBounds) { + ) internal view returns (bool inBounds) { uint256 len = _x.length; inBounds = true; uint256 w; + console.log("min: ", _limits.min, " max :", _limits.max); for (uint256 i = 0; i < len; i++) { w = (_x[i] * 1e18) / _sum; + console.log("weight", i, w); if (w > _limits.max || w < _limits.min) return false; } } diff --git a/contracts/masset/Manager.sol b/contracts/masset/Manager.sol index ecbbc384..5b66812e 100644 --- a/contracts/masset/Manager.sol +++ b/contracts/masset/Manager.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.0; pragma abicoder v2; +import "hardhat/console.sol"; + // External import { IPlatformIntegration } from "../interfaces/IPlatformIntegration.sol"; import { IInvariantValidator } from "./IInvariantValidator.sol"; @@ -397,8 +399,10 @@ library Manager { uint256 _quantity, uint256 _maxCache ) external returns (uint256 quantityDeposited) { + console.log("m1"); // 0. If integration is 0, short circuit if (_bAsset.integrator == address(0)) { + console.log("m2", _bAsset.addr, _quantity, IERC20(_bAsset.addr).allowance(msg.sender, address(this))); (uint256 received, ) = MassetHelpers.transferReturnBalance( msg.sender, @@ -406,6 +410,7 @@ library Manager { _bAsset.addr, _quantity ); + console.log("m3", _bAsset.addr, _quantity, IERC20(_bAsset.addr).allowance(msg.sender, address(this))); return received; } diff --git a/contracts/shared/MassetHelpers.sol b/contracts/shared/MassetHelpers.sol index 36aa5ce6..449ce814 100644 --- a/contracts/shared/MassetHelpers.sol +++ b/contracts/shared/MassetHelpers.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity 0.8.0; +import "hardhat/console.sol"; + import { SafeERC20 } from "@openzeppelin/contracts-sol8/contracts/token/ERC20/SafeERC20.sol"; import { IERC20 } from "@openzeppelin/contracts-sol8/contracts/token/ERC20/IERC20.sol"; @@ -21,8 +23,11 @@ library MassetHelpers { uint256 _qty ) internal returns (uint256 receivedQty, uint256 recipientBalance) { uint256 balBefore = IERC20(_bAsset).balanceOf(_recipient); + console.log('tf1', _qty, IERC20(_bAsset).balanceOf(_sender), IERC20(_bAsset).allowance(_sender, _recipient)); IERC20(_bAsset).safeTransferFrom(_sender, _recipient, _qty); + console.log('tf2'); recipientBalance = IERC20(_bAsset).balanceOf(_recipient); + console.log('tf3', recipientBalance, balBefore); receivedQty = recipientBalance - balBefore; } diff --git a/test-utils/machines/feederMachine.ts b/test-utils/machines/feederMachine.ts new file mode 100644 index 00000000..f6f3eb1e --- /dev/null +++ b/test-utils/machines/feederMachine.ts @@ -0,0 +1,218 @@ +/* eslint-disable class-methods-use-this */ +/* eslint-disable no-nested-ternary */ + +import { Signer } from "ethers" +import { ethers } from "hardhat" +import { + InvariantValidator__factory, + MockInvariantValidator__factory, + AssetProxy__factory, + MockNexus__factory, + ExposedMasset, + ExposedMasset__factory, + Masset, + InvariantValidator, + MockERC20, + DelayedProxyAdmin, + MockInitializableToken, + MockAaveV2__factory, + MockATokenV2__factory, + MockPlatformIntegration, + MockPlatformIntegration__factory, + IPlatformIntegration, + MockInitializableToken__factory, + MockInitializableTokenWithFee__factory, + Manager, + AssetProxy, + FeederPool, + FeederPool__factory, +} from "types/generated" +import { BN, minimum, simpleToExactAmount } from "@utils/math" +import { fullScale, MainnetAccounts, ratioScale, ZERO_ADDRESS, DEAD_ADDRESS } from "@utils/constants" +import { Basset } from "@utils/mstable-objects" +import { StandardAccounts } from "./standardAccounts" +import { ActionDetails, BasketComposition, BassetIntegrationDetails } from "../../types/machines" +import { MassetMachine, MassetDetails } from "./mAssetMachine" + +export interface FeederDetails { + pool?: FeederPool + validator?: InvariantValidator + mAsset?: MockERC20 + fAsset?: MockERC20 + // [0] = mAsset + // [1] = fAsset + bAssets?: MockERC20[] + mAssetDetails?: MassetDetails +} + +export class FeederMachine { + public sa: StandardAccounts + + public ma: MainnetAccounts + + public mAssetMachine: MassetMachine + + constructor(massetMachine: MassetMachine) { + this.mAssetMachine = massetMachine + this.sa = massetMachine.sa + } + + public async initAccounts(accounts: Signer[]): Promise { + this.sa = await new StandardAccounts().initAccounts(accounts) + return this + } + + public async deployFeeder(seedBasket = true): Promise { + const mAssetDetails = await this.mAssetMachine.deployMasset(false, false, false) + // Mints 10k mAsset to begin with + await this.mAssetMachine.seedWithWeightings(mAssetDetails, [2500, 2500, 2500, 2500]) + + const bBtc = await this.mAssetMachine.loadBassetProxy("Binance BTC", "bBTC", 18) + const bAssets = [mAssetDetails.mAsset as MockERC20, bBtc] + const validator = await new InvariantValidator__factory(this.sa.default.signer).deploy( + simpleToExactAmount(1, 24), + simpleToExactAmount(1, 24), + ) + const linkedAddress = { + __$1a38b0db2bd175b310a9a3f8697d44eb75$__: mAssetDetails.managerLib.address, + } + console.log("i") + const impl = await new FeederPool__factory(linkedAddress, this.sa.default.signer).deploy(DEAD_ADDRESS, mAssetDetails.mAsset.address) + console.log("ii") + const data = impl.interface.encodeFunctionData("initialize", [ + "mStable mBTC/bBTC Feeder", + "bBTC fPool", + validator.address, + { + addr: mAssetDetails.mAsset.address, + integrator: ZERO_ADDRESS, + hasTxFee: false, + status: 0, + }, + { + addr: bBtc.address, + integrator: ZERO_ADDRESS, + hasTxFee: false, + status: 0, + }, + mAssetDetails.bAssets.map((b) => b.address), + { + a: simpleToExactAmount(1, 2), + limits: { + min: simpleToExactAmount(3, 16), + max: simpleToExactAmount(97, 16), + }, + }, + ]) + console.log("iii") + const poolProxy = await new AssetProxy__factory(this.sa.default.signer).deploy(impl.address, DEAD_ADDRESS, data) + console.log("iv") + const pool = await new FeederPool__factory(linkedAddress, this.sa.default.signer).attach(poolProxy.address) + console.log("v") + const a = await pool.getConfig() + console.log(a) + if (seedBasket) { + const approvals = await Promise.all(bAssets.map((b) => this.mAssetMachine.approveMasset(b, pool, 50, this.sa.default.signer))) + console.log("vi") + await pool.mintMulti( + bAssets.map((b) => b.address), + approvals, + 0, + this.sa.default.address, + ) + } + return { + pool, + validator, + mAsset: mAssetDetails.mAsset as MockERC20, + fAsset: bBtc, + bAssets, + mAssetDetails, + } + } + + public async getBassets(feederDetails: FeederDetails): Promise { + const [personal, data] = await feederDetails.mAsset.getBassets() + const bArrays: Array = personal.map((b, i) => { + const d = data[i] + return { + addr: b.addr, + status: b.status, + isTransferFeeCharged: b.hasTxFee, + ratio: BN.from(d.ratio), + vaultBalance: BN.from(d.vaultBalance), + integratorAddr: b.integrator, + } + }) + const bAssetContracts: MockERC20[] = await Promise.all( + bArrays.map((b) => ethers.getContractAt("MockERC20", b.addr, this.sa.default.signer) as Promise), + ) + const integrators = (await Promise.all( + bArrays.map((b) => + b.integratorAddr === ZERO_ADDRESS + ? null + : ethers.getContractAt("MockPlatformIntegration", b.integratorAddr, this.sa.default.signer), + ), + )) as Array + return bArrays.map((b, i) => ({ + ...b, + contract: bAssetContracts[i], + integrator: integrators[i], + })) + } + + public async getBasketComposition(feederDetails: FeederDetails): Promise { + // raw bAsset data + const bAssets = await this.getBassets(feederDetails) + + // total supply of mAsset + const supply = await feederDetails.pool.totalSupply() + // get actual balance of each bAsset + const rawBalances = await Promise.all( + bAssets.map((b) => + b.integrator ? b.contract.balanceOf(b.integrator.address) : b.contract.balanceOf(feederDetails.pool.address), + ), + ) + const platformBalances = await Promise.all( + bAssets.map((b) => (b.integrator ? b.integrator.callStatic.checkBalance(b.addr) : BN.from(0))), + ) + + const balances = rawBalances.map((b, i) => b.add(platformBalances[i])) + // get overweight + const currentVaultUnits = bAssets.map((b) => + BN.from(b.vaultBalance) + .mul(BN.from(b.ratio)) + .div(ratioScale), + ) + // get total amount + const sumOfBassets = currentVaultUnits.reduce((p, c) => p.add(c), BN.from(0)) + return { + bAssets: bAssets.map((b, i) => ({ + ...b, + address: b.addr, + mAssetUnits: currentVaultUnits[i], + actualBalance: balances[i], + rawBalance: rawBalances[i], + platformBalance: platformBalances[i], + })), + totalSupply: supply, + surplus: BN.from(0), + sumOfBassets, + failed: false, + undergoingRecol: false, + } + } + + public async approveFeeder( + bAsset: MockERC20, + feeder: string, + fullMassetUnits: number | BN | string, + sender: Signer = this.sa.default.signer, + inputIsBaseUnits = false, + ): Promise { + const bAssetDecimals = await bAsset.decimals() + const approvalAmount: BN = inputIsBaseUnits ? BN.from(fullMassetUnits) : simpleToExactAmount(fullMassetUnits, bAssetDecimals) + await bAsset.connect(sender).approve(feeder, approvalAmount) + return approvalAmount + } +} diff --git a/test-utils/machines/index.ts b/test-utils/machines/index.ts index 10d9b628..cb12aab1 100644 --- a/test-utils/machines/index.ts +++ b/test-utils/machines/index.ts @@ -1,2 +1,3 @@ export { MassetMachine, MassetDetails } from "./mAssetMachine" export { StandardAccounts, Account } from "./standardAccounts" +export { FeederMachine, FeederDetails } from "./feederMachine" diff --git a/test-utils/machines/mAssetMachine.ts b/test-utils/machines/mAssetMachine.ts index d131196e..6650c77c 100644 --- a/test-utils/machines/mAssetMachine.ts +++ b/test-utils/machines/mAssetMachine.ts @@ -23,6 +23,7 @@ import { MockInitializableToken__factory, MockInitializableTokenWithFee__factory, Manager, + AssetProxy, } from "types/generated" import { BN, minimum, simpleToExactAmount } from "@utils/math" import { fullScale, MainnetAccounts, ratioScale, ZERO_ADDRESS, DEAD_ADDRESS } from "@utils/constants" @@ -39,6 +40,7 @@ export interface MassetDetails { platform?: MockPlatformIntegration aavePlatformAddress?: string managerLib?: Manager + wrappedManagerLib?: Manager } export class MassetMachine { @@ -127,7 +129,8 @@ export class MassetMachine { platform: useLendingMarkets ? await new MockPlatformIntegration__factory(this.sa.default.signer).attach(integrationAddress) : null, - managerLib: (await ManagerFactory.attach(mAsset.address)) as Manager, + managerLib, + wrappedManagerLib: (await ManagerFactory.attach(mAsset.address)) as Manager, } } @@ -593,7 +596,11 @@ export class MassetMachine { const balances = rawBalances.map((b, i) => b.add(platformBalances[i])) // get overweight - const currentVaultUnits = bAssets.map((b) => BN.from(b.vaultBalance).mul(BN.from(b.ratio)).div(ratioScale)) + const currentVaultUnits = bAssets.map((b) => + BN.from(b.vaultBalance) + .mul(BN.from(b.ratio)) + .div(ratioScale), + ) // get total amount const sumOfBassets = currentVaultUnits.reduce((p, c) => p.add(c), BN.from(0)) return { @@ -624,7 +631,7 @@ export class MassetMachine { */ public async approveMasset( bAsset: MockERC20, - mAsset: Masset | ExposedMasset, + mAsset: Masset | ExposedMasset | MockERC20 | AssetProxy, fullMassetUnits: number | BN | string, sender: Signer = this.sa.default.signer, inputIsBaseUnits = false, @@ -672,7 +679,12 @@ export class MassetMachine { const totalSupply = await mAsset.totalSupply() const surplus = await mAsset.surplus() const cacheSize = await mAsset.cacheSize() - const maxC = totalSupply.add(surplus).mul(ratioScale).div(BN.from(bAsset.ratio)).mul(cacheSize).div(fullScale) + const maxC = totalSupply + .add(surplus) + .mul(ratioScale) + .div(BN.from(bAsset.ratio)) + .mul(cacheSize) + .div(fullScale) const newSum = BN.from(integratorBalBefore).add(amount) const expectInteraction = type === "deposit" ? newSum.gte(maxC) : amount.gt(BN.from(integratorBalBefore)) return { @@ -682,7 +694,10 @@ export class MassetMachine { type === "deposit" ? newSum.sub(maxC.div(2)) : minimum( - maxC.div(2).add(amount).sub(BN.from(integratorBalBefore)), + maxC + .div(2) + .add(amount) + .sub(BN.from(integratorBalBefore)), BN.from(bAsset.vaultBalance).sub(BN.from(integratorBalBefore)), ), rawBalance: diff --git a/test/feeders/basic-fns.spec.ts b/test/feeders/basic-fns.spec.ts index e69de29b..8161a389 100644 --- a/test/feeders/basic-fns.spec.ts +++ b/test/feeders/basic-fns.spec.ts @@ -0,0 +1,73 @@ +import { ethers } from "hardhat" +import { expect } from "chai" + +import { assertBNClosePercent } from "@utils/assertions" +import { simpleToExactAmount } from "@utils/math" +import { MassetDetails, MassetMachine, StandardAccounts, FeederMachine, FeederDetails } from "@utils/machines" + +describe("Feeder Pools", () => { + let sa: StandardAccounts + let feederMachine: FeederMachine + let details: MassetDetails + let feeder: FeederDetails + + const runSetup = async (seedBasket = true): Promise => { + feeder = await feederMachine.deployFeeder(seedBasket) + } + + before("Init contract", async () => { + const accounts = await ethers.getSigners() + const mAssetMachine = await new MassetMachine().initAccounts(accounts) + feederMachine = await new FeederMachine(mAssetMachine) + sa = mAssetMachine.sa + + await runSetup() + }) + + describe("testing some mints", () => { + before(async () => { + await runSetup(true) + }) + it("should mint multi locally", async () => { + const { bAssets, pool } = feeder + const dataStart = await feederMachine.getBasketComposition(feeder) + + const approvals = await Promise.all(bAssets.map((b) => feederMachine.approveFeeder(b, pool.address, 100))) + await pool.mintMulti( + bAssets.map((b) => b.address), + approvals, + 99, + sa.default.address, + ) + const dataEnd = await feederMachine.getBasketComposition(feeder) + + expect(dataEnd.totalSupply.sub(dataStart.totalSupply)).to.eq(simpleToExactAmount(200, 18)) + }) + it("should mint single locally", async () => { + const { pool, mAsset, fAsset } = feeder + + // Mint with mAsset + let approval = await feederMachine.approveFeeder(mAsset, pool.address, 100) + await pool.mint(mAsset.address, approval, 95, sa.default.address) + + // Mint with fAsset + approval = await feederMachine.approveFeeder(fAsset, pool.address, 100) + await pool.mint(fAsset.address, approval, 95, sa.default.address) + }) + it("should mint single via main pool", async () => { + const { pool, mAssetDetails } = feeder + const { bAssets } = mAssetDetails + // Mint with mpAsset[0] + let approval = await feederMachine.approveFeeder(bAssets[0], pool.address, 100) + await pool.mint(bAssets[0].address, approval, 95, sa.default.address) + // Mint with mpAsset[1] + approval = await feederMachine.approveFeeder(bAssets[1], pool.address, 100) + await pool.mint(bAssets[1].address, approval, 95, sa.default.address) + }) + }) + describe("testing some swaps", () => { + before(async () => { + await runSetup() + }) + }) +}) diff --git a/test/masset/admin.spec.ts b/test/masset/admin.spec.ts index 87a9f97c..bf9abe1c 100644 --- a/test/masset/admin.spec.ts +++ b/test/masset/admin.spec.ts @@ -161,13 +161,13 @@ describe("Masset Admin", () => { expect(personalData.hasTxFee).to.be.false const tx = mAsset.connect(sa.governor.signer).setTransferFeesFlag(personalData.addr, true) - await expect(tx).to.emit(details.managerLib, "TransferFeeEnabled").withArgs(personalData.addr, true) + await expect(tx).to.emit(details.wrappedManagerLib, "TransferFeeEnabled").withArgs(personalData.addr, true) personalData = await mAsset.bAssetPersonal(3) expect(personalData.hasTxFee).to.be.true // restore the flag back to false const tx2 = mAsset.connect(sa.governor.signer).setTransferFeesFlag(personalData.addr, false) - await expect(tx2).to.emit(details.managerLib, "TransferFeeEnabled").withArgs(personalData.addr, false) + await expect(tx2).to.emit(details.wrappedManagerLib, "TransferFeeEnabled").withArgs(personalData.addr, false) await tx2 personalData = await mAsset.bAssetPersonal(3) expect(personalData.hasTxFee).to.be.false @@ -179,13 +179,13 @@ describe("Masset Admin", () => { expect(personalData.hasTxFee).to.be.false const tx = details.mAsset.connect(sa.governor.signer).setTransferFeesFlag(personalData.addr, true) - await expect(tx).to.emit(details.managerLib, "TransferFeeEnabled").withArgs(personalData.addr, true) + await expect(tx).to.emit(details.wrappedManagerLib, "TransferFeeEnabled").withArgs(personalData.addr, true) const personalDataAfter = await details.mAsset.bAssetPersonal(2) expect(personalDataAfter.hasTxFee).to.be.true // restore the flag back to false const tx2 = details.mAsset.connect(sa.governor.signer).setTransferFeesFlag(personalData.addr, false) - await expect(tx2).to.emit(details.managerLib, "TransferFeeEnabled").withArgs(personalData.addr, false) + await expect(tx2).to.emit(details.wrappedManagerLib, "TransferFeeEnabled").withArgs(personalData.addr, false) const personalDataAfterRestore = await details.mAsset.bAssetPersonal(2) expect(personalDataAfterRestore.hasTxFee).to.be.false }) @@ -454,7 +454,7 @@ describe("Masset Admin", () => { // call migrate const tx = details.mAsset.connect(sa.governor.signer).migrateBassets([transferringAsset.address], newMigration.address) // emits BassetsMigrated - await expect(tx).to.emit(details.managerLib, "BassetsMigrated").withArgs([transferringAsset.address], newMigration.address) + await expect(tx).to.emit(details.wrappedManagerLib, "BassetsMigrated").withArgs([transferringAsset.address], newMigration.address) // moves all bAssets from old to new const migratedBal = await newMigration.callStatic.checkBalance(transferringAsset.address) expect(migratedBal).eq(bal) @@ -481,7 +481,7 @@ describe("Masset Admin", () => { // call migrate const tx = details.mAsset.connect(sa.governor.signer).migrateBassets([transferringAsset.address], newMigration.address) // emits BassetsMigrated - await expect(tx).to.emit(details.managerLib, "BassetsMigrated").withArgs([transferringAsset.address], newMigration.address) + await expect(tx).to.emit(details.wrappedManagerLib, "BassetsMigrated").withArgs([transferringAsset.address], newMigration.address) // moves all bAssets from old to new const migratedBal = await newMigration.callStatic.checkBalance(transferringAsset.address) expect(migratedBal).eq(bal) @@ -514,7 +514,7 @@ describe("Masset Admin", () => { // call migrate const tx = details.mAsset.connect(sa.governor.signer).migrateBassets([transferringAsset.address], newMigration.address) // emits BassetsMigrated - await expect(tx).to.emit(details.managerLib, "BassetsMigrated").withArgs([transferringAsset.address], newMigration.address) + await expect(tx).to.emit(details.wrappedManagerLib, "BassetsMigrated").withArgs([transferringAsset.address], newMigration.address) // moves all bAssets from old to new const migratedBal = await newMigration.callStatic.checkBalance(transferringAsset.address) expect(migratedBal).eq(0) @@ -534,11 +534,11 @@ describe("Masset Admin", () => { await runSetup(true, false, true) }) it("should skip when Normal (by governor)", async () => { - const { bAssets, mAsset, managerLib } = details + const { bAssets, mAsset, wrappedManagerLib } = details const basketBefore = await mAsset.getBasket() expect(basketBefore[0]).to.false const tx = mAsset.connect(sa.governor.signer).negateIsolation(bAssets[0].address) - await expect(tx).to.emit(managerLib, "BassetStatusChanged").withArgs(bAssets[0].address, BassetStatus.Normal) + await expect(tx).to.emit(wrappedManagerLib, "BassetStatusChanged").withArgs(bAssets[0].address, BassetStatus.Normal) const afterBefore = await mAsset.getBasket() expect(afterBefore[0]).to.false }) @@ -555,7 +555,7 @@ describe("Masset Admin", () => { await expect(mAsset.connect(sa.governor.signer).negateIsolation(sa.other.address)).to.be.revertedWith("Invalid asset") }) it("should succeed when status is 'BrokenAbovePeg' (by governor)", async () => { - const { bAssets, mAsset, managerLib } = details + const { bAssets, mAsset, wrappedManagerLib } = details const bAsset = bAssets[1] const basketBefore = await mAsset.getBasket() @@ -572,7 +572,7 @@ describe("Masset Admin", () => { const tx = mAsset.connect(sa.governor.signer).negateIsolation(bAsset.address) - await expect(tx).to.emit(managerLib, "BassetStatusChanged").withArgs(bAsset.address, BassetStatus.Normal) + await expect(tx).to.emit(wrappedManagerLib, "BassetStatusChanged").withArgs(bAsset.address, BassetStatus.Normal) await tx const basketAfterNegateIsolation = await mAsset.getBasket() expect(basketAfterNegateIsolation[0], "after negateIsolation undergoingRecol").to.false @@ -580,7 +580,7 @@ describe("Masset Admin", () => { expect(bAssetStateAfterNegateIsolation.personal.status, "after negateIsolation personal.status").to.eq(BassetStatus.Normal) }) it("should succeed when two bAssets have BrokenBelowPeg", async () => { - const { bAssets, mAsset, managerLib } = details + const { bAssets, mAsset, wrappedManagerLib } = details const basketBefore = await mAsset.getBasket() expect(basketBefore[0], "before undergoingRecol").to.false @@ -597,7 +597,7 @@ describe("Masset Admin", () => { const tx = mAsset.connect(sa.governor.signer).negateIsolation(bAssets[3].address) - await expect(tx).to.emit(managerLib, "BassetStatusChanged").withArgs(bAssets[3].address, BassetStatus.Normal) + await expect(tx).to.emit(wrappedManagerLib, "BassetStatusChanged").withArgs(bAssets[3].address, BassetStatus.Normal) await tx const basketAfterNegateIsolation = await mAsset.getBasket() expect(basketAfterNegateIsolation[0], "after negateIsolation undergoingRecol").to.true @@ -626,7 +626,7 @@ describe("Masset Admin", () => { const startTime = await getTimestamp() const endTime = startTime.add(ONE_WEEK.mul(2)) const tx = mAsset.startRampA(120, endTime) - await expect(tx).to.emit(details.managerLib, "StartRampA").withArgs(10000, 12000, startTime.add(1), endTime) + await expect(tx).to.emit(details.wrappedManagerLib, "StartRampA").withArgs(10000, 12000, startTime.add(1), endTime) // after values const ampDataAfter = await mAsset.ampData() @@ -864,7 +864,7 @@ describe("Masset Admin", () => { const currentA = await mAsset.getA() const currentTime = await getTimestamp() const tx = mAsset.stopRampA() - await expect(tx).to.emit(details.managerLib, "StopRampA").withArgs(currentA, currentTime.add(1)) + await expect(tx).to.emit(details.wrappedManagerLib, "StopRampA").withArgs(currentA, currentTime.add(1)) expect(await mAsset.getA()).to.eq(currentA) const ampDataAfter = await mAsset.ampData()