Skip to content

Commit 963d78d

Browse files
authored
SIP-187 fix partial synth updates and debt cache updates (#1551)
* fix partial synth updates and debt cache updates * Remove require check that cachedSum < Debt as excluded Debt can cause this to fail. Update calc of delta in new synths changed.
1 parent f1fcfcf commit 963d78d

File tree

12 files changed

+320
-35
lines changed

12 files changed

+320
-35
lines changed

contracts/DebtCache.sol

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,28 +48,41 @@ contract DebtCache is BaseDebtCache {
4848

4949
function updateCachedSynthDebts(bytes32[] calldata currencyKeys) external requireSystemActiveIfNotOwner {
5050
(uint[] memory rates, bool anyRateInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
51-
_updateCachedSynthDebtsWithRates(currencyKeys, rates, anyRateInvalid, false);
51+
_updateCachedSynthDebtsWithRates(currencyKeys, rates, anyRateInvalid);
5252
}
5353

5454
function updateCachedSynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external onlyIssuer {
5555
bytes32[] memory synthKeyArray = new bytes32[](1);
5656
synthKeyArray[0] = currencyKey;
5757
uint[] memory synthRateArray = new uint[](1);
5858
synthRateArray[0] = currencyRate;
59-
_updateCachedSynthDebtsWithRates(synthKeyArray, synthRateArray, false, false);
59+
_updateCachedSynthDebtsWithRates(synthKeyArray, synthRateArray, false);
6060
}
6161

6262
function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates)
6363
external
6464
onlyIssuerOrExchanger
6565
{
66-
_updateCachedSynthDebtsWithRates(currencyKeys, currencyRates, false, false);
66+
_updateCachedSynthDebtsWithRates(currencyKeys, currencyRates, false);
6767
}
6868

6969
function updateDebtCacheValidity(bool currentlyInvalid) external onlyIssuer {
7070
_updateDebtCacheValidity(currentlyInvalid);
7171
}
7272

73+
function updateCachedsUSDDebt(int amount) external onlyIssuer {
74+
uint delta = SafeDecimalMath.abs(amount);
75+
if (amount > 0) {
76+
_cachedSynthDebt[sUSD] = _cachedSynthDebt[sUSD].add(delta);
77+
_cachedDebt = _cachedDebt.add(delta);
78+
} else {
79+
_cachedSynthDebt[sUSD] = _cachedSynthDebt[sUSD].sub(delta);
80+
_cachedDebt = _cachedDebt.sub(delta);
81+
}
82+
83+
emit DebtCacheUpdated(_cachedDebt);
84+
}
85+
7386
/* ========== INTERNAL FUNCTIONS ========== */
7487

7588
function _updateDebtCacheValidity(bool currentlyInvalid) internal {
@@ -83,8 +96,7 @@ contract DebtCache is BaseDebtCache {
8396
function _updateCachedSynthDebtsWithRates(
8497
bytes32[] memory currencyKeys,
8598
uint[] memory currentRates,
86-
bool anyRateIsInvalid,
87-
bool recomputeExcludedDebt
99+
bool anyRateIsInvalid
88100
) internal {
89101
uint numKeys = currencyKeys.length;
90102
require(numKeys == currentRates.length, "Input array lengths differ");
@@ -104,26 +116,12 @@ contract DebtCache is BaseDebtCache {
104116
_cachedSynthDebt[key] = currentSynthDebt;
105117
}
106118

107-
// Compute the excluded debt (wrappers, etc.) if necessary.
108-
uint excludedDebtSum = _cachedSynthDebt[EXCLUDED_DEBT_KEY];
109-
if (recomputeExcludedDebt) {
110-
(uint excludedDebt, bool anyNonSnxDebtRateIsInvalid) = _totalNonSnxBackedDebt();
111-
anyRateIsInvalid = anyRateIsInvalid || anyNonSnxDebtRateIsInvalid;
112-
excludedDebtSum = excludedDebt;
113-
}
114-
115-
// Subtract excluded debt.
116-
cachedSum = cachedSum.floorsub(_cachedSynthDebt[EXCLUDED_DEBT_KEY]);
117-
currentSum = currentSum.floorsub(excludedDebtSum);
118-
_cachedSynthDebt[EXCLUDED_DEBT_KEY] = excludedDebtSum;
119-
120119
// Apply the debt update.
121120
if (cachedSum != currentSum) {
122121
uint debt = _cachedDebt;
123-
// This requirement should never fail, as the total debt snapshot is the sum of the individual synth
124-
// debt snapshots.
125-
require(cachedSum <= debt, "Cached synth sum exceeds total debt");
126-
debt = debt.sub(cachedSum).add(currentSum);
122+
// apply the delta between the cachedSum and currentSum
123+
// add currentSum before sub cachedSum to prevent overflow as cachedSum > debt for large amount of excluded debt
124+
debt = debt.add(currentSum).sub(cachedSum);
127125
_cachedDebt = debt;
128126
emit DebtCacheUpdated(debt);
129127
}

contracts/Issuer.sol

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import "./MixinSystemSettings.sol";
77
import "./interfaces/IIssuer.sol";
88

99
// Libraries
10+
import "./SafeCast.sol";
1011
import "./SafeDecimalMath.sol";
1112

1213
// Internal references
@@ -47,6 +48,8 @@ interface IIssuerInternalDebtCache {
4748
bool isInvalid,
4849
bool isStale
4950
);
51+
52+
function updateCachedsUSDDebt(int amount) external;
5053
}
5154

5255
// https://docs.synthetix.io/contracts/source/contracts/issuer
@@ -466,7 +469,7 @@ contract Issuer is Owned, MixinSystemSettings, IIssuer {
466469
ISynthRedeemer _synthRedeemer = synthRedeemer();
467470
synths[sUSD].issue(address(_synthRedeemer), amountOfsUSD);
468471
// ensure the debt cache is aware of the new sUSD issued
469-
debtCache().updateCachedSynthDebtWithRate(sUSD, SafeDecimalMath.unit());
472+
debtCache().updateCachedsUSDDebt(SafeCast.toInt256(amountOfsUSD));
470473
_synthRedeemer.deprecate(IERC20(address(Proxyable(address(synthToRemove)).proxy())), rateToRedeem);
471474
}
472475

@@ -666,7 +669,7 @@ contract Issuer is Owned, MixinSystemSettings, IIssuer {
666669
synths[sUSD].issue(from, amount);
667670

668671
// Account for the issued debt in the cache
669-
debtCache().updateCachedSynthDebtWithRate(sUSD, SafeDecimalMath.unit());
672+
debtCache().updateCachedsUSDDebt(SafeCast.toInt256(amount));
670673

671674
// Store their locked SNX amount to determine their fee % for the period
672675
_appendAccountIssuanceRecord(from);
@@ -692,7 +695,7 @@ contract Issuer is Owned, MixinSystemSettings, IIssuer {
692695
synths[sUSD].burn(burnAccount, amountBurnt);
693696

694697
// Account for the burnt debt in the cache.
695-
debtCache().updateCachedSynthDebtWithRate(sUSD, SafeDecimalMath.unit());
698+
debtCache().updateCachedsUSDDebt(-SafeCast.toInt256(amountBurnt));
696699

697700
// Store their debtRatio against a fee period to determine their fee/rewards % for the period
698701
_appendAccountIssuanceRecord(debtAccount);

contracts/SafeCast.sol

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.5.16;
4+
5+
/**
6+
* @dev Wrappers over Solidity's uintXX casting operators with added overflow
7+
* checks.
8+
*
9+
* Downcasting from uint256 in Solidity does not revert on overflow. This can
10+
* easily result in undesired exploitation or bugs, since developers usually
11+
* assume that overflows raise errors. `SafeCast` restores this intuition by
12+
* reverting the transaction when such an operation overflows.
13+
*
14+
* Using this library instead of the unchecked operations eliminates an entire
15+
* class of bugs, so it's recommended to use it always.
16+
*
17+
* Can be combined with {SafeMath} to extend it to smaller types, by performing
18+
* all math on `uint256` and then downcasting.
19+
*/
20+
library SafeCast {
21+
/**
22+
* @dev Returns the downcasted uint128 from uint256, reverting on
23+
* overflow (when the input is greater than largest uint128).
24+
*
25+
* Counterpart to Solidity's `uint128` operator.
26+
*
27+
* Requirements:
28+
*
29+
* - input must fit into 128 bits
30+
*/
31+
function toUint128(uint256 value) internal pure returns (uint128) {
32+
require(value < 2**128, "SafeCast: value doesn't fit in 128 bits");
33+
return uint128(value);
34+
}
35+
36+
/**
37+
* @dev Returns the downcasted uint64 from uint256, reverting on
38+
* overflow (when the input is greater than largest uint64).
39+
*
40+
* Counterpart to Solidity's `uint64` operator.
41+
*
42+
* Requirements:
43+
*
44+
* - input must fit into 64 bits
45+
*/
46+
function toUint64(uint256 value) internal pure returns (uint64) {
47+
require(value < 2**64, "SafeCast: value doesn't fit in 64 bits");
48+
return uint64(value);
49+
}
50+
51+
/**
52+
* @dev Returns the downcasted uint32 from uint256, reverting on
53+
* overflow (when the input is greater than largest uint32).
54+
*
55+
* Counterpart to Solidity's `uint32` operator.
56+
*
57+
* Requirements:
58+
*
59+
* - input must fit into 32 bits
60+
*/
61+
function toUint32(uint256 value) internal pure returns (uint32) {
62+
require(value < 2**32, "SafeCast: value doesn't fit in 32 bits");
63+
return uint32(value);
64+
}
65+
66+
/**
67+
* @dev Returns the downcasted uint16 from uint256, reverting on
68+
* overflow (when the input is greater than largest uint16).
69+
*
70+
* Counterpart to Solidity's `uint16` operator.
71+
*
72+
* Requirements:
73+
*
74+
* - input must fit into 16 bits
75+
*/
76+
function toUint16(uint256 value) internal pure returns (uint16) {
77+
require(value < 2**16, "SafeCast: value doesn't fit in 16 bits");
78+
return uint16(value);
79+
}
80+
81+
/**
82+
* @dev Returns the downcasted uint8 from uint256, reverting on
83+
* overflow (when the input is greater than largest uint8).
84+
*
85+
* Counterpart to Solidity's `uint8` operator.
86+
*
87+
* Requirements:
88+
*
89+
* - input must fit into 8 bits.
90+
*/
91+
function toUint8(uint256 value) internal pure returns (uint8) {
92+
require(value < 2**8, "SafeCast: value doesn't fit in 8 bits");
93+
return uint8(value);
94+
}
95+
96+
/**
97+
* @dev Converts a signed int256 into an unsigned uint256.
98+
*
99+
* Requirements:
100+
*
101+
* - input must be greater than or equal to 0.
102+
*/
103+
function toUint256(int256 value) internal pure returns (uint256) {
104+
require(value >= 0, "SafeCast: value must be positive");
105+
return uint256(value);
106+
}
107+
108+
/**
109+
* @dev Converts an unsigned uint256 into a signed int256.
110+
*
111+
* Requirements:
112+
*
113+
* - input must be less than or equal to maxInt256.
114+
*/
115+
function toInt256(uint256 value) internal pure returns (int256) {
116+
require(value < 2**255, "SafeCast: value doesn't fit in an int256");
117+
return int256(value);
118+
}
119+
}

contracts/SafeDecimalMath.sol

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,19 @@ library SafeDecimalMath {
189189
function floorsub(uint a, uint b) internal pure returns (uint) {
190190
return b >= a ? 0 : a - b;
191191
}
192+
193+
/* ---------- Utilities ---------- */
194+
/*
195+
* Absolute value of the input, returned as a signed number.
196+
*/
197+
function signedAbs(int x) internal pure returns (int) {
198+
return x < 0 ? -x : x;
199+
}
200+
201+
/*
202+
* Absolute value of the input, returned as an unsigned number.
203+
*/
204+
function abs(int x) internal pure returns (uint) {
205+
return uint(signedAbs(x));
206+
}
192207
}

contracts/interfaces/IDebtCache.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,6 @@ interface IDebtCache {
5151
function purgeCachedSynthDebt(bytes32 currencyKey) external;
5252

5353
function takeDebtSnapshot() external;
54+
55+
function updateCachedsUSDDebt(int amount) external;
5456
}

publish/releases.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,11 @@
373373
"layer": "both",
374374
"released": "both",
375375
"sources": ["Exchanger", "Issuer", "SynthRedeemer"]
376+
},
377+
{
378+
"sip": 187,
379+
"layer": "both",
380+
"sources": ["DebtCache", "Issuer"]
376381
}
377382
],
378383
"releases": [

publish/src/commands/deploy/configure-loans.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -225,15 +225,17 @@ module.exports = async ({
225225
comment: 'Set the max amount of debt in the CollateralManager',
226226
});
227227

228-
await runStep({
229-
contract: 'CollateralManager',
230-
target: CollateralManager,
231-
read: 'maxSkewRate',
232-
expected: input => input !== '0', // only change if zero
233-
write: 'setMaxSkewRate',
234-
writeArg: [collateralManagerDefaults['MAX_SKEW_RATE']],
235-
comment: 'Set the max skew rate in the CollateralManager',
236-
});
228+
if (CollateralManager.maxSkewRate) {
229+
await runStep({
230+
contract: 'CollateralManager',
231+
target: CollateralManager,
232+
read: 'maxSkewRate',
233+
expected: input => input !== '0', // only change if zero
234+
write: 'setMaxSkewRate',
235+
writeArg: [collateralManagerDefaults['MAX_SKEW_RATE']],
236+
comment: 'Set the max skew rate in the CollateralManager',
237+
});
238+
}
237239

238240
await runStep({
239241
contract: 'CollateralManager',

0 commit comments

Comments
 (0)