Skip to content

Commit

Permalink
[KS-241] Update metadata passed to Forwarder and Receiver
Browse files Browse the repository at this point in the history
  • Loading branch information
bolekk committed Jun 3, 2024
1 parent 0a62f02 commit 09db180
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 102 deletions.
85 changes: 77 additions & 8 deletions contracts/src/v0.8/keystone/KeystoneFeedsConsumer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,96 @@
pragma solidity ^0.8.19;

import {IReceiver} from "./interfaces/IReceiver.sol";
import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol";

contract KeystoneFeedsConsumer is IReceiver {
event MessageReceived(bytes32 indexed workflowId, address indexed workflowOwner, uint256 nReports);
contract KeystoneFeedsConsumer is IReceiver, ConfirmedOwner {
event MessageReceived(bytes10 indexed workflowName, address indexed workflowOwner, uint256 nReports);
event FeedReceived(bytes32 indexed feedId, uint256 price, uint64 timestamp);

constructor() {}
error UnauthorizedSender(address sender);
error UnauthorizedWorkflowOwner(address workflowOwner);
error UnauthorizedWorkflowName(bytes10 workflowName);

constructor() ConfirmedOwner(msg.sender) {}

struct FeedReport {
bytes32 FeedID;
bytes32 FeedId;
uint256 Price;
uint64 Timestamp;
}

function onReport(bytes32 workflowId, address workflowOwner, bytes calldata rawReport) external {
// TODO: validate sender and workflowOwner
mapping(bytes32 feedId => uint256 price) internal s_prices;
address[] internal allowedSenders;
address[] internal allowedWorkflowOwners;
bytes10[] internal allowedWorkflowNames;

function setConfig(address[] calldata _allowedSenders, address[] calldata _allowedWorkflowOwners, bytes10[] calldata _allowedWorkflowNames) external onlyOwner {
allowedSenders = _allowedSenders;
allowedWorkflowOwners = _allowedWorkflowOwners;
allowedWorkflowNames = _allowedWorkflowNames;
}

function onReport(bytes calldata metadata, bytes calldata rawReport) external {
bool allowed = false;
for (uint32 i = 0; i < allowedSenders.length; i++) {
if (msg.sender == allowedSenders[i]) {
allowed = true;
break;
}
}
if (!allowed) {
revert UnauthorizedSender(msg.sender);
}

(bytes10 workflowName, address workflowOwner) = _getInfo(metadata);
allowed = false;
for (uint32 i = 0; i < allowedWorkflowNames.length; i++) {
if (workflowName == allowedWorkflowNames[i]) {
allowed = true;
break;
}
}
if (!allowed) {
revert UnauthorizedWorkflowName(workflowName);
}

allowed = false;
for (uint32 i = 0; i < allowedWorkflowOwners.length; i++) {
if (workflowOwner == allowedWorkflowOwners[i]) {
allowed = true;
break;
}
}
if (!allowed) {
revert UnauthorizedWorkflowOwner(workflowOwner);
}

FeedReport[] memory feeds = abi.decode(rawReport, (FeedReport[]));
for (uint32 i = 0; i < feeds.length; i++) {
emit FeedReceived(feeds[i].FeedID, feeds[i].Price, feeds[i].Timestamp);
s_prices[feeds[i].FeedId] = feeds[i].Price;
emit FeedReceived(feeds[i].FeedId, feeds[i].Price, feeds[i].Timestamp);
}

emit MessageReceived(workflowName, workflowOwner, feeds.length);
}

function _getInfo(
bytes memory metadata
) internal pure returns (bytes10 workflowName, address workflowOwner) {
// (first 32 bytes contain length of the byte array)
// workflow_cid // offset 32, size 32
// workflow_name // offset 64, size 10
// workflow_owner // offset 74, size 20
// report_name // offset 94, size 2
assembly {
// shift right by 22 bytes to get the actual value
workflowName := shr(mul(22, 8), mload(add(metadata, 64)))
// shift right by 12 bytes to get the actual value
workflowOwner := shr(mul(12, 8), mload(add(metadata, 74)))
}
}

emit MessageReceived(workflowId, workflowOwner, feeds.length);
function getPrice(bytes32 feedId) external view returns (uint256) {
return s_prices[feedId];
}
}
58 changes: 29 additions & 29 deletions contracts/src/v0.8/keystone/KeystoneForwarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ contract KeystoneForwarder is IForwarder, ConfirmedOwner, TypeAndVersionInterfac
/// @param signature The signature that was invalid
error InvalidSignature(bytes signature);

/// @notice This error is thrown whenever a report has already been processed.
/// @param reportId The ID of the report that was already processed
error ReportAlreadyProcessed(bytes32 reportId);
/// @notice This error is thrown whenever a message has already been processed.
/// @param messageId The ID of the message that was already processed
error AlreadyProcessed(bytes32 messageId);

bool internal s_reentrancyGuard; // guard against reentrancy

Expand All @@ -83,22 +83,18 @@ contract KeystoneForwarder is IForwarder, ConfirmedOwner, TypeAndVersionInterfac

/// @notice Emitted when a report is processed
/// @param receiver The address of the receiver contract
/// @param workflowOwner The address of the workflow owner
/// @param workflowExecutionId The ID of the workflow execution
/// @param result The result of the attempted delivery. True if successful.
event ReportProcessed(
address indexed receiver,
address indexed workflowOwner,
bytes32 indexed workflowExecutionId,
bool result
);

constructor() ConfirmedOwner(msg.sender) {}

uint256 internal constant MAX_ORACLES = 31;
// 32 bytes for workflowId, 4 bytes for donId, 32 bytes for
// workflowExecutionId, 20 bytes for workflowOwner
uint256 internal constant REPORT_METADATA_LENGTH = 88;
uint256 internal constant REPORT_METADATA_LENGTH = 105;
uint256 internal constant SIGNATURE_LENGTH = 65;

function setConfig(uint32 donId, uint8 f, address[] calldata signers) external onlyOwner {
Expand Down Expand Up @@ -137,13 +133,13 @@ contract KeystoneForwarder is IForwarder, ConfirmedOwner, TypeAndVersionInterfac
revert InvalidReport();
}

(bytes32 workflowId, uint32 donId, bytes32 workflowExecutionId, address workflowOwner) = _getMetadata(rawReport);
(bytes32 workflowExecutionId, uint32 donId, bytes2 reportName) = _getMetadata(rawReport);

// f can never be 0, so this means the config doesn't actually exist
if (s_configs[donId].f == 0) revert InvalidDonId(donId);

bytes32 reportId = _reportId(receiverAddress, workflowExecutionId);
if (s_reports[reportId].transmitter != address(0)) revert ReportAlreadyProcessed(reportId);
bytes32 id = _reportId(receiverAddress, workflowExecutionId, reportName);
if (s_reports[id].transmitter != address(0)) revert AlreadyProcessed(id);

if (s_configs[donId].f + 1 != signatures.length)
revert InvalidSignatureCount(s_configs[donId].f + 1, signatures.length);
Expand All @@ -169,25 +165,25 @@ contract KeystoneForwarder is IForwarder, ConfirmedOwner, TypeAndVersionInterfac
}

bool success;
try IReceiver(receiverAddress).onReport(workflowId, workflowOwner, rawReport[REPORT_METADATA_LENGTH:]) {
try IReceiver(receiverAddress).onReport(rawReport[41:105], rawReport[REPORT_METADATA_LENGTH:]) {
success = true;
} catch {
// Do nothing, success is already false
}

s_reports[reportId] = DeliveryStatus(msg.sender, success);
s_reports[id] = DeliveryStatus(msg.sender, success);

emit ReportProcessed(receiverAddress, workflowOwner, workflowExecutionId, success);
emit ReportProcessed(receiverAddress, workflowExecutionId, success);
}

function _reportId(address receiver, bytes32 workflowExecutionId) internal pure returns (bytes32) {
function _reportId(address receiver, bytes32 workflowExecutionId, bytes2 reportId) internal pure returns (bytes32) {
// TODO: gas savings: could we just use a bytes key and avoid another keccak256 call
return keccak256(bytes.concat(bytes20(uint160(receiver)), workflowExecutionId));
return keccak256(bytes.concat(bytes20(uint160(receiver)), workflowExecutionId, reportId));
}

// get transmitter of a given report or 0x0 if it wasn't transmitted yet
function getTransmitter(address receiver, bytes32 workflowExecutionId) external view returns (address) {
bytes32 reportId = _reportId(receiver, workflowExecutionId);
function getTransmitter(address receiver, bytes32 workflowExecutionId, bytes2 reportName) external view returns (address) {
bytes32 reportId = _reportId(receiver, workflowExecutionId, reportName);
return s_reports[reportId].transmitter;
}

Expand Down Expand Up @@ -215,18 +211,22 @@ contract KeystoneForwarder is IForwarder, ConfirmedOwner, TypeAndVersionInterfac

function _getMetadata(
bytes memory rawReport
) internal pure returns (bytes32 workflowId, uint32 donId, bytes32 workflowExecutionId, address workflowOwner) {
) internal pure returns (bytes32 workflowExecutionId, uint32 donId, bytes2 reportName) {
// (first 32 bytes contain length of the report)
// version // offset 32, size 1
// workflow_execution_id // offset 33, size 32
// timestamp // offset 65, size 4
// don_id // offset 69, size 4
// workflow_cid // offset 73, size 32
// workflow_name // offset 105, size 10
// workflow_owner // offset 115, size 20
// report_name // offset 135, size 2
assembly {
// skip first 32 bytes, contains length of the report
// first 32 bytes is the workflowId
workflowId := mload(add(rawReport, 32))
// next 4 bytes is donId. We shift right by 28 bytes to get the actual value
donId := shr(mul(28, 8), mload(add(rawReport, 64)))
// next 32 bytes is the workflowExecutionId
workflowExecutionId := mload(add(rawReport, 68))
// next 20 bytes is the workflowOwner. We shift right by 12 bytes to get
// the actual value
workflowOwner := shr(mul(12, 8), mload(add(rawReport, 100)))
workflowExecutionId := mload(add(rawReport, 33))
// shift right by 28 bytes to get the actual value
donId := shr(mul(28, 8), mload(add(rawReport, 69)))
// shift right by 30 bytes to get the actual value
reportName := shr(mul(30, 8), mload(add(rawReport, 135)))
}
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/src/v0.8/keystone/interfaces/IReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ pragma solidity ^0.8.19;

/// @title IReceiver - receives keystone reports
interface IReceiver {
function onReport(bytes32 workflowId, address workflowOwner, bytes calldata report) external;
function onReport(bytes calldata metadata, bytes calldata report) external;
}
4 changes: 4 additions & 0 deletions core/capabilities/targets/write_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func (cap *WriteTarget) Execute(ctx context.Context, request capabilities.Capabi
if err != nil {
return nil, err
}
reqConfig.Address = "0x8615B263C4eD056a22AD3E0B2977301EcC9AbE63"

signedReport, ok := request.Inputs.Underlying[signedReportField]
if !ok {
Expand Down Expand Up @@ -110,9 +111,11 @@ func (cap *WriteTarget) Execute(ctx context.Context, request capabilities.Capabi
queryInputs := struct {
Receiver string
WorkflowExecutionID []byte
ReportName []byte
}{
Receiver: reqConfig.Address,
WorkflowExecutionID: rawExecutionID,
ReportName: []byte{0x00, 0x00}, // TODO: how to access it?
}
var transmitter common.Address
if err = cap.cr.GetLatestValue(ctx, "forwarder", "getTransmitter", queryInputs, &transmitter); err != nil {
Expand Down Expand Up @@ -149,6 +152,7 @@ func (cap *WriteTarget) Execute(ctx context.Context, request capabilities.Capabi
if req.Signatures == nil {
req.Signatures = make([][]byte, 0)
}
cap.lggr.Debugw("Transaction raw report", "report", hex.EncodeToString(req.RawReport))

meta := commontypes.TxMeta{WorkflowExecutionID: &request.Metadata.WorkflowExecutionID}
value := big.NewInt(0)
Expand Down
Loading

0 comments on commit 09db180

Please sign in to comment.