Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 31 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,31 @@ jobs:
- attach_workspace:
at: .
- run: npx hardhat compile --optimizer --fail-oversize
job-fork-tests-ovm:
working_directory: ~/repo
docker:
- image: synthetixio/docker-node:16.13-ubuntu
auth:
username: $DOCKERHUB_USERNAME
password: $DOCKERHUB_TOKEN
steps:
- checkout
- attach_workspace:
at: .
- run:
command: npm run fork:ovm
background: true
- cmd-wait-for-port:
port: 8545
- run:
name: Run integration tests on l2
command: |
NEW_CONTRACTS=$(node bin.js sips --layer=base --unreleased --with-sources)
if [ -z "$NEW_CONTRACTS" ]; then
npx hardhat test:integration:l2 --use-fork
else
npx hardhat test:integration:l2 --compile --deploy --use-sips --use-fork
fi;
job-fork-tests:
working_directory: ~/repo
docker:
Expand All @@ -57,15 +82,15 @@ jobs:
command: npm run fork:mainnet
background: true
- cmd-wait-for-port:
port: 8545
port: 9545
- run:
name: Run integration tests on l1
command: |
NEW_CONTRACTS=$(node bin.js sips --layer=base --unreleased --with-sources)
if [ -z "$NEW_CONTRACTS" ]; then
npx hardhat test:integration:l1 --use-fork
npx hardhat test:integration:l1 --use-fork --provider-port 9545
else
npx hardhat test:integration:l1 --compile --deploy --use-sips --use-fork
npx hardhat test:integration:l1 --compile --deploy --use-sips --use-fork --provider-port 9545
fi;
job-integration-tests:
working_directory: ~/repo
Expand Down Expand Up @@ -406,6 +431,9 @@ workflows:
- job-fork-tests:
requires:
- job-prepare
- job-fork-tests-ovm:
requires:
- job-prepare
- job-simulate-release:
requires:
- job-prepare
Expand Down
21 changes: 21 additions & 0 deletions .circleci/src/jobs/job-fork-tests-ovm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Starts a fork of OVM, deploys the latest release, and runs L2 integration tests
{{> job-header-node.yml}}
steps:
- checkout
- attach_workspace:
at: .
- run:
command: npm run fork:ovm
background: true
- cmd-wait-for-port:
port: 8545
- run:
name: Run integration tests on l2
command: |
# Only compile and deploy when there are new contracts
NEW_CONTRACTS=$(node bin.js sips --layer=base --unreleased --with-sources)
if [ -z "$NEW_CONTRACTS" ]; then
npx hardhat test:integration:l2 --use-fork
else
npx hardhat test:integration:l2 --compile --deploy --use-sips --use-fork
fi;
6 changes: 3 additions & 3 deletions .circleci/src/jobs/job-fork-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ steps:
command: npm run fork:mainnet
background: true
- cmd-wait-for-port:
port: 8545
port: 9545
- run:
name: Run integration tests on l1
command: |
# Only compile and deploy when there are new contracts
NEW_CONTRACTS=$(node bin.js sips --layer=base --unreleased --with-sources)
if [ -z "$NEW_CONTRACTS" ]; then
npx hardhat test:integration:l1 --use-fork
npx hardhat test:integration:l1 --use-fork --provider-port 9545
else
npx hardhat test:integration:l1 --compile --deploy --use-sips --use-fork
npx hardhat test:integration:l1 --compile --deploy --use-sips --use-fork --provider-port 9545
fi;
2 changes: 2 additions & 0 deletions .circleci/src/workflows/workflow-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- job-fork-tests:
{{> require-prepare.yml}}
- job-fork-tests-ovm:
{{> require-prepare.yml}}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Simulate release on fork & test
Expand Down
2 changes: 1 addition & 1 deletion contracts/ExchangeCircuitBreaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ contract ExchangeCircuitBreaker is Owned, MixinSystemSettings, IExchangeCircuitB
}

// if no last exchange for this synth, then we need to look up last 3 rates (+1 for current rate)
(uint[] memory rates, ) = _exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, 4);
(uint[] memory rates, ) = _exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, 4, 0);

// start at index 1 to ignore current rate
for (uint i = 1; i < rates.length; i++) {
Expand Down
99 changes: 76 additions & 23 deletions contracts/ExchangeRates.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,25 +117,34 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
return _getCurrentRoundId(currencyKey);
}

