Skip to content

Commit

Permalink
Update contract (#2314)
Browse files Browse the repository at this point in the history
* feat: update contract

* fix: update based on feedback

* fix: separated error cases

* fix: add function based on feedback

* fix: update based on feedback

* Review of superseded feature (#2315)

* chore: superseeded review

* fix: fix test

---------

Co-authored-by: nick <[email protected]>

* fix: remove unnecessary

* fix: rename, update test

* fix: update visibility

* fix: fix typo

* fix: typo

---------

Co-authored-by: Martin Kersner <[email protected]>
  • Loading branch information
nick-bisonai and martinkersner authored Nov 12, 2024
1 parent 1625938 commit 0b8ff1f
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 53 deletions.
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[]));
}
}

0 comments on commit 0b8ff1f

Please sign in to comment.