Skip to content

Commit

Permalink
feat: d3 protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
sevenzing committed Nov 1, 2024
1 parent 8b574f0 commit 70c8e18
Show file tree
Hide file tree
Showing 42 changed files with 6,647 additions and 299 deletions.
16 changes: 14 additions & 2 deletions blockscout-ens/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion blockscout-ens/bens-logic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2021"

[dependencies]
tokio = { version = "1.23", features = [ "rt-multi-thread", "macros" ] }
alloy-ccip-read = "0.2.0"
alloy-ccip-read = "0.3.0"
anyhow = "1"
hex = "0.4"
chrono = "0.4"
Expand Down
24 changes: 24 additions & 0 deletions blockscout-ens/bens-logic/src/entity/subgraph/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::collections::HashMap;

#[derive(Debug, Clone, Default, PartialEq, Eq, sqlx::FromRow)]
pub struct DetailedDomain {
pub vid: i64,
pub id: String,
pub name: Option<String>,
pub label_name: Option<String>,
Expand All @@ -18,10 +19,12 @@ pub struct DetailedDomain {
pub owner: String,
pub registrant: Option<String>,
pub wrapped_owner: Option<String>,
pub created_at: BigDecimal,
pub expiry_date: Option<chrono::DateTime<Utc>>,
pub is_expired: bool,
pub stored_offchain: bool,
pub resolved_with_wildcard: bool,
pub protocol_slug: String,
#[sqlx(default)]
pub other_addresses: sqlx::types::Json<HashMap<String, String>>,
}
Expand Down Expand Up @@ -86,3 +89,24 @@ pub struct AddrReverseDomainWithActualName {
pub resolved_address: String,
pub name: String,
}

impl From<DetailedDomain> for Domain {
fn from(domain: DetailedDomain) -> Self {
Self {
vid: domain.vid,
id: domain.id,
name: domain.name,
resolved_address: domain.resolved_address,
resolver: domain.resolver,
registration_date: domain.registration_date,
owner: domain.owner,
wrapped_owner: domain.wrapped_owner,
created_at: domain.created_at,
expiry_date: domain.expiry_date,
is_expired: domain.is_expired,
protocol_slug: domain.protocol_slug,
stored_offchain: domain.stored_offchain,
resolved_with_wildcard: domain.resolved_with_wildcard,
}
}
}
29 changes: 15 additions & 14 deletions blockscout-ens/bens-logic/src/protocols/domain_name.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use super::{domain_id, ProtocolError, Tld};
use crate::protocols::protocoler::DeployedProtocol;
use super::{hash_name::hash_ens_domain_name, ProtocolError, Tld};
use crate::{hex, protocols::protocoler::DeployedProtocol};
use alloy::primitives::{keccak256, Address, B256};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DomainName {
pub id: String,
pub id_bytes: B256,
pub label_name: String,
pub name: String,
pub empty_label_hash: Option<B256>,
Expand All @@ -17,13 +18,15 @@ impl DomainName {
pub fn new(name: &str, empty_label_hash: Option<B256>) -> Result<Self, ProtocolError> {
let name = ens_normalize(name)?;
let (label_name, _) = name.split_once(SEPARATOR).unwrap_or((&name, ""));
let id = domain_id(&name, empty_label_hash);
let id_bytes = hash_ens_domain_name(&name, empty_label_hash);
let id = hex(id_bytes);
let tld = Tld::from_domain_name(&name).ok_or_else(|| ProtocolError::InvalidName {
name: name.clone(),
reason: "tld not found".to_string(),
})?;
Ok(Self {
id,
id_bytes,
label_name: label_name.to_string(),
name: name.to_string(),
empty_label_hash,
Expand All @@ -32,18 +35,9 @@ impl DomainName {
}

pub fn addr_reverse(addr: &Address) -> Self {
// label name is hexed address without 0x prefix
let label_name = format!("{:x}", addr);
let name = format!("{}.addr.reverse", label_name);
// note that addr.reverse doesn't need empty_label_hash
let id = domain_id(&name, None);
Self {
id,
label_name,
name,
empty_label_hash: None,
tld: Tld::reverse(),
}
Self::new(&name, None).expect("addr.reverse is always valid")
}

/// Returns true if level of domain is greater than 1
Expand Down Expand Up @@ -99,7 +93,14 @@ impl<'a> DomainNameOnProtocol<'a> {
name: &str,
protocol_network: DeployedProtocol<'a>,
) -> Result<Self, ProtocolError> {
let name = DomainName::new(name, protocol_network.protocol.info.empty_label_hash)?;
let name = DomainName::new(
name,
protocol_network
.protocol
.info
.protocol_specific
.empty_label_hash(),
)?;

Ok(Self::new(name, protocol_network))
}
Expand Down
5 changes: 1 addition & 4 deletions blockscout-ens/bens-logic/src/protocols/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ mod protocoler;

pub use domain_name::{DomainName, DomainNameOnProtocol};
pub use hash_name::domain_id;
pub use protocoler::{
AddressResolveTechnique, DeployedProtocol, Network, OffchainStrategy, Protocol, ProtocolInfo,
ProtocolMeta, Protocoler, Tld,
};
pub use protocoler::*;

#[derive(thiserror::Error, Debug)]
pub enum ProtocolError {
Expand Down
69 changes: 54 additions & 15 deletions blockscout-ens/bens-logic/src/protocols/protocoler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,30 +54,69 @@ pub struct ProtocolInfo {
pub tld_list: NonEmpty<Tld>,
pub subgraph_name: String,
pub address_resolve_technique: AddressResolveTechnique,
pub empty_label_hash: Option<B256>,
pub native_token_contract: Option<Address>,
pub registry_contract: Option<Address>,
pub meta: ProtocolMeta,
pub offchain_strategy: OffchainStrategy,
pub protocol_specific: ProtocolSpecific,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum OffchainStrategy {
#[default]
None,
EnsWildcard,
D3Connect,
#[serde(tag = "type")]
pub enum ProtocolSpecific {
EnsLike(EnsLikeProtocol),
D3Connect(D3ConnectProtocol),
}

impl Default for ProtocolSpecific {
fn default() -> Self {
Self::EnsLike(Default::default())
}
}

impl OffchainStrategy {
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
impl ProtocolSpecific {
pub fn try_offchain_resolve(&self) -> bool {
match self {
ProtocolSpecific::EnsLike(ens) => ens.try_offchain_resolve,
ProtocolSpecific::D3Connect(d3) => !d3.disable_offchain_resolve,
}
}

pub fn is_some(&self) -> bool {
!self.is_none()
pub fn empty_label_hash(&self) -> Option<B256> {
match self {
ProtocolSpecific::EnsLike(ens) => ens.empty_label_hash,
ProtocolSpecific::D3Connect(_) => None,
}
}

pub fn native_token_contract(&self) -> Option<Address> {
match self {
ProtocolSpecific::EnsLike(ens) => ens.native_token_contract,
ProtocolSpecific::D3Connect(d3) => Some(d3.native_token_contract),
}
}

pub fn registry_contract(&self) -> Option<Address> {
match self {
ProtocolSpecific::EnsLike(ens) => ens.registry_contract,
ProtocolSpecific::D3Connect(_) => None,
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct EnsLikeProtocol {
pub registry_contract: Option<Address>,
pub empty_label_hash: Option<B256>,
pub native_token_contract: Option<Address>,
#[serde(default)]
pub try_offchain_resolve: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct D3ConnectProtocol {
pub resolver_contract: Address,
pub native_token_contract: Address,
#[serde(default)]
pub disable_offchain_resolve: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
Expand Down
34 changes: 28 additions & 6 deletions blockscout-ens/bens-logic/src/subgraph/domain_tokens.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::{DomainToken, DomainTokenType};
use crate::{entity::subgraph::domain::DetailedDomain, protocols::DomainNameOnProtocol};
use crate::{
entity::subgraph::domain::DetailedDomain,
protocols::{DomainNameOnProtocol, EnsLikeProtocol, ProtocolSpecific},
};
use alloy::primitives::Address;
use anyhow::Context;
use bigdecimal::{num_bigint::BigInt, Num};
Expand All @@ -10,7 +13,7 @@ use std::str::FromStr;
skip_all,
fields(
domain_name = domain.name,
native_token_contract = ?name.deployed_protocol.protocol.info.native_token_contract,
protocol_type = ?name.deployed_protocol.protocol.info.protocol_specific,
),
err,
)]
Expand All @@ -19,7 +22,26 @@ pub fn extract_tokens_from_domain(
name: &DomainNameOnProtocol<'_>,
) -> Result<Vec<DomainToken>, anyhow::Error> {
let mut tokens = vec![];
if let Some(contract) = name.deployed_protocol.protocol.info.native_token_contract {

match &name.deployed_protocol.protocol.info.protocol_specific {
ProtocolSpecific::EnsLike(ens_like) => {
extract_tokens_for_ens_like(domain, name, ens_like, &mut tokens)?;
}
ProtocolSpecific::D3Connect(_) => {
// TODO: implement D3Connect tokens extraction
}
}

Ok(tokens)
}

fn extract_tokens_for_ens_like(
domain: &DetailedDomain,
name: &DomainNameOnProtocol<'_>,
ens_like: &EnsLikeProtocol,
tokens: &mut Vec<DomainToken>,
) -> Result<(), anyhow::Error> {
if let Some(contract) = ens_like.native_token_contract {
let is_second_level_domain = name.inner.level() == 2;
let is_native_domain = name.tld_is_native();

Expand Down Expand Up @@ -50,7 +72,7 @@ pub fn extract_tokens_from_domain(
});
};

Ok(tokens)
Ok(())
}

fn token_id(hexed_id: &str) -> Result<String, anyhow::Error> {
Expand All @@ -64,7 +86,7 @@ mod tests {
use super::*;
use crate::{
blockscout::BlockscoutClient,
protocols::{DeployedProtocol, Network, Protocol, Tld},
protocols::{DeployedProtocol, EnsLikeProtocol, Network, Protocol, Tld},
};
use nonempty::nonempty;
use pretty_assertions::assert_eq;
Expand Down Expand Up @@ -182,7 +204,7 @@ mod tests {
] {
let mut protocol = Protocol::default();
protocol.info.tld_list = nonempty![Tld::new("eth")];
protocol.info.native_token_contract = native_token_contract;
protocol.info.protocol_specific = ProtocolSpecific::EnsLike(EnsLikeProtocol { native_token_contract, ..Default::default() });
let deployed_protocol = DeployedProtocol {
protocol: &protocol,
deployment_network: &Network {
Expand Down
22 changes: 22 additions & 0 deletions blockscout-ens/bens-logic/src/subgraph/offchain/ccip_read.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::protocols::{hash_name::CustomDomainIdGenerator, DeployedProtocol};
use alloy::{
providers::{ProviderBuilder, RootProvider},
transports::BoxTransport,
};
use alloy_ccip_read::CCIPReader;

pub type Reader = CCIPReader<RootProvider<BoxTransport>, CustomDomainIdGenerator>;

pub fn reader_from_protocol(d: &DeployedProtocol) -> Reader {
let domain_id_provider =
CustomDomainIdGenerator::new(d.protocol.info.protocol_specific.empty_label_hash());

let provider = ProviderBuilder::new()
.on_http(d.deployment_network.rpc_url())
.boxed();
let builder = alloy_ccip_read::CCIPReader::builder()
.with_provider(provider)
.with_domain_id_provider(domain_id_provider);

builder.build().expect("provider passed")
}
Loading

0 comments on commit 70c8e18

Please sign in to comment.