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

Update contract #2314

Merged
merged 11 commits into from
Nov 12, 2024
116 changes: 109 additions & 7 deletions contracts/v0.2/src/SubmissionProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ contract SubmissionProxy is Ownable {

mapping(address => OracleInfo) public whitelist;
mapping(bytes32 feedHash => IFeed feed) public feeds;
mapping(bytes32 feedHash => uint8 threshold) thresholds;
mapping(bytes32 feedHash => uint256 lastSubmissionTime) lastSubmissionTimes;
mapping(bytes32 feedHash => uint8 threshold) public thresholds;
mapping(bytes32 feedHash => uint256 lastSubmissionTime) public lastSubmissionTimes;

event OracleAdded(address oracle, uint256 expirationTime);
event OracleRemoved(address oracle);
Expand All @@ -61,7 +61,8 @@ contract SubmissionProxy is Ownable {
error InvalidSignatureLength();
error InvalidFeed();
error ZeroAddressGiven();
error AnswerTooOld();
error AnswerOutdated();
error AnswerSuperseded();
error InvalidProofFormat();
error InvalidProof();
error FeedHashNotFound();
Expand Down Expand Up @@ -396,6 +397,15 @@ contract SubmissionProxy is Ownable {
}
}

/**
* @notice Submit a batch of answers to multiple feeds. If any
* of the answers do not meet required conditions the
* whole batch is reverted.
* @param _feedHashes The hashes of the feeds
* @param _answers The submissions
* @param _timestamps The unixmilli timestamps of the proofs
* @param _proofs The proofs
*/
function submitStrict(
bytes32[] calldata _feedHashes,
int256[] calldata _answers,
Expand All @@ -411,13 +421,105 @@ contract SubmissionProxy is Ownable {

uint256 feedsLength_ = _feedHashes.length;
for (uint256 i = 0; i < feedsLength_; i++) {
submitSingle(_feedHashes[i], _answers[i], _timestamps[i], _proofs[i]);
submitStrictSingle(_feedHashes[i], _answers[i], _timestamps[i], _proofs[i]);
}
}

/**
* @notice Submit a single answer to a feed. The answer is
* reverted if any of the required conditions are not met.
* @param _feedHash The hash of the feed
* @param _answer The submission
* @param _timestamp The unixmilli timestamp of the proof
* @param _proof The proof
*/
function submitStrictSingle(bytes32 _feedHash, int256 _answer, uint256 _timestamp, bytes calldata _proof) public {
if (_timestamp <= (block.timestamp - dataFreshness) * 1000) {
// answer is not fresh -> do not submit!
revert AnswerOutdated();
}

if (lastSubmissionTimes[_feedHash] >= _timestamp) {
// answer is superseded -> do not submit!
revert AnswerSuperseded();
}

(bytes[] memory proofs_, bool success_) = splitProofs(_proof);
if (!success_) {
// splitting proofs failed -> do not submit!
revert InvalidProofFormat();
}

if (address(feeds[_feedHash]) == address(0)) {
// feedHash not registered -> do not submit!
revert FeedHashNotFound();
}

if (keccak256(abi.encodePacked(feeds[_feedHash].name())) != _feedHash) {
// feedHash not matching with registered feed -> do not submit!
revert InvalidFeedHash();
}

bytes32 message_ = keccak256(abi.encodePacked(_answer, _timestamp, _feedHash));
if (validateProof(_feedHash, message_, proofs_)) {
feeds[_feedHash].submit(_answer);
lastSubmissionTimes[_feedHash] = _timestamp;
} else {
revert InvalidProof();
}
}

/**
* @notice Submit a batch of answers to multiple feeds. The answers are
* ignored if they have been superseded. If any of the answers do not
* meet the rest of required conditions the whole batch is reverted.
* @param _feedHashes The hashes of the feeds
* @param _answers The submissions
* @param _timestamps The unixmilli timestamps of the proofs
* @param _proofs The proofs
*/
function submitWithoutSupersedValidation(
bytes32[] calldata _feedHashes,
int256[] calldata _answers,
uint256[] calldata _timestamps,
bytes[] calldata _proofs
) external {
if (
_feedHashes.length != _answers.length || _answers.length != _proofs.length
|| _proofs.length != _timestamps.length || _feedHashes.length > maxSubmission
) {
revert InvalidSubmissionLength();
}

uint256 feedsLength_ = _feedHashes.length;
for (uint256 i = 0; i < feedsLength_; i++) {
submitSingleWithoutSupersedValidation(_feedHashes[i], _answers[i], _timestamps[i], _proofs[i]);
}
}

function submitSingle(bytes32 _feedHash, int256 _answer, uint256 _timestamp, bytes calldata _proof) public {
if (_timestamp <= (block.timestamp - dataFreshness) * 1000 || lastSubmissionTimes[_feedHash] >= _timestamp) {
revert AnswerTooOld();
/**
* @notice Submit a single submission to a feed. The submission is
* ignored if it has been superseded. If the submission does not
* meet the rest of required conditions the submission is reverted.
* @param _feedHash The hash of the feed
* @param _answer The submission
* @param _timestamp The unixmilli timestamp of the proof
* @param _proof The proof
*/
function submitSingleWithoutSupersedValidation(
bytes32 _feedHash,
int256 _answer,
uint256 _timestamp,
bytes calldata _proof
) public {
if (lastSubmissionTimes[_feedHash] >= _timestamp) {
// answer is superseded -> skip submission
return;
}

if (_timestamp <= (block.timestamp - dataFreshness) * 1000) {
// answer is not fresh -> do not submit!
revert AnswerOutdated();
}

(bytes[] memory proofs_, bool success_) = splitProofs(_proof);
Expand Down
100 changes: 100 additions & 0 deletions contracts/v0.2/test/SubmissionProxy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,106 @@ contract SubmissionProxyTest is Test {
IFeed(feeds_[0]).latestRoundData();
}

function test_SubmitStrict() public {
(address alice_, uint256 aliceSk_) = makeAddrAndKey("alice");
(address bob_, uint256 bobSk_) = makeAddrAndKey("bob");
(address celine_, uint256 celineSk_) = makeAddrAndKey("celine");
(, uint256 dummySk_) = makeAddrAndKey("dummy");

submissionProxy.addOracle(alice_);
submissionProxy.addOracle(bob_);
submissionProxy.addOracle(celine_);

uint256 numOracles_ = 1;
int256 submissionValue_ = 10;
(
bytes32[] memory feedHashes_,
int256[] memory submissions_,
bytes[] memory proofs_,
uint256[] memory timestamps_,
address[] memory feeds_
) = prepareFeedsSubmissions(numOracles_, submissionValue_, dummySk_);
bytes32 hash_ = keccak256(abi.encodePacked(submissions_[0], timestamps_[0], feedHashes_[0]));
proofs_[0] =
abi.encodePacked(createProof(aliceSk_, hash_), createProof(bobSk_, hash_), createProof(celineSk_, hash_));

submissionProxy.setProofThreshold(feedHashes_[0], 100); // 100 % of the oracles must submit a valid proof
submissionProxy.submitStrict(feedHashes_, submissions_, timestamps_, proofs_);

// don't raise `NoDataPresent`
IFeed(feeds_[0]).latestRoundData();
}

function test_submitWithoutSupersedValidation() public {
(address alice_, uint256 aliceSk_) = makeAddrAndKey("alice");
(address bob_, uint256 bobSk_) = makeAddrAndKey("bob");
(address celine_, uint256 celineSk_) = makeAddrAndKey("celine");
(, uint256 dummySk_) = makeAddrAndKey("dummy");

submissionProxy.addOracle(alice_);
submissionProxy.addOracle(bob_);
submissionProxy.addOracle(celine_);

uint256 numOracles_ = 1;
int256 submissionValue_ = 10;
(
bytes32[] memory feedHashes_,
int256[] memory submissions_,
bytes[] memory proofs_,
uint256[] memory timestamps_,
address[] memory feeds_
) = prepareFeedsSubmissions(numOracles_, submissionValue_, dummySk_);
bytes32 hash_ = keccak256(abi.encodePacked(submissions_[0], timestamps_[0], feedHashes_[0]));
proofs_[0] =
abi.encodePacked(createProof(aliceSk_, hash_), createProof(bobSk_, hash_), createProof(celineSk_, hash_));

submissionProxy.setProofThreshold(feedHashes_[0], 100); // 100 % of the oracles must submit a valid proof
submissionProxy.submitWithoutSupersedValidation(feedHashes_, submissions_, timestamps_, proofs_);

// don't raise `NoDataPresent`
IFeed(feeds_[0]).latestRoundData();
}

function test_submitWithoutSupersedValidationIgnoreLateSubmission() public {


(address alice_, uint256 aliceSk_) = makeAddrAndKey("alice");
(address bob_, uint256 bobSk_) = makeAddrAndKey("bob");
(address celine_, uint256 celineSk_) = makeAddrAndKey("celine");
(, uint256 dummySk_) = makeAddrAndKey("dummy");

submissionProxy.addOracle(alice_);
submissionProxy.addOracle(bob_);
submissionProxy.addOracle(celine_);

uint256 numOracles_ = 1;
int256 submissionValue_ = 10;

(
bytes32[] memory feedHashes_,
int256[] memory submissions_,
bytes[] memory proofs_,
uint256[] memory timestamps_,
address[] memory feeds_
) = prepareFeedsSubmissions(numOracles_, submissionValue_, dummySk_);
bytes32 hash_ = keccak256(abi.encodePacked(submissions_[0], timestamps_[0], feedHashes_[0]));
proofs_[0] =
abi.encodePacked(createProof(aliceSk_, hash_), createProof(bobSk_, hash_), createProof(celineSk_, hash_));

submissionProxy.setProofThreshold(feedHashes_[0], 100); // 100 % of the oracles must submit a valid proof
submissionProxy.submitWithoutSupersedValidation(feedHashes_, submissions_, timestamps_, proofs_);

uint256[] memory oldTimestamps = new uint256[](timestamps_.length);
for (uint256 i = 0; i < timestamps_.length; i++) {
oldTimestamps[i] = timestamps_[i] -1;
}
// should not revert
submissionProxy.submitWithoutSupersedValidation(feedHashes_, submissions_, oldTimestamps, proofs_);

// don't raise `NoDataPresent`
IFeed(feeds_[0]).latestRoundData();
}

function prepareFeedsSubmissions(uint256 _numOracles, int256 _submissionValue, uint256 _oracleSk)
private
returns (bytes32[] memory, int256[] memory, bytes[] memory, uint256[] memory, address[] memory)
Expand Down
87 changes: 41 additions & 46 deletions contracts/v0.2/test/SubmitStrict.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.20;

import "forge-std/Vm.sol";
import {Test, console} from "forge-std/Test.sol";
import { stdJson } from "forge-std/StdJson.sol";
import {stdJson} from "forge-std/StdJson.sol";
import {SubmissionProxy} from "../src/SubmissionProxy.sol";
import {FeedRouter} from "../src/FeedRouter.sol";
import "forge-std/console.sol";
Expand All @@ -12,73 +12,68 @@ contract SubmitStrictTest is Test {
using stdJson for string;

function run() public {
string memory json = vm.readFile("submission.json");
string memory json = vm.readFile("submission.json");

string[] memory symbols = getsymbols(json);
bytes32[] memory feedHashes = gethashes(json);
int256[] memory valuesInt = getvalues(json);
uint256[] memory timestampsInt = gettimestamps(json);
bytes[] memory proofs = getproofs(json);
string[] memory symbols = getsymbols(json);
bytes32[] memory feedHashes = gethashes(json);
int256[] memory valuesInt = getvalues(json);
uint256[] memory timestampsInt = gettimestamps(json);
bytes[] memory proofs = getproofs(json);

console.log("submission");
console.logBytes32(feedHashes[0]);
console.logInt(valuesInt[0]);
console.logUint(timestampsInt[0]);
console.logBytes(proofs[0]);
console.log("submission");
console.logBytes32(feedHashes[0]);
console.logInt(valuesInt[0]);
console.logUint(timestampsInt[0]);
console.logBytes(proofs[0]);

SubmissionProxy sp = SubmissionProxy(0x3a251c738e19806A546815eb6065e139A8D65B4b);
sp.submitStrict(
feedHashes,
valuesInt,
timestampsInt,
proofs
);
SubmissionProxy sp = SubmissionProxy(0x3a251c738e19806A546815eb6065e139A8D65B4b);
sp.submitStrict(feedHashes, valuesInt, timestampsInt, proofs);

console.log(block.timestamp);
console.log(block.timestamp);

FeedRouter fr = FeedRouter(0x653078F0D3a230416A59aA6486466470Db0190A2);
(uint64 roundId, int256 answer, uint256 blockTimestamp) = fr.latestRoundData(symbols[0]);
FeedRouter fr = FeedRouter(0x653078F0D3a230416A59aA6486466470Db0190A2);
(uint64 roundId, int256 answer, uint256 blockTimestamp) = fr.latestRoundData(symbols[0]);

console.log("latestRoundData");
console.logUint(roundId);
console.logInt(answer);
console.logUint(blockTimestamp);
console.log("latestRoundData");
console.logUint(roundId);
console.logInt(answer);
console.logUint(blockTimestamp);

require(block.timestamp == blockTimestamp, "Timestamps do not match");
require(block.timestamp == blockTimestamp, "Timestamps do not match");
}

function getsymbols(string memory json) public pure returns (string[] memory) {
bytes memory rawSymbols = json.parseRaw(".symbols");
return abi.decode(rawSymbols, (string[]));
bytes memory rawSymbols = json.parseRaw(".symbols");
return abi.decode(rawSymbols, (string[]));
}

function gethashes(string memory json) public pure returns (bytes32[] memory) {
bytes memory rawFeedHashes = json.parseRaw(".feedHashes");
bytes memory rawFeedHashes = json.parseRaw(".feedHashes");
return abi.decode(rawFeedHashes, (bytes32[]));
}

function getvalues(string memory json) public pure returns (int256[] memory) {
bytes memory rawValues = json.parseRaw(".values");
string[] memory values = abi.decode(rawValues, (string[]));
int256[] memory valuesInt = new int256[](values.length);
for (uint i = 0; i < values.length; i++) {
valuesInt[i] = vm.parseInt(values[i]);
}
return valuesInt;
bytes memory rawValues = json.parseRaw(".values");
string[] memory values = abi.decode(rawValues, (string[]));
int256[] memory valuesInt = new int256[](values.length);
for (uint256 i = 0; i < values.length; i++) {
valuesInt[i] = vm.parseInt(values[i]);
}
return valuesInt;
}

function gettimestamps(string memory json) public pure returns (uint256[] memory) {
bytes memory rawTimestamps = json.parseRaw(".aggregateTimes");
string[] memory timestamps = abi.decode(rawTimestamps, (string[]));
uint256[] memory timestampsInt = new uint256[](timestamps.length);
for (uint i = 0; i < timestamps.length; i++) {
timestampsInt[i] = vm.parseUint(timestamps[i]);
}
return timestampsInt;
bytes memory rawTimestamps = json.parseRaw(".aggregateTimes");
string[] memory timestamps = abi.decode(rawTimestamps, (string[]));
uint256[] memory timestampsInt = new uint256[](timestamps.length);
for (uint256 i = 0; i < timestamps.length; i++) {
timestampsInt[i] = vm.parseUint(timestamps[i]);
}
return timestampsInt;
}

function getproofs(string memory json) public pure returns (bytes[] memory) {
bytes memory rawProofs = json.parseRaw(".proofs");
return abi.decode(rawProofs, (bytes[]));
bytes memory rawProofs = json.parseRaw(".proofs");
return abi.decode(rawProofs, (bytes[]));
}
}
Loading