@@ -4,6 +4,7 @@ pragma solidity ^0.8.0;
44import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol " ;
55import "../data-verification-mechanism/interfaces/OracleInterface.sol " ;
66import "../data-verification-mechanism/interfaces/RegistryInterface.sol " ;
7+ import "./AncillaryDataCompression.sol " ;
78import "./OracleBase.sol " ;
89import "../common/implementation/AncillaryData.sol " ;
910import "../common/implementation/Lockable.sol " ;
@@ -29,6 +30,28 @@ contract OracleSpoke is
2930 ChildMessengerConsumerInterface ,
3031 Lockable
3132{
33+ using AncillaryDataCompression for bytes ;
34+
35+ // Mapping of parent request ID to child request ID.
36+ mapping (bytes32 => bytes32 ) public childRequestIds;
37+
38+ event PriceRequestBridged (
39+ address indexed requester ,
40+ bytes32 identifier ,
41+ uint256 time ,
42+ bytes ancillaryData ,
43+ bytes32 indexed childRequestId ,
44+ bytes32 indexed parentRequestId
45+ );
46+ event ResolvedLegacyRequest (
47+ bytes32 indexed identifier ,
48+ uint256 time ,
49+ bytes ancillaryData ,
50+ int256 price ,
51+ bytes32 indexed requestHash ,
52+ bytes32 indexed legacyRequestHash
53+ );
54+
3255 constructor (address _finderAddress ) HasFinder (_finderAddress) {}
3356
3457 // This assumes that the local network has a Registry that resembles the mainnet registry.
@@ -55,21 +78,43 @@ contract OracleSpoke is
5578 uint256 time ,
5679 bytes memory ancillaryData
5780 ) public override nonReentrant () onlyRegisteredContract () {
58- bool newPriceRequested = _requestPrice (identifier, time, _stampAncillaryData (ancillaryData));
59- if (newPriceRequested) {
60- getChildMessenger ().sendMessageToParent (abi.encode (identifier, time, _stampAncillaryData (ancillaryData)));
61- }
81+ _requestPriceSpoke (identifier, time, ancillaryData);
6282 }
6383
6484 /**
6585 * @notice Overloaded function to provide backwards compatibility for legacy financial contracts that do not use
6686 * ancillary data.
6787 */
6888 function requestPrice (bytes32 identifier , uint256 time ) public override nonReentrant () onlyRegisteredContract () {
69- bool newPriceRequested = _requestPrice (identifier, time, _stampAncillaryData ("" ));
70- if (newPriceRequested) {
71- getChildMessenger ().sendMessageToParent (abi.encode (identifier, time, _stampAncillaryData ("" )));
72- }
89+ _requestPriceSpoke (identifier, time, "" );
90+ }
91+
92+ function _requestPriceSpoke (
93+ bytes32 identifier ,
94+ uint256 time ,
95+ bytes memory ancillaryData
96+ ) internal {
97+ address requester = msg .sender ;
98+ bytes32 childRequestId = _encodePriceRequest (identifier, time, ancillaryData);
99+ Price storage lookup = prices[childRequestId];
100+
101+ // Send the request to mainnet only if it has not been requested yet.
102+ if (lookup.state != RequestState.NeverRequested) return ;
103+ lookup.state = RequestState.Requested;
104+
105+ // Only the compressed ancillary data is sent to the mainnet. As it includes the request block number that is
106+ // not available when getting the resolved price, we map the derived request ID.
107+ bytes memory parentAncillaryData = ancillaryData.compress (requester, block .number );
108+ bytes32 parentRequestId = _encodePriceRequest (identifier, time, parentAncillaryData);
109+ childRequestIds[parentRequestId] = childRequestId;
110+
111+ // Emit all required information so that voters on mainnet can track the origin of the request and full
112+ // ancillary data by using the parentRequestId that is derived from identifier, time and ancillary data as
113+ // observed on mainnet.
114+ emit PriceRequestBridged (requester, identifier, time, ancillaryData, childRequestId, parentRequestId);
115+ emit PriceRequestAdded (identifier, time, parentAncillaryData, parentRequestId);
116+
117+ getChildMessenger ().sendMessageToParent (abi.encode (identifier, time, parentAncillaryData));
73118 }
74119
75120 /**
@@ -81,7 +126,50 @@ contract OracleSpoke is
81126 function processMessageFromParent (bytes memory data ) public override nonReentrant () onlyMessenger () {
82127 (bytes32 identifier , uint256 time , bytes memory ancillaryData , int256 price ) =
83128 abi.decode (data, (bytes32 , uint256 , bytes , int256 ));
84- _publishPrice (identifier, time, ancillaryData, price);
129+ bytes32 parentRequestId = _encodePriceRequest (identifier, time, ancillaryData);
130+
131+ // Resolve the requestId used when requesting and checking the price. The childRequestIds value in the mapping
132+ // could be uninitialized if the request was originated from:
133+ // - the previous implementation of this contract, or
134+ // - another chain and was pushed to this chain by mistake.
135+ bytes32 priceRequestId = childRequestIds[parentRequestId];
136+ if (priceRequestId == bytes32 (0 )) priceRequestId = parentRequestId;
137+ Price storage lookup = prices[priceRequestId];
138+
139+ // In order to support resolving the requests initiated from the previous implementation of this contract, we
140+ // only update the state and emit an event if it has not yet been resolved.
141+ if (lookup.state == RequestState.Resolved) return ;
142+ lookup.price = price;
143+ lookup.state = RequestState.Resolved;
144+ emit PushedPrice (identifier, time, ancillaryData, price, priceRequestId);
145+ }
146+
147+ /**
148+ * @notice This method handles a special case when a price request was originated on the previous implementation of
149+ * this contract, but was not settled before the upgrade.
150+ * @dev Duplicates the resolved state from the legacy request to the new request where original ancillary data is
151+ * used for request ID derivation. Will revert if the legacy request has not been pushed from mainnet.
152+ * @param identifier Identifier of price request to resolve.
153+ * @param time Timestamp of price request to resolve.
154+ * @param ancillaryData Original ancillary data passed by the requester before stamping by the legacy spoke.
155+ */
156+ function resolveLegacyRequest (
157+ bytes32 identifier ,
158+ uint256 time ,
159+ bytes memory ancillaryData
160+ ) external {
161+ bytes32 legacyRequestId = _encodePriceRequest (identifier, time, _legacyStampAncillaryData (ancillaryData));
162+ Price storage legacyLookup = prices[legacyRequestId];
163+ require (legacyLookup.state == RequestState.Resolved, "Price has not been resolved " );
164+
165+ bytes32 priceRequestId = _encodePriceRequest (identifier, time, ancillaryData);
166+ Price storage lookup = prices[priceRequestId];
167+
168+ // Update the state and emit an event only if the legacy request has not been resolved yet.
169+ if (lookup.state == RequestState.Resolved) return ;
170+ lookup.price = legacyLookup.price;
171+ lookup.state = RequestState.Resolved;
172+ emit ResolvedLegacyRequest (identifier, time, ancillaryData, lookup.price, priceRequestId, legacyRequestId);
85173 }
86174
87175 /**
@@ -96,7 +184,7 @@ contract OracleSpoke is
96184 uint256 time ,
97185 bytes memory ancillaryData
98186 ) public view override nonReentrantView () onlyRegisteredContract () returns (bool ) {
99- bytes32 priceRequestId = _encodePriceRequest (identifier, time, _stampAncillaryData ( ancillaryData) );
187+ bytes32 priceRequestId = _encodePriceRequest (identifier, time, ancillaryData);
100188 return prices[priceRequestId].state == RequestState.Resolved;
101189 }
102190
@@ -112,7 +200,7 @@ contract OracleSpoke is
112200 onlyRegisteredContract ()
113201 returns (bool )
114202 {
115- bytes32 priceRequestId = _encodePriceRequest (identifier, time, _stampAncillaryData ( "" ) );
203+ bytes32 priceRequestId = _encodePriceRequest (identifier, time, "" );
116204 return prices[priceRequestId].state == RequestState.Resolved;
117205 }
118206
@@ -128,7 +216,7 @@ contract OracleSpoke is
128216 uint256 time ,
129217 bytes memory ancillaryData
130218 ) public view override nonReentrantView () onlyRegisteredContract () returns (int256 ) {
131- bytes32 priceRequestId = _encodePriceRequest (identifier, time, _stampAncillaryData ( ancillaryData) );
219+ bytes32 priceRequestId = _encodePriceRequest (identifier, time, ancillaryData);
132220 Price storage lookup = prices[priceRequestId];
133221 require (lookup.state == RequestState.Resolved, "Price has not been resolved " );
134222 return lookup.price;
@@ -146,27 +234,34 @@ contract OracleSpoke is
146234 onlyRegisteredContract ()
147235 returns (int256 )
148236 {
149- bytes32 priceRequestId = _encodePriceRequest (identifier, time, _stampAncillaryData ( "" ) );
237+ bytes32 priceRequestId = _encodePriceRequest (identifier, time, "" );
150238 Price storage lookup = prices[priceRequestId];
151239 require (lookup.state == RequestState.Resolved, "Price has not been resolved " );
152240 return lookup.price;
153241 }
154242
155243 /**
156- * @notice Generates stamped ancillary data in the format that it would be used in the case of a price request.
157- * @param ancillaryData ancillary data of the price being requested.
158- * @return the stamped ancillary bytes.
244+ * @notice Compresses ancillary data by providing sufficient information to track back the original ancillary data
245+ * mainnet.
246+ * @dev This is expected to be used in offchain infrastructure when speeding up requests to the mainnet.
247+ * @param ancillaryData original ancillary data to be processed.
248+ * @param requester address of the requester who initiated the price request.
249+ * @param requestBlockNumber block number when the price request was initiated.
250+ * @return compressed ancillary data.
159251 */
160- function stampAncillaryData (bytes memory ancillaryData ) public view nonReentrantView () returns (bytes memory ) {
161- return _stampAncillaryData (ancillaryData);
252+ function compressAncillaryData (
253+ bytes memory ancillaryData ,
254+ address requester ,
255+ uint256 requestBlockNumber
256+ ) external view returns (bytes memory ) {
257+ return ancillaryData.compress (requester, requestBlockNumber);
162258 }
163259
164260 /**
165- * @dev We don't handle specifically the case where `ancillaryData` is not already readily translatable in utf8.
166- * For those cases, we assume that the client will be able to strip out the utf8-translatable part of the
167- * ancillary data that this contract stamps.
261+ * @dev This replicates the implementation of `_stampAncillaryData` from the previous version of this contract for
262+ * the purpose of resolving legacy requests if they had not been resolved before the upgrade.
168263 */
169- function _stampAncillaryData (bytes memory ancillaryData ) internal view returns (bytes memory ) {
264+ function _legacyStampAncillaryData (bytes memory ancillaryData ) internal view returns (bytes memory ) {
170265 // This contract should stamp the child network's ID so that voters on the parent network can
171266 // deterministically track unique price requests back to this contract.
172267 return AncillaryData.appendKeyValueUint (ancillaryData, "childChainId " , block .chainid );
0 commit comments