import { Signer } from "ethers" ; import { AssetProxy__factory , MockNexus__factory , ExposedMasset , ExposedMasset__factory , Masset , MockERC20 , DelayedProxyAdmin , MockInitializableToken , MockAaveV2__factory , MockATokenV2__factory , MockPlatformIntegration , MockPlatformIntegration__factory , IPlatformIntegration , MockInitializableToken__factory , MockInitializableTokenWithFee__factory , AssetProxy , MockNexus , MassetLogic , MassetManager , MassetLogic__factory , MassetManager__factory , MockERC20__factory , } from "types/generated" ; import { BN , minimum , simpleToExactAmount } from "@utils/math" ; import { fullScale , ratioScale , ZERO_ADDRESS , DEAD_ADDRESS } from "@utils/constants" ; import { Basset } from "@utils/mstable-objects" ; import { EthAddress } from "types/common" ; import { StandardAccounts } from "./standardAccounts" ; import { ActionDetails , ATokenDetails , BasketComposition , BassetIntegrationDetails } from "../../types/machines" ; export interface MassetDetails { mAsset ? : ExposedMasset ; bAssets ? : Array < MockERC20 > ; pTokens ? : Array < string > ; proxyAdmin ? : DelayedProxyAdmin ; platform ? : MockPlatformIntegration ; aavePlatformAddress ? : string ; integrationAddress ? : string ; logicLib ? : MassetLogic ; managerLib ? : MassetManager ; wrappedManagerLib ? : MassetManager ; nexus ? : MockNexus ; } export class MassetMachine { public sa : StandardAccounts ; public async initAccounts ( accounts ) { this . sa = await new StandardAccounts ( ) . initAccounts ( accounts ) ; return this ; } public async deployLite ( a = 135 ) { const bAssets = await Promise . all ( [ 0 , 1 , 2 ] . map ( ( i ) => this . loadBassetProxy ( `${ i }BASSET` , `${ i }BASSET` , 18 ) ) ) ; const logicLib = await new MassetLogic__factory ( this . sa . default . signer ) . deploy ( ) ; const ManagerFactory = await new MassetManager__factory ( this . sa . default . signer ) ; const managerLib = await ManagerFactory . deploy ( ) ; const nexus = await new MockNexus__factory ( this . sa . default . signer ) . deploy ( this . sa . governor . address , this . sa . mockSavingsManager . address , this . sa . mockInterestValidator . address ) ; const mAssetFactoryLibs = { "contracts/masset/MassetLogic.sol:MassetLogic" : logicLib . address , "contracts/masset/MassetManager.sol:MassetManager" : managerLib . address , } ; const MassetFactory = new ExposedMasset__factory ( mAssetFactoryLibs , this . sa . default . signer ) ; const impl = ( await MassetFactory . deploy ( nexus . address , simpleToExactAmount ( 5 , 13 ) ) ) as ExposedMasset ; const data = impl . interface . encodeFunctionData ( "initialize" , [ "mAsset Lite" , "mLite" , bAssets . map ( ( b ) => ( { addr : b . address , integrator : ZERO_ADDRESS , hasTxFee : false , status : 0 , } ) ) , { a : BN . from ( a ) , limits : { min : simpleToExactAmount ( 5 , 16 ) , max : simpleToExactAmount ( 75 , 16 ) , } , } , ] ) ; const mAsset = await new AssetProxy__factory ( this . sa . default . signer ) . deploy ( impl . address , DEAD_ADDRESS , data ) ; return { mAsset : ( await MassetFactory . attach ( mAsset . address ) ) as ExposedMasset , bAssets , logicLib : logicLib as MassetLogic , nexus , } ; } public async deployMasset ( useLendingMarkets = false , useTransferFees = false , a = 100 ) { const bAssets = await this . loadBassetsLocal ( useLendingMarkets , useTransferFees ) ; const logicLib = await new MassetLogic__factory ( this . sa . default . signer ) . deploy ( ) ; const ManagerFactory = await new MassetManager__factory ( this . sa . default . signer ) ; const managerLib = await ManagerFactory . deploy ( ) ; const nexus = await new MockNexus__factory ( this . sa . default . signer ) . deploy ( this . sa . governor . address , this . sa . mockSavingsManager . address , this . sa . mockInterestValidator . address ) ; const integrationAddress = useLendingMarkets ? ( await new MockPlatformIntegration__factory ( this . sa . default . signer ) . deploy ( nexus . address , bAssets . aavePlatformAddress , bAssets . bAssets . map ( ( b ) => b . address ) , bAssets . aTokens . map ( ( at ) => at . aToken ) ) ) . address : ZERO_ADDRESS ; const mAssetFactoryLibs = { "contracts/masset/MassetLogic.sol:MassetLogic" : logicLib . address , "contracts/masset/MassetManager.sol:MassetManager" : managerLib . address , } ; const MassetFactory = new ExposedMasset__factory ( mAssetFactoryLibs , this . sa . default . signer ) ; const impl = ( await MassetFactory . deploy ( nexus . address , simpleToExactAmount ( 5 , 13 ) ) ) as Masset ; const data = impl . interface . encodeFunctionData ( "initialize" , [ "mStable BTC" , "mBTC" , bAssets . bAssets . map ( ( b , i ) => ( { addr : b . address , integrator : integrationAddress , hasTxFee : bAssets . bAssetTxFees [ i ] , status : 0 , } ) ) , { a , limits : { min : simpleToExactAmount ( 5 , 16 ) , max : simpleToExactAmount ( 65 , 16 ) , } , } , ] ) ; const mAsset = await new AssetProxy__factory ( this . sa . default . signer ) . deploy ( impl . address , DEAD_ADDRESS , data ) ; if ( useLendingMarkets ) { await new MockPlatformIntegration__factory ( this . sa . default . signer ) . attach ( integrationAddress ) . addWhitelist ( [ mAsset . address ] ) ; } return { mAsset : ( await MassetFactory . attach ( mAsset . address ) ) as ExposedMasset , bAssets : bAssets . bAssets , aavePlatformAddress : bAssets . aavePlatformAddress , integrationAddress , platform : useLendingMarkets ? await new MockPlatformIntegration__factory ( this . sa . default . signer ) . attach ( integrationAddress ) : null , pTokens : useLendingMarkets ? bAssets . aTokens . map ( ( at ) => at . aToken ) : [ ] , logicLib , managerLib , wrappedManagerLib : await ManagerFactory . attach ( mAsset . address ) , nexus , } ; } dev param md param weights public async seedWithWeightings ( md , weights , inputIsInBaseUnits = false ) { const { mAsset , bAssets } = md ; const approvals = await Promise . all ( bAssets . map ( ( b , i ) => this . approveMasset ( b , mAsset , weights [ i ] , this . sa . default . signer , inputIsInBaseUnits ) ) ) ; await mAsset . mintMulti ( bAssets . map ( ( b ) => b . address ) , approvals , 0 , this . sa . default . address ) ; } public async loadBassetProxy ( name , sym , dec , recipient = this . sa . default . address , init = 10000000000 , enableUSDTFee = false ) { const tokenFactory = enableUSDTFee ? await new MockInitializableTokenWithFee__factory ( this . sa . default . signer ) : await new MockInitializableToken__factory ( this . sa . default . signer ) ; const AssetProxyFactory = new AssetProxy__factory ( this . sa . default . signer ) ; const mockInitializableToken = ( await tokenFactory . deploy ( ) ) as MockInitializableToken ; const data = await mockInitializableToken . interface . encodeFunctionData ( "initialize" , [ name , sym , dec , recipient , init ] ) ; const mAssetProxy = await AssetProxyFactory . deploy ( mockInitializableToken . address , this . sa . governor . address , data ) ; const mAsset = MockERC20__factory . connect ( mAssetProxy . address , this . sa . default . signer ) ; return mAsset ; } public async loadBassetsLocal ( useLendingMarkets = false , useTransferFees = false , recipient = this . sa . default . address ) { const mockBasset1 = await this . loadBassetProxy ( "Ren BTC" , "renBTC" , 18 , recipient ) ; const mockBasset2 = await this . loadBassetProxy ( "Synthetix BTC" , "sBTC" , 6 , recipient ) ; const mockBasset3 = await this . loadBassetProxy ( "Wrapped BTC" , "wBTC" , 12 , recipient , 10000000000 , useTransferFees ) ; const mockBasset4 = await this . loadBassetProxy ( "Binance Wrapped BTC" , "bBTC" , 18 , recipient , 10000000000 , useTransferFees ) ; const bAssets = [ mockBasset1 , mockBasset2 , mockBasset3 , mockBasset4 ] ; const bAssetTxFees = bAssets . map ( ( _ , i ) => useTransferFees && ( i === 2 || i === 3 ) ) ; const lendingProperties = useLendingMarkets ? await this . loadATokens ( bAssets ) : { } ; return { aavePlatformAddress : ZERO_ADDRESS , aTokens : [ ] , ... lendingProperties , bAssets , bAssetTxFees , } ; } param bAssets returns public async loadATokens ( bAssets ) { const mockAave = await new MockAaveV2__factory ( this . sa . default . signer ) . deploy ( ) ; await Promise . all ( bAssets . map ( async ( b ) => b . transfer ( mockAave . address , ( await b . totalSupply ( ) ) . div ( 1000 ) ) ) ) ; const aTokenFactory = new MockATokenV2__factory ( this . sa . default . signer ) ; const mockATokens = await Promise . all ( bAssets . map ( ( b ) => aTokenFactory . deploy ( mockAave . address , b . address ) ) ) ; await Promise . all ( bAssets . map ( ( b , i ) => mockAave . addAToken ( mockATokens [ i ] . address , b . address ) ) ) ; return { aavePlatformAddress : mockAave . address , aTokens : bAssets . map ( ( b , i ) => ( { bAsset : b . address , aToken : mockATokens [ i ] . address , } ) ) , } ; } public async getBassetsInMasset ( mAssetDetails ) { const [ personal , data ] = await mAssetDetails . mAsset . getBassets ( ) ; const bArrays = 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 = await Promise . all ( bArrays . map ( ( b ) => MockERC20__factory . connect ( b . addr , this . sa . default . signer ) ) ) ; const integrators = await Promise . all ( bArrays . map ( ( b ) => b . integratorAddr === ZERO_ADDRESS ? null : ( MockPlatformIntegration__factory . connect ( b . integratorAddr , this . sa . default . signer ) as unknown as IPlatformIntegration ) ) ) ; return bArrays . map ( ( b , i ) => ( { ... b , contract : bAssetContracts [ i ] , integrator : integrators [ i ] , } ) ) ; } public async getBasset ( mAssetDetails , bAssetAddress ) { const bAsset = await mAssetDetails . mAsset . getBasset ( bAssetAddress ) ; const bAssetContract = MockERC20__factory . connect ( bAsset . personal . addr , this . sa . default . signer ) ; const integrator = bAsset . personal . integrator === ZERO_ADDRESS ? null : ( ( await new MockPlatformIntegration__factory ( this . sa . default . signer ) . attach ( bAsset . personal . integrator ) ) as unknown as IPlatformIntegration ) ; return { addr : bAsset . personal . addr , status : bAsset . personal . status , isTransferFeeCharged : bAsset . personal . hasTxFee , ratio : BN . from ( bAsset . bData . ratio ) , vaultBalance : BN . from ( bAsset . bData . vaultBalance ) , integratorAddr : bAsset . personal . integrator , contract : bAssetContract , pToken : integrator ? await integrator . callStatic [ "bAssetToPToken(address)" ] ( bAsset . personal . addr ) : null , integrator , } ; } public async getBasketComposition ( mAssetDetails ) { const bAssets = await this . getBassetsInMasset ( mAssetDetails ) ; const [ failed , undergoingRecol ] = await mAssetDetails . mAsset . getBasket ( ) ; const supply = await mAssetDetails . mAsset . totalSupply ( ) ; const { surplus } = await mAssetDetails . mAsset . data ( ) ; const rawBalances = await Promise . all ( bAssets . map ( ( b ) => b . integrator ? b . contract . balanceOf ( b . integrator . address ) : b . contract . balanceOf ( mAssetDetails . mAsset . 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 ] ) ) ; const currentVaultUnits = bAssets . map ( ( b ) => BN . from ( b . vaultBalance ) . mul ( BN . from ( b . ratio ) ) . div ( ratioScale ) ) ; 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 , sumOfBassets , failed , undergoingRecol , } ; } dev param bAsset param mAsset param fullMassetUnits param sender param inputIsBaseUnits public async approveMasset ( bAsset , mAsset , fullMassetUnits , sender = this . sa . default . signer , inputIsBaseUnits = false ) { const bAssetDecimals = await bAsset . decimals ( ) ; const approvalAmount = inputIsBaseUnits ? BN . from ( fullMassetUnits ) : simpleToExactAmount ( fullMassetUnits , bAssetDecimals ) ; await bAsset . connect ( sender ) . approve ( mAsset . address , approvalAmount ) ; return approvalAmount ; } public async approveMassetMulti ( bAssets , mAsset , fullMassetUnits , sender ) { const result = Promise . all ( bAssets . map ( ( b ) => this . approveMasset ( b , mAsset , fullMassetUnits , sender ) ) ) ; return result ; } public static async getPlatformInteraction ( mAsset , type , amount , bAsset ) { const hasIntegrator = bAsset . integratorAddr === ZERO_ADDRESS ; const integratorBalBefore = await bAsset . contract . balanceOf ( bAsset . integrator ? bAsset . integratorAddr : mAsset . address ) ; if ( hasIntegrator ) { return { hasLendingMarket : false , expectInteraction : false , rawBalance : type === "deposit" ? integratorBalBefore . add ( amount ) : integratorBalBefore . sub ( amount ) , } ; } const hasTxFee = bAsset . isTransferFeeCharged ; if ( hasTxFee ) { return { hasLendingMarket : true , expectInteraction : true , amount , rawBalance : BN . from ( 0 ) , } ; } const totalSupply = await mAsset . totalSupply ( ) ; const { cacheSize , surplus } = await mAsset . data ( ) ; 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 { hasLendingMarket : true , expectInteraction , amount : type === "deposit" ? newSum . sub ( maxC . div ( 2 ) ) : minimum ( maxC . div ( 2 ) . add ( amount ) . sub ( BN . from ( integratorBalBefore ) ) , BN . from ( bAsset . vaultBalance ) . sub ( BN . from ( integratorBalBefore ) ) ) , rawBalance : type === "deposit" ? expectInteraction ? maxC . div ( 2 ) : newSum : expectInteraction ? minimum ( maxC . div ( 2 ) , BN . from ( bAsset . vaultBalance ) . sub ( amount ) ) : BN . from ( integratorBalBefore ) . sub ( amount ) , } ; } }