Skip to content
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

feat: As an attestation issuer, I would like to remain compatible with the ERC-721 standard #220

Merged
Show file tree
Hide file tree
Changes from 4 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
62 changes: 62 additions & 0 deletions src/example/NFTPortal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import { IERC721, ERC721 } from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
import { IERC165 } from "openzeppelin-contracts/contracts/utils/introspection/ERC165.sol";
import { AbstractPortal } from "../interface/AbstractPortal.sol";
import { Attestation, AttestationPayload } from "../types/Structs.sol";

/**
* @title NFT Portal
* @author Consensys
* @notice This contract aims to provide ERC 721 compatibility
* @dev This Portal implements parts of ERC 721 - balanceOf and ownerOf functions
*/
contract NFTPortal is AbstractPortal, ERC721 {
mapping(bytes owner => uint256 numberOfAttestations) private numberOfAttestationsPerOwner;

constructor(
address[] memory modules,
address router
) AbstractPortal(modules, router) ERC721("NFTPortal", "NFTPortal") {}

/** @notice Count all attestations assigned to an owner
alainncls marked this conversation as resolved.
Show resolved Hide resolved
* @param owner An address for whom to query the balance
* @return The number of attestations owned by `owner`, possibly zero
*/
function balanceOf(address owner) public view virtual override returns (uint256) {
return numberOfAttestationsPerOwner[abi.encode(owner)];
}

/** @notice Find the owner of an attestation
alainncls marked this conversation as resolved.
Show resolved Hide resolved
* @param tokenId The identifier for an attestation
* @return The address of the owner of the attestation
*/
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
bytes32 attestationId = bytes32(tokenId);
Attestation memory attestation = attestationRegistry.getAttestation(attestationId);
return abi.decode(attestation.subject, (address));
}

/**
* @notice Method run before a payload is attested
* @param attestationPayload the attestation payload supposed to be attested
*/
function _onAttest(AttestationPayload memory attestationPayload) internal override {
numberOfAttestationsPerOwner[attestationPayload.subject]++;
}

function withdraw(address payable to, uint256 amount) external override {}

/**
* @notice Verifies that a specific interface is implemented by the Portal, following ERC-165 specification
* @param interfaceID the interface identifier checked in this call
* @return The list of modules addresses linked to the Portal
*/
function supportsInterface(bytes4 interfaceID) public pure virtual override(AbstractPortal, ERC721) returns (bool) {
return
interfaceID == type(AbstractPortal).interfaceId ||
interfaceID == type(IERC165).interfaceId ||
interfaceID == type(IERC721).interfaceId;
}
}
68 changes: 68 additions & 0 deletions test/example/NFTPortal.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import { Test } from "forge-std/Test.sol";
import { NFTPortal } from "../../src/example/NFTPortal.sol";
import { Router } from "../../src/Router.sol";
import { AbstractPortal } from "../../src/interface/AbstractPortal.sol";
import { AttestationPayload } from "../../src/types/Structs.sol";
import { AttestationRegistryMock } from "../mocks/AttestationRegistryMock.sol";
import { ModuleRegistryMock } from "../mocks/ModuleRegistryMock.sol";
import { IERC721 } from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
import { IERC165 } from "openzeppelin-contracts/contracts/utils/introspection/ERC165.sol";

contract NFTPortalTest is Test {
address public attester = makeAddr("attester");
NFTPortal public nftPortal;
address[] public modules = new address[](0);
ModuleRegistryMock public moduleRegistryMock = new ModuleRegistryMock();
AttestationRegistryMock public attestationRegistryMock = new AttestationRegistryMock();
Router public router = new Router();

event Initialized(uint8 version);
event AttestationRegistered();
event BulkAttestationsRegistered();

function setUp() public {
router.initialize();
router.updateModuleRegistry(address(moduleRegistryMock));
router.updateAttestationRegistry(address(attestationRegistryMock));

nftPortal = new NFTPortal(modules, address(router));
}

function test_balanceOf_ownerOf() public {
satyajeetkolhapure marked this conversation as resolved.
Show resolved Hide resolved
// Create attestation payload
AttestationPayload memory attestationPayload = AttestationPayload(
bytes32(uint256(1)),
uint64(block.timestamp + 1 days),
abi.encode(address(1)), // Convert address(1) to bytes and use as subject
new bytes(1)
);
// Create validation payload
bytes[] memory validationPayload = new bytes[](0);
vm.expectEmit(true, true, true, true);
emit AttestationRegistered();
nftPortal.attest(attestationPayload, validationPayload);
vm.expectEmit(true, true, true, true);
emit AttestationRegistered();
nftPortal.attest(attestationPayload, validationPayload);

uint256 balance = nftPortal.balanceOf(address(1));
assertEq(balance, 2);

address ownerOfFirstAttestation = nftPortal.ownerOf(1);
address ownerOfSecondAttestation = nftPortal.ownerOf(2);
assertEq(ownerOfFirstAttestation, address(1));
assertEq(ownerOfSecondAttestation, address(1));
}

function testSupportsInterface() public {
bool isIERC165Supported = nftPortal.supportsInterface(type(IERC165).interfaceId);
assertEq(isIERC165Supported, true);
satyajeetkolhapure marked this conversation as resolved.
Show resolved Hide resolved
bool isIERC721Supported = nftPortal.supportsInterface(type(IERC721).interfaceId);
assertEq(isIERC721Supported, true);
alainncls marked this conversation as resolved.
Show resolved Hide resolved
bool isEASAbstractPortalSupported = nftPortal.supportsInterface(type(AbstractPortal).interfaceId);
assertEq(isEASAbstractPortalSupported, true);
alainncls marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading