phos_data_network_precompiles/ip_graph/
dispatch.rs1use 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}