@@ -4,6 +4,7 @@ pragma solidity ^0.8.0;
4
4
import "../data-verification-mechanism/interfaces/OracleAncillaryInterface.sol " ;
5
5
import "../data-verification-mechanism/interfaces/OracleInterface.sol " ;
6
6
import "../data-verification-mechanism/interfaces/RegistryInterface.sol " ;
7
+ import "./AncillaryDataCompression.sol " ;
7
8
import "./OracleBase.sol " ;
8
9
import "../common/implementation/AncillaryData.sol " ;
9
10
import "../common/implementation/Lockable.sol " ;
@@ -29,6 +30,28 @@ contract OracleSpoke is
29
30
ChildMessengerConsumerInterface ,
30
31
Lockable
31
32
{
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
+
32
55
constructor (address _finderAddress ) HasFinder (_finderAddress) {}
33
56
34
57
// This assumes that the local network has a Registry that resembles the mainnet registry.
@@ -55,21 +78,43 @@ contract OracleSpoke is
55
78
uint256 time ,
56
79
bytes memory ancillaryData
57
80
) 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);
62
82
}
63
83
64
84
/**
65
85
* @notice Overloaded function to provide backwards compatibility for legacy financial contracts that do not use
66
86
* ancillary data.
67
87
*/
68
88
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));
73
118
}
74
119
75
120
/**
@@ -81,7 +126,50 @@ contract OracleSpoke is
81
126
function processMessageFromParent (bytes memory data ) public override nonReentrant () onlyMessenger () {
82
127
(bytes32 identifier , uint256 time , bytes memory ancillaryData , int256 price ) =
83
128
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);
85
173
}
86
174
87
175
/**
@@ -96,7 +184,7 @@ contract OracleSpoke is
96
184
uint256 time ,
97
185
bytes memory ancillaryData
98
186
) public view override nonReentrantView () onlyRegisteredContract () returns (bool ) {
99
- bytes32 priceRequestId = _encodePriceRequest (identifier, time, _stampAncillaryData ( ancillaryData) );
187
+ bytes32 priceRequestId = _encodePriceRequest (identifier, time, ancillaryData);
100
188
return prices[priceRequestId].state == RequestState.Resolved;
101
189
}
102
190
@@ -112,7 +200,7 @@ contract OracleSpoke is
112
200
onlyRegisteredContract ()
113
201
returns (bool )
114
202
{
115
- bytes32 priceRequestId = _encodePriceRequest (identifier, time, _stampAncillaryData ( "" ) );
203
+ bytes32 priceRequestId = _encodePriceRequest (identifier, time, "" );
116
204
return prices[priceRequestId].state == RequestState.Resolved;
117
205
}
118
206
@@ -128,7 +216,7 @@ contract OracleSpoke is
128
216
uint256 time ,
129
217
bytes memory ancillaryData
130
218
) public view override nonReentrantView () onlyRegisteredContract () returns (int256 ) {
131
- bytes32 priceRequestId = _encodePriceRequest (identifier, time, _stampAncillaryData ( ancillaryData) );
219
+ bytes32 priceRequestId = _encodePriceRequest (identifier, time, ancillaryData);
132
220
Price storage lookup = prices[priceRequestId];
133
221
require (lookup.state == RequestState.Resolved, "Price has not been resolved " );
134
222
return lookup.price;
@@ -146,27 +234,34 @@ contract OracleSpoke is
146
234
onlyRegisteredContract ()
147
235
returns (int256 )
148
236
{
149
- bytes32 priceRequestId = _encodePriceRequest (identifier, time, _stampAncillaryData ( "" ) );
237
+ bytes32 priceRequestId = _encodePriceRequest (identifier, time, "" );
150
238
Price storage lookup = prices[priceRequestId];
151
239
require (lookup.state == RequestState.Resolved, "Price has not been resolved " );
152
240
return lookup.price;
153
241
}
154
242
155
243
/**
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.
159
251
*/
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);
162
258
}
163
259
164
260
/**
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.
168
263
*/
169
- function _stampAncillaryData (bytes memory ancillaryData ) internal view returns (bytes memory ) {
264
+ function _legacyStampAncillaryData (bytes memory ancillaryData ) internal view returns (bytes memory ) {
170
265
// This contract should stamp the child network's ID so that voters on the parent network can
171
266
// deterministically track unique price requests back to this contract.
172
267
return AncillaryData.appendKeyValueUint (ancillaryData, "childChainId " , block .chainid );
0 commit comments