use alloy::contract::Error as ContractError;
use alloy::primitives::Address;
use alloy::providers::Provider;
use alloy::sol;
use alloy::sol_types::{SolCall, SolInterface};
use thiserror::Error;
sol!(
#[sol(rpc)]
interface IERC165 {
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
);
#[derive(Error, Debug)]
pub enum XorSelectorsError {
#[error("no selectors")]
NoSelectors,
}
#[derive(Error, Debug)]
pub enum Erc165Error {
#[error(transparent)]
Call(#[from] ContractError),
}
fn is_revert_like(e: &ContractError) -> bool {
e.as_revert_data().is_some() || matches!(e, ContractError::ZeroData(_, _))
}
pub trait XorSelectors<T: SolInterface> {
fn xor_selectors() -> Result<[u8; 4], XorSelectorsError> {
let selectors = T::selectors().collect::<Vec<_>>();
if selectors.is_empty() {
return Err(XorSelectorsError::NoSelectors);
}
let mut result = u32::from_be_bytes(selectors[0]);
for selector in &selectors[1..] {
result ^= u32::from_be_bytes(*selector);
}
Ok(result.to_be_bytes())
}
}
impl<T: SolInterface> XorSelectors<T> for T {}
async fn supports_erc165_check1<P: Provider>(
provider: &P,
contract_address: Address,
) -> Result<bool, Erc165Error> {
let contract = IERC165::new(contract_address, provider);
match contract
.supportsInterface(IERC165::supportsInterfaceCall::SELECTOR.into())
.call()
.await
{
Ok(v) => Ok(v),
Err(e) if is_revert_like(&e) => Ok(false),
Err(e) => Err(e.into()),
}
}
async fn supports_erc165_check2<P: Provider>(
provider: &P,
contract_address: Address,
) -> Result<bool, Erc165Error> {
let contract = IERC165::new(contract_address, provider);
match contract
.supportsInterface([0xff, 0xff, 0xff, 0xff].into())
.call()
.await
{
Ok(v) => Ok(!v),
Err(e) if is_revert_like(&e) => Ok(false),
Err(e) => Err(e.into()),
}
}
pub async fn supports_erc165<P: Provider>(
provider: &P,
contract_address: Address,
) -> Result<bool, Erc165Error> {
if !supports_erc165_check1(provider, contract_address).await? {
return Ok(false);
}
supports_erc165_check2(provider, contract_address).await
}
#[cfg(test)]
mod tests {
use super::XorSelectors;
use super::*;
use alloy::providers::{ProviderBuilder, mock::Asserter};
use alloy::rpc::json_rpc::ErrorPayload;
use serde_json::json;
sol! {
#[sol(rpc)]
interface ITest {
function externalFn1() external pure returns (bool);
function externalFn2(uint256 val1, uint256 val2) external returns (uint256, bool);
function externalFn3(address add) external returns (address);
error SomeError();
event SomeEvent(uint256 value);
}
}
sol! {
interface IOne {
function only() external;
}
}
sol! {
interface ITwo {
function first() external;
function second(uint256 v) external;
}
}
fn mocked_provider(asserter: Asserter) -> impl Provider {
ProviderBuilder::new().connect_mocked_client(asserter)
}
fn revert_payload() -> ErrorPayload {
ErrorPayload {
code: -32003,
message: "execution reverted".into(),
data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
}
}
fn transport_error_payload() -> ErrorPayload {
ErrorPayload {
code: -32603,
message: "internal error".into(),
data: None,
}
}
#[test]
fn test_get_interface_id() {
let result = IERC165::IERC165Calls::xor_selectors().unwrap();
let expected: [u8; 4] = 0x01ffc9a7u32.to_be_bytes(); assert_eq!(result, expected);
let result = ITest::ITestCalls::xor_selectors().unwrap();
let expected: [u8; 4] = 0x3dcd3fedu32.to_be_bytes(); assert_eq!(result, expected);
}
#[test]
fn test_xor_selectors_single_selector_returns_that_selector() {
let result = IOne::IOneCalls::xor_selectors().unwrap();
let expected = IOne::onlyCall::SELECTOR;
assert_eq!(result, expected);
}
#[test]
fn test_xor_selectors_two_function_interface_xors_both() {
let result = ITwo::ITwoCalls::xor_selectors().unwrap();
let selectors = ITwo::ITwoCalls::selectors().collect::<Vec<_>>();
assert_eq!(selectors.len(), 2);
let manual = u32::from_be_bytes(selectors[0]) ^ u32::from_be_bytes(selectors[1]);
assert_eq!(result, manual.to_be_bytes());
assert_ne!(result, selectors[0]);
assert_ne!(result, selectors[1]);
}
#[tokio::test]
async fn test_supports_erc165_check1_true_response() {
let asserter = Asserter::new();
let address = Address::random();
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
let provider = mocked_provider(asserter);
let result = supports_erc165_check1(&provider, address).await.unwrap();
assert!(result);
}
#[tokio::test]
async fn test_supports_erc165_check1_false_response() {
let asserter = Asserter::new();
let address = Address::random();
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
let provider = mocked_provider(asserter);
let result = supports_erc165_check1(&provider, address).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_supports_erc165_check1_revert_response() {
let asserter = Asserter::new();
let address = Address::random();
asserter.push_failure(revert_payload());
let provider = mocked_provider(asserter);
let result = supports_erc165_check1(&provider, address).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_supports_erc165_check1_transport_error_propagates() {
let asserter = Asserter::new();
let address = Address::random();
asserter.push_failure(transport_error_payload());
let provider = mocked_provider(asserter);
let err = supports_erc165_check1(&provider, address)
.await
.unwrap_err();
assert!(matches!(err, Erc165Error::Call(_)));
}
#[tokio::test]
async fn test_supports_erc165_check1_zero_data_response() {
let asserter = Asserter::new();
let address = Address::random();
asserter.push_success(&"0x");
let provider = mocked_provider(asserter);
let result = supports_erc165_check1(&provider, address).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_supports_erc165_check2_returns_false() {
let asserter = Asserter::new();
let address = Address::random();
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
let provider = mocked_provider(asserter);
let result = supports_erc165_check2(&provider, address).await.unwrap();
assert!(result);
}
#[tokio::test]
async fn test_supports_erc165_check2_returns_true() {
let asserter = Asserter::new();
let address = Address::random();
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
let provider = mocked_provider(asserter);
let result = supports_erc165_check2(&provider, address).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_supports_erc165_check2_reverts() {
let asserter = Asserter::new();
let address = Address::random();
asserter.push_failure(revert_payload());
let provider = mocked_provider(asserter);
let result = supports_erc165_check2(&provider, address).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_supports_erc165_check2_transport_error_propagates() {
let asserter = Asserter::new();
let address = Address::random();
asserter.push_failure(transport_error_payload());
let provider = mocked_provider(asserter);
let err = supports_erc165_check2(&provider, address)
.await
.unwrap_err();
assert!(matches!(err, Erc165Error::Call(_)));
}
#[tokio::test]
async fn test_supports_erc165_both_checks_pass() {
let asserter = Asserter::new();
let address = Address::random();
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
let provider = mocked_provider(asserter);
let result = supports_erc165(&provider, address).await.unwrap();
assert!(result);
}
#[tokio::test]
async fn test_supports_erc165_check1_fails() {
let asserter = Asserter::new();
let address = Address::random();
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
let provider = mocked_provider(asserter);
let result = supports_erc165(&provider, address).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_supports_erc165_check2_fails() {
let asserter = Asserter::new();
let address = Address::random();
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
let provider = mocked_provider(asserter);
let result = supports_erc165(&provider, address).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_supports_erc165_check1_reverts() {
let asserter = Asserter::new();
let address = Address::random();
asserter.push_failure(revert_payload());
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
let provider = mocked_provider(asserter);
let result = supports_erc165(&provider, address).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_supports_erc165_check2_reverts_after_check1_passes() {
let asserter = Asserter::new();
let address = Address::random();
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
asserter.push_failure(revert_payload());
let provider = mocked_provider(asserter);
let result = supports_erc165(&provider, address).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_supports_erc165_propagates_check1_transport_error() {
let asserter = Asserter::new();
let address = Address::random();
asserter.push_failure(transport_error_payload());
let provider = mocked_provider(asserter);
let err = supports_erc165(&provider, address).await.unwrap_err();
assert!(matches!(err, Erc165Error::Call(_)));
}
#[tokio::test]
async fn test_supports_erc165_propagates_check2_transport_error() {
let asserter = Asserter::new();
let address = Address::random();
asserter
.push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
asserter.push_failure(transport_error_payload());
let provider = mocked_provider(asserter);
let err = supports_erc165(&provider, address).await.unwrap_err();
assert!(matches!(err, Erc165Error::Call(_)));
}
}