Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SC-885] OraclePrices library #75

Merged
merged 13 commits into from
Aug 10, 2023
17 changes: 8 additions & 9 deletions .solcover.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
module.exports = {
copyPackages: ['openzeppelin-solidity'],
skipFiles: ['mocks'],
configureYulOptimizer: true,
solcOptimizerDetails: {
yul: true,
yulDetails: {
optimizerSteps:
"dhfoDgvlfnTUtnIf" + // None of these can make stack problems worse
"[" +
"xa[r]EscLM" + // Turn into SSA and simplify
"cCTUtTOntnfDIl" + // Perform structural simplification
"Lcl" + // Simplify again
"Vcl [j]" + // Reverse SSA
"xa[r]EsLM" + // Turn into SSA and simplify
"CTUtTOntnfDIl" + // Perform structural simplification
"Ll" + // Simplify again
"Vl [j]" + // Reverse SSA

// should have good "compilability" property here.

"Tpel" + // Run functional expression inliner
"xa[rl]" + // Prune a bit more in SSA
"xa[r]cL" + // Turn into SSA again and simplify
"xa[r]L" + // Turn into SSA again and simplify
"gvf" + // Run full inliner
"CTUca[r]LSsTFOtfDnca[r]Ilc" + // SSA plus simplify
"CTUa[r]LSsTFOtfDna[r]Il" + // SSA plus simplify
"]" +
"jml[jl] VcTOcl jml",
"jml[jl] VTOl jml : fDnTO",
},
},
skipFiles: ['mocks', 'interfaces'],
}
5 changes: 2 additions & 3 deletions contracts/MultiWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
pragma solidity 0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "./interfaces/IWrapper.sol";

contract MultiWrapper is Ownable {
using SafeMath for uint256;
using EnumerableSet for EnumerableSet.AddressSet;

error WrapperAlreadyAdded();
Expand Down Expand Up @@ -69,7 +68,7 @@ contract MultiWrapper is Ownable {
}
if (!used) {
memWrappedTokens[len] = wrappedToken2;
memRates[len] = rate.mul(rate2).div(1e18);
memRates[len] = Math.mulDiv(rate, rate2, 1e18);
len += 1;
}
} catch { continue; }
Expand Down
127 changes: 39 additions & 88 deletions contracts/OffchainOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@
pragma solidity 0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "./interfaces/IOracle.sol";
import "./interfaces/IWrapper.sol";
import "./MultiWrapper.sol";
import "./libraries/Sqrt.sol";
import "./libraries/OraclePrices.sol";

