From c919bec2b266b46cc6e18fcd1a3ec38e6e9b0816 Mon Sep 17 00:00:00 2001 From: hemulin Date: Thu, 30 Apr 2020 23:58:51 +0300 Subject: [PATCH] Adding Gas fee to mint/burn. Supporting stablecoin by address. Removed KYCVerifier from Persistent storage --- contracts/CompositionCalculator.sol | 32 ++- contracts/KYCVerifier.sol | 60 ++++- contracts/PersistentStorage.sol | 71 +++--- contracts/TokenSwapManager.sol | 88 +++++--- test/CashPool.test.js | 26 ++- test/CompositionCalculator.test.js | 338 +++++++++++++++++++++------- test/KYCVerifier.test.js | 118 ++++++++-- test/Modifier.test.js | 55 +++-- test/PersistentStorage.test.js | 111 ++------- test/TokenSwapManager.test.js | 186 ++++++++++----- 10 files changed, 745 insertions(+), 340 deletions(-) diff --git a/contracts/CompositionCalculator.sol b/contracts/CompositionCalculator.sol index c61185c..ac5fe86 100644 --- a/contracts/CompositionCalculator.sol +++ b/contracts/CompositionCalculator.sol @@ -300,13 +300,17 @@ contract CompositionCalculator is Initializable { * @dev Returns cash without fee * @param _cash The cash provided to create token * @param _mintingFee The minting fee to remove + * @param _minimumMintingFee The minimum minting fee in $ to remove */ - function removeMintingFeeFromCash(uint256 _cash, uint256 _mintingFee) - public - pure - returns (uint256 cashAfterFee) - { + function removeMintingFeeFromCash( + uint256 _cash, + uint256 _mintingFee, + uint256 _minimumMintingFee + ) public pure returns (uint256 cashAfterFee) { uint256 creationFeeInCash = DSMath.wmul(_cash, _mintingFee); + if (_minimumMintingFee > creationFeeInCash) { + creationFeeInCash = _minimumMintingFee; + } cashAfterFee = DSMath.sub(_cash, creationFeeInCash); } @@ -348,7 +352,12 @@ contract CompositionCalculator is Initializable { returns (uint256 cashAfterFee) { uint256 creationFee = persistentStorage.getMintingFee(_cash); - cashAfterFee = removeMintingFeeFromCash(_cash, creationFee); + uint256 minimumMintingFee = persistentStorage.minimumMintingFee(); + cashAfterFee = removeMintingFeeFromCash( + _cash, + creationFee, + minimumMintingFee + ); } /** @@ -359,9 +368,11 @@ contract CompositionCalculator is Initializable { function getCurrentTokenAmountCreatedByCash( //Create uint256 _cash, - uint256 _spotPrice + uint256 _spotPrice, + uint256 _gasFee ) public view returns (uint256 tokenAmountCreated) { - uint256 cashAfterFee = removeCurrentMintingFeeFromCash(_cash); + uint256 cashAfterGas = DSMath.sub(_cash, _gasFee); + uint256 cashAfterFee = removeCurrentMintingFeeFromCash(cashAfterGas); tokenAmountCreated = getTokenAmountCreatedByCash( persistentStorage.getCashPositionPerTokenUnit(), persistentStorage.getBalancePerTokenUnit(), @@ -379,7 +390,8 @@ contract CompositionCalculator is Initializable { function getCurrentCashAmountCreatedByToken( //Redeem uint256 _tokenAmount, - uint256 _spotPrice + uint256 _spotPrice, + uint256 _gasFee ) public view returns (uint256 cashFromTokenRedeem) { uint256 lendingFee = persistentStorage.getLendingFee(); uint256 daysSinceLastRebalance = getDaysSinceLastRebalance() + 1; @@ -408,6 +420,8 @@ contract CompositionCalculator is Initializable { cashFromTokenRedeem = removeCurrentMintingFeeFromCash( DSMath.sub(cashFromToken, fiatForLendingFee) ); + + cashFromTokenRedeem = DSMath.sub(cashFromTokenRedeem, _gasFee); } function getDaysSinceLastRebalance() diff --git a/contracts/KYCVerifier.sol b/contracts/KYCVerifier.sol index 9d30c9b..867d286 100644 --- a/contracts/KYCVerifier.sol +++ b/contracts/KYCVerifier.sol @@ -1,14 +1,16 @@ pragma solidity ^0.5.0; -import "@openzeppelin/upgrades/contracts/Initializable.sol"; -import "./Abstract/InterfaceStorage.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; -contract KYCVerifier is Initializable { - InterfaceStorage public persistentStorage; +contract KYCVerifier is Ownable { + address public bridge; - function initialize(address _persistentStorage) public initializer { - persistentStorage = InterfaceStorage(_persistentStorage); + mapping(address => bool) public whitelistedAddresses; + + function initialize(address ownerAddress) public initializer { + require(ownerAddress != address(0), "owner adddress must not be empty"); + Ownable.initialize(ownerAddress); } function isAddressWhitelisted(address userAddress) @@ -16,6 +18,50 @@ contract KYCVerifier is Initializable { view returns (bool) { - return persistentStorage.whitelistedAddresses(userAddress); + return whitelistedAddresses[userAddress]; + } + + // @dev Set whitelisted addresses + function setWhitelistedAddress(address adddressToAdd) + public + onlyOwnerOrBridge + { + require(adddressToAdd != address(0), "adddress must not be empty"); + + whitelistedAddresses[adddressToAdd] = true; + } + + // @dev Remove whitelisted addresses + function removeWhitelistedAddress(address addressToRemove) + public + onlyOwnerOrBridge + { + require( + whitelistedAddresses[addressToRemove], + "address must be added to be removed allowed" + ); + + delete whitelistedAddresses[addressToRemove]; + } + + // @dev Updates whitelisted addresses + function updateWhitelistedAddress(address oldAddress, address newAddress) + public + { + removeWhitelistedAddress(oldAddress); + setWhitelistedAddress(newAddress); + } + + function setBridge(address _bridge) public onlyOwner { + require(_bridge != address(0), "adddress must not be empty"); + bridge = _bridge; + } + + modifier onlyOwnerOrBridge() { + require( + isOwner() || _msgSender() == bridge, + "caller is not the owner or bridge" + ); + _; } } diff --git a/contracts/PersistentStorage.sol b/contracts/PersistentStorage.sol index 615a0db..5d117b0 100644 --- a/contracts/PersistentStorage.sol +++ b/contracts/PersistentStorage.sol @@ -29,12 +29,13 @@ contract PersistentStorage is Ownable { uint256 public lastActivityDay; uint256 public minRebalanceAmount; uint256 public managementFee; + uint256 public minimumMintingFee; + uint256 public minimumTrade; + uint8 public balancePrecision; mapping(uint256 => Accounting[]) private accounting; - mapping(address => bool) public whitelistedAddresses; - uint256[] public mintingFeeBracket; mapping(uint256 => uint256) public mintingFee; @@ -49,17 +50,19 @@ contract PersistentStorage is Ownable { function initialize( address ownerAddress, uint256 _managementFee, - uint256 _minRebalanceAmount + uint256 _minRebalanceAmount, + uint8 _balancePrecision, + uint256 _lastMintingFee, + uint256 _minimumMintingFee, + uint256 _minimumTrade ) public initializer { initialize(ownerAddress); managementFee = _managementFee; minRebalanceAmount = _minRebalanceAmount; - mintingFeeBracket.push(50000 ether); - mintingFeeBracket.push(100000 ether); - mintingFee[50000 ether] = 3 ether / 1000; //0.3% - mintingFee[100000 ether] = 2 ether / 1000; //0.2% - mintingFee[~uint256(0)] = 1 ether / 1000; //0.1% all values higher - balancePrecision = 10; + mintingFee[~uint256(0)] = _lastMintingFee; + balancePrecision = _balancePrecision; + minimumMintingFee = _minimumMintingFee; + minimumTrade = _minimumTrade; } function setTokenSwapManager(address _tokenSwapManager) public onlyOwner { @@ -91,6 +94,14 @@ contract PersistentStorage is Ownable { _; } + modifier onlyOwnerOrBridge() { + require( + isOwner() || _msgSender() == bridge, + "caller is not the owner or bridge" + ); + _; + } + function setDelayedRedemptionsByUser( uint256 amountToRedeem, address whitelistedAddress @@ -210,34 +221,6 @@ contract PersistentStorage is Ownable { ); } - // @dev Set whitelisted addresses - function setWhitelistedAddress(address adddressToAdd) public onlyOwner { - require(adddressToAdd != address(0), "adddress must not be empty"); - - whitelistedAddresses[adddressToAdd] = true; - } - - // @dev Remove whitelisted addresses - function removeWhitelistedAddress(address addressToRemove) - public - onlyOwner - { - require( - whitelistedAddresses[addressToRemove], - "address must be added to be removed allowed" - ); - - delete whitelistedAddresses[addressToRemove]; - } - - // @dev Updates whitelisted addresses - function updateWhitelistedAddress(address oldAddress, address newAddress) - public - { - removeWhitelistedAddress(oldAddress); - setWhitelistedAddress(newAddress); - } - // @dev Get accounting values for a specific day // @param date format as 20200123 for 23th of January 2020 function getAccounting(uint256 date) @@ -353,7 +336,9 @@ contract PersistentStorage is Ownable { onlyOwner { require( - _mintingFeeLimit > mintingFeeBracket[mintingFeeBracket.length - 1], + mintingFeeBracket.length == 0 || + _mintingFeeLimit > + mintingFeeBracket[mintingFeeBracket.length - 1], "New minting fee bracket needs to be bigger then last one" ); mintingFeeBracket.push(_mintingFeeLimit); @@ -416,4 +401,14 @@ contract PersistentStorage is Ownable { function setLastPrecision(uint8 _balancePrecision) public onlyOwner { balancePrecision = _balancePrecision; } + + // @dev Sets minimum minting fee + function setMinimumMintingFee(uint256 _minimumMintingFee) public onlyOwner { + minimumMintingFee = _minimumMintingFee; + } + + // @dev Sets minimum trade value + function setMinimumTrade(uint256 _minimumTrade) public onlyOwner { + minimumTrade = _minimumTrade; + } } diff --git a/contracts/TokenSwapManager.sol b/contracts/TokenSwapManager.sol index 2c7c9ea..8f8a6ba 100644 --- a/contracts/TokenSwapManager.sol +++ b/contracts/TokenSwapManager.sol @@ -8,6 +8,7 @@ import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol import "./CashPool.sol"; import "./KYCVerifier.sol"; import "./CompositionCalculator.sol"; +import "./Token/InverseToken.sol"; import "./Abstract/InterfaceInverseToken.sol"; import "./PersistentStorage.sol"; @@ -18,9 +19,9 @@ contract TokenSwapManager is Initializable, Ownable { using Strings for string; using SafeMath for uint256; - address public stablecoin; address public inverseToken; + InverseToken public erc20; KYCVerifier public kycVerifier; CashPool public cashPool; PersistentStorage public persistentStorage; @@ -30,7 +31,8 @@ contract TokenSwapManager is Initializable, Ownable { string orderType, address whitelistedAddress, uint256 tokensGiven, - uint256 tokensRecieved + uint256 tokensRecieved, + address stablecoin ); event RebalanceEvent( @@ -42,7 +44,6 @@ contract TokenSwapManager is Initializable, Ownable { function initialize( address _owner, - address _stablecoin, address _inverseToken, address _cashPool, address _compositionCalculator @@ -51,14 +52,12 @@ contract TokenSwapManager is Initializable, Ownable { require( _owner != address(0) && - _stablecoin != address(0) && _inverseToken != address(0) && _cashPool != address(0) && _compositionCalculator != address(0), "addresses cannot be zero" ); - stablecoin = _stablecoin; inverseToken = _inverseToken; cashPool = CashPool(_cashPool); @@ -79,7 +78,9 @@ contract TokenSwapManager is Initializable, Ownable { uint256 tokensRecieved, uint256 avgBlendedFee, uint256 executionPrice, - address whitelistedAddress + address whitelistedAddress, + address stablecoin, + uint256 gasFee ) public onlyOwnerOrBridge() notPausedOrShutdown() returns (bool retVal) { // Require is Whitelisted require( @@ -92,14 +93,18 @@ contract TokenSwapManager is Initializable, Ownable { transferTokenFromPool( stablecoin, whitelistedAddress, - normalizeUSDC(tokensGiven) + normalizeStablecoin(tokensGiven, stablecoin) ); return false; } // Check Tokens Recieved with Composition Calculator uint256 _tokensRecieved = compositionCalculator - .getCurrentTokenAmountCreatedByCash(tokensGiven, executionPrice); + .getCurrentTokenAmountCreatedByCash( + tokensGiven, + executionPrice, + gasFee + ); require( _tokensRecieved == tokensRecieved, "tokens created must equal tokens recieved" @@ -121,7 +126,8 @@ contract TokenSwapManager is Initializable, Ownable { "CREATE", whitelistedAddress, tokensGiven, - tokensRecieved + tokensRecieved, + stablecoin ); // Mint Tokens to Address @@ -137,7 +143,9 @@ contract TokenSwapManager is Initializable, Ownable { uint256 tokensRecieved, uint256 avgBlendedFee, uint256 executionPrice, - address whitelistedAddress + address whitelistedAddress, + address stablecoin, + uint256 gasFee ) public onlyOwnerOrBridge() notPausedOrShutdown() returns (bool retVal) { // Require Whitelisted require( @@ -157,7 +165,11 @@ contract TokenSwapManager is Initializable, Ownable { // Check Cash Recieved with Composition Calculator uint256 _tokensRecieved = compositionCalculator - .getCurrentCashAmountCreatedByToken(tokensGiven, executionPrice); + .getCurrentCashAmountCreatedByToken( + tokensGiven, + executionPrice, + gasFee + ); require( _tokensRecieved == tokensRecieved, "cash redeemed must equal tokens recieved" @@ -175,7 +187,12 @@ contract TokenSwapManager is Initializable, Ownable { ); // Redeem Stablecoin or Perform Delayed Settlement - redeemFunds(tokensGiven, tokensRecieved, whitelistedAddress); + redeemFunds( + tokensGiven, + tokensRecieved, + whitelistedAddress, + stablecoin + ); // Burn Tokens to Address InterfaceInverseToken token = InterfaceInverseToken(inverseToken); @@ -188,7 +205,8 @@ contract TokenSwapManager is Initializable, Ownable { string memory orderType, address whiteListedAddress, uint256 tokensGiven, - uint256 tokensRecieved + uint256 tokensRecieved, + address stablecoin ) internal { require( tokensGiven != 0 && tokensRecieved != 0, @@ -199,20 +217,25 @@ contract TokenSwapManager is Initializable, Ownable { orderType, whiteListedAddress, tokensGiven, - tokensRecieved + tokensRecieved, + stablecoin ); } function settleDelayedFunds( uint256 tokensToRedeem, - address whitelistedAddress + address whitelistedAddress, + address stablecoin ) public onlyOwnerOrBridge notPausedOrShutdown { require( kycVerifier.isAddressWhitelisted(whitelistedAddress), "only whitelisted may redeem funds" ); - bool isSufficientFunds = isHotWalletSufficient(tokensToRedeem); + bool isSufficientFunds = isHotWalletSufficient( + tokensToRedeem, + stablecoin + ); require( isSufficientFunds == true, "not enough funds in the hot wallet" @@ -230,28 +253,33 @@ contract TokenSwapManager is Initializable, Ownable { transferTokenFromPool( stablecoin, whitelistedAddress, - normalizeUSDC(tokensToRedeem) + normalizeStablecoin(tokensToRedeem, stablecoin) ); } function redeemFunds( uint256 tokensGiven, uint256 tokensToRedeem, - address whitelistedAddress + address whitelistedAddress, + address stablecoin ) internal { - bool isSufficientFunds = isHotWalletSufficient(tokensToRedeem); + bool isSufficientFunds = isHotWalletSufficient( + tokensToRedeem, + stablecoin + ); if (isSufficientFunds) { transferTokenFromPool( stablecoin, whitelistedAddress, - normalizeUSDC(tokensToRedeem) + normalizeStablecoin(tokensToRedeem, stablecoin) ); writeOrderResponse( "REDEEM", whitelistedAddress, tokensGiven, - tokensToRedeem + tokensToRedeem, + stablecoin ); } else { uint256 tokensOutstanding = persistentStorage @@ -265,25 +293,31 @@ contract TokenSwapManager is Initializable, Ownable { "REDEEM_NO_SETTLEMENT", whitelistedAddress, tokensGiven, - tokensToRedeem + tokensToRedeem, + stablecoin ); } } - function isHotWalletSufficient(uint256 tokensToRedeem) + function isHotWalletSufficient(uint256 tokensToRedeem, address stablecoin) internal - view returns (bool) { InterfaceInverseToken _stablecoin = InterfaceInverseToken(stablecoin); uint256 stablecoinBalance = _stablecoin.balanceOf(address(cashPool)); - if (normalizeUSDC(tokensToRedeem) > stablecoinBalance) return false; + if (normalizeStablecoin(tokensToRedeem, stablecoin) > stablecoinBalance) + return false; return true; } - function normalizeUSDC(uint256 usdcValue) public pure returns (uint256) { - return usdcValue / 10**12; + function normalizeStablecoin(uint256 stablecoinValue, address stablecoin) + internal + returns (uint256) + { + erc20 = InverseToken(stablecoin); + uint256 exponent = 18 - erc20.decimals(); + return stablecoinValue / 10**exponent; // 6 decimal stable coin = 10**12 } //////////////// Daily Rebalance //////////////// diff --git a/test/CashPool.test.js b/test/CashPool.test.js index cdac333..cb9670e 100644 --- a/test/CashPool.test.js +++ b/test/CashPool.test.js @@ -24,12 +24,29 @@ describe("CashPool", function() { persistentStorage = await PersistentStorage.new({ from: owner }); const managementFee = ether("7"); const minRebalanceAmount = ether("1"); + const lastMintingFee = ether("0.001"); + const balancePrecision = 12; + const minimumMintingFee = ether("5"); + const minimumTrade = ether("50"); await persistentStorage.initialize( owner, managementFee, - minRebalanceAmount + minRebalanceAmount, + balancePrecision, + lastMintingFee, + minimumMintingFee, + minimumTrade ); - await persistentStorage.setWhitelistedAddress(user, { from: owner }); // user is whitelisted + await persistentStorage.addMintingFeeBracket( + ether("50000"), + ether("0.003"), + { from: owner } + ); //0.3% + await persistentStorage.addMintingFeeBracket( + ether("100000"), + ether("0.002"), + { from: owner } + ); //0.2% await persistentStorage.setTokenSwapManager(tokenSwap, { from: owner }); // initialize token @@ -43,8 +60,9 @@ describe("CashPool", function() { ); await token.mintTokens(user, 10, { from: owner }); - kycVerifier = await KYCVerifier.new({ owner }); - await kycVerifier.initialize(persistentStorage.address); + kycVerifier = await KYCVerifier.new({ from: owner }); + await kycVerifier.initialize(owner); + await kycVerifier.setWhitelistedAddress(user, { from: owner }); // user is whitelisted // initialize cash pool this.contract = await CashPool.new({ from: owner }); diff --git a/test/CompositionCalculator.test.js b/test/CompositionCalculator.test.js index c713953..edbe397 100644 --- a/test/CompositionCalculator.test.js +++ b/test/CompositionCalculator.test.js @@ -2,7 +2,7 @@ const { accounts, contract } = require("@openzeppelin/test-environment"); const { expect } = require("chai"); const BigNumber = require("bignumber.js"); -const { time, expectRevert, ether } = require("@openzeppelin/test-helpers"); +const { time, expectRevert, ether, BN } = require("@openzeppelin/test-helpers"); const InverseToken = contract.fromArtifact("InverseToken"); const CompositionCalculator = contract.fromArtifact("CompositionCalculator"); @@ -25,8 +25,25 @@ describe("CompositionCalculator", function() { this.storage = await PersistentStorage.new({ from: owner }); const managementFee = ether("7"); const minRebalanceAmount = ether("1"); - await this.storage.initialize(owner, managementFee, minRebalanceAmount); - + const lastMintingFee = ether("0.001"); + const balancePrecision = 12; + const minimumMintingFee = ether("5"); + const minimumTrade = ether("50"); + await this.storage.initialize( + owner, + managementFee, + minRebalanceAmount, + balancePrecision, + lastMintingFee, + minimumMintingFee, + minimumTrade + ); + await this.storage.addMintingFeeBracket(ether("50000"), ether("0.003"), { + from: owner + }); //0.3% + await this.storage.addMintingFeeBracket(ether("100000"), ether("0.002"), { + from: owner + }); //0 this.token = await InverseToken.new({ from: owner }); await this.token.initialize( "InverseToken", @@ -47,7 +64,11 @@ describe("CompositionCalculator", function() { const price = getEth(1000); const expectedNetTokenValue = getEth(100000); - const netTokenValue = await this.contract.getNetTokenValue(cashPosition, balance, price); + const netTokenValue = await this.contract.getNetTokenValue( + cashPosition, + balance, + price + ); expect(getNumberWithDecimal(expectedNetTokenValue)).to.be.equal( getNumberWithDecimal(netTokenValue) @@ -152,7 +173,8 @@ describe("CompositionCalculator", function() { ] = Object.values(result); expect(changeInBalance.toNumber()).to.be.equal(0); - expect(new BigNumber(getNumberWithDecimal(endNetTokenValue)).eq(100000)).to.be.true; + expect(new BigNumber(getNumberWithDecimal(endNetTokenValue)).eq(100000)) + .to.be.true; }); }); @@ -164,7 +186,7 @@ describe("CompositionCalculator", function() { const lendingFee = getEth(2.5); const daysSinceLastRebalance = 1; const minRebalanceAmount = getEth(1); - const precision = '0'; + const precision = "0"; const result = await this.contract.calculatePCF( cashPosition, @@ -175,7 +197,7 @@ describe("CompositionCalculator", function() { minRebalanceAmount, precision ); - + const [ endNetTokenValue, endBalance, @@ -188,7 +210,8 @@ describe("CompositionCalculator", function() { expect(changeInBalance.toNumber()).to.be.equal(0); //netTokenValue should get smaller because of fee - expect(new BigNumber(getNumberWithDecimal(endNetTokenValue)).lt(100000)).to.be.true; + expect(new BigNumber(getNumberWithDecimal(endNetTokenValue)).lt(100000)) + .to.be.true; }); it("does change positive when smaller price", async function() { const balance = getEth(100); @@ -197,7 +220,7 @@ describe("CompositionCalculator", function() { const lendingFee = getEth(2.5); const daysSinceLastRebalance = 1; const minRebalanceAmount = getEth(1); - const precision = '0'; + const precision = "0"; const result = await this.contract.calculatePCF( cashPosition, @@ -216,14 +239,15 @@ describe("CompositionCalculator", function() { changeInBalance, isChangeInBalanceNeg ] = Object.values(result); - + expect(getNumberWithDecimal(changeInBalance)).to.be.equal( "22.215372907153729022" ); expect(getNumberWithDecimal(feeInFiat)).to.be.equal( "6.164383561643880000" ); - expect(new BigNumber(getNumberWithDecimal(endNetTokenValue)).gt(100000)).to.be.true; + expect(new BigNumber(getNumberWithDecimal(endNetTokenValue)).gt(100000)) + .to.be.true; expect(new BigNumber(endBalance).gt(balance)).to.be.true; expect(new BigNumber(endCashPosition).gt(cashPosition)).to.be.true; expect(isChangeInBalanceNeg).to.be.false; @@ -235,7 +259,7 @@ describe("CompositionCalculator", function() { const lendingFee = getEth(2.5); const daysSinceLastRebalance = 1; const minRebalanceAmount = getEth(1); - const precision = '10'; + const precision = "10"; const result = await this.contract.calculatePCF( cashPosition, @@ -261,7 +285,8 @@ describe("CompositionCalculator", function() { expect(getNumberWithDecimal(feeInFiat)).to.be.equal( "6.164383561643880000" ); - expect(new BigNumber(getNumberWithDecimal(endNetTokenValue)).gt(100000)).to.be.true; + expect(new BigNumber(getNumberWithDecimal(endNetTokenValue)).gt(100000)) + .to.be.true; expect(new BigNumber(endBalance).gt(balance)).to.be.true; expect(new BigNumber(endCashPosition).gt(cashPosition)).to.be.true; expect(isChangeInBalanceNeg).to.be.false; @@ -273,7 +298,7 @@ describe("CompositionCalculator", function() { const lendingFee = getEth(2.5); const daysSinceLastRebalance = 1; const minRebalanceAmount = getEth(1); - const precision = '0'; + const precision = "0"; const result = await this.contract.calculatePCF( cashPosition, @@ -299,15 +324,16 @@ describe("CompositionCalculator", function() { expect(getNumberWithDecimal(feeInFiat)).to.be.equal( "7.534246575342520000" ); - expect(new BigNumber(getNumberWithDecimal(endNetTokenValue)).lt(100000)).to.be.true; + expect(new BigNumber(getNumberWithDecimal(endNetTokenValue)).lt(100000)) + .to.be.true; expect(new BigNumber(endBalance).lt(balance)).to.be.true; expect(new BigNumber(endCashPosition).lt(cashPosition)).to.be.true; expect(isChangeInBalanceNeg).to.be.true; }); }); - describe("#getTokenAmountCreatedByCash", function () { - it("does give correct token amount for unbalanced product with lower spot price.", async function () { + describe("#getTokenAmountCreatedByCash", function() { + it("does give correct token amount for unbalanced product with lower spot price.", async function() { //2000+1*900 = (1200 + 800)-1*800 const balance = getEth(1); const cashPosition = getEth(2000); @@ -316,12 +342,19 @@ describe("CompositionCalculator", function() { const cash = getEth(1200); const spot = getEth(800); - - const tokenCreated = await this.contract.getTokenAmountCreatedByCash(cashPosition, balance, totalTokenSupply, cash, spot); - expect(getNumberWithDecimal(tokenCreated)).to.be.equal(getNumberWithDecimal(totalTokenSupply)); + const tokenCreated = await this.contract.getTokenAmountCreatedByCash( + cashPosition, + balance, + totalTokenSupply, + cash, + spot + ); + expect(getNumberWithDecimal(tokenCreated)).to.be.equal( + getNumberWithDecimal(totalTokenSupply) + ); }); - it("does give correct token amount for rebalanced product with lower spot price.", async function () { + it("does give correct token amount for rebalanced product with lower spot price.", async function() { //2000 - 1 * 1000 = (1100 + 900) - 1 * 1000 const balance = getEth(1); const cashPosition = getEth(2000); @@ -330,12 +363,19 @@ describe("CompositionCalculator", function() { const cash = getEth(1100); const spot = getEth(900); - - const tokenCreated = await this.contract.getTokenAmountCreatedByCash(cashPosition, balance, totalTokenSupply, cash, spot); - expect(getNumberWithDecimal(tokenCreated)).to.be.equal(getNumberWithDecimal(totalTokenSupply)); + const tokenCreated = await this.contract.getTokenAmountCreatedByCash( + cashPosition, + balance, + totalTokenSupply, + cash, + spot + ); + expect(getNumberWithDecimal(tokenCreated)).to.be.equal( + getNumberWithDecimal(totalTokenSupply) + ); }); - it("does give correct token amount for rebalanced product with higher spot price.", async function () { + it("does give correct token amount for rebalanced product with higher spot price.", async function() { //2000 - 1 * 1000 = (900 + 1100) - 1 * 1000 const balance = getEth(1); const cashPosition = getEth(2000); @@ -344,11 +384,18 @@ describe("CompositionCalculator", function() { const cash = getEth(900); const spot = getEth(1100); - - const tokenCreated = await this.contract.getTokenAmountCreatedByCash(cashPosition, balance, totalTokenSupply, cash, spot); - expect(getNumberWithDecimal(tokenCreated)).to.be.equal(getNumberWithDecimal(totalTokenSupply)); + const tokenCreated = await this.contract.getTokenAmountCreatedByCash( + cashPosition, + balance, + totalTokenSupply, + cash, + spot + ); + expect(getNumberWithDecimal(tokenCreated)).to.be.equal( + getNumberWithDecimal(totalTokenSupply) + ); }); - it("does give correct token amount for unbalanced product with higher spot price.", async function () { + it("does give correct token amount for unbalanced product with higher spot price.", async function() { //2000+1*900 = (900 + 1100)-1*900 const balance = getEth(1); const cashPosition = getEth(2000); @@ -357,11 +404,19 @@ describe("CompositionCalculator", function() { const cash = getEth(900); const spot = getEth(1100); - const tokenCreated = await this.contract.getTokenAmountCreatedByCash(cashPosition, balance, totalTokenSupply, cash, spot); - expect(getNumberWithDecimal(tokenCreated)).to.be.equal(getNumberWithDecimal(totalTokenSupply)); + const tokenCreated = await this.contract.getTokenAmountCreatedByCash( + cashPosition, + balance, + totalTokenSupply, + cash, + spot + ); + expect(getNumberWithDecimal(tokenCreated)).to.be.equal( + getNumberWithDecimal(totalTokenSupply) + ); }); - it("does give correct token amount for unbalanced product with same spot price.", async function () { + it("does give correct token amount for unbalanced product with same spot price.", async function() { //2000+1*900 = (1100 + 900)-1*900 const balance = getEth(1); const cashPosition = getEth(2000); @@ -370,12 +425,19 @@ describe("CompositionCalculator", function() { const cash = getEth(2200); const spot = getEth(900); - - const tokenCreated = await this.contract.getTokenAmountCreatedByCash(cashPosition, balance, totalTokenSupply, cash, spot); - expect(getNumberWithDecimal(tokenCreated)).to.be.equal(getNumberWithDecimal(getEth(2))); + const tokenCreated = await this.contract.getTokenAmountCreatedByCash( + cashPosition, + balance, + totalTokenSupply, + cash, + spot + ); + expect(getNumberWithDecimal(tokenCreated)).to.be.equal( + getNumberWithDecimal(getEth(2)) + ); }); - it("does give correct token amount for rebalanced product with same spot price.", async function () { + it("does give correct token amount for rebalanced product with same spot price.", async function() { //2000+1*1000 = (1000 + 1000)-1*1000 const balance = getEth(1); const cashPosition = getEth(2000); @@ -384,15 +446,21 @@ describe("CompositionCalculator", function() { const cash = getEth(2000); const spot = getEth(1000); - - const tokenCreated = await this.contract.getTokenAmountCreatedByCash(cashPosition, balance, totalTokenSupply, cash, spot); - expect(getNumberWithDecimal(tokenCreated)).to.be.equal(getNumberWithDecimal(getEth(2))); + const tokenCreated = await this.contract.getTokenAmountCreatedByCash( + cashPosition, + balance, + totalTokenSupply, + cash, + spot + ); + expect(getNumberWithDecimal(tokenCreated)).to.be.equal( + getNumberWithDecimal(getEth(2)) + ); }); }); - - describe("#getCashAmountCreatedByToken", function () { - it("does give correct cash payed for lower spot price.", async function () { + describe("#getCashAmountCreatedByToken", function() { + it("does give correct cash payed for lower spot price.", async function() { //2000-1*800 = 1200 const balance = getEth(1); const cashPosition = getEth(2000); @@ -401,11 +469,19 @@ describe("CompositionCalculator", function() { const tokenAmount = getEth(1); const spot = getEth(800); - const cashFromTokenRedeem = await this.contract.getCashAmountCreatedByToken(cashPosition, balance, totalTokenSupply, tokenAmount, spot); + const cashFromTokenRedeem = await this.contract.getCashAmountCreatedByToken( + cashPosition, + balance, + totalTokenSupply, + tokenAmount, + spot + ); - expect(getNumberWithDecimal(cashFromTokenRedeem)).to.be.equal(getNumberWithDecimal(cashPosition - spot)); + expect(getNumberWithDecimal(cashFromTokenRedeem)).to.be.equal( + getNumberWithDecimal(cashPosition - spot) + ); }); - it("does give correct cash payed for higher spot price.", async function () { + it("does give correct cash payed for higher spot price.", async function() { //2000-1*1200 = 800 const balance = getEth(1); const cashPosition = getEth(2000); @@ -414,12 +490,20 @@ describe("CompositionCalculator", function() { const tokenAmount = getEth(1); const spot = getEth(1200); - const cashFromTokenRedeem = await this.contract.getCashAmountCreatedByToken(cashPosition, balance, totalTokenSupply, tokenAmount, spot); + const cashFromTokenRedeem = await this.contract.getCashAmountCreatedByToken( + cashPosition, + balance, + totalTokenSupply, + tokenAmount, + spot + ); - expect(getNumberWithDecimal(cashFromTokenRedeem)).to.be.equal(getNumberWithDecimal(cashPosition - spot)); + expect(getNumberWithDecimal(cashFromTokenRedeem)).to.be.equal( + getNumberWithDecimal(cashPosition - spot) + ); }); - it("does give correct cash payed for balanced spot price.", async function () { + it("does give correct cash payed for balanced spot price.", async function() { //2000-1*1000 = 1000 const balance = getEth(1); const cashPosition = getEth(2000); @@ -428,9 +512,17 @@ describe("CompositionCalculator", function() { const tokenAmount = getEth(1); const spot = getEth(1000); - const cashFromTokenRedeem = await this.contract.getCashAmountCreatedByToken(cashPosition, balance, totalTokenSupply, tokenAmount, spot); + const cashFromTokenRedeem = await this.contract.getCashAmountCreatedByToken( + cashPosition, + balance, + totalTokenSupply, + tokenAmount, + spot + ); - expect(getNumberWithDecimal(cashFromTokenRedeem)).to.be.equal(getNumberWithDecimal(cashPosition - spot)); + expect(getNumberWithDecimal(cashFromTokenRedeem)).to.be.equal( + getNumberWithDecimal(cashPosition - spot) + ); }); }); describe("#getCurrentNetTokenValue", function() { @@ -485,26 +577,46 @@ describe("CompositionCalculator", function() { await this.storage.setAccounting(price, cashPosition, balance, fee, { from: owner }); - const cash = getEth(1200); + const cash = getEth(2400); const spot = getEth(800); const tokenCreated = await this.contract.getCurrentTokenAmountCreatedByCash( cash, - spot + spot, + 0 ); expect(getNumberWithDecimal(tokenCreated)).to.be.equal( getNumberWithDecimal( - totalTokenSupply.times(percentageMinusCreationRedemptionFee) + totalTokenSupply.times(2).times(percentageMinusCreationRedemptionFee) ) ); }); + it("does give correct token amount for unbalanced product with lower spot price.", async function() { + //2000+1*900 = (1200 + 800)-1*800 + const balance = getEth(1); + const cashPosition = getEth(2000); + + await this.storage.setAccounting(price, cashPosition, balance, fee, { + from: owner + }); + const cash = getEth(1205); + const spot = getEth(800); + const tokenCreated = await this.contract.getCurrentTokenAmountCreatedByCash( + cash, + spot, + 0 + ); + expect(getNumberWithDecimal(tokenCreated)).to.be.equal( + getNumberWithDecimal(totalTokenSupply) + ); + }); it("does give correct token amount for rebalanced product with lower spot price.", async function() { //2000 - 1 * 1000 = (1100 + 900) - 1 * 1000 const balance = getEth(1); const cashPosition = getEth(2000); - const cash = getEth(1100); + const cash = getEth(2200); const spot = getEth(900); await this.storage.setAccounting(price, cashPosition, balance, fee, { from: owner @@ -512,11 +624,12 @@ describe("CompositionCalculator", function() { const tokenCreated = await this.contract.getCurrentTokenAmountCreatedByCash( cash, - spot + spot, + 0 ); expect(getNumberWithDecimal(tokenCreated)).to.be.equal( getNumberWithDecimal( - totalTokenSupply.times(percentageMinusCreationRedemptionFee) + totalTokenSupply.times(2).times(percentageMinusCreationRedemptionFee) ) ); }); @@ -527,7 +640,7 @@ describe("CompositionCalculator", function() { const cashPosition = getEth(2000); const totalTokenSupply = getEth(1); - const cash = getEth(900); + const cash = getEth(1800); const spot = getEth(1100); await this.storage.setAccounting(price, cashPosition, balance, fee, { from: owner @@ -535,11 +648,12 @@ describe("CompositionCalculator", function() { const tokenCreated = await this.contract.getCurrentTokenAmountCreatedByCash( cash, - spot + spot, + 0 ); expect(getNumberWithDecimal(tokenCreated)).to.be.equal( getNumberWithDecimal( - totalTokenSupply.times(percentageMinusCreationRedemptionFee) + totalTokenSupply.times(2).times(percentageMinusCreationRedemptionFee) ) ); }); @@ -549,7 +663,7 @@ describe("CompositionCalculator", function() { const cashPosition = getEth(2000); const totalTokenSupply = getEth(1); - const cash = getEth(900); + const cash = getEth(1800); const spot = getEth(1100); await this.storage.setAccounting(price, cashPosition, balance, fee, { from: owner @@ -557,11 +671,12 @@ describe("CompositionCalculator", function() { const tokenCreated = await this.contract.getCurrentTokenAmountCreatedByCash( cash, - spot + spot, + 0 ); expect(getNumberWithDecimal(tokenCreated)).to.be.equal( getNumberWithDecimal( - totalTokenSupply.times(percentageMinusCreationRedemptionFee) + totalTokenSupply.times(2).times(percentageMinusCreationRedemptionFee) ) ); }); @@ -580,7 +695,8 @@ describe("CompositionCalculator", function() { const tokenCreated = await this.contract.getCurrentTokenAmountCreatedByCash( cash, - spot + spot, + 0 ); expect(getNumberWithDecimal(tokenCreated)).to.be.equal( getNumberWithDecimal( @@ -603,7 +719,8 @@ describe("CompositionCalculator", function() { const tokenCreated = await this.contract.getCurrentTokenAmountCreatedByCash( cash, - spot + spot, + 0 ); expect(getNumberWithDecimal(tokenCreated)).to.be.equal( getNumberWithDecimal( @@ -634,14 +751,13 @@ describe("CompositionCalculator", function() { const cashFromTokenRedeem = await this.contract.getCurrentCashAmountCreatedByToken( tokenAmount, - spot + spot, + 0 ); expect(getNumberWithDecimal(cashFromTokenRedeem)).to.be.equal( getNumberWithDecimal( - new BigNumber(cashPosition - spot).times( - percentageMinusCreationRedemptionFee - ) + new BigNumber(cashPosition - spot).minus(getEth(5)) ) ); }); @@ -658,14 +774,13 @@ describe("CompositionCalculator", function() { const cashFromTokenRedeem = await this.contract.getCurrentCashAmountCreatedByToken( tokenAmount, - spot + spot, + 0 ); expect(getNumberWithDecimal(cashFromTokenRedeem)).to.be.equal( getNumberWithDecimal( - new BigNumber(cashPosition - spot).times( - percentageMinusCreationRedemptionFee - ) + new BigNumber(cashPosition - spot).minus(getEth(5)) ) ); }); @@ -683,14 +798,13 @@ describe("CompositionCalculator", function() { const cashFromTokenRedeem = await this.contract.getCurrentCashAmountCreatedByToken( tokenAmount, - spot + spot, + 0 ); expect(getNumberWithDecimal(cashFromTokenRedeem)).to.be.equal( getNumberWithDecimal( - new BigNumber(cashPosition - spot).times( - percentageMinusCreationRedemptionFee - ) + new BigNumber(cashPosition - spot).minus(getEth(5)) ) ); }); @@ -740,4 +854,76 @@ describe("CompositionCalculator", function() { expect(result.toString()).to.be.equal(cashPositionPerTokenUnit); }); }); + describe("#removeCurrentMintingFeeFromCash", function() { + it("Should remove minimumMintingFee when bigger then creationFee.", async function() { + const cash = getEth(10); + const resultingCash = getEth(5); + const cashPositionPerTokenUnit = getEth(4).toFixed(); + await this.storage.setAccounting(1, cashPositionPerTokenUnit, 3, 4, { + from: owner + }); + await this.token.mintTokens(owner, getEth(1), { + from: owner + }); + + const result = await this.contract.removeCurrentMintingFeeFromCash(cash); + expect(result).to.be.bignumber.equal(String(resultingCash)); + }); + it("Should remove creationFee when bigger then minimumMintingFee.", async function() { + const cash = getEth(10000); + const resultingCash = ether(new BigNumber(10000).times(0.997).toString()); + const cashPositionPerTokenUnit = getEth(4).toFixed(); + await this.storage.setAccounting(1, cashPositionPerTokenUnit, 3, 4, { + from: owner + }); + await this.token.mintTokens(owner, getEth(1), { + from: owner + }); + + const result = await this.contract.removeCurrentMintingFeeFromCash(cash); + expect(result).to.be.bignumber.equal(new BN(resultingCash)); + }); + }); + describe("#removeMintingFeeFromCash", function() { + it("Should remove minimumMintingFee when bigger then creationFee.", async function() { + const cash = getEth(10); + const creationFee = getEth(0.001); + const minimumMintingFee = getEth(5); + const cashPositionPerTokenUnit = getEth(4).toFixed(); + await this.storage.setAccounting(1, cashPositionPerTokenUnit, 3, 4, { + from: owner + }); + await this.token.mintTokens(owner, getEth(1), { + from: owner + }); + + const result = await this.contract.removeMintingFeeFromCash( + cash, + creationFee, + minimumMintingFee + ); + expect(result).to.be.bignumber.equal(String(getEth(5))); + }); + it("Should remove creationFee when bigger then minimumMintingFee.", async function() { + const cash = getEth(10000); + const creationFee = getEth(0.003); + const minimumMintingFee = getEth(5); + const cashPositionPerTokenUnit = getEth(4).toFixed(); + const resultingCash = ether(new BigNumber(10000).times(0.997).toString()); + + await this.storage.setAccounting(1, cashPositionPerTokenUnit, 3, 4, { + from: owner + }); + await this.token.mintTokens(owner, getEth(1), { + from: owner + }); + + const result = await this.contract.removeMintingFeeFromCash( + cash, + creationFee, + minimumMintingFee + ); + expect(result).to.be.bignumber.equal(String(resultingCash)); + }); + }); }); diff --git a/test/KYCVerifier.test.js b/test/KYCVerifier.test.js index b2cbe52..b856fa5 100644 --- a/test/KYCVerifier.test.js +++ b/test/KYCVerifier.test.js @@ -1,33 +1,125 @@ const { accounts, contract } = require("@openzeppelin/test-environment"); const { expect } = require("chai"); -const { ether } = require("@openzeppelin/test-helpers"); +const { expectRevert } = require("@openzeppelin/test-helpers"); -const PersistentStorage = contract.fromArtifact("PersistentStorage"); const KYCVerifier = contract.fromArtifact("KYCVerifier"); describe("KYCVerifier", function() { - const [owner, listedUser, unlistedUser] = accounts; + const [owner, bridge, listedUser, unlistedUser, notOwner] = accounts; beforeEach(async function() { - const persistentStorage = await PersistentStorage.new({ from: owner }); - const managementFee = ether("7"); - const minRebalanceAmount = ether("1"); - await persistentStorage.initialize(owner, managementFee, minRebalanceAmount); - await persistentStorage.setWhitelistedAddress(listedUser, { from: owner }); - this.contract = await KYCVerifier.new({ from: owner }); - await this.contract.initialize(persistentStorage.address); + await this.contract.initialize(owner); + await this.contract.setWhitelistedAddress(listedUser, { from: owner }); // listedUser is whitelisted + await this.contract.setBridge(bridge, { from: owner }); }); describe("#isAddressWhitelisted", function() { it("checks whether an address is whitelisted", async function() { const isListedUser = await this.contract.isAddressWhitelisted(listedUser); - expect(isListedUser).to.be.true + expect(isListedUser).to.be.true; }); it("tells whether an address is NOT whitelisted", async function() { - const isListedUser = await this.contract.isAddressWhitelisted(unlistedUser); - expect(isListedUser).to.be.false + const isListedUser = await this.contract.isAddressWhitelisted( + unlistedUser + ); + expect(isListedUser).to.be.false; + }); + }); + + describe("#setWhitelistedAddress", function() { + it("does not allow a non owner to add a whitelisted address", async function() { + await expectRevert( + this.contract.setWhitelistedAddress(notOwner, { from: notOwner }), + "caller is not the owner or bridge" + ); + }); + + it("does not allow empty address to be whitelisted", async function() { + await expectRevert( + this.contract.setWhitelistedAddress( + "0x0000000000000000000000000000000000000000", + { from: owner } + ), + "adddress must not be empty" + ); + }); + + it("allows bridge to whitelisted address", async function() { + await this.contract.setWhitelistedAddress(notOwner, { from: bridge }); + + const isAddressAdded = await this.contract.whitelistedAddresses(notOwner); + expect(isAddressAdded).to.be.true; + await this.contract.removeWhitelistedAddress(notOwner, { from: bridge }); + }); + + it("adds whitelisted address", async function() { + await this.contract.setWhitelistedAddress(notOwner, { from: owner }); + + const isAddressAdded = await this.contract.whitelistedAddresses(notOwner); + expect(isAddressAdded).to.be.true; + }); + }); + + describe("#removeWhitelistedAddress", function() { + beforeEach(async function() { + await this.contract.setWhitelistedAddress(notOwner, { from: owner }); + }); + + it("prohibits a non owner from removing whitelisted user", async function() { + await expectRevert( + this.contract.removeWhitelistedAddress(notOwner, { + from: notOwner, + }), + "caller is not the owner or bridge" + ); + }); + + it("does not allow an address to be removed which has not been added", async function() { + await expectRevert( + this.contract.removeWhitelistedAddress(unlistedUser, { from: owner }), + "address must be added to be removed allowed" + ); + }); + + it("removes the whitelisted user", async function() { + await this.contract.removeWhitelistedAddress(notOwner, { + from: owner, + }); + + const isAddressAdded = await this.contract.whitelistedAddresses(notOwner); + expect(isAddressAdded).to.be.false; + }); + }); + + describe("#updateWhitelistedAddress", function() { + beforeEach(async function() { + await this.contract.setWhitelistedAddress(notOwner, { from: owner }); + }); + + it("prohibits a non owner from updating whitelisted address", async function() { + await expectRevert( + this.contract.updateWhitelistedAddress(notOwner, unlistedUser, { + from: notOwner, + }), + "caller is not the owner or bridge" + ); + }); + + it("updates an whitelisted user", async function() { + await this.contract.updateWhitelistedAddress(notOwner, unlistedUser, { + from: owner, + }); + + const isAddressAdded = await this.contract.whitelistedAddresses( + unlistedUser + ); + expect(isAddressAdded).to.be.true; + const isAddressAdded2 = await this.contract.whitelistedAddresses( + notOwner + ); + expect(isAddressAdded2).to.be.false; }); }); }); diff --git a/test/Modifier.test.js b/test/Modifier.test.js index b4b1e4c..d589abd 100644 --- a/test/Modifier.test.js +++ b/test/Modifier.test.js @@ -27,7 +27,25 @@ describe("Modifier", function() { this.storage = await PersistentStorage.new({ from: owner }); const managementFee = ether("7"); const minRebalanceAmount = ether("1"); - await this.storage.initialize(owner, managementFee, minRebalanceAmount); + const lastMintingFee = ether("0.001"); + const balancePrecision = 12; + const minimumMintingFee = ether("5"); + const minimumTrade = ether("50"); + await this.storage.initialize( + owner, + managementFee, + minRebalanceAmount, + balancePrecision, + lastMintingFee, + minimumMintingFee, + minimumTrade + ); + await this.storage.addMintingFeeBracket(ether("50000"), ether("0.003"), { + from: owner + }); //0.3% + await this.storage.addMintingFeeBracket(ether("100000"), ether("0.002"), { + from: owner + }); //0 // Inverse Token + Stablecoin Initialize this.inverseToken = await ERC20WithMinting.new({ from: owner }); await this.inverseToken.initialize( @@ -49,7 +67,7 @@ describe("Modifier", function() { // Initialize KYC Verifier this.kycVerifier = await KYCVerifier.new({ from: owner }); - await this.kycVerifier.initialize(this.storage.address); + await this.kycVerifier.initialize(owner); // Deploy Cash Pool this.cashPool = await CashPool.new({ from: owner }); @@ -74,7 +92,6 @@ describe("Modifier", function() { this.tokenSwapManager = await TokenSwapManager.new({ from: owner }); await this.tokenSwapManager.initialize( owner, - this.stableCoin.address, this.inverseToken.address, this.cashPool.address, this.compositionCalculator.address @@ -94,10 +111,10 @@ describe("Modifier", function() { const price = getEth(1000); const lendingFee = getEth(0); const tokensGiven = getEth(10); - const tokensRecieved = getEth(0.00000997); + const tokensRecieved = getEth(0.000005); beforeEach(async function() { - await this.storage.setWhitelistedAddress(user, { from: owner }); + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); await this.inverseToken.mintTokens(owner, totalTokenSupply, { from: owner }); @@ -119,7 +136,9 @@ describe("Modifier", function() { tokensRecieved, // Tokens Recieved 2, // Avg Blended Fee price, - user, // Whitelisted User + user, // Whitelisted User, + this.stableCoin.address, // Stablecoin address + 0, { from: bridge } // Sent From Bridge ); @@ -127,7 +146,8 @@ describe("Modifier", function() { orderType: "CREATE", whitelistedAddress: user, tokensGiven: tokensGiven.toString(), - tokensRecieved: tokensRecieved.toString() + tokensRecieved: tokensRecieved.toString(), + stablecoin: this.stableCoin.address }); }); @@ -140,6 +160,8 @@ describe("Modifier", function() { 2, price, user, + this.stableCoin.address, + 0, { from: user } ), "caller is not the owner or bridge" @@ -156,6 +178,8 @@ describe("Modifier", function() { 2, price, user, + this.stableCoin.address, + 0, { from: bridge } ), "contract is paused" @@ -172,6 +196,8 @@ describe("Modifier", function() { 2, price, user, + this.stableCoin.address, + 0, { from: bridge } ); }); @@ -186,6 +212,8 @@ describe("Modifier", function() { 2, price, user, + this.stableCoin.address, + 0, { from: bridge } ), "contract is shutdown" @@ -195,7 +223,7 @@ describe("Modifier", function() { describe("#inverseToken", function() { beforeEach(async function() { - await this.storage.setWhitelistedAddress(user, { from: owner }); + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); }); it("mints token from owner", async function() { @@ -227,7 +255,7 @@ describe("Modifier", function() { describe("#cashPool", function() { beforeEach(async function() { - await this.storage.setWhitelistedAddress(user, { from: owner }); + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); }); it("allows owner to move tokens from pool", async function() { @@ -251,12 +279,9 @@ describe("Modifier", function() { }); await expectRevert( - this.cashPool.moveTokenfromPool( - this.inverseToken.address, - bridge, - 5, - { from: user } - ), + this.cashPool.moveTokenfromPool(this.inverseToken.address, bridge, 5, { + from: user + }), "caller is not the owner or token swap manager" ); }); diff --git a/test/PersistentStorage.test.js b/test/PersistentStorage.test.js index 9bb9f46..e00f2fc 100644 --- a/test/PersistentStorage.test.js +++ b/test/PersistentStorage.test.js @@ -24,99 +24,32 @@ const getDateForBlockTime = async () => { return year * 10000 + month * 100 + day; }; describe("PersistentStorage", function() { - const [owner, notOwner, notListed] = accounts; + const [owner, notOwner, notListed, bridge] = accounts; const managementFee = ether("7"); const minRebalanceAmount = ether("1"); + const lastMintingFee = ether("0.001"); + const balancePrecision = 12; + const minimumMintingFee = ether("5"); + const minimumTrade = ether("50"); beforeEach(async function() { this.contract = await PersistentStorage.new({ from: owner }); - await this.contract.initialize(owner, managementFee, minRebalanceAmount); - }); - - describe("#setWhitelistedAddress", function() { - it("does not allow a non owner to add a whitelisted address", async function() { - await expectRevert( - this.contract.setWhitelistedAddress(notOwner, { from: notOwner }), - "Ownable: caller is not the owner" - ); - }); - - it("does not allow empty address to be whitelisted", async function() { - await expectRevert( - this.contract.setWhitelistedAddress( - "0x0000000000000000000000000000000000000000", - { from: owner } - ), - "adddress must not be empty" - ); - }); - - it("adds whitelisted address", async function() { - await this.contract.setWhitelistedAddress(notOwner, { from: owner }); - - const isAddressAdded = await this.contract.whitelistedAddresses(notOwner); - expect(isAddressAdded).to.be.true; - }); - }); - - describe("#removeWhitelistedAddress", function() { - beforeEach(async function() { - await this.contract.setWhitelistedAddress(notOwner, { from: owner }); - }); - - it("prohibits a non owner from removing whitelisted user", async function() { - await expectRevert( - this.contract.removeWhitelistedAddress(notOwner, { - from: notOwner - }), - "Ownable: caller is not the owner" - ); - }); - - it("does not allow an address to be removed which has not been added", async function() { - await expectRevert( - this.contract.removeWhitelistedAddress(notListed, { from: owner }), - "address must be added to be removed allowed" - ); - }); - - it("removes the whitelisted user", async function() { - await this.contract.removeWhitelistedAddress(notOwner, { - from: owner - }); - - const isAddressAdded = await this.contract.whitelistedAddresses(notOwner); - expect(isAddressAdded).to.be.false; - }); - }); - - describe("#updateWhitelistedAddress", function() { - beforeEach(async function() { - await this.contract.setWhitelistedAddress(notOwner, { from: owner }); - }); - - it("prohibits a non owner from updating whitelisted address", async function() { - await expectRevert( - this.contract.updateWhitelistedAddress(notOwner, notListed, { - from: notOwner - }), - "Ownable: caller is not the owner" - ); - }); - - it("updates an whitelisted user", async function() { - await this.contract.updateWhitelistedAddress(notOwner, notListed, { - from: owner - }); - - const isAddressAdded = await this.contract.whitelistedAddresses( - notListed - ); - expect(isAddressAdded).to.be.true; - const isAddressAdded2 = await this.contract.whitelistedAddresses( - notOwner - ); - expect(isAddressAdded2).to.be.false; - }); + await this.contract.initialize( + owner, + managementFee, + minRebalanceAmount, + balancePrecision, + lastMintingFee, + minimumMintingFee, + minimumTrade + ); + + await this.contract.addMintingFeeBracket(ether("50000"), ether("0.003"), { + from: owner + }); //0.3% + await this.contract.addMintingFeeBracket(ether("100000"), ether("0.002"), { + from: owner + }); //0 + await this.contract.setBridge(bridge, { from: owner }); }); describe("Accounting getter and setter", function() { diff --git a/test/TokenSwapManager.test.js b/test/TokenSwapManager.test.js index 1cc57fc..923c56f 100644 --- a/test/TokenSwapManager.test.js +++ b/test/TokenSwapManager.test.js @@ -24,7 +24,7 @@ const getUsdc = num => new BigNumber(num).times(new BigNumber("10").pow("6")).integerValue(); const normalizeUsdc = num => - new BigNumber(num).times(new BigNumber("10").pow("-12")).integerValue(); + new BigNumber(num).dividedBy(new BigNumber("10").pow("12")).integerValue(); describe("TokenSwapManager", function() { const [owner, user, bridge] = accounts; @@ -35,8 +35,25 @@ describe("TokenSwapManager", function() { this.storage = await PersistentStorage.new({ from: owner }); const managementFee = ether("7"); const minRebalanceAmount = ether("1"); - await this.storage.initialize(owner, managementFee, minRebalanceAmount); - + const lastMintingFee = ether("0.001"); + const balancePrecision = 12; + const minimumMintingFee = ether("5"); + const minimumTrade = ether("50"); + await this.storage.initialize( + owner, + managementFee, + minRebalanceAmount, + balancePrecision, + lastMintingFee, + minimumMintingFee, + minimumTrade + ); + await this.storage.addMintingFeeBracket(ether("50000"), ether("0.003"), { + from: owner + }); //0.3% + await this.storage.addMintingFeeBracket(ether("100000"), ether("0.002"), { + from: owner + }); //0.2% // Inverse Token + Stablecoin Initialize this.inverseToken = await ERC20WithMinting.new({ from: owner }); await this.inverseToken.initialize( @@ -47,18 +64,18 @@ describe("TokenSwapManager", function() { owner ); - this.stableCoin = await ERC20WithMinting.new({ from: owner }); - await this.stableCoin.initialize( + this.stablecoin = await ERC20WithMinting.new({ from: owner }); + await this.stablecoin.initialize( "Stablecoin", "USDC", - 18, + 6, this.storage.address, owner ); // Initialize KYC Verifier this.kycVerifier = await KYCVerifier.new({ from: owner }); - await this.kycVerifier.initialize(this.storage.address); + await this.kycVerifier.initialize(owner); // Initialize Cash Pool this.cashPool = await CashPool.new({ from: owner }); @@ -83,7 +100,6 @@ describe("TokenSwapManager", function() { this.tokenSwapManager = await TokenSwapManager.new({ from: owner }); await this.tokenSwapManager.initialize( owner, - this.stableCoin.address, this.inverseToken.address, this.cashPool.address, this.compositionCalculator.address @@ -103,10 +119,11 @@ describe("TokenSwapManager", function() { const price = getEth(1000); const lendingFee = getEth(0); const tokensGiven = getEth(10); - const tokensRecieved = getEth(0.00000997); + + const tokensRecieved = getEth(0.000005); beforeEach(async function() { - await this.storage.setWhitelistedAddress(user, { from: owner }); + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); await this.inverseToken.mintTokens(owner, totalTokenSupply, { from: owner }); @@ -127,6 +144,8 @@ describe("TokenSwapManager", function() { 2, // Avg Blended Fee price, user, // Whitelisted User + this.stablecoin.address, // Stablecoin Address + 0, { from: bridge } // Sent From Bridge ); }); @@ -136,7 +155,8 @@ describe("TokenSwapManager", function() { orderType: "CREATE", whitelistedAddress: user, tokensGiven: tokensGiven.toString(), - tokensRecieved: tokensRecieved.toString() + tokensRecieved: tokensRecieved.toString(), + stablecoin: this.stablecoin.address }); }); @@ -179,6 +199,8 @@ describe("TokenSwapManager", function() { 2, price, user, + this.stablecoin.address, // Stablecoin Address + 0, { from: bridge } ), "only whitelisted address may place orders" @@ -188,17 +210,26 @@ describe("TokenSwapManager", function() { it("throws error from trading engine: return user funds", async function() { const usdcAmount = getUsdc(10); const usdcAmountWithDecimals = getEth(10); - - await this.storage.setWhitelistedAddress(user, { from: owner }); - await this.stableCoin.mintTokens(this.cashPool.address, usdcAmount, { + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); + await this.stablecoin.mintTokens(this.cashPool.address, usdcAmount, { from: owner }); - await this.tokenSwapManager.createOrder(false, usdcAmountWithDecimals, usdcAmountWithDecimals, 2, price, user, { - from: bridge - }); + await this.tokenSwapManager.createOrder( + false, + usdcAmountWithDecimals, + usdcAmountWithDecimals, + 2, + price, + user, + this.stablecoin.address, // Stablecoin Address + 0, + { + from: bridge + } + ); - const userReturnedBalance = await this.stableCoin.balanceOf(user); - expect(userReturnedBalance.toNumber()).to.be.equal(usdcAmount.toNumber()); + const userReturnedBalance = await this.stablecoin.balanceOf(user); + expect(userReturnedBalance.toString()).to.be.equal(usdcAmount.toString()); }); }); @@ -206,15 +237,14 @@ describe("TokenSwapManager", function() { const cashPosition = getEth(2000); const balance = getEth(1); const totalTokenSupply = getEth(10); - const stableCoinsToMint = getEth(10000); + const stablecoinsToMint = getEth(10000); const price = getEth(1000); const spot = getEth(1200); const lendingFee = getEth(0); const tokensGiven = getEth(1); - const tokensRecieved = new BigNumber(cashPosition - spot).times(0.997); - + const tokensRecieved = new BigNumber(cashPosition - spot).minus(getEth(5)); beforeEach(async function() { - await this.storage.setWhitelistedAddress(user, { from: owner }); + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); await this.storage.setAccounting( price, cashPosition, @@ -224,6 +254,7 @@ describe("TokenSwapManager", function() { from: owner } ); + await this.inverseToken.mintTokens( this.cashPool.address, totalTokenSupply, @@ -232,9 +263,9 @@ describe("TokenSwapManager", function() { } ); - await this.stableCoin.mintTokens( + await this.stablecoin.mintTokens( this.cashPool.address, - stableCoinsToMint, + stablecoinsToMint, { from: owner } @@ -247,24 +278,25 @@ describe("TokenSwapManager", function() { 2, // Avg Blended Fee spot, user, // Whitelisted User + this.stablecoin.address, // Stablecoin Address + 0, { from: bridge } // Sent From Bridge ); }); it("emits successful order event", async function() { - expectEvent(this.receipt, "SuccessfulOrder", { orderType: "REDEEM", whitelistedAddress: user, tokensGiven: tokensGiven.toString(), - tokensRecieved: tokensRecieved.toString() + tokensRecieved: tokensRecieved.toString(), + stablecoin: this.stablecoin.address }); }); - it("successfully redeem after creation order", async function() { - const tokensSent = getEth(10); - const tokensCreated = getEth(0.00997); + const tokensSent = getEth(15); + const tokensCreated = getEth(0.01); await this.tokenSwapManager.createOrder( true, @@ -273,48 +305,51 @@ describe("TokenSwapManager", function() { 2, price, user, + this.stablecoin.address, // Stablecoin Address + 0, { from: bridge } ); const receipt = await this.tokenSwapManager.redeemOrder( true, - 100000000000, - 99700000000000, + tokensCreated, + getEth(5), 2, price, user, + this.stablecoin.address, // Stablecoin Address + 0, { from: bridge } ); expectEvent(receipt, "SuccessfulOrder", { orderType: "REDEEM", whitelistedAddress: user, - tokensGiven: "100000000000", - tokensRecieved: "99700000000000" + tokensGiven: String(tokensCreated), + tokensRecieved: String(getEth(5)), + stablecoin: this.stablecoin.address }); }); it("successfully burn tokens from cash pool", async function() { - const balance = await this.inverseToken.balanceOf( - this.cashPool.address - ); + const balance = await this.inverseToken.balanceOf(this.cashPool.address); expect(balance.toString()).to.be.equal( new BigNumber(totalTokenSupply).minus(tokensGiven).toString() ); }); }); - describe('#delayedRedemptionOrder', function() { + describe("#delayedRedemptionOrder", function() { const cashPosition = getEth(2000); const balance = getEth(1); const totalTokenSupply = getEth(10); - const stableCoinsToMint = getEth(10000); + const stablecoinsToMint = getUsdc(10000); const price = getEth(1000); const spot = getEth(1200); const lendingFee = getEth(0); const tokensGiven = getEth(1); - const tokensRecieved = new BigNumber(cashPosition - spot).times(0.997); + const tokensRecieved = new BigNumber(cashPosition - spot).minus(getEth(5)); beforeEach(async function() { - await this.storage.setWhitelistedAddress(user, { from: owner }); + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); await this.storage.setAccounting( price, cashPosition, @@ -339,9 +374,10 @@ describe("TokenSwapManager", function() { 2, // Avg Blended Fee spot, user, // Whitelisted User + this.stablecoin.address, // Stablecoin Address + 0, { from: bridge } // Sent From Bridge ); - }); it("executes redemption without settlement", async function() { @@ -349,43 +385,69 @@ describe("TokenSwapManager", function() { orderType: "REDEEM_NO_SETTLEMENT", whitelistedAddress: user, tokensGiven: tokensGiven.toString(), - tokensRecieved: tokensRecieved.toString() + tokensRecieved: tokensRecieved.toString(), + stablecoin: this.stablecoin.address }); - }); it("settles redemption at a later date", async function() { - await this.stableCoin.mintTokens( + await this.stablecoin.mintTokens( this.cashPool.address, - stableCoinsToMint, + stablecoinsToMint, { from: owner } ); - await this.tokenSwapManager.settleDelayedFunds(tokensRecieved, user, { from: bridge }); - const balance = await this.stableCoin.balanceOf(user); - const normalizedUSDC = normalizeUsdc(tokensRecieved) + await this.tokenSwapManager.settleDelayedFunds( + tokensRecieved, + user, + this.stablecoin.address, + { + from: bridge + } + ); + const balance = await this.stablecoin.balanceOf(user); + const normalizedUSDC = normalizeUsdc(tokensRecieved); expect(balance.toString()).to.be.equal(normalizedUSDC.toString()); }); - }); describe("#unsuccessfulRedemptionOrder", function() { it("throws error when user is not whitelisted", async function() { await expectRevert( - this.tokenSwapManager.redeemOrder(true, 10, 10, 2, 1000, user, { - from: bridge - }), + this.tokenSwapManager.redeemOrder( + true, + 10, + 10, + 2, + 1000, + user, + this.stablecoin.address, + 0, + { + from: bridge + } + ), "only whitelisted address may place orders" ); }); it("throws error from trading engine: return user funds", async function() { - await this.storage.setWhitelistedAddress(user, { from: owner }); + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); await this.inverseToken.mintTokens(this.cashPool.address, 10, { from: owner }); - await this.tokenSwapManager.redeemOrder(false, 10, 10, 2, 1000, user, { - from: bridge - }); + await this.tokenSwapManager.redeemOrder( + false, + 10, + 10, + 2, + 1000, + user, + this.stablecoin.address, + 0, + { + from: bridge + } + ); const userReturnedBalance = await this.inverseToken.balanceOf(user); expect(userReturnedBalance.toNumber()).to.be.equal(10); @@ -460,7 +522,7 @@ describe("TokenSwapManager", function() { lendingFee, 1, getEth(0), - 10 + 12 ); const endBalance = result[1]; @@ -507,7 +569,7 @@ describe("TokenSwapManager", function() { lendingFee, 1, getEth(0), - 10 + 12 ); const endBalance = result[1]; const endCashPosition = result[2]; @@ -555,7 +617,7 @@ describe("TokenSwapManager", function() { lendingFee, 1, getEth(0), - 10 + 12 ); const endBalance = result[1]; const endCashPosition = result[2]; @@ -674,7 +736,7 @@ describe("TokenSwapManager", function() { 0, 1, getEth(0), - 10 + 12 ); const endBalance = result[1]; const endCashPosition = result[2]; @@ -721,7 +783,7 @@ describe("TokenSwapManager", function() { 0, 1, getEth(0), - 10 + 12 ); const endBalance = result[1]; const endCashPosition = result[2];