1use bitmaps::Bitmap;
2use ink_env::hash::{HashOutput, Keccak256};
3use std::convert::TryInto;
4
5const REDSTONE_MARKER_BS: usize = 9;
6const UNSIGNED_METADATA_BYTE_SIZE_BS: usize = 3;
7const DATA_PACKAGES_COUNT_BS: usize = 2;
8const DATA_POINTS_COUNT_BS: usize = 3;
9const SIGNATURE_BS: usize = 65;
10const MAX_SIGNERS_COUNT: usize = 256;
11const DATA_POINT_VALUE_BYTE_SIZE_BS: usize = 4;
12const DATA_FEED_ID_BS: usize = 32;
13const TIMESTAMP_BS: usize = 6;
14const MAX_TIMESTAMP_DELAY_MS: u128 = 3 * 60 * 1000; const REDSTONE_MARKER: [u8; 9] = [0, 0, 2, 237, 87, 1, 30, 0, 0]; struct DataPackageExtractionResult {
18 contains_requested_data_feed: bool,
19 value_for_requested_data_feed: u128,
20 signer_index: usize,
21 data_package_byte_size: usize,
22}
23
24pub fn get_oracle_value(
25 data_feed_id: &[u8; 32],
26 unique_signers_threshold: u8,
27 authorised_signers: &[[u8; 33]],
28 current_timestamp_milliseconds: u128,
29 redstone_payload: &[u8],
30) -> u128 {
31 assert_valid_redstone_marker(redstone_payload);
32 let mut negative_offset = extract_unsigned_metadata_offset(redstone_payload);
33 let number_of_data_packages =
34 extract_number_of_data_packages(redstone_payload, negative_offset);
35 negative_offset += DATA_PACKAGES_COUNT_BS;
36 let mut unique_signers_bitmap = Bitmap::<MAX_SIGNERS_COUNT>::new();
37 let mut values: Vec<u128> = vec![];
38
39 for _data_package_index in 0..number_of_data_packages {
40 let DataPackageExtractionResult {
41 contains_requested_data_feed,
42 value_for_requested_data_feed,
43 signer_index,
44 data_package_byte_size,
45 } = extract_data_package(
46 data_feed_id,
47 redstone_payload,
48 negative_offset,
49 authorised_signers,
50 current_timestamp_milliseconds,
51 );
52
53 negative_offset += data_package_byte_size;
55
56 if contains_requested_data_feed && !unique_signers_bitmap.get(signer_index) {
58 unique_signers_bitmap.set(signer_index, true);
59 values.push(value_for_requested_data_feed)
60 }
61 }
62
63 if values.len() < usize::from(unique_signers_threshold) {
64 panic!("Insufficient number of unique signers");
65 }
66
67 aggregate_values(&mut values)
68}
69
70fn assert_valid_redstone_marker(redstone_payload: &[u8]) {
71 let marker_start_index = redstone_payload.len() - REDSTONE_MARKER_BS;
72 let redstone_marker = &redstone_payload[marker_start_index..];
73 if REDSTONE_MARKER != redstone_marker {
74 panic!("Invalid redstone marker");
75 }
76}
77
78fn extract_unsigned_metadata_offset(redstone_payload: &[u8]) -> usize {
79 let end_index = redstone_payload.len() - REDSTONE_MARKER_BS; let start_index = end_index - UNSIGNED_METADATA_BYTE_SIZE_BS;
81 let unsigned_metadata_bs =
82 extract_usize_num_from_redstone_payload(redstone_payload, start_index, end_index);
83
84 unsigned_metadata_bs + UNSIGNED_METADATA_BYTE_SIZE_BS + REDSTONE_MARKER_BS
85}
86
87fn extract_number_of_data_packages(
88 redstone_payload: &[u8],
89 unsigned_metadata_offset: usize,
90) -> usize {
91 let end_index = redstone_payload.len() - unsigned_metadata_offset;
92 let start_index = end_index - DATA_PACKAGES_COUNT_BS;
93 extract_usize_num_from_redstone_payload(redstone_payload, start_index, end_index)
94}
95
96fn extract_data_package(
97 requested_data_feed_id: &[u8; 32],
98 redstone_payload: &[u8],
99 negative_offset_to_package: usize,
100 authorised_signers: &[[u8; 33]],
101 current_timestamp_milliseconds: u128,
102) -> DataPackageExtractionResult {
103 let mut value_for_requested_data_feed: u128 = 0;
104 let mut contains_requested_data_feed = false;
105 let mut signer_index: usize = 0;
106
107 let mut end_index = redstone_payload.len() - negative_offset_to_package;
109 let mut start_index = end_index - SIGNATURE_BS;
110 let signature = &redstone_payload[start_index..end_index];
111
112 start_index -= DATA_POINTS_COUNT_BS;
114 end_index = start_index + DATA_POINTS_COUNT_BS;
115 let data_points_count =
116 extract_usize_num_from_redstone_payload(redstone_payload, start_index, end_index);
117
118 start_index -= DATA_POINT_VALUE_BYTE_SIZE_BS;
120 end_index = start_index + DATA_POINT_VALUE_BYTE_SIZE_BS;
121 let data_points_value_bs =
122 extract_usize_num_from_redstone_payload(redstone_payload, start_index, end_index);
123
124 let data_package_byte_size_without_sig = (data_points_value_bs + DATA_FEED_ID_BS)
126 * data_points_count
127 + TIMESTAMP_BS
128 + DATA_POINT_VALUE_BYTE_SIZE_BS
129 + DATA_POINTS_COUNT_BS;
130
131 start_index -= TIMESTAMP_BS;
133 end_index = start_index + TIMESTAMP_BS;
134 let timestamp_milliseconds = bytes_arr_to_number(&redstone_payload[start_index..end_index]);
135 validate_timestamp(timestamp_milliseconds, current_timestamp_milliseconds);
136
137 for _data_point_index in 0..data_points_count {
139 start_index -= data_points_value_bs;
141 end_index = start_index + data_points_value_bs;
142 let data_point_value = bytes_arr_to_number(&redstone_payload[start_index..end_index]);
143
144 start_index -= DATA_FEED_ID_BS;
146 end_index = start_index + DATA_FEED_ID_BS;
147 let data_feed_id = &redstone_payload[start_index..end_index];
148
149 if data_feed_id == requested_data_feed_id {
150 value_for_requested_data_feed = data_point_value;
151 contains_requested_data_feed = true;
152 break;
153 }
154 }
155
156 end_index = redstone_payload.len() - (negative_offset_to_package + SIGNATURE_BS);
158 start_index = end_index - data_package_byte_size_without_sig;
159 let signable_message = &redstone_payload[start_index..end_index];
160
161 let mut message_hash = <Keccak256 as HashOutput>::Type::default(); ink_env::hash_bytes::<Keccak256>(signable_message, &mut message_hash);
164
165 let mut recovered_signer = [0; 33];
167 ink_env::ecdsa_recover(
168 &(signature.try_into().unwrap()),
169 &message_hash,
170 &mut recovered_signer,
171 )
172 .unwrap();
173
174 let mut signer_is_authorised = false;
176 for (authorised_signer_index, authorised_signer) in authorised_signers.iter().enumerate() {
177 if authorised_signer == &recovered_signer {
178 signer_index = authorised_signer_index;
179 signer_is_authorised = true;
180 }
181 }
182 if !signer_is_authorised {
183 panic!("Signer is not authorised");
184 }
185
186 DataPackageExtractionResult {
188 contains_requested_data_feed,
189 value_for_requested_data_feed,
190 data_package_byte_size: data_package_byte_size_without_sig + SIGNATURE_BS,
191 signer_index,
192 }
193}
194
195fn aggregate_values(values: &mut Vec<u128>) -> u128 {
196 if values.len() == 0 {
197 panic!("Can not take median of an empty array");
198 }
199 values.sort();
200 let mid = values.len() / 2;
201 if values.len() % 2 == 0 {
202 return (values[mid - 1] + values[mid]) / 2;
203 } else {
204 return values[mid];
205 }
206}
207
208fn validate_timestamp(received_timestamp_milliseconds: u128, current_timestamp_milliseconds: u128) {
209 if received_timestamp_milliseconds + MAX_TIMESTAMP_DELAY_MS < current_timestamp_milliseconds {
210 panic!("Timestamp is too old");
211 }
212}
213
214fn extract_usize_num_from_redstone_payload(
215 redstone_payload: &[u8],
216 start: usize,
217 end: usize,
218) -> usize {
219 let number_bytes = &redstone_payload[start..end];
220 usize::try_from(bytes_arr_to_number(number_bytes)).unwrap()
221}
222
223fn bytes_arr_to_number(number_bytes: &[u8]) -> u128 {
224 let mut result_number = 0;
225 let mut multiplier = 1;
226
227 for i in (0..number_bytes.len()).rev() {
228 if i == 16 {
230 break;
231 }
232 result_number += u128::from(number_bytes[i]) * multiplier;
233 multiplier *= 256;
234 }
235
236 result_number
237}