contract OffchainOracle is Ownable {
using Math for uint256;
using SafeMath for uint256;
using Sqrt for uint256;
using EnumerableSet for EnumerableSet.AddressSet;
using OraclePrices for OraclePrices.Data;

error ArraysLengthMismatch();
error OracleAlreadyAdded();
Expand All @@ -34,9 +32,14 @@ contract OffchainOracle is Ownable {
event ConnectorRemoved(IERC20 connector);
event MultiWrapperUpdated(MultiWrapper multiWrapper);

struct OraclePrice {
uint256 rate;
uint256 weight;
struct GetRateImplParams {
IOracle oracle;
IERC20 srcToken;
uint256 srcTokenRate;
IERC20 dstToken;
uint256 dstTokenRate;
IERC20 connector;
uint256 thresholdFilter;
}

EnumerableSet.AddressSet private _wethOracles;
Expand Down Expand Up @@ -260,22 +263,12 @@ contract OffchainOracle is Ownable {
IERC20[][2] memory allConnectors = _getAllConnectors(customConnectors);

uint256 maxArrLength = wrappedSrcTokens.length * wrappedDstTokens.length * (allConnectors[0].length + allConnectors[1].length) * allOracles.length;
OraclePrice[] memory oraclePrices;
// Memory allocation in assembly to avoid array zeroing
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
oraclePrices := mload(0x40)
mstore(0x40, add(oraclePrices, add(0x20, mul(maxArrLength, 0x40))))
mstore(oraclePrices, maxArrLength)
}

uint256 oracleIndex;
uint256 maxOracleWeight;

OraclePrices.Data memory ratesAndWeights = OraclePrices.init(maxArrLength);
unchecked {
for (uint256 k1 = 0; k1 < wrappedSrcTokens.length; k1++) {
for (uint256 k2 = 0; k2 < wrappedDstTokens.length; k2++) {
if (wrappedSrcTokens[k1] == wrappedDstTokens[k2]) {
return srcRates[k1].mul(dstRates[k2]).div(1e18);
return srcRates[k1] * dstRates[k2] / 1e18;
}
for (uint256 k3 = 0; k3 < 2; k3++) {
for (uint256 j = 0; j < allConnectors[k3].length; j++) {
Expand All @@ -284,38 +277,22 @@ contract OffchainOracle is Ownable {
continue;
}
for (uint256 i = 0; i < allOracles.length; i++) {
(OraclePrice memory oraclePrice) = _getRateImpl(allOracles[i], wrappedSrcTokens[k1], srcRates[k1], wrappedDstTokens[k2], dstRates[k2], connector);
if (oraclePrice.weight > 0) {
oraclePrices[oracleIndex] = oraclePrice;
oracleIndex++;
if (oraclePrice.weight > maxOracleWeight) {
maxOracleWeight = oraclePrice.weight;
}
}
GetRateImplParams memory params = GetRateImplParams({
oracle: allOracles[i],
srcToken: wrappedSrcTokens[k1],
srcTokenRate: srcRates[k1],
dstToken: wrappedDstTokens[k2],
dstTokenRate: dstRates[k2],
connector: connector,
thresholdFilter: thresholdFilter
});
ratesAndWeights.append(_getRateImpl(params));
}
}
}
}
}
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
mstore(oraclePrices, oracleIndex)
}

uint256 totalWeight;
for (uint256 i = 0; i < oraclePrices.length; i++) {
if (oraclePrices[i].weight * 100 < maxOracleWeight * thresholdFilter) {
continue;
}
(bool ok, uint256 weightedRateI) = oraclePrices[i].rate.tryMul(oraclePrices[i].weight);
if (ok) {
(ok, weightedRate) = _tryAdd(weightedRate, weightedRateI);
if (ok) totalWeight += oraclePrices[i].weight;
}
}

if (totalWeight > 0) {
weightedRate = weightedRate / totalWeight;
}
(weightedRate,) = ratesAndWeights.getRateAndWeightWithSafeMath(thresholdFilter);
}
}

Expand Down Expand Up @@ -353,17 +330,7 @@ contract OffchainOracle is Ownable {
IERC20[][2] memory allConnectors = _getAllConnectors(customConnectors);

uint256 maxArrLength = wrappedSrcTokens.length * wrappedDstTokens.length * (allConnectors[0].length + allConnectors[1].length) * (wrappedOracles[0].length + wrappedOracles[1].length);
OraclePrice[] memory oraclePrices;
// Memory allocation in assembly to avoid array zeroing
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
oraclePrices := mload(0x40)
mstore(0x40, add(oraclePrices, mul(maxArrLength, 0x40)))
mstore(oraclePrices, maxArrLength)
}

uint256 oracleIndex;
uint256 maxOracleWeight;

OraclePrices.Data memory ratesAndWeights = OraclePrices.init(maxArrLength);
unchecked {
for (uint256 k1 = 0; k1 < wrappedSrcTokens.length; k1++) {
for (uint256 k2 = 0; k2 < wrappedDstTokens.length; k2++) {
Expand All @@ -377,38 +344,22 @@ contract OffchainOracle is Ownable {
continue;
}
for (uint256 i = 0; i < wrappedOracles[k2].length; i++) {
(OraclePrice memory oraclePrice) = _getRateImpl(IOracle(address(uint160(uint256(wrappedOracles[k2][i])))), wrappedSrcTokens[k1], srcRates[k1], wrappedDstTokens[k2], 1e18, connector);
if (oraclePrice.weight > 0) {
oraclePrices[oracleIndex] = oraclePrice;
oracleIndex++;
if (oraclePrice.weight > maxOracleWeight) {
maxOracleWeight = oraclePrice.weight;
}
}
GetRateImplParams memory params = GetRateImplParams({
oracle: IOracle(address(uint160(uint256(wrappedOracles[k2][i])))),
srcToken: wrappedSrcTokens[k1],
srcTokenRate: srcRates[k1],
dstToken: wrappedDstTokens[k2],
dstTokenRate: 1e18,
connector: connector,
thresholdFilter: thresholdFilter
});
ratesAndWeights.append(_getRateImpl(params));
}
}
}
}
}
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
mstore(oraclePrices, oracleIndex)
}

uint256 totalWeight;
for (uint256 i = 0; i < oracleIndex; i++) {
if (oraclePrices[i].weight < maxOracleWeight * thresholdFilter / 100) {
continue;
}
(bool ok, uint256 weightedRateI) = oraclePrices[i].rate.tryMul(oraclePrices[i].weight);
if (ok) {
(ok, weightedRate) = _tryAdd(weightedRate, weightedRateI);
if (ok) totalWeight += oraclePrices[i].weight;
}
}

if (totalWeight > 0) {
weightedRate = weightedRate / totalWeight;
}
(weightedRate,) = ratesAndWeights.getRateAndWeightWithSafeMath(thresholdFilter);
}
}

Expand All @@ -433,10 +384,10 @@ contract OffchainOracle is Ownable {
allConnectors[1] = customConnectors;
}

function _getRateImpl(IOracle oracle, IERC20 srcToken, uint256 srcTokenRate, IERC20 dstToken, uint256 dstTokenRate, IERC20 connector) private view returns (OraclePrice memory oraclePrice) {
try oracle.getRate(srcToken, dstToken, connector) returns (uint256 rate, uint256 weight) {
uint256 result = _scaledMul([srcTokenRate, rate, dstTokenRate], 1e18);
oraclePrice = OraclePrice(result, result == 0 ? 0 : weight);
function _getRateImpl(GetRateImplParams memory p) private view returns (OraclePrices.OraclePrice memory oraclePrice) {
try p.oracle.getRate(p.srcToken, p.dstToken, p.connector, p.thresholdFilter) returns (uint256 rate, uint256 weight) {
uint256 result = _scaledMul([p.srcTokenRate, rate, p.dstTokenRate], 1e18);
oraclePrice = OraclePrices.OraclePrice(result, result == 0 ? 0 : weight);
} catch {} // solhint-disable-line no-empty-blocks
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ interface IOracle {
error PoolNotFound();
error PoolWithConnectorNotFound();

function getRate(IERC20 srcToken, IERC20 dstToken, IERC20 connector) external view returns (uint256 rate, uint256 weight);
function getRate(IERC20 srcToken, IERC20 dstToken, IERC20 connector, uint256 thresholdFilter) external view returns (uint256 rate, uint256 weight);
}
Loading