Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6059132
theoretical contract changes
dbeal-eth Aug 31, 2021
c7f9a84
add tests for wrapper, and stub for wrapperfactory
dbeal-eth Sep 2, 2021
4fc47ec
fix tests
dbeal-eth Sep 2, 2021
64dc546
add comment
dbeal-eth Sep 8, 2021
b2c5571
fix tests for WrapperFactory
dbeal-eth Sep 9, 2021
55fc210
more test fixes
dbeal-eth Sep 9, 2021
5369678
implement excludedDebt in DebtCache, fixed associated tests
dbeal-eth Sep 12, 2021
56a9c72
re-add old ether wrapper in case we need
dbeal-eth Sep 13, 2021
fc5e171
add missing mock file
dbeal-eth Sep 14, 2021
7460d21
Merge branch 'abstract-wrapper-with-old-ether' into abstract-eth-wrapper
dbeal-eth Sep 23, 2021
d61d750
Merge remote-tracking branch 'origin/develop' into abstract-eth-wrapper
dbeal-eth Sep 23, 2021
72c1249
fix test failures and misses
dbeal-eth Sep 24, 2021
fc6167d
fix accidential synths local-ovm update
dbeal-eth Sep 24, 2021
4d3ed73
add missing tests
dbeal-eth Sep 24, 2021
1e7c4b2
fix duplicate maxTokenAmountCall
dbeal-eth Sep 24, 2021
586818d
better naming pattern
dbeal-eth Sep 24, 2021
db11994
replace with safemath
dbeal-eth Sep 24, 2021
3fe239e
try fix test failures
dbeal-eth Sep 24, 2021
5a23fc2
fix math error
dbeal-eth Sep 28, 2021
8aa3ff9
fix local-ovm deployment oops
dbeal-eth Sep 28, 2021
9d1a5e8
remove unused totalIssuedSynths
dbeal-eth Sep 28, 2021
0d70923
update to allow for negative fees
dbeal-eth Oct 1, 2021
34c2b7b
fix comment
dbeal-eth Oct 5, 2021
19c8bc4
fix(test): extract correct address from event
leckylao Oct 9, 2021
d8e71b8
Try to fix CI by loading the contract with ethers
leckylao Oct 11, 2021
68fd43e
using ethers from hardhat
leckylao Oct 11, 2021
89389aa
Try to fix integration test
leckylao Oct 11, 2021
3057c1a
deploying WETH for OVM integration test
leckylao Oct 12, 2021
4d04cd4
perf(test): fix WrapperFactory integration test:
leckylao Oct 12, 2021
fac5931
revert "-d" option on Ops start
leckylao Oct 12, 2021
d65f99b
Add fallback coverage
barrasso Oct 12, 2021
f2fbc13
Merge develop into abstract-eth-wrapper
barrasso Oct 13, 2021
e160741
Fix merge conflict
barrasso Oct 13, 2021
d147d8a
fix slither security alerts
barrasso Oct 13, 2021
7017dd0
remove unused code
barrasso Oct 13, 2021
9192ea5
Merge branch 'develop' into abstract-eth-wrapper
barrasso Oct 15, 2021
4f6e1b6
Merge branch develop into abstract-eth-wrapper and fix conflicts
barrasso Oct 20, 2021
2b76dca
changes requested by isiro
dbeal-eth Oct 26, 2021
1f2847e
fix lint test
barrasso Oct 26, 2021
d41faf2
update _totalNonSnxBackedDebt to better handle excluded debt
dbeal-eth Oct 26, 2021
077b0e8
checkout to dev branch updateCachedSynthDebtsWithRates
dbeal-eth Oct 26, 2021
61a0fad
Merge branch 'abstract-eth-wrapper' of https://github.com/Synthetixio…
dbeal-eth Oct 26, 2021
447401c
re-add debt-cache comment
dbeal-eth Oct 26, 2021
1fc0b87
fix isInvalid
dbeal-eth Oct 26, 2021
66e944e
fix unneeded constants
dbeal-eth Oct 26, 2021
f52432a
remove convenience function and accompanying modifier
dbeal-eth Oct 26, 2021
5ddb558
fix minor things
dbeal-eth Oct 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 58 additions & 16 deletions contracts/BaseDebtCache.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import "./interfaces/ISystemStatus.sol";
import "./interfaces/IERC20.sol";
import "./interfaces/ICollateralManager.sol";
import "./interfaces/IEtherWrapper.sol";
import "./interfaces/IWrapperFactory.sol";

