Skip to content

Commit

Permalink
Move ERC165 (Introspection) docs to its own page (OpenZeppelin#322)
Browse files Browse the repository at this point in the history
* add introspection docs

* remove erc165 from erc721

* change heading

* fix link

* fix supports_interface link

* Update docs/Introspection.md

Co-authored-by: Martín Triay <martriay@gmail.com>

* add casing note

* fix link

* update extensibility reference

* fix comment

* fix blockquote

Co-authored-by: Martín Triay <martriay@gmail.com>
  • Loading branch information
andrew-fleming and martriay authored May 21, 2022
1 parent 37b6001 commit 61bdc91
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 49 deletions.
51 changes: 2 additions & 49 deletions docs/ERC721.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,6 @@ The ERC721 token standard is a specification for [non-fungible tokens](https://d
- [`IERC721_Receiver`](#ierc721_receiver)
- [`onERC721Received`](#onerc721received)

- [ERC165](#erc165)
- [IERC165](#ierc165)
- [ERC165 API Specification](#erc165-api-specification)
- [`IERC165`](#ierc165)
- [`supportsInterface`](#supportsinterface)

## IERC721

```cairo
Expand Down Expand Up @@ -110,7 +104,7 @@ But some differences can still be found, such as:
- `safeTransferFrom` can only be expressed as a single function in Cairo as opposed to the two functions declared in EIP721. The difference between both functions consists of accepting `data` as an argument. Because function overloading is currently not possible in Cairo, `safeTransferFrom` by default accepts the `data` argument. If `data` is not used, simply insert `0`.
- `safeTransferFrom` is specified such that the optional `data` argument should be of type bytes. In Solidity, this means a dynamically-sized array. To be as close as possible to the standard, it accepts a dynamic array of felts. In Cairo, arrays are expressed with the array length preceding the actual array; hence, the method accepts `data_len` and `data` respectively as types `felt` and `felt*`
- `ERC165.register_interface` allows contracts to set and communicate which interfaces they support. This follows OpenZeppelin's [ERC165Storage](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/introspection/ERC165Storage.sol)
- `IERC721_Receiver` compliant contracts (`ERC721_Holder`) return a hardcoded selector id according to EVM selectors, since selectors are calculated differently in Cairo. This is in line with the ERC165 interfaces design choice towards EVM compatibility
- `IERC721_Receiver` compliant contracts (`ERC721_Holder`) return a hardcoded selector id according to EVM selectors, since selectors are calculated differently in Cairo. This is in line with the ERC165 interfaces design choice towards EVM compatibility. See the [Introspection docs](./Introspection.md) for more info

- `IERC721_Receiver` compliant contracts (`ERC721_Holder`) must support ERC165 by registering the `IERC721_Receiver` selector id in its constructor and exposing the `supportsInterface` method. In doing so, recipient contracts (both accounts and non-accounts) can be verified that they support ERC721 transfers

Expand Down Expand Up @@ -208,7 +202,7 @@ string_uri = felt_to_str(felt_uri)

In order to be sure a contract can safely accept ERC721 tokens, said contract must implement the `ERC721_Receiver` interface (as expressed in the EIP721 specification). Methods such as `safeTransferFrom` and `safeMint` call the recipient contract's `onERC721Received` method. If the contract fails to return the correct magic value, the transaction fails.

StarkNet contracts that support safe transfers, however, must also support ERC165 and include `supportsInterface` as proposed in [#100](https://github.com/OpenZeppelin/cairo-contracts/discussions/100). `safeTransferFrom` requires a means of differentiating between account and non-account contracts. Currently, StarkNet does not support error handling from the contract level;
StarkNet contracts that support safe transfers, however, must also support [ERC165](./Introspection.md#erc165) and include `supportsInterface` as proposed in [#100](https://github.com/OpenZeppelin/cairo-contracts/discussions/100). `safeTransferFrom` requires a means of differentiating between account and non-account contracts. Currently, StarkNet does not support error handling from the contract level;
therefore, the current ERC721 implementation requires that all contracts that support safe ERC721 transfers (both accounts and non-accounts) include the `supportsInterface` method. Further, `supportsInterface` should return `TRUE` if the recipient contract supports the `IERC721_Receiver` magic value `0x150b7a02` (which invokes `onERC721Received`). If the recipient contract supports the `IAccount` magic value `0x50b70dcb`, `supportsInterface` should return `TRUE`. Otherwise, `safeTransferFrom` should fail.

#### IERC721_Receiver
Expand Down Expand Up @@ -714,44 +708,3 @@ Returns:
```cairo
selector: felt
```

## ERC165

The ERC165 standard allows smart contracts to exercise [type introspection](https://en.wikipedia.org/wiki/Type_introspection) on other contracts, that is, examining which functions can be called on them. This is usually referred to as a contract’s interface.

Cairo contracts, like Ethereum contracts, have no native concept of an interface, so applications must usually simply trust they are not making an incorrect call. For trusted setups this is a non-issue, but often unknown and untrusted third-party addresses need to be interacted with. There may even not be any direct calls to them! (e.g. ERC20 tokens may be sent to a contract that lacks a way to transfer them out of it, locking them forever). In these cases, a contract declaring its interface can be very helpful in preventing errors.

It should be noted that the [constants library](../src/openzeppelin/utils/constants.cairo) includes constant variables referencing all of the interface ids used in these contracts. This allows for more legible code i.e. using `IERC165_ID` instead of `0x01ffc9a7`.

### IERC165

```cairo
@contract_interface
namespace IERC165:
func supportsInterface(interfaceId: felt) -> (success: felt):
end
end
```

### ERC165 API Specification

```cairo
func supportsInterface(interfaceId: felt) -> (success: felt):
end
```

#### `supportsInterface`

Returns true if this contract implements the interface defined by `interfaceId`.

Parameters:

```cairo
interfaceId: felt
```

Returns:

```cairo
success: felt
```
144 changes: 144 additions & 0 deletions docs/Introspection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Introspection

> Expect this module to evolve.
## Table of Contents

* [ERC165](#erc165)
* [Interface calculations](#interface-calculations)
* [Registering interfaces](#registering-interfaces)
* [Querying interfaces](#querying-interfaces)
* [IERC165](#ierc165)
* [IERC165 API Specification](#ierc165-api-specification)
* [`supportsInterface`](#supportsinterface)
* [ERC165 Library Functions](#erc165-library-functions)
* [`supports_interface`](#supportsinterface2)
* [`register_interface`](#register_interface)

## ERC165

The ERC165 standard allows smart contracts to exercise [type introspection](https://en.wikipedia.org/wiki/Type_introspection) on other contracts, that is, examining which functions can be called on them. This is usually referred to as a contract’s interface.

Cairo contracts, like Ethereum contracts, have no native concept of an interface, so applications must usually simply trust they are not making an incorrect call. For trusted setups this is a non-issue, but often unknown and untrusted third-party addresses need to be interacted with. There may even not be any direct calls to them! (e.g. ERC20 tokens may be sent to a contract that lacks a way to transfer them out of it, locking them forever). In these cases, a contract declaring its interface can be very helpful in preventing errors.

It should be noted that the [constants library](../src/openzeppelin/utils/constants.cairo) includes constant variables referencing all of the interface ids used in these contracts. This allows for more legible code i.e. using `IERC165_ID` instead of `0x01ffc9a7`.

### Interface calculations

In order to ensure EVM/StarkNet compatibility, interface identifiers are not calculated on StarkNet. Instead, the interface IDs are hardcoded from their EVM calculations. On the EVM, the interface ID is calculated from the selector's first four bytes of the hash of the function's signature while Cairo selectors are 252 bytes long. Due to this difference, hardcoding EVM's already-calculated interface IDs is the most consistent approach to both follow the EIP165 standard and EVM compatibility.

### Registering interfaces

For a contract to declare its support for a given interface, the contract should import the ERC165 library and register its support. It's recommended to register interface support upon contract deployment through a constructor either directly or indirectly (as an initializer) like this:

```cairo
from openzeppelin.introspection.ERC165 import ERC165
INTERFACE_ID = 0x12345678
@constructor
func constructor{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr
}():
ERC165.register_interface(INTERFACE_ID)
return ()
end
```

### Querying interfaces

To query a target contract's support for an interface, the querying contract should call `supportsInterface` through IERC165 with the target contract's address and the queried interface id. Here's an example:

```cairo
from openzeppelin.introspection.IERC165 import IERC165
INTERFACE_ID = 0x12345678
func check_support{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr
}(
target_contract: felt,
) -> (success: felt):
let (is_supported) = IERC165.supportsInterface(target_contract, INTERFACE_ID)
return (is_supported)
end
```

> Please note that `supportsInterface` is camelCased because it is an exposed contract method as part of ERC165's interface. This differs from library methods (such as `supports_interface` from the [ERC165 library](../src/openzeppelin/introspection/ERC165.cairo)) which are snake_cased and not exposed. See the [Function names and coding style](../docs/Extensibility.md#function-names-and-coding-style) for more details.
### IERC165

```cairo
@contract_interface
namespace IERC165:
func supportsInterface(interfaceId: felt) -> (success: felt):
end
end
```

### IERC165 API Specification

```cairo
func supportsInterface(interfaceId: felt) -> (success: felt):
end
```

#### `supportsInterface`

Returns true if this contract implements the interface defined by `interfaceId`.

Parameters:

```cairo
interfaceId: felt
```

Returns:

```cairo
success: felt
```

### ERC165 Library Functions

```cairo
func supports_interface(interface_id: felt) -> (success: felt):
end
func register_interface(interface_id: felt):
end
```

<h4 id="supportsinterface2"><code>supports_interface</code></h4>

Returns true if this contract implements the interface defined by `interface_id`.

Parameters:

```cairo
interface_id: felt
```

Returns:

```cairo
success: felt
```

#### `register_interface`

Calling contract declares support for a specific interface defined by `interface_id`.

Parameters:

```cairo
interface_id: felt
```

Returns:

None.

0 comments on commit 61bdc91

Please sign in to comment.