Skip to content

Add return bomb resistance test for ERC165 #610

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions contracts/src/utils/introspection/erc165.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,125 @@ pub trait IErc165 {
/// [ERC]: https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified
fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool;
}

#[cfg(all(test, feature = "std"))]
mod tests {
use alloc::{vec, vec::Vec};

use alloy_primitives::{Address, FixedBytes};
use motsu::prelude::*;
use stylus_sdk::prelude::*;

use super::*;

#[test]
fn test_supports_interface() {
// The interface ID for IErc165 itself
let interface_id =
FixedBytes::<4>::from(Erc165::INTERFACE_ID.to_be_bytes());
assert!(Erc165::supports_interface(interface_id));

// Example interface ID that Erc165 should not support
let unsupported_interface_id =
FixedBytes::<4>::from([0xFF, 0xFF, 0xFF, 0xFF]);
assert!(!Erc165::supports_interface(unsupported_interface_id));
}

// A wrapper contract for Erc165 that can be tested with motsu
#[storage]
struct Erc165Wrapper;

unsafe impl TopLevelStorage for Erc165Wrapper {}

#[external]
impl Erc165Wrapper {
fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool {
Erc165::supports_interface(interface_id)
}

fn get_supported_interfaces(
&self,
interface_ids: Vec<FixedBytes<4>>,
) -> Vec<bool> {
interface_ids
.iter()
.map(|&id| Erc165::supports_interface(id))
.collect()
}
}

#[motsu::test]
fn return_bomb_resistance(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This test does not perform the necessary return bomb resistance check (see the link in the issue description).
  2. Since motsu does not yet support gas usage, this test case has to be an E2E test (in our repo these are in examples).

contract: Contract<Erc165Wrapper>,
alice: Address,
) {
// The interface ID for IErc165 itself
let erc165_interface_id =
FixedBytes::<4>::from(Erc165::INTERFACE_ID.to_be_bytes());

// Some other interface IDs for testing
let dummy_id_2 = FixedBytes::<4>::from([0x34u8; 4]);
let dummy_id_3 = FixedBytes::<4>::from([0x56u8; 4]);
let dummy_unsupported_id =
FixedBytes::<4>::from([0xFF, 0xFF, 0xFF, 0xFF]);
let dummy_unsupported_id_2 = FixedBytes::<4>::from([0x9Au8; 4]);

// Test that our implementation works correctly for the ERC165 interface
// ID
let result =
contract.sender(alice).supports_interface(erc165_interface_id);
assert!(result);

// Test that our implementation correctly rejects unsupported interface
// IDs
let result_unsupported =
contract.sender(alice).supports_interface(dummy_unsupported_id);
assert!(!result_unsupported);

// Test the get_supported_interfaces method with a small set of IDs
let results = contract.sender(alice).get_supported_interfaces(vec![
erc165_interface_id,
dummy_id_2,
dummy_id_3,
dummy_unsupported_id,
dummy_unsupported_id_2,
]);
assert_eq!(results.len(), 5);
assert!(results[0]); // ERC165 interface ID should be supported
assert!(!results[1]); // dummy_id_2 should not be supported
assert!(!results[2]); // dummy_id_3 should not be supported
assert!(!results[3]); // Unsupported interface ID should not be supported
assert!(!results[4]); // dummy_unsupported_id_2 should not be supported

// Test with a large number of interface IDs to simulate a potential
// attack Create a vector with 100+ interface IDs
let mut large_interface_ids = Vec::new();
for i in 0..120 {
let bytes = [(i % 256) as u8, ((i >> 8) % 256) as u8, 0, 0];
large_interface_ids.push(FixedBytes::<4>::from(bytes));
}
// Add the ERC165 interface ID at a known position
large_interface_ids[50] = erc165_interface_id;

// Call get_supported_interfaces with the large list
let large_results = contract
.sender(alice)
.get_supported_interfaces(large_interface_ids);

// Verify results
assert_eq!(large_results.len(), 120);
assert!(large_results[50]); // ERC165 interface ID should be supported

// Verify that all other results are false (except for the ERC165
// interface ID)
for (i, &result) in large_results.iter().enumerate() {
if i != 50 {
assert!(
!result,
"Interface at index {} should not be supported",
i
);
}
}
}
}