diff --git a/contracts/common/UsingRegistry.sol b/contracts/common/UsingRegistry.sol
new file mode 100644
index 0000000..1ff264b
--- /dev/null
+++ b/contracts/common/UsingRegistry.sol
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity ^0.5.13;
+
+import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
+import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
+
+import "celo/contracts/common/interfaces/IFreezer.sol";
+import "celo/contracts/common/interfaces/IRegistry.sol";
+
+import "../interfaces/IExchange.sol";
+import "../interfaces/IStableTokenV2.sol";
+import "../interfaces/IReserve.sol";
+import "../interfaces/ISortedOracles.sol";
+
+contract UsingRegistry is Ownable {
+ event RegistrySet(address indexed registryAddress);
+
+ // solhint-disable state-visibility
+ bytes32 constant ACCOUNTS_REGISTRY_ID = keccak256(abi.encodePacked("Accounts"));
+ bytes32 constant ATTESTATIONS_REGISTRY_ID = keccak256(abi.encodePacked("Attestations"));
+ bytes32 constant DOWNTIME_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("DowntimeSlasher"));
+ bytes32 constant DOUBLE_SIGNING_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("DoubleSigningSlasher"));
+ bytes32 constant ELECTION_REGISTRY_ID = keccak256(abi.encodePacked("Election"));
+ bytes32 constant EXCHANGE_REGISTRY_ID = keccak256(abi.encodePacked("Exchange"));
+ bytes32 constant FEE_CURRENCY_WHITELIST_REGISTRY_ID = keccak256(abi.encodePacked("FeeCurrencyWhitelist"));
+ bytes32 constant FREEZER_REGISTRY_ID = keccak256(abi.encodePacked("Freezer"));
+ bytes32 constant GOLD_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("GoldToken"));
+ bytes32 constant GOVERNANCE_REGISTRY_ID = keccak256(abi.encodePacked("Governance"));
+ bytes32 constant GOVERNANCE_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("GovernanceSlasher"));
+ bytes32 constant LOCKED_GOLD_REGISTRY_ID = keccak256(abi.encodePacked("LockedGold"));
+ bytes32 constant RESERVE_REGISTRY_ID = keccak256(abi.encodePacked("Reserve"));
+ bytes32 constant RANDOM_REGISTRY_ID = keccak256(abi.encodePacked("Random"));
+ bytes32 constant SORTED_ORACLES_REGISTRY_ID = keccak256(abi.encodePacked("SortedOracles"));
+ bytes32 constant STABLE_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("StableToken"));
+ bytes32 constant VALIDATORS_REGISTRY_ID = keccak256(abi.encodePacked("Validators"));
+ // solhint-enable state-visibility
+
+ IRegistry public registry;
+
+ modifier onlyRegisteredContract(bytes32 identifierHash) {
+ require(registry.getAddressForOrDie(identifierHash) == msg.sender, "only registered contract");
+ _;
+ }
+
+ modifier onlyRegisteredContracts(bytes32[] memory identifierHashes) {
+ require(registry.isOneOf(identifierHashes, msg.sender), "only registered contracts");
+ _;
+ }
+
+ /**
+ * @notice Updates the address pointing to a Registry contract.
+ * @param registryAddress The address of a registry contract for routing to other contracts.
+ */
+ function setRegistry(address registryAddress) public onlyOwner {
+ require(registryAddress != address(0), "Cannot register the null address");
+ registry = IRegistry(registryAddress);
+ emit RegistrySet(registryAddress);
+ }
+
+ function getExchange() internal view returns (IExchange) {
+ return IExchange(registry.getAddressForOrDie(EXCHANGE_REGISTRY_ID));
+ }
+
+ function getFreezer() internal view returns (IFreezer) {
+ return IFreezer(registry.getAddressForOrDie(FREEZER_REGISTRY_ID));
+ }
+
+ function getGoldToken() internal view returns (IERC20) {
+ return IERC20(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID));
+ }
+
+ function getReserve() internal view returns (IReserve) {
+ return IReserve(registry.getAddressForOrDie(RESERVE_REGISTRY_ID));
+ }
+
+ function getSortedOracles() internal view returns (ISortedOracles) {
+ return ISortedOracles(registry.getAddressForOrDie(SORTED_ORACLES_REGISTRY_ID));
+ }
+
+ function getStableToken() internal view returns (IStableTokenV2) {
+ return IStableTokenV2(registry.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID));
+ }
+}
diff --git a/contracts/interfaces/IBreakerBox.sol b/contracts/interfaces/IBreakerBox.sol
new file mode 100644
index 0000000..f74b7d0
--- /dev/null
+++ b/contracts/interfaces/IBreakerBox.sol
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity >0.5.13 <0.9;
+pragma experimental ABIEncoderV2;
+
+import { ISortedOracles } from "./ISortedOracles.sol";
+
+/**
+ * @title Breaker Box Interface
+ * @notice Defines the basic interface for the Breaker Box
+ */
+interface IBreakerBox {
+ /**
+ * @dev Used to keep track of the status of a breaker for a specific rate feed.
+ *
+ * - TradingMode: Represents the trading mode the breaker is in for a rate feed.
+ * This uses a bitmask approach, meaning each bit represents a
+ * different trading mode. The final trading mode of the rate feed
+ * is obtained by applying a logical OR operation to the TradingMode
+ * of all breakers associated with that rate feed. This allows multiple
+ * breakers to contribute to the final trading mode simultaneously.
+ * Possible values:
+ * 0: bidirectional trading.
+ * 1: inflow only.
+ * 2: outflow only.
+ * 3: trading halted.
+ *
+ * - LastUpdatedTime: Records the last time the breaker status was updated. This is
+ * used to manage cooldown periods before the breaker can be reset.
+ *
+ * - Enabled: Indicates whether the breaker is enabled for the associated rate feed.
+ */
+ struct BreakerStatus {
+ uint8 tradingMode;
+ uint64 lastUpdatedTime;
+ bool enabled;
+ }
+
+ /**
+ * @notice Emitted when a new breaker is added to the breaker box.
+ * @param breaker The address of the breaker.
+ */
+ event BreakerAdded(address indexed breaker);
+
+ /**
+ * @notice Emitted when a breaker is removed from the breaker box.
+ * @param breaker The address of the breaker.
+ */
+ event BreakerRemoved(address indexed breaker);
+
+ /**
+ * @notice Emitted when a breaker is tripped by a rate feed.
+ * @param breaker The address of the breaker.
+ * @param rateFeedID The address of the rate feed.
+ */
+ event BreakerTripped(address indexed breaker, address indexed rateFeedID);
+
+ /**
+ * @notice Emitted when a new rate feed is added to the breaker box.
+ * @param rateFeedID The address of the rate feed.
+ */
+ event RateFeedAdded(address indexed rateFeedID);
+
+ /**
+ * @notice Emitted when dependencies for a rate feed are set.
+ * @param rateFeedID The address of the rate feed.
+ * @param dependencies The addresses of the dependendent rate feeds.
+ */
+ event RateFeedDependenciesSet(address indexed rateFeedID, address[] indexed dependencies);
+
+ /**
+ * @notice Emitted when a rate feed is removed from the breaker box.
+ * @param rateFeedID The address of the rate feed.
+ */
+ event RateFeedRemoved(address indexed rateFeedID);
+
+ /**
+ * @notice Emitted when the trading mode for a rate feed is updated
+ * @param rateFeedID The address of the rate feed.
+ * @param tradingMode The new trading mode.
+ */
+ event TradingModeUpdated(address indexed rateFeedID, uint256 tradingMode);
+
+ /**
+ * @notice Emitted after a reset attempt is successful.
+ * @param rateFeedID The address of the rate feed.
+ * @param breaker The address of the breaker.
+ */
+ event ResetSuccessful(address indexed rateFeedID, address indexed breaker);
+
+ /**
+ * @notice Emitted after a reset attempt fails when the
+ * rate feed fails the breakers reset criteria.
+ * @param rateFeedID The address of the rate feed.
+ * @param breaker The address of the breaker.
+ */
+ event ResetAttemptCriteriaFail(address indexed rateFeedID, address indexed breaker);
+
+ /**
+ * @notice Emitted after a reset attempt fails when cooldown time has not elapsed.
+ * @param rateFeedID The address of the rate feed.
+ * @param breaker The address of the breaker.
+ */
+ event ResetAttemptNotCool(address indexed rateFeedID, address indexed breaker);
+
+ /**
+ * @notice Emitted when the sortedOracles address is updated.
+ * @param newSortedOracles The address of the new sortedOracles.
+ */
+ event SortedOraclesUpdated(address indexed newSortedOracles);
+
+ /**
+ * @notice Emitted when the breaker is enabled or disabled for a rate feed.
+ * @param breaker The address of the breaker.
+ * @param rateFeedID The address of the rate feed.
+ * @param status Indicating the status.
+ */
+ event BreakerStatusUpdated(address breaker, address rateFeedID, bool status);
+
+ /**
+ * @notice Retrives an array of all breaker addresses.
+ */
+ function getBreakers() external view returns (address[] memory);
+
+ /**
+ * @notice Checks if a breaker with the specified address has been added to the breaker box.
+ * @param breaker The address of the breaker to check;
+ * @return A bool indicating whether or not the breaker has been added.
+ */
+ function isBreaker(address breaker) external view returns (bool);
+
+ /**
+ * @notice Checks breakers for the rateFeedID and sets correct trading mode
+ * if any breakers are tripped or need to be reset.
+ * @param rateFeedID The address of the rate feed to run checks for.
+ */
+ function checkAndSetBreakers(address rateFeedID) external;
+
+ /**
+ * @notice Gets the trading mode for the specified rateFeedID.
+ * @param rateFeedID The address of the rate feed to retrieve the trading mode for.
+ */
+ function getRateFeedTradingMode(address rateFeedID) external view returns (uint8 tradingMode);
+
+ /**
+ * @notice Adds a breaker to the end of the list of breakers & the breakerTradingMode mapping.
+ * @param breaker The address of the breaker to be added.
+ * @param tradingMode The trading mode of the breaker to be added.
+ */
+ function addBreaker(address breaker, uint8 tradingMode) external;
+
+ /**
+ * @notice Removes the specified breaker from the list of breakers
+ * and resets breakerTradingMode mapping + BreakerStatus.
+ * @param breaker The address of the breaker to be removed.
+ */
+ function removeBreaker(address breaker) external;
+
+ /**
+ * @notice Enables or disables a breaker for the specified rate feed.
+ * @param breakerAddress The address of the breaker.
+ * @param rateFeedID The address of the rateFeed to be toggled.
+ * @param enable Boolean indicating whether the breaker should be
+ * enabled or disabled for the given rateFeed.
+ */
+ function toggleBreaker(address breakerAddress, address rateFeedID, bool enable) external;
+
+ /**
+ * @notice Adds a rateFeedID to the mapping of monitored rateFeedIDs.
+ * @param rateFeedID The address of the rateFeed to be added.
+ */
+ function addRateFeed(address rateFeedID) external;
+
+ /**
+ * @notice Adds the specified rateFeedIDs to the mapping of monitored rateFeedIDs.
+ * @param newRateFeedIDs The array of rateFeed addresses to be added.
+ */
+ function addRateFeeds(address[] calldata newRateFeedIDs) external;
+
+ /**
+ * @notice Sets dependent rate feeds for a given rate feed.
+ * @param rateFeedID The address of the rate feed.
+ * @param dependencies The array of dependent rate feeds.
+ */
+ function setRateFeedDependencies(address rateFeedID, address[] calldata dependencies) external;
+
+ /**
+ * @notice Removes a rateFeed from the mapping of monitored rateFeeds
+ * and resets all the BreakerStatus entries for that rateFeed.
+ * @param rateFeedID The address of the rateFeed to be removed.
+ */
+ function removeRateFeed(address rateFeedID) external;
+
+ /**
+ * @notice Sets the trading mode for the specified rateFeed.
+ * @param rateFeedID The address of the rateFeed.
+ * @param tradingMode The trading mode that should be set.
+ */
+ function setRateFeedTradingMode(address rateFeedID, uint8 tradingMode) external;
+
+ /**
+ * @notice Returns addresses of rateFeedIDs that have been added.
+ */
+ function getRateFeeds() external view returns (address[] memory);
+
+ /**
+ * @notice Checks if a breaker is enabled for a specific rate feed.
+ * @param breaker The address of the breaker we're checking for.
+ * @param rateFeedID The address of the rateFeed.
+ */
+ function isBreakerEnabled(address breaker, address rateFeedID) external view returns (bool);
+
+ /**
+ * @notice Sets the address of the sortedOracles contract.
+ * @param _sortedOracles The new address of the sorted oracles contract.
+ */
+ function setSortedOracles(ISortedOracles _sortedOracles) external;
+
+ /// @notice Public state variable getters:
+ function breakerTradingMode(address) external view returns (uint8);
+
+ function sortedOracles() external view returns (address);
+
+ function rateFeedStatus(address) external view returns (bool);
+
+ function owner() external view returns (address);
+
+ function rateFeedBreakerStatus(address, address) external view returns (BreakerStatus memory);
+
+ function rateFeedDependencies(address, uint256) external view returns (address);
+
+ function rateFeedTradingMode(address) external view returns (uint8);
+}
diff --git a/contracts/interfaces/IExchange.sol b/contracts/interfaces/IExchange.sol
new file mode 100644
index 0000000..6e18611
--- /dev/null
+++ b/contracts/interfaces/IExchange.sol
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity ^0.5.13;
+
+interface IExchange {
+ function buy(uint256, uint256, bool) external returns (uint256);
+
+ function sell(uint256, uint256, bool) external returns (uint256);
+
+ function exchange(uint256, uint256, bool) external returns (uint256);
+
+ function setUpdateFrequency(uint256) external;
+
+ function getBuyTokenAmount(uint256, bool) external view returns (uint256);
+
+ function getSellTokenAmount(uint256, bool) external view returns (uint256);
+
+ function getBuyAndSellBuckets(bool) external view returns (uint256, uint256);
+}
diff --git a/contracts/interfaces/ISortedOracles.sol b/contracts/interfaces/ISortedOracles.sol
new file mode 100644
index 0000000..2a57852
--- /dev/null
+++ b/contracts/interfaces/ISortedOracles.sol
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity >0.5.13 <0.9;
+
+import { IBreakerBox } from "./IBreakerBox.sol";
+
+interface ISortedOracles {
+ enum MedianRelation {
+ Undefined,
+ Lesser,
+ Greater,
+ Equal
+ }
+
+ function addOracle(address, address) external;
+
+ function removeOracle(address, address, uint256) external;
+
+ function report(address, uint256, address, address) external;
+
+ function removeExpiredReports(address, uint256) external;
+
+ function isOldestReportExpired(address token) external view returns (bool, address);
+
+ function numRates(address) external view returns (uint256);
+
+ function medianRate(address) external view returns (uint256, uint256);
+
+ function numTimestamps(address) external view returns (uint256);
+
+ function medianTimestamp(address) external view returns (uint256);
+
+ function getOracles(address) external view returns (address[] memory);
+
+ function getRates(address token) external view returns (address[] memory, uint256[] memory, MedianRelation[] memory);
+
+ function getTimestamps(
+ address token
+ ) external view returns (address[] memory, uint256[] memory, MedianRelation[] memory);
+
+ function initialize(uint256) external;
+
+ function setBreakerBox(IBreakerBox) external;
+
+ function getTokenReportExpirySeconds(address token) external view returns (uint256);
+
+ function oracles(address, uint256) external view returns (address);
+
+ function breakerBox() external view returns (IBreakerBox);
+}
diff --git a/contracts/swap/Reserve.sol b/contracts/swap/Reserve.sol
index 918e938..397801a 100644
--- a/contracts/swap/Reserve.sol
+++ b/contracts/swap/Reserve.sol
@@ -19,734 +19,937 @@ import "contracts/interfaces/ISortedOracles.sol";
* @title Ensures price stability of StableTokens with respect to their pegs
*/
// solhint-disable-next-line max-states-count
-contract Reserve is IReserve, ICeloVersionedContract, Ownable, Initializable, UsingRegistry, ReentrancyGuard {
- using SafeMath for uint256;
- using FixidityLib for FixidityLib.Fraction;
- using Address for address payable; // prettier-ignore
- using SafeERC20 for IERC20;
-
- struct TobinTaxCache {
- uint128 numerator;
- uint128 timestamp;
- }
-
- mapping(address => bool) public isToken;
- address[] private _tokens;
- TobinTaxCache public tobinTaxCache;
- uint256 public tobinTaxStalenessThreshold;
- uint256 public tobinTax;
- uint256 public tobinTaxReserveRatio;
- mapping(address => bool) public isSpender;
-
- mapping(address => bool) public isOtherReserveAddress;
- address[] public otherReserveAddresses;
-
- bytes32[] public assetAllocationSymbols;
- mapping(bytes32 => uint256) public assetAllocationWeights;
-
- uint256 public lastSpendingDay;
- uint256 public spendingLimit;
- FixidityLib.Fraction private spendingRatio;
-
- uint256 public frozenReserveGoldStartBalance;
- uint256 public frozenReserveGoldStartDay;
- uint256 public frozenReserveGoldDays;
-
- mapping(address => bool) public isExchangeSpender;
- address[] public exchangeSpenderAddresses;
- mapping(address => FixidityLib.Fraction) private collateralAssetDailySpendingRatio;
- mapping(address => uint256) public collateralAssetLastSpendingDay;
- address[] public collateralAssets;
- mapping(address => bool) public isCollateralAsset;
- mapping(address => uint256) public collateralAssetSpendingLimit;
-
- event TobinTaxStalenessThresholdSet(uint256 value);
- event DailySpendingRatioSet(uint256 ratio);
- event TokenAdded(address indexed token);
- event TokenRemoved(address indexed token, uint256 index);
- event SpenderAdded(address indexed spender);
- event SpenderRemoved(address indexed spender);
- event OtherReserveAddressAdded(address indexed otherReserveAddress);
- event OtherReserveAddressRemoved(address indexed otherReserveAddress, uint256 index);
- event AssetAllocationSet(bytes32[] symbols, uint256[] weights);
- event ReserveGoldTransferred(address indexed spender, address indexed to, uint256 value);
- event TobinTaxSet(uint256 value);
- event TobinTaxReserveRatioSet(uint256 value);
- event ExchangeSpenderAdded(address indexed exchangeSpender);
- event ExchangeSpenderRemoved(address indexed exchangeSpender);
- event DailySpendingRatioForCollateralAssetSet(address collateralAsset, uint256 collateralAssetDailySpendingRatios);
- event ReserveCollateralAssetsTransferred(address indexed spender, address indexed to, uint256 value, address token);
- event CollateralAssetRemoved(address collateralAsset);
- event CollateralAssetAdded(address collateralAsset);
-
- /**
- * @notice Sets initialized == true on implementation contracts
- * @param test Set to true to skip implementation initialization
- */
- constructor(bool test) public Initializable(test) {}
-
- modifier isStableToken(address token) {
- require(isToken[token], "token addr was never registered");
- _;
- }
-
- /**
- * @notice Returns the storage, major, minor, and patch version of the contract.
- * @return Storage version of the contract.
- * @return Major version of the contract.
- * @return Minor version of the contract.
- * @return Patch version of the contract.
- */
- function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) {
- return (2, 1, 0, 0);
- }
-
- function() external payable {} // solhint-disable no-empty-blocks
-
- /**
- * @notice Used in place of the constructor to allow the contract to be upgradable via proxy.
- * @param registryAddress The address of the registry core smart contract.
- * @param _tobinTaxStalenessThreshold The initial number of seconds to cache tobin tax value for.
- * @param _spendingRatioForCelo The relative daily spending limit for the reserve spender.
- * @param _frozenGold The balance of reserve gold that is frozen.
- * @param _frozenDays The number of days during which the frozen gold thaws.
- * @param _assetAllocationSymbols The symbols of the reserve assets.
- * @param _assetAllocationWeights The reserve asset weights.
- * @param _tobinTax The tobin tax value as a fixidity fraction.
- * @param _tobinTaxReserveRatio When to turn on the tobin tax, as a fixidity fraction.
- * @param _collateralAssets The relative daily spending limit
- * of an ERC20 collateral asset for the reserve spender.
- * @param _collateralAssetDailySpendingRatios The address of an ERC20 collateral asset
- */
- function initialize(
- address registryAddress,
- uint256 _tobinTaxStalenessThreshold,
- uint256 _spendingRatioForCelo,
- uint256 _frozenGold,
- uint256 _frozenDays,
- bytes32[] calldata _assetAllocationSymbols,
- uint256[] calldata _assetAllocationWeights,
- uint256 _tobinTax,
- uint256 _tobinTaxReserveRatio,
- address[] calldata _collateralAssets,
- uint256[] calldata _collateralAssetDailySpendingRatios
- ) external initializer {
- _transferOwnership(msg.sender);
- setRegistry(registryAddress);
- setTobinTaxStalenessThreshold(_tobinTaxStalenessThreshold);
- setDailySpendingRatio(_spendingRatioForCelo);
- setFrozenGold(_frozenGold, _frozenDays);
- setAssetAllocations(_assetAllocationSymbols, _assetAllocationWeights);
- setTobinTax(_tobinTax);
- setTobinTaxReserveRatio(_tobinTaxReserveRatio);
- for (uint256 i = 0; i < _collateralAssets.length; i++) {
- addCollateralAsset(_collateralAssets[i]);
- }
- setDailySpendingRatioForCollateralAssets(_collateralAssets, _collateralAssetDailySpendingRatios);
- }
-
- /**
- * @notice Sets the number of seconds to cache the tobin tax value for.
- * @param value The number of seconds to cache the tobin tax value for.
- */
- function setTobinTaxStalenessThreshold(uint256 value) public onlyOwner {
- require(value > 0, "value was zero");
- tobinTaxStalenessThreshold = value;
- emit TobinTaxStalenessThresholdSet(value);
- }
-
- /**
- * @notice Sets the tobin tax.
- * @param value The tobin tax.
- */
- function setTobinTax(uint256 value) public onlyOwner {
- require(FixidityLib.wrap(value).lte(FixidityLib.fixed1()), "tobin tax cannot be larger than 1");
- tobinTax = value;
- emit TobinTaxSet(value);
- }
-
- /**
- * @notice Sets the reserve ratio at which the tobin tax sets in.
- * @param value The reserve ratio at which the tobin tax sets in.
- */
- function setTobinTaxReserveRatio(uint256 value) public onlyOwner {
- tobinTaxReserveRatio = value;
- emit TobinTaxReserveRatioSet(value);
- }
-
- /**
- * @notice Set the ratio of reserve that is spendable per day.
- * @param ratio Spending ratio as unwrapped Fraction.
- */
- function setDailySpendingRatio(uint256 ratio) public onlyOwner {
- spendingRatio = FixidityLib.wrap(ratio);
- require(spendingRatio.lte(FixidityLib.fixed1()), "spending ratio cannot be larger than 1");
- emit DailySpendingRatioSet(ratio);
- }
-
- /**
- * @notice Set the ratio of reserve for a given collateral asset
- * that is spendable per day.
- * @param _collateralAssets Collection of the addresses of collateral assets
- * we're setting a limit for.
- * @param collateralAssetDailySpendingRatios Collection of the relative daily spending limits
- * of collateral assets.
- */
- function setDailySpendingRatioForCollateralAssets(
- address[] memory _collateralAssets,
- uint256[] memory collateralAssetDailySpendingRatios
- ) public onlyOwner {
- require(
- _collateralAssets.length == collateralAssetDailySpendingRatios.length,
- "token addresses and spending ratio lengths have to be the same"
+contract Reserve is
+ IReserve,
+ ICeloVersionedContract,
+ Ownable,
+ Initializable,
+ UsingRegistry,
+ ReentrancyGuard
+{
+ using SafeMath for uint256;
+ using FixidityLib for FixidityLib.Fraction;
+ using Address for address payable; // prettier-ignore
+ using SafeERC20 for IERC20;
+
+ struct TobinTaxCache {
+ uint128 numerator;
+ uint128 timestamp;
+ }
+
+ mapping(address => bool) public isToken;
+ address[] private _tokens;
+ TobinTaxCache public tobinTaxCache;
+ uint256 public tobinTaxStalenessThreshold;
+ uint256 public tobinTax;
+ uint256 public tobinTaxReserveRatio;
+ mapping(address => bool) public isSpender;
+
+ mapping(address => bool) public isOtherReserveAddress;
+ address[] public otherReserveAddresses;
+
+ bytes32[] public assetAllocationSymbols;
+ mapping(bytes32 => uint256) public assetAllocationWeights;
+
+ uint256 public lastSpendingDay;
+ uint256 public spendingLimit;
+ FixidityLib.Fraction private spendingRatio;
+
+ uint256 public frozenReserveGoldStartBalance;
+ uint256 public frozenReserveGoldStartDay;
+ uint256 public frozenReserveGoldDays;
+
+ mapping(address => bool) public isExchangeSpender;
+ address[] public exchangeSpenderAddresses;
+ mapping(address => FixidityLib.Fraction)
+ private collateralAssetDailySpendingRatio;
+ mapping(address => uint256) public collateralAssetLastSpendingDay;
+ address[] public collateralAssets;
+ mapping(address => bool) public isCollateralAsset;
+ mapping(address => uint256) public collateralAssetSpendingLimit;
+
+ event TobinTaxStalenessThresholdSet(uint256 value);
+ event DailySpendingRatioSet(uint256 ratio);
+ event TokenAdded(address indexed token);
+ event TokenRemoved(address indexed token, uint256 index);
+ event SpenderAdded(address indexed spender);
+ event SpenderRemoved(address indexed spender);
+ event OtherReserveAddressAdded(address indexed otherReserveAddress);
+ event OtherReserveAddressRemoved(
+ address indexed otherReserveAddress,
+ uint256 index
+ );
+ event AssetAllocationSet(bytes32[] symbols, uint256[] weights);
+ event ReserveGoldTransferred(
+ address indexed spender,
+ address indexed to,
+ uint256 value
+ );
+ event TobinTaxSet(uint256 value);
+ event TobinTaxReserveRatioSet(uint256 value);
+ event ExchangeSpenderAdded(address indexed exchangeSpender);
+ event ExchangeSpenderRemoved(address indexed exchangeSpender);
+ event DailySpendingRatioForCollateralAssetSet(
+ address collateralAsset,
+ uint256 collateralAssetDailySpendingRatios
+ );
+ event ReserveCollateralAssetsTransferred(
+ address indexed spender,
+ address indexed to,
+ uint256 value,
+ address token
);
- for (uint256 i = 0; i < _collateralAssets.length; i++) {
- if (_collateralAssets[i] != address(0) && collateralAssetDailySpendingRatios[i] != 0) {
+ event CollateralAssetRemoved(address collateralAsset);
+ event CollateralAssetAdded(address collateralAsset);
+
+ /**
+ * @notice Sets initialized == true on implementation contracts
+ * @param test Set to true to skip implementation initialization
+ */
+ constructor(bool test) public Initializable(test) {}
+
+ modifier isStableToken(address token) {
+ require(isToken[token], "token addr was never registered");
+ _;
+ }
+
+ /**
+ * @notice Returns the storage, major, minor, and patch version of the contract.
+ * @return Storage version of the contract.
+ * @return Major version of the contract.
+ * @return Minor version of the contract.
+ * @return Patch version of the contract.
+ */
+ function getVersionNumber()
+ external
+ pure
+ returns (uint256, uint256, uint256, uint256)
+ {
+ return (2, 1, 0, 0);
+ }
+
+ function() external payable {} // solhint-disable no-empty-blocks
+
+ /**
+ * @notice Used in place of the constructor to allow the contract to be upgradable via proxy.
+ * @param registryAddress The address of the registry core smart contract.
+ * @param _tobinTaxStalenessThreshold The initial number of seconds to cache tobin tax value for.
+ * @param _spendingRatioForCelo The relative daily spending limit for the reserve spender.
+ * @param _frozenGold The balance of reserve gold that is frozen.
+ * @param _frozenDays The number of days during which the frozen gold thaws.
+ * @param _assetAllocationSymbols The symbols of the reserve assets.
+ * @param _assetAllocationWeights The reserve asset weights.
+ * @param _tobinTax The tobin tax value as a fixidity fraction.
+ * @param _tobinTaxReserveRatio When to turn on the tobin tax, as a fixidity fraction.
+ * @param _collateralAssets The relative daily spending limit
+ * of an ERC20 collateral asset for the reserve spender.
+ * @param _collateralAssetDailySpendingRatios The address of an ERC20 collateral asset
+ */
+ function initialize(
+ address registryAddress,
+ uint256 _tobinTaxStalenessThreshold,
+ uint256 _spendingRatioForCelo,
+ uint256 _frozenGold,
+ uint256 _frozenDays,
+ bytes32[] calldata _assetAllocationSymbols,
+ uint256[] calldata _assetAllocationWeights,
+ uint256 _tobinTax,
+ uint256 _tobinTaxReserveRatio,
+ address[] calldata _collateralAssets,
+ uint256[] calldata _collateralAssetDailySpendingRatios
+ ) external initializer {
+ _transferOwnership(msg.sender);
+ setRegistry(registryAddress);
+ setTobinTaxStalenessThreshold(_tobinTaxStalenessThreshold);
+ setDailySpendingRatio(_spendingRatioForCelo);
+ setFrozenGold(_frozenGold, _frozenDays);
+ setAssetAllocations(_assetAllocationSymbols, _assetAllocationWeights);
+ setTobinTax(_tobinTax);
+ setTobinTaxReserveRatio(_tobinTaxReserveRatio);
+ for (uint256 i = 0; i < _collateralAssets.length; i++) {
+ addCollateralAsset(_collateralAssets[i]);
+ }
+ setDailySpendingRatioForCollateralAssets(
+ _collateralAssets,
+ _collateralAssetDailySpendingRatios
+ );
+ }
+
+ /**
+ * @notice Sets the number of seconds to cache the tobin tax value for.
+ * @param value The number of seconds to cache the tobin tax value for.
+ */
+ function setTobinTaxStalenessThreshold(uint256 value) public onlyOwner {
+ require(value > 0, "value was zero");
+ tobinTaxStalenessThreshold = value;
+ emit TobinTaxStalenessThresholdSet(value);
+ }
+
+ /**
+ * @notice Sets the tobin tax.
+ * @param value The tobin tax.
+ */
+ function setTobinTax(uint256 value) public onlyOwner {
require(
- checkIsCollateralAsset(_collateralAssets[i]),
- "the address specified is not a reserve collateral asset"
+ FixidityLib.wrap(value).lte(FixidityLib.fixed1()),
+ "tobin tax cannot be larger than 1"
);
+ tobinTax = value;
+ emit TobinTaxSet(value);
+ }
+
+ /**
+ * @notice Sets the reserve ratio at which the tobin tax sets in.
+ * @param value The reserve ratio at which the tobin tax sets in.
+ */
+ function setTobinTaxReserveRatio(uint256 value) public onlyOwner {
+ tobinTaxReserveRatio = value;
+ emit TobinTaxReserveRatioSet(value);
+ }
+
+ /**
+ * @notice Set the ratio of reserve that is spendable per day.
+ * @param ratio Spending ratio as unwrapped Fraction.
+ */
+ function setDailySpendingRatio(uint256 ratio) public onlyOwner {
+ spendingRatio = FixidityLib.wrap(ratio);
require(
- FixidityLib.wrap(collateralAssetDailySpendingRatios[i]).lte(FixidityLib.fixed1()),
- "spending ratio cannot be larger than 1"
+ spendingRatio.lte(FixidityLib.fixed1()),
+ "spending ratio cannot be larger than 1"
);
- collateralAssetDailySpendingRatio[_collateralAssets[i]] = FixidityLib.wrap(
- collateralAssetDailySpendingRatios[i]
+ emit DailySpendingRatioSet(ratio);
+ }
+
+ /**
+ * @notice Set the ratio of reserve for a given collateral asset
+ * that is spendable per day.
+ * @param _collateralAssets Collection of the addresses of collateral assets
+ * we're setting a limit for.
+ * @param collateralAssetDailySpendingRatios Collection of the relative daily spending limits
+ * of collateral assets.
+ */
+ function setDailySpendingRatioForCollateralAssets(
+ address[] memory _collateralAssets,
+ uint256[] memory collateralAssetDailySpendingRatios
+ ) public onlyOwner {
+ require(
+ _collateralAssets.length ==
+ collateralAssetDailySpendingRatios.length,
+ "token addresses and spending ratio lengths have to be the same"
);
- emit DailySpendingRatioForCollateralAssetSet(_collateralAssets[i], collateralAssetDailySpendingRatios[i]);
- }
- }
- }
-
- /**
- * @notice Get daily spending ratio.
- * @return Spending ratio as unwrapped Fraction.
- */
- function getDailySpendingRatio() public view returns (uint256) {
- return spendingRatio.unwrap();
- }
-
- /**
- * @notice Get daily spending ratio of a collateral asset.
- * @param collateralAsset The address of a collateral asset we're getting a spending ratio for.
- * @return Daily spending ratio for the collateral asset as unwrapped Fraction.
- */
- function getDailySpendingRatioForCollateralAsset(address collateralAsset) public view returns (uint256) {
- return collateralAssetDailySpendingRatio[collateralAsset].unwrap();
- }
-
- /**
- * @notice Sets the balance of reserve gold frozen from transfer.
- * @param frozenGold The amount of CELO frozen.
- * @param frozenDays The number of days the frozen CELO thaws over.
- */
- function setFrozenGold(uint256 frozenGold, uint256 frozenDays) public onlyOwner {
- require(frozenGold <= address(this).balance, "Cannot freeze more than balance");
- frozenReserveGoldStartBalance = frozenGold;
- // slither-disable-start events-maths
- frozenReserveGoldStartDay = now / 1 days;
- frozenReserveGoldDays = frozenDays;
- // slither-disable-end events-maths
- }
-
- /**
- * @notice Sets target allocations for CELO and a diversified basket of non-Celo assets.
- * @param symbols The symbol of each asset in the Reserve portfolio.
- * @param weights The weight for the corresponding asset as unwrapped Fixidity.Fraction.
- */
- function setAssetAllocations(bytes32[] memory symbols, uint256[] memory weights) public onlyOwner {
- require(symbols.length == weights.length, "Array length mismatch");
- FixidityLib.Fraction memory sum = FixidityLib.wrap(0);
- for (uint256 i = 0; i < weights.length; i = i.add(1)) {
- sum = sum.add(FixidityLib.wrap(weights[i]));
- }
- require(sum.equals(FixidityLib.fixed1()), "Sum of asset allocation must be 1");
- // slither-disable-next-line cache-array-length
- for (uint256 i = 0; i < assetAllocationSymbols.length; i = i.add(1)) {
- delete assetAllocationWeights[assetAllocationSymbols[i]];
- }
- assetAllocationSymbols = symbols;
- for (uint256 i = 0; i < symbols.length; i = i.add(1)) {
- require(assetAllocationWeights[symbols[i]] == 0, "Cannot set weight twice");
- assetAllocationWeights[symbols[i]] = weights[i];
- }
- // NOTE: The CELO asset launched as "Celo Gold" (cGLD), but was renamed to
- // just CELO by the community.
- // TODO: Change "cGLD" to "CELO" in this file, after ensuring that any
- // off chain tools working with asset allocation weights are aware of this
- // change.
- require(assetAllocationWeights["cGLD"] != 0, "Must set cGLD asset weight");
- emit AssetAllocationSet(symbols, weights);
- }
-
- /**
- * @notice Add a token that the reserve will stabilize.
- * @param token The address of the token being stabilized.
- * @return Returns true if the transaction succeeds.
- */
- function addToken(address token) external onlyOwner returns (bool) {
- require(!isToken[token], "token addr already registered");
- isToken[token] = true;
- _tokens.push(token);
- emit TokenAdded(token);
- return true;
- }
-
- /**
- * @notice Remove a token that the reserve will no longer stabilize.
- * @param token The address of the token no longer being stabilized.
- * @param index The index of the token in _tokens.
- * @return Returns true if the transaction succeeds.
- */
- function removeToken(address token, uint256 index) external onlyOwner isStableToken(token) returns (bool) {
- require(index < _tokens.length && _tokens[index] == token, "index into tokens list not mapped to token");
- isToken[token] = false;
- address lastItem = _tokens[_tokens.length.sub(1)];
- _tokens[index] = lastItem;
- _tokens.length = _tokens.length.sub(1);
- emit TokenRemoved(token, index);
- return true;
- }
-
- /**
- * @notice Add a reserve address whose balance shall be included in the reserve ratio.
- * @param reserveAddress The reserve address to add.
- * @return Returns true if the transaction succeeds.
- */
- function addOtherReserveAddress(address reserveAddress) external onlyOwner returns (bool) {
- require(!isOtherReserveAddress[reserveAddress], "reserve addr already added");
- isOtherReserveAddress[reserveAddress] = true;
- otherReserveAddresses.push(reserveAddress);
- emit OtherReserveAddressAdded(reserveAddress);
- return true;
- }
-
- /**
- * @notice Remove reserve address whose balance shall no longer be included in the reserve ratio.
- * @param reserveAddress The reserve address to remove.
- * @param index The index of the reserve address in otherReserveAddresses.
- * @return Returns true if the transaction succeeds.
- */
- function removeOtherReserveAddress(address reserveAddress, uint256 index) external onlyOwner returns (bool) {
- require(isOtherReserveAddress[reserveAddress], "reserve addr was never added");
- require(
- index < otherReserveAddresses.length && otherReserveAddresses[index] == reserveAddress,
- "index into reserve list not mapped to address"
- );
- isOtherReserveAddress[reserveAddress] = false;
- address lastItem = otherReserveAddresses[otherReserveAddresses.length.sub(1)];
- otherReserveAddresses[index] = lastItem;
- otherReserveAddresses.length = otherReserveAddresses.length.sub(1);
- emit OtherReserveAddressRemoved(reserveAddress, index);
- return true;
- }
-
- /**
- * @notice Gives an address permission to spend Reserve funds.
- * @param spender The address that is allowed to spend Reserve funds.
- */
- function addSpender(address spender) external onlyOwner {
- require(address(0) != spender, "Spender can't be null");
- isSpender[spender] = true;
- emit SpenderAdded(spender);
- }
-
- /**
- * @notice Takes away an address's permission to spend Reserve funds.
- * @param spender The address that is to be no longer allowed to spend Reserve funds.
- */
- function removeSpender(address spender) external onlyOwner {
- require(isSpender[spender], "Spender hasn't been added");
- isSpender[spender] = false;
- emit SpenderRemoved(spender);
- }
-
- /**
- * @notice Checks if an address is able to spend as an exchange.
- * @dev isExchangeSpender was introduced after cUSD, so the cUSD Exchange is not included in it.
- * If cUSD's Exchange were to be added to isExchangeSpender, the check with the
- * registry could be removed.
- * @param spender The address to be checked.
- */
- modifier isAllowedToSpendExchange(address spender) {
- require(
- isExchangeSpender[spender] || (registry.getAddressForOrDie(EXCHANGE_REGISTRY_ID) == spender),
- "Address not allowed to spend"
- );
- _;
- }
-
- /**
- * @notice Gives an address permission to spend Reserve without limit.
- * @param spender The address that is allowed to spend Reserve funds.
- */
- function addExchangeSpender(address spender) external onlyOwner {
- require(address(0) != spender, "Spender can't be null");
- require(!isExchangeSpender[spender], "Address is already Exchange Spender");
- isExchangeSpender[spender] = true;
- exchangeSpenderAddresses.push(spender);
- emit ExchangeSpenderAdded(spender);
- }
-
- /**
- * @notice Takes away an address's permission to spend Reserve funds without limits.
- * @param spender The address that is to be no longer allowed to spend Reserve funds.
- * @param index The index in exchangeSpenderAddresses of spender.
- */
- function removeExchangeSpender(address spender, uint256 index) external onlyOwner {
- isExchangeSpender[spender] = false;
- uint256 numAddresses = exchangeSpenderAddresses.length;
- require(index < numAddresses, "Index is invalid");
- require(spender == exchangeSpenderAddresses[index], "Index does not match spender");
- uint256 newNumAddresses = numAddresses.sub(1);
-
- if (index != newNumAddresses) {
- exchangeSpenderAddresses[index] = exchangeSpenderAddresses[newNumAddresses];
- }
-
- exchangeSpenderAddresses[newNumAddresses] = address(0x0);
- exchangeSpenderAddresses.length = newNumAddresses;
- emit ExchangeSpenderRemoved(spender);
- }
-
- /**
- * @notice Returns addresses of exchanges permitted to spend Reserve funds.
- * Because exchangeSpenderAddresses was introduced after cUSD, cUSD's exchange
- * is not included in this list.
- * @return An array of addresses permitted to spend Reserve funds.
- */
- function getExchangeSpenders() external view returns (address[] memory) {
- return exchangeSpenderAddresses;
- }
-
- /**
- * @notice Transfer gold to a whitelisted address subject to reserve spending limits.
- * @param to The address that will receive the gold.
- * @param value The amount of gold to transfer.
- * @return Returns true if the transaction succeeds.
- */
- function transferGold(address payable to, uint256 value) external returns (bool) {
- require(isSpender[msg.sender], "sender not allowed to transfer Reserve funds");
- require(isOtherReserveAddress[to], "can only transfer to other reserve address");
- uint256 currentDay = now / 1 days;
- if (currentDay > lastSpendingDay) {
- uint256 balance = getUnfrozenReserveGoldBalance();
- lastSpendingDay = currentDay;
- spendingLimit = spendingRatio.multiply(FixidityLib.newFixed(balance)).fromFixed();
- }
- require(spendingLimit >= value, "Exceeding spending limit");
- spendingLimit = spendingLimit.sub(value);
- return _transferGold(to, value);
- }
-
- /**
- * @notice Transfer collateral asset subject to reserve spending limits to the trader,
- * if the limit is set, othersise the limit is 100%.
- * @param collateralAsset The token address you're transferring.
- * @param to The address that will receive the funds.
- * @param value The amount of collateral assets to transfer.
- * @return Returns true if the transaction succeeds.
- */
- function transferCollateralAsset(address collateralAsset, address payable to, uint256 value) external returns (bool) {
- require(isSpender[msg.sender], "sender not allowed to transfer Reserve funds");
- require(isOtherReserveAddress[to], "can only transfer to other reserve address");
- require(
- getDailySpendingRatioForCollateralAsset(collateralAsset) > 0,
- "this asset has no spending ratio, therefore can't be transferred"
- );
- uint256 currentDay = now / 1 days;
- if (currentDay > collateralAssetLastSpendingDay[collateralAsset]) {
- uint256 balance = getReserveAddressesCollateralAssetBalance(collateralAsset);
- collateralAssetLastSpendingDay[collateralAsset] = currentDay;
- collateralAssetSpendingLimit[collateralAsset] = collateralAssetDailySpendingRatio[collateralAsset]
- .multiply(FixidityLib.newFixed(balance))
- .fromFixed();
- }
- uint256 spendingLimitForThisAsset = collateralAssetSpendingLimit[collateralAsset];
- require(spendingLimitForThisAsset >= value, "Exceeding spending limit");
-
- collateralAssetSpendingLimit[collateralAsset] = spendingLimitForThisAsset.sub(value);
- return _transferCollateralAsset(collateralAsset, to, value);
- }
-
- /**
- * @notice Transfer collateral asset to any address.
- * @param collateralAsset The token address you're transferring.
- * @param to The address that will receive the funds.
- * @param value The amount of collateral assets to transfer.
- * @return Returns true if the transaction succeeds.
- */
- function _transferCollateralAsset(
- address collateralAsset,
- address payable to,
- uint256 value
- ) internal returns (bool) {
- require(value <= getReserveAddressesCollateralAssetBalance(collateralAsset), "Exceeding the amount reserve holds");
- // slither-disable-next-line reentrancy-events
- IERC20(collateralAsset).safeTransfer(to, value);
- emit ReserveCollateralAssetsTransferred(msg.sender, to, value, collateralAsset);
- return true;
- }
-
- /**
- * @notice Transfer collateral asset to any address.
- * @dev Transfers are not subject to a daily spending limit.
- * @param collateralAsset The address of collateral asset being transferred.
- * @param to The address that will receive the collateral asset.
- * @param value The amount of collateral asset to transfer.
- * @return Returns true if the transaction succeeds.
- */
- function transferExchangeCollateralAsset(
- address collateralAsset,
- address payable to,
- uint256 value
- ) external returns (bool) {
- require(isExchangeSpender[msg.sender], "Address not allowed to spend");
- return _transferCollateralAsset(collateralAsset, to, value);
- }
-
- /**
- * @notice Transfer unfrozen gold to any address.
- * @param to The address that will receive the gold.
- * @param value The amount of gold to transfer.
- * @return Returns true if the transaction succeeds.
- */
- function _transferGold(address payable to, uint256 value) internal returns (bool) {
- require(value <= getUnfrozenBalance(), "Exceeding unfrozen reserves");
- // slither-disable-next-line reentrancy-events
- to.sendValue(value);
- emit ReserveGoldTransferred(msg.sender, to, value);
- return true;
- }
-
- /**
- * @notice Transfer unfrozen gold to any address, used for one side of CP-DOTO.
- * @dev Transfers are not subject to a daily spending limit.
- * @param to The address that will receive the gold.
- * @param value The amount of gold to transfer.
- * @return Returns true if the transaction succeeds.
- */
- function transferExchangeGold(
- address payable to,
- uint256 value
- ) external isAllowedToSpendExchange(msg.sender) returns (bool) {
- return _transferGold(to, value);
- }
-
- /**
- * @notice Returns the tobin tax, recomputing it if it's stale.
- * @return The numerator - tobin tax amount as a fraction.
- * @return The denominator - tobin tax amount as a fraction.
- */
- function getOrComputeTobinTax() external nonReentrant returns (uint256, uint256) {
- // solhint-disable-next-line not-rely-on-time
- if (now.sub(tobinTaxCache.timestamp) > tobinTaxStalenessThreshold) {
- tobinTaxCache.numerator = uint128(computeTobinTax().unwrap());
- tobinTaxCache.timestamp = uint128(now); // solhint-disable-line not-rely-on-time
- }
- return (uint256(tobinTaxCache.numerator), FixidityLib.fixed1().unwrap());
- }
-
- /**
- * @notice Returns the list of stabilized token addresses.
- * @return An array of addresses of stabilized tokens.
- */
- function getTokens() external view returns (address[] memory) {
- return _tokens;
- }
-
- /**
- * @notice Returns the list other addresses included in the reserve total.
- * @return An array of other addresses included in the reserve total.
- */
- function getOtherReserveAddresses() external view returns (address[] memory) {
- return otherReserveAddresses;
- }
-
- /**
- * @notice Returns a list of token symbols that have been allocated.
- * @return An array of token symbols that have been allocated.
- */
- function getAssetAllocationSymbols() external view returns (bytes32[] memory) {
- return assetAllocationSymbols;
- }
-
- /**
- * @notice Returns a list of weights used for the allocation of reserve assets.
- * @return An array of a list of weights used for the allocation of reserve assets.
- */
- function getAssetAllocationWeights() external view returns (uint256[] memory) {
- uint256[] memory weights = new uint256[](assetAllocationSymbols.length);
- // slither-disable-next-line cache-array-length
- for (uint256 i = 0; i < assetAllocationSymbols.length; i = i.add(1)) {
- weights[i] = assetAllocationWeights[assetAllocationSymbols[i]];
- }
- return weights;
- }
-
- /**
- * @notice Returns the amount of unfrozen CELO in the reserve.
- * @return The total unfrozen CELO in the reserve.
- */
- function getUnfrozenBalance() public view returns (uint256) {
- uint256 balance = address(this).balance;
- uint256 frozenReserveGold = getFrozenReserveGoldBalance();
- return balance > frozenReserveGold ? balance.sub(frozenReserveGold) : 0;
- }
-
- /**
- * @notice Returns the amount of CELO included in the reserve.
- * @return The CELO amount included in the reserve.
- */
- function getReserveGoldBalance() public view returns (uint256) {
- return address(this).balance.add(getOtherReserveAddressesGoldBalance());
- }
-
- /**
- * @notice Returns the amount of CELO included in other reserve addresses.
- * @return The CELO amount included in other reserve addresses.
- */
- function getOtherReserveAddressesGoldBalance() public view returns (uint256) {
- uint256 reserveGoldBalance = 0;
- // slither-disable-next-line cache-array-length
- for (uint256 i = 0; i < otherReserveAddresses.length; i = i.add(1)) {
- reserveGoldBalance = reserveGoldBalance.add(otherReserveAddresses[i].balance);
- }
- return reserveGoldBalance;
- }
-
- /**
- * @notice Returns the amount of unfrozen CELO included in the reserve.
- * @return The unfrozen CELO amount included in the reserve.
- */
- function getUnfrozenReserveGoldBalance() public view returns (uint256) {
- return getUnfrozenBalance().add(getOtherReserveAddressesGoldBalance());
- }
-
- /**
- * @notice Returns the amount of particular collateral asset
- * in reserve including other reserve addresses.
- * @param collateralAsset the asset we're checking a balance of
- * @return The balance of particular collateral asset.
- */
- function getReserveAddressesCollateralAssetBalance(address collateralAsset) public view returns (uint256) {
- require(checkIsCollateralAsset(collateralAsset), "specified address is not a collateral asset");
- uint256 reserveCollateralAssetBalance = 0;
- // slither-disable-next-line cache-array-length
- for (uint256 i = 0; i < otherReserveAddresses.length; i++) {
- // slither-disable-next-line calls-loop
- reserveCollateralAssetBalance = reserveCollateralAssetBalance.add(
- IERC20(collateralAsset).balanceOf(otherReserveAddresses[i])
- );
- }
- return reserveCollateralAssetBalance.add(IERC20(collateralAsset).balanceOf(address(this)));
- }
-
- /**
- * @notice Add a collateral asset in the reserve.
- * @param collateralAsset The address of the token being added.
- * @return Returns true if the transaction succeeds.
- */
- function addCollateralAsset(address collateralAsset) public onlyOwner returns (bool) {
- require(!checkIsCollateralAsset(collateralAsset), "specified address is already added as a collateral asset");
- require(collateralAsset != address(0), "can't be a zero address");
- isCollateralAsset[collateralAsset] = true;
- collateralAssets.push(collateralAsset);
- emit CollateralAssetAdded(collateralAsset);
- return true;
- }
-
- /**
- * @notice Remove a collateral asset in the reserve.
- * @param collateralAsset The address of the token being removed.
- * @param index The index of the token being removed.
- * @return Returns true if the transaction succeeds.
- */
- function removeCollateralAsset(address collateralAsset, uint256 index) external onlyOwner returns (bool) {
- require(checkIsCollateralAsset(collateralAsset), "specified address is not a collateral asset");
- require(
- index < collateralAssets.length && collateralAssets[index] == collateralAsset,
- "index into collateralAssets list not mapped to token"
- );
- collateralAssets[index] = collateralAssets[collateralAssets.length.sub(1)];
- collateralAssets.pop();
- delete isCollateralAsset[collateralAsset];
- emit CollateralAssetRemoved(collateralAsset);
- return true;
- }
-
- /**
- * @notice Check if a collateral asset is added to the reserve.
- * @param collateralAsset The address of the token being checked.
- * @return Returns true if the token was added as a collateral asset.
- */
- function checkIsCollateralAsset(address collateralAsset) public view returns (bool) {
- return isCollateralAsset[collateralAsset];
- }
-
- /**
- * @notice Returns the amount of frozen CELO in the reserve.
- * @return The total frozen CELO in the reserve.
- */
- function getFrozenReserveGoldBalance() public view returns (uint256) {
- uint256 currentDay = now / 1 days;
- uint256 frozenDays = currentDay.sub(frozenReserveGoldStartDay);
- if (frozenDays >= frozenReserveGoldDays) return 0;
- return frozenReserveGoldStartBalance.sub(frozenReserveGoldStartBalance.mul(frozenDays).div(frozenReserveGoldDays));
- }
-
- /**
- * @notice Computes the ratio of current reserve balance to total stable token valuation.
- * @return Reserve ratio in a fixed point format.
- */
- function getReserveRatio() public view returns (uint256) {
- address sortedOraclesAddress = registry.getAddressForOrDie(SORTED_ORACLES_REGISTRY_ID);
- ISortedOracles sortedOracles = ISortedOracles(sortedOraclesAddress);
- uint256 reserveGoldBalance = getUnfrozenReserveGoldBalance();
- uint256 stableTokensValueInGold = 0;
- FixidityLib.Fraction memory cgldWeight = FixidityLib.wrap(assetAllocationWeights["cGLD"]);
-
- // slither-disable-next-line cache-array-length
- for (uint256 i = 0; i < _tokens.length; i = i.add(1)) {
- uint256 stableAmount;
- uint256 goldAmount;
- // slither-disable-next-line calls-loop
- (stableAmount, goldAmount) = sortedOracles.medianRate(_tokens[i]);
-
- if (goldAmount != 0) {
- // tokens with no oracle reports don't count towards collateralization ratio
- // slither-disable-next-line calls-loop
- uint256 stableTokenSupply = IERC20(_tokens[i]).totalSupply();
- uint256 aStableTokenValueInGold = stableTokenSupply.mul(goldAmount).div(stableAmount);
- stableTokensValueInGold = stableTokensValueInGold.add(aStableTokenValueInGold);
- }
- }
- return
- FixidityLib
- .newFixed(reserveGoldBalance)
- .divide(cgldWeight)
- .divide(FixidityLib.newFixed(stableTokensValueInGold))
- .unwrap();
- }
-
- /*
- * Internal functions
- */
-
- /**
- * @notice Computes a tobin tax based on the reserve ratio.
- * @return The tobin tax expresesed as a fixidity fraction.
- */
- function computeTobinTax() private view returns (FixidityLib.Fraction memory) {
- FixidityLib.Fraction memory ratio = FixidityLib.wrap(getReserveRatio());
- if (ratio.gte(FixidityLib.wrap(tobinTaxReserveRatio))) {
- return FixidityLib.wrap(0);
- } else {
- return FixidityLib.wrap(tobinTax);
- }
- }
-
- function isStableAsset(address token) external view returns (bool) {
- return isToken[token];
- }
+ for (uint256 i = 0; i < _collateralAssets.length; i++) {
+ if (
+ _collateralAssets[i] != address(0) &&
+ collateralAssetDailySpendingRatios[i] != 0
+ ) {
+ require(
+ checkIsCollateralAsset(_collateralAssets[i]),
+ "the address specified is not a reserve collateral asset"
+ );
+ require(
+ FixidityLib.wrap(collateralAssetDailySpendingRatios[i]).lte(
+ FixidityLib.fixed1()
+ ),
+ "spending ratio cannot be larger than 1"
+ );
+ collateralAssetDailySpendingRatio[
+ _collateralAssets[i]
+ ] = FixidityLib.wrap(collateralAssetDailySpendingRatios[i]);
+ emit DailySpendingRatioForCollateralAssetSet(
+ _collateralAssets[i],
+ collateralAssetDailySpendingRatios[i]
+ );
+ }
+ }
+ }
+
+ /**
+ * @notice Get daily spending ratio.
+ * @return Spending ratio as unwrapped Fraction.
+ */
+ function getDailySpendingRatio() public view returns (uint256) {
+ return spendingRatio.unwrap();
+ }
+
+ /**
+ * @notice Get daily spending ratio of a collateral asset.
+ * @param collateralAsset The address of a collateral asset we're getting a spending ratio for.
+ * @return Daily spending ratio for the collateral asset as unwrapped Fraction.
+ */
+ function getDailySpendingRatioForCollateralAsset(
+ address collateralAsset
+ ) public view returns (uint256) {
+ return collateralAssetDailySpendingRatio[collateralAsset].unwrap();
+ }
+
+ /**
+ * @notice Sets the balance of reserve gold frozen from transfer.
+ * @param frozenGold The amount of CELO frozen.
+ * @param frozenDays The number of days the frozen CELO thaws over.
+ */
+ function setFrozenGold(
+ uint256 frozenGold,
+ uint256 frozenDays
+ ) public onlyOwner {
+ require(
+ frozenGold <= address(this).balance,
+ "Cannot freeze more than balance"
+ );
+ frozenReserveGoldStartBalance = frozenGold;
+ // slither-disable-start events-maths
+ frozenReserveGoldStartDay = now / 1 days;
+ frozenReserveGoldDays = frozenDays;
+ // slither-disable-end events-maths
+ }
+
+ /**
+ * @notice Sets target allocations for CELO and a diversified basket of non-Celo assets.
+ * @param symbols The symbol of each asset in the Reserve portfolio.
+ * @param weights The weight for the corresponding asset as unwrapped Fixidity.Fraction.
+ */
+ function setAssetAllocations(
+ bytes32[] memory symbols,
+ uint256[] memory weights
+ ) public onlyOwner {
+ require(symbols.length == weights.length, "Array length mismatch");
+ FixidityLib.Fraction memory sum = FixidityLib.wrap(0);
+ for (uint256 i = 0; i < weights.length; i = i.add(1)) {
+ sum = sum.add(FixidityLib.wrap(weights[i]));
+ }
+ require(
+ sum.equals(FixidityLib.fixed1()),
+ "Sum of asset allocation must be 1"
+ );
+ // slither-disable-next-line cache-array-length
+ for (uint256 i = 0; i < assetAllocationSymbols.length; i = i.add(1)) {
+ delete assetAllocationWeights[assetAllocationSymbols[i]];
+ }
+ assetAllocationSymbols = symbols;
+ for (uint256 i = 0; i < symbols.length; i = i.add(1)) {
+ require(
+ assetAllocationWeights[symbols[i]] == 0,
+ "Cannot set weight twice"
+ );
+ assetAllocationWeights[symbols[i]] = weights[i];
+ }
+ // NOTE: The CELO asset launched as "Celo Gold" (cGLD), but was renamed to
+ // just CELO by the community.
+ // TODO: Change "cGLD" to "CELO" in this file, after ensuring that any
+ // off chain tools working with asset allocation weights are aware of this
+ // change.
+ require(
+ assetAllocationWeights["cGLD"] != 0,
+ "Must set cGLD asset weight"
+ );
+ emit AssetAllocationSet(symbols, weights);
+ }
+
+ /**
+ * @notice Add a token that the reserve will stabilize.
+ * @param token The address of the token being stabilized.
+ * @return Returns true if the transaction succeeds.
+ */
+ function addToken(address token) external onlyOwner returns (bool) {
+ require(!isToken[token], "token addr already registered");
+ isToken[token] = true;
+ _tokens.push(token);
+ emit TokenAdded(token);
+ return true;
+ }
+
+ /**
+ * @notice Remove a token that the reserve will no longer stabilize.
+ * @param token The address of the token no longer being stabilized.
+ * @param index The index of the token in _tokens.
+ * @return Returns true if the transaction succeeds.
+ */
+ function removeToken(
+ address token,
+ uint256 index
+ ) external onlyOwner isStableToken(token) returns (bool) {
+ require(
+ index < _tokens.length && _tokens[index] == token,
+ "index into tokens list not mapped to token"
+ );
+ isToken[token] = false;
+ address lastItem = _tokens[_tokens.length.sub(1)];
+ _tokens[index] = lastItem;
+ _tokens.length = _tokens.length.sub(1);
+ emit TokenRemoved(token, index);
+ return true;
+ }
+
+ /**
+ * @notice Add a reserve address whose balance shall be included in the reserve ratio.
+ * @param reserveAddress The reserve address to add.
+ * @return Returns true if the transaction succeeds.
+ */
+ function addOtherReserveAddress(
+ address reserveAddress
+ ) external onlyOwner returns (bool) {
+ require(
+ !isOtherReserveAddress[reserveAddress],
+ "reserve addr already added"
+ );
+ isOtherReserveAddress[reserveAddress] = true;
+ otherReserveAddresses.push(reserveAddress);
+ emit OtherReserveAddressAdded(reserveAddress);
+ return true;
+ }
+
+ /**
+ * @notice Remove reserve address whose balance shall no longer be included in the reserve ratio.
+ * @param reserveAddress The reserve address to remove.
+ * @param index The index of the reserve address in otherReserveAddresses.
+ * @return Returns true if the transaction succeeds.
+ */
+ function removeOtherReserveAddress(
+ address reserveAddress,
+ uint256 index
+ ) external onlyOwner returns (bool) {
+ require(
+ isOtherReserveAddress[reserveAddress],
+ "reserve addr was never added"
+ );
+ require(
+ index < otherReserveAddresses.length &&
+ otherReserveAddresses[index] == reserveAddress,
+ "index into reserve list not mapped to address"
+ );
+ isOtherReserveAddress[reserveAddress] = false;
+ address lastItem = otherReserveAddresses[
+ otherReserveAddresses.length.sub(1)
+ ];
+ otherReserveAddresses[index] = lastItem;
+ otherReserveAddresses.length = otherReserveAddresses.length.sub(1);
+ emit OtherReserveAddressRemoved(reserveAddress, index);
+ return true;
+ }
+
+ /**
+ * @notice Gives an address permission to spend Reserve funds.
+ * @param spender The address that is allowed to spend Reserve funds.
+ */
+ function addSpender(address spender) external onlyOwner {
+ require(address(0) != spender, "Spender can't be null");
+ isSpender[spender] = true;
+ emit SpenderAdded(spender);
+ }
+
+ /**
+ * @notice Takes away an address's permission to spend Reserve funds.
+ * @param spender The address that is to be no longer allowed to spend Reserve funds.
+ */
+ function removeSpender(address spender) external onlyOwner {
+ require(isSpender[spender], "Spender hasn't been added");
+ isSpender[spender] = false;
+ emit SpenderRemoved(spender);
+ }
+
+ /**
+ * @notice Checks if an address is able to spend as an exchange.
+ * @dev isExchangeSpender was introduced after cUSD, so the cUSD Exchange is not included in it.
+ * If cUSD's Exchange were to be added to isExchangeSpender, the check with the
+ * registry could be removed.
+ * @param spender The address to be checked.
+ */
+ modifier isAllowedToSpendExchange(address spender) {
+ require(
+ isExchangeSpender[spender] ||
+ (registry.getAddressForOrDie(EXCHANGE_REGISTRY_ID) == spender),
+ "Address not allowed to spend"
+ );
+ _;
+ }
+
+ /**
+ * @notice Gives an address permission to spend Reserve without limit.
+ * @param spender The address that is allowed to spend Reserve funds.
+ */
+ function addExchangeSpender(address spender) external onlyOwner {
+ require(address(0) != spender, "Spender can't be null");
+ require(
+ !isExchangeSpender[spender],
+ "Address is already Exchange Spender"
+ );
+ isExchangeSpender[spender] = true;
+ exchangeSpenderAddresses.push(spender);
+ emit ExchangeSpenderAdded(spender);
+ }
+
+ /**
+ * @notice Takes away an address's permission to spend Reserve funds without limits.
+ * @param spender The address that is to be no longer allowed to spend Reserve funds.
+ * @param index The index in exchangeSpenderAddresses of spender.
+ */
+ function removeExchangeSpender(
+ address spender,
+ uint256 index
+ ) external onlyOwner {
+ isExchangeSpender[spender] = false;
+ uint256 numAddresses = exchangeSpenderAddresses.length;
+ require(index < numAddresses, "Index is invalid");
+ require(
+ spender == exchangeSpenderAddresses[index],
+ "Index does not match spender"
+ );
+ uint256 newNumAddresses = numAddresses.sub(1);
+
+ if (index != newNumAddresses) {
+ exchangeSpenderAddresses[index] = exchangeSpenderAddresses[
+ newNumAddresses
+ ];
+ }
+
+ exchangeSpenderAddresses[newNumAddresses] = address(0x0);
+ exchangeSpenderAddresses.length = newNumAddresses;
+ emit ExchangeSpenderRemoved(spender);
+ }
+
+ /**
+ * @notice Returns addresses of exchanges permitted to spend Reserve funds.
+ * Because exchangeSpenderAddresses was introduced after cUSD, cUSD's exchange
+ * is not included in this list.
+ * @return An array of addresses permitted to spend Reserve funds.
+ */
+ function getExchangeSpenders() external view returns (address[] memory) {
+ return exchangeSpenderAddresses;
+ }
+
+ /**
+ * @notice Transfer gold to a whitelisted address subject to reserve spending limits.
+ * @param to The address that will receive the gold.
+ * @param value The amount of gold to transfer.
+ * @return Returns true if the transaction succeeds.
+ */
+ function transferGold(
+ address payable to,
+ uint256 value
+ ) external returns (bool) {
+ require(
+ isSpender[msg.sender],
+ "sender not allowed to transfer Reserve funds"
+ );
+ require(
+ isOtherReserveAddress[to],
+ "can only transfer to other reserve address"
+ );
+ uint256 currentDay = now / 1 days;
+ if (currentDay > lastSpendingDay) {
+ uint256 balance = getUnfrozenReserveGoldBalance();
+ lastSpendingDay = currentDay;
+ spendingLimit = spendingRatio
+ .multiply(FixidityLib.newFixed(balance))
+ .fromFixed();
+ }
+ require(spendingLimit >= value, "Exceeding spending limit");
+ spendingLimit = spendingLimit.sub(value);
+ return _transferGold(to, value);
+ }
+
+ /**
+ * @notice Transfer collateral asset subject to reserve spending limits to the trader,
+ * if the limit is set, othersise the limit is 100%.
+ * @param collateralAsset The token address you're transferring.
+ * @param to The address that will receive the funds.
+ * @param value The amount of collateral assets to transfer.
+ * @return Returns true if the transaction succeeds.
+ */
+ function transferCollateralAsset(
+ address collateralAsset,
+ address payable to,
+ uint256 value
+ ) external returns (bool) {
+ require(
+ isSpender[msg.sender],
+ "sender not allowed to transfer Reserve funds"
+ );
+ require(
+ isOtherReserveAddress[to],
+ "can only transfer to other reserve address"
+ );
+ require(
+ getDailySpendingRatioForCollateralAsset(collateralAsset) > 0,
+ "this asset has no spending ratio, therefore can't be transferred"
+ );
+ uint256 currentDay = now / 1 days;
+ if (currentDay > collateralAssetLastSpendingDay[collateralAsset]) {
+ uint256 balance = getReserveAddressesCollateralAssetBalance(
+ collateralAsset
+ );
+ collateralAssetLastSpendingDay[collateralAsset] = currentDay;
+ collateralAssetSpendingLimit[
+ collateralAsset
+ ] = collateralAssetDailySpendingRatio[collateralAsset]
+ .multiply(FixidityLib.newFixed(balance))
+ .fromFixed();
+ }
+ uint256 spendingLimitForThisAsset = collateralAssetSpendingLimit[
+ collateralAsset
+ ];
+ require(spendingLimitForThisAsset >= value, "Exceeding spending limit");
+
+ collateralAssetSpendingLimit[
+ collateralAsset
+ ] = spendingLimitForThisAsset.sub(value);
+ return _transferCollateralAsset(collateralAsset, to, value);
+ }
+
+ /**
+ * @notice Transfer collateral asset to any address.
+ * @param collateralAsset The token address you're transferring.
+ * @param to The address that will receive the funds.
+ * @param value The amount of collateral assets to transfer.
+ * @return Returns true if the transaction succeeds.
+ */
+ function _transferCollateralAsset(
+ address collateralAsset,
+ address payable to,
+ uint256 value
+ ) internal returns (bool) {
+ require(
+ value <= getReserveAddressesCollateralAssetBalance(collateralAsset),
+ "Exceeding the amount reserve holds"
+ );
+ // slither-disable-next-line reentrancy-events
+ IERC20(collateralAsset).safeTransfer(to, value);
+ emit ReserveCollateralAssetsTransferred(
+ msg.sender,
+ to,
+ value,
+ collateralAsset
+ );
+ return true;
+ }
+
+ /**
+ * @notice Transfer collateral asset to any address.
+ * @dev Transfers are not subject to a daily spending limit.
+ * @param collateralAsset The address of collateral asset being transferred.
+ * @param to The address that will receive the collateral asset.
+ * @param value The amount of collateral asset to transfer.
+ * @return Returns true if the transaction succeeds.
+ */
+ function transferExchangeCollateralAsset(
+ address collateralAsset,
+ address payable to,
+ uint256 value
+ ) external returns (bool) {
+ require(isExchangeSpender[msg.sender], "Address not allowed to spend");
+ return _transferCollateralAsset(collateralAsset, to, value);
+ }
+
+ /**
+ * @notice Transfer unfrozen gold to any address.
+ * @param to The address that will receive the gold.
+ * @param value The amount of gold to transfer.
+ * @return Returns true if the transaction succeeds.
+ */
+ function _transferGold(
+ address payable to,
+ uint256 value
+ ) internal returns (bool) {
+ require(value <= getUnfrozenBalance(), "Exceeding unfrozen reserves");
+ // slither-disable-next-line reentrancy-events
+ to.sendValue(value);
+ emit ReserveGoldTransferred(msg.sender, to, value);
+ return true;
+ }
+
+ /**
+ * @notice Transfer unfrozen gold to any address, used for one side of CP-DOTO.
+ * @dev Transfers are not subject to a daily spending limit.
+ * @param to The address that will receive the gold.
+ * @param value The amount of gold to transfer.
+ * @return Returns true if the transaction succeeds.
+ */
+ function transferExchangeGold(
+ address payable to,
+ uint256 value
+ ) external isAllowedToSpendExchange(msg.sender) returns (bool) {
+ return _transferGold(to, value);
+ }
+
+ /**
+ * @notice Returns the tobin tax, recomputing it if it's stale.
+ * @return The numerator - tobin tax amount as a fraction.
+ * @return The denominator - tobin tax amount as a fraction.
+ */
+ function getOrComputeTobinTax()
+ external
+ nonReentrant
+ returns (uint256, uint256)
+ {
+ // solhint-disable-next-line not-rely-on-time
+ if (now.sub(tobinTaxCache.timestamp) > tobinTaxStalenessThreshold) {
+ tobinTaxCache.numerator = uint128(computeTobinTax().unwrap());
+ tobinTaxCache.timestamp = uint128(now); // solhint-disable-line not-rely-on-time
+ }
+ return (
+ uint256(tobinTaxCache.numerator),
+ FixidityLib.fixed1().unwrap()
+ );
+ }
+
+ /**
+ * @notice Returns the list of stabilized token addresses.
+ * @return An array of addresses of stabilized tokens.
+ */
+ function getTokens() external view returns (address[] memory) {
+ return _tokens;
+ }
+
+ /**
+ * @notice Returns the list other addresses included in the reserve total.
+ * @return An array of other addresses included in the reserve total.
+ */
+ function getOtherReserveAddresses()
+ external
+ view
+ returns (address[] memory)
+ {
+ return otherReserveAddresses;
+ }
+
+ /**
+ * @notice Returns a list of token symbols that have been allocated.
+ * @return An array of token symbols that have been allocated.
+ */
+ function getAssetAllocationSymbols()
+ external
+ view
+ returns (bytes32[] memory)
+ {
+ return assetAllocationSymbols;
+ }
+
+ /**
+ * @notice Returns a list of weights used for the allocation of reserve assets.
+ * @return An array of a list of weights used for the allocation of reserve assets.
+ */
+ function getAssetAllocationWeights()
+ external
+ view
+ returns (uint256[] memory)
+ {
+ uint256[] memory weights = new uint256[](assetAllocationSymbols.length);
+ // slither-disable-next-line cache-array-length
+ for (uint256 i = 0; i < assetAllocationSymbols.length; i = i.add(1)) {
+ weights[i] = assetAllocationWeights[assetAllocationSymbols[i]];
+ }
+ return weights;
+ }
+
+ /**
+ * @notice Returns the amount of unfrozen CELO in the reserve.
+ * @return The total unfrozen CELO in the reserve.
+ */
+ function getUnfrozenBalance() public view returns (uint256) {
+ uint256 balance = address(this).balance;
+ uint256 frozenReserveGold = getFrozenReserveGoldBalance();
+ return balance > frozenReserveGold ? balance.sub(frozenReserveGold) : 0;
+ }
+
+ /**
+ * @notice Returns the amount of CELO included in the reserve.
+ * @return The CELO amount included in the reserve.
+ */
+ function getReserveGoldBalance() public view returns (uint256) {
+ return address(this).balance.add(getOtherReserveAddressesGoldBalance());
+ }
+
+ /**
+ * @notice Returns the amount of CELO included in other reserve addresses.
+ * @return The CELO amount included in other reserve addresses.
+ */
+ function getOtherReserveAddressesGoldBalance()
+ public
+ view
+ returns (uint256)
+ {
+ uint256 reserveGoldBalance = 0;
+ // slither-disable-next-line cache-array-length
+ for (uint256 i = 0; i < otherReserveAddresses.length; i = i.add(1)) {
+ reserveGoldBalance = reserveGoldBalance.add(
+ otherReserveAddresses[i].balance
+ );
+ }
+ return reserveGoldBalance;
+ }
+
+ /**
+ * @notice Returns the amount of unfrozen CELO included in the reserve.
+ * @return The unfrozen CELO amount included in the reserve.
+ */
+ function getUnfrozenReserveGoldBalance() public view returns (uint256) {
+ return getUnfrozenBalance().add(getOtherReserveAddressesGoldBalance());
+ }
+
+ /**
+ * @notice Returns the amount of particular collateral asset
+ * in reserve including other reserve addresses.
+ * @param collateralAsset the asset we're checking a balance of
+ * @return The balance of particular collateral asset.
+ */
+ function getReserveAddressesCollateralAssetBalance(
+ address collateralAsset
+ ) public view returns (uint256) {
+ require(
+ checkIsCollateralAsset(collateralAsset),
+ "specified address is not a collateral asset"
+ );
+ uint256 reserveCollateralAssetBalance = 0;
+ // slither-disable-next-line cache-array-length
+ for (uint256 i = 0; i < otherReserveAddresses.length; i++) {
+ // slither-disable-next-line calls-loop
+ reserveCollateralAssetBalance = reserveCollateralAssetBalance.add(
+ IERC20(collateralAsset).balanceOf(otherReserveAddresses[i])
+ );
+ }
+ return
+ reserveCollateralAssetBalance.add(
+ IERC20(collateralAsset).balanceOf(address(this))
+ );
+ }
+
+ /**
+ * @notice Add a collateral asset in the reserve.
+ * @param collateralAsset The address of the token being added.
+ * @return Returns true if the transaction succeeds.
+ */
+ function addCollateralAsset(
+ address collateralAsset
+ ) public onlyOwner returns (bool) {
+ require(
+ !checkIsCollateralAsset(collateralAsset),
+ "specified address is already added as a collateral asset"
+ );
+ require(collateralAsset != address(0), "can't be a zero address");
+ isCollateralAsset[collateralAsset] = true;
+ collateralAssets.push(collateralAsset);
+ emit CollateralAssetAdded(collateralAsset);
+ return true;
+ }
+
+ /**
+ * @notice Remove a collateral asset in the reserve.
+ * @param collateralAsset The address of the token being removed.
+ * @param index The index of the token being removed.
+ * @return Returns true if the transaction succeeds.
+ */
+ function removeCollateralAsset(
+ address collateralAsset,
+ uint256 index
+ ) external onlyOwner returns (bool) {
+ require(
+ checkIsCollateralAsset(collateralAsset),
+ "specified address is not a collateral asset"
+ );
+ require(
+ index < collateralAssets.length &&
+ collateralAssets[index] == collateralAsset,
+ "index into collateralAssets list not mapped to token"
+ );
+ collateralAssets[index] = collateralAssets[
+ collateralAssets.length.sub(1)
+ ];
+ collateralAssets.pop();
+ delete isCollateralAsset[collateralAsset];
+ emit CollateralAssetRemoved(collateralAsset);
+ return true;
+ }
+
+ /**
+ * @notice Check if a collateral asset is added to the reserve.
+ * @param collateralAsset The address of the token being checked.
+ * @return Returns true if the token was added as a collateral asset.
+ */
+ function checkIsCollateralAsset(
+ address collateralAsset
+ ) public view returns (bool) {
+ return isCollateralAsset[collateralAsset];
+ }
+
+ /**
+ * @notice Returns the amount of frozen CELO in the reserve.
+ * @return The total frozen CELO in the reserve.
+ */
+ function getFrozenReserveGoldBalance() public view returns (uint256) {
+ uint256 currentDay = now / 1 days;
+ uint256 frozenDays = currentDay.sub(frozenReserveGoldStartDay);
+ if (frozenDays >= frozenReserveGoldDays) return 0;
+ return
+ frozenReserveGoldStartBalance.sub(
+ frozenReserveGoldStartBalance.mul(frozenDays).div(
+ frozenReserveGoldDays
+ )
+ );
+ }
+
+ /**
+ * @notice Computes the ratio of current reserve balance to total stable token valuation.
+ * @return Reserve ratio in a fixed point format.
+ */
+ function getReserveRatio() public view returns (uint256) {
+ address sortedOraclesAddress = registry.getAddressForOrDie(
+ SORTED_ORACLES_REGISTRY_ID
+ );
+ ISortedOracles sortedOracles = ISortedOracles(sortedOraclesAddress);
+ uint256 reserveGoldBalance = getUnfrozenReserveGoldBalance();
+ uint256 stableTokensValueInGold = 0;
+ FixidityLib.Fraction memory cgldWeight = FixidityLib.wrap(
+ assetAllocationWeights["cGLD"]
+ );
+
+ // slither-disable-next-line cache-array-length
+ for (uint256 i = 0; i < _tokens.length; i = i.add(1)) {
+ uint256 stableAmount;
+ uint256 goldAmount;
+ // slither-disable-next-line calls-loop
+ (stableAmount, goldAmount) = sortedOracles.medianRate(_tokens[i]);
+
+ if (goldAmount != 0) {
+ // tokens with no oracle reports don't count towards collateralization ratio
+ // slither-disable-next-line calls-loop
+ uint256 stableTokenSupply = IERC20(_tokens[i]).totalSupply();
+ uint256 aStableTokenValueInGold = stableTokenSupply
+ .mul(goldAmount)
+ .div(stableAmount);
+ stableTokensValueInGold = stableTokensValueInGold.add(
+ aStableTokenValueInGold
+ );
+ }
+ }
+ return
+ FixidityLib
+ .newFixed(reserveGoldBalance)
+ .divide(cgldWeight)
+ .divide(FixidityLib.newFixed(stableTokensValueInGold))
+ .unwrap();
+ }
+
+ /*
+ * Internal functions
+ */
+
+ /**
+ * @notice Computes a tobin tax based on the reserve ratio.
+ * @return The tobin tax expresesed as a fixidity fraction.
+ */
+ function computeTobinTax()
+ private
+ view
+ returns (FixidityLib.Fraction memory)
+ {
+ FixidityLib.Fraction memory ratio = FixidityLib.wrap(getReserveRatio());
+ if (ratio.gte(FixidityLib.wrap(tobinTaxReserveRatio))) {
+ return FixidityLib.wrap(0);
+ } else {
+ return FixidityLib.wrap(tobinTax);
+ }
+ }
+
+ function isStableAsset(address token) external view returns (bool) {
+ return isToken[token];
+ }
}
diff --git a/coverage_report/cmd_line b/coverage_report/cmd_line
index fc262d8..e348a86 100644
--- a/coverage_report/cmd_line
+++ b/coverage_report/cmd_line
@@ -1 +1 @@
-genhtml -o coverage_report lcov.info --ignore-errors corrupt --ignore-errors inconsistent
+genhtml -o coverage_report lcov.info --ignore-errors inconsistent
diff --git a/coverage_report/contracts/BancorExchangeProvider.sol.func-c.html b/coverage_report/contracts/BancorExchangeProvider.sol.func-c.html
index b5ca3fd..4ce4744 100644
--- a/coverage_report/contracts/BancorExchangeProvider.sol.func-c.html
+++ b/coverage_report/contracts/BancorExchangeProvider.sol.func-c.html
@@ -37,7 +37,7 @@