Skip to content

Commit 1b935f1

Browse files
authored
Muhlifain release v2.76.1 (Synthetixio#1871)
1 parent 63f9fdb commit 1b935f1

File tree

82 files changed

+5236
-36224
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+5236
-36224
lines changed

bin.js

+79-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,85 @@ program
5656
.description('Decode a data payload from a Synthetix contract')
5757
.option('-n, --network <value>', 'The network to use', x => x.toLowerCase(), 'mainnet')
5858
.option('-z, --use-ovm', 'Target deployment for the OVM (Optimism).')
59-
.action(async (data, target, { network, useOvm }) => {
60-
console.log(util.inspect(decode({ network, data, target, useOvm }), false, null, true));
59+
.option('-m, --decode-migration', 'Decodes a migration contract execution call')
60+
.action(async (data, target, { network, useOvm, decodeMigration }) => {
61+
console.log(
62+
util.inspect(decode({ network, data, target, useOvm, decodeMigration }), false, null, true)
63+
);
64+
});
65+
66+
program
67+
.command('decode-multi-send <txsdata> [target]')
68+
.description('Decode a data payload from a gnosis multi-send staged to Synthetix contracts')
69+
.option('-n, --network <value>', 'The network to use', x => x.toLowerCase(), 'mainnet')
70+
.option('-z, --use-ovm', 'Target deployment for the OVM (Optimism).')
71+
.option('-m, --decode-migration', 'Decodes a migration contract execution call')
72+
.action(async (txsdata, target, { network, useOvm, decodeMigration }) => {
73+
if (txsdata.length <= 2) {
74+
console.log('data too short');
75+
}
76+
77+
const splitByLen = (s, len) => [s.slice(0, len), s.slice(len)];
78+
79+
const cleanMultiSendRawData = raw => {
80+
let parts = splitByLen(raw, 8);
81+
if (parts[0] === '8d80ff0a') {
82+
// is multisend raw data
83+
// value
84+
parts = splitByLen(parts[1], 64);
85+
// length
86+
parts = splitByLen(parts[1], 64);
87+
const dataLen = parts[0];
88+
const dataLenDecimal = parseInt(dataLen, 16);
89+
parts = splitByLen(parts[1], dataLenDecimal * 2);
90+
return parts[0];
91+
} else {
92+
return raw;
93+
}
94+
};
95+
96+
const cleanData = txsdata.toLowerCase().startsWith('0x')
97+
? txsdata.slice(2).toLowerCase()
98+
: txsdata.toLowerCase();
99+
100+
let parts = splitByLen(cleanMultiSendRawData(cleanData), 0);
101+
let index = 1;
102+
const decodedTransactions = [];
103+
while (parts[1].length > 20) {
104+
// operation type
105+
parts = splitByLen(parts[1], 2);
106+
const operationType = parts[0] === '00' ? 'Call' : 'DelegateCall';
107+
108+
// destination
109+
parts = splitByLen(parts[1], 40);
110+
const destAddress = '0x' + parts[0];
111+
112+
// value
113+
parts = splitByLen(parts[1], 64);
114+
const txValue = parts[0];
115+
const valueDecimal = parseInt(txValue, 16);
116+
117+
// data Len
118+
parts = splitByLen(parts[1], 64);
119+
const dataLen = parts[0];
120+
const dataLenDecimal = parseInt(dataLen, 16);
121+
122+
// data
123+
parts = splitByLen(parts[1], dataLenDecimal * 2);
124+
const data = ('00' + dataLenDecimal.toString(16)).slice(0, 2) + parts[0];
125+
126+
decodedTransactions.push({
127+
index,
128+
destAddress,
129+
operationType,
130+
value: valueDecimal,
131+
decoded: decode({ network, data, target, useOvm, decodeMigration }),
132+
});
133+
134+
index++;
135+
}
136+
137+
console.log(util.inspect(decodedTransactions, false, null, true));
61138
});
62139

63140
program

cannonfile.optimism.toml

+2
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ depends = ["run.synthetix"]
107107
[invoke.set_ownership]
108108
target = [
109109
"AddressResolver",
110+
"CircuitBreaker",
110111
"CollateralManager",
111112
"CollateralManagerState",
112113
"CollateralShort",
@@ -161,6 +162,7 @@ depends = ["run.synthetix"]
161162
[invoke.accept_ownership]
162163
target = [
163164
"AddressResolver",
165+
"CircuitBreaker",
164166
"CollateralManager",
165167
"CollateralManagerState",
166168
"CollateralShort",

cannonfile.toml

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ depends = ["run.synthetix"]
6262
[invoke.set_ownership]
6363
target = [
6464
"AddressResolver",
65+
"CircuitBreaker",
6566
"CollateralErc20",
6667
"CollateralEth",
6768
"CollateralManager",
@@ -121,6 +122,7 @@ depends = ["run.synthetix"]
121122
[invoke.accept_ownership]
122123
target = [
123124
"AddressResolver",
125+
"CircuitBreaker",
124126
"CollateralErc20",
125127
"CollateralEth",
126128
"CollateralManager",

contracts/CircuitBreaker.sol

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
pragma solidity ^0.5.16;
2+
3+
// Inheritance
4+
import "./Owned.sol";
5+
import "./MixinResolver.sol";
6+
import "./MixinSystemSettings.sol";
7+
import "./interfaces/ICircuitBreaker.sol";
8+
9+
// Libraries
10+
import "./SafeDecimalMath.sol";
11+
12+
// Internal references
13+
import "./interfaces/ISynth.sol";
14+
import "./interfaces/ISystemStatus.sol";
15+
import "./interfaces/IExchangeRates.sol";
16+
import "./Proxyable.sol";
17+
18+
// Chainlink
19+
import "@chainlink/contracts-0.0.10/src/v0.5/interfaces/AggregatorV2V3Interface.sol";
20+
21+
/**
22+
* Compares current exchange rate to previous, and suspends a synth if the
23+
* difference is outside of deviation bounds.
24+
* Stores last "good" rate for each synth on each invocation.
25+
* Inteded use is to use in combination with ExchangeRates on mutative exchange-like
26+
* methods.
27+
* Suspend functionality is public, resume functionality is controlled by owner.
28+
*
29+
* https://docs.synthetix.io/contracts/source/contracts/CircuitBreaker
30+
*/
31+
contract CircuitBreaker is Owned, MixinSystemSettings, ICircuitBreaker {
32+
using SafeMath for uint;
33+
using SafeDecimalMath for uint;
34+
35+
bytes32 public constant CONTRACT_NAME = "CircuitBreaker";
36+
37+
// is internal to have lastValue getter in interface in solidity v0.5
38+
// TODO: after upgrading solidity, switch to just public lastValue instead
39+
// of maintaining this internal one
40+
mapping(address => uint) internal _lastValue;
41+
mapping(address => bool) internal _circuitBroken;
42+
43+
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
44+
45+
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
46+
bytes32 private constant CONTRACT_ISSUER = "Issuer";
47+
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
48+
49+
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
50+
51+
/* ========== VIEWS ========== */
52+
53+
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
54+
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
55+
bytes32[] memory newAddresses = new bytes32[](3);
56+
newAddresses[0] = CONTRACT_SYSTEMSTATUS;
57+
newAddresses[1] = CONTRACT_ISSUER;
58+
newAddresses[2] = CONTRACT_EXRATES;
59+
addresses = combineArrays(existingAddresses, newAddresses);
60+
}
61+
62+
// Returns whether or not a rate would be come invalid
63+
// ignores systemStatus check
64+
function isInvalid(address oracleAddress, uint value) external view returns (bool) {
65+
return _circuitBroken[oracleAddress] || _isRateOutOfBounds(oracleAddress, value) || value == 0;
66+
}
67+
68+
function isDeviationAboveThreshold(uint base, uint comparison) external view returns (bool) {
69+
return _isDeviationAboveThreshold(base, comparison);
70+
}
71+
72+
function priceDeviationThresholdFactor() external view returns (uint) {
73+
return getPriceDeviationThresholdFactor();
74+
}
75+
76+
function lastValue(address oracleAddress) external view returns (uint) {
77+
return _lastValue[oracleAddress];
78+
}
79+
80+
function circuitBroken(address oracleAddress) external view returns (bool) {
81+
return _circuitBroken[oracleAddress];
82+
}
83+
84+
/* ========== Internal views ========== */
85+
86+
function systemStatus() internal view returns (ISystemStatus) {
87+
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
88+
}
89+
90+
/* ========== Mutating ========== */
91+
92+
/**
93+
* Checks rate deviation from previous and its "invalid" oracle state (stale rate, of flagged by oracle).
94+
* If its valid, set the `circuitBoken` flag and return false. Continue storing price updates as normal.
95+
* Also, checks that system is not suspended currently, if it is - doesn't perform any checks, and
96+
* returns last rate and the current broken state, to prevent synths suspensions during maintenance.
97+
*/
98+
function probeCircuitBreaker(address oracleAddress, uint value) external onlyProbers returns (bool circuitBroken) {
99+
require(oracleAddress != address(0), "Oracle address is 0");
100+
101+
// these conditional statements are ordered for short circuit (heh) efficiency to reduce gas usage
102+
// in the usual case of no circuit broken.
103+
if (
104+
// cases where the new price should be triggering a circuit break
105+
(value == 0 || _isRateOutOfBounds(oracleAddress, value)) &&
106+
// other necessary states in order to break
107+
!_circuitBroken[oracleAddress] &&
108+
!systemStatus().systemSuspended()
109+
) {
110+
_circuitBroken[oracleAddress] = true;
111+
emit CircuitBroken(oracleAddress, _lastValue[oracleAddress], value);
112+
}
113+
114+
_lastValue[oracleAddress] = value;
115+
116+
return _circuitBroken[oracleAddress];
117+
}
118+
119+
/**
120+
* SIP-139
121+
* resets the stored value for _lastValue for multiple currencies to the latest rate
122+
* can be used to enable synths after a broken circuit happenned
123+
* doesn't check deviations here, so believes that owner knows better
124+
* emits LastRateOverridden
125+
*/
126+
function resetLastValue(address[] calldata oracleAddresses, uint[] calldata values) external onlyOwner {
127+
for (uint i = 0; i < oracleAddresses.length; i++) {
128+
require(oracleAddresses[i] != address(0), "Oracle address is 0");
129+
emit LastValueOverridden(oracleAddresses[i], _lastValue[oracleAddresses[i]], values[i]);
130+
_lastValue[oracleAddresses[i]] = values[i];
131+
_circuitBroken[oracleAddresses[i]] = false;
132+
}
133+
}
134+
135+
/* ========== INTERNAL FUNCTIONS ========== */
136+
137+
function _isDeviationAboveThreshold(uint base, uint comparison) internal view returns (bool) {
138+
if (base == 0 || comparison == 0) {
139+
return true;
140+
}
141+
142+
uint factor;
143+
if (comparison > base) {
144+
factor = comparison.divideDecimal(base);
145+
} else {
146+
factor = base.divideDecimal(comparison);
147+
}
148+
149+
return factor >= getPriceDeviationThresholdFactor();
150+
}
151+
152+
/**
153+
* Rate is invalid if it is outside of deviation bounds relative to previous non-zero rate
154+
*/
155+
function _isRateOutOfBounds(address oracleAddress, uint current) internal view returns (bool) {
156+
uint last = _lastValue[oracleAddress];
157+
158+
// `last == 0` indicates unset/unpopulated oracle. If we dont have any data on the previous oracle price,
159+
// we should skip the deviation check and allow it to be populated.
160+
if (last > 0) {
161+
return _isDeviationAboveThreshold(last, current);
162+
}
163+
164+
return false;
165+
}
166+
167+
// ========== MODIFIERS =======
168+
169+
modifier onlyProbers() {
170+
require(
171+
msg.sender == requireAndGetAddress(CONTRACT_ISSUER) || msg.sender == requireAndGetAddress(CONTRACT_EXRATES),
172+
"Only internal contracts can call this function"
173+
);
174+
175+
_;
176+
}
177+
178+
// ========== EVENTS ==========
179+
180+
// @notice signals that a the "last value" was overridden by one of the admin methods
181+
// with a value that didn't come directly from the ExchangeRates.getRates methods
182+
event LastValueOverridden(address indexed oracleAddress, uint256 previousValue, uint256 newValue);
183+
184+
// @notice signals that the circuit was broken
185+
event CircuitBroken(address indexed oracleAddress, uint256 previousValue, uint256 newValue);
186+
}

0 commit comments

Comments
 (0)