Skip to content

Commit cfccd53

Browse files
committed
fix: validate dips pricing
1 parent 0a11da4 commit cfccd53

File tree

9 files changed

+172
-22
lines changed

9 files changed

+172
-22
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ members = [
44
"crates/attestation",
55
"crates/config",
66
"crates/dips",
7-
"crates/indexer-receipt",
7+
"crates/indexer-receipt",
88
"crates/monitor",
99
"crates/query",
1010
"crates/service",
@@ -82,6 +82,7 @@ tonic-build = "0.12.3"
8282
serde_yaml = "0.9.21"
8383
bon = "3.3"
8484
test-log = { version = "0.2.12", features = ["trace"] }
85+
graph-networks-registry = "0.6.1"
8586

8687
[patch.crates-io.tap_core]
8788
git = "https://github.com/semiotic-ai/timeline-aggregation-protocol"

crates/dips/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,23 @@ uuid.workspace = true
1818
tokio.workspace = true
1919
indexer-monitor = { path = "../monitor" }
2020
tracing.workspace = true
21+
graph-networks-registry.workspace = true
2122

2223
bytes = { version = "1.10.0", optional = true }
2324
derivative = "2.2.0"
2425

2526
futures = "0.3"
2627
http = "0.2"
2728
prost = { workspace = true, optional = true }
28-
ipfs-api-backend-hyper = { version = "0.6.0", features = ["with-send-sync", "with-hyper-tls"] }
29+
ipfs-api-backend-hyper = { version = "0.6.0", features = [
30+
"with-send-sync",
31+
"with-hyper-tls",
32+
] }
2933
serde_yaml.workspace = true
3034
serde.workspace = true
3135
sqlx = { workspace = true, optional = true }
3236
tonic = { workspace = true, optional = true }
37+
serde_json.workspace = true
3338

3439
[dev-dependencies]
3540
rand = "0.9.0"

crates/dips/src/lib.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use std::{str::FromStr, sync::Arc};
66
use server::DipsServerContext;
77
use thegraph_core::alloy::{
88
core::primitives::Address,
9-
primitives::{b256, ChainId, PrimitiveSignature as Signature, Uint, B256},
9+
primitives::{
10+
b256, ruint::aliases::U256, ChainId, PrimitiveSignature as Signature, Uint, B256,
11+
},
1012
signers::SignerSync,
1113
sol,
1214
sol_types::{eip712_domain, Eip712Domain, SolStruct, SolValue},
@@ -18,6 +20,7 @@ pub mod ipfs;
1820
pub mod price;
1921
#[cfg(feature = "rpc")]
2022
pub mod proto;
23+
pub mod registry;
2124
#[cfg(feature = "rpc")]
2225
pub mod server;
2326
pub mod signers;
@@ -145,8 +148,12 @@ pub enum DipsError {
145148
InvalidSubgraphManifest(String),
146149
#[error("chainId {0} is not supported")]
147150
UnsupportedChainId(String),
148-
#[error("price per block is below configured price for chain {0}, minimum: {1}, offered: {2}")]
149-
PricePerBlockTooLow(String, u64, String),
151+
#[error("price per epoch is below configured price for chain {0}, minimum: {1}, offered: {2}")]
152+
PricePerEpochTooLow(String, u64, String),
153+
#[error(
154+
"price per entity is below configured price for chain {0}, minimum: {1}, offered: {2}"
155+
)]
156+
PricePerEntityTooLow(String, u64, String),
150157
// cancellation
151158
#[error("cancelled_by is expected to match the signer")]
152159
UnexpectedSigner,
@@ -304,6 +311,7 @@ pub async fn validate_and_create_agreement(
304311
ipfs_fetcher,
305312
price_calculator,
306313
signer_validator,
314+
registry,
307315
} = ctx.as_ref();
308316
let decoded_voucher = SignedIndexingAgreementVoucher::abi_decode(voucher.as_ref(), true)
309317
.map_err(|e| DipsError::AbiDecoding(e.to_string()))?;
@@ -330,19 +338,33 @@ pub async fn validate_and_create_agreement(
330338
}
331339
}
332340

