Skip to main content

phos_data_network_precompiles/ip_graph/
dispatch.rs

1use alloy_primitives::{Bytes, U256};
2use alloy_sol_types::{sol, SolCall, SolInterface};
3use revm::interpreter::CallScheme;
4use revm::precompile::{PrecompileError, PrecompileResult};
5
6use super::IpGraph;
7use crate::{
8    error::{DataNetworkPrecompileError, IntoPrecompileResult, Result},
9    storage::StorageCtx,
10    Precompile,
11};
12
13const IP_GRAPH_WRITE_GAS: u64 = 100;
14const IP_GRAPH_READ_GAS: u64 = 10;
15const AVERAGE_ANCESTOR_IP_COUNT: u64 = 30;
16const AVERAGE_PARENT_IP_COUNT: u64 = 4;
17const INTRINSIC_GAS: u64 = 1_000;
18const IP_GRAPH_EXTERNAL_READ_GAS: u64 = 2_100;
19
20sol! {
21    interface IIpGraph {
22        function addParentIp(address ipId, address[] parentIpIds) external;
23        function hasParentIp(address ipId, address parentIpId) external view returns (bool);
24        function getParentIps(address ipId) external view returns (address[] memory);
25        function getParentIpsCount(address ipId) external view returns (uint256);
26        function getAncestorIps(address ipId) external view returns (address[] memory);
27        function getAncestorIpsCount(address ipId) external view returns (uint256);
28        function hasAncestorIp(address ipId, address ancestorIpId) external view returns (bool);
29        function setRoyalty(
30            address ipId,
31            address parentIpId,
32            uint256 royaltyPolicyKind,
33            uint256 royalty
34        ) external;
35        function getRoyalty(
36            address ipId,
37            address ancestorIpId,
38            uint256 royaltyPolicyKind
39        ) external view returns (uint256);
40        function getRoyaltyStack(
41            address ipId,
42            uint256 royaltyPolicyKind
43        ) external view returns (uint256);
44        function hasParentIpExt(
45            address ipId,
46            address parentIpId
47        ) external view returns (bool);
48        function getParentIpsExt(address ipId) external view returns (address[] memory);
49        function getParentIpsCountExt(address ipId) external view returns (uint256);
50        function getAncestorIpsExt(address ipId) external view returns (address[] memory);
51        function getAncestorIpsCountExt(address ipId) external view returns (uint256);
52        function hasAncestorIpExt(
53            address ipId,
54            address ancestorIpId
55        ) external view returns (bool);
56        function getRoyaltyExt(
57            address ipId,
58            address ancestorIpId,
59            uint256 royaltyPolicyKind
60        ) external view returns (uint256);
61        function getRoyaltyStackExt(
62            address ipId,
63            uint256 royaltyPolicyKind
64        ) external view returns (uint256);
65    }
66}
67
68use IIpGraph::IIpGraphCalls;
69
70impl Precompile for IpGraph {
71    fn call(
72        &mut self,
73        calldata: &[u8],
74        msg_sender: alloy_primitives::Address,
75        call_scheme: CallScheme,
76    ) -> PrecompileResult {
77        dispatch_call(calldata, IIpGraphCalls::abi_decode, |call| match call {
78            IIpGraphCalls::addParentIp(call) => {
79                mutate_void(call, msg_sender, call_scheme, |sender, call| {
80                    self.add_parent_ip(sender, call.ipId, call.parentIpIds)
81                })
82            }
83            IIpGraphCalls::hasParentIp(call) => view(call, call_scheme, |call| {
84                self.has_parent_ip(msg_sender, call.ipId, call.parentIpId)
85            }),
86            IIpGraphCalls::getParentIps(call) => view(call, call_scheme, |call| {
87                self.get_parent_ips(msg_sender, call.ipId)
88            }),
89            IIpGraphCalls::getParentIpsCount(call) => view(call, call_scheme, |call| {
90                self.get_parent_ips_count(msg_sender, call.ipId)
91            }),
92            IIpGraphCalls::getAncestorIps(call) => view(call, call_scheme, |call| {
93                self.get_ancestor_ips(msg_sender, call.ipId)
94            }),
95            IIpGraphCalls::getAncestorIpsCount(call) => view(call, call_scheme, |call| {
96                self.get_ancestor_ips_count(msg_sender, call.ipId)
97            }),
98            IIpGraphCalls::hasAncestorIp(call) => view(call, call_scheme, |call| {
99                self.has_ancestor_ip(msg_sender, call.ipId, call.ancestorIpId)
100            }),
101            IIpGraphCalls::setRoyalty(call) => {
102                mutate_void(call, msg_sender, call_scheme, |sender, call| {
103                    self.set_royalty(
104                        sender,
105                        call.ipId,
106                        call.parentIpId,
107                        call.royaltyPolicyKind,
108                        call.royalty,
109                    )
110                })
111            }
112            IIpGraphCalls::getRoyalty(call) => view(call, call_scheme, |call| {
113                self.get_royalty(
114                    msg_sender,
115                    call.ipId,
116                    call.ancestorIpId,
117                    call.royaltyPolicyKind,
118                )
119            }),
120            IIpGraphCalls::getRoyaltyStack(call) => view(call, call_scheme, |call| {
121                self.get_royalty_stack(msg_sender, call.ipId, call.royaltyPolicyKind)
122            }),
123            IIpGraphCalls::hasParentIpExt(call) => view(call, call_scheme, |call| {
124                self.has_parent_ip(msg_sender, call.ipId, call.parentIpId)
125            }),
126            IIpGraphCalls::getParentIpsExt(call) => view(call, call_scheme, |call| {
127                self.get_parent_ips(msg_sender, call.ipId)
128            }),
129            IIpGraphCalls::getParentIpsCountExt(call) => view(call, call_scheme, |call| {
130                self.get_parent_ips_count(msg_sender, call.ipId)
131            }),
132            IIpGraphCalls::getAncestorIpsExt(call) => view(call, call_scheme, |call| {
133                self.get_ancestor_ips(msg_sender, call.ipId)
134            }),
135            IIpGraphCalls::getAncestorIpsCountExt(call) => view(call, call_scheme, |call| {
136                self.get_ancestor_ips_count(msg_sender, call.ipId)
137            }),
138            IIpGraphCalls::hasAncestorIpExt(call) => view(call, call_scheme, |call| {
139                self.has_ancestor_ip(msg_sender, call.ipId, call.ancestorIpId)
140            }),
141            IIpGraphCalls::getRoyaltyExt(call) => view(call, call_scheme, |call| {
142                self.get_royalty(
143                    msg_sender,
144                    call.ipId,
145                    call.ancestorIpId,
146                    call.royaltyPolicyKind,
147                )
148            }),
149            IIpGraphCalls::getRoyaltyStackExt(call) => view(call, call_scheme, |call| {
150                self.get_royalty_stack(msg_sender, call.ipId, call.royaltyPolicyKind)
151            }),
152        })
153    }
154}
155
156#[inline]
157fn view<T: SolCall>(
158    call: T,
159    call_scheme: CallScheme,
160    f: impl FnOnce(T) -> Result<T::Return>,
161) -> PrecompileResult {
162    if call_scheme == CallScheme::DelegateCall {
163        return Err(DataNetworkPrecompileError::Revert(
164            "DELEGATECALL is not allowed for IP Graph reads",
165        )
166        .into());
167    }
168
169    f(call).into_precompile_result(|value| T::abi_encode_returns(&value).into())
170}
171
172#[inline]
173fn mutate_void<T: SolCall>(
174    call: T,
175    sender: alloy_primitives::Address,
176    call_scheme: CallScheme,
177    f: impl FnOnce(alloy_primitives::Address, T) -> Result<()>,
178) -> PrecompileResult {
179    if call_scheme != CallScheme::Call {
180        return Err(DataNetworkPrecompileError::Revert("IP Graph writes require CALL").into());
181    }
182
183    if StorageCtx.is_static() {
184        return Err(
185            DataNetworkPrecompileError::Revert("state modification during static call").into(),
186        );
187    }
188
189    f(sender, call).into_precompile_result(|()| Bytes::new())
190}
191
192#[inline]
193fn dispatch_call<T: SolInterface>(
194    calldata: &[u8],
195    decode: impl FnOnce(&[u8]) -> core::result::Result<T, alloy_sol_types::Error>,
196    f: impl FnOnce(T) -> PrecompileResult,
197) -> PrecompileResult {
198    if calldata.len() < 4 {
199        return Err(PrecompileError::Other("input too short".into()));
200    }
201
202    match decode(calldata) {
203        Ok(call) if calldata.len() == call.abi_encoded_size().saturating_add(4) => f(call),
204        Ok(_) => Err(PrecompileError::Other("invalid input length".into())),
205        Err(alloy_sol_types::Error::UnknownSelector { .. }) => {
206            Err(PrecompileError::Other("unknown selector".into()))
207        }
208        Err(_) => Err(PrecompileError::Other("invalid input".into())),
209    }
210}
211
212impl IpGraph {
213    pub fn required_gas(&self, input: &[u8]) -> u64 {
214        if input.len() < 4 {
215            return INTRINSIC_GAS;
216        }
217
218        let read_word = |data: &[u8], start: usize| {
219            let mut word = [0u8; 32];
220            let start = start.min(data.len());
221            let end = start.saturating_add(32).min(data.len());
222            word[..end - start].copy_from_slice(&data[start..end]);
223            U256::from_be_bytes(word)
224        };
225
226        let selector = &input[..4];
227
228        if selector == IIpGraph::addParentIpCall::SELECTOR {
229            let args = &input[4..];
230            let parent_count = read_word(args, 64);
231            if parent_count > U256::from(1_024) {
232                return u64::MAX;
233            }
234            return INTRINSIC_GAS + IP_GRAPH_WRITE_GAS * parent_count.to::<u64>();
235        }
236
237        if selector == IIpGraph::hasParentIpCall::SELECTOR
238            || selector == IIpGraph::getParentIpsCall::SELECTOR
239        {
240            return IP_GRAPH_READ_GAS * AVERAGE_PARENT_IP_COUNT;
241        }
242
243        if selector == IIpGraph::getParentIpsCountCall::SELECTOR {
244            return IP_GRAPH_READ_GAS;
245        }
246
247        if selector == IIpGraph::getAncestorIpsCall::SELECTOR
248            || selector == IIpGraph::hasAncestorIpCall::SELECTOR
249        {
250            return IP_GRAPH_READ_GAS * AVERAGE_ANCESTOR_IP_COUNT * 2;
251        }
252
253        if selector == IIpGraph::getAncestorIpsCountCall::SELECTOR {
254            return IP_GRAPH_READ_GAS * AVERAGE_PARENT_IP_COUNT * 2;
255        }
256
257        if selector == IIpGraph::setRoyaltyCall::SELECTOR {
258            return IP_GRAPH_WRITE_GAS;
259        }
260
261        if selector == IIpGraph::getRoyaltyCall::SELECTOR {
262            let royalty_policy_kind = read_word(input, 64 + 4);
263            return match royalty_policy_kind {
264                U256::ZERO => IP_GRAPH_READ_GAS * AVERAGE_ANCESTOR_IP_COUNT * 3,
265                U256::ONE => IP_GRAPH_READ_GAS * (AVERAGE_ANCESTOR_IP_COUNT * 2 + 2),
266                _ => INTRINSIC_GAS,
267            };
268        }
269
270        if selector == IIpGraph::getRoyaltyStackCall::SELECTOR {
271            let royalty_policy_kind = read_word(input, 32 + 4);
272            return match royalty_policy_kind {
273                U256::ZERO => IP_GRAPH_READ_GAS * (AVERAGE_PARENT_IP_COUNT + 1),
274                U256::ONE => IP_GRAPH_READ_GAS * AVERAGE_ANCESTOR_IP_COUNT * 2,
275                _ => INTRINSIC_GAS,
276            };
277        }
278
279        if selector == IIpGraph::hasParentIpExtCall::SELECTOR
280            || selector == IIpGraph::getParentIpsExtCall::SELECTOR
281        {
282            return IP_GRAPH_EXTERNAL_READ_GAS * AVERAGE_PARENT_IP_COUNT;
283        }
284
285        if selector == IIpGraph::getParentIpsCountExtCall::SELECTOR {
286            return IP_GRAPH_EXTERNAL_READ_GAS;
287        }
288
289        if selector == IIpGraph::getAncestorIpsExtCall::SELECTOR
290            || selector == IIpGraph::hasAncestorIpExtCall::SELECTOR
291        {
292            return IP_GRAPH_EXTERNAL_READ_GAS * AVERAGE_ANCESTOR_IP_COUNT * 2;
293        }
294
295        if selector == IIpGraph::getAncestorIpsCountExtCall::SELECTOR {
296            return IP_GRAPH_EXTERNAL_READ_GAS * AVERAGE_PARENT_IP_COUNT * 2;
297        }
298
299        if selector == IIpGraph::getRoyaltyExtCall::SELECTOR {
300            let royalty_policy_kind = read_word(input, 64 + 4);
301            return match royalty_policy_kind {
302                U256::ZERO => IP_GRAPH_EXTERNAL_READ_GAS * AVERAGE_ANCESTOR_IP_COUNT * 3,
303                U256::ONE => IP_GRAPH_EXTERNAL_READ_GAS * (AVERAGE_ANCESTOR_IP_COUNT * 2 + 2),
304                _ => INTRINSIC_GAS,
305            };
306        }
307
308        if selector == IIpGraph::getRoyaltyStackExtCall::SELECTOR {
309            let royalty_policy_kind = read_word(input, 32 + 4);
310            return match royalty_policy_kind {
311                U256::ZERO => IP_GRAPH_EXTERNAL_READ_GAS * (AVERAGE_PARENT_IP_COUNT + 1),
312                U256::ONE => IP_GRAPH_EXTERNAL_READ_GAS * AVERAGE_ANCESTOR_IP_COUNT * 2,
313                _ => INTRINSIC_GAS,
314            };
315        }
316
317        INTRINSIC_GAS
318    }
319}