//
// The debt cache (SIP-91) caches the global debt and the debt of each synth in the system.
Expand All @@ -37,6 +38,7 @@ contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {

uint internal _cachedDebt;
mapping(bytes32 => uint) internal _cachedSynthDebt;
mapping(bytes32 => uint) internal _excludedIssuedDebt;
uint internal _cacheTimestamp;
bool internal _cacheInvalid = true;

Expand All @@ -53,20 +55,22 @@ contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_COLLATERALMANAGER = "CollateralManager";
bytes32 private constant CONTRACT_ETHER_WRAPPER = "EtherWrapper";
bytes32 private constant CONTRACT_WRAPPER_FACTORY = "WrapperFactory";

constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}

/* ========== VIEWS ========== */

function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](6);
bytes32[] memory newAddresses = new bytes32[](7);
newAddresses[0] = CONTRACT_ISSUER;
newAddresses[1] = CONTRACT_EXCHANGER;
newAddresses[2] = CONTRACT_EXRATES;
newAddresses[3] = CONTRACT_SYSTEMSTATUS;
newAddresses[4] = CONTRACT_COLLATERALMANAGER;
newAddresses[5] = CONTRACT_ETHER_WRAPPER;
newAddresses[5] = CONTRACT_WRAPPER_FACTORY;
newAddresses[6] = CONTRACT_ETHER_WRAPPER;
addresses = combineArrays(existingAddresses, newAddresses);
}

Expand Down Expand Up @@ -94,6 +98,10 @@ contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {
return IEtherWrapper(requireAndGetAddress(CONTRACT_ETHER_WRAPPER));
}

function wrapperFactory() internal view returns (IWrapperFactory) {
return IWrapperFactory(requireAndGetAddress(CONTRACT_WRAPPER_FACTORY));
}

function debtSnapshotStaleTime() external view returns (uint) {
return getDebtSnapshotStaleTime();
}
Expand Down Expand Up @@ -125,7 +133,6 @@ contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {
return _cacheStale(_cacheTimestamp);
}

