-
Notifications
You must be signed in to change notification settings - Fork 30
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
WIP: Hyperboard contract #1068
Open
Abhimanyu121
wants to merge
32
commits into
hypercerts-org:develop-contracts
Choose a base branch
from
Abhimanyu121:develop
base: develop-contracts
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
WIP: Hyperboard contract #1068
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
f6d8d84
feat(db): update allowlist query (#1057)
bitbeckers 04cf3b2
wip: NFT contract
Abhimanyu121 d48d5f3
WIP: NFT contract - Fixed counter errors
Abhimanyu121 d8a918c
forge install: safe-contracts
Abhimanyu121 19594ed
WIP: Addeding 6551 support
Abhimanyu121 9a6d8f6
WIP: test case helpers
Abhimanyu121 5dbf643
forge install: openzeppelin-contracts
Abhimanyu121 fcb2cd2
WIP: test contract
Abhimanyu121 a1a20c6
Initial draft for Hyperboard NFT
Abhimanyu121 674bee8
Merge branch 'main' into develop
Abhimanyu121 cd81b8e
Added events and added way to updated base uri
Abhimanyu121 cca0911
Updated hyperboard contract with the new logic
Abhimanyu121 87b7827
Updated comments
Abhimanyu121 5fc4cb0
removed unused import
Abhimanyu121 300f3a0
WIP:Wallet impl
Abhimanyu121 0f19644
forge install: safe-contracts
Abhimanyu121 6f3e95e
WIP:wallet IMPL
Abhimanyu121 a83a7cc
WIP:Wallet IMPL
Abhimanyu121 2b5ed01
forge install: safe-contracts
Abhimanyu121 ce9db2a
WIP: wallet impl
Abhimanyu121 dce74cd
WIP: wallet impl
Abhimanyu121 d236b97
Added subgraph for tracking hyperboard
Abhimanyu121 a08e0a2
Updated hyperboards contract and fixed testcases
Abhimanyu121 a50b90b
Added deploy scripts and code cleanup
Abhimanyu121 d25368b
removed unused safe contracts
Abhimanyu121 29a2eaa
removed test logs
Abhimanyu121 433233d
fix(graph): update matchstick dependency version
bitbeckers 35b33f3
Minor fixes and corrections
Abhimanyu121 8670086
chore: update hyperboard deployment script
0xartem b79636b
Merge pull request #1 from 0xartem/develop
Abhimanyu121 afe36de
Updated start block and config
Abhimanyu121 3c19045
Merge branch 'develop' of https://github.com/Abhimanyu121/hypercerts …
Abhimanyu121 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.16; | ||
// import { ERC721URIStorageUpgradeable } from "oz-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; | ||
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 { EIP712 } from "oz-contracts/contracts/utils/cryptography/draft-EIP712.sol"; | ||
import { ECDSA } from "oz-contracts/contracts/utils/cryptography/ECDSA.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"; | ||
|
||
/// @title A hyperboard NFT | ||
/// @author Abhimanyu Shekhawat | ||
/// @notice This is an NFT for representing various hyperboards | ||
contract Hyperboard is ERC721, ERC721URIStorage, ERC721Enumerable, Ownable, EIP712 { | ||
string public subgraphEndpoint; | ||
string public baseUri; | ||
HypercertMinter public hypercertMinter; | ||
|
||
mapping(uint256 => uint256[]) private _allowListedCertsMapping; | ||
mapping(uint256 => uint256[]) public consentBasedCertsMapping; | ||
|
||
using CountersUpgradeable for CountersUpgradeable.Counter; | ||
CountersUpgradeable.Counter private _counter; | ||
|
||
/// @notice Emitted when new token is minted | ||
/// @param to Address thats recieving NFT. | ||
/// @param tokenId tokenId of the new mint. | ||
/// @param metadata of hyperboard. | ||
event Mint(address indexed to, uint256 indexed tokenId, string metadata); | ||
|
||
/// @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. | ||
/// @param amount amount of tokems withdrawn. | ||
event WithdrawErc20(address indexed to, address indexed tokenAddress, uint256 indexed amount); | ||
|
||
/// @notice Emitted when Ether tokens are withdrawn | ||
/// @param to address where tokens were sent to. | ||
/// @param amount amount of tokems withdrawn. | ||
event WithdrawEther(address indexed to, uint256 indexed amount); | ||
|
||
/// @notice Emitted when subgraph endpoint is updated. | ||
/// @param endpoint updated Subgraph endpoint. | ||
event SubgraphUpdated(string endpoint); | ||
|
||
/// @notice Emitted when base uri is updated. | ||
/// @param baseUri Updated baseuri. | ||
event BaseUriUpdated(string baseUri); | ||
|
||
/// @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. | ||
constructor( | ||
HypercertMinter hypercertMinter_, | ||
string memory name_, | ||
string memory symbol_, | ||
string memory subgraphEndpoint_, | ||
string memory baseUri_, | ||
string memory version | ||
) ERC721(name_, symbol_) EIP712(name_, version) { | ||
if (address(hypercertMinter_) == address(0)) revert Errors.ZeroAddress(); | ||
hypercertMinter = hypercertMinter_; | ||
emit HypercertMinterUpdated(hypercertMinter); | ||
|
||
subgraphEndpoint = subgraphEndpoint_; | ||
emit SubgraphUpdated(subgraphEndpoint); | ||
|
||
baseUri = baseUri_; | ||
emit BaseUriUpdated(baseUri); | ||
} | ||
|
||
/// @notice Mints a new Hyperboard. | ||
/// @param to The address to which the Hyperboard NFT will be minted. | ||
/// @param allowlistedClaimIds_ Claim IDs corresponding to allowlisted certificates. | ||
/// @return tokenId The ID of the minted NFT. | ||
function mint( | ||
address to, | ||
uint256[] memory allowlistedClaimIds_, | ||
string memory metadata_ | ||
) external returns (uint256 tokenId) { | ||
if (allowlistedClaimIds_.length == 0) revert Errors.Invalid(); | ||
Abhimanyu121 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (to == address(0)) revert Errors.ZeroAddress(); | ||
|
||
tokenId = _counter.current(); | ||
_mint(to, tokenId); | ||
_setTokenURI(tokenId, metadata_); | ||
_setAllowlist(tokenId, allowlistedClaimIds_); | ||
|
||
_counter.increment(); | ||
emit Mint(to, tokenId, metadata_); | ||
|
||
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 { | ||
Abhimanyu121 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_consentForHyperboard(tokenId_, claimId_, msg.sender); | ||
} | ||
|
||
/// @notice gives consent from a hypercert to be part of Hyperboard using signatures of the owner | ||
/// @param tokenId_ tokenId of a hyperboard. | ||
/// @param claimId_ ClaimId of a hypercert. | ||
/// @param signer Address of owner of hypercert. | ||
/// @param r r of signature of digest for consent. | ||
/// @param s s of signature of digest for consent. | ||
/// @param v v of signature of digest for consent. | ||
function consentForHyperboardWithSignature( | ||
uint256 tokenId_, | ||
uint256 claimId_, | ||
address signer, | ||
bytes32 r, | ||
bytes32 s, | ||
uint8 v | ||
) external { | ||
bytes32 digest = _hashTypedDataV4( | ||
keccak256( | ||
abi.encode(keccak256("ConsentForHyperBoard(uint256 tokenId_,uint256 claimId_)"), tokenId_, claimId_) | ||
) | ||
); | ||
address recoveredSigner = ECDSA.recover(digest, v, r, s); | ||
if (recoveredSigner != signer) revert Errors.InvalidSigner(); | ||
_consentForHyperboard(tokenId_, claimId_, signer); | ||
} | ||
|
||
/// @notice gives digest that needs to be signed by owner to give consenst using signature. | ||
/// @param tokenId_ tokenId of a hyperboard. | ||
/// @param claimId_ ClaimId of a hypercert. | ||
function getDigestForConsent(uint256 tokenId_, uint256 claimId_) external returns (bytes32) { | ||
bytes32 digest = _hashTypedDataV4( | ||
keccak256( | ||
abi.encode(keccak256("ConsentForHyperBoard(uint256 tokenId_,uint256 claimId_)"), tokenId_, claimId_) | ||
) | ||
); | ||
return digest; | ||
} | ||
|
||
/// @notice internal function that actually gives consent. | ||
/// @param tokenId_ tokenId of a hyperboard. | ||
/// @param claimId_ ClaimId of a hypercert. | ||
/// @param owner_ address of owner of hypercert. | ||
function _consentForHyperboard(uint256 tokenId_, uint256 claimId_, address owner_) internal { | ||
if (hypercertMinter.ownerOf(claimId_) != owner_) 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 allowlistedClaimIds_ Updated claim IDs corresponding to allowlisted certificates. | ||
/// @param metadata_ Updated metadata, to reflect new claimIds in the board. | ||
function updateHyperboad( | ||
uint256 tokenId_, | ||
uint256[] memory allowlistedClaimIds_, | ||
string memory metadata_ | ||
) external { | ||
if (ownerOf(tokenId_) != msg.sender) revert Errors.NotOwner(); | ||
_setTokenURI(tokenId_, metadata_); | ||
_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 (uint256[] memory) { | ||
return _allowListedCertsMapping[tokenId]; | ||
} | ||
|
||
/// @dev Get URI of token, i.e. URL of NFT webpage | ||
/// @param tokenId id of the token to get URI for. | ||
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { | ||
return | ||
string.concat( | ||
baseUri, | ||
"?tokenId=", | ||
Strings.toString(tokenId), | ||
"&subgraph=", | ||
subgraphEndpoint, | ||
"&tokenURI=", | ||
ERC721URIStorage.tokenURI(tokenId) | ||
); | ||
} | ||
|
||
/// @dev checks if this contract supports specific interface. | ||
/// @param interfaceId interface ID that you want to check the contract against. | ||
function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) { | ||
return super.supportsInterface(interfaceId); | ||
} | ||
|
||
/// @notice Sets the subgraph endpoint for the hyperboards. | ||
/// @param endpoint_ The new subgraph endpoint. | ||
function setSubgraphEndpoint(string memory endpoint_) external onlyOwner { | ||
subgraphEndpoint = endpoint_; | ||
emit SubgraphUpdated(subgraphEndpoint); | ||
} | ||
|
||
/// @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. | ||
/// @param account The recipient account address. | ||
function withdrawErc20(IERC20 token, uint256 amount, address account) external onlyOwner { | ||
token.transfer(account, amount); | ||
Abhimanyu121 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
emit WithdrawErc20(account, address(token), amount); | ||
} | ||
|
||
/// @notice Withdraws accidentally transferred Ether from the contract to a specified account. | ||
/// @param amount The amount of Ether to withdraw. | ||
/// @param account The recipient account address. | ||
function withdrawEther(uint256 amount, address payable account) external onlyOwner { | ||
(bool sent, ) = account.call{ value: amount }(""); | ||
if (!sent) revert Errors.FailedToSendEther(); | ||
emit WithdrawEther(account, amount); | ||
} | ||
|
||
/// @dev Sets the allowlisted certificates and their corresponding claim IDs for an NFT. | ||
/// @param tokenId The ID of the NFT. | ||
/// @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, 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. | ||
/// @param from which accounts token needs to be tranferred. | ||
/// @param to who will be the recipient of token. | ||
/// @param tokenId token Id of token being transferred. | ||
function _beforeTokenTransfer( | ||
address from, | ||
address to, | ||
uint256 tokenId | ||
) internal override(ERC721, ERC721Enumerable) { | ||
super._beforeTokenTransfer(from, to, tokenId); | ||
} | ||
|
||
/// @dev internal function to burn the token | ||
/// @param tokenId Id of token that needs to be burned. | ||
function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { | ||
super._burn(tokenId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This variable is used for the dynamic NFT we specced out, correct? I think this setup is tricky because a change in the subgraph endpoints would result in a breaking change. To compare, an ipfs:// URL can be mapped to a gateway, but subgraphs don't follow these kind of standards I believe.
One solution could be a central registry for the IPFS uri so that the rendered page and the subgraph URL can be governed on a protocol level.nvm, the contract is the central registry. Still, maybe we can find a way around something like the Graph.
What do you think?
P.S. this is something beyond PoC scope, but we should be aware of the implications.