eth_twc_sdk/
utils.rs

1//! チェーン検証および EIP-712 `domain` と設定アドレスの照合。
2
3use alloy::primitives::Address;
4use alloy::providers::Provider;
5use alloy::sol_types::Eip712Domain;
6
7use crate::config::{
8    self, assert_transfer_contract_configured, EIP712_DOMAIN_NAME, EIP712_DOMAIN_VERSION,
9};
10use crate::error::SdkError;
11
12/// EIP-712 `domain.chainId`([`U256`](alloy::primitives::U256))が、数値のクライアントチェーン ID と等しいか。
13#[inline]
14pub fn chain_id_matches(domain_chain_id: alloy::primitives::U256, client_chain_id: u64) -> bool {
15    domain_chain_id == alloy::primitives::U256::from(client_chain_id)
16}
17
18/// 接続先に canonical TWC がデプロイされていることを `eth_getCode` で確認する。
19pub async fn assert_transfer_contract_deployed<P: Provider>(
20    provider: &P,
21) -> Result<(), SdkError> {
22    assert_transfer_contract_configured()?;
23    let addr = config::transfer_with_commitment_address();
24    let chain_id = provider.get_chain_id().await?;
25    let code = provider.get_code_at(addr).await?;
26    if code.is_empty() {
27        return Err(SdkError::ContractNotDeployed {
28            address: addr,
29            chain_id,
30        });
31    }
32    Ok(())
33}
34
35/// `assert_transfer_contract_deployed` のエイリアス(旧名前との互換)。
36#[inline]
37pub async fn assert_provider_supported_chain<P: Provider>(
38    provider: &P,
39) -> Result<(), SdkError> {
40    assert_transfer_contract_deployed(provider).await
41}
42
43/// オンチェーンから読んだ EIP-712 ドメインが設定 TWC / 接続チェーンと一致すること。
44pub fn assert_onchain_eip712_domain_matches_config(
45    domain: &Eip712Domain,
46    provider_chain_id: u64,
47) -> Result<(), SdkError> {
48    let twc = config::transfer_with_commitment_address();
49    let vc = domain.verifying_contract.ok_or_else(|| {
50        SdkError::Alloy("eip712Domain: verifyingContract missing".into())
51    })?;
52    if vc != twc {
53        return Err(SdkError::DomainVerifyingContractMismatch {
54            domain: vc,
55            configured: twc,
56        });
57    }
58    let domain_chain = domain
59        .chain_id
60        .ok_or_else(|| SdkError::Alloy("eip712Domain: chainId missing".into()))?;
61    if !chain_id_matches(domain_chain, provider_chain_id) {
62        return Err(SdkError::DomainChainIdMismatch {
63            domain: domain_chain.to_string(),
64            client: provider_chain_id,
65        });
66    }
67    let name_ok = domain
68        .name
69        .as_ref()
70        .map(|n| n.as_ref() == EIP712_DOMAIN_NAME)
71        .unwrap_or(false);
72    if !name_ok {
73        return Err(SdkError::Alloy(format!(
74            "eip712Domain: unexpected name (expected {EIP712_DOMAIN_NAME})"
75        )));
76    }
77    let ver_ok = domain
78        .version
79        .as_ref()
80        .map(|v| v.as_ref() == EIP712_DOMAIN_VERSION)
81        .unwrap_or(false);
82    if !ver_ok {
83        return Err(SdkError::Alloy(format!(
84            "eip712Domain: unexpected version (expected {EIP712_DOMAIN_VERSION})"
85        )));
86    }
87    Ok(())
88}
89
90/// 署名済みバンドルの `Eip712Domain` が、クライアントのチェーンおよび設定コントラクトアドレスと整合するか検証する。
91pub fn assert_signed_domain_matches_client_and_config(
92    client_chain_id: u64,
93    domain_chain_id: alloy::primitives::U256,
94    domain_verifying_contract: Address,
95    configured_contract: Address,
96) -> Result<(), SdkError> {
97    if !chain_id_matches(domain_chain_id, client_chain_id) {
98        return Err(SdkError::DomainChainIdMismatch {
99            domain: domain_chain_id.to_string(),
100            client: client_chain_id,
101        });
102    }
103    if domain_verifying_contract != configured_contract {
104        return Err(SdkError::DomainVerifyingContractMismatch {
105            domain: domain_verifying_contract,
106            configured: configured_contract,
107        });
108    }
109    Ok(())
110}
111
112/// [`assert_signed_domain_matches_client_and_config`] を、設定アドレスを [`crate::config::transfer_with_commitment_address`] が返す値で呼び出す。
113#[inline]
114pub fn assert_signed_domain_matches_provider_and_config(
115    provider_chain_id: u64,
116    domain_chain_id: alloy::primitives::U256,
117    domain_verifying_contract: Address,
118) -> Result<(), SdkError> {
119    assert_signed_domain_matches_client_and_config(
120        provider_chain_id,
121        domain_chain_id,
122        domain_verifying_contract,
123        config::transfer_with_commitment_address(),
124    )
125}
126
127#[cfg(test)]
128mod tests {
129    use alloy::primitives::{address, U256};
130
131    use super::*;
132
133    #[test]
134    fn chain_id_matches_u256_and_u64() {
135        assert!(chain_id_matches(U256::from(1u64), 1));
136        assert!(chain_id_matches(U256::from(137u64), 137));
137        assert!(!chain_id_matches(U256::from(137u64), 1));
138    }
139
140    #[test]
141    fn assert_signed_domain_matches_ok_when_ids_and_contract_match() {
142        let vc = address!("0x2222222222222222222222222222222222222222");
143        assert!(assert_signed_domain_matches_client_and_config(
144            1,
145            U256::from(1u64),
146            vc,
147            vc,
148        )
149        .is_ok());
150    }
151
152    #[test]
153    fn assert_signed_domain_matches_fails_domain_chain_id_mismatch() {
154        let vc = address!("0x2222222222222222222222222222222222222222");
155        assert!(matches!(
156            assert_signed_domain_matches_client_and_config(1, U256::from(137u64), vc, vc),
157            Err(SdkError::DomainChainIdMismatch { .. })
158        ));
159    }
160
161    #[test]
162    fn assert_signed_domain_matches_fails_verifying_contract_mismatch() {
163        let a = address!("0x2222222222222222222222222222222222222222");
164        let b = address!("0x4444444444444444444444444444444444444444");
165        assert!(matches!(
166            assert_signed_domain_matches_client_and_config(1, U256::from(1u64), a, b),
167            Err(SdkError::DomainVerifyingContractMismatch { .. })
168        ));
169    }
170}