// Returns the USD-denominated supply of each synth in `currencyKeys`, according to `rates`.
function _issuedSynthValues(bytes32[] memory currencyKeys, uint[] memory rates)
internal
view
Expand Down Expand Up @@ -156,11 +163,11 @@ contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {
{
(uint[] memory rates, bool isInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
uint[] memory values = _issuedSynthValues(currencyKeys, rates);
(uint excludedDebt, bool isAnyNonSnxDebtRateInvalid) = _totalNonSnxBackedDebt();
return (values, excludedDebt, isInvalid || isAnyNonSnxDebtRateInvalid);
(uint excludedDebt, bool isAnyNonSnxDebtRateInvalid) = _totalNonSnxBackedDebt(currencyKeys, rates, isInvalid);

return (values, excludedDebt, isAnyNonSnxDebtRateInvalid);
}

// Returns the USD-denominated supply of each synth in `currencyKeys`, using current exchange rates.
function currentSynthDebts(bytes32[] calldata currencyKeys)
external
view
Expand All @@ -186,24 +193,44 @@ contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {
return _cachedSynthDebts(currencyKeys);
}

function _totalNonSnxBackedDebt() internal view returns (uint excludedDebt, bool isInvalid) {
function _excludedIssuedDebts(bytes32[] memory currencyKeys) internal view returns (uint[] memory) {
uint numKeys = currencyKeys.length;
uint[] memory debts = new uint[](numKeys);
for (uint i = 0; i < numKeys; i++) {
debts[i] = _excludedIssuedDebt[currencyKeys[i]];
}
return debts;
}

function excludedIssuedDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory excludedDebts) {
return _excludedIssuedDebts(currencyKeys);
}

// Returns the total sUSD debt backed by non-SNX collateral, excluding debts recorded with _excludedIssuedDebt
function totalNonSnxBackedDebt() external view returns (uint excludedDebt, bool isInvalid) {
bytes32[] memory currencyKeys = issuer().availableCurrencyKeys();
(uint[] memory rates, bool ratesAreInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);

return _totalNonSnxBackedDebt(currencyKeys, rates, ratesAreInvalid);
}

function _totalNonSnxBackedDebt(bytes32[] memory currencyKeys, uint[] memory rates, bool ratesAreInvalid) internal view returns (uint excludedDebt, bool isInvalid) {
// Calculate excluded debt.
// 1. MultiCollateral long debt + short debt.
(uint longValue, bool anyTotalLongRateIsInvalid) = collateralManager().totalLong();
(uint shortValue, bool anyTotalShortRateIsInvalid) = collateralManager().totalShort();
isInvalid = anyTotalLongRateIsInvalid || anyTotalShortRateIsInvalid;
isInvalid = ratesAreInvalid || anyTotalLongRateIsInvalid || anyTotalShortRateIsInvalid;
excludedDebt = longValue.add(shortValue);

// 2. EtherWrapper.
// Subtract sETH and sUSD issued by EtherWrapper.
excludedDebt = excludedDebt.add(etherWrapper().totalIssuedSynths());

return (excludedDebt, isInvalid);
}
for (uint i = 0; i < currencyKeys.length; i++) {
excludedDebt = excludedDebt.add(_excludedIssuedDebt[currencyKeys[i]].multiplyDecimalRound(rates[i]));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbeal-eth Why not track and update the delta for _excludedDebt as a single value and then you don't need to recompute the sum fo all _excludedIssuedDebt here when taking snapshot.

I don't see where else the individual _excludedIssuedDebt[keys] are used atm. I think it is good to have a granular breakdown of each excluded issued debt but you can optimise this here with rolling up the values as a single excludedDebt value.

This is what is currently used to store the excluded debt value _cachedSynthDebt[EXCLUDED_DEBT_KEY] = excludedDebt https://github.com/Synthetixio/synthetix/blob/master/contracts/DebtCache.sol#L38-L39

}

// Returns the total sUSD debt backed by non-SNX collateral.
function totalNonSnxBackedDebt() external view returns (uint excludedDebt, bool isInvalid) {
return _totalNonSnxBackedDebt();
return (excludedDebt, isInvalid);
}

function _currentDebt() internal view returns (uint debt, bool anyRateIsInvalid) {
Expand All @@ -212,7 +239,7 @@ contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {

// Sum all issued synth values based on their supply.
uint[] memory values = _issuedSynthValues(currencyKeys, rates);
(uint excludedDebt, bool isAnyNonSnxDebtRateInvalid) = _totalNonSnxBackedDebt();
(uint excludedDebt, bool isAnyNonSnxDebtRateInvalid) = _totalNonSnxBackedDebt(currencyKeys, rates, isInvalid);

uint numValues = values.length;
uint total;
Expand All @@ -221,10 +248,9 @@ contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {
}
total = total < excludedDebt ? 0 : total.sub(excludedDebt);

return (total, isInvalid || isAnyNonSnxDebtRateInvalid);
return (total, isAnyNonSnxDebtRateInvalid);
}

// Returns the current debt of the system, excluding non-SNX backed debt (eg. EtherWrapper).
function currentDebt() external view returns (uint debt, bool anyRateIsInvalid) {
return _currentDebt();
}
Expand Down Expand Up @@ -260,6 +286,8 @@ contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {

function takeDebtSnapshot() external {}

function recordExcludedDebtChange(bytes32 currencyKey, int256 delta) external {}

/* ========== MODIFIERS ========== */

function _requireSystemActiveIfNotOwner() internal view {
Expand Down Expand Up @@ -290,4 +318,18 @@ contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {
_onlyIssuerOrExchanger();
_;
}

function _onlyDebtIssuer() internal view {
bool isWrapper = wrapperFactory().isWrapper(msg.sender);

// owner included for debugging and fixing in emergency situation
bool isOwner = msg.sender == owner;

require(isOwner || isWrapper, "Only debt issuers may call this");
}

modifier onlyDebtIssuer() {
_onlyDebtIssuer();
_;
}
}
8 changes: 8 additions & 0 deletions contracts/DebtCache.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ contract DebtCache is BaseDebtCache {
_updateDebtCacheValidity(currentlyInvalid);
}

function recordExcludedDebtChange(bytes32 currencyKey, int256 delta) external onlyDebtIssuer {
int256 newExcludedDebt = int256(_excludedIssuedDebt[currencyKey]) + delta;

require(newExcludedDebt >= 0, "Excluded debt cannot become negative");

_excludedIssuedDebt[currencyKey] = uint(newExcludedDebt);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbeal-eth Do we want to update the debt cache cachedDebt value as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after discussion this will be reviewed and replaced if necessary before mainnet later this week

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As comment above, consider updating a single value to roll up excludedDebt saves iterating over a mapping of all excluded debts when taking snapshot

}

function updateCachedsUSDDebt(int amount) external onlyIssuer {
uint delta = SafeDecimalMath.abs(amount);
if (amount > 0) {
Expand Down
15 changes: 12 additions & 3 deletions contracts/FeePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import "./interfaces/IDelegateApprovals.sol";
import "./interfaces/IRewardsDistribution.sol";
import "./interfaces/ICollateralManager.sol";
import "./interfaces/IEtherWrapper.sol";
import "./interfaces/IWrapperFactory.sol";

// https://docs.synthetix.io/contracts/source/contracts/feepool
contract FeePool is Owned, Proxyable, LimitedSetup, MixinSystemSettings, IFeePool {
Expand Down Expand Up @@ -75,6 +76,7 @@ contract FeePool is Owned, Proxyable, LimitedSetup, MixinSystemSettings, IFeePoo
bytes32 private constant CONTRACT_COLLATERALMANAGER = "CollateralManager";
bytes32 private constant CONTRACT_REWARDSDISTRIBUTION = "RewardsDistribution";
bytes32 private constant CONTRACT_ETHER_WRAPPER = "EtherWrapper";
bytes32 private constant CONTRACT_WRAPPER_FACTORY = "WrapperFactory";

/* ========== ETERNAL STORAGE CONSTANTS ========== */

Expand All @@ -93,7 +95,7 @@ contract FeePool is Owned, Proxyable, LimitedSetup, MixinSystemSettings, IFeePoo
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](12);
bytes32[] memory newAddresses = new bytes32[](13);
newAddresses[0] = CONTRACT_SYSTEMSTATUS;
newAddresses[1] = CONTRACT_SYNTHETIX;
newAddresses[2] = CONTRACT_FEEPOOLSTATE;
Expand All @@ -105,7 +107,8 @@ contract FeePool is Owned, Proxyable, LimitedSetup, MixinSystemSettings, IFeePoo
newAddresses[8] = CONTRACT_DELEGATEAPPROVALS;
newAddresses[9] = CONTRACT_REWARDSDISTRIBUTION;
newAddresses[10] = CONTRACT_COLLATERALMANAGER;
newAddresses[11] = CONTRACT_ETHER_WRAPPER;
newAddresses[11] = CONTRACT_WRAPPER_FACTORY;
newAddresses[12] = CONTRACT_ETHER_WRAPPER;
addresses = combineArrays(existingAddresses, newAddresses);
}

Expand Down Expand Up @@ -157,6 +160,10 @@ contract FeePool is Owned, Proxyable, LimitedSetup, MixinSystemSettings, IFeePoo
return IEtherWrapper(requireAndGetAddress(CONTRACT_ETHER_WRAPPER));
}

function wrapperFactory() internal view returns (IWrapperFactory) {
return IWrapperFactory(requireAndGetAddress(CONTRACT_WRAPPER_FACTORY));
}

function issuanceRatio() external view returns (uint) {
return getIssuanceRatio();
}
Expand Down Expand Up @@ -250,6 +257,7 @@ contract FeePool is Owned, Proxyable, LimitedSetup, MixinSystemSettings, IFeePoo
require(_recentFeePeriodsStorage(0).startTime <= (now - getFeePeriodDuration()), "Too early to close fee period");

etherWrapper().distributeFees();
wrapperFactory().distributeFees();

// Note: when FEE_PERIOD_LENGTH = 2, periodClosing is the current period & periodToRollover is the last open claimable period
FeePeriod storage periodClosing = _recentFeePeriodsStorage(FEE_PERIOD_LENGTH - 2);
Expand Down Expand Up @@ -725,8 +733,9 @@ contract FeePool is Owned, Proxyable, LimitedSetup, MixinSystemSettings, IFeePoo
bool isSynth = issuer().synthsByAddress(msg.sender) != bytes32(0);
bool isCollateral = collateralManager().hasCollateral(msg.sender);
bool isEtherWrapper = msg.sender == address(etherWrapper());
bool isWrapper = msg.sender == address(wrapperFactory());

require(isExchanger || isSynth || isCollateral || isEtherWrapper, "Only Internal Contracts");
require(isExchanger || isSynth || isCollateral || isEtherWrapper || isWrapper, "Only Internal Contracts");
_;
}

Expand Down
27 changes: 27 additions & 0 deletions contracts/MixinSystemSettings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ contract MixinSystemSettings is MixinResolver {
bytes32 internal constant SETTING_ETHER_WRAPPER_MAX_ETH = "etherWrapperMaxETH";
bytes32 internal constant SETTING_ETHER_WRAPPER_MINT_FEE_RATE = "etherWrapperMintFeeRate";
bytes32 internal constant SETTING_ETHER_WRAPPER_BURN_FEE_RATE = "etherWrapperBurnFeeRate";
bytes32 internal constant SETTING_WRAPPER_MAX_TOKEN_AMOUNT = "wrapperMaxTokens";
bytes32 internal constant SETTING_WRAPPER_MINT_FEE_RATE = "wrapperMintFeeRate";
bytes32 internal constant SETTING_WRAPPER_BURN_FEE_RATE = "wrapperBurnFeeRate";
bytes32 internal constant SETTING_MIN_CRATIO = "minCratio";
bytes32 internal constant SETTING_NEW_COLLATERAL_MANAGER = "newCollateralManager";
bytes32 internal constant SETTING_INTERACTION_DELAY = "interactionDelay";
Expand Down Expand Up @@ -143,6 +146,30 @@ contract MixinSystemSettings is MixinResolver {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ETHER_WRAPPER_BURN_FEE_RATE);
}

function getWrapperMaxTokenAmount(address wrapper) internal view returns (uint) {
return
flexibleStorage().getUIntValue(
SETTING_CONTRACT_NAME,
keccak256(abi.encodePacked(SETTING_WRAPPER_MAX_TOKEN_AMOUNT, wrapper))
);
}

function getWrapperMintFeeRate(address wrapper) internal view returns (int) {
return
flexibleStorage().getIntValue(
SETTING_CONTRACT_NAME,
keccak256(abi.encodePacked(SETTING_WRAPPER_MINT_FEE_RATE, wrapper))
);
}

function getWrapperBurnFeeRate(address wrapper) internal view returns (int) {
return
flexibleStorage().getIntValue(
SETTING_CONTRACT_NAME,
keccak256(abi.encodePacked(SETTING_WRAPPER_BURN_FEE_RATE, wrapper))
);
}

function getMinCratio(address collateral) internal view returns (uint) {
return
flexibleStorage().getUIntValue(
Expand Down
16 changes: 12 additions & 4 deletions contracts/MultiCollateralSynth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "./Synth.sol";
// Internal references
import "./interfaces/ICollateralManager.sol";
import "./interfaces/IEtherWrapper.sol";
import "./interfaces/IWrapperFactory.sol";

// https://docs.synthetix.io/contracts/source/contracts/multicollateralsynth
contract MultiCollateralSynth is Synth {
Expand All @@ -15,6 +16,7 @@ contract MultiCollateralSynth is Synth {

bytes32 private constant CONTRACT_COLLATERALMANAGER = "CollateralManager";
bytes32 private constant CONTRACT_ETHER_WRAPPER = "EtherWrapper";
bytes32 private constant CONTRACT_WRAPPER_FACTORY = "WrapperFactory";

/* ========== CONSTRUCTOR ========== */

Expand All @@ -39,11 +41,16 @@ contract MultiCollateralSynth is Synth {
return IEtherWrapper(requireAndGetAddress(CONTRACT_ETHER_WRAPPER));
}

function wrapperFactory() internal view returns (IWrapperFactory) {
return IWrapperFactory(requireAndGetAddress(CONTRACT_WRAPPER_FACTORY));
}

function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = Synth.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](2);
bytes32[] memory newAddresses = new bytes32[](3);
newAddresses[0] = CONTRACT_COLLATERALMANAGER;
newAddresses[1] = CONTRACT_ETHER_WRAPPER;
newAddresses[2] = CONTRACT_WRAPPER_FACTORY;
addresses = combineArrays(existingAddresses, newAddresses);
}

Expand All @@ -69,17 +76,18 @@ contract MultiCollateralSynth is Synth {

/* ========== MODIFIERS ========== */

// Contracts directly interacting with multiCollateralSynth to issue and burn
// Contracts directly interacting with multiCollateralSynth or wrapper to issue and burn
modifier onlyInternalContracts() {
bool isFeePool = msg.sender == address(feePool());
bool isExchanger = msg.sender == address(exchanger());
bool isIssuer = msg.sender == address(issuer());
bool isEtherWrapper = msg.sender == address(etherWrapper());
bool isWrapper = wrapperFactory().isWrapper(msg.sender);
bool isMultiCollateral = collateralManager().hasCollateral(msg.sender);

require(
isFeePool || isExchanger || isIssuer || isEtherWrapper || isMultiCollateral,
"Only FeePool, Exchanger, Issuer, MultiCollateral contracts allowed"
isFeePool || isExchanger || isIssuer || isEtherWrapper || isWrapper || isMultiCollateral,
"Only FeePool, Exchanger, Issuer, Wrapper, or MultiCollateral contracts allowed"
);
_;
}
Expand Down
Loading