333-
let offered_price = metadata.pricePerEntity;
341+
let network = match registry.get_network_by_id(&metadata.chainId) {
342+
Some(network) => network.id.clone(),
343+
None => return Err(DipsError::UnsupportedChainId(metadata.chainId)),
344+
};
345+
346+
let offered_epoch_price = metadata.basePricePerEpoch;
334347
match price_calculator.get_minimum_price(&metadata.chainId) {
335-
Some(price) if offered_price.lt(&Uint::from(price)) => {
336-
return Err(DipsError::PricePerBlockTooLow(
337-
metadata.chainId,
348+
Some(price) if offered_epoch_price.lt(&Uint::from(price)) => {
349+
return Err(DipsError::PricePerEpochTooLow(
350+
network,
338351
price,
339-
offered_price.to_string(),
352+
offered_epoch_price.to_string(),
340353
))
341354
}
342355
Some(_) => {}
343356
None => return Err(DipsError::UnsupportedChainId(metadata.chainId)),
344357
}
345358

359+
let offered_entity_price = metadata.pricePerEntity;
360+
if offered_entity_price < U256::from(price_calculator.entity_price()) {
361+
return Err(DipsError::PricePerEntityTooLow(
362+
network,
363+
price_calculator.entity_price(),
364+
offered_entity_price.to_string(),
365+
));
366+
}
367+
346368
store
347369
.create_agreement(decoded_voucher.clone(), metadata)
348370
.await?;
@@ -754,7 +776,17 @@ mod test {
754776
subgraphDeploymentId: voucher_ctx.deployment_id.clone(),
755777
};
756778

757-
let low_price_voucher = voucher_ctx.test_voucher(metadata);
779+
let low_entity_price_voucher = voucher_ctx.test_voucher(metadata);
780+
781+
let metadata = SubgraphIndexingVoucherMetadata {
782+
basePricePerEpoch: U256::from(10_u64),
783+
pricePerEntity: U256::from(10000_u64),
784+
protocolNetwork: "eip155:42161".to_string(),
785+
chainId: "mainnet".to_string(),
786+
subgraphDeploymentId: voucher_ctx.deployment_id.clone(),
787+
};
788+
789+
let low_epoch_price_voucher = voucher_ctx.test_voucher(metadata);
758790

759791
let metadata = SubgraphIndexingVoucherMetadata {
760792
basePricePerEpoch: U256::from(10000_u64),
@@ -775,11 +807,16 @@ mod test {
775807
Err(DipsError::InvalidSubgraphManifest(
776808
voucher_ctx.deployment_id.clone(),
777809
)),
778-
Err(DipsError::PricePerBlockTooLow(
810+
Err(DipsError::PricePerEntityTooLow(
779811
"mainnet".to_string(),
780812
100,
781813
"10".to_string(),
782814
)),
815+
Err(DipsError::PricePerEpochTooLow(
816+
"mainnet".to_string(),
817+
200,
818+
"10".to_string(),
819+
)),
783820
Err(DipsError::SignerNotAuthorised(signer.address())),
784821
Ok(valid_voucher
785822
.voucher
@@ -790,7 +827,8 @@ mod test {
790827
];
791828
let cases = vec![
792829
no_network_voucher,
793-
low_price_voucher,
830+
low_entity_price_voucher,
831+
low_epoch_price_voucher,
794832
valid_voucher_invalid_signer,
795833
valid_voucher,
796834
];

crates/dips/src/price.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,27 @@ use std::collections::HashMap;
55

66
#[derive(Debug, Default)]
77
pub struct PriceCalculator {
8-
prices_per_chain: HashMap<String, u64>,
9-
default_price: Option<u64>,
8+
base_price_per_epoch: HashMap<String, u64>,
9+
price_per_entity: u64,
1010
}
1111

1212
impl PriceCalculator {
1313
#[cfg(test)]
1414
pub fn for_testing() -> Self {
1515
Self {
16-
prices_per_chain: HashMap::default(),
17-
default_price: Some(100),
16+
base_price_per_epoch: HashMap::from_iter(vec![("mainnet".to_string(), 200)]),
17+
price_per_entity: 100,
1818
}
1919
}
2020

2121
pub fn is_supported(&self, chain_id: &str) -> bool {
2222
self.get_minimum_price(chain_id).is_some()
2323
}
2424
pub fn get_minimum_price(&self, chain_id: &str) -> Option<u64> {
25-
let chain_price = self.prices_per_chain.get(chain_id).copied();
25+
self.base_price_per_epoch.get(chain_id).copied()
26+
}
2627

27-
chain_price.or(self.default_price)
28+
pub fn entity_price(&self) -> u64 {
29+
self.price_per_entity
2830
}
2931
}

crates/dips/src/registry.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#[cfg(test)]
2+
use graph_networks_registry::NetworksRegistry;
3+
4+
#[cfg(test)]
5+
pub fn test_registry() -> NetworksRegistry {
6+
use graph_networks_registry::{Network, NetworkType, Services};
7+
8+
NetworksRegistry {
9+
schema: "".to_string(),
10+
description: "".to_string(),
11+
networks: vec![
12+
Network {
13+
aliases: None,
14+
api_urls: None,
15+
caip2_id: "eip155:1".to_string(),
16+
docs_url: None,
17+
explorer_urls: None,
18+
firehose: None,
19+
full_name: "".to_string(),
20+
genesis: None,
21+
graph_node: None,
22+
icon: None,
23+
id: "mainnet".to_string(),
24+
indexer_docs_urls: None,
25+
issuance_rewards: false,
26+
native_token: None,
27+
network_type: NetworkType::Mainnet,
28+
relations: None,
29+
rpc_urls: None,
30+
second_name: None,
31+
services: Services {
32+
firehose: None,
33+
sps: None,
34+
subgraphs: None,
35+
substreams: None,
36+
},
37+
short_name: "mainnet".to_string(),
38+
},
39+
Network {
40+
aliases: None,
41+
api_urls: None,
42+
caip2_id: "eip155:1337".to_string(),
43+
docs_url: None,
44+
explorer_urls: None,
45+
firehose: None,
46+
full_name: "".to_string(),
47+
genesis: None,
48+
graph_node: None,
49+
icon: None,
50+
id: "hardhat".to_string(),
51+
indexer_docs_urls: None,
52+
issuance_rewards: false,
53+
native_token: None,
54+
network_type: NetworkType::Mainnet,
55+
relations: None,
56+
rpc_urls: None,
57+
second_name: None,
58+
services: Services {
59+
firehose: None,
60+
sps: None,
61+
subgraphs: None,
62+
substreams: None,
63+
},
64+
short_name: "hardhat".to_string(),
65+
},
66+
],
67+
title: "".to_string(),
68+
updated_at: "".to_string(),
69+
version: "".to_string(),
70+
}
71+
}

crates/dips/src/server.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use std::sync::Arc;
55

66
use async_trait::async_trait;
7+
use graph_networks_registry::NetworksRegistry;
78
#[cfg(test)]
89
use indexer_monitor::EscrowAccounts;
910
use thegraph_core::alloy::primitives::{Address, ChainId};
@@ -29,20 +30,24 @@ pub struct DipsServerContext {
2930
pub ipfs_fetcher: Arc<dyn IpfsFetcher>,
3031
pub price_calculator: PriceCalculator,
3132
pub signer_validator: Arc<dyn SignerValidator>,
33+
pub registry: Arc<NetworksRegistry>,
3234
}
3335

3436
impl DipsServerContext {
3537
#[cfg(test)]
3638
pub fn for_testing() -> Arc<Self> {
3739
use std::sync::Arc;
3840

39-
use crate::{ipfs::TestIpfsClient, signers, test::InMemoryAgreementStore};
41+
use crate::{
42+
ipfs::TestIpfsClient, registry::test_registry, signers, test::InMemoryAgreementStore,
43+
};
4044

4145
Arc::new(DipsServerContext {
4246
store: Arc::new(InMemoryAgreementStore::default()),
4347
ipfs_fetcher: Arc::new(TestIpfsClient::mainnet()),
4448
price_calculator: PriceCalculator::for_testing(),
4549
signer_validator: Arc::new(signers::NoopSignerValidator),
50+
registry: Arc::new(test_registry()),
4651
})
4752
}
4853

@@ -55,18 +60,22 @@ impl DipsServerContext {
5560
ipfs_fetcher: Arc::new(TestIpfsClient::mainnet()),
5661
price_calculator: PriceCalculator::for_testing(),
5762
signer_validator: Arc::new(signers::EscrowSignerValidator::mock(accounts).await),
63+
registry: Arc::new(crate::registry::test_registry()),
5864
})
5965
}
6066

6167
#[cfg(test)]
6268
pub async fn for_testing_mocked_accounts_no_network(accounts: EscrowAccounts) -> Arc<Self> {
63-
use crate::{ipfs::TestIpfsClient, signers, test::InMemoryAgreementStore};
69+
use crate::{
70+
ipfs::TestIpfsClient, registry::test_registry, signers, test::InMemoryAgreementStore,
71+
};
6472

6573
Arc::new(DipsServerContext {
6674
store: Arc::new(InMemoryAgreementStore::default()),
6775
ipfs_fetcher: Arc::new(TestIpfsClient::no_network()),
6876
price_calculator: PriceCalculator::for_testing(),
6977
signer_validator: Arc::new(signers::EscrowSignerValidator::mock(accounts).await),
78+
registry: Arc::new(test_registry()),
7079
})
7180
}
7281
}

0 commit comments

Comments
 (0)