UNPKG

@marketprotocol/marketprotocol

Version:

Decentralized Derivatives Trading Protocol For The Ethereum Blockchain

1,154 lines (1,002 loc) 43.1 kB
const BN = require('bn.js'); const CollateralToken = artifacts.require('CollateralToken'); const MarketCollateralPool = artifacts.require('MarketCollateralPool'); const MarketContractRegistry = artifacts.require('MarketContractRegistry'); const MarketToken = artifacts.require('MarketToken'); const PositionToken = artifacts.require('PositionToken'); const truffleAssert = require('truffle-assertions'); const utility = require('./utility.js'); // basic tests to ensure MarketCollateralPool works and is set up to allow trading contract('MarketCollateralPool', function(accounts) { let collateralToken; let collateralPool; let marketContract; let feeMarketContract; let marketContractRegistry; let mktToken; let qtyMultiplier; let priceFloor; let priceCap; let longPositionToken; let shortPositionToken; let snapshotId; let initialCollateralBalance; let initialMktBalance; let mktFeePerUnit; let collateralFeePerUnit; let collateralFee = 1000; let mktFee = 500; const MarketSides = { Long: 0, Short: 1, Both: 2 }; before(async function() { marketContractRegistry = await MarketContractRegistry.deployed(); }); beforeEach(async function() { snapshotId = await utility.createEVMSnapshot(); collateralPool = await MarketCollateralPool.deployed(); collateralToken = await CollateralToken.deployed(); mktToken = await MarketToken.deployed(); // .at(await collateralPool.mktToken()); marketContract = await utility.createMarketContract( collateralToken, collateralPool, accounts[0], accounts[0], [0, 150, 2, 1, 0, 0, utility.expirationInDays(1)] ); feeMarketContract = await utility.createMarketContract( collateralToken, collateralPool, accounts[0], accounts[0], [0, 150, 2, 2, collateralFee, mktFee, utility.expirationInDays(1)] ); await marketContractRegistry.addAddressToWhiteList(marketContract.address, { from: accounts[0] }); await marketContractRegistry.addAddressToWhiteList(feeMarketContract.address, { from: accounts[0] }); qtyMultiplier = await marketContract.QTY_MULTIPLIER.call(); priceFloor = await marketContract.PRICE_FLOOR.call(); priceCap = await marketContract.PRICE_CAP.call(); longPositionToken = await PositionToken.at(await marketContract.LONG_POSITION_TOKEN()); shortPositionToken = await PositionToken.at(await marketContract.SHORT_POSITION_TOKEN()); initialMktBalance = await mktToken.balanceOf.call(accounts[0]); initialCollateralBalance = await collateralToken.balanceOf.call(accounts[0]); }); afterEach(async function() { await utility.restoreEVMSnapshotsnapshotId(snapshotId); }); describe('mintPositionTokens()', function() { it('should fail for non whitelisted addresses', async function() { // 1. create unregistered contract const unregisteredContract = await utility.createMarketContract( collateralToken, collateralPool, accounts[0] ); // 2. Approve appropriate tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove, { from: accounts[0] }); // 3. minting tokens should fail let error = null; try { await collateralPool.mintPositionTokens(unregisteredContract.address, 1, false, { from: accounts[0] }); } catch (err) { error = err; } assert.ok( error instanceof Error, 'should not be able to mint from contracts not whitelisted' ); }); it('should fail if contract is settled', async function() { // 1. force contract to settlement await utility.settleContract(marketContract, priceCap, accounts[0]); // 2. approve collateral and mint tokens should fail const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); await utility.shouldFail(async () => { const qtyToMint = 1; await collateralPool.mintPositionTokens(marketContract.address, qtyToMint, false, { from: accounts[0] }); }, 'should not be able to mint position tokens after settlement'); }); it('should emit TokensMinted', async function() { // 1. Approve and mint tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); const qtyToMint = new BN('100'); const amountToBeLocked = qtyToMint.mul( utility.calculateTotalCollateral(priceFloor, priceCap, qtyMultiplier) ); const result = await collateralPool.mintPositionTokens( marketContract.address, qtyToMint, false, { from: accounts[0] } ); // 2. assert correct TokensMinted event emitted let tokensMintedEvent; await truffleAssert.eventEmitted(result, 'TokensMinted', mintedEvent => { tokensMintedEvent = mintedEvent; return true; }); assert.equal( tokensMintedEvent.marketContract, marketContract.address, 'incorrect marketContract arg for TokensMinted' ); assert.equal(tokensMintedEvent.user, accounts[0], 'incorrect user arg for TokensMinted'); assert.equal( tokensMintedEvent.qtyMinted.toNumber(), qtyToMint, 'incorrect qtyMinted arg for TokensMinted' ); assert.equal( tokensMintedEvent.collateralLocked.toNumber(), amountToBeLocked, 'incorrect collateralLocked arg for TokensMinted' ); }); it('should lock the correct amount of collateral', async function() { // 1. approve collateral and mint tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); const qtyToMint = new BN('100'); await collateralPool.mintPositionTokens(marketContract.address, qtyToMint, false, { from: accounts[0] }); // 2. balance after should be equal to expected balance const amountToBeLocked = qtyToMint.mul( utility.calculateTotalCollateral(priceFloor, priceCap, qtyMultiplier) ); const expectedBalanceAfterMint = initialCollateralBalance.sub(amountToBeLocked); const actualBalanceAfterMint = await collateralToken.balanceOf.call(accounts[0]); assert.isTrue( actualBalanceAfterMint.eq(expectedBalanceAfterMint), 'incorrect collateral amount locked for minting' ); }); it('should mint with zero MKT fee', async function() { // 1. approve collateral and mint tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); await mktToken.approve(collateralPool.address, amountToApprove); const qtyToMint = new BN('100'); await collateralPool.mintPositionTokens(marketContract.address, qtyToMint, false, { from: accounts[0] }); // 2. balance after should be equal to expected balance const expectedMktFees = new BN('0'); const finalMktBalance = await mktToken.balanceOf.call(accounts[0]); const actualMktFees = finalMktBalance.sub(initialMktBalance); assert.isTrue(actualMktFees.eq(expectedMktFees), 'non-zero mkt fees charged'); }); describe('when marketContract has both MKT and Collateral fees', async function() { let qtyToMint = new BN('5'); let collateralFeePerUnit; let mktFeePerUnit; beforeEach(async function() { collateralFeePerUnit = await feeMarketContract.COLLATERAL_TOKEN_FEE_PER_UNIT.call(); mktFeePerUnit = await feeMarketContract.MKT_TOKEN_FEE_PER_UNIT.call(); // approve tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove, { from: accounts[0] }); await mktToken.approve(collateralPool.address, amountToApprove); longPositionToken = await PositionToken.at(await feeMarketContract.LONG_POSITION_TOKEN()); shortPositionToken = await PositionToken.at(await feeMarketContract.SHORT_POSITION_TOKEN()); }); it('should mint position tokens', async function() { // 1. Start with fresh account const initialBalance = await collateralToken.balanceOf.call(accounts[1]); assert.isTrue(initialBalance.eq(new BN('0')), 'Account 1 already has a balance'); // 2. should fail to mint when user has no collateral. let error = null; try { await collateralPool.mintPositionTokens(marketContract.address, new BN('1'), false, { from: accounts[1] }); } catch (err) { error = err; } assert.ok( error instanceof Error, 'should not be able to mint with no collateral token balance' ); // 3. should fail to mint when user has not approved transfer of collateral (erc20 approve) const accountBalance = await collateralToken.balanceOf.call(accounts[0]); assert.isFalse( accountBalance.eq(new BN('0')), 'Account 0 does not have a balance of collateral' ); await collateralToken.approve(collateralPool.address, new BN('0')); // set zero approval const initialApproval = await collateralToken.allowance.call( accounts[0], collateralPool.address ); assert.isTrue(initialApproval.eq(new BN('0')), 'Account 0 already has an approval'); error = null; try { await collateralPool.mintPositionTokens(feeMarketContract.address, new BN('1'), false, { from: accounts[0] }); } catch (err) { error = err; } assert.ok( error instanceof Error, 'should not be able to mint with no collateral approval balance' ); // 4. should allow to mint when user has collateral tokens and has approved them const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); const qtyToMint = new BN('100'); await collateralPool.mintPositionTokens(feeMarketContract.address, qtyToMint, false, { from: accounts[0] }); const longPosTokenBalance = await longPositionToken.balanceOf.call(accounts[0]); const shortPosTokenBalance = await shortPositionToken.balanceOf.call(accounts[0]); assert.isTrue(longPosTokenBalance.eq(qtyToMint), 'incorrect amount of long tokens minted'); assert.isTrue( shortPosTokenBalance.eq(qtyToMint), 'incorrect amount of short tokens minted' ); }); describe('when isAttemptToPayInMKT is true', function() { beforeEach(async function() { // mint await collateralPool.mintPositionTokens(feeMarketContract.address, qtyToMint, true, { from: accounts[0] }); }); it('should charge correct mktFees', async function() { const expectedMktFee = mktFeePerUnit.mul(qtyToMint); const finalMktBalance = await mktToken.balanceOf.call(accounts[0]); const actualMktFee = initialMktBalance.sub(finalMktBalance); assert.isTrue(actualMktFee.eq(expectedMktFee), 'wrong mkt fee charged for minting'); }); it('should not charge collateral fees', async function() { const expectedCollateralTransfer = (await feeMarketContract.COLLATERAL_PER_UNIT()).mul( qtyToMint ); const finalCollateralBalance = await collateralToken.balanceOf.call(accounts[0]); const actualCollateralTransfer = initialCollateralBalance.sub(finalCollateralBalance); assert.isTrue( actualCollateralTransfer.eq(expectedCollateralTransfer), 'collateral token also charged for fees' ); }); }); describe('when isAttemptToPayInMKT is false', function() { beforeEach(async function() { // mint with isAttemptToPayInMKT == false await collateralPool.mintPositionTokens(feeMarketContract.address, qtyToMint, false, { from: accounts[0] }); }); it('should charge correct collateralFees', async function() { const expectedCollateralFee = collateralFeePerUnit.mul(qtyToMint); const expectedCollateralTransfer = (await feeMarketContract.COLLATERAL_PER_UNIT()).mul( qtyToMint ); const finalCollateralBalance = await collateralToken.balanceOf.call(accounts[0]); const actualCollateralFee = initialCollateralBalance .sub(finalCollateralBalance) .sub(expectedCollateralTransfer); assert.isTrue( actualCollateralFee.eq(expectedCollateralFee), 'wrong collateral fee charged for minting' ); }); it('should not charge mkt fees', async function() { const finalMktBalance = await mktToken.balanceOf.call(accounts[0]); assert.isTrue(initialMktBalance.eq(finalMktBalance), 'mkt token also charged for fees'); }); }); }); describe('when marketContract has only MKT fees', async function() { let qtyToMint = new BN('5'); let mktFeeMarketContract; beforeEach(async function() { mktFeeMarketContract = await utility.createMarketContract( collateralToken, collateralPool, accounts[0], accounts[0], [0, 150, 2, 1, 0, mktFee, utility.expirationInDays(1)] ); mktFeePerUnit = await mktFeeMarketContract.MKT_TOKEN_FEE_PER_UNIT.call(); await marketContractRegistry.addAddressToWhiteList(mktFeeMarketContract.address, { from: accounts[0] }); }); describe('when isAttemptToPayInMKT is true', function() { beforeEach(async function() { const amountToApprove = new BN('10000000000000000000000'); // 1e22 await mktToken.approve(collateralPool.address, amountToApprove, { from: accounts[0] }); await collateralToken.approve(collateralPool.address, amountToApprove); // mint await collateralPool.mintPositionTokens(mktFeeMarketContract.address, qtyToMint, true, { from: accounts[0] }); }); it('should charge correct mktFees', async function() { const expectedMktFee = mktFeePerUnit.mul(qtyToMint); const finalMktBalance = await mktToken.balanceOf.call(accounts[0]); const actualMktFee = initialMktBalance.sub(finalMktBalance); assert.isTrue(actualMktFee.eq(expectedMktFee), 'wrong mkt fee charged for minting'); }); it('should not charge collateral fees', async function() { const expectedCollateralTransfer = (await mktFeeMarketContract.COLLATERAL_PER_UNIT()).mul( qtyToMint ); const finalCollateralBalance = await collateralToken.balanceOf.call(accounts[0]); const actualCollateralTransfer = initialCollateralBalance.sub(finalCollateralBalance); assert.isTrue( actualCollateralTransfer.eq(expectedCollateralTransfer), 'collateral token also charged for fees' ); }); }); describe('when isAttemptToPayInMKT is false', function() { it('should still charge in mktFees', async function() { const qtyToMint = new BN('5'); const expectedMktFee = mktFeePerUnit.mul(qtyToMint); await mktToken.approve(collateralPool.address, expectedMktFee, { from: accounts[0] }); const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); // mint with isAttemptToPayInMKT == false await collateralPool.mintPositionTokens(mktFeeMarketContract.address, qtyToMint, false, { from: accounts[0] }); const finalMktBalance = await mktToken.balanceOf.call(accounts[0]); const actualMktFee = initialMktBalance.sub(finalMktBalance); assert.isTrue(actualMktFee.eq(expectedMktFee), 'mkt fees have been bypassed'); }); }); }); describe('when marketContract has only Collateral fees', async function() { let qtyToMint = new BN('5'); let collateralFeeMarketContract; beforeEach(async function() { collateralFeeMarketContract = await utility.createMarketContract( collateralToken, collateralPool, accounts[0], accounts[0], [0, 150, 2, 1, collateralFee, 0, utility.expirationInDays(1)] ); collateralFeePerUnit = await collateralFeeMarketContract.COLLATERAL_TOKEN_FEE_PER_UNIT.call(); await marketContractRegistry.addAddressToWhiteList(collateralFeeMarketContract.address, { from: accounts[0] }); }); describe('when isAttemptToPayInMKT is true', function() { beforeEach(async function() { // approve tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove, { from: accounts[0] }); await mktToken.approve(collateralPool.address, amountToApprove); // mint with isAttemptToPayInMKT == true await collateralPool.mintPositionTokens( collateralFeeMarketContract.address, qtyToMint, true, { from: accounts[0] } ); }); it('should still charge correct collateralFees', async function() { const expectedCollateralFee = collateralFeePerUnit.mul(qtyToMint); const expectedCollateralTransfer = (await collateralFeeMarketContract.COLLATERAL_PER_UNIT()).mul( qtyToMint ); const finalCollateralBalance = await collateralToken.balanceOf.call(accounts[0]); const actualCollateralFee = initialCollateralBalance .sub(finalCollateralBalance) .sub(expectedCollateralTransfer); assert.isTrue( actualCollateralFee.eq(expectedCollateralFee), 'wrong collateral fee charged for minting' ); }); it('should not charge mkt fees', async function() { const finalMktBalance = await mktToken.balanceOf.call(accounts[0]); assert.isTrue(initialMktBalance.eq(finalMktBalance), 'mkt token also charged for fees'); }); }); describe('when isAttemptToPayInMKT is false', function() { beforeEach(async function() { // approve tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove, { from: accounts[0] }); await mktToken.approve(collateralPool.address, amountToApprove); // mint with isAttemptToPayInMKT == false await collateralPool.mintPositionTokens( collateralFeeMarketContract.address, qtyToMint, false, { from: accounts[0] } ); }); it('should still charge correct collateralFees', async function() { const expectedCollateralFee = collateralFeePerUnit.mul(qtyToMint); const expectedCollateralTransfer = (await collateralFeeMarketContract.COLLATERAL_PER_UNIT()).mul( qtyToMint ); const finalCollateralBalance = await collateralToken.balanceOf.call(accounts[0]); const actualCollateralFee = initialCollateralBalance .sub(finalCollateralBalance) .sub(expectedCollateralTransfer); assert.isTrue( actualCollateralFee.eq(expectedCollateralFee), 'wrong collateral fee charged for minting' ); }); it('should not charge mkt fees', async function() { const finalMktBalance = await mktToken.balanceOf.call(accounts[0]); assert.isTrue(initialMktBalance.eq(finalMktBalance), 'mkt token also charged for fees'); }); }); }); }); describe('redeemPositionTokens()', function() { it('should fail for non whitelisted addresses', async function() { // 1. create unregistered contract const unregisteredContract = await utility.createMarketContract( collateralToken, collateralPool, accounts[0] ); // 2. redeemingPositionTokens should fail for correct reason. let error = null; try { await collateralPool.redeemPositionTokens(unregisteredContract.address, 1, { from: accounts[0] }); } catch (err) { error = err; } // TODO: When we upgrade to truffle 5, update test to check for actual failure reason assert.ok( error instanceof Error, 'should not be able to mint from contracts not whitelisted' ); }); it('should redeem token sets and return correct amount of collateral', async function() { // 1. approve collateral and mint tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); const qtyToMint = new BN('100'); await collateralPool.mintPositionTokens(marketContract.address, qtyToMint, false, { from: accounts[0] }); const initialLongPosTokenBalance = await longPositionToken.balanceOf.call(accounts[0]); const initialShortPosTokenBalance = await shortPositionToken.balanceOf.call(accounts[0]); // 2. redeem tokens const qtyToRedeem = new BN('50'); const collateralBalanceBeforeRedeem = await collateralToken.balanceOf.call(accounts[0]); await collateralPool.redeemPositionTokens(marketContract.address, qtyToRedeem, { from: accounts[0] }); // 3. assert final tokens balance are as expected const expectedFinalLongPosTokenBalance = initialLongPosTokenBalance.sub(qtyToRedeem); const expectedFinalShortPosTokenBalance = initialShortPosTokenBalance.sub(qtyToRedeem); const finalLongPosTokenBalance = await longPositionToken.balanceOf.call(accounts[0]); const finalShortPosTokenBalance = await shortPositionToken.balanceOf.call(accounts[0]); assert.isTrue( finalLongPosTokenBalance.eq(expectedFinalLongPosTokenBalance), 'incorrect long position token balance after redeeming' ); assert.isTrue( finalShortPosTokenBalance.eq(expectedFinalShortPosTokenBalance), 'incorrect short position token balance after redeeming' ); // 4. assert correct collateral is returned const collateralAmountToBeReleased = qtyToRedeem.mul( utility.calculateTotalCollateral(priceFloor, priceCap, qtyMultiplier) ); const expectedCollateralBalanceAfterRedeem = collateralBalanceBeforeRedeem.add( collateralAmountToBeReleased ); const actualCollateralBalanceAfterRedeem = await collateralToken.balanceOf.call(accounts[0]); assert.isTrue( actualCollateralBalanceAfterRedeem.eq(expectedCollateralBalanceAfterRedeem), 'incorrect collateral amount returned after redeeming' ); }); it('should emit TokensRedeemed', async function() { // 1. approve collateral and mint tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); const qtyToMint = new BN('100'); await collateralPool.mintPositionTokens(marketContract.address, qtyToMint, false, { from: accounts[0] }); // 2. redeem tokens const qtyToRedeem = new BN('50'); const collateralAmountToBeReleased = qtyToRedeem.mul( utility.calculateTotalCollateral(priceFloor, priceCap, qtyMultiplier) ); const result = await collateralPool.redeemPositionTokens( marketContract.address, qtyToRedeem, { from: accounts[0] } ); // 3. assert correct TokensMinted event emitted let tokensRedeemedEvent; await truffleAssert.eventEmitted(result, 'TokensRedeemed', redeemed => { tokensRedeemedEvent = redeemed; return true; }); assert.equal( tokensRedeemedEvent.marketContract, marketContract.address, 'incorrect marketContract arg for TokensRedeemed' ); assert.equal(tokensRedeemedEvent.user, accounts[0], 'incorrect user arg for TokensRedeemed'); assert.equal( tokensRedeemedEvent.longQtyRedeemed.toNumber(), qtyToRedeem, 'incorrect qtyRedeemed arg for TokensRedeemed' ); assert.equal( tokensRedeemedEvent.shortQtyRedeemed.toNumber(), qtyToRedeem, 'incorrect qtyRedeemed arg for TokensRedeemed' ); assert.equal( tokensRedeemedEvent.collateralUnlocked.toNumber(), collateralAmountToBeReleased, 'incorrect collateralUnlocked arg for TokensRedeemed' ); }); it('should fail to redeem single tokens before settlement', async function() { // 1. approve collateral and mint tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); const qtyToMint = new BN('1'); await collateralPool.mintPositionTokens(marketContract.address, qtyToMint, false, { from: accounts[0] }); const shortTokenBalance = await shortPositionToken.balanceOf.call(accounts[0]); const longTokenBalance = await longPositionToken.balanceOf.call(accounts[0]); assert.isTrue( shortTokenBalance.eq(longTokenBalance), 'long token and short token balances are not equals' ); // 2. transfer part of the long token await longPositionToken.transfer(accounts[1], qtyToMint, { from: accounts[0] }); // 3. attempting to redeem all shorts before settlement should fails let error = null; try { const qtyToRedeem = await shortPositionToken.balanceOf.call(accounts[0]); await collateralPool.redeemPositionTokens(marketContract.address, qtyToRedeem, { from: accounts[0] }); } catch (err) { error = err; } assert.ok( error instanceof Error, 'should not be able to redeem single tokens before settlement' ); }); }); describe('settleAndClose()', function() { it('should fail if called before settlement', async () => { let settleAndCloseError = null; try { await collateralPool.settleAndClose(marketContract.address, 1, 0, { from: accounts[0] }); } catch (err) { settleAndCloseError = err; } assert.ok( settleAndCloseError instanceof Error, 'settleAndClose() did not fail before settlement' ); }); it('should fail if user has insufficient tokens', async function() { let error = null; // 1. approve collateral and mint tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); const qtyToMint = new BN('1'); await collateralPool.mintPositionTokens(marketContract.address, qtyToMint, false, { from: accounts[0] }); // 2. force contract to settlement const settlementPrice = await utility.settleContract( marketContract, priceCap.sub(new BN('10')), accounts[0] ); // 3. attempt to redeem too much long tokens const longTokenQtyToRedeem = (await longPositionToken.balanceOf.call(accounts[0])).add( new BN('1') ); try { await collateralPool.settleAndClose(marketContract.address, longTokenQtyToRedeem, 0, { from: accounts[0] }); } catch (err) { error = err; } assert.instanceOf(error, Error, 'should not be able to redeem insufficient long tokens'); // 4. attempt to redeem too much short tokens error = null; const shortTokenQtyToRedeem = (await shortPositionToken.balanceOf.call(accounts[0])).add( new BN('1') ); try { await collateralPool.settleAndClose(marketContract.address, 0, shortTokenQtyToRedeem, { from: accounts[0] }); } catch (err) { error = err; } assert.instanceOf(error, Error, 'should not be able to redeem insufficient short tokens'); }); it('should fail if time not pass settlement delay', async function() { let error = null; // 1. force contract to settlement await utility.settleContract(marketContract, priceCap.sub(new BN('10')), accounts[0]); // 2. move time a little ahead but less than postSettlement < 1 day await utility.increase(7000); // 3. attempting to redeem token should fail await utility.shouldFail( async () => { const shortTokenQtyToRedeem = new BN('1'); await collateralPool.settleAndClose(marketContract.address, 0, shortTokenQtyToRedeem, { from: accounts[0] }); }, 'should be able to settle and close', 'Contract is not past settlement delay', 'should have for contract not past settlement delay' ); }); it('should redeem short and long tokens after settlement', async function() { let error = null; let result = null; // 1. approve collateral and mint tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); const qtyToMint = new BN('1'); await collateralPool.mintPositionTokens(marketContract.address, qtyToMint, false, { from: accounts[0] }); // 2. force contract to settlement const settlementPrice = await utility.settleContract( marketContract, priceCap.sub(new BN('10')), accounts[0] ); await utility.increase(87000); // extend time past delay for withdrawal of funds // 3. redeem all short position tokens after settlement should pass const shortTokenBalanceBeforeRedeem = await shortPositionToken.balanceOf.call(accounts[0]); const shortTokenQtyToRedeem = new BN('1'); try { result = await collateralPool.settleAndClose( marketContract.address, 0, shortTokenQtyToRedeem, { from: accounts[0] } ); } catch (err) { error = err; } assert.isNull(error, 'should be able to redeem short tokens after settlement'); // 4. balance of short tokens should be updated. const expectedShortTokenBalanceAfterRedeem = shortTokenBalanceBeforeRedeem.sub( shortTokenQtyToRedeem ); const actualShortTokenBalanceAfterRedeem = await shortPositionToken.balanceOf.call( accounts[0] ); assert.isTrue( actualShortTokenBalanceAfterRedeem.eq(expectedShortTokenBalanceAfterRedeem), 'short position tokens balance was not reduced' ); // 5. ensure correct events are emitted for short settlement const shortCollateralAmountReleased = utility.calculateCollateralToReturn( priceFloor, priceCap, qtyMultiplier, shortTokenQtyToRedeem.mul(new BN('-1')), settlementPrice ); // assert correct TokensRedeemed event emitted let shortTokensRedeemedEvent; await truffleAssert.eventEmitted(result, 'TokensRedeemed', redeemed => { shortTokensRedeemedEvent = redeemed; return true; }); assert.equal( shortTokensRedeemedEvent.marketContract, marketContract.address, 'incorrect marketContract arg for TokensRedeemed' ); assert.equal( shortTokensRedeemedEvent.user, accounts[0], 'incorrect user arg for TokensRedeemed' ); assert.isTrue( shortTokensRedeemedEvent.shortQtyRedeemed.eq(shortTokenQtyToRedeem), 'incorrect qtyRedeemed arg for TokensRedeemed' ); assert.isTrue( shortCollateralAmountReleased.eq(shortTokensRedeemedEvent.collateralUnlocked), 'incorrect collateralUnlocked arg for TokensRedeemed' ); // 6. redeem all long position tokens after settlement should pass const longTokenBalanceBeforeRedeem = await longPositionToken.balanceOf.call(accounts[0]); const longTokenQtyToRedeem = new BN('1'); error = null; result = null; try { result = await collateralPool.settleAndClose( marketContract.address, longTokenQtyToRedeem, 0, { from: accounts[0] } ); } catch (err) { error = err; } assert.isNull(error, 'should be able to redeem long tokens after settlement'); // 7. balance of long tokens should be updated. const expectedLongTokenBalanceAfterRedeem = longTokenBalanceBeforeRedeem.sub( longTokenQtyToRedeem ); const actualLongTokenBalanceAfterRedeem = await longPositionToken.balanceOf.call(accounts[0]); assert.isTrue( actualLongTokenBalanceAfterRedeem.eq(expectedLongTokenBalanceAfterRedeem), 'long position tokens balance was not reduced' ); // 8. ensure correct events are emitted for long settlement const longCollateralAmountReleased = utility.calculateCollateralToReturn( priceFloor, priceCap, qtyMultiplier, longTokenQtyToRedeem, settlementPrice ); // assert correct TokensRedeemed event emitted let longTokensRedeemedEvent; await truffleAssert.eventEmitted(result, 'TokensRedeemed', redeemed => { longTokensRedeemedEvent = redeemed; return true; }); assert.equal( longTokensRedeemedEvent.marketContract, marketContract.address, 'incorrect marketContract arg for TokensRedeemed' ); assert.equal( longTokensRedeemedEvent.user, accounts[0], 'incorrect user arg for TokensRedeemed' ); assert.isTrue( longTokensRedeemedEvent.longQtyRedeemed.eq(longTokenQtyToRedeem), 'incorrect qtyRedeemed arg for TokensRedeemed' ); assert.isTrue( longTokensRedeemedEvent.collateralUnlocked.eq(longCollateralAmountReleased), 'incorrect collateralUnlocked arg for TokensRedeemed' ); }); it('should return correct amount of collateral when redeemed after settlement', async function() { // 1. approve collateral and mint tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove); const qtyToMint = new BN('1'); await collateralPool.mintPositionTokens(marketContract.address, qtyToMint, false, { from: accounts[0] }); // 2. transfer part of the long token await longPositionToken.transfer(accounts[1], 1, { from: accounts[0] }); // 3. force contract to settlement const settlementPrice = await utility.settleContract( marketContract, priceCap.sub(new BN('10')), accounts[0] ); await utility.increase(87000); // extend time past delay for withdrawal of funds // 4. redeem all shorts on settlement const collateralBalanceBeforeRedeem = await collateralToken.balanceOf.call(accounts[0]); const qtyToRedeem = await shortPositionToken.balanceOf.call(accounts[0]); await collateralPool.settleAndClose(marketContract.address, 0, qtyToRedeem, { from: accounts[0] }); // 5. should return appropriate collateral const collateralToReturn = utility.calculateCollateralToReturn( priceFloor, priceCap, qtyMultiplier, qtyToRedeem.mul(new BN('-1')), settlementPrice ); const expectedCollateralBalanceAfterRedeem = collateralBalanceBeforeRedeem.add( collateralToReturn ); const actualCollateralBalanceAfterRedeem = await collateralToken.balanceOf.call(accounts[0]); assert.isTrue( actualCollateralBalanceAfterRedeem.eq(expectedCollateralBalanceAfterRedeem), 'short position tokens balance was not reduced' ); }); }); describe('withdrawFees()', function() { beforeEach(async function() { // approve tokens const amountToApprove = new BN('10000000000000000000000'); // 1e22 await collateralToken.approve(collateralPool.address, amountToApprove, { from: accounts[0] }); await mktToken.approve(collateralPool.address, amountToApprove); }); it('should fail if no fees available for token address', async function() { await utility.shouldFail( async function() { await collateralPool.withdrawFees(collateralToken.address, accounts[1], { from: accounts[0] }); }, 'did not fail on withdrawal of zero fees', 'No fees available for withdrawal', 'did not fail for no fees available' ); }); it('should fail if sender is not owner of collateralPool', async function() { await utility.shouldFail(async function() { const notOwner = accounts[1]; await collateralPool.withdrawFees(collateralToken.address, accounts[1], { from: notOwner }); }, 'did not fail for sender !== owner'); }); it('should be able to withdraw collateral token fees', async function() { const qtyToMint = new BN('5'); collateralFeePerUnit = await feeMarketContract.COLLATERAL_TOKEN_FEE_PER_UNIT.call(); const expectedFeesWithdrawn = collateralFeePerUnit.mul(qtyToMint); // mint tokens with collateral fees await collateralPool.mintPositionTokens(feeMarketContract.address, qtyToMint, false, { from: accounts[0] }); // withdraw collateral tokens to account[1] const initialReceipientBalance = await collateralToken.balanceOf.call(accounts[1]); await collateralPool.withdrawFees(collateralToken.address, accounts[1], { from: accounts[0] }); const finalRecipientBalance = await collateralToken.balanceOf.call(accounts[1]); const actualFeesWithdrawn = finalRecipientBalance.sub(initialReceipientBalance); assert.isTrue( actualFeesWithdrawn.eq(expectedFeesWithdrawn), 'incorrect collateral fees withdrawn' ); }); it('should be able to withdraw mkt token fees', async function() { const qtyToMint = new BN('5'); mktFeePerUnit = await feeMarketContract.MKT_TOKEN_FEE_PER_UNIT.call(); const expectedFeesWithdrawn = mktFeePerUnit.mul(qtyToMint); // mint tokens with mkt fees await collateralPool.mintPositionTokens(feeMarketContract.address, qtyToMint, true, { from: accounts[0] }); // withdraw fees to account[1] const initialReceipientBalance = await mktToken.balanceOf.call(accounts[1]); await collateralPool.withdrawFees(mktToken.address, accounts[1], { from: accounts[0] }); const finalReceipientBalance = await mktToken.balanceOf.call(accounts[1]); const actualFeesWithdrawn = finalReceipientBalance.sub(initialReceipientBalance); assert.isTrue(actualFeesWithdrawn.eq(expectedFeesWithdrawn), 'incorrect mkt fees withdrawn'); }); }); describe('setMKTTokenAddress()', function() { it('should fail if attempting to set MKT address to null', async function() { await utility.shouldFail( async function() { await collateralPool.setMKTTokenAddress('0x0000000000000000000000000000000000000000', { from: accounts[0] }); }, 'did not fail on attempt to set MKT Token Address to null', 'Cannot set MKT Token Address To Null', 'did not fail with null MKT Token address' ); }); it('should fail if attempting to set MKT address from non owner address', async function() { await utility.shouldFail( async function() { await collateralPool.setMKTTokenAddress(collateralToken.address, { from: accounts[3] }); }, 'did not fail on attempt to set MKT Token Address from non owner address', '', 'did not fail from non owner address' ); }); it('should work attempting to set MKT address from owner address', async function() { await collateralPool.setMKTTokenAddress(collateralToken.address, { from: accounts[0] }); const newlySetAddress = await collateralPool.mktToken.call(); assert.equal( newlySetAddress, collateralToken.address, 'unable to set new MKT Token Address from owner account' ); }); }); describe('setMarketContractRegistryAddress()', function() { it('should fail if attempting to set registry address to null', async function() { await utility.shouldFail( async function() { await collateralPool.setMarketContractRegistryAddress( '0x0000000000000000000000000000000000000000', { from: accounts[0] } ); }, 'did not fail on attempt to set registry address to null', 'Cannot set Market Contract Registry Address To Null', 'did not fail with null address' ); }); it('should fail if attempting to set registry address from non owner address', async function() { await utility.shouldFail( async function() { await collateralPool.setMarketContractRegistryAddress(collateralToken.address, { from: accounts[3] }); }, 'did not fail on attempt to set registry Address from non owner address', '', 'did not fail from non owner address' ); }); it('should work attempting to set registry address from owner address', async function() { await collateralPool.setMarketContractRegistryAddress(collateralToken.address, { from: accounts[0] }); const newlySetAddress = await collateralPool.marketContractRegistry.call(); assert.equal( newlySetAddress, collateralToken.address, 'unable to set new MKT Token Address from owner account' ); }); }); });