function effectiveValueAtRound(
function effectiveValueAndRatesAtRound(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
uint roundIdForSrc,
uint roundIdForDest
) external view returns (uint value) {
)
external
view
returns (
uint value,
uint sourceRate,
uint destinationRate
)
{
(sourceRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc);
// If there's no change in the currency, then just return the amount they gave us
if (sourceCurrencyKey == destinationCurrencyKey) return sourceAmount;

(uint srcRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc);
(uint destRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest);
if (destRate == 0) {
// prevent divide-by 0 error (this can happen when roundIDs jump epochs due
// to aggregator upgrades)
return 0;
if (sourceCurrencyKey == destinationCurrencyKey) {
destinationRate = sourceRate;
value = sourceAmount;
} else {
(destinationRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest);
// prevent divide-by 0 error (this happens if the dest is not a valid rate)
if (destinationRate > 0) {
// Calculate the effective value by going from source -> USD -> destination
value = sourceAmount.multiplyDecimalRound(sourceRate).divideDecimalRound(destinationRate);
}
}
// Calculate the effective value by going from source -> USD -> destination
value = sourceAmount.multiplyDecimalRound(srcRate).divideDecimalRound(destRate);
}

function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time) {
Expand Down Expand Up @@ -202,15 +211,20 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
return _getRateAndUpdatedTime(currencyKey).rate;
}

function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds)
external
view
returns (uint[] memory rates, uint[] memory times)
{
/// @notice getting N rounds of rates for a currency at a specific round
/// @param currencyKey the currency key
/// @param numRounds the number of rounds to get
/// @param roundId the round id
/// @return a list of rates and a list of times
function ratesAndUpdatedTimeForCurrencyLastNRounds(
bytes32 currencyKey,
uint numRounds,
uint roundId
) external view returns (uint[] memory rates, uint[] memory times) {
rates = new uint[](numRounds);
times = new uint[](numRounds);

uint roundId = _getCurrentRoundId(currencyKey);
roundId = roundId > 0 ? roundId : _getCurrentRoundId(currencyKey);
for (uint i = 0; i < numRounds; i++) {
// fetch the rate and treat is as current, so inverse limits if frozen will always be applied
// regardless of current rate
Expand Down Expand Up @@ -299,6 +313,30 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
return false;
}

/// this method checks whether any rate is:
/// 1. flagged
/// 2. stale with respect to current time (now)
function anyRateIsInvalidAtRound(bytes32[] calldata currencyKeys, uint[] calldata roundIds)
external
view
returns (bool)
{
// Loop through each key and check whether the data point is stale.

require(roundIds.length == currencyKeys.length, "roundIds must be the same length as currencyKeys");

uint256 _rateStalePeriod = getRateStalePeriod();
bool[] memory flagList = getFlagsForRates(currencyKeys);

for (uint i = 0; i < currencyKeys.length; i++) {
if (flagList[i] || _rateIsStaleAtRound(currencyKeys[i], roundIds[i], _rateStalePeriod)) {
return true;
}
}

return false;
}

function synthTooVolatileForAtomicExchange(bytes32) external view returns (bool) {
_notImplemented();
}
Expand Down Expand Up @@ -379,7 +417,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {

function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) {
if (currencyKey == sUSD) {
return 0; // no roundIds for sUSD
return 0;
}
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
if (aggregator != AggregatorV2V3Interface(0)) {
Expand All @@ -395,7 +433,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
return (SafeDecimalMath.unit(), 0);
} else {
AggregatorV2V3Interface aggregator = aggregators[currencyKey];

if (aggregator != AggregatorV2V3Interface(0)) {
// this view from the aggregator is the most gas efficient but it can throw when there's no data,
// so let's call it low-level to suppress any reverts
Expand Down Expand Up @@ -450,18 +487,34 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {

function _rateIsStale(bytes32 currencyKey, uint _rateStalePeriod) internal view returns (bool) {
// sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime)
if (currencyKey == sUSD) return false;

if (currencyKey == sUSD) {
return false;
}
return _rateIsStaleWithTime(_rateStalePeriod, _getUpdatedTime(currencyKey));
}

function _rateIsStaleAtRound(
bytes32 currencyKey,
uint roundId,
uint _rateStalePeriod
) internal view returns (bool) {
// sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime)
if (currencyKey == sUSD) {
return false;
}
(, uint time) = _getRateAndTimestampAtRound(currencyKey, roundId);
return _rateIsStaleWithTime(_rateStalePeriod, time);
}

function _rateIsStaleWithTime(uint _rateStalePeriod, uint _time) internal view returns (bool) {
return _time.add(_rateStalePeriod) < now;
}

function _rateIsFlagged(bytes32 currencyKey, FlagsInterface flags) internal view returns (bool) {
// sUSD is a special case and is never invalid
if (currencyKey == sUSD) return false;
if (currencyKey == sUSD) {
return false;
}
address aggregator = address(aggregators[currencyKey]);
// when no aggregator or when the flags haven't been setup
if (aggregator == address(0) || flags == FlagsInterface(0)) {
Expand Down
Loading