Skip to content

Commit

Permalink
Merge pull request #5 from mento-protocol/m-chrzan/payload-testing
Browse files Browse the repository at this point in the history
Test payload serialization
  • Loading branch information
chapati23 authored May 15, 2024
2 parents f41212b + 3587b42 commit 662ee77
Show file tree
Hide file tree
Showing 3 changed files with 281 additions and 21 deletions.
159 changes: 145 additions & 14 deletions test/RedStonePayload.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,96 @@
pragma solidity ^0.8.24;

import {Test} from "forge-std/Test.sol";

import {DebugRedStoneConsumer} from "./lib/DebugRedStoneConsumer.sol";
import {RedStonePayload} from "./lib/RedStonePayload.sol";

contract RedStonePayloadTest is Test {}
contract RedStonePayloadTest is Test {
DebugRedStoneConsumer consumer;

uint256 constant PRIVATE_KEY_1 =
0x1111111111111111111111111111111111111111111111111111111111111111;
address constant ADDRESS_1 = 0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A;
uint256 constant PRIVATE_KEY_2 =
0x2222222222222222222222222222222222222222222222222222222222222222;
address constant ADDRESS_2 = 0x1563915e194D8CfBA1943570603F7606A3115508;

// The Solidity compiler does not support storing some more complex structs
// in storage, so we return example data packages from functions.
function payloadWithOneDataPackage()
internal
pure
returns (RedStonePayload.Payload memory)
{
bytes32 dataFeedId = "USDCELO";
uint256[] memory values = new uint256[](1);
uint256[] memory privateKeys = new uint256[](1);
uint256[] memory timestamps = new uint256[](1);

values[0] = 42;
privateKeys[0] = PRIVATE_KEY_1;
timestamps[0] = 1337;

return
RedStonePayload.makePayload(
dataFeedId,
values,
privateKeys,
timestamps
);
}

function payloadWithTwoDataPackages()
internal
pure
returns (RedStonePayload.Payload memory)
{
bytes32 dataFeedId = "USDCELO";
uint256[] memory values = new uint256[](2);
uint256[] memory privateKeys = new uint256[](2);
uint256[] memory timestamps = new uint256[](2);

values[0] = 42;
values[1] = 24;
privateKeys[0] = PRIVATE_KEY_1;
privateKeys[1] = PRIVATE_KEY_2;
timestamps[0] = 1337;
timestamps[1] = 1337;
return
RedStonePayload.makePayload(
dataFeedId,
values,
privateKeys,
timestamps
);
}

function setUp() public {
consumer = new DebugRedStoneConsumer();
}

function reportWithPayload(
bytes32 rateFeedId,
bytes memory payload,
uint8 signers
) public returns (uint64[4] memory) {
bytes memory callData = abi.encodeWithSignature(
"report(bytes32)",
rateFeedId
);
bytes memory callDataWithPayload = bytes.concat(callData, payload);
consumer.setUniqueSignersThreshold(signers);
// solhint-disable-next-line avoid-low-level-calls
(, bytes memory returnBytes) = address(consumer).call(
callDataWithPayload
);
uint256 returnValue = abi.decode(returnBytes, (uint256));
return consumer.parseAggregatedValue(returnValue);
}
}

