diff --git a/contracts/Abstract/InterfaceStorage.sol b/contracts/Abstract/InterfaceStorage.sol deleted file mode 100644 index 54134e6..0000000 --- a/contracts/Abstract/InterfaceStorage.sol +++ /dev/null @@ -1,6 +0,0 @@ -pragma solidity ^0.5.0; - - -interface InterfaceStorage { - function whitelistedAddresses(address) external view returns (bool); -} diff --git a/contracts/README.md b/contracts/README.md index c1274de..cee0038 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -4,7 +4,7 @@ - Creation and Redemption - Daily Rebalance and Threshold Rebalance -- Composition PCF And Funding Rate [WIP] +- Composition PCF And Funding Rate ### List of Smart Contracts: diff --git a/contracts/TestStorageExample/Storage.sol b/contracts/TestStorageExample/Storage.sol deleted file mode 100644 index c97baf4..0000000 --- a/contracts/TestStorageExample/Storage.sol +++ /dev/null @@ -1,42 +0,0 @@ -pragma solidity ^0.5.0; - -import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; - - -contract Storage is Ownable { - uint256 public instrumentCounter; - mapping(string => uint256) instruments; - mapping(address => uint256[]) public allowedInstruments; - event AddInstrument(string addedInstrument); - - function getInstrumentIndex(string memory instrument) - public - view - returns (uint256) - { - return instruments[instrument]; - } - - function insertInstrument(string memory instrument) public onlyOwner { - require(instruments[instrument] == 0, "instrument already exists"); - instrumentCounter = instrumentCounter + 1; - instruments[instrument] = instrumentCounter; - emit AddInstrument(instrument); - } - - function addAllowedInstruments(address user, string memory instrument) - public - onlyOwner - { - require(instruments[instrument] != 0, "instrument does not exist"); - allowedInstruments[user].push(instruments[instrument]); - } - - function getUserAllowedInstruments(address user) - public - view - returns (uint256[] memory) - { - return allowedInstruments[user]; - } -} diff --git a/contracts/TestStorageExample/StorageProxy.sol b/contracts/TestStorageExample/StorageProxy.sol deleted file mode 100644 index d0163f6..0000000 --- a/contracts/TestStorageExample/StorageProxy.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity ^0.5.0; -// Taken from: https://blog.openzeppelin.com/smart-contract-upgradeability-using-eternal-storage/ - -import "@openzeppelin/upgrades/contracts/upgradeability/UpgradeabilityProxy.sol"; -import "./Storage.sol"; - - -contract StorageProxy is UpgradeabilityProxy, Storage {} diff --git a/contracts/TestStorageExample/StorageVersionUpgradeExample.sol b/contracts/TestStorageExample/StorageVersionUpgradeExample.sol deleted file mode 100644 index b216783..0000000 --- a/contracts/TestStorageExample/StorageVersionUpgradeExample.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity ^0.5.0; - -import "./UpgradeableStorage.sol"; - - -contract Storage_V0 is UpgradeableStorage { - function test_storage_v0() public view returns (uint256) { - return instrumentCounter; - } -} - - -contract Storage_V1 is UpgradeableStorage { - function test_storage_v1() public view returns (uint256) { - return instrumentCounter + 1; - } -} diff --git a/contracts/TestStorageExample/UpgradeableStorage.sol b/contracts/TestStorageExample/UpgradeableStorage.sol deleted file mode 100644 index dd4b36d..0000000 --- a/contracts/TestStorageExample/UpgradeableStorage.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity ^0.5.0; -// Taken from: https://blog.openzeppelin.com/smart-contract-upgradeability-using-eternal-storage/ - -import "@openzeppelin/upgrades/contracts/upgradeability/InitializableUpgradeabilityProxy.sol"; -import "./Storage.sol"; - - -contract UpgradeableStorage is InitializableUpgradeabilityProxy, Storage {} diff --git a/contracts/leverage-tokens/.keep b/contracts/leverage-tokens/.keep new file mode 100644 index 0000000..79c0014 --- /dev/null +++ b/contracts/leverage-tokens/.keep @@ -0,0 +1 @@ +Here will live leverage tokens smart contracts \ No newline at end of file diff --git a/contracts/leverage-tokens/Abstract/InterfaceCalculator.sol b/contracts/leverage-tokens/Abstract/InterfaceCalculator.sol new file mode 100644 index 0000000..f200482 --- /dev/null +++ b/contracts/leverage-tokens/Abstract/InterfaceCalculator.sol @@ -0,0 +1,28 @@ +pragma solidity ^0.5.0; + + +interface InterfaceCalculator { + function getTokensCreatedByCash( + uint256 mintingPrice, + uint256 cash, + uint256 gasFee + ) external view returns (uint256 tokensCreated); + + function getCashCreatedByTokens( + uint256 burningPrice, + uint256 elapsedTime, + uint256 tokens, + uint256 gasFee + ) external view returns (uint256 stablecoinRedeemed); + + function removeCurrentMintingFeeFromCash(uint256 _cash) + external + view + returns (uint256 cashAfterFee); + + function removeMintingFeeFromCash( + uint256 _cash, + uint256 _mintingFee, + uint256 _minimumMintingFee + ) external pure returns (uint256 cashAfterFee); +} diff --git a/contracts/leverage-tokens/Abstract/InterfaceStorageLeverage.sol b/contracts/leverage-tokens/Abstract/InterfaceStorageLeverage.sol new file mode 100644 index 0000000..9f955d4 --- /dev/null +++ b/contracts/leverage-tokens/Abstract/InterfaceStorageLeverage.sol @@ -0,0 +1,66 @@ +pragma solidity ^0.5.0; + + + +interface InterfaceStorageLeverage { + function whitelistedAddresses(address) external view returns (bool); + + function isPaused() external view returns (bool); + + function isShutdown() external view returns (bool); + + function tokenSwapManager() external view returns (address); + + function bridge() external view returns (address); + + function managementFee() external view returns (uint256); + + function getExecutionPrice() external view returns (uint256); + + function getMarkPrice() external view returns (uint256); + + function getNotional() external view returns (uint256); + + function getTokenValue() external view returns (uint256); + + function getFundingRate() external view returns (uint256); + + function getMintingFee(uint256 cash) external view returns (uint256); + + function minimumMintingFee() external view returns (uint256); + + function minRebalanceAmount() external view returns (uint8); + + function delayedRedemptionsByUser(address) external view returns (uint256); + + function setDelayedRedemptionsByUser( + uint256 amountToRedeem, + address whitelistedAddress + ) external; + + function setOrderByUser( + address whitelistedAddress, + string calldata orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice, + uint256 orderIndex, + bool overwrite + ) external; + + function setAccounting( + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) external; + + function setAccountingForLastActivityDay( + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) external; +} diff --git a/contracts/leverage-tokens/CalculatorLeverage.sol b/contracts/leverage-tokens/CalculatorLeverage.sol new file mode 100644 index 0000000..dc0f764 --- /dev/null +++ b/contracts/leverage-tokens/CalculatorLeverage.sol @@ -0,0 +1,118 @@ +pragma solidity ^0.5.0; + +import "@openzeppelin/upgrades/contracts/Initializable.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; + +import "../short-tokens/utils/Math.sol"; +import "../short-tokens/utils/DateTimeLibrary.sol"; +import "./Abstract/InterfaceStorageLeverage.sol"; +import "../short-tokens/Abstract/InterfaceInverseToken.sol"; + + +/** + * @dev uint256 are expected to use last 18 numbers as decimal points except when specifid differently in @params + */ +contract CalculatorLeverage is Initializable { + using SafeMath for uint256; + + InterfaceStorageLeverage public persistentStorage; + InterfaceInverseToken public inverseToken; + + function initialize( + address _persistentStorageAddress, + address _inverseTokenAddress + ) public initializer { + persistentStorage = InterfaceStorageLeverage(_persistentStorageAddress); + inverseToken = InterfaceInverseToken(_inverseTokenAddress); + } + + // Start fill in the values + // Creation 3x Long + + function getTokensCreatedByCash( + uint256 mintingPrice, + uint256 cash, + uint256 gasFee + ) public view returns (uint256 tokensCreated) { + // Cash Remove Gas Fee + // Cash Remove Minting Fee (Cash Proceeds) + // Cash Proceeds / Minting Price (# of Tokens to Mint) + + uint256 cashAfterGas = DSMath.sub(cash, gasFee); + uint256 cashAfterFee = removeCurrentMintingFeeFromCash(cashAfterGas); + + uint256 tokensMinted = DSMath.wdiv(cashAfterFee, mintingPrice); + + return tokensMinted; + } + + function getCashCreatedByTokens( + uint256 burningPrice, + uint256 elapsedTime, + uint256 tokens, + uint256 gasFee + ) public view returns (uint256 stablecoinAfterFees) { + uint256 stablecoin = DSMath.wmul(tokens, burningPrice); + uint256 stablecoinAfterGas = DSMath.sub(stablecoin, gasFee); + uint256 stablecoinRedeemed = removeCurrentMintingFeeFromCash( + stablecoinAfterGas + ); + uint256 managementFeeDaily = DSMath.wdiv( + persistentStorage.managementFee(), + 365 ether + ); + uint256 managementFeeHourly = DSMath.wdiv( + DSMath.wmul(managementFeeDaily, elapsedTime), + 24 ether + ); + uint256 normalizedManagementFee = DSMath.sub( + 1 ether, + DSMath.wdiv(managementFeeHourly, 100 ether) + ); + + stablecoinAfterFees = DSMath.wmul( + stablecoinRedeemed, + normalizedManagementFee + ); + } + + function removeCurrentMintingFeeFromCash(uint256 _cash) + public + view + returns (uint256 cashAfterFee) + { + uint256 creationFee = persistentStorage.getMintingFee(_cash); + uint256 minimumMintingFee = persistentStorage.minimumMintingFee(); + cashAfterFee = removeMintingFeeFromCash( + _cash, + creationFee, + minimumMintingFee + ); + } + + 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); + } + + int256 constant WAD = 10**18; + + function addInt256(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x + y) >= x, "ds-math-add-overflow"); + } + + function mulInt256(int256 x, int256 y) internal pure returns (int256 z) { + require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); + } + + function wmulInt256(int256 x, int256 y) internal pure returns (int256 z) { + z = addInt256(mulInt256(x, y), WAD / 2) / WAD; + } +} diff --git a/contracts/leverage-tokens/StorageLeverage.sol b/contracts/leverage-tokens/StorageLeverage.sol new file mode 100644 index 0000000..c870be2 --- /dev/null +++ b/contracts/leverage-tokens/StorageLeverage.sol @@ -0,0 +1,427 @@ +pragma solidity ^0.5.0; + +import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; +import "../short-tokens/utils/DateTimeLibrary.sol"; +import "../short-tokens/utils/Math.sol"; + + +contract StorageLeverage is Ownable { + address public tokenSwapManager; + address public bridge; + + bool public isPaused; + bool public isShutdown; + + struct Accounting { + uint256 bestExecutionPrice; + uint256 markPrice; + uint256 notional; + uint256 tokenValue; + uint256 effectiveFundingRate; + } + + struct Order { + string orderType; + uint256 tokensGiven; + uint256 tokensRecieved; + uint256 mintingPrice; + } + + uint256 public lastActivityDay; + uint256 public minRebalanceAmount; + uint256 public managementFee; + uint256 public minimumMintingFee; + uint256 public minimumTrade; + + uint8 public balancePrecision; + + mapping(uint256 => Accounting[]) private accounting; + + uint256[] public mintingFeeBracket; + mapping(uint256 => uint256) public mintingFee; + + Order[] public allOrders; + mapping(address => Order[]) public orderByUser; + mapping(address => uint256) public delayedRedemptionsByUser; + + event AccountingValuesSet(uint256 today); + event RebalanceValuesSet(uint256 newMinRebalanceAmount); + event ManagementFeeValuesSet(uint256 newManagementFee); + + function initialize( + address ownerAddress, + uint256 _managementFee, + uint256 _minRebalanceAmount, + uint8 _balancePrecision, + uint256 _lastMintingFee, + uint256 _minimumMintingFee, + uint256 _minimumTrade + ) public initializer { + initialize(ownerAddress); + managementFee = _managementFee; + minRebalanceAmount = _minRebalanceAmount; + mintingFee[~uint256(0)] = _lastMintingFee; + balancePrecision = _balancePrecision; + minimumMintingFee = _minimumMintingFee; + minimumTrade = _minimumTrade; + } + + function setTokenSwapManager(address _tokenSwapManager) public onlyOwner { + require(_tokenSwapManager != address(0), "adddress must not be empty"); + tokenSwapManager = _tokenSwapManager; + } + + function setBridge(address _bridge) public onlyOwner { + require(_bridge != address(0), "adddress must not be empty"); + bridge = _bridge; + } + + function setIsPaused(bool _isPaused) public onlyOwner { + isPaused = _isPaused; + } + + function shutdown() public onlyOwner { + isShutdown = true; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwnerOrTokenSwap() { + require( + isOwner() || _msgSender() == tokenSwapManager, + "caller is not the owner or token swap manager" + ); + _; + } + + modifier onlyOwnerOrBridge() { + require( + isOwner() || _msgSender() == bridge, + "caller is not the owner or bridge" + ); + _; + } + + function setDelayedRedemptionsByUser( + uint256 amountToRedeem, + address whitelistedAddress + ) public onlyOwnerOrTokenSwap { + delayedRedemptionsByUser[whitelistedAddress] = amountToRedeem; + } + + /* + * Saves order in mapping (address => Order[]) orderByUser + * overwrite == false, append to Order[] + * overwrite == true, overwrite element at orderIndex + */ + + function setOrderByUser( + address whitelistedAddress, + string memory orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice, + uint256 orderIndex, + bool overwrite + ) public onlyOwnerOrTokenSwap() { + Order memory newOrder = Order( + orderType, + tokensGiven, + tokensRecieved, + mintingPrice + ); + + if (!overwrite) { + orderByUser[whitelistedAddress].push(newOrder); + setOrder( + orderType, + tokensGiven, + tokensRecieved, + mintingPrice, + orderIndex, + overwrite + ); + } else { + orderByUser[whitelistedAddress][orderIndex] = newOrder; + } + } + + /* + * Gets Order[] For User Address + * Return order at Index in Order[] + */ + + function getOrderByUser(address whitelistedAddress, uint256 orderIndex) + public + view + returns ( + string memory orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice + ) + { + + Order storage orderAtIndex + = orderByUser[whitelistedAddress][orderIndex]; + return ( + orderAtIndex.orderType, + orderAtIndex.tokensGiven, + orderAtIndex.tokensRecieved, + orderAtIndex.mintingPrice + ); + } + + /* + * Save order to allOrders array + * overwrite == false, append to allOrders array + * overwrite == true, overwrite element at orderIndex + */ + function setOrder( + string memory orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice, + uint256 orderIndex, + bool overwrite + ) public onlyOwnerOrTokenSwap() { + Order memory newOrder = Order( + orderType, + tokensGiven, + tokensRecieved, + mintingPrice + ); + + if (!overwrite) { + allOrders.push(newOrder); + } else { + allOrders[orderIndex] = newOrder; + } + } + + /* + * Get Order + */ + function getOrder(uint256 index) + public + view + returns ( + string memory orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice + ) + { + Order storage orderAtIndex = allOrders[index]; + return ( + orderAtIndex.orderType, + orderAtIndex.tokensGiven, + orderAtIndex.tokensRecieved, + orderAtIndex.mintingPrice + ); + } + + // @dev Get accounting values for a specific day + // @param date format as 20200123 for 23th of January 2020 + function getAccounting(uint256 date) + public + view + returns (uint256, uint256, uint256, uint256, uint256) + { + Accounting[] storage accountingsForDate = accounting[date]; + uint256 lastIndex = accountingsForDate.length - 1; + return ( + accountingsForDate[lastIndex].bestExecutionPrice, + accountingsForDate[lastIndex].markPrice, + accountingsForDate[lastIndex].notional, + accountingsForDate[lastIndex].tokenValue, + accountingsForDate[lastIndex].effectiveFundingRate + ); + } + + // @dev Set accounting values for the day + function setAccounting( + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) external onlyOwnerOrTokenSwap() { + (uint256 year, uint256 month, uint256 day) = DateTimeLibrary + .timestampToDate(block.timestamp); + uint256 today = year * 10000 + month * 100 + day; + accounting[today].push( + Accounting( + _bestExecutionPrice, + _markPrice, + _notional, + _tokenValue, + _effectiveFundingRate + ) + ); + lastActivityDay = today; + emit AccountingValuesSet(today); + } + + // @dev Set accounting values for the day + function setAccountingForLastActivityDay( + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) external onlyOwnerOrTokenSwap() { + accounting[lastActivityDay].push( + Accounting( + _bestExecutionPrice, + _markPrice, + _notional, + _tokenValue, + _effectiveFundingRate + ) + ); + emit AccountingValuesSet(lastActivityDay); + } + + // @dev Set last rebalance information + function setMinRebalanceAmount(uint256 _minRebalanceAmount) + external + onlyOwner + { + minRebalanceAmount = _minRebalanceAmount; + + emit RebalanceValuesSet(minRebalanceAmount); + } + + // @dev Set last rebalance information + function setManagementFee(uint256 _managementFee) external onlyOwner { + managementFee = _managementFee; + emit ManagementFeeValuesSet(managementFee); + } + + // @dev Returns execution price + function getExecutionPrice() public view returns (uint256 price) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .bestExecutionPrice; + } + + // @dev Returns mark price + function getMarkPrice() public view returns (uint256 price) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .markPrice; + } + + // @dev Returns notional amount + function getNotional() public view returns (uint256 amount) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .notional; + } + + // @dev Returns token value + function getTokenValue() public view returns (uint256 tokenValue) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .tokenValue; + } + + // @dev Returns effective funding rate + function getFundingRate() public view returns (uint256 fundingRate) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .effectiveFundingRate; + } + + // @dev Sets last minting fee + function setLastMintingFee(uint256 _mintingFee) public onlyOwner { + mintingFee[~uint256(0)] = _mintingFee; + } + + // @dev Adds minting fee + function addMintingFeeBracket(uint256 _mintingFeeLimit, uint256 _mintingFee) + public + onlyOwner + { + require( + mintingFeeBracket.length == 0 || + _mintingFeeLimit > + mintingFeeBracket[mintingFeeBracket.length - 1], + "New minting fee bracket needs to be bigger then last one" + ); + mintingFeeBracket.push(_mintingFeeLimit); + mintingFee[_mintingFeeLimit] = _mintingFee; + } + + // @dev Deletes last minting fee + function deleteLastMintingFeeBracket() public onlyOwner { + delete mintingFee[mintingFeeBracket[mintingFeeBracket.length - 1]]; + mintingFeeBracket.length--; + } + + // @dev Changes minting fee + function changeMintingLimit( + uint256 _position, + uint256 _mintingFeeLimit, + uint256 _mintingFee + ) public onlyOwner { + require( + _mintingFeeLimit > mintingFeeBracket[mintingFeeBracket.length - 1], + "New minting fee bracket needs to be bigger then last one" + ); + if (_position != 0) { + require( + _mintingFeeLimit > mintingFeeBracket[_position - 1], + "New minting fee bracket needs to be bigger then last one" + ); + } + if (_position < mintingFeeBracket.length - 1) { + require( + _mintingFeeLimit < mintingFeeBracket[_position + 1], + "New minting fee bracket needs to be smaller then next one" + ); + } + mintingFeeBracket[_position] = _mintingFeeLimit; + mintingFee[_mintingFeeLimit] = _mintingFee; + } + + function getMintingFee(uint256 cash) public view returns (uint256) { + // Define Start + End Index + uint256 startIndex = 0; + if (mintingFeeBracket.length > 0) { + uint256 endIndex = mintingFeeBracket.length - 1; + uint256 middleIndex = endIndex / 2; + + if (cash <= mintingFeeBracket[middleIndex]) { + endIndex = middleIndex; + } else { + startIndex = middleIndex + 1; + } + + for (uint256 i = startIndex; i <= endIndex; i++) { + if (cash <= mintingFeeBracket[i]) { + return mintingFee[mintingFeeBracket[i]]; + } + } + } + + return mintingFee[~uint256(0)]; + } + + // @dev Sets last balance precision + 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/leverage-tokens/TokenSwapLeverage.sol b/contracts/leverage-tokens/TokenSwapLeverage.sol new file mode 100644 index 0000000..bde75d6 --- /dev/null +++ b/contracts/leverage-tokens/TokenSwapLeverage.sol @@ -0,0 +1,436 @@ +pragma solidity ^0.5.0; + +import "@openzeppelin/upgrades/contracts/Initializable.sol"; +import "solidity-util/lib/Strings.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; + +import "../short-tokens/Abstract/InterfaceCashPool.sol"; +import "../short-tokens/Abstract/InterfaceKYCVerifier.sol"; +import "./Abstract/InterfaceCalculator.sol"; +import "../short-tokens/Abstract/InterfaceERC20.sol"; + +import "../short-tokens/Abstract/InterfaceInverseToken.sol"; +import "./Abstract/InterfaceStorageLeverage.sol"; +import "../short-tokens/utils/Math.sol"; + + +contract TokenSwapLeverage is Initializable, Ownable { + using Strings for string; + using SafeMath for uint256; + + address public inverseToken; + + InterfaceERC20 public erc20; + InterfaceKYCVerifier public kycVerifier; + InterfaceCashPool public cashPool; + InterfaceStorageLeverage public persistentStorage; + InterfaceCalculator public compositionCalculator; + + event SuccessfulOrder( + string orderType, + address whitelistedAddress, + uint256 tokensGiven, + uint256 tokensRecieved, + address stablecoin, + uint256 price + ); + + event RebalanceEvent( + uint256 bestExecutionPrice, + uint256 markPrice, + uint256 notional, + uint256 tokenValue, + uint256 effectiveFundingRate + ); + + function initialize( + address _owner, + address _inverseToken, + address _cashPool, + address _storage, + address _compositionCalculator + ) public initializer { + initialize(_owner); + + require( + _owner != address(0) && + _inverseToken != address(0) && + _cashPool != address(0) && + _storage != address(0) && + _compositionCalculator != address(0), + "addresses cannot be zero" + ); + + inverseToken = _inverseToken; + + cashPool = InterfaceCashPool(_cashPool); + persistentStorage = InterfaceStorageLeverage(_storage); + kycVerifier = InterfaceKYCVerifier(address(cashPool.kycVerifier())); + compositionCalculator = InterfaceCalculator(_compositionCalculator); + } + + //////////////// Create + Redeem Order Request //////////////// + //////////////// Create: Recieve Inverse Token //////////////// + //////////////// Redeem: Recieve Stable Coin //////////////// + + function createOrder( + bool success, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice, + address whitelistedAddress, + address stablecoin, + uint256 gasFee + ) public onlyOwnerOrBridge() notPausedOrShutdown() returns (bool retVal) { + // Require is Whitelisted + require( + kycVerifier.isAddressWhitelisted(whitelistedAddress), + "only whitelisted address may place orders" + ); + + // Return Funds if Bridge Pass an Error + if (!success) { + transferTokenFromPool( + stablecoin, + whitelistedAddress, + normalizeStablecoin(tokensGiven, stablecoin) + ); + return false; + } + + // Check Tokens Recieved with Composition Calculator + uint256 _tokensRecieved = compositionCalculator.getTokensCreatedByCash( + mintingPrice, + tokensGiven, + gasFee + ); + require( + _tokensRecieved == tokensRecieved, + "tokens created must equal tokens recieved" + ); + + // Save Order to Storage and Lock Funds for 1 Hour + persistentStorage.setOrderByUser( + whitelistedAddress, + "CREATE", + tokensGiven, + tokensRecieved, + mintingPrice, + 0, + false + ); + + // Write Successful Order to Log + writeOrderResponse( + "CREATE", + whitelistedAddress, + tokensGiven, + tokensRecieved, + stablecoin, + mintingPrice + ); + + // Mint Tokens to Address + InterfaceInverseToken token = InterfaceInverseToken(inverseToken); + token.mintTokens(whitelistedAddress, tokensRecieved); + + return true; + } + + function redeemOrder( + bool success, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 burningPrice, + address whitelistedAddress, + address stablecoin, + uint256 gasFee, + uint256 elapsedTime + ) public onlyOwnerOrBridge() notPausedOrShutdown() returns (bool retVal) { + // Require Whitelisted + require( + kycVerifier.isAddressWhitelisted(whitelistedAddress), + "only whitelisted address may place orders" + ); + + // Return Funds if Bridge Pass an Error + if (!success) { + transferTokenFromPool( + inverseToken, + whitelistedAddress, + tokensGiven + ); + return false; + } + + // Check Cash Recieved with Composition Calculator + uint256 _tokensRecieved = compositionCalculator.getCashCreatedByTokens( + burningPrice, + elapsedTime, + tokensGiven, + gasFee + ); + require( + _tokensRecieved == tokensRecieved, + "cash redeemed must equal tokens recieved" + ); + + // Save To Storage + persistentStorage.setOrderByUser( + whitelistedAddress, + "REDEEM", + tokensGiven, + tokensRecieved, + burningPrice, + 0, + false + ); + + // Redeem Stablecoin or Perform Delayed Settlement + redeemFunds( + tokensGiven, + tokensRecieved, + whitelistedAddress, + stablecoin, + burningPrice + ); + + // Burn Tokens to Address + InterfaceInverseToken token = InterfaceInverseToken(inverseToken); + token.burnTokens(address(cashPool), tokensGiven); + + return true; + } + + function writeOrderResponse( + string memory orderType, + address whiteListedAddress, + uint256 tokensGiven, + uint256 tokensRecieved, + address stablecoin, + uint256 price + ) internal { + require( + tokensGiven != 0 && tokensRecieved != 0, + "amount must be greater than 0" + ); + + emit SuccessfulOrder( + orderType, + whiteListedAddress, + tokensGiven, + tokensRecieved, + stablecoin, + price + ); + } + + function settleDelayedFunds( + uint256 tokensToRedeem, + address whitelistedAddress, + address stablecoin + ) public onlyOwnerOrBridge notPausedOrShutdown { + require( + kycVerifier.isAddressWhitelisted(whitelistedAddress), + "only whitelisted may redeem funds" + ); + + bool isSufficientFunds = isHotWalletSufficient( + tokensToRedeem, + stablecoin + ); + require( + isSufficientFunds == true, + "not enough funds in the hot wallet" + ); + + uint256 tokensOutstanding = persistentStorage.delayedRedemptionsByUser( + whitelistedAddress + ); + uint256 tokensRemaining = DSMath.sub(tokensOutstanding, tokensToRedeem); + + persistentStorage.setDelayedRedemptionsByUser( + tokensRemaining, + whitelistedAddress + ); + transferTokenFromPool( + stablecoin, + whitelistedAddress, + normalizeStablecoin(tokensToRedeem, stablecoin) + ); + } + + function redeemFunds( + uint256 tokensGiven, + uint256 tokensToRedeem, + address whitelistedAddress, + address stablecoin, + uint256 price + ) internal { + bool isSufficientFunds = isHotWalletSufficient( + tokensToRedeem, + stablecoin + ); + + if (isSufficientFunds) { + transferTokenFromPool( + stablecoin, + whitelistedAddress, + normalizeStablecoin(tokensToRedeem, stablecoin) + ); + writeOrderResponse( + "REDEEM", + whitelistedAddress, + tokensGiven, + tokensToRedeem, + stablecoin, + price + ); + } else { + uint256 tokensOutstanding = persistentStorage + .delayedRedemptionsByUser(whitelistedAddress); + tokensOutstanding = DSMath.add(tokensOutstanding, tokensToRedeem); + persistentStorage.setDelayedRedemptionsByUser( + tokensOutstanding, + whitelistedAddress + ); + writeOrderResponse( + "REDEEM_NO_SETTLEMENT", + whitelistedAddress, + tokensGiven, + tokensToRedeem, + stablecoin, + price + ); + } + } + + function isHotWalletSufficient(uint256 tokensToRedeem, address stablecoin) + internal + returns (bool) + { + InterfaceInverseToken _stablecoin = InterfaceInverseToken(stablecoin); + uint256 stablecoinBalance = _stablecoin.balanceOf(address(cashPool)); + + if (normalizeStablecoin(tokensToRedeem, stablecoin) > stablecoinBalance) + return false; + return true; + } + + function normalizeStablecoin(uint256 stablecoinValue, address stablecoin) + internal + returns (uint256) + { + erc20 = InterfaceERC20(stablecoin); + uint256 exponent = 18 - erc20.decimals(); + return stablecoinValue / 10**exponent; // 6 decimal stable coin = 10**12 + } + + //////////////// Daily Rebalance //////////////// + //////////////// Threshold Rebalance //////////////// + + /** + * @dev Saves results of rebalance calculations in persistent storages + * @param _bestExecutionPrice The best execution price for rebalancing + * @param _markPrice The Mark Price + * @param _notional The new notional amount after rebalance + * @param _tokenValue The targetLeverage + * @param _effectiveFundingRate The effectiveFundingRate + */ + + function rebalance( + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) public onlyOwnerOrBridge() notPausedOrShutdown() { + persistentStorage.setAccounting( + _bestExecutionPrice, + _markPrice, + _notional, + _tokenValue, + _effectiveFundingRate + ); + emit RebalanceEvent( + _bestExecutionPrice, + _markPrice, + _notional, + _tokenValue, + _effectiveFundingRate + ); + } + + //////////////// Transfer Stablecoin Out of Pool //////////////// + //////////////// Transfer Stablecoin In of Pool //////////////// + //////////////// Transfer InverseToken Out of Pool //////////////// + //////////////// Transfer InverseToken In of Pool //////////////// + + function transferTokenToPool( + address tokenContract, + address whiteListedAddress, + uint256 orderAmount + ) internal returns (bool) { + // Check orderAmount <= availableAmount + // Transfer USDC to Stablecoin Cash Pool + return + cashPool.moveTokenToPool( + tokenContract, + whiteListedAddress, + orderAmount + ); + } + + function transferTokenFromPool( + address tokenContract, + address destinationAddress, + uint256 orderAmount + ) internal returns (bool) { + // Check orderAmount <= availableAmount + // Transfer USDC to Destination Address + return + cashPool.moveTokenfromPool( + tokenContract, + destinationAddress, + orderAmount + ); + } + + function setCashPool(address _cashPool) public onlyOwner { + require(_cashPool != address(0), "adddress must not be empty"); + cashPool = InterfaceCashPool(_cashPool); + } + + function setStorage(address _storage) public onlyOwner { + require(_storage != address(0), "adddress must not be empty"); + persistentStorage = InterfaceStorageLeverage(_storage); + } + + function setKycVerfier(address _kycVerifier) public onlyOwner { + require(_kycVerifier != address(0), "adddress must not be empty"); + kycVerifier = InterfaceKYCVerifier(_kycVerifier); + } + + function setCalculator(address _calculator) public onlyOwner { + require(_calculator != address(0), "adddress must not be empty"); + compositionCalculator = InterfaceCalculator(_calculator); + } + + modifier onlyOwnerOrBridge() { + require( + isOwner() || _msgSender() == persistentStorage.bridge(), + "caller is not the owner or bridge" + ); + _; + } + + modifier notPausedOrShutdown() { + require(persistentStorage.isPaused() == false, "contract is paused"); + require( + persistentStorage.isShutdown() == false, + "contract is shutdown" + ); + _; + } +} diff --git a/contracts/AdminMultiSig/OwnerMultiSig.sol b/contracts/shared/AdminMultiSig/OwnerMultiSig.sol similarity index 100% rename from contracts/AdminMultiSig/OwnerMultiSig.sol rename to contracts/shared/AdminMultiSig/OwnerMultiSig.sol diff --git a/contracts/Token/ERC20.sol b/contracts/shared/Token/ERC20.sol similarity index 100% rename from contracts/Token/ERC20.sol rename to contracts/shared/Token/ERC20.sol diff --git a/contracts/Token/ERC20Detailed.sol b/contracts/shared/Token/ERC20Detailed.sol similarity index 91% rename from contracts/Token/ERC20Detailed.sol rename to contracts/shared/Token/ERC20Detailed.sol index a50934c..3d2439e 100644 --- a/contracts/Token/ERC20Detailed.sol +++ b/contracts/shared/Token/ERC20Detailed.sol @@ -3,7 +3,7 @@ pragma solidity ^0.5.0; import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/upgrades/contracts/Initializable.sol"; import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; -import "../PersistentStorage.sol"; +import "../../short-tokens/Abstract/InterfaceStorage.sol"; /** @@ -13,7 +13,7 @@ contract ERC20Detailed is IERC20, Initializable, Ownable { string private _name; string private _symbol; uint8 private _decimals; - PersistentStorage public _persistenStorage; + InterfaceStorage public _persistenStorage; /** * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of @@ -31,7 +31,7 @@ contract ERC20Detailed is IERC20, Initializable, Ownable { _name = name; _symbol = symbol; _decimals = decimals; - _persistenStorage = PersistentStorage(persistenStorage); + _persistenStorage = InterfaceStorage(persistenStorage); } /** diff --git a/contracts/Token/Sender.sol b/contracts/shared/Token/Sender.sol similarity index 100% rename from contracts/Token/Sender.sol rename to contracts/shared/Token/Sender.sol diff --git a/contracts/Token/USDC.sol b/contracts/shared/Token/USDC.sol similarity index 100% rename from contracts/Token/USDC.sol rename to contracts/shared/Token/USDC.sol diff --git a/contracts/shared/Token/USDT.sol b/contracts/shared/Token/USDT.sol new file mode 100644 index 0000000..c8e2800 --- /dev/null +++ b/contracts/shared/Token/USDT.sol @@ -0,0 +1,653 @@ +/** + *Submitted for verification at Etherscan.io on 2020-03-03 + */ + +/** + *Submitted for verification at Etherscan.io on 2018-08-03 + */ + +pragma solidity ^0.5.0; + + +// File: contracts/OwnableUSDCT.sol + +/** + * Copyright CENTRE SECZ 2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @title OwnableUSDCT + * @dev The OwnableUSDCT contract from https://github.com/zeppelinos/labs/blob/master/upgradeability_ownership/contracts/ownership/Ownable.sol + * branch: master commit: 3887ab77b8adafba4a26ace002f3a684c1a3388b modified to: + * 1) Add emit prefix to OwnershipTransferred event (7/13/18) + * 2) Replace constructor with constructor syntax (7/13/18) + * 3) consolidate OwnableUSDCTStorage into this contract + */ +contract OwnableUSDCT { + // Owner of the contract + address private _owner; + + /** + * @dev Event to show ownership has been transferred + * @param previousOwner representing the address of the previous owner + * @param newOwner representing the address of the new owner + */ + event OwnershipTransferred(address previousOwner, address newOwner); + + /** + * @dev The constructor sets the original owner of the contract to the sender account. + */ + function initialize() public { + setOwner(msg.sender); + } + + /** + * @dev Tells the address of the owner + * @return the address of the owner + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Sets a new owner address + */ + function setOwner(address newOwner) internal { + _owner = newOwner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner()); + _; + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) public onlyOwner { + require(newOwner != address(0)); + emit OwnershipTransferred(owner(), newOwner); + setOwner(newOwner); + } +} + + +// File: contracts/Blacklistable.sol + +/** + * Copyright CENTRE SECZ 2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @title Blacklistable Token + * @dev Allows accounts to be blacklisted by a "blacklister" role + */ +contract Blacklistable is OwnableUSDCT { + address public blacklister; + mapping(address => bool) internal blacklisted; + + event Blacklisted(address indexed _account); + event UnBlacklisted(address indexed _account); + event BlacklisterChanged(address indexed newBlacklister); + + /** + * @dev Throws if called by any account other than the blacklister + */ + modifier onlyBlacklister() { + require(msg.sender == blacklister); + _; + } + + /** + * @dev Throws if argument account is blacklisted + * @param _account The address to check + */ + modifier notBlacklisted(address _account) { + require(blacklisted[_account] == false); + _; + } + + /** + * @dev Checks if account is blacklisted + * @param _account The address to check + */ + function isBlacklisted(address _account) public view returns (bool) { + return blacklisted[_account]; + } + + /** + * @dev Adds account to blacklist + * @param _account The address to blacklist + */ + function blacklist(address _account) public onlyBlacklister { + blacklisted[_account] = true; + emit Blacklisted(_account); + } + + /** + * @dev Removes account from blacklist + * @param _account The address to remove from the blacklist + */ + function unBlacklist(address _account) public onlyBlacklister { + blacklisted[_account] = false; + emit UnBlacklisted(_account); + } + + function updateBlacklister(address _newBlacklister) public onlyOwner { + require(_newBlacklister != address(0)); + blacklister = _newBlacklister; + emit BlacklisterChanged(blacklister); + } +} + + +// File: contracts/Pausable.sol + +/** + * Copyright CENTRE SECZ 2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @title Pausable + * @dev Base contract which allows children to implement an emergency stop mechanism. + * Based on openzeppelin tag v1.10.0 commit: feb665136c0dae9912e08397c1a21c4af3651ef3 + * Modifications: + * 1) Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018) + * 2) Removed whenNotPause/whenPaused from pause/unpause (6/14/2018) + * 3) Removed whenPaused (6/14/2018) + * 4) Switches ownable library to use zeppelinos (7/12/18) + * 5) Remove constructor (7/13/18) + */ +contract Pausable is OwnableUSDCT { + event Pause(); + event Unpause(); + event PauserChanged(address indexed newAddress); + + address public pauser; + bool public paused = false; + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!paused); + _; + } + + /** + * @dev throws if called by any account other than the pauser + */ + modifier onlyPauser() { + require(msg.sender == pauser); + _; + } + + /** + * @dev called by the owner to pause, triggers stopped state + */ + function pause() public onlyPauser { + paused = true; + emit Pause(); + } + + /** + * @dev called by the owner to unpause, returns to normal state + */ + function unpause() public onlyPauser { + paused = false; + emit Unpause(); + } + + /** + * @dev update the pauser role + */ + function updatePauser(address _newPauser) public onlyOwner { + require(_newPauser != address(0)); + pauser = _newPauser; + emit PauserChanged(pauser); + } +} + + +// File: openzeppelin-solidity/contracts/math/SafeMath.sol + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { + // Gas optimization: this is cheaper than asserting 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (a == 0) { + return 0; + } + + c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + // uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return a / b; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256 c) { + c = a + b; + assert(c >= a); + return c; + } +} + + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol + +/** + * @title ERC20Basic + * @dev Simpler version of ERC20 interface + * See https://github.com/ethereum/EIPs/issues/179 + */ +contract ERC20Basic { + function totalSupply() public view returns (uint256); + + function balanceOf(address who) public view returns (uint256); + + function transfer(address to, uint256 value) public returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); +} + + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +contract ERC20T is ERC20Basic { + function allowance(address owner, address spender) + public + view + returns (uint256); + + function transferFrom(address from, address to, uint256 value) + public + returns (bool); + + function approve(address spender, uint256 value) public returns (bool); + + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); +} + + +// File: contracts/FiatTokenV1.sol + +/** + * Copyright CENTRE SECZ 2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @title FiatToken + * @dev ERC20 Token backed by fiat reserves + */ +contract USDT is OwnableUSDCT, ERC20T, Pausable, Blacklistable { + using SafeMath for uint256; + + string public name; + string public symbol; + uint8 public decimals; + string public currency; + address public masterMinter; + bool internal initialized; + uint256 internal totalSupply_; + + mapping(address => uint256) internal balances; + mapping(address => mapping(address => uint256)) internal allowed; + mapping(address => bool) internal minters; + mapping(address => uint256) internal minterAllowed; + + event Mint(address indexed minter, address indexed to, uint256 amount); + event Burn(address indexed burner, uint256 amount); + event MinterConfigured(address indexed minter, uint256 minterAllowedAmount); + event MinterRemoved(address indexed oldMinter); + event MasterMinterChanged(address indexed newMasterMinter); + + function initialize( + string memory _name, + string memory _symbol, + string memory _currency, + uint8 _decimals, + address _masterMinter, + address _pauser, + address _blacklister, + address _owner + ) public { + require(!initialized); + require(_masterMinter != address(0)); + require(_pauser != address(0)); + require(_blacklister != address(0)); + require(_owner != address(0)); + + totalSupply_ = 0; + name = _name; + symbol = _symbol; + currency = _currency; + decimals = _decimals; + masterMinter = _masterMinter; + pauser = _pauser; + blacklister = _blacklister; + setOwner(_owner); + initialized = true; + } + + /** + * @dev Throws if called by any account other than a minter + */ + modifier onlyMinters() { + require(minters[msg.sender] == true); + _; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. Must be less than or equal to the minterAllowance of the caller. + * @return A boolean that indicates if the operation was successful. + */ + function mint(address _to, uint256 _amount) + public + whenNotPaused + onlyMinters + notBlacklisted(msg.sender) + notBlacklisted(_to) + returns (bool) + { + require(_to != address(0)); + require(_amount > 0); + + uint256 mintingAllowedAmount = minterAllowed[msg.sender]; + require(_amount <= mintingAllowedAmount); + + totalSupply_ = totalSupply_.add(_amount); + balances[_to] = balances[_to].add(_amount); + minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount); + emit Mint(msg.sender, _to, _amount); + emit Transfer(address(0), _to, _amount); + return true; + } + + /** + * @dev Throws if called by any account other than the masterMinter + */ + modifier onlyMasterMinter() { + require(msg.sender == masterMinter); + _; + } + + /** + * @dev Get minter allowance for an account + * @param minter The address of the minter + */ + function minterAllowance(address minter) public view returns (uint256) { + return minterAllowed[minter]; + } + + /** + * @dev Checks if account is a minter + * @param account The address to check + */ + function isMinter(address account) public view returns (bool) { + return minters[account]; + } + + /** + * @dev Get allowed amount for an account + * @param owner address The account owner + * @param spender address The account spender + */ + function allowance(address owner, address spender) + public + view + returns (uint256) + { + return allowed[owner][spender]; + } + + /** + * @dev Get totalSupply of token + */ + function totalSupply() public view returns (uint256) { + return totalSupply_; + } + + /** + * @dev Get token balance of an account + * @param account address The account + */ + function balanceOf(address account) public view returns (uint256) { + return balances[account]; + } + + /** + * @dev Adds blacklisted check to approve + * @return True if the operation was successful. + */ + function approve(address _spender, uint256 _value) + public + whenNotPaused + notBlacklisted(msg.sender) + notBlacklisted(_spender) + returns (bool) + { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @dev Transfer tokens from one address to another. + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @return bool success + */ + function transferFrom(address _from, address _to, uint256 _value) + public + whenNotPaused + notBlacklisted(_to) + notBlacklisted(msg.sender) + notBlacklisted(_from) + returns (bool) + { + require(_to != address(0)); + require(_value <= balances[_from]); + require(_value <= allowed[_from][msg.sender]); + + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + emit Transfer(_from, _to, _value); + return true; + } + + /** + * @dev transfer token for a specified address + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + * @return bool success + */ + function transfer(address _to, uint256 _value) + public + whenNotPaused + notBlacklisted(msg.sender) + notBlacklisted(_to) + returns (bool) + { + require(_to != address(0)); + require(_value <= balances[msg.sender]); + + balances[msg.sender] = balances[msg.sender].sub(_value); + balances[_to] = balances[_to].add(_value); + emit Transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev Function to add/update a new minter + * @param minter The address of the minter + * @param minterAllowedAmount The minting amount allowed for the minter + * @return True if the operation was successful. + */ + function configureMinter(address minter, uint256 minterAllowedAmount) + public + whenNotPaused + onlyMasterMinter + returns (bool) + { + minters[minter] = true; + minterAllowed[minter] = minterAllowedAmount; + emit MinterConfigured(minter, minterAllowedAmount); + return true; + } + + /** + * @dev Function to remove a minter + * @param minter The address of the minter to remove + * @return True if the operation was successful. + */ + function removeMinter(address minter) + public + onlyMasterMinter + returns (bool) + { + minters[minter] = false; + minterAllowed[minter] = 0; + emit MinterRemoved(minter); + return true; + } + + /** + * @dev allows a minter to burn some of its own tokens + * Validates that caller is a minter and that sender is not blacklisted + * amount is less than or equal to the minter's account balance + * @param _amount uint256 the amount of tokens to be burned + */ + function burn(uint256 _amount) + public + whenNotPaused + onlyMinters + notBlacklisted(msg.sender) + { + uint256 balance = balances[msg.sender]; + require(_amount > 0); + require(balance >= _amount); + + totalSupply_ = totalSupply_.sub(_amount); + balances[msg.sender] = balance.sub(_amount); + emit Burn(msg.sender, _amount); + emit Transfer(msg.sender, address(0), _amount); + } + + function updateMasterMinter(address _newMasterMinter) public onlyOwner { + require(_newMasterMinter != address(0)); + masterMinter = _newMasterMinter; + emit MasterMinterChanged(masterMinter); + } +} diff --git a/contracts/short-tokens/Abstract/InterfaceCashPool.sol b/contracts/short-tokens/Abstract/InterfaceCashPool.sol new file mode 100644 index 0000000..1b4301c --- /dev/null +++ b/contracts/short-tokens/Abstract/InterfaceCashPool.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.5.0; + + +interface InterfaceCashPool { + function kycVerifier() external view returns (address); + + function moveTokenToPool( + address _token, + address whiteListedAddress, + uint256 orderAmount + ) external returns (bool); + + function moveTokenfromPool( + address _token, + address destinationAddress, + uint256 orderAmount + ) external returns (bool); +} diff --git a/contracts/short-tokens/Abstract/InterfaceCompositionCalculator.sol b/contracts/short-tokens/Abstract/InterfaceCompositionCalculator.sol new file mode 100644 index 0000000..67c66af --- /dev/null +++ b/contracts/short-tokens/Abstract/InterfaceCompositionCalculator.sol @@ -0,0 +1,21 @@ +pragma solidity ^0.5.0; + + +interface InterfaceCompositionCalculator { + function getCurrentCashAmountCreatedByToken( + uint256 _tokenAmount, + uint256 _spotPrice, + uint256 _gasFee + ) external view returns (uint256); + + function getCurrentTokenAmountCreatedByCash( + uint256 _cash, + uint256 _spotPrice, + uint256 _gasFee + ) external view returns (uint256); + + function calculateDailyPCF(uint256 _price, uint256 _lendingFee) + external + view + returns (uint256, uint256, uint256, uint256, uint256, bool); +} diff --git a/contracts/short-tokens/Abstract/InterfaceERC20.sol b/contracts/short-tokens/Abstract/InterfaceERC20.sol new file mode 100644 index 0000000..4e1d92b --- /dev/null +++ b/contracts/short-tokens/Abstract/InterfaceERC20.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.5.0; + + +interface InterfaceERC20 { + function decimals() external view returns (uint8); +} diff --git a/contracts/Abstract/InterfaceInverseToken.sol b/contracts/short-tokens/Abstract/InterfaceInverseToken.sol similarity index 100% rename from contracts/Abstract/InterfaceInverseToken.sol rename to contracts/short-tokens/Abstract/InterfaceInverseToken.sol diff --git a/contracts/short-tokens/Abstract/InterfaceKYCVerifier.sol b/contracts/short-tokens/Abstract/InterfaceKYCVerifier.sol new file mode 100644 index 0000000..55d782a --- /dev/null +++ b/contracts/short-tokens/Abstract/InterfaceKYCVerifier.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.5.0; + + +interface InterfaceKYCVerifier { + function isAddressWhitelisted(address) external view returns (bool); +} diff --git a/contracts/short-tokens/Abstract/InterfaceStorage.sol b/contracts/short-tokens/Abstract/InterfaceStorage.sol new file mode 100644 index 0000000..500e9ae --- /dev/null +++ b/contracts/short-tokens/Abstract/InterfaceStorage.sol @@ -0,0 +1,59 @@ +pragma solidity ^0.5.0; + + +interface InterfaceStorage { + function whitelistedAddresses(address) external view returns (bool); + + function isPaused() external view returns (bool); + + function isShutdown() external view returns (bool); + + function tokenSwapManager() external view returns (address); + + function bridge() external view returns (address); + + function getCashPositionPerTokenUnit() external view returns (uint256); + + function getBalancePerTokenUnit() external view returns (uint256); + + function getMintingFee(uint256 cash) external view returns (uint256); + + function getPrice() external view returns (uint256); + + function minimumMintingFee() external view returns (uint256); + + function getLendingFee() external view returns (uint256); + + function minRebalanceAmount() external view returns (uint8); + + function delayedRedemptionsByUser(address) external view returns (uint256); + + function setDelayedRedemptionsByUser( + uint256 amountToRedeem, + address whitelistedAddress + ) external; + + function setOrderByUser( + address whitelistedAddress, + string calldata orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 avgBlendedFee, + uint256 orderIndex, + bool overwrite + ) external; + + function setAccounting( + uint256 _price, + uint256 _cashPositionPerTokenUnit, + uint256 _balancePerTokenUnit, + uint256 _lendingFee + ) external; + + function setAccountingForLastActivityDay( + uint256 _price, + uint256 _cashPositionPerTokenUnit, + uint256 _balancePerTokenUnit, + uint256 _lendingFee + ) external; +} diff --git a/contracts/CashPool.sol b/contracts/short-tokens/CashPool.sol similarity index 76% rename from contracts/CashPool.sol rename to contracts/short-tokens/CashPool.sol index 78996d7..e75514c 100644 --- a/contracts/CashPool.sol +++ b/contracts/short-tokens/CashPool.sol @@ -3,18 +3,18 @@ pragma solidity ^0.5.0; import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; import "./Abstract/InterfaceInverseToken.sol"; -import "./PersistentStorage.sol"; -import "./KYCVerifier.sol"; +import "./Abstract//InterfaceKYCVerifier.sol"; contract CashPool is Ownable { using SafeMath for uint256; - KYCVerifier public kycVerifier; - PersistentStorage public persistentStorage; + InterfaceKYCVerifier public kycVerifier; uint256[2] public percentageOfFundsForColdStorage; address public coldStorage; + mapping(address => bool) public approvedTokenManagers; + event SetPercentageOfFundsForColdStorageEvent( uint256[2] newPercentageOfFundsForColdStorage ); @@ -22,7 +22,6 @@ contract CashPool is Ownable { function initialize( address ownerAddress, address _kycVerifier, - address _persistentStorage, address _coldStorage, uint256[2] memory _percentageOfFundsForColdStorage ) public initializer { @@ -39,8 +38,7 @@ contract CashPool is Ownable { "cannot set more than 100% for coldstorage" ); initialize(ownerAddress); - kycVerifier = KYCVerifier(_kycVerifier); - persistentStorage = PersistentStorage(_persistentStorage); + kycVerifier = InterfaceKYCVerifier(_kycVerifier); coldStorage = _coldStorage; percentageOfFundsForColdStorage = _percentageOfFundsForColdStorage; } @@ -51,56 +49,6 @@ contract CashPool is Ownable { return tokenBalance; } - // @dev Move tokens to cash pool - // @param _token ERC20 address - // @param whiteListedAddress address allowed to transfer to pool - // @param orderAmount amount to transfer to cash pool - function moveTokenToPool( - address _token, - address whiteListedAddress, - uint256 orderAmount - ) public onlyOwnerOrTokenSwap() returns (bool) { - InterfaceInverseToken token_ = InterfaceInverseToken(_token); - require( - kycVerifier.isAddressWhitelisted(whiteListedAddress), - "only whitelisted address are allowed to move tokens to pool" - ); - - uint256 percentageNumerator = percentageOfFundsForColdStorage[0]; - uint256 percentageDenominator = percentageOfFundsForColdStorage[1]; - uint256 amountForColdStorage = orderAmount.mul(percentageNumerator).div( - percentageDenominator - ); - - token_.transferFrom(whiteListedAddress, address(this), orderAmount); - token_.transfer(coldStorage, amountForColdStorage); - - return true; - } - - // @dev Move tokens out of cash pool - // @param _token ERC20 address - // @param destinationAddress address to send to - // @param orderAmount amount to transfer from cash pool - function moveTokenfromPool( - address _token, - address destinationAddress, - uint256 orderAmount - ) public onlyOwnerOrTokenSwap() returns (bool) { - InterfaceInverseToken token_ = InterfaceInverseToken(_token); - - token_.transfer(destinationAddress, orderAmount); - return true; - } - - modifier onlyOwnerOrTokenSwap() { - require( - isOwner() || _msgSender() == persistentStorage.tokenSwapManager(), - "caller is not the owner or token swap manager" - ); - _; - } - // @dev Sets new coldStorage // @param _newColdStorage Address for new cold storage wallet function setColdStorage(address _newColdStorage) public onlyOwner { @@ -130,4 +78,77 @@ contract CashPool is Ownable { _newPercentageOfFundsForColdStorage ); } + + // ############################################## Add/Remove ############################################## // + // ############################################## Token ################################################### // + // ############################################## Manager ################################################# // + + function addTokenManager(address tokenManager) public onlyOwner { + require(tokenManager != address(0), "adddress must not be empty"); + approvedTokenManagers[tokenManager] = true; + } + + function removeTokenManager(address tokenManager) public onlyOwner { + require(tokenManager != address(0), "adddress must not be empty"); + delete approvedTokenManagers[tokenManager]; + } + + function isTokenManager(address potentialAddress) + public + view + returns (bool) + { + require(potentialAddress != address(0), "adddress must not be empty"); + return approvedTokenManagers[potentialAddress]; + } + + modifier onlyOwnerOrTokenSwap() { + require( + isOwner() || approvedTokenManagers[_msgSender()] == true, + "caller is not the owner or an approved token swap manager" + ); + _; + } + + // @dev Move tokens out of cash pool + // @param _token ERC20 address + // @param destinationAddress address to send to + // @param orderAmount amount to transfer from cash pool + function moveTokenfromPool( + address _token, + address destinationAddress, + uint256 orderAmount + ) public onlyOwnerOrTokenSwap() returns (bool) { + InterfaceInverseToken token_ = InterfaceInverseToken(_token); + + token_.transfer(destinationAddress, orderAmount); + return true; + } + + // @dev Move tokens to cash pool + // @param _token ERC20 address + // @param whiteListedAddress address allowed to transfer to pool + // @param orderAmount amount to transfer to cash pool + function moveTokenToPool( + address _token, + address whiteListedAddress, + uint256 orderAmount + ) public onlyOwnerOrTokenSwap() returns (bool) { + InterfaceInverseToken token_ = InterfaceInverseToken(_token); + require( + kycVerifier.isAddressWhitelisted(whiteListedAddress), + "only whitelisted address are allowed to move tokens to pool" + ); + + uint256 percentageNumerator = percentageOfFundsForColdStorage[0]; + uint256 percentageDenominator = percentageOfFundsForColdStorage[1]; + uint256 amountForColdStorage = orderAmount.mul(percentageNumerator).div( + percentageDenominator + ); + + token_.transferFrom(whiteListedAddress, address(this), orderAmount); + token_.transfer(coldStorage, amountForColdStorage); + + return true; + } } diff --git a/contracts/CompositionCalculator.sol b/contracts/short-tokens/CompositionCalculator.sol similarity index 100% rename from contracts/CompositionCalculator.sol rename to contracts/short-tokens/CompositionCalculator.sol diff --git a/contracts/Token/InverseToken.sol b/contracts/short-tokens/InverseToken.sol similarity index 96% rename from contracts/Token/InverseToken.sol rename to contracts/short-tokens/InverseToken.sol index 4d3b124..21adc19 100644 --- a/contracts/Token/InverseToken.sol +++ b/contracts/short-tokens/InverseToken.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.0; -import "./ERC20.sol"; +import "../shared/Token/ERC20.sol"; contract InverseToken is ERC20 { diff --git a/contracts/KYCVerifier.sol b/contracts/short-tokens/KYCVerifier.sol similarity index 76% rename from contracts/KYCVerifier.sol rename to contracts/short-tokens/KYCVerifier.sol index 867d286..2bbe75e 100644 --- a/contracts/KYCVerifier.sol +++ b/contracts/short-tokens/KYCVerifier.sol @@ -5,9 +5,10 @@ import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol contract KYCVerifier is Ownable { address public bridge; - mapping(address => bool) public whitelistedAddresses; + event WhitelistedAddressAdded(address); + function initialize(address ownerAddress) public initializer { require(ownerAddress != address(0), "owner adddress must not be empty"); Ownable.initialize(ownerAddress); @@ -22,13 +23,21 @@ contract KYCVerifier is Ownable { } // @dev Set whitelisted addresses - function setWhitelistedAddress(address adddressToAdd) + function setWhitelistedAddress(address addressToAdd) public onlyOwnerOrBridge { - require(adddressToAdd != address(0), "adddress must not be empty"); + require(addressToAdd != address(0), "adddress must not be empty"); + + whitelistedAddresses[addressToAdd] = true; + + emit WhitelistedAddressAdded(addressToAdd); + } - whitelistedAddresses[adddressToAdd] = true; + function batchWhitelistedAddress(address[] calldata addresses) external { + for (uint8 index = 0; index < addresses.length; index++) { + setWhitelistedAddress(addresses[index]); + } } // @dev Remove whitelisted addresses diff --git a/contracts/PersistentStorage.sol b/contracts/short-tokens/PersistentStorage.sol similarity index 100% rename from contracts/PersistentStorage.sol rename to contracts/short-tokens/PersistentStorage.sol diff --git a/contracts/TokenSwapManager.sol b/contracts/short-tokens/TokenSwapManager.sol similarity index 94% rename from contracts/TokenSwapManager.sol rename to contracts/short-tokens/TokenSwapManager.sol index 8f8a6ba..7d25e9e 100644 --- a/contracts/TokenSwapManager.sol +++ b/contracts/short-tokens/TokenSwapManager.sol @@ -5,13 +5,13 @@ import "solidity-util/lib/Strings.sol"; import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; -import "./CashPool.sol"; -import "./KYCVerifier.sol"; -import "./CompositionCalculator.sol"; -import "./Token/InverseToken.sol"; +import "./Abstract/InterfaceCashPool.sol"; +import "./Abstract/InterfaceKYCVerifier.sol"; +import "./Abstract/InterfaceCompositionCalculator.sol"; +import "./Abstract/InterfaceERC20.sol"; import "./Abstract/InterfaceInverseToken.sol"; -import "./PersistentStorage.sol"; +import "./Abstract/InterfaceStorage.sol"; import "./utils/Math.sol"; @@ -21,11 +21,11 @@ contract TokenSwapManager is Initializable, Ownable { address public inverseToken; - InverseToken public erc20; - KYCVerifier public kycVerifier; - CashPool public cashPool; - PersistentStorage public persistentStorage; - CompositionCalculator public compositionCalculator; + InterfaceERC20 public erc20; + InterfaceKYCVerifier public kycVerifier; + InterfaceCashPool public cashPool; + InterfaceStorage public persistentStorage; + InterfaceCompositionCalculator public compositionCalculator; event SuccessfulOrder( string orderType, @@ -46,6 +46,7 @@ contract TokenSwapManager is Initializable, Ownable { address _owner, address _inverseToken, address _cashPool, + address _persistentStorage, address _compositionCalculator ) public initializer { initialize(_owner); @@ -60,12 +61,12 @@ contract TokenSwapManager is Initializable, Ownable { inverseToken = _inverseToken; - cashPool = CashPool(_cashPool); - persistentStorage = PersistentStorage( - address(cashPool.persistentStorage()) + cashPool = InterfaceCashPool(_cashPool); + persistentStorage = InterfaceStorage(_persistentStorage); + kycVerifier = InterfaceKYCVerifier(address(cashPool.kycVerifier())); + compositionCalculator = InterfaceCompositionCalculator( + _compositionCalculator ); - kycVerifier = KYCVerifier(address(cashPool.kycVerifier())); - compositionCalculator = CompositionCalculator(_compositionCalculator); } //////////////// Create + Redeem Order Request //////////////// @@ -315,7 +316,7 @@ contract TokenSwapManager is Initializable, Ownable { internal returns (uint256) { - erc20 = InverseToken(stablecoin); + erc20 = InterfaceERC20(stablecoin); uint256 exponent = 18 - erc20.decimals(); return stablecoinValue / 10**exponent; // 6 decimal stable coin = 10**12 } diff --git a/contracts/short-tokens/upgrades/CashPoolv2.sol b/contracts/short-tokens/upgrades/CashPoolv2.sol new file mode 100644 index 0000000..c5243e9 --- /dev/null +++ b/contracts/short-tokens/upgrades/CashPoolv2.sol @@ -0,0 +1,71 @@ +pragma solidity ^0.5.0; + +import "../CashPool.sol"; + + +// used for Reference only +// with oz sdk one does not actually need to create another smart contract as this and inheret the parent. Add the code directly in the parent smart contract and run `npx upgrade` and choose that contract to upgrade. +contract CashPoolv2 is CashPool { + mapping(address => bool) public approvedTokenManagers; + + function addTokenManager(address tokenManager) public onlyOwner { + require(tokenManager != address(0), "adddress must not be empty"); + approvedTokenManagers[tokenManager] = true; + } + + function removeTokenManager(address tokenManager) public onlyOwner { + require(tokenManager != address(0), "adddress must not be empty"); + delete approvedTokenManagers[tokenManager]; + } + + function isTokenManager(address potentialAddress) + public + view + returns (bool) + { + require(potentialAddress != address(0), "adddress must not be empty"); + return approvedTokenManagers[potentialAddress]; + } + + modifier onlyOwnerOrTokenSwapv2() { + require( + isOwner() || approvedTokenManagers[_msgSender()] == true, + "caller is not the owner or an approved token swap manager" + ); + _; + } + + function moveTokenfromPoolv2( + address _token, + address destinationAddress, + uint256 orderAmount + ) public onlyOwnerOrTokenSwapv2() returns (bool) { + InterfaceInverseToken token_ = InterfaceInverseToken(_token); + + token_.transfer(destinationAddress, orderAmount); + return true; + } + + function moveTokenToPoolv2( + address _token, + address whiteListedAddress, + uint256 orderAmount + ) public onlyOwnerOrTokenSwapv2() returns (bool) { + InterfaceInverseToken token_ = InterfaceInverseToken(_token); + require( + kycVerifier.isAddressWhitelisted(whiteListedAddress), + "only whitelisted address are allowed to move tokens to pool" + ); + + uint256 percentageNumerator = percentageOfFundsForColdStorage[0]; + uint256 percentageDenominator = percentageOfFundsForColdStorage[1]; + uint256 amountForColdStorage = orderAmount.mul(percentageNumerator).div( + percentageDenominator + ); + + token_.transferFrom(whiteListedAddress, address(this), orderAmount); + token_.transfer(coldStorage, amountForColdStorage); + + return true; + } +} diff --git a/contracts/utils/DateTimeLibrary.sol b/contracts/short-tokens/utils/DateTimeLibrary.sol similarity index 100% rename from contracts/utils/DateTimeLibrary.sol rename to contracts/short-tokens/utils/DateTimeLibrary.sol diff --git a/contracts/utils/Math.sol b/contracts/short-tokens/utils/Math.sol similarity index 100% rename from contracts/utils/Math.sol rename to contracts/short-tokens/utils/Math.sol diff --git a/flats/CalculatorLeverage.sol b/flats/CalculatorLeverage.sol new file mode 100644 index 0000000..6e609a2 --- /dev/null +++ b/flats/CalculatorLeverage.sol @@ -0,0 +1,1134 @@ + +// File: @openzeppelin/upgrades/contracts/Initializable.sol + +pragma solidity >=0.4.24 <0.6.0; + + +/** + * @title Initializable + * + * @dev Helper contract to support initializer functions. To use it, replace + * the constructor with a function that has the `initializer` modifier. + * WARNING: Unlike constructors, initializer functions must be manually + * invoked. This applies both to deploying an Initializable contract, as well + * as extending an Initializable contract via inheritance. + * WARNING: When used with inheritance, manual care must be taken to not invoke + * a parent initializer twice, or ensure that all initializers are idempotent, + * because this is not dealt with automatically as with constructors. + */ +contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private initializing; + + /** + * @dev Modifier to use in the initializer function of a contract. + */ + modifier initializer() { + require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized"); + + bool isTopLevelCall = !initializing; + if (isTopLevelCall) { + initializing = true; + initialized = true; + } + + _; + + if (isTopLevelCall) { + initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function isConstructor() private view returns (bool) { + // extcodesize checks the size of the code stored in an address, and + // address returns the current address. Since the code is still not + // deployed when running a constructor, any checks on its code size will + // yield zero, making it an effective way to detect if a contract is + // under construction or not. + uint256 cs; + assembly { cs := extcodesize(address) } + return cs == 0; + } + + // Reserved storage space to allow for layout changes in the future. + uint256[50] private ______gap; +} + +// File: @openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol + +pragma solidity ^0.5.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + * + * _Available since v2.4.0._ + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + * + * _Available since v2.4.0._ + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + * + * _Available since v2.4.0._ + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +// File: contracts/short-tokens/utils/Math.sol + +/// Math.sol -- mixin for inline numerical wizardry + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.5.0; + + +library DSMath { + + // --- Unsigned Math ---- + function add(uint x, uint y) internal pure returns (uint z) { + require((z = x + y) >= x, "ds-math-add-overflow"); + } + function sub(uint x, uint y) internal pure returns (uint z) { + require((z = x - y) <= x, "ds-math-sub-underflow"); + } + function mul(uint x, uint y) internal pure returns (uint z) { + require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); + } + + + function min(uint x, uint y) internal pure returns (uint z) { + return x <= y ? x : y; + } + function max(uint x, uint y) internal pure returns (uint z) { + return x >= y ? x : y; + } + function imin(int x, int y) internal pure returns (int z) { + return x <= y ? x : y; + } + function imax(int x, int y) internal pure returns (int z) { + return x >= y ? x : y; + } + + // --- Precise Math --- + + uint public constant WAD = 10 ** 18; + uint public constant RAY = 10 ** 27; + + function wmul(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, y), WAD / 2) / WAD; + } + function rmul(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, y), RAY / 2) / RAY; + } + function wdiv(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, WAD), y / 2) / y; + } + function rdiv(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, RAY), y / 2) / y; + } + + function ray(uint _wad) internal pure returns (uint) { + return mul(_wad, uint(10 ** 9)); + } + + // This famous algorithm is called "exponentiation by squaring" + // and calculates x^n with x as fixed-point and n as regular unsigned. + // + // It's O(log n), instead of O(n) for naive repeated multiplication. + // + // These facts are why it works: + // + // If n is even, then x^n = (x^2)^(n/2). + // If n is odd, then x^n = x * x^(n-1), + // and applying the equation for even x gives + // x^n = x * (x^2)^((n-1) / 2). + // + // Also, EVM division is flooring and + // floor[(n-1) / 2] = floor[n / 2]. + // + function rpow(uint x, uint n) internal pure returns (uint z) { + z = n % 2 != 0 ? x : RAY; + + for (n /= 2; n != 0; n /= 2) { + x = rmul(x, x); + + if (n % 2 != 0) { + z = rmul(z, x); + } + } + } +} + +// File: contracts/short-tokens/utils/DateTimeLibrary.sol + +pragma solidity ^0.5.0; + +// ---------------------------------------------------------------------------- +// BokkyPooBah's DateTime Library v1.01 +// +// A gas-efficient Solidity date and time library +// +// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary +// +// Tested date range 1970/01/01 to 2345/12/31 +// +// Conventions: +// Unit | Range | Notes +// :-------- |:-------------:|:----- +// timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC +// year | 1970 ... 2345 | +// month | 1 ... 12 | +// day | 1 ... 31 | +// hour | 0 ... 23 | +// minute | 0 ... 59 | +// second | 0 ... 59 | +// dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday +// +// +// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence. +// ---------------------------------------------------------------------------- + +library DateTimeLibrary { + uint256 constant SECONDS_PER_DAY = 24 * 60 * 60; + uint256 constant SECONDS_PER_HOUR = 60 * 60; + uint256 constant SECONDS_PER_MINUTE = 60; + int256 constant OFFSET19700101 = 2440588; + + uint256 constant DOW_MON = 1; + uint256 constant DOW_TUE = 2; + uint256 constant DOW_WED = 3; + uint256 constant DOW_THU = 4; + uint256 constant DOW_FRI = 5; + uint256 constant DOW_SAT = 6; + uint256 constant DOW_SUN = 7; + + // ------------------------------------------------------------------------ + // Calculate the number of days from 1970/01/01 to year/month/day using + // the date conversion algorithm from + // http://aa.usno.navy.mil/faq/docs/JD_Formula.php + // and subtracting the offset 2440588 so that 1970/01/01 is day 0 + // + // days = day + // - 32075 + // + 1461 * (year + 4800 + (month - 14) / 12) / 4 + // + 367 * (month - 2 - (month - 14) / 12 * 12) / 12 + // - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4 + // - offset + // ------------------------------------------------------------------------ + function _daysFromDate(uint256 year, uint256 month, uint256 day) + internal + pure + returns (uint256 _days) + { + require(year >= 1970); + int256 _year = int256(year); + int256 _month = int256(month); + int256 _day = int256(day); + + int256 __days = _day - + 32075 + + (1461 * (_year + 4800 + (_month - 14) / 12)) / + 4 + + (367 * (_month - 2 - ((_month - 14) / 12) * 12)) / + 12 - + (3 * ((_year + 4900 + (_month - 14) / 12) / 100)) / + 4 - + OFFSET19700101; + + _days = uint256(__days); + } + + // ------------------------------------------------------------------------ + // Calculate year/month/day from the number of days since 1970/01/01 using + // the date conversion algorithm from + // http://aa.usno.navy.mil/faq/docs/JD_Formula.php + // and adding the offset 2440588 so that 1970/01/01 is day 0 + // + // int L = days + 68569 + offset + // int N = 4 * L / 146097 + // L = L - (146097 * N + 3) / 4 + // year = 4000 * (L + 1) / 1461001 + // L = L - 1461 * year / 4 + 31 + // month = 80 * L / 2447 + // dd = L - 2447 * month / 80 + // L = month / 11 + // month = month + 2 - 12 * L + // year = 100 * (N - 49) + year + L + // ------------------------------------------------------------------------ + function _daysToDate(uint256 _days) + internal + pure + returns (uint256 year, uint256 month, uint256 day) + { + int256 __days = int256(_days); + + int256 L = __days + 68569 + OFFSET19700101; + int256 N = (4 * L) / 146097; + L = L - (146097 * N + 3) / 4; + int256 _year = (4000 * (L + 1)) / 1461001; + L = L - (1461 * _year) / 4 + 31; + int256 _month = (80 * L) / 2447; + int256 _day = L - (2447 * _month) / 80; + L = _month / 11; + _month = _month + 2 - 12 * L; + _year = 100 * (N - 49) + _year + L; + + year = uint256(_year); + month = uint256(_month); + day = uint256(_day); + } + + function daysFromDate(uint256 year, uint256 month, uint256 day) + internal + pure + returns (uint256 daysSinceDate) + { + daysSinceDate = _daysFromDate(year, month, day); + } + function timestampFromDate(uint256 year, uint256 month, uint256 day) + internal + pure + returns (uint256 timestamp) + { + timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY; + } + function timestampFromDateTime( + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second + ) internal pure returns (uint256 timestamp) { + timestamp = + _daysFromDate(year, month, day) * + SECONDS_PER_DAY + + hour * + SECONDS_PER_HOUR + + minute * + SECONDS_PER_MINUTE + + second; + } + function timestampToDate(uint256 timestamp) + internal + pure + returns (uint256 year, uint256 month, uint256 day) + { + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function timestampToDateTime(uint256 timestamp) + internal + pure + returns ( + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second + ) + { + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + uint256 secs = timestamp % SECONDS_PER_DAY; + hour = secs / SECONDS_PER_HOUR; + secs = secs % SECONDS_PER_HOUR; + minute = secs / SECONDS_PER_MINUTE; + second = secs % SECONDS_PER_MINUTE; + } + + function isValidDate(uint256 year, uint256 month, uint256 day) + internal + pure + returns (bool valid) + { + if (year >= 1970 && month > 0 && month <= 12) { + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > 0 && day <= daysInMonth) { + valid = true; + } + } + } + function isValidDateTime( + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second + ) internal pure returns (bool valid) { + if (isValidDate(year, month, day)) { + if (hour < 24 && minute < 60 && second < 60) { + valid = true; + } + } + } + function isLeapYear(uint256 timestamp) + internal + pure + returns (bool leapYear) + { + (uint256 year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); + leapYear = _isLeapYear(year); + } + function _isLeapYear(uint256 year) internal pure returns (bool leapYear) { + leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); + } + function isWeekDay(uint256 timestamp) internal pure returns (bool weekDay) { + weekDay = getDayOfWeek(timestamp) <= DOW_FRI; + } + function isWeekEnd(uint256 timestamp) internal pure returns (bool weekEnd) { + weekEnd = getDayOfWeek(timestamp) >= DOW_SAT; + } + function getDaysInMonth(uint256 timestamp) + internal + pure + returns (uint256 daysInMonth) + { + (uint256 year, uint256 month, ) = _daysToDate( + timestamp / SECONDS_PER_DAY + ); + daysInMonth = _getDaysInMonth(year, month); + } + function _getDaysInMonth(uint256 year, uint256 month) + internal + pure + returns (uint256 daysInMonth) + { + if ( + month == 1 || + month == 3 || + month == 5 || + month == 7 || + month == 8 || + month == 10 || + month == 12 + ) { + daysInMonth = 31; + } else if (month != 2) { + daysInMonth = 30; + } else { + daysInMonth = _isLeapYear(year) ? 29 : 28; + } + } + // 1 = Monday, 7 = Sunday + function getDayOfWeek(uint256 timestamp) + internal + pure + returns (uint256 dayOfWeek) + { + uint256 _days = timestamp / SECONDS_PER_DAY; + dayOfWeek = ((_days + 3) % 7) + 1; + } + + function getYear(uint256 timestamp) internal pure returns (uint256 year) { + (year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function getMonth(uint256 timestamp) internal pure returns (uint256 month) { + (, month, ) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function getDay(uint256 timestamp) internal pure returns (uint256 day) { + (, , day) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function getHour(uint256 timestamp) internal pure returns (uint256 hour) { + uint256 secs = timestamp % SECONDS_PER_DAY; + hour = secs / SECONDS_PER_HOUR; + } + function getMinute(uint256 timestamp) + internal + pure + returns (uint256 minute) + { + uint256 secs = timestamp % SECONDS_PER_HOUR; + minute = secs / SECONDS_PER_MINUTE; + } + function getSecond(uint256 timestamp) + internal + pure + returns (uint256 second) + { + second = timestamp % SECONDS_PER_MINUTE; + } + + function addYears(uint256 timestamp, uint256 _years) + internal + pure + returns (uint256 newTimestamp) + { + (uint256 year, uint256 month, uint256 day) = _daysToDate( + timestamp / SECONDS_PER_DAY + ); + year += _years; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = + _daysFromDate(year, month, day) * + SECONDS_PER_DAY + + (timestamp % SECONDS_PER_DAY); + require(newTimestamp >= timestamp); + } + function addMonths(uint256 timestamp, uint256 _months) + internal + pure + returns (uint256 newTimestamp) + { + (uint256 year, uint256 month, uint256 day) = _daysToDate( + timestamp / SECONDS_PER_DAY + ); + month += _months; + year += (month - 1) / 12; + month = ((month - 1) % 12) + 1; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = + _daysFromDate(year, month, day) * + SECONDS_PER_DAY + + (timestamp % SECONDS_PER_DAY); + require(newTimestamp >= timestamp); + } + function addDays(uint256 timestamp, uint256 _days) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp + _days * SECONDS_PER_DAY; + require(newTimestamp >= timestamp); + } + function addHours(uint256 timestamp, uint256 _hours) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp + _hours * SECONDS_PER_HOUR; + require(newTimestamp >= timestamp); + } + function addMinutes(uint256 timestamp, uint256 _minutes) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE; + require(newTimestamp >= timestamp); + } + function addSeconds(uint256 timestamp, uint256 _seconds) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp + _seconds; + require(newTimestamp >= timestamp); + } + + function subYears(uint256 timestamp, uint256 _years) + internal + pure + returns (uint256 newTimestamp) + { + (uint256 year, uint256 month, uint256 day) = _daysToDate( + timestamp / SECONDS_PER_DAY + ); + year -= _years; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = + _daysFromDate(year, month, day) * + SECONDS_PER_DAY + + (timestamp % SECONDS_PER_DAY); + require(newTimestamp <= timestamp); + } + function subMonths(uint256 timestamp, uint256 _months) + internal + pure + returns (uint256 newTimestamp) + { + (uint256 year, uint256 month, uint256 day) = _daysToDate( + timestamp / SECONDS_PER_DAY + ); + uint256 yearMonth = year * 12 + (month - 1) - _months; + year = yearMonth / 12; + month = (yearMonth % 12) + 1; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = + _daysFromDate(year, month, day) * + SECONDS_PER_DAY + + (timestamp % SECONDS_PER_DAY); + require(newTimestamp <= timestamp); + } + function subDays(uint256 timestamp, uint256 _days) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp - _days * SECONDS_PER_DAY; + require(newTimestamp <= timestamp); + } + function subHours(uint256 timestamp, uint256 _hours) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp - _hours * SECONDS_PER_HOUR; + require(newTimestamp <= timestamp); + } + function subMinutes(uint256 timestamp, uint256 _minutes) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE; + require(newTimestamp <= timestamp); + } + function subSeconds(uint256 timestamp, uint256 _seconds) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp - _seconds; + require(newTimestamp <= timestamp); + } + + function diffYears(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _years) + { + require(fromTimestamp <= toTimestamp); + (uint256 fromYear, , ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); + (uint256 toYear, , ) = _daysToDate(toTimestamp / SECONDS_PER_DAY); + _years = toYear - fromYear; + } + function diffMonths(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _months) + { + require(fromTimestamp <= toTimestamp); + (uint256 fromYear, uint256 fromMonth, ) = _daysToDate( + fromTimestamp / SECONDS_PER_DAY + ); + (uint256 toYear, uint256 toMonth, ) = _daysToDate( + toTimestamp / SECONDS_PER_DAY + ); + _months = toYear * 12 + toMonth - fromYear * 12 - fromMonth; + } + function diffDays(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _days) + { + require(fromTimestamp <= toTimestamp); + _days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY; + } + function diffHours(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _hours) + { + require(fromTimestamp <= toTimestamp); + _hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR; + } + function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _minutes) + { + require(fromTimestamp <= toTimestamp); + _minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE; + } + function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _seconds) + { + require(fromTimestamp <= toTimestamp); + _seconds = toTimestamp - fromTimestamp; + } +} + +// File: contracts/leverage-tokens/Abstract/InterfaceStorageLeverage.sol + +pragma solidity ^0.5.0; + + +interface InterfaceStorageLeverage { + function whitelistedAddresses(address) external view returns (bool); + + function isPaused() external view returns (bool); + + function isShutdown() external view returns (bool); + + function tokenSwapManager() external view returns (address); + + function bridge() external view returns (address); + + function managementFee() external view returns (uint256); + + function getExecutionPrice() external view returns (uint256); + + function getMarkPrice() external view returns (uint256); + + function getTokenValueAfterFees() external view returns (uint256); + + function getNotional() external view returns (uint256); + + function getTokenValue() external view returns (uint256); + + function getChangeInNotional() external view returns (int256); + + function getMintingFee(uint256 cash) external view returns (uint256); + + function minimumMintingFee() external view returns (uint256); + + function minRebalanceAmount() external view returns (uint8); + + function delayedRedemptionsByUser(address) external view returns (uint256); + + function setDelayedRedemptionsByUser( + uint256 amountToRedeem, + address whitelistedAddress + ) external; + + function setOrderByUser( + address whitelistedAddress, + string calldata orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice, + uint256 orderIndex, + bool overwrite + ) external; + + function setAccounting( + uint256 _tokenValueNetFees, + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + int256 _changeInNotional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) external; + + function setAccountingForLastActivityDay( + uint256 _tokenValueNetFees, + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + int256 _changeInNotional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) external; +} + +// File: contracts/short-tokens/Abstract/InterfaceInverseToken.sol + +pragma solidity ^0.5.0; + + +interface InterfaceInverseToken { + function mintTokens(address, uint256) external returns (bool); + + function burnTokens(address, uint256) external returns (bool); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) + external + returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) + external + view + returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) + external + returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); +} + +// File: contracts/leverage-tokens/CalculatorLeverage.sol + +pragma solidity ^0.5.0; + + + + + + + + +/** + * @dev uint256 are expected to use last 18 numbers as decimal points except when specifid differently in @params + */ +contract CalculatorLeverage is Initializable { + using SafeMath for uint256; + + InterfaceStorageLeverage public persistentStorage; + InterfaceInverseToken public inverseToken; + + function initialize( + address _persistentStorageAddress, + address _inverseTokenAddress + ) public initializer { + persistentStorage = InterfaceStorageLeverage(_persistentStorageAddress); + inverseToken = InterfaceInverseToken(_inverseTokenAddress); + } + + // Start fill in the values + // Creation 3x Long + + function getTokensCreatedByCash( + uint256 mintingPrice, + uint256 cash, + uint256 gasFee + ) public view returns (uint256 tokensCreated) { + // Cash Remove Gas Fee + // Cash Remove Minting Fee (Cash Proceeds) + // Cash Proceeds / Minting Price (# of Tokens to Mint) + + uint256 cashAfterGas = DSMath.sub(cash, gasFee); + uint256 cashAfterFee = removeCurrentMintingFeeFromCash(cashAfterGas); + + uint256 tokensMinted = DSMath.wdiv(cashAfterFee, mintingPrice); + + return tokensMinted; + } + + function getCashCreatedByTokens( + uint256 burningPrice, + uint256 elapsedTime, + uint256 tokens, + uint256 gasFee + ) public view returns (uint256 stablecoinAfterFees) { + uint256 stablecoin = DSMath.wmul(tokens, burningPrice); + uint256 stablecoinAfterGas = DSMath.sub(stablecoin, gasFee); + uint256 stablecoinRedeemed = removeCurrentMintingFeeFromCash( + stablecoinAfterGas + ); + uint256 managementFeeDaily = DSMath.wdiv( + persistentStorage.managementFee(), + 365 ether + ); + uint managementFeeHourly = DSMath.wdiv( + DSMath.wmul(managementFeeDaily, elapsedTime), + 24 ether + ); + uint256 normalizedManagementFee = DSMath.sub( + 1 ether, + DSMath.wdiv(managementFeeHourly, 100 ether) + ); + + stablecoinAfterFees = DSMath.wmul( + stablecoinRedeemed, + normalizedManagementFee + ); + } + + /** + * @dev Calculates all new values generated from rebalance + * @param _tokenValueNetFees The token value after fees are removed + * @param _bestExecutionPrice The best execution price for rebalancing + * @param _markPrice The Mark Price + * @param _notionalPreRebalance The notional amount before rebalance + * @param _targetLeverage The targetLeverage + */ + function calculateRebalanceValues( + uint256 _tokenValueNetFees, + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notionalPreRebalance, + uint256 _targetLeverage + ) + public + pure + returns ( + uint256 notional, + int256 changeInNotional, + int256 tokenValue + ) + { + uint256 valueOfNotional = DSMath.wmul( + _targetLeverage, + _tokenValueNetFees + ); + notional = DSMath.wdiv(valueOfNotional, _markPrice); + + changeInNotional = int256(notional - _notionalPreRebalance); + int256 priceSlippage = int256(_markPrice - _bestExecutionPrice); + int256 tokenSlippage = wmulInt256(priceSlippage, changeInNotional); + + tokenValue = int256(_tokenValueNetFees) + tokenSlippage; + } + + function removeCurrentMintingFeeFromCash(uint256 _cash) + public + view + returns (uint256 cashAfterFee) + { + uint256 creationFee = persistentStorage.getMintingFee(_cash); + uint256 minimumMintingFee = persistentStorage.minimumMintingFee(); + cashAfterFee = removeMintingFeeFromCash( + _cash, + creationFee, + minimumMintingFee + ); + } + + 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); + } + + int256 constant WAD = 10**18; + + function addInt256(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x + y) >= x, "ds-math-add-overflow"); + } + + function mulInt256(int256 x, int256 y) internal pure returns (int256 z) { + require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); + } + + function wmulInt256(int256 x, int256 y) internal pure returns (int256 z) { + z = addInt256(mulInt256(x, y), WAD / 2) / WAD; + } +} diff --git a/flats/CashPool.sol b/flats/CashPool.sol index 2c2e795..2a7b9a5 100644 --- a/flats/CashPool.sol +++ b/flats/CashPool.sol @@ -335,7 +335,7 @@ library SafeMath { } } -// File: contracts/Abstract/InterfaceInverseToken.sol +// File: contracts/short-tokens/Abstract/InterfaceInverseToken.sol pragma solidity ^0.5.0; @@ -426,598 +426,16 @@ interface InterfaceInverseToken { ); } -// File: contracts/utils/DateTimeLibrary.sol +// File: contracts/short-tokens/Abstract/InterfaceKYCVerifier.sol pragma solidity ^0.5.0; -// ---------------------------------------------------------------------------- -// BokkyPooBah's DateTime Library v1.01 -// -// A gas-efficient Solidity date and time library -// -// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary -// -// Tested date range 1970/01/01 to 2345/12/31 -// -// Conventions: -// Unit | Range | Notes -// :-------- |:-------------:|:----- -// timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC -// year | 1970 ... 2345 | -// month | 1 ... 12 | -// day | 1 ... 31 | -// hour | 0 ... 23 | -// minute | 0 ... 59 | -// second | 0 ... 59 | -// dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday -// -// -// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence. -// ---------------------------------------------------------------------------- - -library DateTimeLibrary { - uint256 constant SECONDS_PER_DAY = 24 * 60 * 60; - uint256 constant SECONDS_PER_HOUR = 60 * 60; - uint256 constant SECONDS_PER_MINUTE = 60; - int256 constant OFFSET19700101 = 2440588; - - uint256 constant DOW_MON = 1; - uint256 constant DOW_TUE = 2; - uint256 constant DOW_WED = 3; - uint256 constant DOW_THU = 4; - uint256 constant DOW_FRI = 5; - uint256 constant DOW_SAT = 6; - uint256 constant DOW_SUN = 7; - - // ------------------------------------------------------------------------ - // Calculate the number of days from 1970/01/01 to year/month/day using - // the date conversion algorithm from - // http://aa.usno.navy.mil/faq/docs/JD_Formula.php - // and subtracting the offset 2440588 so that 1970/01/01 is day 0 - // - // days = day - // - 32075 - // + 1461 * (year + 4800 + (month - 14) / 12) / 4 - // + 367 * (month - 2 - (month - 14) / 12 * 12) / 12 - // - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4 - // - offset - // ------------------------------------------------------------------------ - function _daysFromDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (uint256 _days) - { - require(year >= 1970); - int256 _year = int256(year); - int256 _month = int256(month); - int256 _day = int256(day); - - int256 __days = _day - - 32075 + - (1461 * (_year + 4800 + (_month - 14) / 12)) / - 4 + - (367 * (_month - 2 - ((_month - 14) / 12) * 12)) / - 12 - - (3 * ((_year + 4900 + (_month - 14) / 12) / 100)) / - 4 - - OFFSET19700101; - - _days = uint256(__days); - } - - // ------------------------------------------------------------------------ - // Calculate year/month/day from the number of days since 1970/01/01 using - // the date conversion algorithm from - // http://aa.usno.navy.mil/faq/docs/JD_Formula.php - // and adding the offset 2440588 so that 1970/01/01 is day 0 - // - // int L = days + 68569 + offset - // int N = 4 * L / 146097 - // L = L - (146097 * N + 3) / 4 - // year = 4000 * (L + 1) / 1461001 - // L = L - 1461 * year / 4 + 31 - // month = 80 * L / 2447 - // dd = L - 2447 * month / 80 - // L = month / 11 - // month = month + 2 - 12 * L - // year = 100 * (N - 49) + year + L - // ------------------------------------------------------------------------ - function _daysToDate(uint256 _days) - internal - pure - returns (uint256 year, uint256 month, uint256 day) - { - int256 __days = int256(_days); - - int256 L = __days + 68569 + OFFSET19700101; - int256 N = (4 * L) / 146097; - L = L - (146097 * N + 3) / 4; - int256 _year = (4000 * (L + 1)) / 1461001; - L = L - (1461 * _year) / 4 + 31; - int256 _month = (80 * L) / 2447; - int256 _day = L - (2447 * _month) / 80; - L = _month / 11; - _month = _month + 2 - 12 * L; - _year = 100 * (N - 49) + _year + L; - - year = uint256(_year); - month = uint256(_month); - day = uint256(_day); - } - - function daysFromDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (uint256 daysSinceDate) - { - daysSinceDate = _daysFromDate(year, month, day); - } - function timestampFromDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (uint256 timestamp) - { - timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY; - } - function timestampFromDateTime( - uint256 year, - uint256 month, - uint256 day, - uint256 hour, - uint256 minute, - uint256 second - ) internal pure returns (uint256 timestamp) { - timestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - hour * - SECONDS_PER_HOUR + - minute * - SECONDS_PER_MINUTE + - second; - } - function timestampToDate(uint256 timestamp) - internal - pure - returns (uint256 year, uint256 month, uint256 day) - { - (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function timestampToDateTime(uint256 timestamp) - internal - pure - returns ( - uint256 year, - uint256 month, - uint256 day, - uint256 hour, - uint256 minute, - uint256 second - ) - { - (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); - uint256 secs = timestamp % SECONDS_PER_DAY; - hour = secs / SECONDS_PER_HOUR; - secs = secs % SECONDS_PER_HOUR; - minute = secs / SECONDS_PER_MINUTE; - second = secs % SECONDS_PER_MINUTE; - } - - function isValidDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (bool valid) - { - if (year >= 1970 && month > 0 && month <= 12) { - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > 0 && day <= daysInMonth) { - valid = true; - } - } - } - function isValidDateTime( - uint256 year, - uint256 month, - uint256 day, - uint256 hour, - uint256 minute, - uint256 second - ) internal pure returns (bool valid) { - if (isValidDate(year, month, day)) { - if (hour < 24 && minute < 60 && second < 60) { - valid = true; - } - } - } - function isLeapYear(uint256 timestamp) - internal - pure - returns (bool leapYear) - { - (uint256 year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); - leapYear = _isLeapYear(year); - } - function _isLeapYear(uint256 year) internal pure returns (bool leapYear) { - leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); - } - function isWeekDay(uint256 timestamp) internal pure returns (bool weekDay) { - weekDay = getDayOfWeek(timestamp) <= DOW_FRI; - } - function isWeekEnd(uint256 timestamp) internal pure returns (bool weekEnd) { - weekEnd = getDayOfWeek(timestamp) >= DOW_SAT; - } - function getDaysInMonth(uint256 timestamp) - internal - pure - returns (uint256 daysInMonth) - { - (uint256 year, uint256 month, ) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - daysInMonth = _getDaysInMonth(year, month); - } - function _getDaysInMonth(uint256 year, uint256 month) - internal - pure - returns (uint256 daysInMonth) - { - if ( - month == 1 || - month == 3 || - month == 5 || - month == 7 || - month == 8 || - month == 10 || - month == 12 - ) { - daysInMonth = 31; - } else if (month != 2) { - daysInMonth = 30; - } else { - daysInMonth = _isLeapYear(year) ? 29 : 28; - } - } - // 1 = Monday, 7 = Sunday - function getDayOfWeek(uint256 timestamp) - internal - pure - returns (uint256 dayOfWeek) - { - uint256 _days = timestamp / SECONDS_PER_DAY; - dayOfWeek = ((_days + 3) % 7) + 1; - } - - function getYear(uint256 timestamp) internal pure returns (uint256 year) { - (year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function getMonth(uint256 timestamp) internal pure returns (uint256 month) { - (, month, ) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function getDay(uint256 timestamp) internal pure returns (uint256 day) { - (, , day) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function getHour(uint256 timestamp) internal pure returns (uint256 hour) { - uint256 secs = timestamp % SECONDS_PER_DAY; - hour = secs / SECONDS_PER_HOUR; - } - function getMinute(uint256 timestamp) - internal - pure - returns (uint256 minute) - { - uint256 secs = timestamp % SECONDS_PER_HOUR; - minute = secs / SECONDS_PER_MINUTE; - } - function getSecond(uint256 timestamp) - internal - pure - returns (uint256 second) - { - second = timestamp % SECONDS_PER_MINUTE; - } - - function addYears(uint256 timestamp, uint256 _years) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - year += _years; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp >= timestamp); - } - function addMonths(uint256 timestamp, uint256 _months) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - month += _months; - year += (month - 1) / 12; - month = ((month - 1) % 12) + 1; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp >= timestamp); - } - function addDays(uint256 timestamp, uint256 _days) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _days * SECONDS_PER_DAY; - require(newTimestamp >= timestamp); - } - function addHours(uint256 timestamp, uint256 _hours) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _hours * SECONDS_PER_HOUR; - require(newTimestamp >= timestamp); - } - function addMinutes(uint256 timestamp, uint256 _minutes) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE; - require(newTimestamp >= timestamp); - } - function addSeconds(uint256 timestamp, uint256 _seconds) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _seconds; - require(newTimestamp >= timestamp); - } - - function subYears(uint256 timestamp, uint256 _years) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - year -= _years; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp <= timestamp); - } - function subMonths(uint256 timestamp, uint256 _months) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - uint256 yearMonth = year * 12 + (month - 1) - _months; - year = yearMonth / 12; - month = (yearMonth % 12) + 1; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp <= timestamp); - } - function subDays(uint256 timestamp, uint256 _days) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _days * SECONDS_PER_DAY; - require(newTimestamp <= timestamp); - } - function subHours(uint256 timestamp, uint256 _hours) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _hours * SECONDS_PER_HOUR; - require(newTimestamp <= timestamp); - } - function subMinutes(uint256 timestamp, uint256 _minutes) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE; - require(newTimestamp <= timestamp); - } - function subSeconds(uint256 timestamp, uint256 _seconds) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _seconds; - require(newTimestamp <= timestamp); - } - - function diffYears(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _years) - { - require(fromTimestamp <= toTimestamp); - (uint256 fromYear, , ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); - (uint256 toYear, , ) = _daysToDate(toTimestamp / SECONDS_PER_DAY); - _years = toYear - fromYear; - } - function diffMonths(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _months) - { - require(fromTimestamp <= toTimestamp); - (uint256 fromYear, uint256 fromMonth, ) = _daysToDate( - fromTimestamp / SECONDS_PER_DAY - ); - (uint256 toYear, uint256 toMonth, ) = _daysToDate( - toTimestamp / SECONDS_PER_DAY - ); - _months = toYear * 12 + toMonth - fromYear * 12 - fromMonth; - } - function diffDays(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _days) - { - require(fromTimestamp <= toTimestamp); - _days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY; - } - function diffHours(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _hours) - { - require(fromTimestamp <= toTimestamp); - _hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR; - } - function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _minutes) - { - require(fromTimestamp <= toTimestamp); - _minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE; - } - function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _seconds) - { - require(fromTimestamp <= toTimestamp); - _seconds = toTimestamp - fromTimestamp; - } -} - -// File: contracts/utils/Math.sol - -/// Math.sol -- mixin for inline numerical wizardry - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma solidity ^0.5.0; - - -library DSMath { - - // --- Unsigned Math ---- - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x, "ds-math-add-overflow"); - } - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x, "ds-math-sub-underflow"); - } - function mul(uint x, uint y) internal pure returns (uint z) { - require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); - } - - - function min(uint x, uint y) internal pure returns (uint z) { - return x <= y ? x : y; - } - function max(uint x, uint y) internal pure returns (uint z) { - return x >= y ? x : y; - } - function imin(int x, int y) internal pure returns (int z) { - return x <= y ? x : y; - } - function imax(int x, int y) internal pure returns (int z) { - return x >= y ? x : y; - } - // --- Precise Math --- - - uint public constant WAD = 10 ** 18; - uint public constant RAY = 10 ** 27; - - function wmul(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, y), WAD / 2) / WAD; - } - function rmul(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, y), RAY / 2) / RAY; - } - function wdiv(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, WAD), y / 2) / y; - } - function rdiv(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, RAY), y / 2) / y; - } - - function ray(uint _wad) internal pure returns (uint) { - return mul(_wad, uint(10 ** 9)); - } - - // This famous algorithm is called "exponentiation by squaring" - // and calculates x^n with x as fixed-point and n as regular unsigned. - // - // It's O(log n), instead of O(n) for naive repeated multiplication. - // - // These facts are why it works: - // - // If n is even, then x^n = (x^2)^(n/2). - // If n is odd, then x^n = x * x^(n-1), - // and applying the equation for even x gives - // x^n = x * (x^2)^((n-1) / 2). - // - // Also, EVM division is flooring and - // floor[(n-1) / 2] = floor[n / 2]. - // - function rpow(uint x, uint n) internal pure returns (uint z) { - z = n % 2 != 0 ? x : RAY; - - for (n /= 2; n != 0; n /= 2) { - x = rmul(x, x); - - if (n % 2 != 0) { - z = rmul(z, x); - } - } - } +interface InterfaceKYCVerifier { + function isAddressWhitelisted(address) external view returns (bool); } -// File: contracts/PersistentStorage.sol +// File: contracts/short-tokens/CashPool.sol pragma solidity ^0.5.0; @@ -1025,504 +443,124 @@ pragma solidity ^0.5.0; -contract PersistentStorage is Ownable { - address public tokenSwapManager; - address public bridge; - - bool public isPaused; - bool public isShutdown; - struct Accounting { - uint256 price; - uint256 cashPositionPerTokenUnit; - uint256 balancePerTokenUnit; - uint256 lendingFee; - } - - struct Order { - string orderType; - uint256 tokensGiven; - uint256 tokensRecieved; - uint256 avgBlendedFee; - } - - uint256 public lastActivityDay; - uint256 public minRebalanceAmount; - uint256 private managementFee; - - mapping(uint256 => Accounting[]) private accounting; - - mapping(address => bool) public whitelistedAddresses; +contract CashPool is Ownable { + using SafeMath for uint256; + InterfaceKYCVerifier public kycVerifier; - uint256[] public mintingFeeBracket; - mapping(uint256 => uint256) public mintingFee; + uint256[2] public percentageOfFundsForColdStorage; + address public coldStorage; - Order[] public allOrders; - mapping(address => Order[]) public orderByUser; - mapping(address => uint256) public delayedRedemptionsByUser; + mapping(address => bool) public approvedTokenManagers; - event AccountingValuesSet(uint256 today); - event RebalanceValuesSet(uint256 newMinRebalanceAmount); - event ManagementFeeValuesSet(uint256 newManagementFee); + event SetPercentageOfFundsForColdStorageEvent( + uint256[2] newPercentageOfFundsForColdStorage + ); function initialize( address ownerAddress, - uint256 _managementFee, - uint256 _minRebalanceAmount + address _kycVerifier, + address _coldStorage, + uint256[2] memory _percentageOfFundsForColdStorage ) 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 - } - - function setTokenSwapManager(address _tokenSwapManager) public onlyOwner { - require(_tokenSwapManager != address(0), "adddress must not be empty"); - tokenSwapManager = _tokenSwapManager; - } - - function setBridge(address _bridge) public onlyOwner { - require(_bridge != address(0), "adddress must not be empty"); - bridge = _bridge; - } - - function setIsPaused(bool _isPaused) public onlyOwner { - isPaused = _isPaused; - } - - function shutdown() public onlyOwner { - isShutdown = true; - } - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwnerOrTokenSwap() { require( - isOwner() || _msgSender() == tokenSwapManager, - "caller is not the owner or token swap manager" - ); - _; - } - - function setDelayedRedemptionsByUser( - uint256 amountToRedeem, - address whitelistedAddress - ) public onlyOwnerOrTokenSwap { - delayedRedemptionsByUser[whitelistedAddress] = amountToRedeem; - } - - function getDelayedRedemptionsByUser(address whitelistedAddress) - public - view - returns (uint256) - { - return delayedRedemptionsByUser[whitelistedAddress]; - } - - /* - * Saves order in mapping (address => Order[]) orderByUser - * overwrite == false, append to Order[] - * overwrite == true, overwrite element at orderIndex - */ - - function setOrderByUser( - address whitelistedAddress, - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee, - uint256 orderIndex, - bool overwrite - ) public onlyOwnerOrTokenSwap() { - Order memory newOrder = Order( - orderType, - tokensGiven, - tokensRecieved, - avgBlendedFee - ); - - if (!overwrite) { - orderByUser[whitelistedAddress].push(newOrder); - setOrder( - orderType, - tokensGiven, - tokensRecieved, - avgBlendedFee, - orderIndex, - overwrite - ); - } else { - orderByUser[whitelistedAddress][orderIndex] = newOrder; - } - } - - /* - * Gets Order[] For User Address - * Return order at Index in Order[] - */ - - function getOrderByUser(address whitelistedAddress, uint256 orderIndex) - public - view - returns ( - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee - ) - { - - Order storage orderAtIndex - = orderByUser[whitelistedAddress][orderIndex]; - return ( - orderAtIndex.orderType, - orderAtIndex.tokensGiven, - orderAtIndex.tokensRecieved, - orderAtIndex.avgBlendedFee - ); - } - - /* - * Save order to allOrders array - * overwrite == false, append to allOrders array - * overwrite == true, overwrite element at orderIndex - */ - function setOrder( - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee, - uint256 orderIndex, - bool overwrite - ) public onlyOwnerOrTokenSwap() { - Order memory newOrder = Order( - orderType, - tokensGiven, - tokensRecieved, - avgBlendedFee - ); - - if (!overwrite) { - allOrders.push(newOrder); - } else { - allOrders[orderIndex] = newOrder; - } - } - - /* - * Get Order - */ - function getOrder(uint256 index) - public - view - returns ( - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee - ) - { - Order storage orderAtIndex = allOrders[index]; - return ( - orderAtIndex.orderType, - orderAtIndex.tokensGiven, - orderAtIndex.tokensRecieved, - orderAtIndex.avgBlendedFee + ownerAddress != address(0) && + _kycVerifier != address(0) && + _coldStorage != address(0) && + _percentageOfFundsForColdStorage[1] != 0, + "params variables cannot be empty but _percentageOfFundsForColdStorage[0]" ); - } - - // @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" + _percentageOfFundsForColdStorage[0] <= + _percentageOfFundsForColdStorage[1], + "cannot set more than 100% for coldstorage" ); - - delete whitelistedAddresses[addressToRemove]; - } - - // @dev Updates whitelisted addresses - function updateWhitelistedAddress(address oldAddress, address newAddress) - public - { - removeWhitelistedAddress(oldAddress); - setWhitelistedAddress(newAddress); + initialize(ownerAddress); + kycVerifier = InterfaceKYCVerifier(_kycVerifier); + coldStorage = _coldStorage; + percentageOfFundsForColdStorage = _percentageOfFundsForColdStorage; } - // @dev Get accounting values for a specific day - // @param date format as 20200123 for 23th of January 2020 - function getAccounting(uint256 date) - public - view - returns (uint256, uint256, uint256, uint256) - { - return ( - accounting[date][accounting[date].length - 1].price, - accounting[date][accounting[date].length - 1] - .cashPositionPerTokenUnit, - accounting[date][accounting[date].length - 1].balancePerTokenUnit, - accounting[date][accounting[date].length - 1].lendingFee - ); + function getBalance(address _token) public view returns (uint256) { + InterfaceInverseToken token_ = InterfaceInverseToken(_token); + uint256 tokenBalance = token_.balanceOf(address(this)); + return tokenBalance; } - // @dev Set accounting values for the day - function setAccounting( - uint256 _price, - uint256 _cashPositionPerTokenUnit, - uint256 _balancePerTokenUnit, - uint256 _lendingFee - ) external onlyOwnerOrTokenSwap() { - (uint256 year, uint256 month, uint256 day) = DateTimeLibrary - .timestampToDate(block.timestamp); - uint256 today = year * 10000 + month * 100 + day; - accounting[today].push( - Accounting( - _price, - _cashPositionPerTokenUnit, - _balancePerTokenUnit, - _lendingFee - ) - ); - lastActivityDay = today; - emit AccountingValuesSet(today); + // @dev Sets new coldStorage + // @param _newColdStorage Address for new cold storage wallet + function setColdStorage(address _newColdStorage) public onlyOwner { + require(_newColdStorage != address(0), "address cannot be empty"); + coldStorage = _newColdStorage; } - // @dev Set accounting values for the day - function setAccountingForLastActivityDay( - uint256 _price, - uint256 _cashPositionPerTokenUnit, - uint256 _balancePerTokenUnit, - uint256 _lendingFee - ) external onlyOwnerOrTokenSwap() { - accounting[lastActivityDay].push( - Accounting( - _price, - _cashPositionPerTokenUnit, - _balancePerTokenUnit, - _lendingFee - ) + // @dev Sets percentage of funds to stay in contract. Owner only + // @param _newPercentageOfFundsForColdStorage List with two elements referencing percentage of funds for cold storage as a fraction + // e.g. 1/2 is [1,2] + function setPercentageOfFundsForColdStorage( + uint256[2] memory _newPercentageOfFundsForColdStorage + ) public onlyOwner { + require( + _newPercentageOfFundsForColdStorage[1] != 0, + "denominator should not be zero" ); - emit AccountingValuesSet(lastActivityDay); - } - - // @dev Set last rebalance information - function setMinRebalanceAmount(uint256 _minRebalanceAmount) - external - onlyOwner - { - minRebalanceAmount = _minRebalanceAmount; - - emit RebalanceValuesSet(minRebalanceAmount); - } - - // @dev Set last rebalance information - function setManagementFee(uint256 _managementFee) external onlyOwner { - managementFee = _managementFee; - emit ManagementFeeValuesSet(managementFee); - } - - // @dev Returns price - function getPrice() public view returns (uint256 price) { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .price; - } - - // @dev Returns cash position amount - function getCashPositionPerTokenUnit() - public - view - returns (uint256 amount) - { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .cashPositionPerTokenUnit; - } - - // @dev Returns borrowed crypto amount - function getBalancePerTokenUnit() public view returns (uint256 amount) { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .balancePerTokenUnit; - } - - // @dev Returns lending fee - function getLendingFee() public view returns (uint256 lendingRate) { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .lendingFee; - } - - // @dev Returns lending fee - function getManagementFee() public view returns (uint256 lendingRate) { - return managementFee; - } - - // @dev Sets last minting fee - function setLastMintingFee(uint256 _mintingFee) public onlyOwner { - mintingFee[~uint256(0)] = _mintingFee; - } - - // @dev Adds minting fee - function addMintingFeeBracket(uint256 _mintingFeeLimit, uint256 _mintingFee) - public - onlyOwner - { require( - _mintingFeeLimit > mintingFeeBracket[mintingFeeBracket.length - 1], - "New minting fee bracket needs to be bigger then last one" + _newPercentageOfFundsForColdStorage[0] <= + _newPercentageOfFundsForColdStorage[1], + "cannot set more than 100% for coldstorage" ); - mintingFeeBracket.push(_mintingFeeLimit); - mintingFee[_mintingFeeLimit] = _mintingFee; - } - - // @dev Deletes last minting fee - function deleteLastMintingFeeBracket() public onlyOwner { - delete mintingFee[mintingFeeBracket[mintingFeeBracket.length - 1]]; - mintingFeeBracket.length--; - } + percentageOfFundsForColdStorage[0] = _newPercentageOfFundsForColdStorage[0]; + percentageOfFundsForColdStorage[1] = _newPercentageOfFundsForColdStorage[1]; - // @dev Changes minting fee - function changeMintingLimit( - uint256 _position, - uint256 _mintingFeeLimit, - uint256 _mintingFee - ) public onlyOwner { - require( - _mintingFeeLimit > mintingFeeBracket[mintingFeeBracket.length - 1], - "New minting fee bracket needs to be bigger then last one" + emit SetPercentageOfFundsForColdStorageEvent( + _newPercentageOfFundsForColdStorage ); - if (_position != 0) { - require( - _mintingFeeLimit > mintingFeeBracket[_position - 1], - "New minting fee bracket needs to be bigger then last one" - ); - } - if (_position < mintingFeeBracket.length - 1) { - require( - _mintingFeeLimit < mintingFeeBracket[_position + 1], - "New minting fee bracket needs to be smaller then next one" - ); - } - mintingFeeBracket[_position] = _mintingFeeLimit; - mintingFee[_mintingFeeLimit] = _mintingFee; } - function getMintingFee(uint256 cash) public view returns (uint256) { - // Define Start + End Index - uint256 startIndex = 0; - uint256 endIndex = mintingFeeBracket.length - 1; - uint256 middleIndex = endIndex / 2; + // ############################################## Add/Remove ############################################## // + // ############################################## Token ################################################### // + // ############################################## Manager ################################################# // - if (cash <= mintingFeeBracket[middleIndex]) { - endIndex = middleIndex; - } else { - startIndex = middleIndex + 1; - } - - for (uint256 i = startIndex; i <= endIndex; i++) { - if (cash <= mintingFeeBracket[i]) { - return mintingFee[mintingFeeBracket[i]]; - } - } - return mintingFee[~uint256(0)]; + function addTokenManager(address tokenManager) public onlyOwner { + require(tokenManager != address(0), "adddress must not be empty"); + approvedTokenManagers[tokenManager] = true; } -} - -// File: contracts/Abstract/InterfaceStorage.sol - -pragma solidity ^0.5.0; - - -interface InterfaceStorage { - function whitelistedAddresses(address) external view returns (bool); -} - -// File: contracts/KYCVerifier.sol - -pragma solidity ^0.5.0; - - - -contract KYCVerifier is Initializable { - InterfaceStorage public persistentStorage; - - function initialize(address _persistentStorage) public initializer { - persistentStorage = InterfaceStorage(_persistentStorage); + function removeTokenManager(address tokenManager) public onlyOwner { + require(tokenManager != address(0), "adddress must not be empty"); + delete approvedTokenManagers[tokenManager]; } - function isAddressWhitelisted(address userAddress) + function isTokenManager(address potentialAddress) public view returns (bool) { - return persistentStorage.whitelistedAddresses(userAddress); + require(potentialAddress != address(0), "adddress must not be empty"); + return approvedTokenManagers[potentialAddress]; } -} - -// File: contracts/CashPool.sol - -pragma solidity ^0.5.0; - - - - - - -contract CashPool is Ownable { - using SafeMath for uint256; - KYCVerifier public kycVerifier; - PersistentStorage public persistentStorage; - - uint256[2] public percentageOfFundsForColdStorage; - address public coldStorage; - - event SetPercentageOfFundsForColdStorageEvent( - uint256[2] newPercentageOfFundsForColdStorage - ); - - function initialize( - address ownerAddress, - address _kycVerifier, - address _persistentStorage, - address _coldStorage, - uint256[2] memory _percentageOfFundsForColdStorage - ) public initializer { + modifier onlyOwnerOrTokenSwap() { require( - ownerAddress != address(0) && - _kycVerifier != address(0) && - _coldStorage != address(0) && - _percentageOfFundsForColdStorage[1] != 0, - "params variables cannot be empty but _percentageOfFundsForColdStorage[0]" + isOwner() || approvedTokenManagers[_msgSender()] == true, + "caller is not the owner or an approved token swap manager" ); - initialize(ownerAddress); - kycVerifier = KYCVerifier(_kycVerifier); - persistentStorage = PersistentStorage(_persistentStorage); - coldStorage = _coldStorage; - percentageOfFundsForColdStorage = _percentageOfFundsForColdStorage; + _; } - function getBalance(address _token) public view returns (uint256) { + // @dev Move tokens out of cash pool + // @param _token ERC20 address + // @param destinationAddress address to send to + // @param orderAmount amount to transfer from cash pool + function moveTokenfromPool( + address _token, + address destinationAddress, + uint256 orderAmount + ) public onlyOwnerOrTokenSwap() returns (bool) { InterfaceInverseToken token_ = InterfaceInverseToken(_token); - uint256 tokenBalance = token_.balanceOf(address(this)); - return tokenBalance; + + token_.transfer(destinationAddress, orderAmount); + return true; } // @dev Move tokens to cash pool @@ -1551,57 +589,4 @@ contract CashPool is Ownable { return true; } - - // @dev Move tokens out of cash pool - // @param _token ERC20 address - // @param destinationAddress address to send to - // @param orderAmount amount to transfer from cash pool - function moveTokenfromPool( - address _token, - address destinationAddress, - uint256 orderAmount - ) public onlyOwnerOrTokenSwap() returns (bool) { - InterfaceInverseToken token_ = InterfaceInverseToken(_token); - - token_.transfer(destinationAddress, orderAmount); - return true; - } - - modifier onlyOwnerOrTokenSwap() { - require( - isOwner() || _msgSender() == persistentStorage.tokenSwapManager(), - "caller is not the owner or token swap manager" - ); - _; - } - - // @dev Sets new coldStorage - // @param _newColdStorage Address for new cold storage wallet - function setColdStorage(address _newColdStorage) public onlyOwner { - require(_newColdStorage != address(0), "address cannot be empty"); - coldStorage = _newColdStorage; - } - - // @dev Sets percentage of funds to stay in contract. Owner only - // @param _newPercentageOfFundsForColdStorage List with two elements referencing percentage of funds for cold storage as a fraction - // e.g. 1/2 is [1,2] - function setPercentageOfFundsForColdStorage( - uint256[2] memory _newPercentageOfFundsForColdStorage - ) public onlyOwner { - require( - _newPercentageOfFundsForColdStorage[1] != 0, - "denominator should not be zero" - ); - require( - _newPercentageOfFundsForColdStorage[0] <= - _newPercentageOfFundsForColdStorage[1], - "cannot set more than 100% for coldstorage" - ); - percentageOfFundsForColdStorage[0] = _newPercentageOfFundsForColdStorage[0]; - percentageOfFundsForColdStorage[1] = _newPercentageOfFundsForColdStorage[1]; - - emit SetPercentageOfFundsForColdStorageEvent( - _newPercentageOfFundsForColdStorage - ); - } } diff --git a/flats/CompositionCalculator.sol b/flats/CompositionCalculator.sol index 4fce2e6..3f97744 100644 --- a/flats/CompositionCalculator.sol +++ b/flats/CompositionCalculator.sol @@ -222,7 +222,7 @@ library SafeMath { } } -// File: contracts/utils/Math.sol +// File: contracts/short-tokens/utils/Math.sol /// Math.sol -- mixin for inline numerical wizardry @@ -319,7 +319,7 @@ library DSMath { } } -// File: contracts/utils/DateTimeLibrary.sol +// File: contracts/short-tokens/utils/DateTimeLibrary.sol pragma solidity ^0.5.0; @@ -926,7 +926,7 @@ contract Ownable is Initializable, Context { uint256[50] private ______gap; } -// File: contracts/PersistentStorage.sol +// File: contracts/short-tokens/PersistentStorage.sol pragma solidity ^0.5.0; @@ -957,11 +957,13 @@ contract PersistentStorage is Ownable { uint256 public lastActivityDay; uint256 public minRebalanceAmount; - uint256 private managementFee; + uint256 public managementFee; + uint256 public minimumMintingFee; + uint256 public minimumTrade; - mapping(uint256 => Accounting[]) private accounting; + uint8 public balancePrecision; - mapping(address => bool) public whitelistedAddresses; + mapping(uint256 => Accounting[]) private accounting; uint256[] public mintingFeeBracket; mapping(uint256 => uint256) public mintingFee; @@ -977,16 +979,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 + mintingFee[~uint256(0)] = _lastMintingFee; + balancePrecision = _balancePrecision; + minimumMintingFee = _minimumMintingFee; + minimumTrade = _minimumTrade; } function setTokenSwapManager(address _tokenSwapManager) public onlyOwner { @@ -1018,6 +1023,14 @@ contract PersistentStorage is Ownable { _; } + modifier onlyOwnerOrBridge() { + require( + isOwner() || _msgSender() == bridge, + "caller is not the owner or bridge" + ); + _; + } + function setDelayedRedemptionsByUser( uint256 amountToRedeem, address whitelistedAddress @@ -1025,14 +1038,6 @@ contract PersistentStorage is Ownable { delayedRedemptionsByUser[whitelistedAddress] = amountToRedeem; } - function getDelayedRedemptionsByUser(address whitelistedAddress) - public - view - returns (uint256) - { - return delayedRedemptionsByUser[whitelistedAddress]; - } - /* * Saves order in mapping (address => Order[]) orderByUser * overwrite == false, append to Order[] @@ -1145,34 +1150,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) @@ -1277,11 +1254,6 @@ contract PersistentStorage is Ownable { .lendingFee; } - // @dev Returns lending fee - function getManagementFee() public view returns (uint256 lendingRate) { - return managementFee; - } - // @dev Sets last minting fee function setLastMintingFee(uint256 _mintingFee) public onlyOwner { mintingFee[~uint256(0)] = _mintingFee; @@ -1293,7 +1265,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); @@ -1351,9 +1325,24 @@ contract PersistentStorage is Ownable { } return mintingFee[~uint256(0)]; } + + // @dev Sets last balance precision + 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; + } } -// File: contracts/Abstract/InterfaceInverseToken.sol +// File: contracts/short-tokens/Abstract/InterfaceInverseToken.sol pragma solidity ^0.5.0; @@ -1444,7 +1433,7 @@ interface InterfaceInverseToken { ); } -// File: contracts/CompositionCalculator.sol +// File: contracts/short-tokens/CompositionCalculator.sol pragma solidity ^0.5.0; @@ -1561,6 +1550,7 @@ contract CompositionCalculator is Initializable { * @param _lendingFee The yearly average lending fee for borrowed balance * @param _days The days since the last fee calculation (Natural number) * @param _minRebalanceAmount The minimum amount to rebalance + * @param _changeInBalancePrecision The change in balance precision */ function calculatePCF( uint256 _cashPosition, @@ -1568,7 +1558,8 @@ contract CompositionCalculator is Initializable { uint256 _price, uint256 _lendingFee, uint256 _days, - uint256 _minRebalanceAmount + uint256 _minRebalanceAmount, + uint256 _changeInBalancePrecision ) public pure @@ -1614,12 +1605,16 @@ contract CompositionCalculator is Initializable { _balance, _price ); - + changeInBalance = floor(changeInBalance, _changeInBalancePrecision); if (changeInBalance < _minRebalanceAmount) { changeInBalance = 0; endBalance = _balance; } - + endBalance = addOrSub( + _balance, //cashPositionWithoutFee + changeInBalance, + isChangeInBalanceNeg + ); //result endCashPosition = addOrSub( endCashPosition, //cashPositionWithoutFee @@ -1639,6 +1634,7 @@ contract CompositionCalculator is Initializable { * @param _price The momentary price of the crypto * @param _lendingFee The yearly average lending fee for borrowed balance * @param _days The days since the last fee calculation (Natural number) + * @param _changeInBalancePrecision The change in balance precision */ function calculatePCFWithoutMin( //getDeliverables @@ -1646,7 +1642,8 @@ contract CompositionCalculator is Initializable { uint256 _balance, uint256 _price, uint256 _lendingFee, - uint256 _days + uint256 _days, + uint256 _changeInBalancePrecision ) public pure @@ -1666,7 +1663,8 @@ contract CompositionCalculator is Initializable { _price, _lendingFee, _days, - 0 + 0, + _changeInBalancePrecision ); } @@ -1688,22 +1686,15 @@ contract CompositionCalculator is Initializable { ) public pure returns (uint256 tokenAmountCreated) { require(_spotPrice != 0, "Price cant be zero"); require(_totalTokenSupply != 0, "Token supply cant be zero"); - uint256 netTokenValue = getNetTokenValue( _cashPosition, _balance, _spotPrice ); - uint256 netTokenValueTimesTokenAmount = DSMath.wmul( - netTokenValue, - _totalTokenSupply - ); - require( - netTokenValueTimesTokenAmount != 0, - "netTokenValueTimesTokenAmount cant be zero" - ); - tokenAmountCreated = DSMath.wdiv(_cash, netTokenValueTimesTokenAmount); + uint256 cashTimesTokenAmount = DSMath.wmul(_cash, _totalTokenSupply); + + tokenAmountCreated = DSMath.wdiv(cashTimesTokenAmount, netTokenValue); } /** @@ -1744,13 +1735,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); } @@ -1792,7 +1787,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 + ); } /** @@ -1803,9 +1803,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(), @@ -1823,11 +1825,9 @@ contract CompositionCalculator is Initializable { function getCurrentCashAmountCreatedByToken( //Redeem uint256 _tokenAmount, - uint256 _spotPrice + uint256 _spotPrice, + uint256 _gasFee ) public view returns (uint256 cashFromTokenRedeem) { - uint256 totalTokenSupply = inverseToken.totalSupply(); - require(totalTokenSupply != 0, "Token supply cant be zero"); - uint256 lendingFee = persistentStorage.getLendingFee(); uint256 daysSinceLastRebalance = getDaysSinceLastRebalance() + 1; @@ -1855,6 +1855,8 @@ contract CompositionCalculator is Initializable { cashFromTokenRedeem = removeCurrentMintingFeeFromCash( DSMath.sub(cashFromToken, fiatForLendingFee) ); + + cashFromTokenRedeem = DSMath.sub(cashFromTokenRedeem, _gasFee); } function getDaysSinceLastRebalance() @@ -1951,7 +1953,6 @@ contract CompositionCalculator is Initializable { ); uint256 daysSinceLastRebalance = getDaysSinceLastRebalance(); uint256 minRebalanceAmount = persistentStorage.minRebalanceAmount(); - return calculatePCF( cashPosition, @@ -1959,7 +1960,8 @@ contract CompositionCalculator is Initializable { _price, _lendingFee, daysSinceLastRebalance, - minRebalanceAmount + minRebalanceAmount, + persistentStorage.balancePrecision() ); } @@ -1985,4 +1987,12 @@ contract CompositionCalculator is Initializable { function wdiv(uint256 x, uint256 y) external pure returns (uint256 z) { z = DSMath.wdiv(x, y); } + + function floorBalance(uint256 a) public view returns (uint256) { + return floor(a, persistentStorage.balancePrecision()); + } + + function floor(uint256 a, uint256 precision) public pure returns (uint256) { + return (a / 10**(precision)) * 10**(precision); + } } diff --git a/flats/InverseToken.sol b/flats/InverseToken.sol index 80ea3ae..0b16a90 100644 --- a/flats/InverseToken.sol +++ b/flats/InverseToken.sol @@ -1,5 +1,5 @@ -// File: contracts/Token/Sender.sol +// File: contracts/shared/Token/Sender.sol pragma solidity ^0.5.0; @@ -287,1025 +287,69 @@ contract Ownable is Initializable, Context { uint256[50] private ______gap; } -// File: contracts/utils/DateTimeLibrary.sol +// File: contracts/short-tokens/Abstract/InterfaceStorage.sol pragma solidity ^0.5.0; -// ---------------------------------------------------------------------------- -// BokkyPooBah's DateTime Library v1.01 -// -// A gas-efficient Solidity date and time library -// -// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary -// -// Tested date range 1970/01/01 to 2345/12/31 -// -// Conventions: -// Unit | Range | Notes -// :-------- |:-------------:|:----- -// timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC -// year | 1970 ... 2345 | -// month | 1 ... 12 | -// day | 1 ... 31 | -// hour | 0 ... 23 | -// minute | 0 ... 59 | -// second | 0 ... 59 | -// dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday -// -// -// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence. -// ---------------------------------------------------------------------------- - -library DateTimeLibrary { - uint256 constant SECONDS_PER_DAY = 24 * 60 * 60; - uint256 constant SECONDS_PER_HOUR = 60 * 60; - uint256 constant SECONDS_PER_MINUTE = 60; - int256 constant OFFSET19700101 = 2440588; - - uint256 constant DOW_MON = 1; - uint256 constant DOW_TUE = 2; - uint256 constant DOW_WED = 3; - uint256 constant DOW_THU = 4; - uint256 constant DOW_FRI = 5; - uint256 constant DOW_SAT = 6; - uint256 constant DOW_SUN = 7; - - // ------------------------------------------------------------------------ - // Calculate the number of days from 1970/01/01 to year/month/day using - // the date conversion algorithm from - // http://aa.usno.navy.mil/faq/docs/JD_Formula.php - // and subtracting the offset 2440588 so that 1970/01/01 is day 0 - // - // days = day - // - 32075 - // + 1461 * (year + 4800 + (month - 14) / 12) / 4 - // + 367 * (month - 2 - (month - 14) / 12 * 12) / 12 - // - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4 - // - offset - // ------------------------------------------------------------------------ - function _daysFromDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (uint256 _days) - { - require(year >= 1970); - int256 _year = int256(year); - int256 _month = int256(month); - int256 _day = int256(day); - - int256 __days = _day - - 32075 + - (1461 * (_year + 4800 + (_month - 14) / 12)) / - 4 + - (367 * (_month - 2 - ((_month - 14) / 12) * 12)) / - 12 - - (3 * ((_year + 4900 + (_month - 14) / 12) / 100)) / - 4 - - OFFSET19700101; - - _days = uint256(__days); - } - - // ------------------------------------------------------------------------ - // Calculate year/month/day from the number of days since 1970/01/01 using - // the date conversion algorithm from - // http://aa.usno.navy.mil/faq/docs/JD_Formula.php - // and adding the offset 2440588 so that 1970/01/01 is day 0 - // - // int L = days + 68569 + offset - // int N = 4 * L / 146097 - // L = L - (146097 * N + 3) / 4 - // year = 4000 * (L + 1) / 1461001 - // L = L - 1461 * year / 4 + 31 - // month = 80 * L / 2447 - // dd = L - 2447 * month / 80 - // L = month / 11 - // month = month + 2 - 12 * L - // year = 100 * (N - 49) + year + L - // ------------------------------------------------------------------------ - function _daysToDate(uint256 _days) - internal - pure - returns (uint256 year, uint256 month, uint256 day) - { - int256 __days = int256(_days); - - int256 L = __days + 68569 + OFFSET19700101; - int256 N = (4 * L) / 146097; - L = L - (146097 * N + 3) / 4; - int256 _year = (4000 * (L + 1)) / 1461001; - L = L - (1461 * _year) / 4 + 31; - int256 _month = (80 * L) / 2447; - int256 _day = L - (2447 * _month) / 80; - L = _month / 11; - _month = _month + 2 - 12 * L; - _year = 100 * (N - 49) + _year + L; - - year = uint256(_year); - month = uint256(_month); - day = uint256(_day); - } - - function daysFromDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (uint256 daysSinceDate) - { - daysSinceDate = _daysFromDate(year, month, day); - } - function timestampFromDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (uint256 timestamp) - { - timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY; - } - function timestampFromDateTime( - uint256 year, - uint256 month, - uint256 day, - uint256 hour, - uint256 minute, - uint256 second - ) internal pure returns (uint256 timestamp) { - timestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - hour * - SECONDS_PER_HOUR + - minute * - SECONDS_PER_MINUTE + - second; - } - function timestampToDate(uint256 timestamp) - internal - pure - returns (uint256 year, uint256 month, uint256 day) - { - (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function timestampToDateTime(uint256 timestamp) - internal - pure - returns ( - uint256 year, - uint256 month, - uint256 day, - uint256 hour, - uint256 minute, - uint256 second - ) - { - (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); - uint256 secs = timestamp % SECONDS_PER_DAY; - hour = secs / SECONDS_PER_HOUR; - secs = secs % SECONDS_PER_HOUR; - minute = secs / SECONDS_PER_MINUTE; - second = secs % SECONDS_PER_MINUTE; - } - - function isValidDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (bool valid) - { - if (year >= 1970 && month > 0 && month <= 12) { - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > 0 && day <= daysInMonth) { - valid = true; - } - } - } - function isValidDateTime( - uint256 year, - uint256 month, - uint256 day, - uint256 hour, - uint256 minute, - uint256 second - ) internal pure returns (bool valid) { - if (isValidDate(year, month, day)) { - if (hour < 24 && minute < 60 && second < 60) { - valid = true; - } - } - } - function isLeapYear(uint256 timestamp) - internal - pure - returns (bool leapYear) - { - (uint256 year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); - leapYear = _isLeapYear(year); - } - function _isLeapYear(uint256 year) internal pure returns (bool leapYear) { - leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); - } - function isWeekDay(uint256 timestamp) internal pure returns (bool weekDay) { - weekDay = getDayOfWeek(timestamp) <= DOW_FRI; - } - function isWeekEnd(uint256 timestamp) internal pure returns (bool weekEnd) { - weekEnd = getDayOfWeek(timestamp) >= DOW_SAT; - } - function getDaysInMonth(uint256 timestamp) - internal - pure - returns (uint256 daysInMonth) - { - (uint256 year, uint256 month, ) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - daysInMonth = _getDaysInMonth(year, month); - } - function _getDaysInMonth(uint256 year, uint256 month) - internal - pure - returns (uint256 daysInMonth) - { - if ( - month == 1 || - month == 3 || - month == 5 || - month == 7 || - month == 8 || - month == 10 || - month == 12 - ) { - daysInMonth = 31; - } else if (month != 2) { - daysInMonth = 30; - } else { - daysInMonth = _isLeapYear(year) ? 29 : 28; - } - } - // 1 = Monday, 7 = Sunday - function getDayOfWeek(uint256 timestamp) - internal - pure - returns (uint256 dayOfWeek) - { - uint256 _days = timestamp / SECONDS_PER_DAY; - dayOfWeek = ((_days + 3) % 7) + 1; - } - - function getYear(uint256 timestamp) internal pure returns (uint256 year) { - (year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function getMonth(uint256 timestamp) internal pure returns (uint256 month) { - (, month, ) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function getDay(uint256 timestamp) internal pure returns (uint256 day) { - (, , day) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function getHour(uint256 timestamp) internal pure returns (uint256 hour) { - uint256 secs = timestamp % SECONDS_PER_DAY; - hour = secs / SECONDS_PER_HOUR; - } - function getMinute(uint256 timestamp) - internal - pure - returns (uint256 minute) - { - uint256 secs = timestamp % SECONDS_PER_HOUR; - minute = secs / SECONDS_PER_MINUTE; - } - function getSecond(uint256 timestamp) - internal - pure - returns (uint256 second) - { - second = timestamp % SECONDS_PER_MINUTE; - } - - function addYears(uint256 timestamp, uint256 _years) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - year += _years; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp >= timestamp); - } - function addMonths(uint256 timestamp, uint256 _months) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - month += _months; - year += (month - 1) / 12; - month = ((month - 1) % 12) + 1; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp >= timestamp); - } - function addDays(uint256 timestamp, uint256 _days) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _days * SECONDS_PER_DAY; - require(newTimestamp >= timestamp); - } - function addHours(uint256 timestamp, uint256 _hours) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _hours * SECONDS_PER_HOUR; - require(newTimestamp >= timestamp); - } - function addMinutes(uint256 timestamp, uint256 _minutes) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE; - require(newTimestamp >= timestamp); - } - function addSeconds(uint256 timestamp, uint256 _seconds) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _seconds; - require(newTimestamp >= timestamp); - } - - function subYears(uint256 timestamp, uint256 _years) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - year -= _years; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp <= timestamp); - } - function subMonths(uint256 timestamp, uint256 _months) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - uint256 yearMonth = year * 12 + (month - 1) - _months; - year = yearMonth / 12; - month = (yearMonth % 12) + 1; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp <= timestamp); - } - function subDays(uint256 timestamp, uint256 _days) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _days * SECONDS_PER_DAY; - require(newTimestamp <= timestamp); - } - function subHours(uint256 timestamp, uint256 _hours) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _hours * SECONDS_PER_HOUR; - require(newTimestamp <= timestamp); - } - function subMinutes(uint256 timestamp, uint256 _minutes) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE; - require(newTimestamp <= timestamp); - } - function subSeconds(uint256 timestamp, uint256 _seconds) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _seconds; - require(newTimestamp <= timestamp); - } - - function diffYears(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _years) - { - require(fromTimestamp <= toTimestamp); - (uint256 fromYear, , ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); - (uint256 toYear, , ) = _daysToDate(toTimestamp / SECONDS_PER_DAY); - _years = toYear - fromYear; - } - function diffMonths(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _months) - { - require(fromTimestamp <= toTimestamp); - (uint256 fromYear, uint256 fromMonth, ) = _daysToDate( - fromTimestamp / SECONDS_PER_DAY - ); - (uint256 toYear, uint256 toMonth, ) = _daysToDate( - toTimestamp / SECONDS_PER_DAY - ); - _months = toYear * 12 + toMonth - fromYear * 12 - fromMonth; - } - function diffDays(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _days) - { - require(fromTimestamp <= toTimestamp); - _days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY; - } - function diffHours(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _hours) - { - require(fromTimestamp <= toTimestamp); - _hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR; - } - function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _minutes) - { - require(fromTimestamp <= toTimestamp); - _minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE; - } - function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _seconds) - { - require(fromTimestamp <= toTimestamp); - _seconds = toTimestamp - fromTimestamp; - } -} - -// File: contracts/utils/Math.sol - -/// Math.sol -- mixin for inline numerical wizardry - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma solidity ^0.5.0; - - -library DSMath { - - // --- Unsigned Math ---- - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x, "ds-math-add-overflow"); - } - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x, "ds-math-sub-underflow"); - } - function mul(uint x, uint y) internal pure returns (uint z) { - require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); - } - - - function min(uint x, uint y) internal pure returns (uint z) { - return x <= y ? x : y; - } - function max(uint x, uint y) internal pure returns (uint z) { - return x >= y ? x : y; - } - function imin(int x, int y) internal pure returns (int z) { - return x <= y ? x : y; - } - function imax(int x, int y) internal pure returns (int z) { - return x >= y ? x : y; - } - - // --- Precise Math --- - - uint public constant WAD = 10 ** 18; - uint public constant RAY = 10 ** 27; - - function wmul(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, y), WAD / 2) / WAD; - } - function rmul(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, y), RAY / 2) / RAY; - } - function wdiv(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, WAD), y / 2) / y; - } - function rdiv(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, RAY), y / 2) / y; - } - function ray(uint _wad) internal pure returns (uint) { - return mul(_wad, uint(10 ** 9)); - } +interface InterfaceStorage { + function whitelistedAddresses(address) external view returns (bool); - // This famous algorithm is called "exponentiation by squaring" - // and calculates x^n with x as fixed-point and n as regular unsigned. - // - // It's O(log n), instead of O(n) for naive repeated multiplication. - // - // These facts are why it works: - // - // If n is even, then x^n = (x^2)^(n/2). - // If n is odd, then x^n = x * x^(n-1), - // and applying the equation for even x gives - // x^n = x * (x^2)^((n-1) / 2). - // - // Also, EVM division is flooring and - // floor[(n-1) / 2] = floor[n / 2]. - // - function rpow(uint x, uint n) internal pure returns (uint z) { - z = n % 2 != 0 ? x : RAY; - - for (n /= 2; n != 0; n /= 2) { - x = rmul(x, x); - - if (n % 2 != 0) { - z = rmul(z, x); - } - } - } -} + function isPaused() external view returns (bool); -// File: contracts/PersistentStorage.sol + function isShutdown() external view returns (bool); -pragma solidity ^0.5.0; + function tokenSwapManager() external view returns (address); + function bridge() external view returns (address); + function getCashPositionPerTokenUnit() external view returns (uint256); + function getBalancePerTokenUnit() external view returns (uint256); + function getMintingFee(uint256 cash) external view returns (uint256); -contract PersistentStorage is Ownable { - address public tokenSwapManager; - address public bridge; + function getPrice() external view returns (uint256); - bool public isPaused; - bool public isShutdown; + function minimumMintingFee() external view returns (uint256); - struct Accounting { - uint256 price; - uint256 cashPositionPerTokenUnit; - uint256 balancePerTokenUnit; - uint256 lendingFee; - } + function getLendingFee() external view returns (uint256); - struct Order { - string orderType; - uint256 tokensGiven; - uint256 tokensRecieved; - uint256 avgBlendedFee; - } - - uint256 public lastActivityDay; - uint256 public minRebalanceAmount; - uint256 private managementFee; - - mapping(uint256 => Accounting[]) private accounting; - - mapping(address => bool) public whitelistedAddresses; - - uint256[] public mintingFeeBracket; - mapping(uint256 => uint256) public mintingFee; + function minRebalanceAmount() external view returns (uint8); - Order[] public allOrders; - mapping(address => Order[]) public orderByUser; - mapping(address => uint256) public delayedRedemptionsByUser; - - event AccountingValuesSet(uint256 today); - event RebalanceValuesSet(uint256 newMinRebalanceAmount); - event ManagementFeeValuesSet(uint256 newManagementFee); - - function initialize( - address ownerAddress, - uint256 _managementFee, - uint256 _minRebalanceAmount - ) 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 - } - - function setTokenSwapManager(address _tokenSwapManager) public onlyOwner { - require(_tokenSwapManager != address(0), "adddress must not be empty"); - tokenSwapManager = _tokenSwapManager; - } - - function setBridge(address _bridge) public onlyOwner { - require(_bridge != address(0), "adddress must not be empty"); - bridge = _bridge; - } - - function setIsPaused(bool _isPaused) public onlyOwner { - isPaused = _isPaused; - } - - function shutdown() public onlyOwner { - isShutdown = true; - } - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwnerOrTokenSwap() { - require( - isOwner() || _msgSender() == tokenSwapManager, - "caller is not the owner or token swap manager" - ); - _; - } + function delayedRedemptionsByUser(address) external view returns (uint256); function setDelayedRedemptionsByUser( uint256 amountToRedeem, address whitelistedAddress - ) public onlyOwnerOrTokenSwap { - delayedRedemptionsByUser[whitelistedAddress] = amountToRedeem; - } - - function getDelayedRedemptionsByUser(address whitelistedAddress) - public - view - returns (uint256) - { - return delayedRedemptionsByUser[whitelistedAddress]; - } - - /* - * Saves order in mapping (address => Order[]) orderByUser - * overwrite == false, append to Order[] - * overwrite == true, overwrite element at orderIndex - */ + ) external; function setOrderByUser( address whitelistedAddress, - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee, - uint256 orderIndex, - bool overwrite - ) public onlyOwnerOrTokenSwap() { - Order memory newOrder = Order( - orderType, - tokensGiven, - tokensRecieved, - avgBlendedFee - ); - - if (!overwrite) { - orderByUser[whitelistedAddress].push(newOrder); - setOrder( - orderType, - tokensGiven, - tokensRecieved, - avgBlendedFee, - orderIndex, - overwrite - ); - } else { - orderByUser[whitelistedAddress][orderIndex] = newOrder; - } - } - - /* - * Gets Order[] For User Address - * Return order at Index in Order[] - */ - - function getOrderByUser(address whitelistedAddress, uint256 orderIndex) - public - view - returns ( - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee - ) - { - - Order storage orderAtIndex - = orderByUser[whitelistedAddress][orderIndex]; - return ( - orderAtIndex.orderType, - orderAtIndex.tokensGiven, - orderAtIndex.tokensRecieved, - orderAtIndex.avgBlendedFee - ); - } - - /* - * Save order to allOrders array - * overwrite == false, append to allOrders array - * overwrite == true, overwrite element at orderIndex - */ - function setOrder( - string memory orderType, + string calldata orderType, uint256 tokensGiven, uint256 tokensRecieved, uint256 avgBlendedFee, uint256 orderIndex, bool overwrite - ) public onlyOwnerOrTokenSwap() { - Order memory newOrder = Order( - orderType, - tokensGiven, - tokensRecieved, - avgBlendedFee - ); - - if (!overwrite) { - allOrders.push(newOrder); - } else { - allOrders[orderIndex] = newOrder; - } - } - - /* - * Get Order - */ - function getOrder(uint256 index) - public - view - returns ( - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee - ) - { - Order storage orderAtIndex = allOrders[index]; - return ( - orderAtIndex.orderType, - orderAtIndex.tokensGiven, - orderAtIndex.tokensRecieved, - orderAtIndex.avgBlendedFee - ); - } - - // @dev Set whitelisted addresses - function setWhitelistedAddress(address adddressToAdd) public onlyOwner { - require(adddressToAdd != address(0), "adddress must not be empty"); + ) external; - 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) - public - view - returns (uint256, uint256, uint256, uint256) - { - return ( - accounting[date][accounting[date].length - 1].price, - accounting[date][accounting[date].length - 1] - .cashPositionPerTokenUnit, - accounting[date][accounting[date].length - 1].balancePerTokenUnit, - accounting[date][accounting[date].length - 1].lendingFee - ); - } - - // @dev Set accounting values for the day function setAccounting( uint256 _price, uint256 _cashPositionPerTokenUnit, uint256 _balancePerTokenUnit, uint256 _lendingFee - ) external onlyOwnerOrTokenSwap() { - (uint256 year, uint256 month, uint256 day) = DateTimeLibrary - .timestampToDate(block.timestamp); - uint256 today = year * 10000 + month * 100 + day; - accounting[today].push( - Accounting( - _price, - _cashPositionPerTokenUnit, - _balancePerTokenUnit, - _lendingFee - ) - ); - lastActivityDay = today; - emit AccountingValuesSet(today); - } + ) external; - // @dev Set accounting values for the day function setAccountingForLastActivityDay( uint256 _price, uint256 _cashPositionPerTokenUnit, uint256 _balancePerTokenUnit, uint256 _lendingFee - ) external onlyOwnerOrTokenSwap() { - accounting[lastActivityDay].push( - Accounting( - _price, - _cashPositionPerTokenUnit, - _balancePerTokenUnit, - _lendingFee - ) - ); - emit AccountingValuesSet(lastActivityDay); - } - - // @dev Set last rebalance information - function setMinRebalanceAmount(uint256 _minRebalanceAmount) - external - onlyOwner - { - minRebalanceAmount = _minRebalanceAmount; - - emit RebalanceValuesSet(minRebalanceAmount); - } - - // @dev Set last rebalance information - function setManagementFee(uint256 _managementFee) external onlyOwner { - managementFee = _managementFee; - emit ManagementFeeValuesSet(managementFee); - } - - // @dev Returns price - function getPrice() public view returns (uint256 price) { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .price; - } - - // @dev Returns cash position amount - function getCashPositionPerTokenUnit() - public - view - returns (uint256 amount) - { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .cashPositionPerTokenUnit; - } - - // @dev Returns borrowed crypto amount - function getBalancePerTokenUnit() public view returns (uint256 amount) { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .balancePerTokenUnit; - } - - // @dev Returns lending fee - function getLendingFee() public view returns (uint256 lendingRate) { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .lendingFee; - } - - // @dev Returns lending fee - function getManagementFee() public view returns (uint256 lendingRate) { - return managementFee; - } - - // @dev Sets last minting fee - function setLastMintingFee(uint256 _mintingFee) public onlyOwner { - mintingFee[~uint256(0)] = _mintingFee; - } - - // @dev Adds minting fee - function addMintingFeeBracket(uint256 _mintingFeeLimit, uint256 _mintingFee) - public - onlyOwner - { - require( - _mintingFeeLimit > mintingFeeBracket[mintingFeeBracket.length - 1], - "New minting fee bracket needs to be bigger then last one" - ); - mintingFeeBracket.push(_mintingFeeLimit); - mintingFee[_mintingFeeLimit] = _mintingFee; - } - - // @dev Deletes last minting fee - function deleteLastMintingFeeBracket() public onlyOwner { - delete mintingFee[mintingFeeBracket[mintingFeeBracket.length - 1]]; - mintingFeeBracket.length--; - } - - // @dev Changes minting fee - function changeMintingLimit( - uint256 _position, - uint256 _mintingFeeLimit, - uint256 _mintingFee - ) public onlyOwner { - require( - _mintingFeeLimit > mintingFeeBracket[mintingFeeBracket.length - 1], - "New minting fee bracket needs to be bigger then last one" - ); - if (_position != 0) { - require( - _mintingFeeLimit > mintingFeeBracket[_position - 1], - "New minting fee bracket needs to be bigger then last one" - ); - } - if (_position < mintingFeeBracket.length - 1) { - require( - _mintingFeeLimit < mintingFeeBracket[_position + 1], - "New minting fee bracket needs to be smaller then next one" - ); - } - mintingFeeBracket[_position] = _mintingFeeLimit; - mintingFee[_mintingFeeLimit] = _mintingFee; - } - - function getMintingFee(uint256 cash) public view returns (uint256) { - // Define Start + End Index - uint256 startIndex = 0; - uint256 endIndex = mintingFeeBracket.length - 1; - uint256 middleIndex = endIndex / 2; - - if (cash <= mintingFeeBracket[middleIndex]) { - endIndex = middleIndex; - } else { - startIndex = middleIndex + 1; - } - - for (uint256 i = startIndex; i <= endIndex; i++) { - if (cash <= mintingFeeBracket[i]) { - return mintingFee[mintingFeeBracket[i]]; - } - } - return mintingFee[~uint256(0)]; - } + ) external; } -// File: contracts/Token/ERC20Detailed.sol +// File: contracts/shared/Token/ERC20Detailed.sol pragma solidity ^0.5.0; @@ -1321,7 +365,7 @@ contract ERC20Detailed is IERC20, Initializable, Ownable { string private _name; string private _symbol; uint8 private _decimals; - PersistentStorage public _persistenStorage; + InterfaceStorage public _persistenStorage; /** * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of @@ -1339,7 +383,7 @@ contract ERC20Detailed is IERC20, Initializable, Ownable { _name = name; _symbol = symbol; _decimals = decimals; - _persistenStorage = PersistentStorage(persistenStorage); + _persistenStorage = InterfaceStorage(persistenStorage); } /** @@ -1533,7 +577,7 @@ library SafeMath { } } -// File: contracts/Token/ERC20.sol +// File: contracts/shared/Token/ERC20.sol pragma solidity ^0.5.0; @@ -1812,7 +856,7 @@ contract ERC20 is Sender, ERC20Detailed { } } -// File: contracts/Token/InverseToken.sol +// File: contracts/short-tokens/InverseToken.sol pragma solidity ^0.5.0; diff --git a/flats/KYCVerifier.sol b/flats/KYCVerifier.sol index d21dfee..f7e4f04 100644 --- a/flats/KYCVerifier.sol +++ b/flats/KYCVerifier.sol @@ -63,27 +63,134 @@ contract Initializable { uint256[50] private ______gap; } -// File: contracts/Abstract/InterfaceStorage.sol +// File: @openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol pragma solidity ^0.5.0; -interface InterfaceStorage { - function whitelistedAddresses(address) external view returns (bool); +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +contract Context is Initializable { + // Empty internal constructor, to prevent people from mistakenly deploying + // an instance of this contract, which should be used via inheritance. + constructor () internal { } + // solhint-disable-previous-line no-empty-blocks + + function _msgSender() internal view returns (address payable) { + return msg.sender; + } + + function _msgData() internal view returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// File: @openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol + +pragma solidity ^0.5.0; + + + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be aplied to your functions to restrict their use to + * the owner. + */ +contract Ownable is Initializable, Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + function initialize(address sender) public initializer { + _owner = sender; + emit OwnershipTransferred(address(0), _owner); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(isOwner(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Returns true if the caller is the current owner. + */ + function isOwner() public view returns (bool) { + return _msgSender() == _owner; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * > Note: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + */ + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + + uint256[50] private ______gap; } -// File: contracts/KYCVerifier.sol +// File: contracts/short-tokens/KYCVerifier.sol pragma solidity ^0.5.0; +contract KYCVerifier is Ownable { + address public bridge; + mapping(address => bool) public whitelistedAddresses; -contract KYCVerifier is Initializable { - InterfaceStorage public persistentStorage; + event WhitelistedAddressAdded(address); - function initialize(address _persistentStorage) public initializer { - persistentStorage = InterfaceStorage(_persistentStorage); + function initialize(address ownerAddress) public initializer { + require(ownerAddress != address(0), "owner adddress must not be empty"); + Ownable.initialize(ownerAddress); } function isAddressWhitelisted(address userAddress) @@ -91,6 +198,58 @@ contract KYCVerifier is Initializable { view returns (bool) { - return persistentStorage.whitelistedAddresses(userAddress); + return whitelistedAddresses[userAddress]; + } + + // @dev Set whitelisted addresses + function setWhitelistedAddress(address addressToAdd) + public + onlyOwnerOrBridge + { + require(addressToAdd != address(0), "adddress must not be empty"); + + whitelistedAddresses[addressToAdd] = true; + + emit WhitelistedAddressAdded(addressToAdd); + } + + function batchWhitelistedAddress(address[] calldata addresses) external { + for (uint8 index = 0; index < addresses.length; index++) { + setWhitelistedAddress(addresses[index]); + } + } + + // @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/flats/OwnerMultiSig.sol b/flats/OwnerMultiSig.sol new file mode 100644 index 0000000..939deec --- /dev/null +++ b/flats/OwnerMultiSig.sol @@ -0,0 +1,463 @@ + +// File: @openzeppelin/upgrades/contracts/Initializable.sol + +pragma solidity >=0.4.24 <0.6.0; + + +/** + * @title Initializable + * + * @dev Helper contract to support initializer functions. To use it, replace + * the constructor with a function that has the `initializer` modifier. + * WARNING: Unlike constructors, initializer functions must be manually + * invoked. This applies both to deploying an Initializable contract, as well + * as extending an Initializable contract via inheritance. + * WARNING: When used with inheritance, manual care must be taken to not invoke + * a parent initializer twice, or ensure that all initializers are idempotent, + * because this is not dealt with automatically as with constructors. + */ +contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private initializing; + + /** + * @dev Modifier to use in the initializer function of a contract. + */ + modifier initializer() { + require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized"); + + bool isTopLevelCall = !initializing; + if (isTopLevelCall) { + initializing = true; + initialized = true; + } + + _; + + if (isTopLevelCall) { + initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function isConstructor() private view returns (bool) { + // extcodesize checks the size of the code stored in an address, and + // address returns the current address. Since the code is still not + // deployed when running a constructor, any checks on its code size will + // yield zero, making it an effective way to detect if a contract is + // under construction or not. + uint256 cs; + assembly { cs := extcodesize(address) } + return cs == 0; + } + + // Reserved storage space to allow for layout changes in the future. + uint256[50] private ______gap; +} + +// File: contracts/shared/AdminMultiSig/OwnerMultiSig.sol + +pragma solidity ^0.5.0; + + + +/// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution. +/// @author Stefan George - +contract OwnerMultiSig is Initializable { + /* + * Events + */ + event Confirmation(address indexed sender, uint256 indexed transactionId); + event Revocation(address indexed sender, uint256 indexed transactionId); + event Submission(uint256 indexed transactionId); + event Execution(uint256 indexed transactionId); + event ExecutionFailure(uint256 indexed transactionId); + event Deposit(address indexed sender, uint256 value); + event OwnerAddition(address indexed owner); + event OwnerRemoval(address indexed owner); + event RequirementChange(uint256 required); + + /* + * Constants + */ + uint256 public constant MAX_OWNER_COUNT = 50; + + /* + * Storage + */ + mapping(uint256 => Transaction) public transactions; + mapping(uint256 => mapping(address => bool)) public confirmations; + mapping(address => bool) public isOwner; + address[] public owners; + uint256 public required; + uint256 public transactionCount; + //Txn Struct dest, val, data, and isExecuted + struct Transaction { + address destination; + uint256 value; + bytes data; + bool executed; + } + + /* + * Modifiers + */ + + // Sent by Wallet + modifier onlyWallet() { + require(msg.sender == address(this)); + _; + } + // Check if Owner Does Not Exists + modifier ownerDoesNotExist(address owner) { + require(!isOwner[owner]); + _; + } + // Check if Owner Exists + modifier ownerExists(address owner) { + require(isOwner[owner]); + _; + } + // Check for Txn by Txn ID + modifier transactionExists(uint256 transactionId) { + require(transactions[transactionId].destination != address(0)); + _; + } + // Check if Txn ID is Confirmed + modifier confirmed(uint256 transactionId, address owner) { + require(confirmations[transactionId][owner]); + _; + } + // Not Confirmed Txn ID + modifier notConfirmed(uint256 transactionId, address owner) { + require(!confirmations[transactionId][owner]); + _; + } + // Check if Txn ID is Executed + modifier notExecuted(uint256 transactionId) { + require(!transactions[transactionId].executed); + _; + } + // Check if Addr is not 0 address + modifier notNull(address _address) { + require(_address != address(0)); + _; + } + // Check if Requirements have passed + modifier validRequirement(uint256 ownerCount, uint256 _required) { + require( + ownerCount <= MAX_OWNER_COUNT && + _required <= ownerCount && + _required != 0 && + ownerCount != 0 + ); + _; + } + + // Fallback Function to Depo ETH + /// @dev Fallback function allows to deposit ether. + function() external payable { + if (msg.value > 0) emit Deposit(msg.sender, msg.value); + } + + /* + * Public functions + */ + /// @dev Contract constructor sets initial owners and required number of confirmations. + /// @param _owners List of initial owners. + /// @param _required Number of required confirmations. + // Number of Confirmations for a Txn + function initialize(address[] memory _owners, uint256 _required) + public + initializer + validRequirement(_owners.length, _required) + { + for (uint256 i = 0; i < _owners.length; i++) { + require(!isOwner[_owners[i]] && _owners[i] != address(0)); + isOwner[_owners[i]] = true; + } + owners = _owners; + required = _required; + // + } + + /// @dev Allows to add a new owner. Transaction has to be sent by wallet. + /// @param owner Address of new owner. + function addOwner(address owner) + public + onlyWallet + ownerDoesNotExist(owner) + notNull(owner) + validRequirement(owners.length + 1, required) + { + isOwner[owner] = true; + owners.push(owner); + emit OwnerAddition(owner); + } + + /// @dev Allows to remove an owner. Transaction has to be sent by wallet. + /// @param owner Address of owner. + function removeOwner(address owner) public onlyWallet ownerExists(owner) { + isOwner[owner] = false; + for (uint256 i = 0; i < owners.length - 1; i++) + if (owners[i] == owner) { + owners[i] = owners[owners.length - 1]; + break; + } + owners.length -= 1; + if (required > owners.length) changeRequirement(owners.length); + emit OwnerRemoval(owner); + } + + /// @dev Allows to replace an owner with a new owner. Transaction has to be sent by wallet. + /// @param owner Address of owner to be replaced. + /// @param newOwner Address of new owner. + function replaceOwner(address owner, address newOwner) + public + onlyWallet + ownerExists(owner) + ownerDoesNotExist(newOwner) + { + for (uint256 i = 0; i < owners.length; i++) + if (owners[i] == owner) { + owners[i] = newOwner; + break; + } + isOwner[owner] = false; + isOwner[newOwner] = true; + emit OwnerRemoval(owner); + emit OwnerAddition(newOwner); + } + + /// @dev Allows to change the number of required confirmations. Transaction has to be sent by wallet. + /// @param _required Number of required confirmations. + function changeRequirement(uint256 _required) + public + onlyWallet + validRequirement(owners.length, _required) + { + required = _required; + emit RequirementChange(_required); + } + + /// @dev Allows an owner to submit and confirm a transaction. + /// @param destination Transaction target address. + /// @param value Transaction ether value. + /// @param data Transaction data payload. + /// @return Returns transaction ID. + function submitTransaction( + address destination, + uint256 value, + bytes memory data + ) public returns (uint256 transactionId) { + transactionId = addTransaction(destination, value, data); + confirmTransaction(transactionId); + } + + /// @dev Allows an owner to confirm a transaction. + /// @param transactionId Transaction ID. + // Check if Txn is confirmed + function confirmTransaction(uint256 transactionId) + public + ownerExists(msg.sender) + transactionExists(transactionId) + notConfirmed(transactionId, msg.sender) + { + confirmations[transactionId][msg.sender] = true; + emit Confirmation(msg.sender, transactionId); + executeTransaction(transactionId); + } + + /// @dev Allows an owner to revoke a confirmation for a transaction. + /// @param transactionId Transaction ID. + function revokeConfirmation(uint256 transactionId) + public + ownerExists(msg.sender) + confirmed(transactionId, msg.sender) + notExecuted(transactionId) + { + confirmations[transactionId][msg.sender] = false; + emit Revocation(msg.sender, transactionId); + } + + /// @dev Allows anyone to execute a confirmed transaction. + /// @param transactionId Transaction ID. + function executeTransaction(uint256 transactionId) + public + ownerExists(msg.sender) + confirmed(transactionId, msg.sender) + notExecuted(transactionId) + { + if (isConfirmed(transactionId)) { + Transaction storage txn = transactions[transactionId]; + txn.executed = true; + if ( + external_call( + txn.destination, + txn.value, + txn.data.length, + txn.data + ) + ) emit Execution(transactionId); + else { + emit ExecutionFailure(transactionId); + txn.executed = false; + } + } + } + + // call has been separated into its own function in order to take advantage + // of the Solidity's code generator to produce a loop that copies tx.data into memory. + function external_call( + address destination, + uint256 value, + uint256 dataLength, + bytes memory data + ) internal returns (bool) { + bool result; + assembly { + let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention) + let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that + result := call( + sub(gas, 34710), // 34710 is the value that solidity is currently emitting + // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) + + // callNewAccountGas (25000, in case the destination address does not exist and needs creating) + destination, + value, + d, + dataLength, // Size of the input (in bytes) - this is what fixes the padding problem + x, + 0 // Output is ignored, therefore the output size is zero + ) + } + return result; + } + + /// @dev Returns the confirmation status of a transaction. + /// @param transactionId Transaction ID. + /// @return Confirmation status. + function isConfirmed(uint256 transactionId) public view returns (bool) { + uint256 count = 0; + for (uint256 i = 0; i < owners.length; i++) { + if (confirmations[transactionId][owners[i]]) count += 1; + if (count == required) return true; + } + } + + /* + * Internal functions + */ + /// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet. + /// @param destination Transaction target address. + /// @param value Transaction ether value. + /// @param data Transaction data payload. + /// @return Returns transaction ID. + function addTransaction( + address destination, + uint256 value, + bytes memory data + ) internal notNull(destination) returns (uint256 transactionId) { + transactionId = transactionCount; + transactions[transactionId] = Transaction({ + destination: destination, + value: value, + data: data, + executed: false + }); + transactionCount += 1; + emit Submission(transactionId); + } + + /* + * Web3 call functions + */ + /// @dev Returns number of confirmations of a transaction. + /// @param transactionId Transaction ID. + /// @return Number of confirmations. + function getConfirmationCount(uint256 transactionId) + public + view + returns (uint256 count) + { + for (uint256 i = 0; i < owners.length; i++) + if (confirmations[transactionId][owners[i]]) count += 1; + } + + /// @dev Returns total number of transactions after filers are applied. + /// @param pending Include pending transactions. + /// @param executed Include executed transactions. + /// @return Total number of transactions after filters are applied. + function getTransactionCount(bool pending, bool executed) + public + view + returns (uint256 count) + { + for (uint256 i = 0; i < transactionCount; i++) + if ( + (pending && !transactions[i].executed) || + (executed && transactions[i].executed) + ) count += 1; + } + + /// @dev Returns list of owners. + /// @return List of owner addresses. + function getOwners() public view returns (address[] memory) { + return owners; + } + + /// @dev Returns array with owner addresses, which confirmed transaction. + /// @param transactionId Transaction ID. + /// @return Returns array of owner addresses. + function getConfirmations(uint256 transactionId) + public + view + returns (address[] memory _confirmations) + { + address[] memory confirmationsTemp = new address[](owners.length); + uint256 count = 0; + uint256 i; + for (i = 0; i < owners.length; i++) + if (confirmations[transactionId][owners[i]]) { + confirmationsTemp[count] = owners[i]; + count += 1; + } + _confirmations = new address[](count); + for (i = 0; i < count; i++) _confirmations[i] = confirmationsTemp[i]; + } + + /// @dev Returns list of transaction IDs in defined range. + /// @param from Index start position of transaction array. + /// @param to Index end position of transaction array. + /// @param pending Include pending transactions. + /// @param executed Include executed transactions. + /// @return Returns array of transaction IDs. + function getTransactionIds( + uint256 from, + uint256 to, + bool pending, + bool executed + ) public view returns (uint256[] memory _transactionIds) { + uint256[] memory transactionIdsTemp = new uint256[](transactionCount); + uint256 count = 0; + uint256 i; + for (i = 0; i < transactionCount; i++) + if ( + (pending && !transactions[i].executed) || + (executed && transactions[i].executed) + ) { + transactionIdsTemp[count] = i; + count += 1; + } + _transactionIds = new uint256[](to - from); + for (i = from; i < to; i++) + _transactionIds[i - from] = transactionIdsTemp[i]; + } +} diff --git a/flats/PersistentStorage.sol b/flats/PersistentStorage.sol index fc8cc1d..02bf7f4 100644 --- a/flats/PersistentStorage.sol +++ b/flats/PersistentStorage.sol @@ -176,7 +176,7 @@ contract Ownable is Initializable, Context { uint256[50] private ______gap; } -// File: contracts/utils/DateTimeLibrary.sol +// File: contracts/short-tokens/utils/DateTimeLibrary.sol pragma solidity ^0.5.0; @@ -670,7 +670,7 @@ library DateTimeLibrary { } } -// File: contracts/utils/Math.sol +// File: contracts/short-tokens/utils/Math.sol /// Math.sol -- mixin for inline numerical wizardry @@ -767,7 +767,7 @@ library DSMath { } } -// File: contracts/PersistentStorage.sol +// File: contracts/short-tokens/PersistentStorage.sol pragma solidity ^0.5.0; @@ -798,11 +798,13 @@ contract PersistentStorage is Ownable { uint256 public lastActivityDay; uint256 public minRebalanceAmount; - uint256 private managementFee; + uint256 public managementFee; + uint256 public minimumMintingFee; + uint256 public minimumTrade; - mapping(uint256 => Accounting[]) private accounting; + uint8 public balancePrecision; - mapping(address => bool) public whitelistedAddresses; + mapping(uint256 => Accounting[]) private accounting; uint256[] public mintingFeeBracket; mapping(uint256 => uint256) public mintingFee; @@ -818,16 +820,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 + mintingFee[~uint256(0)] = _lastMintingFee; + balancePrecision = _balancePrecision; + minimumMintingFee = _minimumMintingFee; + minimumTrade = _minimumTrade; } function setTokenSwapManager(address _tokenSwapManager) public onlyOwner { @@ -859,6 +864,14 @@ contract PersistentStorage is Ownable { _; } + modifier onlyOwnerOrBridge() { + require( + isOwner() || _msgSender() == bridge, + "caller is not the owner or bridge" + ); + _; + } + function setDelayedRedemptionsByUser( uint256 amountToRedeem, address whitelistedAddress @@ -866,14 +879,6 @@ contract PersistentStorage is Ownable { delayedRedemptionsByUser[whitelistedAddress] = amountToRedeem; } - function getDelayedRedemptionsByUser(address whitelistedAddress) - public - view - returns (uint256) - { - return delayedRedemptionsByUser[whitelistedAddress]; - } - /* * Saves order in mapping (address => Order[]) orderByUser * overwrite == false, append to Order[] @@ -986,34 +991,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) @@ -1118,11 +1095,6 @@ contract PersistentStorage is Ownable { .lendingFee; } - // @dev Returns lending fee - function getManagementFee() public view returns (uint256 lendingRate) { - return managementFee; - } - // @dev Sets last minting fee function setLastMintingFee(uint256 _mintingFee) public onlyOwner { mintingFee[~uint256(0)] = _mintingFee; @@ -1134,7 +1106,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); @@ -1192,4 +1166,19 @@ contract PersistentStorage is Ownable { } return mintingFee[~uint256(0)]; } + + // @dev Sets last balance precision + 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/flats/StorageLeverage.sol b/flats/StorageLeverage.sol new file mode 100644 index 0000000..4e03ae8 --- /dev/null +++ b/flats/StorageLeverage.sol @@ -0,0 +1,1224 @@ + +// File: @openzeppelin/upgrades/contracts/Initializable.sol + +pragma solidity >=0.4.24 <0.6.0; + + +/** + * @title Initializable + * + * @dev Helper contract to support initializer functions. To use it, replace + * the constructor with a function that has the `initializer` modifier. + * WARNING: Unlike constructors, initializer functions must be manually + * invoked. This applies both to deploying an Initializable contract, as well + * as extending an Initializable contract via inheritance. + * WARNING: When used with inheritance, manual care must be taken to not invoke + * a parent initializer twice, or ensure that all initializers are idempotent, + * because this is not dealt with automatically as with constructors. + */ +contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private initializing; + + /** + * @dev Modifier to use in the initializer function of a contract. + */ + modifier initializer() { + require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized"); + + bool isTopLevelCall = !initializing; + if (isTopLevelCall) { + initializing = true; + initialized = true; + } + + _; + + if (isTopLevelCall) { + initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function isConstructor() private view returns (bool) { + // extcodesize checks the size of the code stored in an address, and + // address returns the current address. Since the code is still not + // deployed when running a constructor, any checks on its code size will + // yield zero, making it an effective way to detect if a contract is + // under construction or not. + uint256 cs; + assembly { cs := extcodesize(address) } + return cs == 0; + } + + // Reserved storage space to allow for layout changes in the future. + uint256[50] private ______gap; +} + +// File: @openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol + +pragma solidity ^0.5.0; + + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +contract Context is Initializable { + // Empty internal constructor, to prevent people from mistakenly deploying + // an instance of this contract, which should be used via inheritance. + constructor () internal { } + // solhint-disable-previous-line no-empty-blocks + + function _msgSender() internal view returns (address payable) { + return msg.sender; + } + + function _msgData() internal view returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// File: @openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol + +pragma solidity ^0.5.0; + + + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be aplied to your functions to restrict their use to + * the owner. + */ +contract Ownable is Initializable, Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + function initialize(address sender) public initializer { + _owner = sender; + emit OwnershipTransferred(address(0), _owner); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(isOwner(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Returns true if the caller is the current owner. + */ + function isOwner() public view returns (bool) { + return _msgSender() == _owner; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * > Note: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + */ + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + + uint256[50] private ______gap; +} + +// File: contracts/short-tokens/utils/DateTimeLibrary.sol + +pragma solidity ^0.5.0; + +// ---------------------------------------------------------------------------- +// BokkyPooBah's DateTime Library v1.01 +// +// A gas-efficient Solidity date and time library +// +// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary +// +// Tested date range 1970/01/01 to 2345/12/31 +// +// Conventions: +// Unit | Range | Notes +// :-------- |:-------------:|:----- +// timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC +// year | 1970 ... 2345 | +// month | 1 ... 12 | +// day | 1 ... 31 | +// hour | 0 ... 23 | +// minute | 0 ... 59 | +// second | 0 ... 59 | +// dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday +// +// +// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence. +// ---------------------------------------------------------------------------- + +library DateTimeLibrary { + uint256 constant SECONDS_PER_DAY = 24 * 60 * 60; + uint256 constant SECONDS_PER_HOUR = 60 * 60; + uint256 constant SECONDS_PER_MINUTE = 60; + int256 constant OFFSET19700101 = 2440588; + + uint256 constant DOW_MON = 1; + uint256 constant DOW_TUE = 2; + uint256 constant DOW_WED = 3; + uint256 constant DOW_THU = 4; + uint256 constant DOW_FRI = 5; + uint256 constant DOW_SAT = 6; + uint256 constant DOW_SUN = 7; + + // ------------------------------------------------------------------------ + // Calculate the number of days from 1970/01/01 to year/month/day using + // the date conversion algorithm from + // http://aa.usno.navy.mil/faq/docs/JD_Formula.php + // and subtracting the offset 2440588 so that 1970/01/01 is day 0 + // + // days = day + // - 32075 + // + 1461 * (year + 4800 + (month - 14) / 12) / 4 + // + 367 * (month - 2 - (month - 14) / 12 * 12) / 12 + // - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4 + // - offset + // ------------------------------------------------------------------------ + function _daysFromDate(uint256 year, uint256 month, uint256 day) + internal + pure + returns (uint256 _days) + { + require(year >= 1970); + int256 _year = int256(year); + int256 _month = int256(month); + int256 _day = int256(day); + + int256 __days = _day - + 32075 + + (1461 * (_year + 4800 + (_month - 14) / 12)) / + 4 + + (367 * (_month - 2 - ((_month - 14) / 12) * 12)) / + 12 - + (3 * ((_year + 4900 + (_month - 14) / 12) / 100)) / + 4 - + OFFSET19700101; + + _days = uint256(__days); + } + + // ------------------------------------------------------------------------ + // Calculate year/month/day from the number of days since 1970/01/01 using + // the date conversion algorithm from + // http://aa.usno.navy.mil/faq/docs/JD_Formula.php + // and adding the offset 2440588 so that 1970/01/01 is day 0 + // + // int L = days + 68569 + offset + // int N = 4 * L / 146097 + // L = L - (146097 * N + 3) / 4 + // year = 4000 * (L + 1) / 1461001 + // L = L - 1461 * year / 4 + 31 + // month = 80 * L / 2447 + // dd = L - 2447 * month / 80 + // L = month / 11 + // month = month + 2 - 12 * L + // year = 100 * (N - 49) + year + L + // ------------------------------------------------------------------------ + function _daysToDate(uint256 _days) + internal + pure + returns (uint256 year, uint256 month, uint256 day) + { + int256 __days = int256(_days); + + int256 L = __days + 68569 + OFFSET19700101; + int256 N = (4 * L) / 146097; + L = L - (146097 * N + 3) / 4; + int256 _year = (4000 * (L + 1)) / 1461001; + L = L - (1461 * _year) / 4 + 31; + int256 _month = (80 * L) / 2447; + int256 _day = L - (2447 * _month) / 80; + L = _month / 11; + _month = _month + 2 - 12 * L; + _year = 100 * (N - 49) + _year + L; + + year = uint256(_year); + month = uint256(_month); + day = uint256(_day); + } + + function daysFromDate(uint256 year, uint256 month, uint256 day) + internal + pure + returns (uint256 daysSinceDate) + { + daysSinceDate = _daysFromDate(year, month, day); + } + function timestampFromDate(uint256 year, uint256 month, uint256 day) + internal + pure + returns (uint256 timestamp) + { + timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY; + } + function timestampFromDateTime( + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second + ) internal pure returns (uint256 timestamp) { + timestamp = + _daysFromDate(year, month, day) * + SECONDS_PER_DAY + + hour * + SECONDS_PER_HOUR + + minute * + SECONDS_PER_MINUTE + + second; + } + function timestampToDate(uint256 timestamp) + internal + pure + returns (uint256 year, uint256 month, uint256 day) + { + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function timestampToDateTime(uint256 timestamp) + internal + pure + returns ( + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second + ) + { + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + uint256 secs = timestamp % SECONDS_PER_DAY; + hour = secs / SECONDS_PER_HOUR; + secs = secs % SECONDS_PER_HOUR; + minute = secs / SECONDS_PER_MINUTE; + second = secs % SECONDS_PER_MINUTE; + } + + function isValidDate(uint256 year, uint256 month, uint256 day) + internal + pure + returns (bool valid) + { + if (year >= 1970 && month > 0 && month <= 12) { + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > 0 && day <= daysInMonth) { + valid = true; + } + } + } + function isValidDateTime( + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second + ) internal pure returns (bool valid) { + if (isValidDate(year, month, day)) { + if (hour < 24 && minute < 60 && second < 60) { + valid = true; + } + } + } + function isLeapYear(uint256 timestamp) + internal + pure + returns (bool leapYear) + { + (uint256 year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); + leapYear = _isLeapYear(year); + } + function _isLeapYear(uint256 year) internal pure returns (bool leapYear) { + leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); + } + function isWeekDay(uint256 timestamp) internal pure returns (bool weekDay) { + weekDay = getDayOfWeek(timestamp) <= DOW_FRI; + } + function isWeekEnd(uint256 timestamp) internal pure returns (bool weekEnd) { + weekEnd = getDayOfWeek(timestamp) >= DOW_SAT; + } + function getDaysInMonth(uint256 timestamp) + internal + pure + returns (uint256 daysInMonth) + { + (uint256 year, uint256 month, ) = _daysToDate( + timestamp / SECONDS_PER_DAY + ); + daysInMonth = _getDaysInMonth(year, month); + } + function _getDaysInMonth(uint256 year, uint256 month) + internal + pure + returns (uint256 daysInMonth) + { + if ( + month == 1 || + month == 3 || + month == 5 || + month == 7 || + month == 8 || + month == 10 || + month == 12 + ) { + daysInMonth = 31; + } else if (month != 2) { + daysInMonth = 30; + } else { + daysInMonth = _isLeapYear(year) ? 29 : 28; + } + } + // 1 = Monday, 7 = Sunday + function getDayOfWeek(uint256 timestamp) + internal + pure + returns (uint256 dayOfWeek) + { + uint256 _days = timestamp / SECONDS_PER_DAY; + dayOfWeek = ((_days + 3) % 7) + 1; + } + + function getYear(uint256 timestamp) internal pure returns (uint256 year) { + (year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function getMonth(uint256 timestamp) internal pure returns (uint256 month) { + (, month, ) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function getDay(uint256 timestamp) internal pure returns (uint256 day) { + (, , day) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function getHour(uint256 timestamp) internal pure returns (uint256 hour) { + uint256 secs = timestamp % SECONDS_PER_DAY; + hour = secs / SECONDS_PER_HOUR; + } + function getMinute(uint256 timestamp) + internal + pure + returns (uint256 minute) + { + uint256 secs = timestamp % SECONDS_PER_HOUR; + minute = secs / SECONDS_PER_MINUTE; + } + function getSecond(uint256 timestamp) + internal + pure + returns (uint256 second) + { + second = timestamp % SECONDS_PER_MINUTE; + } + + function addYears(uint256 timestamp, uint256 _years) + internal + pure + returns (uint256 newTimestamp) + { + (uint256 year, uint256 month, uint256 day) = _daysToDate( + timestamp / SECONDS_PER_DAY + ); + year += _years; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = + _daysFromDate(year, month, day) * + SECONDS_PER_DAY + + (timestamp % SECONDS_PER_DAY); + require(newTimestamp >= timestamp); + } + function addMonths(uint256 timestamp, uint256 _months) + internal + pure + returns (uint256 newTimestamp) + { + (uint256 year, uint256 month, uint256 day) = _daysToDate( + timestamp / SECONDS_PER_DAY + ); + month += _months; + year += (month - 1) / 12; + month = ((month - 1) % 12) + 1; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = + _daysFromDate(year, month, day) * + SECONDS_PER_DAY + + (timestamp % SECONDS_PER_DAY); + require(newTimestamp >= timestamp); + } + function addDays(uint256 timestamp, uint256 _days) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp + _days * SECONDS_PER_DAY; + require(newTimestamp >= timestamp); + } + function addHours(uint256 timestamp, uint256 _hours) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp + _hours * SECONDS_PER_HOUR; + require(newTimestamp >= timestamp); + } + function addMinutes(uint256 timestamp, uint256 _minutes) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE; + require(newTimestamp >= timestamp); + } + function addSeconds(uint256 timestamp, uint256 _seconds) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp + _seconds; + require(newTimestamp >= timestamp); + } + + function subYears(uint256 timestamp, uint256 _years) + internal + pure + returns (uint256 newTimestamp) + { + (uint256 year, uint256 month, uint256 day) = _daysToDate( + timestamp / SECONDS_PER_DAY + ); + year -= _years; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = + _daysFromDate(year, month, day) * + SECONDS_PER_DAY + + (timestamp % SECONDS_PER_DAY); + require(newTimestamp <= timestamp); + } + function subMonths(uint256 timestamp, uint256 _months) + internal + pure + returns (uint256 newTimestamp) + { + (uint256 year, uint256 month, uint256 day) = _daysToDate( + timestamp / SECONDS_PER_DAY + ); + uint256 yearMonth = year * 12 + (month - 1) - _months; + year = yearMonth / 12; + month = (yearMonth % 12) + 1; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = + _daysFromDate(year, month, day) * + SECONDS_PER_DAY + + (timestamp % SECONDS_PER_DAY); + require(newTimestamp <= timestamp); + } + function subDays(uint256 timestamp, uint256 _days) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp - _days * SECONDS_PER_DAY; + require(newTimestamp <= timestamp); + } + function subHours(uint256 timestamp, uint256 _hours) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp - _hours * SECONDS_PER_HOUR; + require(newTimestamp <= timestamp); + } + function subMinutes(uint256 timestamp, uint256 _minutes) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE; + require(newTimestamp <= timestamp); + } + function subSeconds(uint256 timestamp, uint256 _seconds) + internal + pure + returns (uint256 newTimestamp) + { + newTimestamp = timestamp - _seconds; + require(newTimestamp <= timestamp); + } + + function diffYears(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _years) + { + require(fromTimestamp <= toTimestamp); + (uint256 fromYear, , ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); + (uint256 toYear, , ) = _daysToDate(toTimestamp / SECONDS_PER_DAY); + _years = toYear - fromYear; + } + function diffMonths(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _months) + { + require(fromTimestamp <= toTimestamp); + (uint256 fromYear, uint256 fromMonth, ) = _daysToDate( + fromTimestamp / SECONDS_PER_DAY + ); + (uint256 toYear, uint256 toMonth, ) = _daysToDate( + toTimestamp / SECONDS_PER_DAY + ); + _months = toYear * 12 + toMonth - fromYear * 12 - fromMonth; + } + function diffDays(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _days) + { + require(fromTimestamp <= toTimestamp); + _days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY; + } + function diffHours(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _hours) + { + require(fromTimestamp <= toTimestamp); + _hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR; + } + function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _minutes) + { + require(fromTimestamp <= toTimestamp); + _minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE; + } + function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp) + internal + pure + returns (uint256 _seconds) + { + require(fromTimestamp <= toTimestamp); + _seconds = toTimestamp - fromTimestamp; + } +} + +// File: contracts/short-tokens/utils/Math.sol + +/// Math.sol -- mixin for inline numerical wizardry + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.5.0; + + +library DSMath { + + // --- Unsigned Math ---- + function add(uint x, uint y) internal pure returns (uint z) { + require((z = x + y) >= x, "ds-math-add-overflow"); + } + function sub(uint x, uint y) internal pure returns (uint z) { + require((z = x - y) <= x, "ds-math-sub-underflow"); + } + function mul(uint x, uint y) internal pure returns (uint z) { + require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); + } + + + function min(uint x, uint y) internal pure returns (uint z) { + return x <= y ? x : y; + } + function max(uint x, uint y) internal pure returns (uint z) { + return x >= y ? x : y; + } + function imin(int x, int y) internal pure returns (int z) { + return x <= y ? x : y; + } + function imax(int x, int y) internal pure returns (int z) { + return x >= y ? x : y; + } + + // --- Precise Math --- + + uint public constant WAD = 10 ** 18; + uint public constant RAY = 10 ** 27; + + function wmul(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, y), WAD / 2) / WAD; + } + function rmul(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, y), RAY / 2) / RAY; + } + function wdiv(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, WAD), y / 2) / y; + } + function rdiv(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, RAY), y / 2) / y; + } + + function ray(uint _wad) internal pure returns (uint) { + return mul(_wad, uint(10 ** 9)); + } + + // This famous algorithm is called "exponentiation by squaring" + // and calculates x^n with x as fixed-point and n as regular unsigned. + // + // It's O(log n), instead of O(n) for naive repeated multiplication. + // + // These facts are why it works: + // + // If n is even, then x^n = (x^2)^(n/2). + // If n is odd, then x^n = x * x^(n-1), + // and applying the equation for even x gives + // x^n = x * (x^2)^((n-1) / 2). + // + // Also, EVM division is flooring and + // floor[(n-1) / 2] = floor[n / 2]. + // + function rpow(uint x, uint n) internal pure returns (uint z) { + z = n % 2 != 0 ? x : RAY; + + for (n /= 2; n != 0; n /= 2) { + x = rmul(x, x); + + if (n % 2 != 0) { + z = rmul(z, x); + } + } + } +} + +// File: contracts/leverage-tokens/StorageLeverage.sol + +pragma solidity ^0.5.0; + + + + + +contract StorageLeverage is Ownable { + address public tokenSwapManager; + address public bridge; + + bool public isPaused; + bool public isShutdown; + + struct Accounting { + uint256 tokenValueNetFees; + uint256 bestExecutionPrice; + uint256 markPrice; + uint256 notional; + int256 changeInNotional; + uint256 tokenValue; + uint256 effectiveFundingRate; + } + + struct Order { + string orderType; + uint256 tokensGiven; + uint256 tokensRecieved; + uint256 mintingPrice; + } + + uint256 public lastActivityDay; + uint256 public minRebalanceAmount; + uint256 public managementFee; + uint256 public minimumMintingFee; + uint256 public minimumTrade; + + uint8 public balancePrecision; + + mapping(uint256 => Accounting[]) private accounting; + + uint256[] public mintingFeeBracket; + mapping(uint256 => uint256) public mintingFee; + + Order[] public allOrders; + mapping(address => Order[]) public orderByUser; + mapping(address => uint256) public delayedRedemptionsByUser; + + event AccountingValuesSet(uint256 today); + event RebalanceValuesSet(uint256 newMinRebalanceAmount); + event ManagementFeeValuesSet(uint256 newManagementFee); + + function initialize( + address ownerAddress, + uint256 _managementFee, + uint256 _minRebalanceAmount, + uint8 _balancePrecision, + uint256 _lastMintingFee, + uint256 _minimumMintingFee, + uint256 _minimumTrade + ) public initializer { + initialize(ownerAddress); + managementFee = _managementFee; + minRebalanceAmount = _minRebalanceAmount; + mintingFee[~uint256(0)] = _lastMintingFee; + balancePrecision = _balancePrecision; + minimumMintingFee = _minimumMintingFee; + minimumTrade = _minimumTrade; + } + + function setTokenSwapManager(address _tokenSwapManager) public onlyOwner { + require(_tokenSwapManager != address(0), "adddress must not be empty"); + tokenSwapManager = _tokenSwapManager; + } + + function setBridge(address _bridge) public onlyOwner { + require(_bridge != address(0), "adddress must not be empty"); + bridge = _bridge; + } + + function setIsPaused(bool _isPaused) public onlyOwner { + isPaused = _isPaused; + } + + function shutdown() public onlyOwner { + isShutdown = true; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwnerOrTokenSwap() { + require( + isOwner() || _msgSender() == tokenSwapManager, + "caller is not the owner or token swap manager" + ); + _; + } + + modifier onlyOwnerOrBridge() { + require( + isOwner() || _msgSender() == bridge, + "caller is not the owner or bridge" + ); + _; + } + + function setDelayedRedemptionsByUser( + uint256 amountToRedeem, + address whitelistedAddress + ) public onlyOwnerOrTokenSwap { + delayedRedemptionsByUser[whitelistedAddress] = amountToRedeem; + } + + /* + * Saves order in mapping (address => Order[]) orderByUser + * overwrite == false, append to Order[] + * overwrite == true, overwrite element at orderIndex + */ + + function setOrderByUser( + address whitelistedAddress, + string memory orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice, + uint256 orderIndex, + bool overwrite + ) public onlyOwnerOrTokenSwap() { + Order memory newOrder = Order( + orderType, + tokensGiven, + tokensRecieved, + mintingPrice + ); + + if (!overwrite) { + orderByUser[whitelistedAddress].push(newOrder); + setOrder( + orderType, + tokensGiven, + tokensRecieved, + mintingPrice, + orderIndex, + overwrite + ); + } else { + orderByUser[whitelistedAddress][orderIndex] = newOrder; + } + } + + /* + * Gets Order[] For User Address + * Return order at Index in Order[] + */ + + function getOrderByUser(address whitelistedAddress, uint256 orderIndex) + public + view + returns ( + string memory orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice + ) + { + + Order storage orderAtIndex + = orderByUser[whitelistedAddress][orderIndex]; + return ( + orderAtIndex.orderType, + orderAtIndex.tokensGiven, + orderAtIndex.tokensRecieved, + orderAtIndex.mintingPrice + ); + } + + /* + * Save order to allOrders array + * overwrite == false, append to allOrders array + * overwrite == true, overwrite element at orderIndex + */ + function setOrder( + string memory orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice, + uint256 orderIndex, + bool overwrite + ) public onlyOwnerOrTokenSwap() { + Order memory newOrder = Order( + orderType, + tokensGiven, + tokensRecieved, + mintingPrice + ); + + if (!overwrite) { + allOrders.push(newOrder); + } else { + allOrders[orderIndex] = newOrder; + } + } + + /* + * Get Order + */ + function getOrder(uint256 index) + public + view + returns ( + string memory orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice + ) + { + Order storage orderAtIndex = allOrders[index]; + return ( + orderAtIndex.orderType, + orderAtIndex.tokensGiven, + orderAtIndex.tokensRecieved, + orderAtIndex.mintingPrice + ); + } + + // @dev Get accounting values for a specific day + // @param date format as 20200123 for 23th of January 2020 + function getAccounting(uint256 date) + public + view + returns ( + uint256, + uint256, + uint256, + uint256, + int256, + uint256, + uint256 + ) + { + Accounting[] storage accountingsForDate = accounting[date]; + uint256 lastIndex = accountingsForDate.length - 1; + return ( + accountingsForDate[lastIndex].tokenValueNetFees, + accountingsForDate[lastIndex].bestExecutionPrice, + accountingsForDate[lastIndex].markPrice, + accountingsForDate[lastIndex].notional, + accountingsForDate[lastIndex].changeInNotional, + accountingsForDate[lastIndex].tokenValue, + accountingsForDate[lastIndex].effectiveFundingRate + ); + } + + // @dev Set accounting values for the day + function setAccounting( + uint256 _tokenValueNetFees, + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + int256 _changeInNotional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) external onlyOwnerOrTokenSwap() { + (uint256 year, uint256 month, uint256 day) = DateTimeLibrary + .timestampToDate(block.timestamp); + uint256 today = year * 10000 + month * 100 + day; + accounting[today].push( + Accounting( + _tokenValueNetFees, + _bestExecutionPrice, + _markPrice, + _notional, + _changeInNotional, + _tokenValue, + _effectiveFundingRate + ) + ); + lastActivityDay = today; + emit AccountingValuesSet(today); + } + + // @dev Set accounting values for the day + function setAccountingForLastActivityDay( + uint256 _tokenValueNetFees, + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + int256 _changeInNotional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) external onlyOwnerOrTokenSwap() { + accounting[lastActivityDay].push( + Accounting( + _tokenValueNetFees, + _bestExecutionPrice, + _markPrice, + _notional, + _changeInNotional, + _tokenValue, + _effectiveFundingRate + ) + ); + emit AccountingValuesSet(lastActivityDay); + } + + // @dev Set last rebalance information + function setMinRebalanceAmount(uint256 _minRebalanceAmount) + external + onlyOwner + { + minRebalanceAmount = _minRebalanceAmount; + + emit RebalanceValuesSet(minRebalanceAmount); + } + + // @dev Set last rebalance information + function setManagementFee(uint256 _managementFee) external onlyOwner { + managementFee = _managementFee; + emit ManagementFeeValuesSet(managementFee); + } + + // @dev Returns price + function getExecutionPrice() public view returns (uint256 price) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .bestExecutionPrice; + } + + // @dev Returns price + function getMarkPrice() public view returns (uint256 price) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .markPrice; + } + + // @dev Returns token value net of fees + function getTokenValueAfterFees() public view returns (uint256 amount) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .tokenValueNetFees; + } + + // @dev Returns notional amount + function getNotional() public view returns (uint256 amount) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .notional; + } + + // @dev Returns change in notional + function getChangeInNotional() public view returns (int256 amount) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .changeInNotional; + } + + // @dev Returns token value + function getTokenValue() public view returns (uint256 lendingRate) { + return + accounting[lastActivityDay][accounting[lastActivityDay].length - 1] + .tokenValue; + } + + // @dev Sets last minting fee + function setLastMintingFee(uint256 _mintingFee) public onlyOwner { + mintingFee[~uint256(0)] = _mintingFee; + } + + // @dev Adds minting fee + function addMintingFeeBracket(uint256 _mintingFeeLimit, uint256 _mintingFee) + public + onlyOwner + { + require( + mintingFeeBracket.length == 0 || + _mintingFeeLimit > + mintingFeeBracket[mintingFeeBracket.length - 1], + "New minting fee bracket needs to be bigger then last one" + ); + mintingFeeBracket.push(_mintingFeeLimit); + mintingFee[_mintingFeeLimit] = _mintingFee; + } + + // @dev Deletes last minting fee + function deleteLastMintingFeeBracket() public onlyOwner { + delete mintingFee[mintingFeeBracket[mintingFeeBracket.length - 1]]; + mintingFeeBracket.length--; + } + + // @dev Changes minting fee + function changeMintingLimit( + uint256 _position, + uint256 _mintingFeeLimit, + uint256 _mintingFee + ) public onlyOwner { + require( + _mintingFeeLimit > mintingFeeBracket[mintingFeeBracket.length - 1], + "New minting fee bracket needs to be bigger then last one" + ); + if (_position != 0) { + require( + _mintingFeeLimit > mintingFeeBracket[_position - 1], + "New minting fee bracket needs to be bigger then last one" + ); + } + if (_position < mintingFeeBracket.length - 1) { + require( + _mintingFeeLimit < mintingFeeBracket[_position + 1], + "New minting fee bracket needs to be smaller then next one" + ); + } + mintingFeeBracket[_position] = _mintingFeeLimit; + mintingFee[_mintingFeeLimit] = _mintingFee; + } + + function getMintingFee(uint256 cash) public view returns (uint256) { + // Define Start + End Index + uint256 startIndex = 0; + if (mintingFeeBracket.length > 0) { + uint256 endIndex = mintingFeeBracket.length - 1; + uint256 middleIndex = endIndex / 2; + + if (cash <= mintingFeeBracket[middleIndex]) { + endIndex = middleIndex; + } else { + startIndex = middleIndex + 1; + } + + for (uint256 i = startIndex; i <= endIndex; i++) { + if (cash <= mintingFeeBracket[i]) { + return mintingFee[mintingFeeBracket[i]]; + } + } + } + + return mintingFee[~uint256(0)]; + } + + // @dev Sets last balance precision + 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/flats/TokenSwapLeverage.sol b/flats/TokenSwapLeverage.sol new file mode 100644 index 0000000..fa79071 --- /dev/null +++ b/flats/TokenSwapLeverage.sol @@ -0,0 +1,1522 @@ + +// File: @openzeppelin/upgrades/contracts/Initializable.sol + +pragma solidity >=0.4.24 <0.6.0; + + +/** + * @title Initializable + * + * @dev Helper contract to support initializer functions. To use it, replace + * the constructor with a function that has the `initializer` modifier. + * WARNING: Unlike constructors, initializer functions must be manually + * invoked. This applies both to deploying an Initializable contract, as well + * as extending an Initializable contract via inheritance. + * WARNING: When used with inheritance, manual care must be taken to not invoke + * a parent initializer twice, or ensure that all initializers are idempotent, + * because this is not dealt with automatically as with constructors. + */ +contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private initializing; + + /** + * @dev Modifier to use in the initializer function of a contract. + */ + modifier initializer() { + require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized"); + + bool isTopLevelCall = !initializing; + if (isTopLevelCall) { + initializing = true; + initialized = true; + } + + _; + + if (isTopLevelCall) { + initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function isConstructor() private view returns (bool) { + // extcodesize checks the size of the code stored in an address, and + // address returns the current address. Since the code is still not + // deployed when running a constructor, any checks on its code size will + // yield zero, making it an effective way to detect if a contract is + // under construction or not. + uint256 cs; + assembly { cs := extcodesize(address) } + return cs == 0; + } + + // Reserved storage space to allow for layout changes in the future. + uint256[50] private ______gap; +} + +// File: solidity-util/lib/Strings.sol + +pragma solidity ^0.5.0; + +/** + * Strings Library + * + * In summary this is a simple library of string functions which make simple + * string operations less tedious in solidity. + * + * Please be aware these functions can be quite gas heavy so use them only when + * necessary not to clog the blockchain with expensive transactions. + * + * @author James Lockhart + */ +library Strings { + + /** + * Concat (High gas cost) + * + * Appends two strings together and returns a new value + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string which will be the concatenated + * prefix + * @param _value The value to be the concatenated suffix + * @return string The resulting string from combinging the base and value + */ + function concat(string memory _base, string memory _value) + internal + pure + returns (string memory) { + bytes memory _baseBytes = bytes(_base); + bytes memory _valueBytes = bytes(_value); + + assert(_valueBytes.length > 0); + + string memory _tmpValue = new string(_baseBytes.length + + _valueBytes.length); + bytes memory _newValue = bytes(_tmpValue); + + uint i; + uint j; + + for (i = 0; i < _baseBytes.length; i++) { + _newValue[j++] = _baseBytes[i]; + } + + for (i = 0; i < _valueBytes.length; i++) { + _newValue[j++] = _valueBytes[i]; + } + + return string(_newValue); + } + + /** + * Index Of + * + * Locates and returns the position of a character within a string + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string acting as the haystack to be + * searched + * @param _value The needle to search for, at present this is currently + * limited to one character + * @return int The position of the needle starting from 0 and returning -1 + * in the case of no matches found + */ + function indexOf(string memory _base, string memory _value) + internal + pure + returns (int) { + return _indexOf(_base, _value, 0); + } + + /** + * Index Of + * + * Locates and returns the position of a character within a string starting + * from a defined offset + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string acting as the haystack to be + * searched + * @param _value The needle to search for, at present this is currently + * limited to one character + * @param _offset The starting point to start searching from which can start + * from 0, but must not exceed the length of the string + * @return int The position of the needle starting from 0 and returning -1 + * in the case of no matches found + */ + function _indexOf(string memory _base, string memory _value, uint _offset) + internal + pure + returns (int) { + bytes memory _baseBytes = bytes(_base); + bytes memory _valueBytes = bytes(_value); + + assert(_valueBytes.length == 1); + + for (uint i = _offset; i < _baseBytes.length; i++) { + if (_baseBytes[i] == _valueBytes[0]) { + return int(i); + } + } + + return -1; + } + + /** + * Length + * + * Returns the length of the specified string + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string to be measured + * @return uint The length of the passed string + */ + function length(string memory _base) + internal + pure + returns (uint) { + bytes memory _baseBytes = bytes(_base); + return _baseBytes.length; + } + + /** + * Sub String + * + * Extracts the beginning part of a string based on the desired length + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string that will be used for + * extracting the sub string from + * @param _length The length of the sub string to be extracted from the base + * @return string The extracted sub string + */ + function substring(string memory _base, int _length) + internal + pure + returns (string memory) { + return _substring(_base, _length, 0); + } + + /** + * Sub String + * + * Extracts the part of a string based on the desired length and offset. The + * offset and length must not exceed the lenth of the base string. + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string that will be used for + * extracting the sub string from + * @param _length The length of the sub string to be extracted from the base + * @param _offset The starting point to extract the sub string from + * @return string The extracted sub string + */ + function _substring(string memory _base, int _length, int _offset) + internal + pure + returns (string memory) { + bytes memory _baseBytes = bytes(_base); + + assert(uint(_offset + _length) <= _baseBytes.length); + + string memory _tmp = new string(uint(_length)); + bytes memory _tmpBytes = bytes(_tmp); + + uint j = 0; + for (uint i = uint(_offset); i < uint(_offset + _length); i++) { + _tmpBytes[j++] = _baseBytes[i]; + } + + return string(_tmpBytes); + } + + /** + * String Split (Very high gas cost) + * + * Splits a string into an array of strings based off the delimiter value. + * Please note this can be quite a gas expensive function due to the use of + * storage so only use if really required. + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string value to be split. + * @param _value The delimiter to split the string on which must be a single + * character + * @return string[] An array of values split based off the delimiter, but + * do not container the delimiter. + */ + function split(string memory _base, string memory _value) + internal + pure + returns (string[] memory splitArr) { + bytes memory _baseBytes = bytes(_base); + + uint _offset = 0; + uint _splitsCount = 1; + while (_offset < _baseBytes.length - 1) { + int _limit = _indexOf(_base, _value, _offset); + if (_limit == -1) + break; + else { + _splitsCount++; + _offset = uint(_limit) + 1; + } + } + + splitArr = new string[](_splitsCount); + + _offset = 0; + _splitsCount = 0; + while (_offset < _baseBytes.length - 1) { + + int _limit = _indexOf(_base, _value, _offset); + if (_limit == - 1) { + _limit = int(_baseBytes.length); + } + + string memory _tmp = new string(uint(_limit) - _offset); + bytes memory _tmpBytes = bytes(_tmp); + + uint j = 0; + for (uint i = _offset; i < uint(_limit); i++) { + _tmpBytes[j++] = _baseBytes[i]; + } + _offset = uint(_limit) + 1; + splitArr[_splitsCount++] = string(_tmpBytes); + } + return splitArr; + } + + /** + * Compare To + * + * Compares the characters of two strings, to ensure that they have an + * identical footprint + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string base to compare against + * @param _value The string the base is being compared to + * @return bool Simply notates if the two string have an equivalent + */ + function compareTo(string memory _base, string memory _value) + internal + pure + returns (bool) { + bytes memory _baseBytes = bytes(_base); + bytes memory _valueBytes = bytes(_value); + + if (_baseBytes.length != _valueBytes.length) { + return false; + } + + for (uint i = 0; i < _baseBytes.length; i++) { + if (_baseBytes[i] != _valueBytes[i]) { + return false; + } + } + + return true; + } + + /** + * Compare To Ignore Case (High gas cost) + * + * Compares the characters of two strings, converting them to the same case + * where applicable to alphabetic characters to distinguish if the values + * match. + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string base to compare against + * @param _value The string the base is being compared to + * @return bool Simply notates if the two string have an equivalent value + * discarding case + */ + function compareToIgnoreCase(string memory _base, string memory _value) + internal + pure + returns (bool) { + bytes memory _baseBytes = bytes(_base); + bytes memory _valueBytes = bytes(_value); + + if (_baseBytes.length != _valueBytes.length) { + return false; + } + + for (uint i = 0; i < _baseBytes.length; i++) { + if (_baseBytes[i] != _valueBytes[i] && + _upper(_baseBytes[i]) != _upper(_valueBytes[i])) { + return false; + } + } + + return true; + } + + /** + * Upper + * + * Converts all the values of a string to their corresponding upper case + * value. + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string base to convert to upper case + * @return string + */ + function upper(string memory _base) + internal + pure + returns (string memory) { + bytes memory _baseBytes = bytes(_base); + for (uint i = 0; i < _baseBytes.length; i++) { + _baseBytes[i] = _upper(_baseBytes[i]); + } + return string(_baseBytes); + } + + /** + * Lower + * + * Converts all the values of a string to their corresponding lower case + * value. + * + * @param _base When being used for a data type this is the extended object + * otherwise this is the string base to convert to lower case + * @return string + */ + function lower(string memory _base) + internal + pure + returns (string memory) { + bytes memory _baseBytes = bytes(_base); + for (uint i = 0; i < _baseBytes.length; i++) { + _baseBytes[i] = _lower(_baseBytes[i]); + } + return string(_baseBytes); + } + + /** + * Upper + * + * Convert an alphabetic character to upper case and return the original + * value when not alphabetic + * + * @param _b1 The byte to be converted to upper case + * @return bytes1 The converted value if the passed value was alphabetic + * and in a lower case otherwise returns the original value + */ + function _upper(bytes1 _b1) + private + pure + returns (bytes1) { + + if (_b1 >= 0x61 && _b1 <= 0x7A) { + return bytes1(uint8(_b1) - 32); + } + + return _b1; + } + + /** + * Lower + * + * Convert an alphabetic character to lower case and return the original + * value when not alphabetic + * + * @param _b1 The byte to be converted to lower case + * @return bytes1 The converted value if the passed value was alphabetic + * and in a upper case otherwise returns the original value + */ + function _lower(bytes1 _b1) + private + pure + returns (bytes1) { + + if (_b1 >= 0x41 && _b1 <= 0x5A) { + return bytes1(uint8(_b1) + 32); + } + + return _b1; + } +} + +// File: @openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol + +pragma solidity ^0.5.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + * + * _Available since v2.4.0._ + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + * + * _Available since v2.4.0._ + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + * + * _Available since v2.4.0._ + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +// File: @openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol + +pragma solidity ^0.5.0; + + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +contract Context is Initializable { + // Empty internal constructor, to prevent people from mistakenly deploying + // an instance of this contract, which should be used via inheritance. + constructor () internal { } + // solhint-disable-previous-line no-empty-blocks + + function _msgSender() internal view returns (address payable) { + return msg.sender; + } + + function _msgData() internal view returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// File: @openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol + +pragma solidity ^0.5.0; + + + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be aplied to your functions to restrict their use to + * the owner. + */ +contract Ownable is Initializable, Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + function initialize(address sender) public initializer { + _owner = sender; + emit OwnershipTransferred(address(0), _owner); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(isOwner(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Returns true if the caller is the current owner. + */ + function isOwner() public view returns (bool) { + return _msgSender() == _owner; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * > Note: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + */ + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + + uint256[50] private ______gap; +} + +// File: contracts/short-tokens/Abstract/InterfaceCashPool.sol + +pragma solidity ^0.5.0; + + +interface InterfaceCashPool { + function kycVerifier() external view returns (address); + + function moveTokenToPool( + address _token, + address whiteListedAddress, + uint256 orderAmount + ) external returns (bool); + + function moveTokenfromPool( + address _token, + address destinationAddress, + uint256 orderAmount + ) external returns (bool); +} + +// File: contracts/short-tokens/Abstract/InterfaceKYCVerifier.sol + +pragma solidity ^0.5.0; + + +interface InterfaceKYCVerifier { + function isAddressWhitelisted(address) external view returns (bool); +} + +// File: contracts/leverage-tokens/Abstract/InterfaceCalculator.sol + +pragma solidity ^0.5.0; + + +interface InterfaceCalculator { + + function getTokensCreatedByCash( + uint256 mintingPrice, + uint256 cash, + uint256 gasFee + ) external view returns (uint256 tokensCreated) ; + + function getCashCreatedByTokens( + uint256 burningPrice, + uint256 elapsedTime, + uint256 tokens, + uint256 gasFee + ) external view returns (uint256 stablecoinRedeemed); + + + function calculateRebalanceValues( + uint256 _tokenValueNetFees, + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notionalPreRebalance, + uint256 _targetLeverage + ) external view + returns ( + uint256 notional, + int256 changeInNotional, + uint256 tokenValue + ); + + + function removeCurrentMintingFeeFromCash(uint256 _cash) + external view returns (uint256 cashAfterFee); + + function removeMintingFeeFromCash( + uint256 _cash, + uint256 _mintingFee, + uint256 _minimumMintingFee + ) external pure returns (uint256 cashAfterFee); + +} + +// File: contracts/short-tokens/Abstract/InterfaceERC20.sol + +pragma solidity ^0.5.0; + + +interface InterfaceERC20 { + function decimals() external view returns (uint8); +} + +// File: contracts/short-tokens/Abstract/InterfaceInverseToken.sol + +pragma solidity ^0.5.0; + + +interface InterfaceInverseToken { + function mintTokens(address, uint256) external returns (bool); + + function burnTokens(address, uint256) external returns (bool); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) + external + returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) + external + view + returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) + external + returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); +} + +// File: contracts/leverage-tokens/Abstract/InterfaceStorageLeverage.sol + +pragma solidity ^0.5.0; + + +interface InterfaceStorageLeverage { + function whitelistedAddresses(address) external view returns (bool); + + function isPaused() external view returns (bool); + + function isShutdown() external view returns (bool); + + function tokenSwapManager() external view returns (address); + + function bridge() external view returns (address); + + function managementFee() external view returns (uint256); + + function getExecutionPrice() external view returns (uint256); + + function getMarkPrice() external view returns (uint256); + + function getTokenValueAfterFees() external view returns (uint256); + + function getNotional() external view returns (uint256); + + function getTokenValue() external view returns (uint256); + + function getChangeInNotional() external view returns (int256); + + function getMintingFee(uint256 cash) external view returns (uint256); + + function minimumMintingFee() external view returns (uint256); + + function minRebalanceAmount() external view returns (uint8); + + function delayedRedemptionsByUser(address) external view returns (uint256); + + function setDelayedRedemptionsByUser( + uint256 amountToRedeem, + address whitelistedAddress + ) external; + + function setOrderByUser( + address whitelistedAddress, + string calldata orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice, + uint256 orderIndex, + bool overwrite + ) external; + + function setAccounting( + uint256 _tokenValueNetFees, + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + int256 _changeInNotional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) external; + + function setAccountingForLastActivityDay( + uint256 _tokenValueNetFees, + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notional, + int256 _changeInNotional, + uint256 _tokenValue, + uint256 _effectiveFundingRate + ) external; +} + +// File: contracts/short-tokens/utils/Math.sol + +/// Math.sol -- mixin for inline numerical wizardry + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.5.0; + + +library DSMath { + + // --- Unsigned Math ---- + function add(uint x, uint y) internal pure returns (uint z) { + require((z = x + y) >= x, "ds-math-add-overflow"); + } + function sub(uint x, uint y) internal pure returns (uint z) { + require((z = x - y) <= x, "ds-math-sub-underflow"); + } + function mul(uint x, uint y) internal pure returns (uint z) { + require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); + } + + + function min(uint x, uint y) internal pure returns (uint z) { + return x <= y ? x : y; + } + function max(uint x, uint y) internal pure returns (uint z) { + return x >= y ? x : y; + } + function imin(int x, int y) internal pure returns (int z) { + return x <= y ? x : y; + } + function imax(int x, int y) internal pure returns (int z) { + return x >= y ? x : y; + } + + // --- Precise Math --- + + uint public constant WAD = 10 ** 18; + uint public constant RAY = 10 ** 27; + + function wmul(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, y), WAD / 2) / WAD; + } + function rmul(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, y), RAY / 2) / RAY; + } + function wdiv(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, WAD), y / 2) / y; + } + function rdiv(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, RAY), y / 2) / y; + } + + function ray(uint _wad) internal pure returns (uint) { + return mul(_wad, uint(10 ** 9)); + } + + // This famous algorithm is called "exponentiation by squaring" + // and calculates x^n with x as fixed-point and n as regular unsigned. + // + // It's O(log n), instead of O(n) for naive repeated multiplication. + // + // These facts are why it works: + // + // If n is even, then x^n = (x^2)^(n/2). + // If n is odd, then x^n = x * x^(n-1), + // and applying the equation for even x gives + // x^n = x * (x^2)^((n-1) / 2). + // + // Also, EVM division is flooring and + // floor[(n-1) / 2] = floor[n / 2]. + // + function rpow(uint x, uint n) internal pure returns (uint z) { + z = n % 2 != 0 ? x : RAY; + + for (n /= 2; n != 0; n /= 2) { + x = rmul(x, x); + + if (n % 2 != 0) { + z = rmul(z, x); + } + } + } +} + +// File: contracts/leverage-tokens/TokenSwapLeverage.sol + +pragma solidity ^0.5.0; + + + + + + + + + + + + + +contract TokenSwapLeverage is Initializable, Ownable { + using Strings for string; + using SafeMath for uint256; + + address public inverseToken; + + InterfaceERC20 public erc20; + InterfaceKYCVerifier public kycVerifier; + InterfaceCashPool public cashPool; + InterfaceStorageLeverage public persistentStorage; + InterfaceCalculator public compositionCalculator; + + event SuccessfulOrder( + string orderType, + address whitelistedAddress, + uint256 tokensGiven, + uint256 tokensRecieved, + address stablecoin, + uint256 price + ); + + event RebalanceEvent( + uint256 tokenValueNetFees, + uint256 bestExecutionPrice, + uint256 markPrice, + uint256 notional, + int256 changeInNotional, + uint256 tokenValue, + uint256 effectiveFundingRate + ); + + function initialize( + address _owner, + address _inverseToken, + address _cashPool, + address _storage, + address _compositionCalculator + ) public initializer { + initialize(_owner); + + require( + _owner != address(0) && + _inverseToken != address(0) && + _cashPool != address(0) && + _storage != address(0) && + _compositionCalculator != address(0), + "addresses cannot be zero" + ); + + inverseToken = _inverseToken; + + cashPool = InterfaceCashPool(_cashPool); + persistentStorage = InterfaceStorageLeverage(_storage); + kycVerifier = InterfaceKYCVerifier(address(cashPool.kycVerifier())); + compositionCalculator = InterfaceCalculator(_compositionCalculator); + } + + //////////////// Create + Redeem Order Request //////////////// + //////////////// Create: Recieve Inverse Token //////////////// + //////////////// Redeem: Recieve Stable Coin //////////////// + + function createOrder( + bool success, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 mintingPrice, + address whitelistedAddress, + address stablecoin, + uint256 gasFee + ) public onlyOwnerOrBridge() notPausedOrShutdown() returns (bool retVal) { + // Require is Whitelisted + require( + kycVerifier.isAddressWhitelisted(whitelistedAddress), + "only whitelisted address may place orders" + ); + + // Return Funds if Bridge Pass an Error + if (!success) { + transferTokenFromPool( + stablecoin, + whitelistedAddress, + normalizeStablecoin(tokensGiven, stablecoin) + ); + return false; + } + + // Check Tokens Recieved with Composition Calculator + uint256 _tokensRecieved = compositionCalculator.getTokensCreatedByCash( + mintingPrice, + tokensGiven, + gasFee + ); + require( + _tokensRecieved == tokensRecieved, + "tokens created must equal tokens recieved" + ); + + // Save Order to Storage and Lock Funds for 1 Hour + persistentStorage.setOrderByUser( + whitelistedAddress, + "CREATE", + tokensGiven, + tokensRecieved, + mintingPrice, + 0, + false + ); + + // Write Successful Order to Log + writeOrderResponse( + "CREATE", + whitelistedAddress, + tokensGiven, + tokensRecieved, + stablecoin, + mintingPrice + ); + + // Mint Tokens to Address + InterfaceInverseToken token = InterfaceInverseToken(inverseToken); + token.mintTokens(whitelistedAddress, tokensRecieved); + + return true; + } + + function redeemOrder( + bool success, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 burningPrice, + address whitelistedAddress, + address stablecoin, + uint256 gasFee, + uint256 elapsedTime + ) public onlyOwnerOrBridge() notPausedOrShutdown() returns (bool retVal) { + // Require Whitelisted + require( + kycVerifier.isAddressWhitelisted(whitelistedAddress), + "only whitelisted address may place orders" + ); + + // Return Funds if Bridge Pass an Error + if (!success) { + transferTokenFromPool( + inverseToken, + whitelistedAddress, + tokensGiven + ); + return false; + } + + // Check Cash Recieved with Composition Calculator + uint256 _tokensRecieved = compositionCalculator.getCashCreatedByTokens( + burningPrice, + elapsedTime, + tokensGiven, + gasFee + ); + require( + _tokensRecieved == tokensRecieved, + "cash redeemed must equal tokens recieved" + ); + + // Save To Storage + persistentStorage.setOrderByUser( + whitelistedAddress, + "REDEEM", + tokensGiven, + tokensRecieved, + burningPrice, + 0, + false + ); + + // Redeem Stablecoin or Perform Delayed Settlement + redeemFunds( + tokensGiven, + tokensRecieved, + whitelistedAddress, + stablecoin, + burningPrice + ); + + // Burn Tokens to Address + InterfaceInverseToken token = InterfaceInverseToken(inverseToken); + token.burnTokens(address(cashPool), tokensGiven); + + return true; + } + + function writeOrderResponse( + string memory orderType, + address whiteListedAddress, + uint256 tokensGiven, + uint256 tokensRecieved, + address stablecoin, + uint256 price + ) internal { + require( + tokensGiven != 0 && tokensRecieved != 0, + "amount must be greater than 0" + ); + + emit SuccessfulOrder( + orderType, + whiteListedAddress, + tokensGiven, + tokensRecieved, + stablecoin, + price + ); + } + + function settleDelayedFunds( + uint256 tokensToRedeem, + address whitelistedAddress, + address stablecoin + ) public onlyOwnerOrBridge notPausedOrShutdown { + require( + kycVerifier.isAddressWhitelisted(whitelistedAddress), + "only whitelisted may redeem funds" + ); + + bool isSufficientFunds = isHotWalletSufficient( + tokensToRedeem, + stablecoin + ); + require( + isSufficientFunds == true, + "not enough funds in the hot wallet" + ); + + uint256 tokensOutstanding = persistentStorage.delayedRedemptionsByUser( + whitelistedAddress + ); + uint256 tokensRemaining = DSMath.sub(tokensOutstanding, tokensToRedeem); + + persistentStorage.setDelayedRedemptionsByUser( + tokensRemaining, + whitelistedAddress + ); + transferTokenFromPool( + stablecoin, + whitelistedAddress, + normalizeStablecoin(tokensToRedeem, stablecoin) + ); + } + + function redeemFunds( + uint256 tokensGiven, + uint256 tokensToRedeem, + address whitelistedAddress, + address stablecoin, + uint256 price + ) internal { + bool isSufficientFunds = isHotWalletSufficient( + tokensToRedeem, + stablecoin + ); + + if (isSufficientFunds) { + transferTokenFromPool( + stablecoin, + whitelistedAddress, + normalizeStablecoin(tokensToRedeem, stablecoin) + ); + writeOrderResponse( + "REDEEM", + whitelistedAddress, + tokensGiven, + tokensToRedeem, + stablecoin, + price + ); + } else { + uint256 tokensOutstanding = persistentStorage + .delayedRedemptionsByUser(whitelistedAddress); + tokensOutstanding = DSMath.add(tokensOutstanding, tokensToRedeem); + persistentStorage.setDelayedRedemptionsByUser( + tokensOutstanding, + whitelistedAddress + ); + writeOrderResponse( + "REDEEM_NO_SETTLEMENT", + whitelistedAddress, + tokensGiven, + tokensToRedeem, + stablecoin, + price + ); + } + } + + function isHotWalletSufficient(uint256 tokensToRedeem, address stablecoin) + internal + returns (bool) + { + InterfaceInverseToken _stablecoin = InterfaceInverseToken(stablecoin); + uint256 stablecoinBalance = _stablecoin.balanceOf(address(cashPool)); + + if (normalizeStablecoin(tokensToRedeem, stablecoin) > stablecoinBalance) + return false; + return true; + } + + function normalizeStablecoin(uint256 stablecoinValue, address stablecoin) + internal + returns (uint256) + { + erc20 = InterfaceERC20(stablecoin); + uint256 exponent = 18 - erc20.decimals(); + return stablecoinValue / 10**exponent; // 6 decimal stable coin = 10**12 + } + + //////////////// Daily Rebalance //////////////// + //////////////// Threshold Rebalance //////////////// + + /** + * @dev Performs rebalance calculations and saves them in persistent storages + * @param _tokenValueNetFees The token value after fees are removed + * @param _bestExecutionPrice The best execution price for rebalancing + * @param _markPrice The Mark Price + * @param _notionalPreRebalance The notional amount before rebalance + * @param _targetLeverage The targetLeverage + * @param _effectiveFundingRate The effectiveFundingRate + */ + function rebalance( + uint256 _tokenValueNetFees, + uint256 _bestExecutionPrice, + uint256 _markPrice, + uint256 _notionalPreRebalance, + uint256 _targetLeverage, + uint256 _effectiveFundingRate + ) public onlyOwnerOrBridge() notPausedOrShutdown() { + ( + uint256 notional, + int256 changeInNotional, + uint256 tokenValue + ) = compositionCalculator.calculateRebalanceValues( + _tokenValueNetFees, + _bestExecutionPrice, + _markPrice, + _notionalPreRebalance, + _targetLeverage + ); + + persistentStorage.setAccounting( + _tokenValueNetFees, + _bestExecutionPrice, + _markPrice, + notional, + changeInNotional, + tokenValue, + _effectiveFundingRate + ); + emit RebalanceEvent( + _tokenValueNetFees, + _bestExecutionPrice, + _markPrice, + notional, + changeInNotional, + tokenValue, + _effectiveFundingRate + ); + } + + //////////////// Transfer Stablecoin Out of Pool //////////////// + //////////////// Transfer Stablecoin In of Pool //////////////// + //////////////// Transfer InverseToken Out of Pool //////////////// + //////////////// Transfer InverseToken In of Pool //////////////// + + function transferTokenToPool( + address tokenContract, + address whiteListedAddress, + uint256 orderAmount + ) internal returns (bool) { + // Check orderAmount <= availableAmount + // Transfer USDC to Stablecoin Cash Pool + return + cashPool.moveTokenToPool( + tokenContract, + whiteListedAddress, + orderAmount + ); + } + + function transferTokenFromPool( + address tokenContract, + address destinationAddress, + uint256 orderAmount + ) internal returns (bool) { + // Check orderAmount <= availableAmount + // Transfer USDC to Destination Address + return + cashPool.moveTokenfromPool( + tokenContract, + destinationAddress, + orderAmount + ); + } + + function setCashPool(address _cashPool) public onlyOwner { + require(_cashPool != address(0), "adddress must not be empty"); + cashPool = InterfaceCashPool(_cashPool); + } + + function setStorage(address _storage) public onlyOwner { + require(_storage != address(0), "adddress must not be empty"); + persistentStorage = InterfaceStorageLeverage(_storage); + } + + function setKycVerfier(address _kycVerifier) public onlyOwner { + require(_kycVerifier != address(0), "adddress must not be empty"); + kycVerifier = InterfaceKYCVerifier(_kycVerifier); + } + + function setCalculator(address _calculator) public onlyOwner { + require(_calculator != address(0), "adddress must not be empty"); + compositionCalculator = InterfaceCalculator(_calculator); + } + + modifier onlyOwnerOrBridge() { + require( + isOwner() || _msgSender() == persistentStorage.bridge(), + "caller is not the owner or bridge" + ); + _; + } + + modifier notPausedOrShutdown() { + require(persistentStorage.isPaused() == false, "contract is paused"); + require( + persistentStorage.isShutdown() == false, + "contract is shutdown" + ); + _; + } +} diff --git a/flats/TokenSwapManager.sol b/flats/TokenSwapManager.sol index 32c7414..352a3a7 100644 --- a/flats/TokenSwapManager.sol +++ b/flats/TokenSwapManager.sol @@ -719,7 +719,70 @@ contract Ownable is Initializable, Context { uint256[50] private ______gap; } -// File: contracts/Abstract/InterfaceInverseToken.sol +// File: contracts/short-tokens/Abstract/InterfaceCashPool.sol + +pragma solidity ^0.5.0; + + +interface InterfaceCashPool { + function kycVerifier() external view returns (address); + + function moveTokenToPool( + address _token, + address whiteListedAddress, + uint256 orderAmount + ) external returns (bool); + + function moveTokenfromPool( + address _token, + address destinationAddress, + uint256 orderAmount + ) external returns (bool); +} + +// File: contracts/short-tokens/Abstract/InterfaceKYCVerifier.sol + +pragma solidity ^0.5.0; + + +interface InterfaceKYCVerifier { + function isAddressWhitelisted(address) external view returns (bool); +} + +// File: contracts/short-tokens/Abstract/InterfaceCompositionCalculator.sol + +pragma solidity ^0.5.0; + + +interface InterfaceCompositionCalculator { + function getCurrentCashAmountCreatedByToken( + uint256 _tokenAmount, + uint256 _spotPrice, + uint256 _gasFee + ) external view returns (uint256); + + function getCurrentTokenAmountCreatedByCash( + uint256 _cash, + uint256 _spotPrice, + uint256 _gasFee + ) external view returns (uint256); + + function calculateDailyPCF(uint256 _price, uint256 _lendingFee) + external + view + returns (uint256, uint256, uint256, uint256, uint256, bool); +} + +// File: contracts/short-tokens/Abstract/InterfaceERC20.sol + +pragma solidity ^0.5.0; + + +interface InterfaceERC20 { + function decimals() external view returns (uint8); +} + +// File: contracts/short-tokens/Abstract/InterfaceInverseToken.sol pragma solidity ^0.5.0; @@ -810,1730 +873,166 @@ interface InterfaceInverseToken { ); } -// File: contracts/utils/DateTimeLibrary.sol - -pragma solidity ^0.5.0; - -// ---------------------------------------------------------------------------- -// BokkyPooBah's DateTime Library v1.01 -// -// A gas-efficient Solidity date and time library -// -// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary -// -// Tested date range 1970/01/01 to 2345/12/31 -// -// Conventions: -// Unit | Range | Notes -// :-------- |:-------------:|:----- -// timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC -// year | 1970 ... 2345 | -// month | 1 ... 12 | -// day | 1 ... 31 | -// hour | 0 ... 23 | -// minute | 0 ... 59 | -// second | 0 ... 59 | -// dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday -// -// -// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence. -// ---------------------------------------------------------------------------- - -library DateTimeLibrary { - uint256 constant SECONDS_PER_DAY = 24 * 60 * 60; - uint256 constant SECONDS_PER_HOUR = 60 * 60; - uint256 constant SECONDS_PER_MINUTE = 60; - int256 constant OFFSET19700101 = 2440588; - - uint256 constant DOW_MON = 1; - uint256 constant DOW_TUE = 2; - uint256 constant DOW_WED = 3; - uint256 constant DOW_THU = 4; - uint256 constant DOW_FRI = 5; - uint256 constant DOW_SAT = 6; - uint256 constant DOW_SUN = 7; - - // ------------------------------------------------------------------------ - // Calculate the number of days from 1970/01/01 to year/month/day using - // the date conversion algorithm from - // http://aa.usno.navy.mil/faq/docs/JD_Formula.php - // and subtracting the offset 2440588 so that 1970/01/01 is day 0 - // - // days = day - // - 32075 - // + 1461 * (year + 4800 + (month - 14) / 12) / 4 - // + 367 * (month - 2 - (month - 14) / 12 * 12) / 12 - // - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4 - // - offset - // ------------------------------------------------------------------------ - function _daysFromDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (uint256 _days) - { - require(year >= 1970); - int256 _year = int256(year); - int256 _month = int256(month); - int256 _day = int256(day); - - int256 __days = _day - - 32075 + - (1461 * (_year + 4800 + (_month - 14) / 12)) / - 4 + - (367 * (_month - 2 - ((_month - 14) / 12) * 12)) / - 12 - - (3 * ((_year + 4900 + (_month - 14) / 12) / 100)) / - 4 - - OFFSET19700101; - - _days = uint256(__days); - } - - // ------------------------------------------------------------------------ - // Calculate year/month/day from the number of days since 1970/01/01 using - // the date conversion algorithm from - // http://aa.usno.navy.mil/faq/docs/JD_Formula.php - // and adding the offset 2440588 so that 1970/01/01 is day 0 - // - // int L = days + 68569 + offset - // int N = 4 * L / 146097 - // L = L - (146097 * N + 3) / 4 - // year = 4000 * (L + 1) / 1461001 - // L = L - 1461 * year / 4 + 31 - // month = 80 * L / 2447 - // dd = L - 2447 * month / 80 - // L = month / 11 - // month = month + 2 - 12 * L - // year = 100 * (N - 49) + year + L - // ------------------------------------------------------------------------ - function _daysToDate(uint256 _days) - internal - pure - returns (uint256 year, uint256 month, uint256 day) - { - int256 __days = int256(_days); - - int256 L = __days + 68569 + OFFSET19700101; - int256 N = (4 * L) / 146097; - L = L - (146097 * N + 3) / 4; - int256 _year = (4000 * (L + 1)) / 1461001; - L = L - (1461 * _year) / 4 + 31; - int256 _month = (80 * L) / 2447; - int256 _day = L - (2447 * _month) / 80; - L = _month / 11; - _month = _month + 2 - 12 * L; - _year = 100 * (N - 49) + _year + L; - - year = uint256(_year); - month = uint256(_month); - day = uint256(_day); - } - - function daysFromDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (uint256 daysSinceDate) - { - daysSinceDate = _daysFromDate(year, month, day); - } - function timestampFromDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (uint256 timestamp) - { - timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY; - } - function timestampFromDateTime( - uint256 year, - uint256 month, - uint256 day, - uint256 hour, - uint256 minute, - uint256 second - ) internal pure returns (uint256 timestamp) { - timestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - hour * - SECONDS_PER_HOUR + - minute * - SECONDS_PER_MINUTE + - second; - } - function timestampToDate(uint256 timestamp) - internal - pure - returns (uint256 year, uint256 month, uint256 day) - { - (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function timestampToDateTime(uint256 timestamp) - internal - pure - returns ( - uint256 year, - uint256 month, - uint256 day, - uint256 hour, - uint256 minute, - uint256 second - ) - { - (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); - uint256 secs = timestamp % SECONDS_PER_DAY; - hour = secs / SECONDS_PER_HOUR; - secs = secs % SECONDS_PER_HOUR; - minute = secs / SECONDS_PER_MINUTE; - second = secs % SECONDS_PER_MINUTE; - } - - function isValidDate(uint256 year, uint256 month, uint256 day) - internal - pure - returns (bool valid) - { - if (year >= 1970 && month > 0 && month <= 12) { - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > 0 && day <= daysInMonth) { - valid = true; - } - } - } - function isValidDateTime( - uint256 year, - uint256 month, - uint256 day, - uint256 hour, - uint256 minute, - uint256 second - ) internal pure returns (bool valid) { - if (isValidDate(year, month, day)) { - if (hour < 24 && minute < 60 && second < 60) { - valid = true; - } - } - } - function isLeapYear(uint256 timestamp) - internal - pure - returns (bool leapYear) - { - (uint256 year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); - leapYear = _isLeapYear(year); - } - function _isLeapYear(uint256 year) internal pure returns (bool leapYear) { - leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); - } - function isWeekDay(uint256 timestamp) internal pure returns (bool weekDay) { - weekDay = getDayOfWeek(timestamp) <= DOW_FRI; - } - function isWeekEnd(uint256 timestamp) internal pure returns (bool weekEnd) { - weekEnd = getDayOfWeek(timestamp) >= DOW_SAT; - } - function getDaysInMonth(uint256 timestamp) - internal - pure - returns (uint256 daysInMonth) - { - (uint256 year, uint256 month, ) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - daysInMonth = _getDaysInMonth(year, month); - } - function _getDaysInMonth(uint256 year, uint256 month) - internal - pure - returns (uint256 daysInMonth) - { - if ( - month == 1 || - month == 3 || - month == 5 || - month == 7 || - month == 8 || - month == 10 || - month == 12 - ) { - daysInMonth = 31; - } else if (month != 2) { - daysInMonth = 30; - } else { - daysInMonth = _isLeapYear(year) ? 29 : 28; - } - } - // 1 = Monday, 7 = Sunday - function getDayOfWeek(uint256 timestamp) - internal - pure - returns (uint256 dayOfWeek) - { - uint256 _days = timestamp / SECONDS_PER_DAY; - dayOfWeek = ((_days + 3) % 7) + 1; - } - - function getYear(uint256 timestamp) internal pure returns (uint256 year) { - (year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function getMonth(uint256 timestamp) internal pure returns (uint256 month) { - (, month, ) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function getDay(uint256 timestamp) internal pure returns (uint256 day) { - (, , day) = _daysToDate(timestamp / SECONDS_PER_DAY); - } - function getHour(uint256 timestamp) internal pure returns (uint256 hour) { - uint256 secs = timestamp % SECONDS_PER_DAY; - hour = secs / SECONDS_PER_HOUR; - } - function getMinute(uint256 timestamp) - internal - pure - returns (uint256 minute) - { - uint256 secs = timestamp % SECONDS_PER_HOUR; - minute = secs / SECONDS_PER_MINUTE; - } - function getSecond(uint256 timestamp) - internal - pure - returns (uint256 second) - { - second = timestamp % SECONDS_PER_MINUTE; - } - - function addYears(uint256 timestamp, uint256 _years) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - year += _years; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp >= timestamp); - } - function addMonths(uint256 timestamp, uint256 _months) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - month += _months; - year += (month - 1) / 12; - month = ((month - 1) % 12) + 1; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp >= timestamp); - } - function addDays(uint256 timestamp, uint256 _days) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _days * SECONDS_PER_DAY; - require(newTimestamp >= timestamp); - } - function addHours(uint256 timestamp, uint256 _hours) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _hours * SECONDS_PER_HOUR; - require(newTimestamp >= timestamp); - } - function addMinutes(uint256 timestamp, uint256 _minutes) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE; - require(newTimestamp >= timestamp); - } - function addSeconds(uint256 timestamp, uint256 _seconds) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp + _seconds; - require(newTimestamp >= timestamp); - } - - function subYears(uint256 timestamp, uint256 _years) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - year -= _years; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp <= timestamp); - } - function subMonths(uint256 timestamp, uint256 _months) - internal - pure - returns (uint256 newTimestamp) - { - (uint256 year, uint256 month, uint256 day) = _daysToDate( - timestamp / SECONDS_PER_DAY - ); - uint256 yearMonth = year * 12 + (month - 1) - _months; - year = yearMonth / 12; - month = (yearMonth % 12) + 1; - uint256 daysInMonth = _getDaysInMonth(year, month); - if (day > daysInMonth) { - day = daysInMonth; - } - newTimestamp = - _daysFromDate(year, month, day) * - SECONDS_PER_DAY + - (timestamp % SECONDS_PER_DAY); - require(newTimestamp <= timestamp); - } - function subDays(uint256 timestamp, uint256 _days) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _days * SECONDS_PER_DAY; - require(newTimestamp <= timestamp); - } - function subHours(uint256 timestamp, uint256 _hours) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _hours * SECONDS_PER_HOUR; - require(newTimestamp <= timestamp); - } - function subMinutes(uint256 timestamp, uint256 _minutes) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE; - require(newTimestamp <= timestamp); - } - function subSeconds(uint256 timestamp, uint256 _seconds) - internal - pure - returns (uint256 newTimestamp) - { - newTimestamp = timestamp - _seconds; - require(newTimestamp <= timestamp); - } - - function diffYears(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _years) - { - require(fromTimestamp <= toTimestamp); - (uint256 fromYear, , ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); - (uint256 toYear, , ) = _daysToDate(toTimestamp / SECONDS_PER_DAY); - _years = toYear - fromYear; - } - function diffMonths(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _months) - { - require(fromTimestamp <= toTimestamp); - (uint256 fromYear, uint256 fromMonth, ) = _daysToDate( - fromTimestamp / SECONDS_PER_DAY - ); - (uint256 toYear, uint256 toMonth, ) = _daysToDate( - toTimestamp / SECONDS_PER_DAY - ); - _months = toYear * 12 + toMonth - fromYear * 12 - fromMonth; - } - function diffDays(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _days) - { - require(fromTimestamp <= toTimestamp); - _days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY; - } - function diffHours(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _hours) - { - require(fromTimestamp <= toTimestamp); - _hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR; - } - function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _minutes) - { - require(fromTimestamp <= toTimestamp); - _minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE; - } - function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp) - internal - pure - returns (uint256 _seconds) - { - require(fromTimestamp <= toTimestamp); - _seconds = toTimestamp - fromTimestamp; - } -} - -// File: contracts/utils/Math.sol - -/// Math.sol -- mixin for inline numerical wizardry - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +// File: contracts/short-tokens/Abstract/InterfaceStorage.sol pragma solidity ^0.5.0; -library DSMath { - - // --- Unsigned Math ---- - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x, "ds-math-add-overflow"); - } - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x, "ds-math-sub-underflow"); - } - function mul(uint x, uint y) internal pure returns (uint z) { - require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); - } - - - function min(uint x, uint y) internal pure returns (uint z) { - return x <= y ? x : y; - } - function max(uint x, uint y) internal pure returns (uint z) { - return x >= y ? x : y; - } - function imin(int x, int y) internal pure returns (int z) { - return x <= y ? x : y; - } - function imax(int x, int y) internal pure returns (int z) { - return x >= y ? x : y; - } - - // --- Precise Math --- - - uint public constant WAD = 10 ** 18; - uint public constant RAY = 10 ** 27; - - function wmul(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, y), WAD / 2) / WAD; - } - function rmul(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, y), RAY / 2) / RAY; - } - function wdiv(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, WAD), y / 2) / y; - } - function rdiv(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, RAY), y / 2) / y; - } - - function ray(uint _wad) internal pure returns (uint) { - return mul(_wad, uint(10 ** 9)); - } - - // This famous algorithm is called "exponentiation by squaring" - // and calculates x^n with x as fixed-point and n as regular unsigned. - // - // It's O(log n), instead of O(n) for naive repeated multiplication. - // - // These facts are why it works: - // - // If n is even, then x^n = (x^2)^(n/2). - // If n is odd, then x^n = x * x^(n-1), - // and applying the equation for even x gives - // x^n = x * (x^2)^((n-1) / 2). - // - // Also, EVM division is flooring and - // floor[(n-1) / 2] = floor[n / 2]. - // - function rpow(uint x, uint n) internal pure returns (uint z) { - z = n % 2 != 0 ? x : RAY; - - for (n /= 2; n != 0; n /= 2) { - x = rmul(x, x); - - if (n % 2 != 0) { - z = rmul(z, x); - } - } - } -} - -// File: contracts/PersistentStorage.sol - -pragma solidity ^0.5.0; - - - - - -contract PersistentStorage is Ownable { - address public tokenSwapManager; - address public bridge; - - bool public isPaused; - bool public isShutdown; - - struct Accounting { - uint256 price; - uint256 cashPositionPerTokenUnit; - uint256 balancePerTokenUnit; - uint256 lendingFee; - } - - struct Order { - string orderType; - uint256 tokensGiven; - uint256 tokensRecieved; - uint256 avgBlendedFee; - } - - uint256 public lastActivityDay; - uint256 public minRebalanceAmount; - uint256 private managementFee; - - mapping(uint256 => Accounting[]) private accounting; - - mapping(address => bool) public whitelistedAddresses; - - uint256[] public mintingFeeBracket; - mapping(uint256 => uint256) public mintingFee; - - Order[] public allOrders; - mapping(address => Order[]) public orderByUser; - mapping(address => uint256) public delayedRedemptionsByUser; - - event AccountingValuesSet(uint256 today); - event RebalanceValuesSet(uint256 newMinRebalanceAmount); - event ManagementFeeValuesSet(uint256 newManagementFee); - - function initialize( - address ownerAddress, - uint256 _managementFee, - uint256 _minRebalanceAmount - ) 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 - } - - function setTokenSwapManager(address _tokenSwapManager) public onlyOwner { - require(_tokenSwapManager != address(0), "adddress must not be empty"); - tokenSwapManager = _tokenSwapManager; - } - - function setBridge(address _bridge) public onlyOwner { - require(_bridge != address(0), "adddress must not be empty"); - bridge = _bridge; - } - - function setIsPaused(bool _isPaused) public onlyOwner { - isPaused = _isPaused; - } - - function shutdown() public onlyOwner { - isShutdown = true; - } - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwnerOrTokenSwap() { - require( - isOwner() || _msgSender() == tokenSwapManager, - "caller is not the owner or token swap manager" - ); - _; - } - - function setDelayedRedemptionsByUser( - uint256 amountToRedeem, - address whitelistedAddress - ) public onlyOwnerOrTokenSwap { - delayedRedemptionsByUser[whitelistedAddress] = amountToRedeem; - } - - function getDelayedRedemptionsByUser(address whitelistedAddress) - public - view - returns (uint256) - { - return delayedRedemptionsByUser[whitelistedAddress]; - } - - /* - * Saves order in mapping (address => Order[]) orderByUser - * overwrite == false, append to Order[] - * overwrite == true, overwrite element at orderIndex - */ - - function setOrderByUser( - address whitelistedAddress, - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee, - uint256 orderIndex, - bool overwrite - ) public onlyOwnerOrTokenSwap() { - Order memory newOrder = Order( - orderType, - tokensGiven, - tokensRecieved, - avgBlendedFee - ); - - if (!overwrite) { - orderByUser[whitelistedAddress].push(newOrder); - setOrder( - orderType, - tokensGiven, - tokensRecieved, - avgBlendedFee, - orderIndex, - overwrite - ); - } else { - orderByUser[whitelistedAddress][orderIndex] = newOrder; - } - } - - /* - * Gets Order[] For User Address - * Return order at Index in Order[] - */ - - function getOrderByUser(address whitelistedAddress, uint256 orderIndex) - public - view - returns ( - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee - ) - { - - Order storage orderAtIndex - = orderByUser[whitelistedAddress][orderIndex]; - return ( - orderAtIndex.orderType, - orderAtIndex.tokensGiven, - orderAtIndex.tokensRecieved, - orderAtIndex.avgBlendedFee - ); - } - - /* - * Save order to allOrders array - * overwrite == false, append to allOrders array - * overwrite == true, overwrite element at orderIndex - */ - function setOrder( - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee, - uint256 orderIndex, - bool overwrite - ) public onlyOwnerOrTokenSwap() { - Order memory newOrder = Order( - orderType, - tokensGiven, - tokensRecieved, - avgBlendedFee - ); - - if (!overwrite) { - allOrders.push(newOrder); - } else { - allOrders[orderIndex] = newOrder; - } - } - - /* - * Get Order - */ - function getOrder(uint256 index) - public - view - returns ( - string memory orderType, - uint256 tokensGiven, - uint256 tokensRecieved, - uint256 avgBlendedFee - ) - { - Order storage orderAtIndex = allOrders[index]; - return ( - orderAtIndex.orderType, - orderAtIndex.tokensGiven, - orderAtIndex.tokensRecieved, - orderAtIndex.avgBlendedFee - ); - } - - // @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) - public - view - returns (uint256, uint256, uint256, uint256) - { - return ( - accounting[date][accounting[date].length - 1].price, - accounting[date][accounting[date].length - 1] - .cashPositionPerTokenUnit, - accounting[date][accounting[date].length - 1].balancePerTokenUnit, - accounting[date][accounting[date].length - 1].lendingFee - ); - } - - // @dev Set accounting values for the day - function setAccounting( - uint256 _price, - uint256 _cashPositionPerTokenUnit, - uint256 _balancePerTokenUnit, - uint256 _lendingFee - ) external onlyOwnerOrTokenSwap() { - (uint256 year, uint256 month, uint256 day) = DateTimeLibrary - .timestampToDate(block.timestamp); - uint256 today = year * 10000 + month * 100 + day; - accounting[today].push( - Accounting( - _price, - _cashPositionPerTokenUnit, - _balancePerTokenUnit, - _lendingFee - ) - ); - lastActivityDay = today; - emit AccountingValuesSet(today); - } - - // @dev Set accounting values for the day - function setAccountingForLastActivityDay( - uint256 _price, - uint256 _cashPositionPerTokenUnit, - uint256 _balancePerTokenUnit, - uint256 _lendingFee - ) external onlyOwnerOrTokenSwap() { - accounting[lastActivityDay].push( - Accounting( - _price, - _cashPositionPerTokenUnit, - _balancePerTokenUnit, - _lendingFee - ) - ); - emit AccountingValuesSet(lastActivityDay); - } - - // @dev Set last rebalance information - function setMinRebalanceAmount(uint256 _minRebalanceAmount) - external - onlyOwner - { - minRebalanceAmount = _minRebalanceAmount; - - emit RebalanceValuesSet(minRebalanceAmount); - } - - // @dev Set last rebalance information - function setManagementFee(uint256 _managementFee) external onlyOwner { - managementFee = _managementFee; - emit ManagementFeeValuesSet(managementFee); - } - - // @dev Returns price - function getPrice() public view returns (uint256 price) { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .price; - } - - // @dev Returns cash position amount - function getCashPositionPerTokenUnit() - public - view - returns (uint256 amount) - { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .cashPositionPerTokenUnit; - } - - // @dev Returns borrowed crypto amount - function getBalancePerTokenUnit() public view returns (uint256 amount) { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .balancePerTokenUnit; - } - - // @dev Returns lending fee - function getLendingFee() public view returns (uint256 lendingRate) { - return - accounting[lastActivityDay][accounting[lastActivityDay].length - 1] - .lendingFee; - } - - // @dev Returns lending fee - function getManagementFee() public view returns (uint256 lendingRate) { - return managementFee; - } - - // @dev Sets last minting fee - function setLastMintingFee(uint256 _mintingFee) public onlyOwner { - mintingFee[~uint256(0)] = _mintingFee; - } - - // @dev Adds minting fee - function addMintingFeeBracket(uint256 _mintingFeeLimit, uint256 _mintingFee) - public - onlyOwner - { - require( - _mintingFeeLimit > mintingFeeBracket[mintingFeeBracket.length - 1], - "New minting fee bracket needs to be bigger then last one" - ); - mintingFeeBracket.push(_mintingFeeLimit); - mintingFee[_mintingFeeLimit] = _mintingFee; - } - - // @dev Deletes last minting fee - function deleteLastMintingFeeBracket() public onlyOwner { - delete mintingFee[mintingFeeBracket[mintingFeeBracket.length - 1]]; - mintingFeeBracket.length--; - } - - // @dev Changes minting fee - function changeMintingLimit( - uint256 _position, - uint256 _mintingFeeLimit, - uint256 _mintingFee - ) public onlyOwner { - require( - _mintingFeeLimit > mintingFeeBracket[mintingFeeBracket.length - 1], - "New minting fee bracket needs to be bigger then last one" - ); - if (_position != 0) { - require( - _mintingFeeLimit > mintingFeeBracket[_position - 1], - "New minting fee bracket needs to be bigger then last one" - ); - } - if (_position < mintingFeeBracket.length - 1) { - require( - _mintingFeeLimit < mintingFeeBracket[_position + 1], - "New minting fee bracket needs to be smaller then next one" - ); - } - mintingFeeBracket[_position] = _mintingFeeLimit; - mintingFee[_mintingFeeLimit] = _mintingFee; - } - - function getMintingFee(uint256 cash) public view returns (uint256) { - // Define Start + End Index - uint256 startIndex = 0; - uint256 endIndex = mintingFeeBracket.length - 1; - uint256 middleIndex = endIndex / 2; - - if (cash <= mintingFeeBracket[middleIndex]) { - endIndex = middleIndex; - } else { - startIndex = middleIndex + 1; - } - - for (uint256 i = startIndex; i <= endIndex; i++) { - if (cash <= mintingFeeBracket[i]) { - return mintingFee[mintingFeeBracket[i]]; - } - } - return mintingFee[~uint256(0)]; - } -} - -// File: contracts/Abstract/InterfaceStorage.sol - -pragma solidity ^0.5.0; - - -interface InterfaceStorage { - function whitelistedAddresses(address) external view returns (bool); -} - -// File: contracts/KYCVerifier.sol - -pragma solidity ^0.5.0; - - - - -contract KYCVerifier is Initializable { - InterfaceStorage public persistentStorage; - - function initialize(address _persistentStorage) public initializer { - persistentStorage = InterfaceStorage(_persistentStorage); - } - - function isAddressWhitelisted(address userAddress) - public - view - returns (bool) - { - return persistentStorage.whitelistedAddresses(userAddress); - } -} - -// File: contracts/CashPool.sol - -pragma solidity ^0.5.0; - - - - - - - -contract CashPool is Ownable { - using SafeMath for uint256; - KYCVerifier public kycVerifier; - PersistentStorage public persistentStorage; - - uint256[2] public percentageOfFundsForColdStorage; - address public coldStorage; - - event SetPercentageOfFundsForColdStorageEvent( - uint256[2] newPercentageOfFundsForColdStorage - ); - - function initialize( - address ownerAddress, - address _kycVerifier, - address _persistentStorage, - address _coldStorage, - uint256[2] memory _percentageOfFundsForColdStorage - ) public initializer { - require( - ownerAddress != address(0) && - _kycVerifier != address(0) && - _coldStorage != address(0) && - _percentageOfFundsForColdStorage[1] != 0, - "params variables cannot be empty but _percentageOfFundsForColdStorage[0]" - ); - initialize(ownerAddress); - kycVerifier = KYCVerifier(_kycVerifier); - persistentStorage = PersistentStorage(_persistentStorage); - coldStorage = _coldStorage; - percentageOfFundsForColdStorage = _percentageOfFundsForColdStorage; - } - - function getBalance(address _token) public view returns (uint256) { - InterfaceInverseToken token_ = InterfaceInverseToken(_token); - uint256 tokenBalance = token_.balanceOf(address(this)); - return tokenBalance; - } - - // @dev Move tokens to cash pool - // @param _token ERC20 address - // @param whiteListedAddress address allowed to transfer to pool - // @param orderAmount amount to transfer to cash pool - function moveTokenToPool( - address _token, - address whiteListedAddress, - uint256 orderAmount - ) public onlyOwnerOrTokenSwap() returns (bool) { - InterfaceInverseToken token_ = InterfaceInverseToken(_token); - require( - kycVerifier.isAddressWhitelisted(whiteListedAddress), - "only whitelisted address are allowed to move tokens to pool" - ); - - uint256 percentageNumerator = percentageOfFundsForColdStorage[0]; - uint256 percentageDenominator = percentageOfFundsForColdStorage[1]; - uint256 amountForColdStorage = orderAmount.mul(percentageNumerator).div( - percentageDenominator - ); - - token_.transferFrom(whiteListedAddress, address(this), orderAmount); - token_.transfer(coldStorage, amountForColdStorage); - - return true; - } - - // @dev Move tokens out of cash pool - // @param _token ERC20 address - // @param destinationAddress address to send to - // @param orderAmount amount to transfer from cash pool - function moveTokenfromPool( - address _token, - address destinationAddress, - uint256 orderAmount - ) public onlyOwnerOrTokenSwap() returns (bool) { - InterfaceInverseToken token_ = InterfaceInverseToken(_token); - - token_.transfer(destinationAddress, orderAmount); - return true; - } - - modifier onlyOwnerOrTokenSwap() { - require( - isOwner() || _msgSender() == persistentStorage.tokenSwapManager(), - "caller is not the owner or token swap manager" - ); - _; - } - - // @dev Sets new coldStorage - // @param _newColdStorage Address for new cold storage wallet - function setColdStorage(address _newColdStorage) public onlyOwner { - require(_newColdStorage != address(0), "address cannot be empty"); - coldStorage = _newColdStorage; - } - - // @dev Sets percentage of funds to stay in contract. Owner only - // @param _newPercentageOfFundsForColdStorage List with two elements referencing percentage of funds for cold storage as a fraction - // e.g. 1/2 is [1,2] - function setPercentageOfFundsForColdStorage( - uint256[2] memory _newPercentageOfFundsForColdStorage - ) public onlyOwner { - require( - _newPercentageOfFundsForColdStorage[1] != 0, - "denominator should not be zero" - ); - require( - _newPercentageOfFundsForColdStorage[0] <= - _newPercentageOfFundsForColdStorage[1], - "cannot set more than 100% for coldstorage" - ); - percentageOfFundsForColdStorage[0] = _newPercentageOfFundsForColdStorage[0]; - percentageOfFundsForColdStorage[1] = _newPercentageOfFundsForColdStorage[1]; - - emit SetPercentageOfFundsForColdStorageEvent( - _newPercentageOfFundsForColdStorage - ); - } -} - -// File: contracts/CompositionCalculator.sol - -pragma solidity ^0.5.0; - - - - - - - - -/** - * @dev uint256 are expected to use last 18 numbers as decimal points except when specifid differently in @params - */ -contract CompositionCalculator is Initializable { - using SafeMath for uint256; - - PersistentStorage public persistentStorage; - InterfaceInverseToken public inverseToken; - - function initialize( - address _persistentStorageAddress, - address _inverseTokenAddress - ) public initializer { - persistentStorage = PersistentStorage(_persistentStorageAddress); - inverseToken = InterfaceInverseToken(_inverseTokenAddress); - } - - //************************************************************************* - //************************** Pure Functions ******************************* - //************************************************************************* - - /** - * @dev Returns NetTokenValue for the given values - * @param _cashPosition The yearly average lending fee for borrowed balance - * @param _balance The balance (dept/borrow) in crypto - * @param _price The momentary price of the crypto - */ - function getNetTokenValue( - uint256 _cashPosition, - uint256 _balance, - uint256 _price - ) public pure returns (uint256 netTokenValue) { - // Calculate NetTokenValue of Product - uint256 balanceWorth = DSMath.wmul(_balance, _price); - require( - _cashPosition > balanceWorth, - "The cash position needs to be bigger then the borrowed crypto is worth" - ); - netTokenValue = DSMath.sub(_cashPosition, balanceWorth); - } - - /** - * @dev Returns the crypto amount to pay as lending fee - * @param _lendingFee The yearly average lending fee for borrowed balance - * @param _balance The balance (dept/borrow) in crypto - * @param _days The days since the last fee calculation (Natural number) - */ - function getLendingFeeInCrypto( - uint256 _lendingFee, - uint256 _balance, - uint256 _days - ) public pure returns (uint256 feeInCrypto) { - uint256 lendingFeePercent = DSMath.wdiv(_lendingFee, 100 ether); - uint256 feePerDay = DSMath.wdiv(lendingFeePercent, 365 ether); - uint256 feeInCryptoForOneDay = DSMath.wmul(feePerDay, _balance); - feeInCrypto = DSMath.mul(_days, feeInCryptoForOneDay); - } - - /** - * Returns the change of balance with decimal at 18 - * @param _netTokenValue The current netTokenValue of the product - * @param _balance The balance (dept/borrow) in crypto - * @param _price The momentary price of the crypto - */ - function getNeededChangeInBalanceToRebalance( - uint256 _netTokenValue, - uint256 _balance, - uint256 _price - ) public pure returns (uint256 changeInBalance, bool isNegative) { - require(_price != 0, "Price cant be zero"); - - uint256 newAccountBalance = DSMath.wdiv(_netTokenValue, _price); - isNegative = newAccountBalance < _balance; - if (!isNegative) { - changeInBalance = DSMath.sub(newAccountBalance, _balance); - } else { - changeInBalance = DSMath.sub(_balance, newAccountBalance); - } - } +interface InterfaceStorage { + function whitelistedAddresses(address) external view returns (bool); - /** - * @param _a First number - * @param _b Second number - * @param _isBNegative Is number b negative - */ - function addOrSub( - //getDeliverables - uint256 _a, - uint256 _b, - bool _isBNegative - ) internal pure returns (uint256) { - if (_isBNegative) { - return _a.sub(_b); - } else { - return _a.add(_b); - } - } + function isPaused() external view returns (bool); - /** - * @dev Returns imported values for a PCF - * @param _cashPosition The total cash position on the token - * @param _balance The balance (dept/borrow) in crypto - * @param _price The momentary price of the crypto - * @param _lendingFee The yearly average lending fee for borrowed balance - * @param _days The days since the last fee calculation (Natural number) - * @param _minRebalanceAmount The minimum amount to rebalance - */ - function calculatePCF( - uint256 _cashPosition, - uint256 _balance, - uint256 _price, - uint256 _lendingFee, - uint256 _days, - uint256 _minRebalanceAmount - ) - public - pure - returns ( - uint256 endNetTokenValue, - uint256 endBalance, - uint256 endCashPosition, - uint256 feeInFiat, - uint256 changeInBalance, - bool isChangeInBalanceNeg - ) - { - require(_price != 0, "Price cant be zero"); - // Update Calculation for NetTokenValue, Cash Position, Loan Positions, and Accrued Fees - - //remove fees - uint256 feeInCrypto = getLendingFeeInCrypto( - _lendingFee, - _balance, - _days - ); - require( - feeInCrypto <= _balance, - "The balance cant be smaller then the fee" - ); + function isShutdown() external view returns (bool); - feeInFiat = DSMath.wmul(feeInCrypto, _price); + function tokenSwapManager() external view returns (address); - //cashPositionWithoutFee - endCashPosition = DSMath.sub(_cashPosition, feeInFiat); + function bridge() external view returns (address); - //calculte change in balance (rebalance) - endBalance = DSMath.wdiv( - getNetTokenValue(endCashPosition, _balance, _price), - _price - ); + function getCashPositionPerTokenUnit() external view returns (uint256); - ( - changeInBalance, - isChangeInBalanceNeg - ) = getNeededChangeInBalanceToRebalance( - getNetTokenValue(endCashPosition, _balance, _price), - _balance, - _price - ); + function getBalancePerTokenUnit() external view returns (uint256); - if (changeInBalance < _minRebalanceAmount) { - changeInBalance = 0; - endBalance = _balance; - } + function getMintingFee(uint256 cash) external view returns (uint256); - //result - endCashPosition = addOrSub( - endCashPosition, //cashPositionWithoutFee - DSMath.wmul(changeInBalance, _price), - isChangeInBalanceNeg - ); - endNetTokenValue = getNetTokenValue( - endCashPosition, - endBalance, - _price - ); - } + function getPrice() external view returns (uint256); - /** - * @param _cashPosition The total cash position on the token - * @param _balance The balance (dept/borrow) in crypto - * @param _price The momentary price of the crypto - * @param _lendingFee The yearly average lending fee for borrowed balance - * @param _days The days since the last fee calculation (Natural number) - */ - function calculatePCFWithoutMin( - //getDeliverables - uint256 _cashPosition, - uint256 _balance, - uint256 _price, - uint256 _lendingFee, - uint256 _days - ) - public - pure - returns ( - uint256 endNetTokenValue, - uint256 endBalance, - uint256 endCashPosition, - uint256 feeInFiat, - uint256 changeInBalance, - bool isChangeInBalanceNeg - ) - { - return - calculatePCF( - _cashPosition, - _balance, - _price, - _lendingFee, - _days, - 0 - ); - } + function minimumMintingFee() external view returns (uint256); - /** - * @dev Returns the amount of token created by cash at price - * @param _cashPosition The total cash position on the token - * @param _balance The balance (dept/borrow) in crypto - * @param _totalTokenSupply The total token supply - * @param _cash The cash provided to create token - * @param _spotPrice The momentary price of the crypto - */ - function getTokenAmountCreatedByCash( - //Create - uint256 _cashPosition, - uint256 _balance, - uint256 _totalTokenSupply, - uint256 _cash, - uint256 _spotPrice - ) public pure returns (uint256 tokenAmountCreated) { - require(_spotPrice != 0, "Price cant be zero"); - require(_totalTokenSupply != 0, "Token supply cant be zero"); - - uint256 netTokenValue = getNetTokenValue( - _cashPosition, - _balance, - _spotPrice - ); - uint256 netTokenValueTimesTokenAmount = DSMath.wmul( - netTokenValue, - _totalTokenSupply - ); - require( - netTokenValueTimesTokenAmount != 0, - "netTokenValueTimesTokenAmount cant be zero" - ); + function getLendingFee() external view returns (uint256); - tokenAmountCreated = DSMath.wdiv(_cash, netTokenValueTimesTokenAmount); - } + function minRebalanceAmount() external view returns (uint8); - /** - * @dev Returns the amount of cash redeemed at price - * @param _cashPosition The total cash position on the token - * @param _balance The balance (dept/borrow) in crypto - * @param _totalTokenSupply The total token supply - * @param _tokenAmount The token provided to redeem cash - * @param _spotPrice The momentary price of the crypto - */ - function getCashAmountCreatedByToken( - //Redeem - uint256 _cashPosition, - uint256 _balance, - uint256 _totalTokenSupply, - uint256 _tokenAmount, - uint256 _spotPrice - ) public pure returns (uint256 cashFromTokenRedeem) { - require(_spotPrice != 0, "Price cant be zero"); - require(_totalTokenSupply != 0, "Token supply cant be zero"); - - uint256 netTokenValue = getNetTokenValue( - _cashPosition, - _balance, - _spotPrice - ); - uint256 netTokenValueTimesTokenAmount = DSMath.wmul( - netTokenValue, - _tokenAmount - ); - cashFromTokenRedeem = DSMath.wdiv( - netTokenValueTimesTokenAmount, - _totalTokenSupply - ); - } + function delayedRedemptionsByUser(address) external view returns (uint256); - /** - * @dev Returns cash without fee - * @param _cash The cash provided to create token - * @param _mintingFee The minting fee to remove - */ - function removeMintingFeeFromCash(uint256 _cash, uint256 _mintingFee) - public - pure - returns (uint256 cashAfterFee) - { - uint256 creationFeeInCash = DSMath.wmul(_cash, _mintingFee); - cashAfterFee = DSMath.sub(_cash, creationFeeInCash); - } + function setDelayedRedemptionsByUser( + uint256 amountToRedeem, + address whitelistedAddress + ) external; - //************************************************************************* - //***************** Get values for last PCF ******************************* - //************************************************************************* + function setOrderByUser( + address whitelistedAddress, + string calldata orderType, + uint256 tokensGiven, + uint256 tokensRecieved, + uint256 avgBlendedFee, + uint256 orderIndex, + bool overwrite + ) external; - /** - * @dev Returns the current NetTokenValue - */ - function getCurrentNetTokenValue() - public - view - returns (uint256 netTokenValue) - { - uint256 totalTokenSupply = inverseToken.totalSupply(); - require(totalTokenSupply != 0, "Token supply cant be zero"); + function setAccounting( + uint256 _price, + uint256 _cashPositionPerTokenUnit, + uint256 _balancePerTokenUnit, + uint256 _lendingFee + ) external; - uint256 cashPosition = DSMath.wmul( - totalTokenSupply, - persistentStorage.getCashPositionPerTokenUnit() - ); - uint256 balance = DSMath.wmul( - totalTokenSupply, - persistentStorage.getBalancePerTokenUnit() - ); - uint256 price = persistentStorage.getPrice(); + function setAccountingForLastActivityDay( + uint256 _price, + uint256 _cashPositionPerTokenUnit, + uint256 _balancePerTokenUnit, + uint256 _lendingFee + ) external; +} - netTokenValue = getNetTokenValue(cashPosition, balance, price); - } +// File: contracts/short-tokens/utils/Math.sol - /** - * @dev Returns cash without fee - * @param _cash The cash provided to create token - */ - function removeCurrentMintingFeeFromCash(uint256 _cash) - public - view - returns (uint256 cashAfterFee) - { - uint256 creationFee = persistentStorage.getMintingFee(_cash); - cashAfterFee = removeMintingFeeFromCash(_cash, creationFee); - } +/// Math.sol -- mixin for inline numerical wizardry - /** - * @dev Returns the amount of token created by cash - * @param _cash The cash provided to create token - * @param _spotPrice The momentary price of the crypto - */ - function getCurrentTokenAmountCreatedByCash( - //Create - uint256 _cash, - uint256 _spotPrice - ) public view returns (uint256 tokenAmountCreated) { - uint256 cashAfterFee = removeCurrentMintingFeeFromCash(_cash); - tokenAmountCreated = getTokenAmountCreatedByCash( - persistentStorage.getCashPositionPerTokenUnit(), - persistentStorage.getBalancePerTokenUnit(), - 1 ether, - cashAfterFee, - _spotPrice - ); - } +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. - /** - * @dev Returns the amount of cash redeemed at spot price - * @param _tokenAmount The token provided to redeem cash - * @param _spotPrice The momentary price of the crypto - */ - function getCurrentCashAmountCreatedByToken( - //Redeem - uint256 _tokenAmount, - uint256 _spotPrice - ) public view returns (uint256 cashFromTokenRedeem) { - uint256 totalTokenSupply = inverseToken.totalSupply(); - require(totalTokenSupply != 0, "Token supply cant be zero"); - - uint256 lendingFee = persistentStorage.getLendingFee(); - uint256 daysSinceLastRebalance = getDaysSinceLastRebalance() + 1; - - uint256 cryptoForLendingFee = getLendingFeeInCrypto( - lendingFee, - DSMath.wmul( - _tokenAmount, - persistentStorage.getBalancePerTokenUnit() - ), - daysSinceLastRebalance - ); - uint256 fiatForLendingFee = DSMath.wmul( - cryptoForLendingFee, - _spotPrice - ); +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. - uint256 cashFromToken = getCashAmountCreatedByToken( - persistentStorage.getCashPositionPerTokenUnit(), - persistentStorage.getBalancePerTokenUnit(), - 1 ether, - _tokenAmount, - _spotPrice - ); +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . - cashFromTokenRedeem = removeCurrentMintingFeeFromCash( - DSMath.sub(cashFromToken, fiatForLendingFee) - ); - } +pragma solidity ^0.5.0; - function getDaysSinceLastRebalance() - public - view - returns (uint256 daysSinceLastRebalance) - { - uint256 lastRebalanceDay = persistentStorage.lastActivityDay(); - uint256 year = lastRebalanceDay.div(10000); - uint256 month = lastRebalanceDay.div(100) - year.mul(100); - uint256 day = lastRebalanceDay - year.mul(10000) - month.mul(100); - uint256 startDate = DateTimeLibrary.timestampFromDate(year, month, day); - daysSinceLastRebalance = (block.timestamp - startDate) / 60 / 60 / 24; +library DSMath { + + // --- Unsigned Math ---- + function add(uint x, uint y) internal pure returns (uint z) { + require((z = x + y) >= x, "ds-math-add-overflow"); + } + function sub(uint x, uint y) internal pure returns (uint z) { + require((z = x - y) <= x, "ds-math-sub-underflow"); + } + function mul(uint x, uint y) internal pure returns (uint z) { + require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); } - /** - * @dev Returns the lending fee in crypto - */ - function getCurrentLendingFeeInCrypto() - public - view - returns (uint256 cryptoForLendingFee) - { - uint256 totalTokenSupply = inverseToken.totalSupply(); - uint256 lendingFee = persistentStorage.getLendingFee(); - uint256 balance = DSMath.wmul( - totalTokenSupply, - persistentStorage.getBalancePerTokenUnit() - ); - uint256 daysSinceLastRebalance = getDaysSinceLastRebalance(); - cryptoForLendingFee = getLendingFeeInCrypto( - lendingFee, - balance, - daysSinceLastRebalance - ); + function min(uint x, uint y) internal pure returns (uint z) { + return x <= y ? x : y; } - - /** - * @dev Returns balance change needed to perform to have a balanced cashposition to balance ratio - */ - function getCurrentNeededChangeInBalanceToRebalance(uint256 _price) - public - view - returns (uint256 neededChangeInBalance, bool isNegative) - { - uint256 totalTokenSupply = inverseToken.totalSupply(); - uint256 netTokenValue = getCurrentNetTokenValue(); - uint256 balance = DSMath.wmul( - totalTokenSupply, - persistentStorage.getBalancePerTokenUnit() - ); - return - getNeededChangeInBalanceToRebalance(netTokenValue, balance, _price); + function max(uint x, uint y) internal pure returns (uint z) { + return x >= y ? x : y; } - - /** - * @dev Returns total balance - */ - function getTotalBalance() external view returns (uint256 totalBalance) { - uint256 totalTokenSupply = inverseToken.totalSupply(); - totalBalance = DSMath.wmul( - totalTokenSupply, - persistentStorage.getBalancePerTokenUnit() - ); + function imin(int x, int y) internal pure returns (int z) { + return x <= y ? x : y; + } + function imax(int x, int y) internal pure returns (int z) { + return x >= y ? x : y; } - /** - * @dev Returns current PCF values for the given price - * @param _price The momentary price of the crypto - */ - function calculateDailyPCF(uint256 _price, uint256 _lendingFee) - public - view - returns ( - uint256 endNetTokenValue, - uint256 endBalance, - uint256 endCashPosition, - uint256 feeInFiat, - uint256 changeInBalance, - bool isChangeInBalanceNeg - ) - { - require(_price != 0, "Price cant be zero"); - uint256 totalTokenSupply = inverseToken.totalSupply(); - uint256 balance = DSMath.wmul( - totalTokenSupply, - persistentStorage.getBalancePerTokenUnit() - ); - uint256 cashPosition = DSMath.wmul( - totalTokenSupply, - persistentStorage.getCashPositionPerTokenUnit() - ); - uint256 daysSinceLastRebalance = getDaysSinceLastRebalance(); - uint256 minRebalanceAmount = persistentStorage.minRebalanceAmount(); + // --- Precise Math --- - return - calculatePCF( - cashPosition, - balance, - _price, - _lendingFee, - daysSinceLastRebalance, - minRebalanceAmount - ); - } + uint public constant WAD = 10 ** 18; + uint public constant RAY = 10 ** 27; - /** - * @dev Returns total cash position - */ - function getTotalCashPosition() - public - view - returns (uint256 totalCashPosition) - { - uint256 totalTokenSupply = inverseToken.totalSupply(); - totalCashPosition = DSMath.wmul( - totalTokenSupply, - persistentStorage.getCashPositionPerTokenUnit() - ); + function wmul(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, y), WAD / 2) / WAD; + } + function rmul(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, y), RAY / 2) / RAY; + } + function wdiv(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, WAD), y / 2) / y; + } + function rdiv(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, RAY), y / 2) / y; } - function wmul(uint256 x, uint256 y) external pure returns (uint256 z) { - z = DSMath.wmul(x, y); + function ray(uint _wad) internal pure returns (uint) { + return mul(_wad, uint(10 ** 9)); } - function wdiv(uint256 x, uint256 y) external pure returns (uint256 z) { - z = DSMath.wdiv(x, y); + // This famous algorithm is called "exponentiation by squaring" + // and calculates x^n with x as fixed-point and n as regular unsigned. + // + // It's O(log n), instead of O(n) for naive repeated multiplication. + // + // These facts are why it works: + // + // If n is even, then x^n = (x^2)^(n/2). + // If n is odd, then x^n = x * x^(n-1), + // and applying the equation for even x gives + // x^n = x * (x^2)^((n-1) / 2). + // + // Also, EVM division is flooring and + // floor[(n-1) / 2] = floor[n / 2]. + // + function rpow(uint x, uint n) internal pure returns (uint z) { + z = n % 2 != 0 ? x : RAY; + + for (n /= 2; n != 0; n /= 2) { + x = rmul(x, x); + + if (n % 2 != 0) { + z = rmul(z, x); + } + } } } -// File: contracts/TokenSwapManager.sol +// File: contracts/short-tokens/TokenSwapManager.sol pragma solidity ^0.5.0; @@ -2548,23 +1047,25 @@ pragma solidity ^0.5.0; + contract TokenSwapManager is Initializable, Ownable { using Strings for string; using SafeMath for uint256; - address public stablecoin; address public inverseToken; - KYCVerifier public kycVerifier; - CashPool public cashPool; - PersistentStorage public persistentStorage; - CompositionCalculator public compositionCalculator; + InterfaceERC20 public erc20; + InterfaceKYCVerifier public kycVerifier; + InterfaceCashPool public cashPool; + InterfaceStorage public persistentStorage; + InterfaceCompositionCalculator public compositionCalculator; event SuccessfulOrder( string orderType, address whitelistedAddress, uint256 tokensGiven, - uint256 tokensRecieved + uint256 tokensRecieved, + address stablecoin ); event RebalanceEvent( @@ -2576,31 +1077,29 @@ contract TokenSwapManager is Initializable, Ownable { function initialize( address _owner, - address _stablecoin, address _inverseToken, address _cashPool, + address _persistentStorage, address _compositionCalculator ) public initializer { initialize(_owner); 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); - persistentStorage = PersistentStorage( - address(cashPool.persistentStorage()) + cashPool = InterfaceCashPool(_cashPool); + persistentStorage = InterfaceStorage(_persistentStorage); + kycVerifier = InterfaceKYCVerifier(address(cashPool.kycVerifier())); + compositionCalculator = InterfaceCompositionCalculator( + _compositionCalculator ); - kycVerifier = KYCVerifier(address(cashPool.kycVerifier())); - compositionCalculator = CompositionCalculator(_compositionCalculator); } //////////////// Create + Redeem Order Request //////////////// @@ -2613,7 +1112,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( @@ -2626,14 +1127,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" @@ -2655,7 +1160,8 @@ contract TokenSwapManager is Initializable, Ownable { "CREATE", whitelistedAddress, tokensGiven, - tokensRecieved + tokensRecieved, + stablecoin ); // Mint Tokens to Address @@ -2671,7 +1177,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( @@ -2691,7 +1199,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" @@ -2709,7 +1221,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); @@ -2722,7 +1239,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, @@ -2733,27 +1251,33 @@ contract TokenSwapManager is Initializable, Ownable { orderType, whiteListedAddress, tokensGiven, - tokensRecieved + tokensRecieved, + stablecoin ); } function settleDelayedFunds( uint256 tokensToRedeem, - address whitelistedAddress - ) public onlyOwnerOrBridge { + 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" ); - uint256 tokensOutstanding = persistentStorage - .getDelayedRedemptionsByUser(whitelistedAddress); + uint256 tokensOutstanding = persistentStorage.delayedRedemptionsByUser( + whitelistedAddress + ); uint256 tokensRemaining = DSMath.sub(tokensOutstanding, tokensToRedeem); persistentStorage.setDelayedRedemptionsByUser( @@ -2763,60 +1287,71 @@ 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 - .getDelayedRedemptionsByUser(whitelistedAddress); + .delayedRedemptionsByUser(whitelistedAddress); tokensOutstanding = DSMath.add(tokensOutstanding, tokensToRedeem); persistentStorage.setDelayedRedemptionsByUser( tokensOutstanding, whitelistedAddress ); writeOrderResponse( - "REDEEM NO SETTLEMENT", + "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 = InterfaceERC20(stablecoin); + uint256 exponent = 18 - erc20.decimals(); + return stablecoinValue / 10**exponent; // 6 decimal stable coin = 10**12 } //////////////// Daily Rebalance //////////////// diff --git a/flats/USDC.sol b/flats/USDC.sol index 6bd60f4..b16f571 100644 --- a/flats/USDC.sol +++ b/flats/USDC.sol @@ -1,5 +1,5 @@ -// File: contracts/Token/USDC.sol +// File: contracts/shared/Token/USDC.sol /** *Submitted for verification at Etherscan.io on 2020-03-03 diff --git a/flats/USDT.sol b/flats/USDT.sol new file mode 100644 index 0000000..d07cbf7 --- /dev/null +++ b/flats/USDT.sol @@ -0,0 +1,656 @@ + +// File: contracts/shared/Token/USDT.sol + +/** + *Submitted for verification at Etherscan.io on 2020-03-03 + */ + +/** + *Submitted for verification at Etherscan.io on 2018-08-03 + */ + +pragma solidity ^0.5.0; + + +// File: contracts/OwnableUSDCT.sol + +/** + * Copyright CENTRE SECZ 2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @title OwnableUSDCT + * @dev The OwnableUSDCT contract from https://github.com/zeppelinos/labs/blob/master/upgradeability_ownership/contracts/ownership/Ownable.sol + * branch: master commit: 3887ab77b8adafba4a26ace002f3a684c1a3388b modified to: + * 1) Add emit prefix to OwnershipTransferred event (7/13/18) + * 2) Replace constructor with constructor syntax (7/13/18) + * 3) consolidate OwnableUSDCTStorage into this contract + */ +contract OwnableUSDCT { + // Owner of the contract + address private _owner; + + /** + * @dev Event to show ownership has been transferred + * @param previousOwner representing the address of the previous owner + * @param newOwner representing the address of the new owner + */ + event OwnershipTransferred(address previousOwner, address newOwner); + + /** + * @dev The constructor sets the original owner of the contract to the sender account. + */ + function initialize() public { + setOwner(msg.sender); + } + + /** + * @dev Tells the address of the owner + * @return the address of the owner + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Sets a new owner address + */ + function setOwner(address newOwner) internal { + _owner = newOwner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner()); + _; + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) public onlyOwner { + require(newOwner != address(0)); + emit OwnershipTransferred(owner(), newOwner); + setOwner(newOwner); + } +} + + +// File: contracts/Blacklistable.sol + +/** + * Copyright CENTRE SECZ 2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @title Blacklistable Token + * @dev Allows accounts to be blacklisted by a "blacklister" role + */ +contract Blacklistable is OwnableUSDCT { + address public blacklister; + mapping(address => bool) internal blacklisted; + + event Blacklisted(address indexed _account); + event UnBlacklisted(address indexed _account); + event BlacklisterChanged(address indexed newBlacklister); + + /** + * @dev Throws if called by any account other than the blacklister + */ + modifier onlyBlacklister() { + require(msg.sender == blacklister); + _; + } + + /** + * @dev Throws if argument account is blacklisted + * @param _account The address to check + */ + modifier notBlacklisted(address _account) { + require(blacklisted[_account] == false); + _; + } + + /** + * @dev Checks if account is blacklisted + * @param _account The address to check + */ + function isBlacklisted(address _account) public view returns (bool) { + return blacklisted[_account]; + } + + /** + * @dev Adds account to blacklist + * @param _account The address to blacklist + */ + function blacklist(address _account) public onlyBlacklister { + blacklisted[_account] = true; + emit Blacklisted(_account); + } + + /** + * @dev Removes account from blacklist + * @param _account The address to remove from the blacklist + */ + function unBlacklist(address _account) public onlyBlacklister { + blacklisted[_account] = false; + emit UnBlacklisted(_account); + } + + function updateBlacklister(address _newBlacklister) public onlyOwner { + require(_newBlacklister != address(0)); + blacklister = _newBlacklister; + emit BlacklisterChanged(blacklister); + } +} + + +// File: contracts/Pausable.sol + +/** + * Copyright CENTRE SECZ 2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @title Pausable + * @dev Base contract which allows children to implement an emergency stop mechanism. + * Based on openzeppelin tag v1.10.0 commit: feb665136c0dae9912e08397c1a21c4af3651ef3 + * Modifications: + * 1) Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018) + * 2) Removed whenNotPause/whenPaused from pause/unpause (6/14/2018) + * 3) Removed whenPaused (6/14/2018) + * 4) Switches ownable library to use zeppelinos (7/12/18) + * 5) Remove constructor (7/13/18) + */ +contract Pausable is OwnableUSDCT { + event Pause(); + event Unpause(); + event PauserChanged(address indexed newAddress); + + address public pauser; + bool public paused = false; + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!paused); + _; + } + + /** + * @dev throws if called by any account other than the pauser + */ + modifier onlyPauser() { + require(msg.sender == pauser); + _; + } + + /** + * @dev called by the owner to pause, triggers stopped state + */ + function pause() public onlyPauser { + paused = true; + emit Pause(); + } + + /** + * @dev called by the owner to unpause, returns to normal state + */ + function unpause() public onlyPauser { + paused = false; + emit Unpause(); + } + + /** + * @dev update the pauser role + */ + function updatePauser(address _newPauser) public onlyOwner { + require(_newPauser != address(0)); + pauser = _newPauser; + emit PauserChanged(pauser); + } +} + + +// File: openzeppelin-solidity/contracts/math/SafeMath.sol + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { + // Gas optimization: this is cheaper than asserting 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (a == 0) { + return 0; + } + + c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + // uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return a / b; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256 c) { + c = a + b; + assert(c >= a); + return c; + } +} + + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol + +/** + * @title ERC20Basic + * @dev Simpler version of ERC20 interface + * See https://github.com/ethereum/EIPs/issues/179 + */ +contract ERC20Basic { + function totalSupply() public view returns (uint256); + + function balanceOf(address who) public view returns (uint256); + + function transfer(address to, uint256 value) public returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); +} + + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +contract ERC20T is ERC20Basic { + function allowance(address owner, address spender) + public + view + returns (uint256); + + function transferFrom(address from, address to, uint256 value) + public + returns (bool); + + function approve(address spender, uint256 value) public returns (bool); + + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); +} + + +// File: contracts/FiatTokenV1.sol + +/** + * Copyright CENTRE SECZ 2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @title FiatToken + * @dev ERC20 Token backed by fiat reserves + */ +contract USDT is OwnableUSDCT, ERC20T, Pausable, Blacklistable { + using SafeMath for uint256; + + string public name; + string public symbol; + uint8 public decimals; + string public currency; + address public masterMinter; + bool internal initialized; + uint256 internal totalSupply_; + + mapping(address => uint256) internal balances; + mapping(address => mapping(address => uint256)) internal allowed; + mapping(address => bool) internal minters; + mapping(address => uint256) internal minterAllowed; + + event Mint(address indexed minter, address indexed to, uint256 amount); + event Burn(address indexed burner, uint256 amount); + event MinterConfigured(address indexed minter, uint256 minterAllowedAmount); + event MinterRemoved(address indexed oldMinter); + event MasterMinterChanged(address indexed newMasterMinter); + + function initialize( + string memory _name, + string memory _symbol, + string memory _currency, + uint8 _decimals, + address _masterMinter, + address _pauser, + address _blacklister, + address _owner + ) public { + require(!initialized); + require(_masterMinter != address(0)); + require(_pauser != address(0)); + require(_blacklister != address(0)); + require(_owner != address(0)); + + totalSupply_ = 0; + name = _name; + symbol = _symbol; + currency = _currency; + decimals = _decimals; + masterMinter = _masterMinter; + pauser = _pauser; + blacklister = _blacklister; + setOwner(_owner); + initialized = true; + } + + /** + * @dev Throws if called by any account other than a minter + */ + modifier onlyMinters() { + require(minters[msg.sender] == true); + _; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. Must be less than or equal to the minterAllowance of the caller. + * @return A boolean that indicates if the operation was successful. + */ + function mint(address _to, uint256 _amount) + public + whenNotPaused + onlyMinters + notBlacklisted(msg.sender) + notBlacklisted(_to) + returns (bool) + { + require(_to != address(0)); + require(_amount > 0); + + uint256 mintingAllowedAmount = minterAllowed[msg.sender]; + require(_amount <= mintingAllowedAmount); + + totalSupply_ = totalSupply_.add(_amount); + balances[_to] = balances[_to].add(_amount); + minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount); + emit Mint(msg.sender, _to, _amount); + emit Transfer(address(0), _to, _amount); + return true; + } + + /** + * @dev Throws if called by any account other than the masterMinter + */ + modifier onlyMasterMinter() { + require(msg.sender == masterMinter); + _; + } + + /** + * @dev Get minter allowance for an account + * @param minter The address of the minter + */ + function minterAllowance(address minter) public view returns (uint256) { + return minterAllowed[minter]; + } + + /** + * @dev Checks if account is a minter + * @param account The address to check + */ + function isMinter(address account) public view returns (bool) { + return minters[account]; + } + + /** + * @dev Get allowed amount for an account + * @param owner address The account owner + * @param spender address The account spender + */ + function allowance(address owner, address spender) + public + view + returns (uint256) + { + return allowed[owner][spender]; + } + + /** + * @dev Get totalSupply of token + */ + function totalSupply() public view returns (uint256) { + return totalSupply_; + } + + /** + * @dev Get token balance of an account + * @param account address The account + */ + function balanceOf(address account) public view returns (uint256) { + return balances[account]; + } + + /** + * @dev Adds blacklisted check to approve + * @return True if the operation was successful. + */ + function approve(address _spender, uint256 _value) + public + whenNotPaused + notBlacklisted(msg.sender) + notBlacklisted(_spender) + returns (bool) + { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @dev Transfer tokens from one address to another. + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @return bool success + */ + function transferFrom(address _from, address _to, uint256 _value) + public + whenNotPaused + notBlacklisted(_to) + notBlacklisted(msg.sender) + notBlacklisted(_from) + returns (bool) + { + require(_to != address(0)); + require(_value <= balances[_from]); + require(_value <= allowed[_from][msg.sender]); + + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + emit Transfer(_from, _to, _value); + return true; + } + + /** + * @dev transfer token for a specified address + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + * @return bool success + */ + function transfer(address _to, uint256 _value) + public + whenNotPaused + notBlacklisted(msg.sender) + notBlacklisted(_to) + returns (bool) + { + require(_to != address(0)); + require(_value <= balances[msg.sender]); + + balances[msg.sender] = balances[msg.sender].sub(_value); + balances[_to] = balances[_to].add(_value); + emit Transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev Function to add/update a new minter + * @param minter The address of the minter + * @param minterAllowedAmount The minting amount allowed for the minter + * @return True if the operation was successful. + */ + function configureMinter(address minter, uint256 minterAllowedAmount) + public + whenNotPaused + onlyMasterMinter + returns (bool) + { + minters[minter] = true; + minterAllowed[minter] = minterAllowedAmount; + emit MinterConfigured(minter, minterAllowedAmount); + return true; + } + + /** + * @dev Function to remove a minter + * @param minter The address of the minter to remove + * @return True if the operation was successful. + */ + function removeMinter(address minter) + public + onlyMasterMinter + returns (bool) + { + minters[minter] = false; + minterAllowed[minter] = 0; + emit MinterRemoved(minter); + return true; + } + + /** + * @dev allows a minter to burn some of its own tokens + * Validates that caller is a minter and that sender is not blacklisted + * amount is less than or equal to the minter's account balance + * @param _amount uint256 the amount of tokens to be burned + */ + function burn(uint256 _amount) + public + whenNotPaused + onlyMinters + notBlacklisted(msg.sender) + { + uint256 balance = balances[msg.sender]; + require(_amount > 0); + require(balance >= _amount); + + totalSupply_ = totalSupply_.sub(_amount); + balances[msg.sender] = balance.sub(_amount); + emit Burn(msg.sender, _amount); + emit Transfer(msg.sender, address(0), _amount); + } + + function updateMasterMinter(address _newMasterMinter) public onlyOwner { + require(_newMasterMinter != address(0)); + masterMinter = _newMasterMinter; + emit MasterMinterChanged(masterMinter); + } +} diff --git a/scripts/deployContracts.sh b/scripts/deployContracts.sh new file mode 100755 index 0000000..5ecf001 --- /dev/null +++ b/scripts/deployContracts.sh @@ -0,0 +1,302 @@ +#!/usr/bin/env bash + +function deployContracts() { + + if [ "$network" == "development" ]; then + # Check if ganache is running and terminate if not + isGanacheRunning="$(pgrep node | xargs ps -fp | grep -c ganache)" + + if [ "$isGanacheRunning" == 1 ]; then + echo "### Ganache is running. Can move on to compile ###" + else + echo "### Ganache isn't running. Please run Ganache (npm run dev) before running this script ###" + exit + fi + else + echo "### Deploying contract on ${network} network ###" + fi + + managementFee=11000000000000000000 + minRebalanceAmount=1000000000000000000 + inverseTokenName=$2 + lastMintingFee=1000000000000000; + balancePrecision=12; + minimumMintingFee=0; # $5 + minimumTrade=50000000000000000000; # $50 + + + if [ "$isLeverageToken" == "true" ]; then + echo + echo "### Deploying StorageLeverage ###" + echo + persistenStorageContractName="StorageLeverage" + persistentStorageAddress="$(exec npx oz create StorageLeverage -n ${network} --init --args $ownerAddress,$managementFee,$minRebalanceAmount,$balancePrecision,$lastMintingFee,$minimumMintingFee,$minimumTrade --no-interactive --force)" + else + echo + echo "### Compiling and deploying persistentStorage ###" + echo + persistenStorageContractName="PersistentStorage" + persistentStorageAddress="$(exec npx oz create PersistentStorage -n ${network} --init --args $ownerAddress,$managementFee,$minRebalanceAmount,$balancePrecision,$lastMintingFee,$minimumMintingFee,$minimumTrade --no-interactive --force)" + fi + + echo + echo "### Compiling and deploying InverseToken ###" + echo + inverseTokenAddress="$(exec npx oz create InverseToken -n ${network} --init --args $inverseTokenName,$inverseTokenName,18,$persistentStorageAddress,$ownerAddress --no-interactive --force)" + + + if [ "$isLeverageToken" == "true" ]; then + echo + echo "### Deploying CalculatorLeverage ###" + echo + compositionCalculatorContractName="CalculatorLeverage" + compositionCalculatorAddress="$(exec npx oz create CalculatorLeverage -n ${network} --init --args $persistentStorageAddress,$inverseTokenAddress --no-interactive --force)" + else + echo + echo "### Compiling and deploying CompositionCalculator ###" + echo + compositionCalculatorContractName="CompositionCalculator" + compositionCalculatorAddress="$(exec npx oz create CompositionCalculator -n ${network} --init --args $persistentStorageAddress,$inverseTokenAddress --no-interactive --force)" + fi + + + if [ "$isLeverageToken" == "true" ]; then + echo + echo "### Deploying TokenSwapLeverage ###" + echo + tsmContractName="TokenSwapLeverage" + tsmAddress="$(exec npx oz create TokenSwapLeverage -n ${network} --init --args $ownerAddress,$inverseTokenAddress,$cashPoolAddress,$persistentStorageAddress,$compositionCalculatorAddress --no-interactive --force)" + else + echo + echo "### Compiling and deploying TokenSwapManager ###" + echo + tsmContractName="TokenSwapManager" + tsmAddress="$(exec npx oz create TokenSwapManager -n ${network} --init --args $ownerAddress,$inverseTokenAddress,$cashPoolAddress,$persistentStorageAddress,$compositionCalculatorAddress --no-interactive --force)" + fi + + if [ "$isLeverageToken" == "true" ]; then + echo + echo "### Deploying TokenSwapLeverage ###" + echo + stablecoinContractName="USDT" + stablecoinContractAddress=${USDTAddress} + else + echo + echo "### Compiling and deploying TokenSwapManager ###" + echo + stablecoinContractName="USDC" + stablecoinContractAddress=${USDCAddress} + + fi + + + json='{"KYCVerifier":"'${kycVerifierAddress}'","'${persistenStorageContractName}'":"'${persistentStorageAddress}'","'${tsmContractName}'":"'${tsmAddress}'","'${stablecoinContractName}'":"'${stablecoinContractAddress}'","InverseToken":"'${inverseTokenAddress}'","CashPool":"'${cashPoolAddress}'","'${compositionCalculatorContractName}'":"'${compositionCalculatorAddress}'","OwnerMultiSig":"'${ownerAddress}'","ColdStorageMultiSig":"'${coldStorageAddress}'"}' + + if [ "$network" == "mainnet" ]; then + echo + echo "Saving contract addresses to S3 for ${network}" + node '../App/backend/scripts/addContractAddressesToS3.js' ${inverseTokenName}contractsAddresses $json $network + echo + elif [ "$network" == "rinkeby" ]; then + echo + echo "Saving contract addresses to S3 for ${network}" + node '../App/backend/scripts/addContractAddressesToS3.js' ${inverseTokenName}contractsAddresses $json $network + echo + else + echo + echo "### Saving contracts file locally for ${network} network ###" + printf $json > /tmp/${inverseTokenName}contractsAddresses.json + echo + fi +} + +function setIsDappMode() { + printf '{ "isDappMode":"%s" }\n' "$isDappMode" > /tmp/isDappMode.json +} + +function flattenContracts() { + echo + echo "### Flatten contracts into /flat ###" + echo + + npm run flatten +} + +function setUpContractsWithMintInverseTokens() { + product=$1 + network=$2 + isLeverageToken=$3 + # Now invoke the script that whitelists ownerAddress + echo + echo "Mint initial inverse Tokens, set bridgeAddress on KycVerifier contract, KYC whitelist ownerAddress and set Token SwapManager Address in PersistentStorage" + echo + node '../App/backend/scripts/setUpContractsWithMintInverseTokens.js' $product $network $isLeverageToken +} + +function resetContractsInDB() { + echo "### Setting contracts data into Jasper DB ###" + cd ../App/backend && \ + exec npm run db:reset + + cd ../../Contracts +} + +function createLendingRates() { + echo "### Trying to set Lending Rates for 30 days at 2% from Blockfi ###" + echo "### Make sure your backend is running ###" + echo "### ONLY CREATING FOR BTCSHORT ###" + + exec npm run script:createLendingRates + echo "done" +} + +function createFirstRebalanceInDBForInverseNonLeveraged() { + echo "### Trying to set Setup Contracts in DB with initial minted token and price ###" + echo "### Make sure your backend is running ###" + echo "### ONLY CREATING FOR BTCSHORT ###" + exec npm run script:createFirstRebalanceInDBForInverseNonLeveraged + echo "done" +} + +# Main +network=$1 +products=$2 # "BTC2L BTCSHORT", space separated string or "all" for all contracts + + +rm build/contracts/*.json + +npx oz push -n $network + # deploy all contracts +echo +echo "### Compiling and deploying contracts ###" + +if [ "$network" == "mainnet" ]; then + echo + ownerAddress="0x786ea62bC54613C3639A6275f88D3b86Ce3a0D45" + echo "OwnerMultisig for ${network} network is ${ownerAddress}" + echo + coldStorageAddress="0x555EAc576C533579834aE1a6AF6FC1d1925E7D71" + echo "ColdStorageMultisig for ${network} network is ${coldStorageAddress}" + echo + # source: https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + USDCAddress="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + echo "USDC for ${network} network is ${USDCAddress}" + echo + # source: https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7 + USDTAddress="0xdac17f958d2ee523a2206206994597c13d831ec7" + echo "USDC for ${network} network is ${USDCAddress}" + echo + kycVerifierAddress="0xE2E2fB43412818f9827B9e0943C031a518Ef451c" + echo "KycVerifier for ${network} network is ${kycVerifierAddress}" + echo + cashPoolAddress="0x98EABA2684b49BbDe9842cd48f3bAE8cAA31dac4" + echo "CashPool for ${network} network is ${cashPoolAddress}" +elif [ "$network" == "rinkeby" ]; then + echo + ownerAddress="0xa86223E26a74Eb292f23793BE18a3D1A3d5E1495" + echo "OwnerMultisig for ${network} network is ${ownerAddress}" + echo + coldStorageAddress="0x296DFFEf40DfE7b6C356419aF879eB5Cb3fd64E6" + echo "ColdStorageMultisig for ${network} network is ${coldStorageAddress}" + echo + USDCAddress="0x3033f7d702C602BaF4b2D340B8A3DD29f3d91DEf" + echo "USDC for ${network} network is ${USDCAddress}" + echo + USDTAddress="0x8175a4d188cF8317CC1Bf3505C2f709764076987" + echo "USDT for ${network} network is ${USDTAddress}" + echo + kycVerifierAddress="0x3B1110fc383ADC316133bc16fDb2137731ef5B88" + echo "KycVerifier for ${network} network is ${kycVerifierAddress}" + echo + cashPoolAddress="0xDDeF3814b8D6907f61C90A5cC90F38e33fC7Dc36" + echo "CashPool for ${network} network is ${cashPoolAddress}" +else + if [ "$network" != 'development' ]; then + echo + echo "Missing network. Must be development, rinkeby or mainnet " + exit + fi + echo + echo "Deploying contracts through Multisig owner (full dapp mode)" + isDappMode=true + signers="[0x2a78B494852fc74c25B70eC69a78D7332c7Dc87d,0x7e56019B6c9FDA6eE3B98B860E51195d42861355,0xC3902c4e6cbD0AaB4E88bdDBAD75Ac13c9287ABf]" + requiredSignature=2 + ownerAddress="$(exec npx oz create OwnerMultiSig -n ${network} --init --args $signers,$requiredSignature --no-interactive --force)" + coldStorageAddress="$(exec npx oz create OwnerMultiSig -n ${network} --init --args $signers,$requiredSignature --no-interactive --force)" + echo "Deployed ownerMultisig at ${ownerAddress} and coldStorage at ${coldStorageAddress}s" + echo + echo "### Compiling and deploying StableCoin (USDC) in ${network} network ###" + echo + USDCAddress="$(exec npx oz create USDC -n ${network} --init --args 'USDC','USDC','USD',6,$ownerAddress,$ownerAddress,$ownerAddress,$ownerAddress --no-interactive --force)" + echo + echo "### Deployed StableCoin (USDC) at ${USDCAddress} ###" + echo + + echo "### Compiling and deploying Tether USD (USDT) in ${network} network ###" + echo + USDTAddress="$(exec npx oz create USDT -n ${network} --init --args 'USDT','USDT','USD',6,$ownerAddress,$ownerAddress,$ownerAddress,$ownerAddress --no-interactive --force)" + echo + echo "### Deployed Tether USD (USDT) at ${USDTAddress} ###" + echo + + echo "### Compiling and deploying KYCVerifier ###" + echo + kycVerifierAddress="$(exec npx oz create KYCVerifier -n ${network} --init --args $ownerAddress --no-interactive --force)" + + echo + echo "### Compiling and deploying CashPool ###" + echo + zeroPercentInFraction=[0,1] + cashPoolAddress="$(exec npx oz create CashPool -n ${network} --init --args $ownerAddress,$kycVerifierAddress,$coldStorageAddress,$zeroPercentInFraction --no-interactive --force)" +fi + + +if [ "${products}" == "all" ]; then + echo "Inside products all" + products="BTCSHORT ETHSHORT BCHSHORT BTC2S BTC2L BTC3S BTC3L ETH2S ETH2L ETH3S ETH3L" +fi + +echo "Products ${products}" + +for product in $products +do + echo + echo + if [[ "BTC2S BTC2L BTC3S BTC3L ETH2S ETH2L ETH3S ETH3L" =~ $product ]]; then + isLeverageToken="true" + echo "Starting deployment for ${product} in ${network} network; IsLeverageToken: true" + else + isLeverageToken="" + echo "Starting deployment for ${product} in ${network} network; Product ${product}; IsLeverageToken: false" + fi + + deployContracts $network $product $isLeverageToken + setIsDappMode + flattenContracts + setUpContractsWithMintInverseTokens $product $network $isLeverageToken +done + + +if [ "$network" == "mainnet" ]; then + echo + echo "### Saving contract ABIs to S3 for ${network} ###" + node '../App/backend/scripts/addContractsAbiToS3.js' $network + echo +elif [ "$network" == "rinkeby" ]; then + echo + echo "### Saving contract ABIs to S3 for ${network} ###" + node '../App/backend/scripts/addContractsAbiToS3.js' $network + echo +fi + +if [ "$network" == "development" ]; then + if [ -d "../App/backend" ]; then + resetContractsInDB + createLendingRates + createFirstRebalanceInDBForInverseNonLeveraged + else + echo "Your setup seems to be missing the backend of Jasper. Not seeding DB data" + fi +else + echo "Not running db reset because it is in ${network} network. It is only used for development network" +fi diff --git a/scripts/flatten.sh b/scripts/flatten.sh index 61934c7..3416e9f 100755 --- a/scripts/flatten.sh +++ b/scripts/flatten.sh @@ -2,11 +2,16 @@ mkdir flats rm -rf flats/* -./node_modules/.bin/truffle-flattener contracts/CompositionCalculator.sol > flats/CompositionCalculator.sol -./node_modules/.bin/truffle-flattener contracts/TokenSwapManager.sol > flats/TokenSwapManager.sol -./node_modules/.bin/truffle-flattener contracts/PersistentStorage.sol > flats/PersistentStorage.sol -./node_modules/.bin/truffle-flattener contracts/Token/USDC.sol > flats/USDC.sol -./node_modules/.bin/truffle-flattener contracts/Token/InverseToken.sol > flats/InverseToken.sol -./node_modules/.bin/truffle-flattener contracts/CashPool.sol > flats/CashPool.sol -./node_modules/.bin/truffle-flattener contracts/KYCVerifier.sol > flats/KYCVerifier.sol +./node_modules/.bin/truffle-flattener contracts/short-tokens/CompositionCalculator.sol > flats/CompositionCalculator.sol +./node_modules/.bin/truffle-flattener contracts/short-tokens/TokenSwapManager.sol > flats/TokenSwapManager.sol +./node_modules/.bin/truffle-flattener contracts/leverage-tokens/TokenSwapLeverage.sol > flats/TokenSwapLeverage.sol +./node_modules/.bin/truffle-flattener contracts/leverage-tokens/CalculatorLeverage.sol > flats/CalculatorLeverage.sol +./node_modules/.bin/truffle-flattener contracts/leverage-tokens/StorageLeverage.sol > flats/StorageLeverage.sol +./node_modules/.bin/truffle-flattener contracts/short-tokens/PersistentStorage.sol > flats/PersistentStorage.sol +./node_modules/.bin/truffle-flattener contracts/shared/Token/USDC.sol > flats/USDC.sol +./node_modules/.bin/truffle-flattener contracts/shared/Token/USDT.sol > flats/USDT.sol +./node_modules/.bin/truffle-flattener contracts/short-tokens/InverseToken.sol > flats/InverseToken.sol +./node_modules/.bin/truffle-flattener contracts/short-tokens/CashPool.sol > flats/CashPool.sol +./node_modules/.bin/truffle-flattener contracts/short-tokens/KYCVerifier.sol > flats/KYCVerifier.sol +./node_modules/.bin/truffle-flattener contracts/shared/AdminMultiSig/OwnerMultiSig.sol > flats/OwnerMultiSig.sol diff --git a/test/CalculatorLeverage.test.js b/test/CalculatorLeverage.test.js new file mode 100644 index 0000000..d4de248 --- /dev/null +++ b/test/CalculatorLeverage.test.js @@ -0,0 +1,96 @@ +const { accounts, contract } = require("@openzeppelin/test-environment"); +const { expect } = require("chai"); +const BigNumber = require("bignumber.js"); + +const { time, expectRevert, ether, BN } = require("@openzeppelin/test-helpers"); + +const InverseToken = contract.fromArtifact("InverseToken"); +const CalculatorLeverage = contract.fromArtifact("CalculatorLeverage"); +const StorageLeverage = contract.fromArtifact("StorageLeverage"); + +const getNumberWithDecimal = num => + new BigNumber(num) + .div(new BigNumber("10").pow(new BigNumber("18"))) + .toFixed(18); +const getEth = num => + new BigNumber(num).times(new BigNumber("10").pow("18")).integerValue(); + +describe("CalculatorLeverage", function() { + const [owner] = accounts; + this.timeout(5000); + + beforeEach(async function() { + this.storage = await StorageLeverage.new({ from: owner }); + + const managementFee = ether("10.95"); + const minRebalanceAmount = ether("1"); + const lastMintingFee = ether("0.001"); + const balancePrecision = 12; + const minimumMintingFee = ether("0"); + 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", + "IT", + 18, + this.storage.address, + owner + ); + + this.contract = await CalculatorLeverage.new({ from: owner }); + await this.contract.initialize(this.storage.address, this.token.address); + }); + + describe("#getTokenAmountCreatedByCash", function() { + it("Correct Tokens Created From Creation Order", async function() { + const mintPrice = getEth(1000); + const tokensGiven = getEth(10); + const gasFee = getEth(1); + + // (TokensGiven - GasFee - MintingFee) / MintPrice + const expectedTokensCreated = "8973000000000000"; + + const tokenCreated = await this.contract.getTokensCreatedByCash( + mintPrice, + tokensGiven, + gasFee + ); + expect(tokenCreated.toString()).to.be.equal(expectedTokensCreated); + }); + }); + + describe("#getCashAmountCreatedByToken", function() { + it("Correct Stablecoin Recieved from Redemption Order", async function() { + const burnPrice = getEth(1000); + const timeElapsed = getEth(1); + const tokensGiven = getEth(10); + const gasFee = getEth(1); + + // (TokensGiven * BurnPrice) - GasFee - MintingFee - ManagementFee + const expectedTokensCreated = "9968878387462500000000"; + + const tokenCreated = await this.contract.getCashCreatedByTokens( + burnPrice, + timeElapsed, + tokensGiven, + gasFee + ); + expect(tokenCreated.toString()).to.be.equal(expectedTokensCreated); + }); + }); +}); diff --git a/test/CashPool.test.js b/test/CashPool.test.js index cb9670e..08e7a4a 100644 --- a/test/CashPool.test.js +++ b/test/CashPool.test.js @@ -48,7 +48,7 @@ describe("CashPool", function() { { from: owner } ); //0.2% await persistentStorage.setTokenSwapManager(tokenSwap, { from: owner }); - + // initialize token token = await ERC20WithMinting.new({ from: owner }); await token.initialize( @@ -69,10 +69,10 @@ describe("CashPool", function() { await this.contract.initialize( owner, kycVerifier.address, - persistentStorage.address, coldStorage, sixtyPercentInArrayFraction ); + await this.contract.addTokenManager(tokenSwap, {from: owner}) }); describe("#moveTokenToPool", function() { @@ -90,7 +90,7 @@ describe("CashPool", function() { amountOfTokensToPool, { from: user } ), - "caller is not the owner or token swap manager" + "caller is not the owner or an approved token swap manager" ); }); @@ -153,7 +153,7 @@ describe("CashPool", function() { amountOfTokensToPool, { from: anotherUser } ), - "caller is not the owner or token swap manager" + "caller is not the owner or an approved token swap manager" ); }); diff --git a/test/KYCVerifier.test.js b/test/KYCVerifier.test.js index b856fa5..27fe269 100644 --- a/test/KYCVerifier.test.js +++ b/test/KYCVerifier.test.js @@ -47,7 +47,9 @@ describe("KYCVerifier", function() { }); it("allows bridge to whitelisted address", async function() { - await this.contract.setWhitelistedAddress(notOwner, { from: bridge }); + await this.contract.setWhitelistedAddress(notOwner, { + from: bridge, + }); const isAddressAdded = await this.contract.whitelistedAddresses(notOwner); expect(isAddressAdded).to.be.true; @@ -55,13 +57,55 @@ describe("KYCVerifier", function() { }); it("adds whitelisted address", async function() { - await this.contract.setWhitelistedAddress(notOwner, { from: owner }); + await this.contract.setWhitelistedAddress(notOwner, { + from: owner, + }); const isAddressAdded = await this.contract.whitelistedAddresses(notOwner); expect(isAddressAdded).to.be.true; }); }); + describe("#batchWhitelistedAddress", function() { + it("does not allow a non owner to add multiple addresses", async function() { + await expectRevert( + this.contract.batchWhitelistedAddress([notOwner, owner], { + from: notOwner, + }), + "caller is not the owner or bridge" + ); + }); + + it("does not allow an empty address to be whitelisted", async function() { + await expectRevert( + this.contract.batchWhitelistedAddress( + [owner, "0x0000000000000000000000000000000000000000"], + { from: owner } + ), + "adddress must not be empty" + ); + }); + + it("allows bridge or owner to batch whitelisted addresses", async function() { + // as bridge + await this.contract.batchWhitelistedAddress([notOwner, owner], { + from: bridge, + }); + + let isAddressAdded = await this.contract.whitelistedAddresses(notOwner); + expect(isAddressAdded).to.be.true; + await this.contract.removeWhitelistedAddress(notOwner, { from: bridge }); + + // as owner + await this.contract.batchWhitelistedAddress([notOwner, owner, unlistedUser], { + from: owner, + }); + + isAddressAdded = await this.contract.whitelistedAddresses(unlistedUser); + expect(isAddressAdded).to.be.true; + }); + }); + describe("#removeWhitelistedAddress", function() { beforeEach(async function() { await this.contract.setWhitelistedAddress(notOwner, { from: owner }); diff --git a/test/Modifier.test.js b/test/Modifier.test.js index d589abd..627425e 100644 --- a/test/Modifier.test.js +++ b/test/Modifier.test.js @@ -74,7 +74,6 @@ describe("Modifier", function() { await this.cashPool.initialize( owner, this.kycVerifier.address, - this.storage.address, coldStorage, sixtyPercentInArrayFraction ); @@ -94,6 +93,7 @@ describe("Modifier", function() { owner, this.inverseToken.address, this.cashPool.address, + this.storage.address, this.compositionCalculator.address ); await this.storage.setTokenSwapManager(this.tokenSwapManager.address, { @@ -282,7 +282,7 @@ describe("Modifier", function() { this.cashPool.moveTokenfromPool(this.inverseToken.address, bridge, 5, { from: user }), - "caller is not the owner or token swap manager" + "caller is not the owner or an approved token swap manager" ); }); }); diff --git a/test/StorageLeverage.test.js b/test/StorageLeverage.test.js new file mode 100644 index 0000000..823e74c --- /dev/null +++ b/test/StorageLeverage.test.js @@ -0,0 +1,60 @@ +const { accounts, contract } = require("@openzeppelin/test-environment"); +const { expect } = require("chai"); +const BigNumber = require("bignumber.js"); + +const { + expectEvent, + expectRevert, + time, + ether, + BN, +} = require("@openzeppelin/test-helpers"); + +const StorageLeverage = contract.fromArtifact("StorageLeverage"); + +const getDateForBlockTime = async () => { + const latestBlockTime = await time.latest(); + const latestBlockTimestamp = latestBlockTime.toNumber() * 1000; + + const dateObj = new Date(latestBlockTimestamp); + const month = dateObj.getUTCMonth() + 1; //months from 1-12 + const day = dateObj.getUTCDate(); + const year = dateObj.getUTCFullYear(); + + return year * 10000 + month * 100 + day; +}; +describe("PersistentStorage", function() { + 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 StorageLeverage.new({ from: owner }); + await this.contract.initialize( + owner, + managementFee, + minRebalanceAmount, + balancePrecision, + lastMintingFee, + minimumMintingFee, + minimumTrade + ); + + await this.contract.setBridge(bridge, { from: owner }); + }); + + describe("#getMintingFee", function() { + it("gets minting fee", async function() { + const expectedTotalFee = ether("0.001"); + const cash = ether("49000"); + + const resultMintingFee = await this.contract.getMintingFee(cash, { + from: notListed, + }); + expect(resultMintingFee).to.be.bignumber.equal(expectedTotalFee); + }); + }); +}); diff --git a/test/TestStorageExample/Storage.test.js b/test/TestStorageExample/Storage.test.js deleted file mode 100644 index 7493e66..0000000 --- a/test/TestStorageExample/Storage.test.js +++ /dev/null @@ -1,94 +0,0 @@ -const { accounts, contract } = require("@openzeppelin/test-environment"); -const { expect } = require("chai"); - -const { expectEvent, expectRevert } = require("@openzeppelin/test-helpers"); - -const Storage = contract.fromArtifact("Storage"); - -describe("Storage", function() { - const [owner, notOwner] = accounts; - - beforeEach(async function() { - this.contract = await Storage.new({ from: owner }); - await this.contract.initialize(owner); - }); - - describe("#insertInstrument", function() { - it("does not allow a non owner to add an instrument", async function() { - await expectRevert( - this.contract.insertInstrument("ABTC", { from: notOwner }), - "Ownable: caller is not the owner" - ); - }); - - it("is not able to add the same instrument twice", async function() { - this.contract.insertInstrument("AETH", { from: owner }); - - await expectRevert( - this.contract.insertInstrument("AETH", { from: owner }), - "instrument already exists" - ); - }); - - it("adds instrument", async function() { - const instrumentCounter = ( - await this.contract.instrumentCounter() - ).toNumber(); - const incrementedCounter = instrumentCounter + 1; - - await this.contract.insertInstrument("ABTC", { from: owner }); - - const abtcIndex = ( - await this.contract.getInstrumentIndex("ABTC") - ).toNumber(); - - expect(abtcIndex).to.equal(incrementedCounter); - expect((await this.contract.instrumentCounter()).toNumber()).to.equal( - incrementedCounter - ); - }); - - it("emits AddInstrument", async function() { - const receipt = await this.contract.insertInstrument("HODL", { - from: owner - }); - - expectEvent(receipt, "AddInstrument", { addedInstrument: "HODL" }); - }); - }); - - describe("#addAllowedInstruments", function() { - beforeEach(async function() { - await this.contract.insertInstrument("ABTC", { from: owner }); - }); - - it("prohibits a non owner from adding allowed instruments to user", async function() { - await expectRevert( - this.contract.addAllowedInstruments(notOwner, "ABTC", { - from: notOwner - }), - "Ownable: caller is not the owner" - ); - }); - - it("does not allow an instrument which has not been added yet", async function() { - await expectRevert( - this.contract.addAllowedInstruments(notOwner, "AETH", { from: owner }), - "instrument does not exist" - ); - }); - - it("adds allowed instrument to user", async function() { - const abtcIndex = await this.contract.getInstrumentIndex("ABTC"); - - await this.contract.addAllowedInstruments(notOwner, "ABTC", { - from: owner - }); - const userAllowedInstruments = await this.contract.getUserAllowedInstruments( - notOwner - ); - - expect(userAllowedInstruments).to.deep.equal([abtcIndex]); - }); - }); -}); diff --git a/test/TestStorageExample/StorageVersionUpgradeExample.test.js b/test/TestStorageExample/StorageVersionUpgradeExample.test.js deleted file mode 100644 index c41910d..0000000 --- a/test/TestStorageExample/StorageVersionUpgradeExample.test.js +++ /dev/null @@ -1,43 +0,0 @@ -const { accounts, contract } = require("@openzeppelin/test-environment"); -const { expect } = require("chai"); - -const Storage_V0 = contract.fromArtifact("Storage_V0"); -const Storage_V1 = contract.fromArtifact("Storage_V1"); - -describe("Storage_V0", function() { - const [owner] = accounts; - - beforeEach(async function() { - this.contract = await Storage_V0.new({ from: owner }); - await this.contract.initialize(owner); - }); - - it("shows intrument counter Storage.sol", async function() { - await this.contract.insertInstrument("HODL", { from: owner }); - - const instrumentCounter = ( - await this.contract.instrumentCounter() - ); - const result = await this.contract.test_storage_v0(); - - expect(instrumentCounter).to.deep.equal(result); - }); -}); - -describe("Storage_V1", function() { - const [owner] = accounts; - - beforeEach(async function() { - this.contract = await Storage_V1.new({ from: owner }); - await this.contract.initialize(owner); - }); - - it("shows intrument counter Storage.solm plus 1", async function() { - const instrumentCounter = ( - await this.contract.instrumentCounter() - ).toNumber(); - const result = (await this.contract.test_storage_v1()).toNumber(); - - expect(instrumentCounter + 1).to.deep.equal(result); - }); -}); diff --git a/test/TokenSwapLeverage.test.js b/test/TokenSwapLeverage.test.js new file mode 100644 index 0000000..5a67c06 --- /dev/null +++ b/test/TokenSwapLeverage.test.js @@ -0,0 +1,417 @@ +const { accounts, contract } = require("@openzeppelin/test-environment"); +const { expect } = require("chai"); +const { + expectRevert, + expectEvent, + time, + ether +} = require("@openzeppelin/test-helpers"); + +const BigNumber = require("bignumber.js"); + +const ERC20WithMinting = contract.fromArtifact("InverseToken"); +const PersistentStorage = contract.fromArtifact("StorageLeverage"); +const KYCVerifier = contract.fromArtifact("KYCVerifier"); +const CashPool = contract.fromArtifact("CashPool"); +const TokenSwapManager = contract.fromArtifact("TokenSwapLeverage"); +const CompositionCalculator = contract.fromArtifact("CalculatorLeverage"); +const sixtyPercentInArrayFraction = [3, 5]; + +const getEth = num => + new BigNumber(num).times(new BigNumber("10").pow("18")).integerValue(); + +const getUsdc = num => + new BigNumber(num).times(new BigNumber("10").pow("6")).integerValue(); + +const normalizeUsdc = num => + new BigNumber(num).dividedBy(new BigNumber("10").pow("12")).integerValue(); + +describe("TokenSwapLeverage", async function() { + const [owner, user, bridge] = accounts; + const coldStorage = accounts[9]; + + beforeEach(async function() { + // Initialize Persistent Storage + this.storage = await PersistentStorage.new({ from: owner }); + const managementFee = ether("10.95"); + const minRebalanceAmount = ether("1"); + const lastMintingFee = ether("0.001"); + const balancePrecision = 12; + const minimumMintingFee = ether("0"); + 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( + "InverseToken", + "IT", + 18, + this.storage.address, + owner + ); + + this.stablecoin = await ERC20WithMinting.new({ from: owner }); + await this.stablecoin.initialize( + "Stablecoin", + "USDC", + 6, + this.storage.address, + owner + ); + + // Initialize KYC Verifier + this.kycVerifier = await KYCVerifier.new({ from: owner }); + await this.kycVerifier.initialize(owner); + + // Initialize Cash Pool + this.cashPool = await CashPool.new({ from: owner }); + await this.cashPool.initialize( + owner, + this.kycVerifier.address, + coldStorage, + sixtyPercentInArrayFraction + ); + + // Deploy CompositionCalculator + this.compositionCalculator = await CompositionCalculator.new({ + from: owner + }); + await this.compositionCalculator.initialize( + this.storage.address, + this.inverseToken.address + ); + + // Initialize TSM + this.tokenSwapManager = await TokenSwapManager.new({ from: owner }); + await this.tokenSwapManager.initialize( + owner, + this.inverseToken.address, + this.cashPool.address, + this.storage.address, + this.compositionCalculator.address + ); + await this.storage.setTokenSwapManager(this.tokenSwapManager.address, { + from: owner + }); + await this.cashPool.addTokenManager(this.tokenSwapManager.address, { + from: owner + }); + + await this.storage.setBridge(bridge, { from: owner }); + + this.timeout(11500000); + }); + + describe("#successfulCreateOrder", function() { + const totalTokenSupply = getEth(1); + const price = getEth(1000); + const tokensGiven = getEth(10); + const tokensRecieved = getEth(0.00997); + + beforeEach(async function() { + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); + await this.inverseToken.mintTokens(owner, totalTokenSupply, { + from: owner + }); + + this.receipt = await this.tokenSwapManager.createOrder( + true, // Order Type + tokensGiven, // Tokens Given + tokensRecieved, // Tokens Recieved + price, + 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: "CREATE", + whitelistedAddress: user, + tokensGiven: tokensGiven.toString(), + tokensRecieved: tokensRecieved.toString(), + stablecoin: this.stablecoin.address, + price: "1000000000000000000000" + }); + }); + + it("successfully mint tokens to user address", async function() { + const balance = await this.inverseToken.balanceOf(user); + expect(balance.toString()).to.be.equal(tokensRecieved.toString()); + }); + }); + + describe("#unsuccessfulCreateOrder", function() { + const totalTokenSupply = getEth(1); + const price = getEth(1000); + const tokensGiven = getEth(10); + const tokensRecieved = getEth(0.00000997); + + beforeEach(async function() { + await this.inverseToken.mintTokens(owner, totalTokenSupply, { + from: owner + }); + }); + + it("throws error when user is not whitelisted", async function() { + await expectRevert( + this.tokenSwapManager.createOrder( + true, + tokensGiven, + tokensRecieved, + price, + user, + this.stablecoin.address, // Stablecoin Address + 0, + { from: bridge } + ), + "only whitelisted address may place orders" + ); + }); + + it("throws error from trading engine: return user funds", async function() { + const usdcAmount = getUsdc(10); + const usdcAmountWithDecimals = getEth(10); + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); + await this.stablecoin.mintTokens(this.cashPool.address, usdcAmount, { + from: owner + }); + await this.tokenSwapManager.createOrder( + false, + usdcAmountWithDecimals, + usdcAmountWithDecimals, + price, + user, + this.stablecoin.address, // Stablecoin Address + 0, + { + from: bridge + } + ); + + const userReturnedBalance = await this.stablecoin.balanceOf(user); + expect(userReturnedBalance.toString()).to.be.equal(usdcAmount.toString()); + }); + }); + + describe("#successfulRedemptionOrders", function() { + const totalTokenSupply = getEth(100); + const stablecoinsToMint = getEth(10000); + + const price = getEth(1000); + const tokensGiven = getEth(10); + const tokensRecieved = getEth(9969.875375); + const timeElapsed = getEth(1); + + + beforeEach(async function() { + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); + + await this.inverseToken.mintTokens( + this.cashPool.address, + totalTokenSupply, + { + from: owner + } + ); + + await this.stablecoin.mintTokens( + this.cashPool.address, + stablecoinsToMint, + { + from: owner + } + ); + + this.receipt = await this.tokenSwapManager.redeemOrder( + true, // Order Type + tokensGiven, // Tokens Given + tokensRecieved, // Tokens Recieved + price, + user, // Whitelisted User + this.stablecoin.address, // Stablecoin Address + 0, + timeElapsed, + { from: bridge } // Sent From Bridge + ); + }); + + it("emits successful order event", async function() { + expectEvent(this.receipt, "SuccessfulOrder", { + orderType: "REDEEM", + whitelistedAddress: user, + tokensGiven: tokensGiven.toString(), + tokensRecieved: "9969875375000000000000", + stablecoin: this.stablecoin.address, + price: "1000000000000000000000" + }); + }); + + it("successfully redeem after creation order", async function() { + const tokensSent = getEth(10); + const tokensCreated = getEth(0.00997); + + await this.tokenSwapManager.createOrder( + true, + tokensSent, + tokensCreated, + price, + user, + this.stablecoin.address, // Stablecoin Address + 0, + { from: bridge } + ); + const receipt = await this.tokenSwapManager.redeemOrder( + true, + tokensGiven, + tokensRecieved, + price, + user, + this.stablecoin.address, // Stablecoin Address + 0, + timeElapsed, + { from: bridge } + ); + expectEvent(receipt, "SuccessfulOrder", { + orderType: "REDEEM", + whitelistedAddress: user, + tokensGiven: tokensGiven.toString(), + tokensRecieved: "9969875375000000000000", + stablecoin: this.stablecoin.address, + price: "1000000000000000000000" + }); + }); + + it("successfully burn tokens from cash pool", async function() { + const balance = await this.inverseToken.balanceOf(this.cashPool.address); + expect(balance.toString()).to.be.equal( + new BigNumber(totalTokenSupply).minus(tokensGiven).toString() + ); + }); + }); + + describe("#delayedRedemptionOrder", function() { + const totalTokenSupply = getEth(100); + const stablecoinsToMint = getUsdc(10000); + + const price = getEth(1000); + const tokensGiven = getEth(10); + const tokensRecieved = getEth(9969.875375); + const timeElapsed = getEth(1); + + + beforeEach(async function() { + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); + await this.inverseToken.mintTokens( + this.cashPool.address, + totalTokenSupply, + { + from: owner + } + ); + + this.receipt = await this.tokenSwapManager.redeemOrder( + true, // Order Type + tokensGiven, // Tokens Given + tokensRecieved, // Tokens Recieved + price, + user, // Whitelisted User + this.stablecoin.address, // Stablecoin Address + 0, + timeElapsed, + { from: bridge } // Sent From Bridge + ); + }); + + it("executes redemption without settlement", async function() { + expectEvent(this.receipt, "SuccessfulOrder", { + orderType: "REDEEM_NO_SETTLEMENT", + whitelistedAddress: user, + tokensGiven: tokensGiven.toString(), + tokensRecieved: "9969875375000000000000", + stablecoin: this.stablecoin.address, + price: "1000000000000000000000" + }); + }); + + it("settles redemption at a later date", async function() { + await this.stablecoin.mintTokens( + this.cashPool.address, + stablecoinsToMint, + { from: owner } + ); + 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, + 1000, + user, + this.stablecoin.address, + 0, + 1, + { + from: bridge + } + ), + "only whitelisted address may place orders" + ); + }); + + it("throws error from trading engine: return user funds", async function() { + await this.kycVerifier.setWhitelistedAddress(user, { from: owner }); + await this.inverseToken.mintTokens(this.cashPool.address, 10, { + from: owner + }); + await this.tokenSwapManager.redeemOrder( + false, + 10, + 10, + 1000, + user, + this.stablecoin.address, + 0, + 1, + { + from: bridge + } + ); + + const userReturnedBalance = await this.inverseToken.balanceOf(user); + expect(userReturnedBalance.toNumber()).to.be.equal(10); + }); + }); +}); diff --git a/test/TokenSwapManager.test.js b/test/TokenSwapManager.test.js index 923c56f..bc77cd6 100644 --- a/test/TokenSwapManager.test.js +++ b/test/TokenSwapManager.test.js @@ -82,7 +82,6 @@ describe("TokenSwapManager", function() { await this.cashPool.initialize( owner, this.kycVerifier.address, - this.storage.address, coldStorage, sixtyPercentInArrayFraction ); @@ -102,11 +101,14 @@ describe("TokenSwapManager", function() { owner, this.inverseToken.address, this.cashPool.address, + this.storage.address, this.compositionCalculator.address ); await this.storage.setTokenSwapManager(this.tokenSwapManager.address, { from: owner }); + await this.cashPool.addTokenManager(this.tokenSwapManager.address, {from: owner}) + await this.storage.setBridge(bridge, { from: owner }); this.timeout(115000); diff --git a/test/upgradeToCashPoolv2.test.js b/test/upgradeToCashPoolv2.test.js new file mode 100644 index 0000000..91aedfb --- /dev/null +++ b/test/upgradeToCashPoolv2.test.js @@ -0,0 +1,357 @@ +// Skip this test - for reference material only +const Web3 = require("web3"); +const { provider } = require("@openzeppelin/test-environment"); +const { + Contracts, + ZWeb3, + SimpleProject, +} = require("@openzeppelin/upgrades"); +const { ether, expectRevert } = require("@openzeppelin/test-helpers"); + +const { expect } = require("chai"); + +describe.skip("CashPoolv2", async function() { + this.timeout(50000); + + beforeEach(async function() { + // Create web3 provider + const web3 = new Web3(provider); + ZWeb3.initialize(web3.currentProvider); + + [ + this.proxyAdmin, + this.owner, + this.user, + this.anotherUser, + this.tokenSwap, + this.coldStorage, + this.tokenManager1, + this.tokenManager2, + this.user, + ] = await web3.eth.getAccounts(); + + const CashPool = Contracts.getFromLocal("CashPool"); + const CashPoolv2 = Contracts.getFromLocal("CashPoolv2"); + const ERC20WithMinting = Contracts.getFromLocal("InverseToken"); + const PersistentStorage = Contracts.getFromLocal("PersistentStorage"); + const KYCVerifier = Contracts.getFromLocal("KYCVerifier"); + const sixtyPercentInArrayFraction = [3, 5]; + + this.amountOfTokensToPool = 5; + this.userResultTokenBalance = "15"; // 10 from minting plus 5 from amountOfTokensToPool when transfer happens in tests + + // Create an OpenZeppelin project + const project = new SimpleProject( + "BTCShort", + {}, + { from: this.proxyAdmin } + ); + + // Initialize Params for Persistent Storage + const managementFee = ether("7").toString(); + const minRebalanceAmount = ether("1").toString(); + const lastMintingFee = ether("0.001").toString(); + const balancePrecision = 12; + const minimumMintingFee = ether("5").toString(); + const minimumTrade = ether("50").toString(); + + // Deploy an instance of Persistent Storage + this.persistentStorage = await project.createProxy(PersistentStorage, { + packageName: null, + contractName: "PersistentStorage", + initMethod: "initialize", + initArgs: [ + this.owner, + managementFee, + minRebalanceAmount, + balancePrecision, + lastMintingFee, + minimumMintingFee, + minimumTrade, + ], + }); + + // Set 0.2% + await this.persistentStorage.methods + .addMintingFeeBracket( + ether("100000").toString(), + ether("0.002").toString() + ) + .send({ from: this.owner }); + await this.persistentStorage.methods + .setTokenSwapManager(this.tokenSwap) + .send({ from: this.owner }); + + // Deploy an instance of the Token + this.token = await project.createProxy(ERC20WithMinting, { + packageName: null, + contractName: "InverseToken", + initMethod: "initialize", + initArgs: [ + "Test Token", + "TT", + 18, + this.persistentStorage.address, + this.owner, + ], + }); + // Mint Tokens + await this.token.methods + .mintTokens(this.user, 10) + .send({ from: this.owner }); + + // Deploy an instance of Kyc Verfier + this.kycVerifier = await project.createProxy(KYCVerifier, { + packageName: null, + contractName: "KYCVerifier", + initMethod: "initialize", + initArgs: [this.owner], + }); + await this.kycVerifier.methods + .setWhitelistedAddress(this.user) + .send({ from: this.owner }); + + // Deploy Cash Pool + this.cashPool = await project.createProxy(CashPool, { + packageName: null, + contractName: "CashPool", + initMethod: "initialize", + initArgs: [ + this.owner, + this.kycVerifier.address, + this.persistentStorage.address, + this.coldStorage, + sixtyPercentInArrayFraction, + ], + }); + + // Upgrade to Cash Pool v2 + this.cashPoolv2 = await project.upgradeProxy( + this.cashPool.options.address, + CashPoolv2, + { + packageName: null, + contractName: "CashPoolv2", + initMethod: null, + initArgs: null, + } + ); + // Add Token Manager 2 to the Mapping + await this.cashPoolv2.methods + .addTokenManager(this.tokenManager2) + .send({ from: this.owner }); + }); + + it("maintains the owner address after upgrade", async function() { + const implementationOwner = await this.cashPoolv2.methods + .owner() + .call({ from: this.owner }); + expect(this.owner).to.be.equal(implementationOwner); + }); + + it("maintains the proxy address after upgrade", async function() { + const upgradeAddress = this.cashPoolv2.options.address; + expect(this.cashPool.options.address).to.be.equal(upgradeAddress); + }); + + it("adds token manager address to mapping", async function() { + await this.cashPoolv2.methods + .addTokenManager(this.tokenManager1) + .send({ from: this.owner }); + const isAdded = await this.cashPoolv2.methods + .isTokenManager(this.tokenManager1) + .call({ from: this.owner }); + expect(isAdded).to.be.equal(true); + }); + + it("removes token manager address from mapping", async function() { + await this.cashPoolv2.methods + .removeTokenManager(this.tokenManager2) + .send({ from: this.owner }); + const isRemoved = await this.cashPoolv2.methods + .isTokenManager(this.tokenManager2) + .call({ from: this.owner }); + expect(isRemoved).to.be.equal(false); + }); + + it("moves tokens to pool", async function() { + await this.token.methods + .approve(this.cashPoolv2.options.address, this.amountOfTokensToPool) + .send({ from: this.user }); + + await this.cashPoolv2.methods + .moveTokenToPoolv2( + this.token.options.address, + this.user, + this.amountOfTokensToPool + ) + .send({ from: this.owner, gas: 6500000 }); + + const poolFundsInTokens = await this.token.methods + .balanceOf(this.cashPoolv2.options.address) + .call({ from: this.owner }); + const coldStorageAddress = await this.cashPoolv2.methods + .coldStorage() + .call({ from: this.owner }); + const coldStorageTokens = await this.token.methods + .balanceOf(coldStorageAddress) + .call({ from: this.owner }); + + expect(poolFundsInTokens).to.be.bignumber.equal( + (this.amountOfTokensToPool - coldStorageTokens).toString() + ); + }); + + it("moves tokens to pool from approved token manager", async function() { + await this.token.methods + .approve(this.cashPoolv2.options.address, this.amountOfTokensToPool) + .send({ from: this.user }); + + await this.cashPoolv2.methods + .moveTokenToPoolv2( + this.token.options.address, + this.user, + this.amountOfTokensToPool + ) + .send({ from: this.tokenManager2, gas: 6500000 }); + + const poolFundsInTokens = await this.token.methods + .balanceOf(this.cashPoolv2.options.address) + .call({ from: this.owner }); + const coldStorageAddress = await this.cashPoolv2.methods + .coldStorage() + .call({ from: this.owner }); + const coldStorageTokens = await this.token.methods + .balanceOf(coldStorageAddress) + .call({ from: this.owner }); + + expect(poolFundsInTokens).to.be.bignumber.equal( + (this.amountOfTokensToPool - coldStorageTokens).toString() + ); + }); + + it("rejects movement of tokens to pool from non-approved token manager", async function() { + await this.token.methods + .approve(this.cashPoolv2.options.address, this.amountOfTokensToPool) + .send({ from: this.user }); + + await expectRevert( + this.cashPoolv2.methods + .moveTokenToPoolv2( + this.token.options.address, + this.user, + this.amountOfTokensToPool + ) + .send({ from: this.tokenManager1, gas: 6500000 }), + "caller is not the owner or an approved token swap manager" + ); + }); + + it("moves tokens from pool (by owner)", async function() { + await this.token.methods + .mintTokens(this.cashPoolv2.options.address, 10) + .send({ from: this.owner }); + + await this.cashPoolv2.methods + .moveTokenfromPoolv2( + this.token.options.address, + this.user, + this.amountOfTokensToPool + ) + .send({ from: this.owner, gas: 6500000 }); + + const userBalance = await this.token.methods + .balanceOf(this.user) + .call({ from: this.owner }); + expect(userBalance.toString()).to.be.bignumber.equal( + this.userResultTokenBalance + ); + }); + + it("moves tokens from pool (by approved token manager)", async function() { + await this.token.methods + .mintTokens(this.cashPoolv2.options.address, 10) + .send({ from: this.owner }); + + await this.cashPoolv2.methods + .moveTokenfromPoolv2( + this.token.options.address, + this.user, + this.amountOfTokensToPool + ) + .send({ from: this.tokenManager2, gas: 6500000 }); + + const userBalance = await this.token.methods + .balanceOf(this.user) + .call({ from: this.owner }); + expect(userBalance.toString()).to.be.bignumber.equal( + this.userResultTokenBalance + ); + }); + + it("rejects movement of tokens from pool (by non-approved token manager)", async function() { + await this.token.methods + .mintTokens(this.cashPoolv2.options.address, 10) + .send({ from: this.owner }); + + await expectRevert( + this.cashPoolv2.methods + .moveTokenfromPoolv2( + this.token.options.address, + this.user, + this.amountOfTokensToPool + ) + .send({ from: this.tokenManager1, gas: 6500000 }), + "caller is not the owner or an approved token swap manager" + ); + }); + + it("moves tokens to pool (v1 code)", async function() { + await this.token.methods + .approve(this.cashPoolv2.options.address, this.amountOfTokensToPool) + .send({ from: this.user }); + + await this.cashPoolv2.methods + .moveTokenToPool( + this.token.options.address, + this.user, + this.amountOfTokensToPool + ) + .send({ from: this.tokenSwap, gas: 6500000 }); + + const poolFundsInTokens = await this.token.methods + .balanceOf(this.cashPoolv2.options.address) + .call({ from: this.owner }); + const coldStorageAddress = await this.cashPoolv2.methods + .coldStorage() + .call({ from: this.owner }); + const coldStorageTokens = await this.token.methods + .balanceOf(coldStorageAddress) + .call({ from: this.owner }); + + expect(poolFundsInTokens).to.be.bignumber.equal( + (this.amountOfTokensToPool - coldStorageTokens).toString() + ); + }); + + it("moves tokens from pool (v1 code)", async function() { + await this.token.methods + .mintTokens(this.cashPoolv2.options.address, 10) + .send({ from: this.owner }); + + await this.cashPoolv2.methods + .moveTokenfromPool( + this.token.options.address, + this.user, + this.amountOfTokensToPool + ) + .send({ from: this.tokenSwap, gas: 6500000 }); + + const userBalance = await this.token.methods + .balanceOf(this.user) + .call({ from: this.owner }); + expect(userBalance.toString()).to.be.bignumber.equal( + this.userResultTokenBalance + ); + }); +});