Skip to content

Commit

Permalink
Merge pull request #103 from Aut-Labs/chore/copy-over-interactions
Browse files Browse the repository at this point in the history
chore(interactions): cp over interactions
  • Loading branch information
pegahcarter authored Jul 25, 2024
2 parents e91ea05 + 43b424e commit c48c148
Show file tree
Hide file tree
Showing 3 changed files with 425 additions and 0 deletions.
115 changes: 115 additions & 0 deletions contracts/interactions/InteractionDataset.sol
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);
}
}
125 changes: 125 additions & 0 deletions contracts/interactions/InteractionFactory.sol
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);
}
}
Loading

0 comments on commit c48c148

Please sign in to comment.