1use super::*;
2
3#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, Hash)]
4pub enum OracleServiceTarget {
5 LVM(LyquidID),
7 EVM {
9 target: Address,
11 eth_contract: Address,
13 },
14}
15
16#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, Hash)]
17pub struct OracleTarget {
18 pub target: OracleServiceTarget,
20 pub seq_id: SequenceBackendID,
22}
23
24impl OracleTarget {
25 pub fn cipher(&self) -> Cipher {
26 match self.target {
27 OracleServiceTarget::EVM { .. } => Cipher::Secp256k1,
28 OracleServiceTarget::LVM(_) => Cipher::Ed25519,
29 }
30 }
31}
32
33#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug)]
35pub struct OracleHeader {
36 pub proposer: NodeID,
38 pub target: OracleTarget,
40 pub config_hash: HashBytes,
42 pub epoch: u32,
44 pub nonce: HashBytes,
46}
47
48#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug)]
49pub struct OracleEpochInfo {
50 pub epoch: u32,
51 pub config_hash: HashBytes,
52}
53
54pub type SignerID = u32;
55
56#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
57pub struct OracleSigner {
58 pub id: SignerID,
59 pub key: Bytes,
60}
61
62#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
63pub struct OracleConfig {
64 pub committee: Vec<OracleSigner>,
65 pub threshold: u16,
66}
67
68impl OracleConfig {
69 pub fn to_hash(&self) -> Hash {
70 blake3::hash(&encode_object(self))
71 }
72}
73
74#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
75pub struct OracleConfigDelta {
76 pub upsert: Vec<OracleSigner>,
77 pub remove: Vec<SignerID>,
78 pub threshold: Option<u16>,
79}
80
81#[derive(Serialize, Deserialize)]
82pub struct ValidatePreimage {
83 pub header: OracleHeader,
84 pub params: CallParams,
85 pub approval: bool,
86}
87
88impl ValidatePreimage {
89 const PREFIX: &'static [u8] = b"lyquor_validate_preimage_v1\0";
90
91 pub fn to_preimage(&self) -> Vec<u8> {
92 encode_object_with_prefix(Self::PREFIX, self)
93 }
94
95 pub fn to_hash(&self) -> Hash {
96 blake3::hash(&self.to_preimage())
97 }
98}
99
100#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
102pub struct OracleCert {
103 pub header: OracleHeader,
104 pub signers: Vec<SignerID>,
106 pub signatures: Vec<Bytes>,
108}
109
110#[inline]
112pub fn topic_from_group(group: &str) -> &str {
113 group.split_once("::").map(|(topic, _)| topic).unwrap_or(group)
114}
115
116pub const ORACLE_CERTIFIED_GROUP_PREFIX: &str = "oracle::certified::";
118
119#[inline]
125pub fn topic_from_dispatch_group(group: &str) -> &str {
126 let group = group.strip_prefix(ORACLE_CERTIFIED_GROUP_PREFIX).unwrap_or(group);
127 topic_from_group(group)
128}
129
130#[inline]
132pub fn group_with_topic_suffix(topic: &str, suffix: Option<&str>) -> String {
133 match suffix {
134 Some(s) if !s.is_empty() => format!("{topic}::{s}"),
135 _ => topic.to_string(),
136 }
137}
138
139pub mod eth {
140 use alloy_sol_types::{SolType, sol};
141 sol! {
142 struct OracleHeader {
143 bytes32 topic;
144 bytes32 group;
145 bytes32 proposer;
146 address target;
147 bytes32 seqId;
148 address ethContract;
149 bytes32 configHash;
150 uint32 epoch;
151 bytes32 nonce;
152 }
153
154 struct OracleSigner {
155 uint32 id;
156 address key;
157 }
158
159 struct OracleConfig {
160 OracleSigner[] committee;
161 uint16 threshold;
162 }
163
164 struct OracleConfigDelta {
165 OracleSigner[] upsert;
166 uint32[] remove;
167 bool thresholdChanged;
168 uint16 threshold;
169 }
170
171 enum ABI {
172 Lyquor,
173 Eth
174 }
175
176 struct CallParams {
177 address origin;
178 address caller;
179 string group;
180 string method;
181 bytes input;
182 ABI abi_;
183 }
184
185 struct ValidatePreimage {
186 OracleHeader header;
187 CallParams params;
188 bool approval; }
190 }
191
192 impl OracleConfig {
193 pub fn to_hash(&self) -> super::Hash {
194 alloy_primitives::keccak256(&OracleConfig::abi_encode(self)).0.into()
195 }
196 }
197
198 impl Default for OracleConfig {
199 fn default() -> Self {
200 Self {
201 committee: Default::default(),
202 threshold: Default::default(),
203 }
204 }
205 }
206
207 impl Default for OracleConfigDelta {
208 fn default() -> Self {
209 Self {
210 upsert: Default::default(),
211 remove: Default::default(),
212 thresholdChanged: false,
213 threshold: 0,
214 }
215 }
216 }
217
218 impl ValidatePreimage {
219 const PREFIX: &'static [u8] = b"lyquor_validate_preimage_v1\0";
220
221 pub fn to_preimage(&self) -> Vec<u8> {
222 let mut buf = Vec::from(Self::PREFIX);
223 buf.extend_from_slice(&Self::abi_encode(self));
224 buf
225 }
226
227 pub fn to_hash(&self) -> super::Hash {
228 alloy_primitives::keccak256(&self.to_preimage()).0.into()
229 }
230 }
231
232 impl From<OracleHeader> for super::OracleHeader {
233 fn from(oh: OracleHeader) -> Self {
234 Self {
235 proposer: <[u8; 32]>::from(oh.proposer).into(),
236 target: super::OracleTarget {
237 target: super::OracleServiceTarget::EVM {
238 target: oh.target,
239 eth_contract: oh.ethContract,
240 },
241 seq_id: <[u8; 32]>::from(oh.seqId).into(),
242 },
243 config_hash: <[u8; 32]>::from(oh.configHash).into(),
244 epoch: oh.epoch,
245 nonce: <[u8; 32]>::from(oh.nonce).into(),
246 }
247 }
248 }
249
250 impl From<super::OracleConfig> for OracleConfig {
251 fn from(oc: super::OracleConfig) -> Self {
252 Self {
253 committee: oc
254 .committee
255 .into_iter()
256 .map(|s| OracleSigner {
257 id: s.id,
258 key: s.key.as_ref().try_into().unwrap(),
259 })
260 .collect(),
261 threshold: oc.threshold as u16,
262 }
263 }
264 }
265
266 impl TryFrom<super::ValidatePreimage> for ValidatePreimage {
267 type Error = ();
268 fn try_from(om: super::ValidatePreimage) -> Result<Self, ()> {
269 let params = om.params;
270 let topic = super::topic_from_group(params.group.as_str());
271 let topic_hash = alloy_primitives::keccak256(topic.as_bytes());
272 let group_hash = alloy_primitives::keccak256(params.group.as_bytes());
273 let (target, eth_contract) = match om.header.target.target {
274 super::OracleServiceTarget::LVM(_) => return Err(()),
275 super::OracleServiceTarget::EVM { target, eth_contract } => (target, eth_contract),
276 };
277 let params = CallParams {
278 origin: params.origin,
279 caller: params.caller,
280 group: params.group,
281 method: params.method,
282 input: params.input.into(),
283 abi_: match params.abi {
284 super::InputABI::Lyquor => ABI::Lyquor,
285 super::InputABI::Eth => ABI::Eth,
286 },
287 };
288 let header = OracleHeader {
289 topic: topic_hash.into(),
290 group: group_hash.into(),
291 proposer: <[u8; 32]>::from(om.header.proposer).into(),
292 target,
293 seqId: <[u8; 32]>::from(om.header.target.seq_id).into(),
294 ethContract: eth_contract,
295 configHash: <[u8; 32]>::from(om.header.config_hash).into(),
296 epoch: om.header.epoch,
297 nonce: <[u8; 32]>::from(om.header.nonce).into(),
298 };
299
300 Ok(Self {
301 header,
302 params,
303 approval: om.approval,
304 })
305 }
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn topic_from_group_uses_first_segment() {
315 assert_eq!(topic_from_group("price_feed"), "price_feed");
316 assert_eq!(topic_from_group("price_feed::two_phase"), "price_feed");
317 }
318
319 #[test]
320 fn topic_from_dispatch_group_accepts_both_forms() {
321 assert_eq!(topic_from_dispatch_group("price_feed"), "price_feed");
322 assert_eq!(topic_from_dispatch_group("price_feed::two_phase"), "price_feed");
323 assert_eq!(topic_from_dispatch_group("oracle::certified::price_feed"), "price_feed");
324 assert_eq!(
325 topic_from_dispatch_group("oracle::certified::price_feed::two_phase"),
326 "price_feed"
327 );
328 }
329}