import { BigNumber , Signer } from "ethers" ; import { MAX_UINT256 , TIME , asyncForEach , getCurrentBlockTimestamp , getPoolBalances , getUserTokenBalance , getUserTokenBalances , setTimestamp , } from "./testUtils" ; import { solidity } from "ethereum-waffle" ; import { deployments } from "hardhat" ; import { GenericERC20 } from "../build/typechain/GenericERC20" ; import { LPToken } from "../build/typechain/LPToken" ; import { Swap } from "../build/typechain/Swap" ; import { SwapUtils } from "../build/typechain/SwapUtils" ; import chai from "chai" ; chai . use ( solidity ) ; const { expect } = chai ; describe ( "Swap with 4 tokens" , ( ) => { let signers ; let swap ; let swapUtils ; let DAI ; let USDC ; let USDT ; let SUSD ; let swapToken ; let owner ; let user1 ; let user2 ; let attacker ; let ownerAddress ; let user1Address ; let user2Address ; let swapStorage ; const INITIAL_A_VALUE = 50 ; const SWAP_FEE = 1e7 ; const LP_TOKEN_NAME = "Test LP Token Name" ; const LP_TOKEN_SYMBOL = "TESTLP" ; const TOKENS = [ ] ; const setupTest = deployments . createFixture ( async ( { deployments , ethers } ) => { const { get , deploy } = deployments ; await deployments . fixture ( ) ; TOKENS . length = 0 ; signers = await ethers . getSigners ( ) ; owner = signers [ 0 ] ; user1 = signers [ 1 ] ; user2 = signers [ 2 ] ; attacker = signers [ 10 ] ; ownerAddress = await owner . getAddress ( ) ; user1Address = await user1 . getAddress ( ) ; user2Address = await user2 . getAddress ( ) ; await deploy ( "SUSD" , { from : ownerAddress , contract : "GenericERC20" , args : [ "SUSD" , "Synthetix USD" , "18" ] , skipIfAlreadyDeployed : true , } ) ; DAI = await ethers . getContract ( "DAI" ) ; USDC = await ethers . getContract ( "USDC" ) ; USDT = await ethers . getContract ( "USDT" ) ; SUSD = await ethers . getContract ( "SUSD" ) ; TOKENS . push ( DAI , USDC , USDT , SUSD ) ; await asyncForEach ( [ ownerAddress , user1Address , user2Address , await attacker . getAddress ( ) ] , async ( address ) => { await DAI . mint ( address , String ( 1e20 ) ) ; await USDC . mint ( address , String ( 1e8 ) ) ; await USDT . mint ( address , String ( 1e8 ) ) ; await SUSD . mint ( address , String ( 1e20 ) ) ; } ) ; swap = await ethers . getContract ( "Swap" ) ; await swap . initialize ( [ DAI . address , USDC . address , USDT . address , SUSD . address ] , [ 18 , 6 , 6 , 18 ] , LP_TOKEN_NAME , LP_TOKEN_SYMBOL , INITIAL_A_VALUE , SWAP_FEE , 0 , ( await get ( "LPToken" ) ) . address ) ; expect ( await swap . getVirtualPrice ( ) ) . to . be . eq ( 0 ) ; swapStorage = await swap . swapStorage ( ) ; swapToken = ( await ethers . getContractAt ( "LPToken" , swapStorage . lpToken ) ) as LPToken ; await asyncForEach ( [ owner , user1 , user2 , attacker ] , async ( signer ) => { await DAI . connect ( signer ) . approve ( swap . address , MAX_UINT256 ) ; await USDC . connect ( signer ) . approve ( swap . address , MAX_UINT256 ) ; await USDT . connect ( signer ) . approve ( swap . address , MAX_UINT256 ) ; await SUSD . connect ( signer ) . approve ( swap . address , MAX_UINT256 ) ; } ) ; await swap . addLiquidity ( [ String ( 50e18 ) , String ( 50e6 ) , String ( 50e6 ) , String ( 50e18 ) ] , 0 , MAX_UINT256 ) ; expect ( await swap . getTokenBalance ( 0 ) ) . to . be . eq ( String ( 50e18 ) ) ; expect ( await swap . getTokenBalance ( 1 ) ) . to . be . eq ( String ( 50e6 ) ) ; expect ( await swap . getTokenBalance ( 2 ) ) . to . be . eq ( String ( 50e6 ) ) ; expect ( await swap . getTokenBalance ( 3 ) ) . to . be . eq ( String ( 50e18 ) ) ; expect ( await getUserTokenBalance ( owner , swapToken ) ) . to . be . eq ( String ( 200e18 ) ) ; } ) ; beforeEach ( async ( ) => { await setupTest ( ) ; } ) ; describe ( "addLiquidity" , ( ) => { it ( "Add liquidity succeeds with pool with 4 tokens" , async ( ) => { const calcTokenAmount = await swap . calculateTokenAmount ( [ String ( 1e18 ) , 0 , 0 , 0 ] , true ) ; expect ( calcTokenAmount ) . to . be . eq ( "999854620735777893" ) ; await swap . connect ( user1 ) . addLiquidity ( [ String ( 1e18 ) , 0 , 0 , 0 ] , calcTokenAmount . mul ( 99 ) . div ( 100 ) , ( await getCurrentBlockTimestamp ( ) ) + 60 ) ; expect ( await swapToken . balanceOf ( await user1 . getAddress ( ) ) ) . to . be . eq ( "999355335447632820" ) ; } ) ; } ) ; describe ( "swap" , ( ) => { it ( "Swap works between tokens with different decimals" , async ( ) => { const calcTokenAmount = await swap . connect ( user1 ) . calculateSwap ( 2 , 0 , String ( 1e6 ) ) ; expect ( calcTokenAmount ) . to . be . eq ( "998608238366733809" ) ; const DAIBefore = await getUserTokenBalance ( user1 , DAI ) ; await USDT . connect ( user1 ) . approve ( swap . address , String ( 1e6 ) ) ; await swap . connect ( user1 ) . swap ( 2 , 0 , String ( 1e6 ) , calcTokenAmount , ( await getCurrentBlockTimestamp ( ) ) + 60 ) ; const DAIAfter = await getUserTokenBalance ( user1 , DAI ) ; expect ( DAIAfter . sub ( DAIBefore ) ) . to . be . eq ( "998608238366733809" ) ; expect ( await swap . getTokenBalance ( 0 ) ) . to . be . eq ( "49001391761633266191" ) ; } ) ; } ) ; describe ( "removeLiquidity" , ( ) => { it ( "Remove Liquidity succeeds" , async ( ) => { const calcTokenAmount = await swap . calculateTokenAmount ( [ String ( 1e18 ) , 0 , 0 , 0 ] , true ) ; expect ( calcTokenAmount ) . to . be . eq ( "999854620735777893" ) ; await swap . connect ( user1 ) . addLiquidity ( [ String ( 1e18 ) , 0 , 0 , 0 ] , calcTokenAmount . mul ( 99 ) . div ( 100 ) , ( await getCurrentBlockTimestamp ( ) ) + 60 ) ; expect ( await swapToken . balanceOf ( await user1 . getAddress ( ) ) ) . to . be . eq ( "999355335447632820" ) ; const expectedAmounts = await swap . calculateRemoveLiquidity ( "999355335447632820" ) ; expect ( expectedAmounts [ 0 ] ) . to . be . eq ( "253568584947798923" ) ; expect ( expectedAmounts [ 1 ] ) . to . be . eq ( "248596" ) ; expect ( expectedAmounts [ 2 ] ) . to . be . eq ( "248596" ) ; expect ( expectedAmounts [ 3 ] ) . to . be . eq ( "248596651909606787" ) ; await swapToken . connect ( user1 ) . approve ( swap . address , "999355335447632820" ) ; const beforeTokenBalances = await getUserTokenBalances ( user1 , TOKENS ) ; await swap . connect ( user1 ) . removeLiquidity ( "999355335447632820" , expectedAmounts , ( await getCurrentBlockTimestamp ( ) ) + 60 ) ; const afterTokenBalances = await getUserTokenBalances ( user1 , TOKENS ) ; expect ( afterTokenBalances [ 0 ] . sub ( beforeTokenBalances [ 0 ] ) ) . to . be . eq ( "253568584947798923" ) ; expect ( afterTokenBalances [ 1 ] . sub ( beforeTokenBalances [ 1 ] ) ) . to . be . eq ( "248596" ) ; expect ( afterTokenBalances [ 2 ] . sub ( beforeTokenBalances [ 2 ] ) ) . to . be . eq ( "248596" ) ; expect ( afterTokenBalances [ 3 ] . sub ( beforeTokenBalances [ 3 ] ) ) . to . be . eq ( "248596651909606787" ) ; } ) ; } ) ; describe ( "withdrawAdminFees" , ( ) => { it ( "Reverts when called by non-owners" , async ( ) => { await expect ( swap . connect ( user1 ) . withdrawAdminFees ( ) ) . to . be . reverted ; await expect ( swap . connect ( user2 ) . withdrawAdminFees ( ) ) . to . be . reverted ; } ) ; it ( "Succeeds when there are no fees withdrawn" , async ( ) => { await swap . setAdminFee ( BigNumber . from ( 10 ** 8 ) ) ; const balancesBefore = await getUserTokenBalances ( owner , [ DAI , USDC , USDT , SUSD , ] ) ; await swap . withdrawAdminFees ( ) ; const balancesAfter = await getUserTokenBalances ( owner , [ DAI , USDC , USDT , SUSD , ] ) ; expect ( balancesBefore ) . to . eql ( balancesAfter ) ; } ) ; it ( "Succeeds with expected amount of fees withdrawn" , async ( ) => { await swap . setAdminFee ( BigNumber . from ( 10 ** 8 ) ) ; await swap . connect ( user1 ) . swap ( 0 , 1 , String ( 1e18 ) , 0 , MAX_UINT256 ) ; await swap . connect ( user1 ) . swap ( 1 , 0 , String ( 1e6 ) , 0 , MAX_UINT256 ) ; expect ( await swap . getAdminBalance ( 0 ) ) . to . eq ( String ( 10003917589952 ) ) ; expect ( await swap . getAdminBalance ( 1 ) ) . to . eq ( String ( 9 ) ) ; const balancesBefore = await getUserTokenBalances ( owner , [ DAI , USDC , USDT , SUSD , ] ) ; await swap . withdrawAdminFees ( ) ; const balancesAfter = await getUserTokenBalances ( owner , [ DAI , USDC , USDT , SUSD , ] ) ; expect ( balancesAfter [ 0 ] . sub ( balancesBefore [ 0 ] ) ) . to . eq ( String ( 10003917589952 ) ) ; expect ( balancesAfter [ 1 ] . sub ( balancesBefore [ 1 ] ) ) . to . eq ( String ( 9 ) ) ; } ) ; it ( "Withdrawing admin fees has no impact on users' withdrawal" , async ( ) => { await swap . setAdminFee ( BigNumber . from ( 10 ** 8 ) ) ; await swap . connect ( user1 ) . addLiquidity ( [ String ( 1e18 ) , String ( 1e6 ) , String ( 1e6 ) , String ( 1e18 ) ] , 0 , MAX_UINT256 ) ; for ( let i = 0 ; i < 10 ; i ++ ) { await swap . connect ( user2 ) . swap ( 0 , 1 , String ( 1e18 ) , 0 , MAX_UINT256 ) ; await swap . connect ( user2 ) . swap ( 1 , 0 , String ( 1e6 ) , 0 , MAX_UINT256 ) ; } expect ( await swap . getAdminBalance ( 0 ) ) . to . eq ( String ( 100038269603084 ) ) ; expect ( await swap . getAdminBalance ( 1 ) ) . to . eq ( String ( 90 ) ) ; await swap . withdrawAdminFees ( ) ; const balancesBefore = await getUserTokenBalances ( user1 , [ DAI , USDC , USDT , SUSD , ] ) ; const user1LPTokenBalance = await swapToken . balanceOf ( user1Address ) ; await swapToken . connect ( user1 ) . approve ( swap . address , user1LPTokenBalance ) ; await swap . connect ( user1 ) . removeLiquidity ( user1LPTokenBalance , [ 0 , 0 , 0 , 0 ] , MAX_UINT256 ) ; const balancesAfter = await getUserTokenBalances ( user1 , [ DAI , USDC , USDT , SUSD , ] ) ; expect ( balancesAfter [ 0 ] . sub ( balancesBefore [ 0 ] ) ) . to . eq ( BigNumber . from ( "1000119153497686425" ) ) ; expect ( balancesAfter [ 1 ] . sub ( balancesBefore [ 1 ] ) ) . to . eq ( BigNumber . from ( "1000269" ) ) ; } ) ; } ) ; describe ( "Check for timestamp manipulations" , ( ) => { it ( "Check for maximum differences in A and virtual price when increasing" , async ( ) => { await swap . connect ( user1 ) . addLiquidity ( [ String ( 1e20 ) , 0 , 0 , 0 ] , 0 , MAX_UINT256 ) ; await swap . rampA ( 100 , ( await getCurrentBlockTimestamp ( ) ) + 14 * TIME . DAYS + 1 ) ; expect ( await swap . getA ( ) ) . to . be . eq ( 50 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 5000 ) ; expect ( await swap . getVirtualPrice ( ) ) . to . be . eq ( "1000166120891616093" ) ; await setTimestamp ( ( await getCurrentBlockTimestamp ( ) ) + 900 ) ; expect ( await swap . getA ( ) ) . to . be . eq ( 50 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 5003 ) ; expect ( await swap . getVirtualPrice ( ) ) . to . be . eq ( "1000168045277768276" ) ; } ) ; it ( "Check for maximum differences in A and virtual price when decreasing" , async ( ) => { await swap . connect ( user1 ) . addLiquidity ( [ String ( 1e20 ) , 0 , 0 , 0 ] , 0 , MAX_UINT256 ) ; await swap . rampA ( 25 , ( await getCurrentBlockTimestamp ( ) ) + 14 * TIME . DAYS + 1 ) ; expect ( await swap . getA ( ) ) . to . be . eq ( 50 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 5000 ) ; expect ( await swap . getVirtualPrice ( ) ) . to . be . eq ( "1000166120891616093" ) ; await setTimestamp ( ( await getCurrentBlockTimestamp ( ) ) + 900 ) ; expect ( await swap . getA ( ) ) . to . be . eq ( 49 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 4999 ) ; expect ( await swap . getVirtualPrice ( ) ) . to . be . eq ( "1000165478934301535" ) ; } ) ; describe ( "Check for attacks while A is ramping upwards" , ( ) => { let initialAttackerBalances = [ ] ; let initialPoolBalances = [ ] ; beforeEach ( async ( ) => { initialAttackerBalances = await getUserTokenBalances ( attacker , TOKENS ) ; expect ( initialAttackerBalances [ 0 ] ) . to . be . eq ( String ( 1e20 ) ) ; expect ( initialAttackerBalances [ 1 ] ) . to . be . eq ( String ( 1e8 ) ) ; expect ( initialAttackerBalances [ 2 ] ) . to . be . eq ( String ( 1e8 ) ) ; expect ( initialAttackerBalances [ 3 ] ) . to . be . eq ( String ( 1e20 ) ) ; await swap . rampA ( 100 , ( await getCurrentBlockTimestamp ( ) ) + 14 * TIME . DAYS + 10 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 5000 ) ; initialPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( initialPoolBalances [ 0 ] ) . to . be . eq ( String ( 50e18 ) ) ; expect ( initialPoolBalances [ 1 ] ) . to . be . eq ( String ( 50e6 ) ) ; expect ( initialPoolBalances [ 2 ] ) . to . be . eq ( String ( 50e6 ) ) ; expect ( initialPoolBalances [ 3 ] ) . to . be . eq ( String ( 50e18 ) ) ; } ) ; describe ( "When tokens are priced equally: " + "attacker creates massive imbalance prior to A change, and resolves it after" , ( ) => { it ( "Attack fails with 900 seconds between blocks" , async ( ) => { await swap . connect ( attacker ) . swap ( 1 , 3 , String ( 16e6 ) , 0 , MAX_UINT256 ) ; const SUSDOutput = ( await getUserTokenBalance ( attacker , SUSD ) ) . sub ( initialAttackerBalances [ 3 ] ) ; expect ( SUSDOutput ) . to . be . eq ( "15873636661935380627" ) ; expect ( await swap . getTokenBalance ( 1 ) ) . to . be . eq ( String ( 66e6 ) ) ; expect ( await swap . getTokenBalance ( 3 ) ) . to . be . eq ( "34126363338064619373" ) ; await setTimestamp ( ( await getCurrentBlockTimestamp ( ) ) + 900 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 5003 ) ; const balanceBefore = await getUserTokenBalance ( attacker , USDC ) ; await swap . connect ( attacker ) . swap ( 3 , 1 , SUSDOutput , 0 , MAX_UINT256 ) ; const USDCOutput = ( await getUserTokenBalance ( attacker , USDC ) ) . sub ( balanceBefore ) ; expect ( USDCOutput ) . to . be . eq ( "15967909" ) ; const finalAttackerBalances = await getUserTokenBalances ( attacker , TOKENS ) ; expect ( finalAttackerBalances [ 1 ] ) . to . be . lt ( initialAttackerBalances [ 1 ] ) ; expect ( finalAttackerBalances [ 3 ] ) . to . be . eq ( initialAttackerBalances [ 3 ] ) ; expect ( initialAttackerBalances [ 1 ] . sub ( finalAttackerBalances [ 1 ] ) ) . to . be . eq ( "32091" ) ; expect ( initialAttackerBalances [ 3 ] . sub ( finalAttackerBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; const finalPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( finalPoolBalances [ 1 ] ) . to . be . gt ( initialPoolBalances [ 1 ] ) ; expect ( finalPoolBalances [ 3 ] ) . to . be . eq ( initialPoolBalances [ 3 ] ) ; expect ( finalPoolBalances [ 1 ] . sub ( initialPoolBalances [ 1 ] ) ) . to . be . eq ( "32091" ) ; expect ( finalPoolBalances [ 3 ] . sub ( initialPoolBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; } ) ; it ( "Attack fails with 2 weeks between transactions (mimics rapid A change)" , async ( ) => { await swap . connect ( attacker ) . swap ( 1 , 3 , String ( 16e6 ) , 0 , MAX_UINT256 ) ; const SUSDOutput = ( await getUserTokenBalance ( attacker , SUSD ) ) . sub ( initialAttackerBalances [ 3 ] ) ; expect ( SUSDOutput ) . to . be . eq ( "15873636661935380627" ) ; expect ( await swap . getTokenBalance ( 1 ) ) . to . be . eq ( String ( 66e6 ) ) ; expect ( await swap . getTokenBalance ( 3 ) ) . to . be . eq ( "34126363338064619373" ) ; await setTimestamp ( ( await getCurrentBlockTimestamp ( ) ) + 2 * TIME . WEEKS + 10 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 10000 ) ; const balanceBefore = await getUserTokenBalance ( attacker , USDC ) ; await swap . connect ( attacker ) . swap ( 3 , 1 , SUSDOutput , 0 , MAX_UINT256 ) ; const USDCOutput = ( await getUserTokenBalance ( attacker , USDC ) ) . sub ( balanceBefore ) ; expect ( USDCOutput ) . to . be . eq ( "15913488" ) ; const finalAttackerBalances = await getUserTokenBalances ( attacker , TOKENS ) ; expect ( finalAttackerBalances [ 1 ] ) . to . be . lt ( initialAttackerBalances [ 1 ] ) ; expect ( finalAttackerBalances [ 3 ] ) . to . be . eq ( initialAttackerBalances [ 3 ] ) ; expect ( initialAttackerBalances [ 1 ] . sub ( finalAttackerBalances [ 1 ] ) ) . to . be . eq ( "86512" ) ; expect ( initialAttackerBalances [ 3 ] . sub ( finalAttackerBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; const finalPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( finalPoolBalances [ 1 ] ) . to . be . gt ( initialPoolBalances [ 1 ] ) ; expect ( finalPoolBalances [ 3 ] ) . to . be . eq ( initialPoolBalances [ 3 ] ) ; expect ( finalPoolBalances [ 1 ] . sub ( initialPoolBalances [ 1 ] ) ) . to . be . eq ( "86512" ) ; expect ( finalPoolBalances [ 3 ] . sub ( initialPoolBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; } ) ; } ) ; describe ( "When token price is unequal: " + "attacker 'resolves' the imbalance prior to A change, then recreates the imbalance." , ( ) => { beforeEach ( async ( ) => { await swap . connect ( user2 ) . addLiquidity ( [ 0 , 0 , 0 , String ( 50e18 ) ] , 0 , ( await getCurrentBlockTimestamp ( ) ) + 60 ) ; initialPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( initialPoolBalances [ 0 ] ) . to . be . eq ( String ( 50e18 ) ) ; expect ( initialPoolBalances [ 1 ] ) . to . be . eq ( String ( 50e6 ) ) ; expect ( initialPoolBalances [ 2 ] ) . to . be . eq ( String ( 50e6 ) ) ; expect ( initialPoolBalances [ 3 ] ) . to . be . eq ( String ( 100e18 ) ) ; } ) ; it ( "Attack fails with 900 seconds between blocks" , async ( ) => { await swap . connect ( attacker ) . swap ( 1 , 3 , String ( 25e6 ) , 0 , MAX_UINT256 ) ; const SUSDOutput = ( await getUserTokenBalance ( attacker , SUSD ) ) . sub ( initialAttackerBalances [ 3 ] ) ; expect ( SUSDOutput ) . to . be . eq ( "25140480043410581418" ) ; expect ( await swap . getTokenBalance ( 1 ) ) . to . be . eq ( String ( 75e6 ) ) ; expect ( await swap . getTokenBalance ( 3 ) ) . to . be . eq ( "74859519956589418582" ) ; await setTimestamp ( ( await getCurrentBlockTimestamp ( ) ) + 900 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 5003 ) ; const balanceBefore = await getUserTokenBalance ( attacker , USDC ) ; await swap . connect ( attacker ) . swap ( 3 , 1 , SUSDOutput , 0 , MAX_UINT256 ) ; const USDCOutput = ( await getUserTokenBalance ( attacker , USDC ) ) . sub ( balanceBefore ) ; expect ( USDCOutput ) . to . be . eq ( "24950174" ) ; const finalAttackerBalances = await getUserTokenBalances ( attacker , TOKENS ) ; expect ( finalAttackerBalances [ 1 ] ) . to . be . lt ( initialAttackerBalances [ 1 ] ) ; expect ( finalAttackerBalances [ 3 ] ) . to . be . eq ( initialAttackerBalances [ 3 ] ) ; expect ( initialAttackerBalances [ 1 ] . sub ( finalAttackerBalances [ 1 ] ) ) . to . be . eq ( "49826" ) ; expect ( initialAttackerBalances [ 3 ] . sub ( finalAttackerBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; const finalPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( finalPoolBalances [ 1 ] ) . to . be . gt ( initialPoolBalances [ 1 ] ) ; expect ( finalPoolBalances [ 3 ] ) . to . be . eq ( initialPoolBalances [ 3 ] ) ; expect ( finalPoolBalances [ 1 ] . sub ( initialPoolBalances [ 1 ] ) ) . to . be . eq ( "49826" ) ; expect ( finalPoolBalances [ 3 ] . sub ( initialPoolBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; } ) ; it ( "Attack succeeds with 2 weeks between transactions (mimics rapid A change)" , async ( ) => { await swap . connect ( attacker ) . swap ( 1 , 3 , String ( 25e6 ) , 0 , MAX_UINT256 ) ; const SUSDOutput = ( await getUserTokenBalance ( attacker , SUSD ) ) . sub ( initialAttackerBalances [ 3 ] ) ; expect ( SUSDOutput ) . to . be . eq ( "25140480043410581418" ) ; expect ( await swap . getTokenBalance ( 1 ) ) . to . be . eq ( String ( 75e6 ) ) ; expect ( await swap . getTokenBalance ( 3 ) ) . to . be . eq ( "74859519956589418582" ) ; await setTimestamp ( ( await getCurrentBlockTimestamp ( ) ) + 2 * TIME . WEEKS + 10 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 10000 ) ; const balanceBefore = await getUserTokenBalance ( attacker , USDC ) ; await swap . connect ( attacker ) . swap ( 3 , 1 , SUSDOutput , 0 , MAX_UINT256 ) ; const USDCOutput = ( await getUserTokenBalance ( attacker , USDC ) ) . sub ( balanceBefore ) ; expect ( USDCOutput ) . to . be . eq ( "25031387" ) ; const finalAttackerBalances = await getUserTokenBalances ( attacker , TOKENS ) ; expect ( initialAttackerBalances [ 1 ] ) . to . be . lt ( finalAttackerBalances [ 1 ] ) ; expect ( initialAttackerBalances [ 3 ] ) . to . be . eq ( finalAttackerBalances [ 3 ] ) ; expect ( finalAttackerBalances [ 1 ] . sub ( initialAttackerBalances [ 1 ] ) ) . to . be . eq ( "31387" ) ; expect ( finalAttackerBalances [ 3 ] . sub ( initialAttackerBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; const finalPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( finalPoolBalances [ 1 ] ) . to . be . lt ( initialPoolBalances [ 1 ] ) ; expect ( finalPoolBalances [ 3 ] ) . to . be . eq ( initialPoolBalances [ 3 ] ) ; expect ( initialPoolBalances [ 1 ] . sub ( finalPoolBalances [ 1 ] ) ) . to . be . eq ( "31387" ) ; expect ( initialPoolBalances [ 3 ] . sub ( finalPoolBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; } ) ; } ) ; } ) ; describe ( "Check for attacks while A is ramping downwards" , ( ) => { let initialAttackerBalances = [ ] ; let initialPoolBalances = [ ] ; beforeEach ( async ( ) => { initialAttackerBalances = await getUserTokenBalances ( attacker , TOKENS ) ; expect ( initialAttackerBalances [ 0 ] ) . to . be . eq ( String ( 1e20 ) ) ; expect ( initialAttackerBalances [ 1 ] ) . to . be . eq ( String ( 1e8 ) ) ; expect ( initialAttackerBalances [ 2 ] ) . to . be . eq ( String ( 1e8 ) ) ; expect ( initialAttackerBalances [ 3 ] ) . to . be . eq ( String ( 1e20 ) ) ; await swap . rampA ( 25 , ( await getCurrentBlockTimestamp ( ) ) + 14 * TIME . DAYS + 10 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 5000 ) ; initialPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( initialPoolBalances [ 0 ] ) . to . be . eq ( String ( 50e18 ) ) ; expect ( initialPoolBalances [ 1 ] ) . to . be . eq ( String ( 50e6 ) ) ; expect ( initialPoolBalances [ 2 ] ) . to . be . eq ( String ( 50e6 ) ) ; expect ( initialPoolBalances [ 3 ] ) . to . be . eq ( String ( 50e18 ) ) ; } ) ; describe ( "When tokens are priced equally: " + "attacker creates massive imbalance prior to A change, and resolves it after" , ( ) => { it ( "Attack fails with 900 seconds between blocks" , async ( ) => { await swap . connect ( attacker ) . swap ( 1 , 3 , String ( 16e6 ) , 0 , MAX_UINT256 ) ; const SUSDOutput = ( await getUserTokenBalance ( attacker , SUSD ) ) . sub ( initialAttackerBalances [ 3 ] ) ; expect ( SUSDOutput ) . to . be . eq ( "15873636661935380627" ) ; expect ( await swap . getTokenBalance ( 1 ) ) . to . be . eq ( String ( 66e6 ) ) ; expect ( await swap . getTokenBalance ( 3 ) ) . to . be . eq ( "34126363338064619373" ) ; await setTimestamp ( ( await getCurrentBlockTimestamp ( ) ) + 900 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 4999 ) ; const balanceBefore = await getUserTokenBalance ( attacker , USDC ) ; await swap . connect ( attacker ) . swap ( 3 , 1 , SUSDOutput , 0 , MAX_UINT256 ) ; const USDCOutput = ( await getUserTokenBalance ( attacker , USDC ) ) . sub ( balanceBefore ) ; expect ( USDCOutput ) . to . be . eq ( "15967995" ) ; const finalAttackerBalances = await getUserTokenBalances ( attacker , TOKENS ) ; expect ( finalAttackerBalances [ 1 ] ) . to . be . lt ( initialAttackerBalances [ 1 ] ) ; expect ( finalAttackerBalances [ 3 ] ) . to . be . eq ( initialAttackerBalances [ 3 ] ) ; expect ( initialAttackerBalances [ 1 ] . sub ( finalAttackerBalances [ 1 ] ) ) . to . be . eq ( "32005" ) ; expect ( initialAttackerBalances [ 3 ] . sub ( finalAttackerBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; const finalPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( finalPoolBalances [ 1 ] ) . to . be . gt ( initialPoolBalances [ 1 ] ) ; expect ( finalPoolBalances [ 3 ] ) . to . be . eq ( initialPoolBalances [ 3 ] ) ; expect ( finalPoolBalances [ 1 ] . sub ( initialPoolBalances [ 1 ] ) ) . to . be . eq ( "32005" ) ; expect ( finalPoolBalances [ 3 ] . sub ( initialPoolBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; } ) ; it ( "Attack succeeds with 2 weeks between transactions (mimics rapid A change)" , async ( ) => { await swap . connect ( attacker ) . swap ( 1 , 3 , String ( 16e6 ) , 0 , MAX_UINT256 ) ; const SUSDOutput = ( await getUserTokenBalance ( attacker , SUSD ) ) . sub ( initialAttackerBalances [ 3 ] ) ; expect ( SUSDOutput ) . to . be . eq ( "15873636661935380627" ) ; expect ( await swap . getTokenBalance ( 1 ) ) . to . be . eq ( String ( 66e6 ) ) ; expect ( await swap . getTokenBalance ( 3 ) ) . to . be . eq ( "34126363338064619373" ) ; await setTimestamp ( ( await getCurrentBlockTimestamp ( ) ) + 2 * TIME . WEEKS + 10 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 2500 ) ; const balanceBefore = await getUserTokenBalance ( attacker , USDC ) ; await swap . connect ( attacker ) . swap ( 3 , 1 , SUSDOutput , 0 , MAX_UINT256 ) ; const USDCOutput = ( await getUserTokenBalance ( attacker , USDC ) ) . sub ( balanceBefore ) ; expect ( USDCOutput ) . to . be . eq ( "16073391" ) ; const finalAttackerBalances = await getUserTokenBalances ( attacker , TOKENS ) ; expect ( finalAttackerBalances [ 1 ] ) . to . be . gt ( initialAttackerBalances [ 1 ] ) ; expect ( finalAttackerBalances [ 3 ] ) . to . be . eq ( initialAttackerBalances [ 3 ] ) ; expect ( finalAttackerBalances [ 1 ] . sub ( initialAttackerBalances [ 1 ] ) ) . to . be . eq ( "73391" ) ; expect ( finalAttackerBalances [ 3 ] . sub ( initialAttackerBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; const finalPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( finalPoolBalances [ 1 ] ) . to . be . lt ( initialPoolBalances [ 1 ] ) ; expect ( finalPoolBalances [ 3 ] ) . to . be . eq ( initialPoolBalances [ 3 ] ) ; expect ( initialPoolBalances [ 1 ] . sub ( finalPoolBalances [ 1 ] ) ) . to . be . eq ( "73391" ) ; expect ( initialPoolBalances [ 3 ] . sub ( finalPoolBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; } ) ; } ) ; describe ( "When token price is unequal: " + "attacker 'resolves' the imbalance prior to A change, then recreates the imbalance." , ( ) => { beforeEach ( async ( ) => { await swap . connect ( user2 ) . addLiquidity ( [ 0 , 0 , 0 , String ( 50e18 ) ] , 0 , ( await getCurrentBlockTimestamp ( ) ) + 60 ) ; initialPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( initialPoolBalances [ 0 ] ) . to . be . eq ( String ( 50e18 ) ) ; expect ( initialPoolBalances [ 1 ] ) . to . be . eq ( String ( 50e6 ) ) ; expect ( initialPoolBalances [ 2 ] ) . to . be . eq ( String ( 50e6 ) ) ; expect ( initialPoolBalances [ 3 ] ) . to . be . eq ( String ( 100e18 ) ) ; } ) ; it ( "Attack fails with 900 seconds between blocks" , async ( ) => { await swap . connect ( attacker ) . swap ( 1 , 3 , String ( 25e6 ) , 0 , MAX_UINT256 ) ; const SUSDOutput = ( await getUserTokenBalance ( attacker , SUSD ) ) . sub ( initialAttackerBalances [ 3 ] ) ; expect ( SUSDOutput ) . to . be . eq ( "25140480043410581418" ) ; expect ( await swap . getTokenBalance ( 1 ) ) . to . be . eq ( String ( 75e6 ) ) ; expect ( await swap . getTokenBalance ( 3 ) ) . to . be . eq ( "74859519956589418582" ) ; await setTimestamp ( ( await getCurrentBlockTimestamp ( ) ) + 900 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 4999 ) ; const balanceBefore = await getUserTokenBalance ( attacker , USDC ) ; await swap . connect ( attacker ) . swap ( 3 , 1 , SUSDOutput , 0 , MAX_UINT256 ) ; const USDCOutput = ( await getUserTokenBalance ( attacker , USDC ) ) . sub ( balanceBefore ) ; expect ( USDCOutput ) . to . be . eq ( "24950046" ) ; const finalAttackerBalances = await getUserTokenBalances ( attacker , TOKENS ) ; expect ( finalAttackerBalances [ 1 ] ) . to . be . lt ( initialAttackerBalances [ 1 ] ) ; expect ( finalAttackerBalances [ 3 ] ) . to . be . eq ( initialAttackerBalances [ 3 ] ) ; expect ( initialAttackerBalances [ 1 ] . sub ( finalAttackerBalances [ 1 ] ) ) . to . be . eq ( "49954" ) ; expect ( initialAttackerBalances [ 3 ] . sub ( finalAttackerBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; const finalPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( finalPoolBalances [ 1 ] ) . to . be . gt ( initialPoolBalances [ 1 ] ) ; expect ( finalPoolBalances [ 3 ] ) . to . be . eq ( initialPoolBalances [ 3 ] ) ; expect ( finalPoolBalances [ 1 ] . sub ( initialPoolBalances [ 1 ] ) ) . to . be . eq ( "49954" ) ; expect ( finalPoolBalances [ 3 ] . sub ( initialPoolBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; } ) ; it ( "Attack fails with 2 weeks between transactions (mimics rapid A change)" , async ( ) => { await swap . connect ( attacker ) . swap ( 1 , 3 , String ( 25e6 ) , 0 , MAX_UINT256 ) ; const SUSDOutput = ( await getUserTokenBalance ( attacker , SUSD ) ) . sub ( initialAttackerBalances [ 3 ] ) ; expect ( SUSDOutput ) . to . be . eq ( "25140480043410581418" ) ; expect ( await swap . getTokenBalance ( 1 ) ) . to . be . eq ( String ( 75e6 ) ) ; expect ( await swap . getTokenBalance ( 3 ) ) . to . be . eq ( "74859519956589418582" ) ; await setTimestamp ( ( await getCurrentBlockTimestamp ( ) ) + 2 * TIME . WEEKS + 10 ) ; expect ( await swap . getAPrecise ( ) ) . to . be . eq ( 2500 ) ; const balanceBefore = await getUserTokenBalance ( attacker , USDC ) ; await swap . connect ( attacker ) . swap ( 3 , 1 , SUSDOutput , 0 , MAX_UINT256 ) ; const USDCOutput = ( await getUserTokenBalance ( attacker , USDC ) ) . sub ( balanceBefore ) ; expect ( USDCOutput ) . to . be . eq ( "24794844" ) ; const finalAttackerBalances = await getUserTokenBalances ( attacker , TOKENS ) ; expect ( finalAttackerBalances [ 1 ] ) . to . be . lt ( initialAttackerBalances [ 1 ] ) ; expect ( finalAttackerBalances [ 3 ] ) . to . be . eq ( initialAttackerBalances [ 3 ] ) ; expect ( initialAttackerBalances [ 1 ] . sub ( finalAttackerBalances [ 1 ] ) ) . to . be . eq ( "205156" ) ; expect ( initialAttackerBalances [ 3 ] . sub ( finalAttackerBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; const finalPoolBalances = await getPoolBalances ( swap , 4 ) ; expect ( finalPoolBalances [ 1 ] ) . to . be . gt ( initialPoolBalances [ 1 ] ) ; expect ( finalPoolBalances [ 3 ] ) . to . be . eq ( initialPoolBalances [ 3 ] ) ; expect ( finalPoolBalances [ 1 ] . sub ( initialPoolBalances [ 1 ] ) ) . to . be . eq ( "205156" ) ; expect ( finalPoolBalances [ 3 ] . sub ( initialPoolBalances [ 3 ] ) ) . to . be . eq ( "0" ) ; } ) ; } ) ; } ) ; } ) ; } ) ;