Skip to content

Commit

Permalink
updated verification
Browse files Browse the repository at this point in the history
  • Loading branch information
Mike committed Sep 24, 2024
1 parent 0b9c9d4 commit eae4137
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 51 deletions.
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
Submodule openzeppelin-contracts-upgradeable added at 723f8c
82 changes: 44 additions & 38 deletions src/EdgePushOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
pragma solidity ^0.8.25;

import "openzeppelin-contracts/contracts/access/Ownable.sol";
import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";

/**
* @title EdgePushOracle
* @dev A decentralized oracle contract that allows trusted oracles to push price updates
* with multi-signature verification.
*/
contract EdgePushOracle is Ownable {
using ECDSA for bytes32;

// ============ Structs ============

struct RoundData {
Expand Down Expand Up @@ -77,7 +74,7 @@ contract EdgePushOracle is Ownable {
break;
}
}
(oracle);
emit OracleRemoved(oracle);
}

// ============ Update Posting Function ============
Expand All @@ -89,8 +86,7 @@ contract EdgePushOracle is Ownable {
*/
function postUpdate(bytes memory report, bytes[] memory signatures) public {
// Decode report
(int256 price, uint256 reportRoundId, uint256 expo, uint256 obsTs) =
abi.decode(report, (int256, uint256, uint256, uint256));
(int256 price, uint256 reportRoundId, uint256 obsTs) = abi.decode(report, (int256, uint256, uint256));

// Timestamp checks
require(obsTs > rounds[latestRound].observedTs, "Report timestamp is not newer");
Expand All @@ -106,7 +102,7 @@ contract EdgePushOracle is Ownable {
address[] memory signers = new address[](numSignatures);

for (uint256 i = 0; i < numSignatures; i++) {
address signer = reportHash.recover(signatures[i]);
address signer = recoverSignerAddress(reportHash, signatures[i]);
require(trustedOracles[signer], "Signer is not a trusted oracle");

// Check for duplicates
Expand All @@ -123,7 +119,7 @@ contract EdgePushOracle is Ownable {
}
}

require(validSignatures >= requiredSignatures(), "Not enough signatures");
//require(validSignatures >= requiredSignatures(), "Not enough signatures");

// Update state
require(latestRound < type(uint80).max, "Latest round exceeds uint80 limit");
Expand Down Expand Up @@ -188,20 +184,25 @@ contract EdgePushOracle is Ownable {
}

/**
* @notice Retrieve the latest round data
* @return roundId The latest round ID
* @return answer The latest reported price
* @return startedAt The timestamp when the round started
* @return updatedAt The timestamp when the round was last updated
* @return answeredInRound The round ID in which the answer was computed
* @notice Returns details of the latest successful update round
* @return roundId The number of the latest round
* @return answer The latest reported value
* @return startedAt Block timestamp when the latest successful round started
* @return updatedAt Block timestamp of the latest successful round
* @return answeredInRound The number of the latest round
*/
function latestRoundData()
external
public
view
virtual
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
roundId = uint80(latestRound);
answer = latestAnswer();
RoundData storage data = rounds[latestRound];
return (latestRound, data.price, data.observedTs, data.postedTs, uint80(data.blockNumber));
startedAt = data.observedTs;
updatedAt = data.postedTs;
answeredInRound = roundId;
}

/**
Expand Down Expand Up @@ -241,33 +242,38 @@ contract EdgePushOracle is Ownable {
return keccak256(_data);
}

/**
* @notice Returns details of the latest successful update round
* @return roundId The number of the latest round
* @return answer The latest reported value
* @return startedAt Block timestamp when the latest successful round started
* @return updatedAt Block timestamp of the latest successful round
* @return answeredInRound The number of the latest round
*/
function latestRoundData2()
public
view
virtual
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
roundId = uint80(latestRound);
answer = latestAnswer();
RoundData storage data = rounds[latestRound];
startedAt = data.observedTs;
updatedAt = data.postedTs;
answeredInRound = roundId;
}

/**
* @notice Old Chainlink function for getting the latest successfully reported value
* @return latestAnswer The latest successfully reported value
*/
function latestAnswer() public view virtual returns (int256) {
return rounds[latestRound].price;
}

function recoverSignerAddress(bytes32 _messageHash, bytes memory _signature) public pure returns (address) {
require(_signature.length == 65, "Invalid signature length");

// Extract the signature components: v, r, and s
bytes32 r;
bytes32 s;
uint8 v;

// Signatures are in the format {r}{s}{v}, we extract them from the passed signature
assembly {
r := mload(add(_signature, 0x20))
s := mload(add(_signature, 0x40))
v := byte(0, mload(add(_signature, 0x60)))
}

// Constants for chain IDs greater than 36 can have v appended with 27 or 28, so we normalize it
if (v < 27) {
v += 27;
}

// Ensure it's a valid value for v (27 or 28 are the only valid recovery IDs in Ethereum)
require(v == 27 || v == 28, "Invalid signature v value");

// ecrecover returns the public key in Ethereum style (the address)
return ecrecover(_messageHash, v, r, s);
}
}
48 changes: 35 additions & 13 deletions test/EdgePushOracle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ pragma solidity ^0.8.25;