contract RedStonePayloadTest_makePayload is RedStonePayloadTest {
function test_makePayload() public pure {
function test_makePayloadWithSignatures() public pure {
bytes32 dataFeedId = "USDCELO";
uint256[] memory values = new uint256[](1);
bytes32[] memory rs = new bytes32[](1);
Expand All @@ -31,40 +115,87 @@ contract RedStonePayloadTest_makePayload is RedStonePayloadTest {
timestamps
);

assertEq(
payload.dataPackages[0].dataPackage.dataPoints[0].dataFeedId,
"USDCELO"
);
assertEq(payload.dataPackages[0].dataPackage.dataPoints[0].decimals, 8);
assertEq(
payload.dataPackages[0].dataPackage.dataPoints[0].valueBytesSize,
32
);
assertEq(payload.dataPackages[0].dataPackage.dataPoints[0].value, 42);
assertEq(
payload.dataPackages[0].dataPackage.timestampMilliseconds,
1337
);
}
}

contract RedStonePayload_serializePayload is RedStonePayloadTest {
function test_serializePayload() public pure {
function test_makePayloadWithPrivateKeys() public pure {
bytes32 dataFeedId = "USDCELO";
uint256[] memory values = new uint256[](1);
bytes32[] memory rs = new bytes32[](1);
bytes32[] memory ss = new bytes32[](1);
uint8[] memory vs = new uint8[](1);
uint256[] memory privateKeys = new uint256[](1);
uint256[] memory timestamps = new uint256[](1);

values[0] = 42;
rs[0] = 0;
ss[0] = 0;
vs[0] = 0;
privateKeys[0] = PRIVATE_KEY_1;
timestamps[0] = 1337;

RedStonePayload.Payload memory payload = RedStonePayload.makePayload(
dataFeedId,
values,
rs,
ss,
vs,
privateKeys,
timestamps
);

assertEq(payload.dataPackages[0].dataPackage.dataPoints[0].value, 42);
assertEq(payload.dataPackages[0].dataPackage.dataPoints[0].decimals, 8);
assertEq(
payload.dataPackages[0].dataPackage.dataPoints[0].valueBytesSize,
32
);
assertEq(
payload.dataPackages[0].dataPackage.timestampMilliseconds,
1337
);
}
}

contract RedStonePayload_serializePayload is RedStonePayloadTest {
function test_serializePayload_oneDataPackage() public pure {
RedStonePayload.Payload memory payload = payloadWithOneDataPackage();

bytes memory result = RedStonePayload.serializePayload(payload);
// 156 comes from manual math verification of what the length should be.
assertEq(result.length, 156);
}

function test_serializePayload_twoDataPackages() public pure {
RedStonePayload.Payload memory payload = payloadWithTwoDataPackages();

bytes memory result = RedStonePayload.serializePayload(payload);
// 298 comes from manual math verification of what the length should be.
assertEq(result.length, 298);
}

function test_parseSerializedPayload() public {
RedStonePayload.Payload memory payload = payloadWithOneDataPackage();
bytes memory result = RedStonePayload.serializePayload(payload);

uint64[4] memory values = reportWithPayload("USDCELO", result, 1);

assertEq(values[0], 42);
assertEq(consumer.receivedTimestamp(), 1337);
}

function test_parseSerializedPayloadWithTwoValues() public {
RedStonePayload.Payload memory payload = payloadWithTwoDataPackages();
bytes memory result = RedStonePayload.serializePayload(payload);

uint64[4] memory values = reportWithPayload("USDCELO", result, 2);

assertEq(values[0], 42);
assertEq(values[1], 24);
assertEq(consumer.receivedTimestamp(), 1337);
}
}
56 changes: 56 additions & 0 deletions test/lib/DebugRedStoneConsumer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: UNLICENSED
// solhint-disable func-name-mixedcase, gas-strict-inequalities, ordering
pragma solidity ^0.8.24;

import {RedstoneConsumerNumericBase} from "redstone/contracts/core/RedstoneConsumerNumericBase.sol";

contract DebugRedStoneConsumer is RedstoneConsumerNumericBase {
address constant ADDRESS_1 = 0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A;
address constant ADDRESS_2 = 0x1563915e194D8CfBA1943570603F7606A3115508;
uint256[] public receivedValues;
uint256 public receivedTimestamp;
uint8 public signersThreshold;

function report(bytes32 rateFeedId) public returns (uint256) {
receivedTimestamp = extractTimestampsAndAssertAllAreEqual();
return getOracleNumericValueFromTxMsg(rateFeedId);
}

function getAuthorisedSignerIndex(address receivedSigner) public pure override returns (uint8) {
if (receivedSigner == ADDRESS_1) {
return 1;
} else if (receivedSigner == ADDRESS_2) {
return 2;
}
}

function setUniqueSignersThreshold(uint8 threshold) public {
signersThreshold = threshold;
}

function getUniqueSignersThreshold() public view override returns (uint8) {
return signersThreshold;
}

function aggregateValues(uint256[] memory values) public pure override returns (uint256) {
// VERY hacky way of returning up to 4 of the received uint64 values
// while keeping this function `view`.
uint256 result = 0;
for (uint256 i = 0; i < values.length && i < 4; i++) {
result <<= 64;
result |= values[i];
}
return result;
}

function parseAggregatedValue(
uint256 value
) public pure returns (uint64[4] memory) {
uint64[4] memory values;
for (uint64 i = 0; i < 4; i++) {
values[i] = uint64(value % 2**64);
value >>= 64;
}
return values;
}
}
87 changes: 80 additions & 7 deletions test/lib/RedStonePayload.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;

import {Vm} from "forge-std/Vm.sol";

/**
* @notice Implements serialization to a RedStone data payload.
* @dev Reference:
* https://github.com/redstone-finance/redstone-oracles-monorepo/tree/main/packages/protocol/src
*/
library RedStonePayload {
// See https://book.getfoundry.sh/forge/cheatcodes
address private constant CHEATCODE_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
Vm private constant vm = Vm(CHEATCODE_ADDRESS);

struct SerializationBuffer {
bytes buffer;
uint256 currentIndex;
Expand Down Expand Up @@ -84,6 +90,68 @@ library RedStonePayload {
// batch, but our model assumes each batch is for a single data feed.
uint256 private constant DATA_POINTS_PER_PACKAGE = 1;

function signDataPackage(
DataPackage memory dataPackage,
uint256 privateKey
) internal pure returns (Signature memory) {
uint256 dataPackageSerializedSize =
DATA_POINTS_COUNT_BS +
DATA_POINT_VALUE_BYTE_SIZE_BS +
TIMESTAMP_BS +
DATA_POINTS_PER_PACKAGE * (
DATA_FEED_ID_BS +
DEFAULT_NUM_VALUE_BS
);
SerializationBuffer memory buffer = newSerializationBuffer(dataPackageSerializedSize);
writeDataPackage(buffer, dataPackage);
bytes32 digest = keccak256(buffer.buffer);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
return Signature(r, s, v);
}

function makeDataPackage(
bytes32 dataFeedId,
uint256 value,
uint256 timestamp
) internal pure returns (DataPackage memory) {
DataPoint[] memory dataPoints = new DataPoint[](1);
dataPoints[0] = DataPoint(
dataFeedId,
value,
DEFAULT_NUM_VALUE_DECIMALS,
DEFAULT_NUM_VALUE_BS
);
return DataPackage(
dataPoints,
timestamp
);
}

function makePayload(
bytes32 dataFeedId,
uint256[] memory values,
uint256[] memory privateKeys,
uint256[] memory timestamps
) internal pure returns (Payload memory) {
Payload memory payload;

uint256 numberPackages = values.length;

payload.dataPackages = new SignedDataPackage[](numberPackages);

for (uint256 i = 0; i < numberPackages; i++) {
DataPackage memory dataPackage = makeDataPackage(
dataFeedId,
values[i],
timestamps[i]
);
Signature memory signature = signDataPackage(dataPackage, privateKeys[i]);
payload.dataPackages[i] = SignedDataPackage(dataPackage, signature);
}

return payload;
}

function makePayload(
bytes32 dataFeedId,
uint256[] memory values,
Expand All @@ -99,10 +167,9 @@ library RedStonePayload {
payload.dataPackages = new SignedDataPackage[](numberPackages);

for (uint256 i = 0; i < numberPackages; i++) {
DataPoint[] memory dataPoints = new DataPoint[](1);
dataPoints[0] = DataPoint(dataFeedId, values[i], 18, 8);
DataPackage memory dataPackage = DataPackage(
dataPoints,
DataPackage memory dataPackage = makeDataPackage(
dataFeedId,
values[i],
timestamps[i]
);
Signature memory signature = Signature(rs[i], ss[i], vs[i]);
Expand Down Expand Up @@ -190,17 +257,23 @@ library RedStonePayload {
writeNumber(buffer, signature.v, 1);
}

function writeSignedDataPackage(
function writeDataPackage(
SerializationBuffer memory buffer,
SignedDataPackage memory signedDataPackage
DataPackage memory dataPackage
) internal pure {
DataPackage memory dataPackage = signedDataPackage.dataPackage;
for (uint256 i = 0; i < dataPackage.dataPoints.length; i++) {
writeDataPoint(buffer, dataPackage.dataPoints[i]);
}
writeTimestamp(buffer, dataPackage.timestampMilliseconds);
writeDataPointSize(buffer);
writeDataPointNumber(buffer, dataPackage.dataPoints.length);
}

function writeSignedDataPackage(
SerializationBuffer memory buffer,
SignedDataPackage memory signedDataPackage
) internal pure {
writeDataPackage(buffer, signedDataPackage.dataPackage);
writeSignature(buffer, signedDataPackage.signature);
}

Expand Down

0 comments on commit 662ee77

Please sign in to comment.