Skip to content

Commit

Permalink
Updated hyperboard contract with the new logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Abhimanyu121 committed Sep 24, 2023
1 parent cd81b8e commit cca0911
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 136 deletions.
152 changes: 57 additions & 95 deletions contracts/src/hyperboards/HyperboardNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,42 @@ import { IERC20 } from "oz-contracts/contracts/token/ERC20/IERC20.sol";
import { ERC721 } from "oz-contracts/contracts/token/ERC721/ERC721.sol";
import { ERC721URIStorage } from "oz-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import { ERC721Enumerable } from "oz-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import { HypercertMinter } from "../HypercertMinter.sol";

import { Strings } from "oz-contracts/contracts/utils/Strings.sol";

import { Ownable } from "oz-contracts/contracts/access/Ownable.sol";
import { CountersUpgradeable } from "oz-upgradeable/utils/CountersUpgradeable.sol";
import { Errors } from "../libs/errors.sol";
import { IERC6551Registry } from "../interfaces/IERC6551Registry.sol";
import "forge-std/console.sol";

/// @title A hyperboard NFT
/// @author Abhimanyu Shekhawat
/// @notice This is an NFT for representing various hyperboards
contract Hyperboard is ERC721, ERC721URIStorage, ERC721Enumerable, Ownable {
string public subgraphEndpoint;
string public baseUri;
address public walletImpl;
IERC6551Registry public erc6551Registry;
HypercertMinter public hypercertMinter;

mapping(uint256 => uint256[]) private _allowListedCertsMapping;
mapping(uint256 => uint256[]) public consentBasedCertsMapping;

mapping(uint256 => AllowlistedCerts) private _allowListedCertsMapping;
mapping(uint256 => address) public tokenWalletMapping;
using CountersUpgradeable for CountersUpgradeable.Counter;
CountersUpgradeable.Counter private _counter;

struct AllowlistedCerts {
address[] allowlistedCerts;
mapping(address => uint256[]) claimIds;
}

/// @notice Emitted when new token is minted
/// @param to Address thats recieving NFT.
/// @param walletAddress Address of wallet deployed.
/// @param tokenId tokenId of the new mint.
event Mint(address indexed to, address indexed walletAddress, uint256 indexed tokenId);

/// @notice Event is emitted when contract gets consent from a hypercert.
/// @param tokenId Token Id of hyperboard.
/// @param claimId ClaimId of a hypercert.
/// @param owner owner of hypercert.
event GotConsent(uint256 indexed tokenId, uint256 indexed claimId, address owner);

/// @notice Emitted when ERC20 tokens are withdrawn.
/// @param to address where tokens were sent to.
/// @param tokenAddress Address of token withdrawn.
Expand All @@ -52,41 +56,29 @@ contract Hyperboard is ERC721, ERC721URIStorage, ERC721Enumerable, Ownable {
/// @param endpoint updated Subgraph endpoint.
event SubgraphUpdated(string endpoint);

/// @notice Emitted when wallet implementation is updated.
/// @param impl Updated wallet implementation.
event WalletImplUpdated(address indexed impl);

/// @notice Emitted when base uri is updated.
/// @param baseUri Updated baseuri.
event BaseUriUpdated(string baseUri);

/// @notice Emitted when 6551 registry is updated
/// @param registry Updated registry address.
event Erc6551RegistryUpdated(address indexed registry);
/// @notice Emitted when hypercert address is updated
/// @param hypercertMinter semifungible token.
event HypercertMinterUpdated(HypercertMinter indexed hypercertMinter);

/// @param name_ name of NFT.
/// @param hypercertMinter_ hypercertToken address
/// @param symbol_ NFT symbol
/// @param subgraphEndpoint_ updateable subgraph endpoint
/// @param baseUri_ base ipfs uri for NFT page.
/// @param walletImpl_ wallet implementation address
/// @param erc6551Registry_ 6551 registry for registring wallet.
constructor(
HypercertMinter hypercertMinter_,
string memory name_,
string memory symbol_,
string memory subgraphEndpoint_,
string memory baseUri_,
address walletImpl_,
IERC6551Registry erc6551Registry_
string memory baseUri_
) ERC721(name_, symbol_) {
if (walletImpl_ == address(0)) revert Errors.ZeroAddress();

if (address(erc6551Registry_) == address(0)) revert Errors.ZeroAddress();

walletImpl = walletImpl_;
emit WalletImplUpdated(walletImpl);

erc6551Registry = erc6551Registry_;
emit Erc6551RegistryUpdated(address(erc6551Registry));
if (address(hypercertMinter_) == address(0)) revert Errors.ZeroAddress();
hypercertMinter = hypercertMinter_;
emit HypercertMinterUpdated(hypercertMinter);

subgraphEndpoint = subgraphEndpoint_;
emit SubgraphUpdated(subgraphEndpoint);
Expand All @@ -97,66 +89,52 @@ contract Hyperboard is ERC721, ERC721URIStorage, ERC721Enumerable, Ownable {

/// @notice Mints a new Hyperboard.
/// @param to The address to which the Hyperboard NFT will be minted.
/// @param allowlistedCertsAddress_ Addresses of allowlisted certificates.
/// @param allowlistedClaimIds_ Claim IDs corresponding to allowlisted certificates.
/// @param salt Salt value for wallet creation.
/// @return tokenId The ID of the minted NFT.
function mint(
address to,
address[] memory allowlistedCertsAddress_,
uint256[][] memory allowlistedClaimIds_,
uint256 salt
) external returns (uint256 tokenId) {
if (allowlistedCertsAddress_.length != allowlistedClaimIds_.length) revert Errors.ArrayLengthMismatch();
function mint(address to, uint256[] memory allowlistedClaimIds_) external returns (uint256 tokenId) {
if (allowlistedClaimIds_.length == 0) revert Errors.Invalid();

if (to == address(0)) revert Errors.ZeroAddress();

tokenId = _counter.current();
_mint(to, tokenId);

_setAllowlist(tokenId, allowlistedCertsAddress_, allowlistedClaimIds_);

tokenWalletMapping[tokenId] = erc6551Registry.createAccount(
walletImpl,
block.chainid,
address(this),
tokenId,
salt,
bytes("")
);
_setAllowlist(tokenId, allowlistedClaimIds_);

_counter.increment();
return tokenId;
}

/// @notice gives consent from a hypercert to be part of Hyperboard
/// @param tokenId tokenId of a hyperboard.
/// @param claimId ClaimId of a hypercert.
function consentForHyperboard(uint256 tokenId_, uint256 claimId_) external {
_consentForHyperboard(tokenId_, claimId_);
}

function consentForHyperboardWithSignature() external {
//Todo: implement
}

function _consentForHyperboard(uint256 tokenId_, uint256 claimId_) internal {
if (hypercertMinter.ownerOf(claimId_) != msg.sender) revert Errors.NotHypercertOwner();
consentBasedCertsMapping[tokenId_].push(claimId_);
emit GotConsent(tokenId_, claimId_, msg.sender);
}

/// @notice Updates the allowlisted certificates for an Hyperboard NFT.
/// @param tokenId The ID of the NFT.
/// @param allowlistedCertsAddress_ Addresses of updated allowlisted certificates.
/// @param allowlistedClaimIds_ Updated claim IDs corresponding to allowlisted certificates.
function updateAllowListedCerts(
uint256 tokenId,
address[] memory allowlistedCertsAddress_,
uint256[][] memory allowlistedClaimIds_
) external {
function updateAllowListedCerts(uint256 tokenId, uint256[] memory allowlistedClaimIds_) external {
if (ownerOf(tokenId) != msg.sender) revert Errors.NotOwner();
_setAllowlist(tokenId, allowlistedCertsAddress_, allowlistedClaimIds_);
_setAllowlist(tokenId, allowlistedClaimIds_);
}

/// @notice Gets the allowlisted certificates for an NFT.
/// @param tokenId The ID of the NFT.
/// @return allowlistedCerts The array of allowlisted certificate addresses.
function getAllowListedCerts(uint256 tokenId) external view returns (address[] memory) {
return _allowListedCertsMapping[tokenId].allowlistedCerts;
}

/// @notice Gets the allowlisted claim IDs for a specific certificate and Hyperboard NFT.
/// @param tokenId The ID of the Hyperboard NFT.
/// @param hypercertAddress The address of the hypercert certificate.
/// @return claimIds The array of allowlisted claim IDs.
function getAllowListedClaimIds(
uint256 tokenId,
address hypercertAddress
) external view returns (uint256[] memory) {
return _allowListedCertsMapping[tokenId].claimIds[hypercertAddress];
function getAllowListedCerts(uint256 tokenId) external view returns (uint256[] memory) {
return _allowListedCertsMapping[tokenId];
}

/// @dev Get URI of token, i.e. URL of NFT webpage
Expand All @@ -178,27 +156,20 @@ contract Hyperboard is ERC721, ERC721URIStorage, ERC721Enumerable, Ownable {
emit SubgraphUpdated(subgraphEndpoint);
}

/// @notice Sets the wallet implementation address.
/// @param walletImpl_ The new wallet implementation address.
function setWalletImpl(address walletImpl_) external onlyOwner {
walletImpl = walletImpl_;
emit WalletImplUpdated(walletImpl);
}

/// @notice Sets the 6551 registry for registering wallets.
/// @param erc6551Registry_ The new 6551 registry address.
function setErc6551Registry(IERC6551Registry erc6551Registry_) external onlyOwner {
erc6551Registry = erc6551Registry_;
emit Erc6551RegistryUpdated(address(erc6551Registry));
}

/// @notice update the base URI
/// @param baseUri_ The new 6551 registry address.
function setBaseUri(string memory baseUri_) external onlyOwner {
baseUri = baseUri_;
emit BaseUriUpdated(baseUri);
}

/// @notice update the hypercert contract
/// @param hypercertMinter_ The new minter address.
function setHypercertContract(HypercertMinter hypercertMinter_) external onlyOwner {
hypercertMinter_ = hypercertMinter;
emit HypercertMinterUpdated(hypercertMinter_);
}

/// @notice Withdraws accidentally transferred ERC20 tokens from the contract to a specified account.
/// @param token The ERC20 token contract address.
/// @param amount The amount of tokens to withdraw.
Expand All @@ -219,20 +190,11 @@ contract Hyperboard is ERC721, ERC721URIStorage, ERC721Enumerable, Ownable {

/// @dev Sets the allowlisted certificates and their corresponding claim IDs for an NFT.
/// @param tokenId The ID of the NFT.
/// @param allowlistedCertsAddress_ Addresses of allowlisted certificates.
/// @param allowlistedClaimIds_ Claim IDs corresponding to allowlisted certificates.
/// @dev This function is used internally to set the allowlist for a specific NFT.
function _setAllowlist(
uint256 tokenId,
address[] memory allowlistedCertsAddress_,
uint256[][] memory allowlistedClaimIds_
) internal {
if (allowlistedCertsAddress_.length != allowlistedClaimIds_.length) revert Errors.ArrayLengthMismatch();
AllowlistedCerts storage allowListedCerts = _allowListedCertsMapping[tokenId];
allowListedCerts.allowlistedCerts = allowlistedCertsAddress_;
for (uint256 i = 0; i < allowlistedCertsAddress_.length; i++) {
_allowListedCertsMapping[tokenId].claimIds[allowlistedCertsAddress_[i]] = allowlistedClaimIds_[i];
}
function _setAllowlist(uint256 tokenId, uint256[] memory allowlistedClaimIds_) internal {
if (allowlistedClaimIds_.length == 0) revert Errors.Invalid();
_allowListedCertsMapping[tokenId] = allowlistedClaimIds_;
}

/// @dev any condtion can be put into this to be checked before transefering tokens.
Expand Down
1 change: 1 addition & 0 deletions contracts/src/libs/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ library Errors {
error ZeroAddress();
error NotOwner();
error FailedToSendEther();
error NotHypercertOwner();
}
72 changes: 31 additions & 41 deletions contracts/test/foundry/HyperboardNFT.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Hyperboard } from "../../src/hyperboards/HyperboardNFT.sol";
import { Test } from "forge-std/Test.sol";
import { TestToken } from "./helpers/ERC20.sol";
import { Strings } from "oz-contracts/contracts/utils/Strings.sol";
import { HypercertMinter } from "../../src/HypercertMinter.sol";

contract TestHyperboard is Test {
Hyperboard private _hyperboard;
Expand All @@ -15,32 +16,25 @@ contract TestHyperboard is Test {
address private _owner;
address private _user1;
address private _user2;
HypercertMinter private _hypercertMinter = HypercertMinter(0x822F17A9A5EeCFd66dBAFf7946a8071C265D1d07);

constructor() {
_owner = address(this); // Replace with your test account addresses
_user1 = address(0x01); // Replace with user addresses as needed
_user2 = address(0x02); // Replace with user addresses as needed

// walletImpl = new WalletImpl(); // Deploy your wallet implementation contract
// erc6551Registry = new ERC6551Registry(); // Deploy your ERC6551 registry contract

_owner = address(this);
_user1 = address(0x01);
_user2 = address(0x02);
_hyperboard = new Hyperboard(
_hypercertMinter,
"Hyperboard NFT",
"HBNFT",
"https://example.com/subgraph",
"ipfs://Qm12345/",
0x3E5c63644E683549055b9Be8653de26E0B4CD36E,
IERC6551Registry(0x02101dfB77FDE026414827Fdc604ddAF224F0921)
"ipfs://Qm12345/"
);
}

function testMintHyperboard() public {
address[] memory userArray = new address[](1);
userArray[0] = _user1;
uint256[][] memory claimIdArray = new uint256[][](1);
claimIdArray[0] = new uint256[](1);
claimIdArray[0][0] = 1;
uint256 tokenId = _hyperboard.mint(_user1, userArray, claimIdArray, 12345);
uint256[] memory claimIdArray = new uint256[](1);
claimIdArray[0] = 1;
uint256 tokenId = _hyperboard.mint(_user1, claimIdArray);
string memory uri = _hyperboard.tokenURI(tokenId);
assertEq(
uri,
Expand All @@ -52,26 +46,32 @@ contract TestHyperboard is Test {
assertEq(_hyperboard.ownerOf(tokenId), _user1, "Incorrect _owner after minting");
}

function testConsentForHyperboard() public {
uint256 hypercertClaimId = 4169139559515338054353265690254024126758919;
address ownerOfHypercert = 0xf3419771c2551f88a91Db61cB874347f05640172;
uint256[] memory claimIdArray = new uint256[](1);
claimIdArray[0] = hypercertClaimId;

uint256 tokenId = _hyperboard.mint(_user1, claimIdArray);
vm.prank(ownerOfHypercert);
_hyperboard.consentForHyperboard(tokenId, claimIdArray[0]);
uint256 consentedCerts = _hyperboard.consentBasedCertsMapping(tokenId, 0);
assertEq(consentedCerts, claimIdArray[0], "Invalid Consent");
}

function testUpdateAllowListedCerts() public {
address[] memory userArray = new address[](1);
userArray[0] = _user1;
uint256[][] memory claimIdArray = new uint256[][](1);
claimIdArray[0] = new uint256[](1);
claimIdArray[0][0] = 1;
uint256 tokenId = _hyperboard.mint(_user1, userArray, claimIdArray, 12345);

userArray[0] = _user2;
claimIdArray[0][0] = 2;
uint256[] memory claimIdArray = new uint256[](1);
claimIdArray[0] = 1;
uint256 tokenId = _hyperboard.mint(_user1, claimIdArray);

claimIdArray[0] = 2;
vm.prank(_user1);
_hyperboard.updateAllowListedCerts(tokenId, userArray, claimIdArray);
_hyperboard.updateAllowListedCerts(tokenId, claimIdArray);

address[] memory allowlistedCerts = _hyperboard.getAllowListedCerts(tokenId);
uint256[] memory claimIds = _hyperboard.getAllowListedClaimIds(tokenId, _user2);
uint256[] memory allowlistedCerts = _hyperboard.getAllowListedCerts(tokenId);

assertEq(allowlistedCerts.length, 1, "Incorrect number of allowlisted certificates");
assertEq(claimIds.length, 1, "Incorrect number of claim IDs");
assertEq(allowlistedCerts[0], _user2, "Incorrect updated allowlisted certificate");
assertEq(claimIds[0], 2, "Incorrect updated claim ID");
assertEq(allowlistedCerts[0], 2, "Incorrect updated claim ID");
}

function testSetSubgraphEndpoint() public {
Expand All @@ -80,16 +80,6 @@ contract TestHyperboard is Test {
assertEq(_hyperboard.subgraphEndpoint(), newEndpoint, "Subgraph endpoint not set correctly");
}

function testSetWalletImpl() public {
_hyperboard.setWalletImpl(address(1));
assertEq(_hyperboard.walletImpl(), address(1), "Wallet implementation not set correctly");
}

function testSetErc6551Registry() public {
_hyperboard.setErc6551Registry(IERC6551Registry(address(1)));
assertEq(address(_hyperboard.erc6551Registry()), address(1), "ERC6551 registry not set correctly");
}

function testWithdrawErc20() public {
TestToken _erc20Token = new TestToken("Test Token", "TST");
uint256 initialBalance = 1000;
Expand Down

0 comments on commit cca0911

Please sign in to comment.