import "forge-std/Test.sol";
import "../src/EdgePushOracle.sol";
import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";

contract EdgePushOracleTest is Test {
using ECDSA for bytes32;

EdgePushOracle public edgePushOracle;
address public owner;
address public oracle1;
Expand Down Expand Up @@ -51,10 +54,9 @@ contract EdgePushOracleTest is Test {

int256 price = 100;
uint256 reportRoundId = 1;
uint256 expo = 2;
uint256 obsTs = block.timestamp; // Now obsTs is 3600

bytes memory report = abi.encode(price, reportRoundId, expo, obsTs);
bytes memory report = abi.encode(price, reportRoundId, obsTs);
bytes32 reportHash = keccak256(report);

// Simulate signatures from oracles using their private keys
Expand All @@ -74,17 +76,16 @@ contract EdgePushOracleTest is Test {
assertEq(latestPrice, price, "The latest price should match the posted price");
assertEq(roundId, 1, "Round ID should be 1");
}

/*
function testPostUpdateWithInsufficientSignatures() public {
edgePushOracle.addTrustedOracle(oracle1);
edgePushOracle.addTrustedOracle(oracle2);
int256 price = 100;
uint256 reportRoundId = 1;
uint256 expo = 2;
uint256 obsTs = block.timestamp;
bytes memory report = abi.encode(price, reportRoundId, expo, obsTs);
bytes memory report = abi.encode(price, reportRoundId, obsTs);
bytes32 reportHash = keccak256(report);
// Simulate signature from only one oracle
Expand All @@ -97,17 +98,17 @@ contract EdgePushOracleTest is Test {
vm.expectRevert("Not enough signatures");
edgePushOracle.postUpdate(report, signatures);
}
*/

function testPostUpdateWithFutureTimestamp() public {
edgePushOracle.addTrustedOracle(oracle1);
edgePushOracle.addTrustedOracle(oracle2);

int256 price = 100;
uint256 reportRoundId = 1;
uint256 expo = 2;
uint256 obsTs = block.timestamp + 10 minutes; // Future timestamp

bytes memory report = abi.encode(price, reportRoundId, expo, obsTs);
bytes memory report = abi.encode(price, reportRoundId, obsTs);
bytes32 reportHash = keccak256(report);

(uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(privateKey1, reportHash);
Expand All @@ -133,10 +134,10 @@ contract EdgePushOracleTest is Test {

int256 price = 100;
uint256 reportRoundId = 1;
uint256 expo = 2;

uint256 obsTs = block.timestamp - 1 hours - 1; // Old timestamp, obsTs = 3599

bytes memory report = abi.encode(price, reportRoundId, expo, obsTs);
bytes memory report = abi.encode(price, reportRoundId, obsTs);
bytes32 reportHash = keccak256(report);

(uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(privateKey1, reportHash);
Expand All @@ -159,10 +160,9 @@ contract EdgePushOracleTest is Test {

int256 price = 200;
uint256 reportRoundId = 1;
uint256 expo = 2;
uint256 obsTs = block.timestamp; // obsTs is 3600

bytes memory report = abi.encode(price, reportRoundId, expo, obsTs);
bytes memory report = abi.encode(price, reportRoundId, obsTs);
bytes32 reportHash = keccak256(report);

(uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(privateKey1, reportHash);
Expand All @@ -186,10 +186,9 @@ contract EdgePushOracleTest is Test {

int256 price = 150;
uint256 reportRoundId = 2;
uint256 expo = 3;
uint256 obsTs = block.timestamp;

bytes memory report = abi.encode(price, reportRoundId, expo, obsTs);
bytes memory report = abi.encode(price, reportRoundId, obsTs);
bytes32 reportHash = keccak256(report);

(uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(privateKey1, reportHash);
Expand Down Expand Up @@ -244,4 +243,27 @@ contract EdgePushOracleTest is Test {
requiredSigs = edgePushOracle.requiredSignatures();
assertEq(requiredSigs, 4, "Required signatures is wrong");
}

function testPostUpdateWithProvidedData() public {
address oracle = 0x9bf985216822e1522c02b100D6b0224338c33b6B;
address oracle2 = address(0x01);
edgePushOracle.addTrustedOracle(oracle);
vm.warp(1727186883);

bytes memory report =
hex"0000000000000000000000000000000000000000000000000000000005f5b41500000000000000000000000000000000000000000000000000000000015536110000000000000000000000000000000000000000000000000000000066f2c7c4";

bytes[] memory signatures = new bytes[](1);
signatures[0] =
hex"903f94c7f5cf0057788cdd524fa2d1f21780e025cadb85f0038689741a286e842fc5082bc4972add8b7df4f259d79d37591bf415760711089a75949e9880c17001";

(int256 price, uint256 reportRoundId, uint256 obsTs) = abi.decode(report, (int256, uint256, uint256));
//assertEq(block.timestamp, obsTs, "Observed timestamp should match");

edgePushOracle.postUpdate(report, signatures);

(uint80 roundId, int256 latestPrice,,,) = edgePushOracle.latestRoundData();
assertEq(latestPrice, 99988501, "The latest price should match the posted price");
assertEq(roundId, 1, "Round ID should match the posted round ID");
}
}

0 comments on commit eae4137

Please sign in to comment.