-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #103 from Aut-Labs/chore/copy-over-interactions
chore(interactions): cp over interactions
- Loading branch information
Showing
3 changed files
with
425 additions
and
0 deletions.
There are no files selected for viewing
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,115 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; | ||
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; | ||
|
||
/// @notice a dataset of interactions | ||
interface IInteractionDataset { | ||
/// @notice an event emitted on merkle tree root update | ||
/// @param relayer address of the relayer caused an update | ||
/// @param merkleRoot new merkle root pushed by the relayer | ||
/// @param proofsHash hash of the merkle tree source file (nullable) | ||
event MerkleRootUpdated( | ||
address indexed relayer, | ||
bytes32 indexed merkleRoot, | ||
bytes32 proofsHash | ||
); | ||
|
||
/// @notice a role that manage relayer role | ||
/// @return manager role | ||
function MANAGER_ROLE() external pure returns (bytes32 manager); | ||
|
||
/// @notice a role that manage and update merkle roots | ||
/// @return relayer role | ||
function RELAYER_ROLE() external pure returns (bytes32 relayer); | ||
|
||
/// @return current root of the merkle tree of interactions | ||
function merkleRoot() external view returns (bytes32 current); | ||
|
||
/// @return current ipfs hash for the full merkle tree file | ||
function proofsHash() external view returns (bytes32 current); | ||
|
||
/// @return timestamp of the last update of the merkle tree | ||
function updatedAt() external view returns (uint64 timestamp); | ||
|
||
/// @return number of times that merkle tree has been updated | ||
function epoch() external view returns (uint32 number); | ||
|
||
/// @dev checks if the given transaction hash match given interaction id | ||
/// @param txId transaction hash | ||
/// @param interactionId interaction id (see InteractionFactory contract) | ||
/// @return status of inclusion in the interaction dataset for the given entry | ||
function hasEntry( | ||
bytes32 txId, | ||
bytes32 interactionId, | ||
bytes32[] memory hashedPairsProof | ||
) external view returns (bool status); | ||
|
||
/// @dev updates merkle tree (invoked by "relayer" role) | ||
/// @param nextMerkleRoot root hash of the next merkle tree | ||
/// @param nextProofsHash next proofs hash | ||
function updateRoot(bytes32 nextMerkleRoot, bytes32 nextProofsHash) external; | ||
} | ||
|
||
/// @title a helper contract for the interaction dataset with error definitions | ||
abstract contract InteractionDatasetErrorHelper { | ||
/// @notice raised when initialManager is zero address | ||
error InitialManagerEmptyError(); | ||
} | ||
|
||
contract InteractionDataset is | ||
IInteractionDataset, | ||
InteractionDatasetErrorHelper, | ||
AccessControl | ||
{ | ||
/// @inheritdoc IInteractionDataset | ||
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); | ||
/// @inheritdoc IInteractionDataset | ||
bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE"); | ||
|
||
/// @inheritdoc IInteractionDataset | ||
bytes32 public merkleRoot; | ||
/// @inheritdoc IInteractionDataset | ||
bytes32 public proofsHash; | ||
|
||
/// @inheritdoc IInteractionDataset | ||
uint64 public updatedAt; | ||
/// @inheritdoc IInteractionDataset | ||
uint32 public epoch; | ||
|
||
constructor(address initialRelayerManager) { | ||
if (initialRelayerManager == address(0)) { | ||
revert InitialManagerEmptyError(); | ||
} | ||
_setRoleAdmin(RELAYER_ROLE, MANAGER_ROLE); | ||
_grantRole(MANAGER_ROLE, initialRelayerManager); | ||
_grantRole(RELAYER_ROLE, initialRelayerManager); | ||
} | ||
|
||
/// @inheritdoc IInteractionDataset | ||
function hasEntry( | ||
bytes32 txId, | ||
bytes32 interactionId, | ||
bytes32[] calldata hashedPairsProof | ||
) external view returns (bool) { | ||
return | ||
MerkleProof.verifyCalldata( | ||
hashedPairsProof, | ||
keccak256(abi.encodePacked(txId, ":", interactionId)), | ||
merkleRoot | ||
); | ||
} | ||
|
||
/// @inheritdoc IInteractionDataset | ||
function updateRoot(bytes32 nextMerkleRoot, bytes32 nextProofsHash) external { | ||
_checkRole(RELAYER_ROLE); | ||
|
||
merkleRoot = nextMerkleRoot; | ||
proofsHash = nextProofsHash; | ||
updatedAt = uint64(block.timestamp); | ||
++epoch; | ||
|
||
emit MerkleRootUpdated(msg.sender, nextMerkleRoot, nextProofsHash); | ||
} | ||
} |
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,125 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.20; | ||
|
||
import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; | ||
import { | ||
ERC721Upgradeable | ||
} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; | ||
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | ||
import { | ||
ERC721URIStorageUpgradeable | ||
} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; | ||
import { | ||
AccessControlUpgradeable | ||
} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; | ||
|
||
/// @title a ERC721 upgradeable contract with metadata contract for interactions | ||
contract InteractionFactory is ERC721URIStorageUpgradeable, AccessControlUpgradeable { | ||
/// @notice en event emmited on interactionURI update | ||
/// @param sender sender address of the update | ||
/// @param interactionId id of interaction | ||
/// @param uri the uri itself | ||
event InteractionURIUpdated( | ||
address indexed sender, | ||
uint256 indexed interactionId, | ||
string uri | ||
); | ||
/// @notice en event emitted on interaction mint | ||
event InteractionMinted(address indexed sender, uint256 interactionId); | ||
/// @notice an event emitted on transferability change | ||
event TransferabilitySet(address indexed sender, bool isTransferable); | ||
|
||
/// @notice an error raised when initialManager is zero address | ||
error InitialManagerEmptyError(); | ||
/// @notice an error raised when attemtep to transfer token | ||
error TransferUnallowedError(); | ||
/// @notice en error raised when metadataURI is empty | ||
error MetadataURIEmptyError(); | ||
|
||
/// @notice a role that manage minter role and trasferability | ||
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); | ||
/// @notice a role that manage minting | ||
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); | ||
|
||
/// @notice a variable that indicates whether tokens are non-transferable | ||
bool public isNonTransferable = false; | ||
/// @notice a variable that stores mint timestamps for each interaction id | ||
mapping(uint256 interactionId => uint64 timestamp) public mintedAt; | ||
|
||
function initialize(address initialManager) public initializer { | ||
if (address(initialManager) == address(0)) { | ||
revert InitialManagerEmptyError(); | ||
} | ||
__ERC721_init("InteractionFactory", "IF"); | ||
_setRoleAdmin(MINTER_ROLE, MANAGER_ROLE); | ||
_grantRole(MANAGER_ROLE, initialManager); | ||
_grantRole(MINTER_ROLE, initialManager); | ||
} | ||
|
||
/// @notice enable or disable transferability of the whole collection | ||
/// @param isTransferable true, if the collection should be transferable, false otherwise | ||
function setTransferability(bool isTransferable) external { | ||
_checkRole(MANAGER_ROLE); | ||
isNonTransferable = !isTransferable; | ||
emit TransferabilitySet(msg.sender, isTransferable); | ||
} | ||
|
||
/// @notice mint interaction | ||
/// @param to address of the recipient of the interaction token | ||
/// @param interactionId id of the interaction | ||
/// @param uri tokenURI | ||
function mintInteraction( | ||
address to, | ||
uint256 interactionId, | ||
string memory uri | ||
) external { | ||
_checkRole(MINTER_ROLE); | ||
_mint(to, interactionId); | ||
_setInteractionURI(interactionId, uri); | ||
mintedAt[interactionId] = uint64(block.timestamp); | ||
emit InteractionMinted(to, interactionId); | ||
} | ||
|
||
/// @notice update tokenURI for the interaction by the owner of interaction token | ||
/// @param interactionId id of the interaction | ||
/// @param uri new tokenURI | ||
function updateInteractionURI(uint256 interactionId, string memory uri) external { | ||
if (bytes(uri).length == 0) { | ||
revert MetadataURIEmptyError(); | ||
} | ||
address owner = _ownerOf(interactionId); | ||
_checkAuthorized(owner, msg.sender, interactionId); | ||
_setInteractionURI(interactionId, uri); | ||
} | ||
|
||
/// @inheritdoc IERC721 | ||
function transferFrom( | ||
address from, | ||
address to, | ||
uint256 tokenId | ||
) public virtual override(ERC721Upgradeable, IERC721) { | ||
if (isNonTransferable) { | ||
revert TransferUnallowedError(); | ||
} else { | ||
super.transferFrom(from, to, tokenId); | ||
} | ||
} | ||
|
||
/// @inheritdoc IERC165 | ||
function supportsInterface( | ||
bytes4 interfaceId | ||
) | ||
public | ||
view | ||
override(ERC721URIStorageUpgradeable, AccessControlUpgradeable) | ||
returns (bool) | ||
{ | ||
return (ERC721URIStorageUpgradeable.supportsInterface(interfaceId) || | ||
AccessControlUpgradeable.supportsInterface(interfaceId)); | ||
} | ||
|
||
function _setInteractionURI(uint256 interactionId, string memory uri) internal { | ||
_setTokenURI(interactionId, uri); | ||
emit InteractionURIUpdated(msg.sender, interactionId, uri); | ||
} | ||
} |
Oops, something went wrong.