Skip to main content

lyquor_primitives/
oracle.rs

1use super::*;
2
3#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, Hash)]
4pub enum OracleServiceTarget {
5    // Lyquor network fn
6    LVM(LyquidID),
7    // EVM-based sequence backend
8    EVM {
9        /// Final destination contract that the sequencing contract calls into.
10        target: Address,
11        /// Sequencing contract on the destination backend that must receive the cert.
12        eth_contract: Address,
13    },
14}
15
16#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, Hash)]
17pub struct OracleTarget {
18    /// Service destination of the certified call.
19    pub target: OracleServiceTarget,
20    /// Sequence backend this target belongs to.
21    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/// Contains all fields needed to define a call other than the call parameters.
34#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug)]
35pub struct OracleHeader {
36    /// Node that proposed the call for certification.
37    pub proposer: NodeID,
38    /// Destination of the call (where it will be finally executed).
39    pub target: OracleTarget,
40    /// Oracle config digest.
41    pub config_hash: HashBytes,
42    /// Epoch number used by OracleDest.
43    pub epoch: u32,
44    /// Random nonce that uniquely identifies the certified call within an epoch.
45    pub nonce: HashBytes,
46}
47
48#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
49pub struct OracleEpochInfo {
50    pub epoch: u32,
51    pub config_hash: HashBytes,
52    pub change_count: u32,
53    pub config: Option<OracleConfig>,
54}
55
56pub type SignerID = u32;
57
58#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
59pub struct OracleSigner {
60    pub id: SignerID,
61    pub key: Bytes,
62}
63
64#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
65pub struct OracleConfig {
66    pub committee: Vec<OracleSigner>,
67    pub threshold: u16,
68}
69
70impl OracleConfig {
71    pub fn to_hash(&self) -> Hash {
72        blake3::hash(&encode_object(self))
73    }
74}
75
76#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
77pub struct OracleConfigDelta {
78    pub upsert: Vec<OracleSigner>,
79    pub remove: Vec<SignerID>,
80    pub threshold: Option<u16>,
81}
82
83#[derive(Serialize, Deserialize)]
84pub struct ValidatePreimage {
85    pub header: OracleHeader,
86    pub params: CallParams,
87    pub approval: bool,
88}
89
90impl ValidatePreimage {
91    const PREFIX: &'static [u8] = b"lyquor_validate_preimage_v1\0";
92
93    pub fn to_preimage(&self) -> Vec<u8> {
94        encode_object_with_prefix(Self::PREFIX, self)
95    }
96
97    pub fn to_hash(&self) -> Hash {
98        blake3::hash(&self.to_preimage())
99    }
100}
101
102/// Oracle certificate that could be sequenced.
103#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
104pub struct OracleCert {
105    pub header: OracleHeader,
106    /// Signers for the signatures in order.
107    pub signers: Vec<SignerID>,
108    /// Vote signatures.
109    pub signatures: Vec<Bytes>,
110}
111
112pub mod eth {
113    use crate::{Address, decode_by_fields};
114    use alloy_sol_types::{SolValue, sol};
115    sol! {
116        struct OracleHeader {
117            bytes32 topic;
118            bytes32 group;
119            bytes32 proposer;
120            address target;
121            bytes32 seqId;
122            address ethContract;
123            bytes32 configHash;
124            uint32 epoch;
125            bytes32 nonce;
126        }
127
128        struct OracleSigner {
129            uint32 id;
130            bytes32 nodeID;
131        }
132
133        #[derive(Default)]
134        struct OracleConfig {
135            OracleSigner[] committee;
136            uint16 threshold;
137        }
138
139        #[derive(Default)]
140        struct OracleConfigDelta {
141            OracleSigner[] upsert;
142            uint32[] remove;
143            bool thresholdChanged;
144            uint16 threshold;
145        }
146
147        enum ABI {
148            Lyquor,
149            Eth
150        }
151
152        struct CallParams {
153            address origin;
154            address caller;
155            string group;
156            string method;
157            bytes input;
158            ABI abi_;
159        }
160
161        struct ValidatePreimage {
162            OracleHeader header;
163            CallParams params;
164            bool approval; // Should always be true signed by multi-sigs that make up the final cert.
165        }
166    }
167
168    impl OracleConfig {
169        pub fn to_hash(&self) -> super::Hash {
170            alloy_primitives::keccak256(SolValue::abi_encode(self)).0.into()
171        }
172    }
173
174    impl TryFrom<super::OracleConfigDelta> for OracleConfigDelta {
175        type Error = ();
176
177        fn try_from(delta: super::OracleConfigDelta) -> Result<Self, Self::Error> {
178            let upsert = delta
179                .upsert
180                .into_iter()
181                .map(|signer| {
182                    let key = signer.key.as_ref();
183                    if key.len() != 32 {
184                        return Err(());
185                    }
186                    let node_id = <[u8; 32]>::try_from(key).map_err(|_| ())?.into();
187                    Ok(OracleSigner {
188                        id: signer.id,
189                        nodeID: node_id,
190                    })
191                })
192                .collect::<Result<Vec<_>, ()>>()?;
193
194            Ok(Self {
195                upsert,
196                remove: delta.remove,
197                thresholdChanged: delta.threshold.is_some(),
198                threshold: delta.threshold.unwrap_or(0),
199            })
200        }
201    }
202
203    impl ValidatePreimage {
204        const PREFIX: &'static [u8] = b"lyquor_validate_preimage_v1\0";
205
206        pub fn to_preimage(&self) -> Vec<u8> {
207            let mut buf = Vec::from(Self::PREFIX);
208            buf.extend_from_slice(&SolValue::abi_encode(self));
209            buf
210        }
211
212        pub fn to_hash(&self) -> super::Hash {
213            alloy_primitives::keccak256(self.to_preimage()).0.into()
214        }
215    }
216
217    impl From<OracleHeader> for super::OracleHeader {
218        fn from(oh: OracleHeader) -> Self {
219            Self {
220                proposer: <[u8; 32]>::from(oh.proposer).into(),
221                target: super::OracleTarget {
222                    target: super::OracleServiceTarget::EVM {
223                        target: oh.target,
224                        eth_contract: oh.ethContract,
225                    },
226                    seq_id: <[u8; 32]>::from(oh.seqId).into(),
227                },
228                config_hash: <[u8; 32]>::from(oh.configHash).into(),
229                epoch: oh.epoch,
230                nonce: <[u8; 32]>::from(oh.nonce).into(),
231            }
232        }
233    }
234
235    impl From<super::OracleConfig> for OracleConfig {
236        fn from(oc: super::OracleConfig) -> Self {
237            Self {
238                committee: oc
239                    .committee
240                    .into_iter()
241                    .map(|s| {
242                        let key = s.key;
243                        OracleSigner {
244                            id: s.id,
245                            nodeID: <[u8; 32]>::try_from(key.as_ref()).unwrap().into(),
246                        }
247                    })
248                    .collect(),
249                threshold: oc.threshold,
250            }
251        }
252    }
253
254    impl From<OracleConfig> for super::OracleConfig {
255        fn from(oc: OracleConfig) -> Self {
256            Self {
257                committee: oc
258                    .committee
259                    .into_iter()
260                    .map(|s| super::OracleSigner {
261                        id: s.id,
262                        key: s.nodeID.as_slice().to_vec().into(),
263                    })
264                    .collect(),
265                threshold: oc.threshold,
266            }
267        }
268    }
269
270    impl TryFrom<super::ValidatePreimage> for ValidatePreimage {
271        type Error = ();
272        fn try_from(om: super::ValidatePreimage) -> Result<Self, ()> {
273            let params = om.params;
274            let is_epoch_advance = params.origin == Address::ZERO &&
275                params.abi == super::InputABI::Lyquor &&
276                params.group == "oracle::internal" &&
277                params.method == "__lyquor_oracle_on_epoch_advance";
278            let (topic, input) = if is_epoch_advance {
279                let payload = decode_by_fields!(
280                    params.input.as_ref(),
281                    topic: String,
282                    config_delta: super::OracleConfigDelta,
283                    change_count: u32
284                )
285                .ok_or(())?;
286                let config_delta = OracleConfigDelta::try_from(payload.config_delta)?;
287                let topic = payload.topic;
288                let input = (config_delta, payload.change_count).abi_encode_params();
289                (topic, input)
290            } else {
291                (
292                    params
293                        .group
294                        .split_once("::")
295                        .map(|(topic, _)| topic)
296                        .unwrap_or(params.group.as_str())
297                        .to_string(),
298                    params.input.to_vec(),
299                )
300            };
301            let topic_hash = alloy_primitives::keccak256(topic.as_bytes());
302            let group_hash = alloy_primitives::keccak256(params.group.as_bytes());
303            let (target, eth_contract) = match om.header.target.target {
304                super::OracleServiceTarget::LVM(_) => return Err(()),
305                super::OracleServiceTarget::EVM { target, eth_contract } => (target, eth_contract),
306            };
307            let params = CallParams {
308                origin: params.origin,
309                caller: params.caller,
310                group: params.group,
311                method: params.method,
312                input: input.into(),
313                abi_: match params.abi {
314                    super::InputABI::Lyquor => ABI::Lyquor,
315                    super::InputABI::Eth => ABI::Eth,
316                },
317            };
318            let header = OracleHeader {
319                topic: topic_hash,
320                group: group_hash,
321                proposer: <[u8; 32]>::from(om.header.proposer).into(),
322                target,
323                seqId: <[u8; 32]>::from(om.header.target.seq_id).into(),
324                ethContract: eth_contract,
325                configHash: <[u8; 32]>::from(om.header.config_hash).into(),
326                epoch: om.header.epoch,
327                nonce: <[u8; 32]>::from(om.header.nonce).into(),
328            };
329
330            Ok(Self {
331                header,
332                params,
333                approval: om.approval,
334            })
335        }
336    }
337}