cyberia/research/events/ethereum/contracts/CyberValleyEventTicket.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "./CyberValley.sol";

// TODO: Pad layout after general work finish
contract CyberValleyEventTicket is ERC721, AccessControl {
    using CyberValley for CyberValley.Multihash;

    bytes32 public constant MASTER_ROLE = keccak256("MASTER_ROLE");
    bytes32 public constant STAFF_ROLE = keccak256("STAFF_ROLE");
    bytes32 public constant EVENT_MANAGER_ROLE =
        keccak256("EVENT_MANAGER_ROLE");

    uint256 private lastTokenId;
    mapping(uint256 => CyberValley.Multihash) public ticketsMeta;
    mapping(uint256 => bool) public isRedeemed;

    string public ipfsHost;
    address public eventManagerAddress;

    event TicketMinted(
        uint256 eventId,
        uint256 ticketId,
        uint256 categoryId,
        address owner,
        bytes32 digest,
        uint8 hashFunction,
        uint8 size,
        address referrer,
        uint256 pricePaid
    );

    event TicketRedeemed(uint256 ticketId);

    constructor(
        string memory name,
        string memory symbol,
        address _master,
        string memory _ipfsHost
    ) ERC721(name, symbol) {
        _grantRole(DEFAULT_ADMIN_ROLE, _master);
        _grantRole(MASTER_ROLE, _master);
        _grantRole(STAFF_ROLE, _master);
        ipfsHost = _ipfsHost;
    }

    modifier onlyEventManager() {
        require(
            hasRole(EVENT_MANAGER_ROLE, msg.sender),
            "Must have event manager role"
        );
        _;
    }

    modifier onlyMaster() {
        require(hasRole(MASTER_ROLE, msg.sender), "Must have master role");
        _;
    }

    modifier onlyStaff() {
        require(hasRole(STAFF_ROLE, msg.sender), "Must have staff role");
        _;
    }

    function setEventManagerAddress(address _eventManagerAddress) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(
            _eventManagerAddress != address(0),
            "Event manager address cannot be zero"
        );
        require(
            eventManagerAddress == address(0),
            "Event manager was already saved"
        );
        _grantRole(EVENT_MANAGER_ROLE, _eventManagerAddress);
        eventManagerAddress = _eventManagerAddress;
    }

    function setIpfsHost(string calldata host) public onlyMaster {
        ipfsHost = host;
    }

    function mint(
        address to,
        uint256 eventId,
        uint256 categoryId,
        bytes32 digest,
        uint8 hashFunction,
        uint8 size,
        address referrer,
        uint256 pricePaid
    ) external onlyEventManager {
        lastTokenId += 1;
        _mint(to, lastTokenId);
        ticketsMeta[lastTokenId] = CyberValley.Multihash(
            digest,
            hashFunction,
            size
        );
        emit TicketMinted(eventId, lastTokenId, categoryId, to, digest, hashFunction, size, referrer, pricePaid);
    }

    function mintBatch(
        address to,
        uint256 eventId,
        uint256 categoryId,
        uint256 amount,
        bytes32 digest,
        uint8 hashFunction,
        uint8 size,
        address referrer,
        uint256 pricePaid
    ) external onlyEventManager {
        for (uint256 i = 0; i < amount; i++) {
            lastTokenId += 1;
            _mint(to, lastTokenId);
            ticketsMeta[lastTokenId] = CyberValley.Multihash(
                digest,
                hashFunction,
                size
            );
            emit TicketMinted(eventId, lastTokenId, categoryId, to, digest, hashFunction, size, referrer, pricePaid);
        }
    }

    function ticketMeta(
        uint256 tokenId
    ) external view returns (bytes32 digest, uint8 hashFunction, uint8 size) {
        _requireOwned(tokenId);
        CyberValley.Multihash storage meta = ticketsMeta[tokenId];
        return (meta.digest, meta.hashFunction, meta.size);
    }

    function tokenURI(
        uint256 tokenId
    ) public view virtual override returns (string memory) {
        _requireOwned(tokenId);
        CyberValley.Multihash memory mh = ticketsMeta[tokenId];
        string memory cid = toCID(mh.digest, mh.hashFunction, mh.size);
        return string(abi.encodePacked(ipfsHost, "/", cid));
    }

    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        require(from == address(0), "Token transfer is disabled");
        super.transferFrom(from, to, tokenId);
    }

    function redeemTicket(uint256 tokenId) external onlyStaff {
        require(!isRedeemed[tokenId], "Token was redeemed already");
        isRedeemed[tokenId] = true;
        emit TicketRedeemed(tokenId);
    }

    function supportsInterface(
        bytes4 interfaceId
    ) public view virtual override(ERC721, AccessControl) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    function toCID(
        bytes32 digest,
        uint8 hashFunction,
        uint8 size
    ) internal pure returns (string memory) {
        if (size == 0) return ""; // Return empty string for size 0

        bytes memory hashBytes = new bytes(32);
        for (uint j = 0; j < 32; j++) {
            hashBytes[j] = digest[j];
        }

        bytes memory multihashBytes = new bytes(2 + hashBytes.length);
        multihashBytes[0] = bytes1(hashFunction);
        multihashBytes[1] = bytes1(size);

        for (uint j = 0; j < hashBytes.length; j++) {
            multihashBytes[j + 2] = hashBytes[j];
        }

        return string(Base58.encode(multihashBytes));
    }
}

// Source: https://github.com/storyicon/base58-solidity/blob/master/contracts/Base58.sol
// It's hard copied cause of requirement to deploy this library if it's imported
// and i it's damn stupid for the given use case
library Base58 {
    bytes constant ALPHABET =
        "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

    function encode(bytes memory data_) internal pure returns (bytes memory) {
        unchecked {
            uint256 size = data_.length;
            uint256 zeroCount;
            while (zeroCount < size && data_[zeroCount] == 0) {
                zeroCount++;
            }
            size = zeroCount + ((size - zeroCount) * 8351) / 6115 + 1;
            bytes memory slot = new bytes(size);
            uint32 carry;
            int256 m;
            int256 high = int256(size) - 1;
            for (uint256 i = 0; i < data_.length; i++) {
                m = int256(size - 1);
                for (carry = uint8(data_[i]); m > high || carry != 0; m--) {
                    carry = carry + 256 * uint8(slot[uint256(m)]);
                    slot[uint256(m)] = bytes1(uint8(carry % 58));
                    carry /= 58;
                }
                high = m;
            }
            uint256 n;
            for (n = zeroCount; n < size && slot[n] == 0; n++) {}
            size = slot.length - (n - zeroCount);
            bytes memory out = new bytes(size);
            for (uint256 i = 0; i < size; i++) {
                uint256 j = i + n - zeroCount;
                out[i] = ALPHABET[uint8(slot[j])];
            }
            return out;
        }
    }
}

Homonyms

cyberia/research/events/backend/ethereum_artifacts/contracts/CyberValleyEventTicket.sol
CyberValleyEventTicket.sol

Graph