Ape Curtis Testnet
    /

    Contract Diff Checker

    Contract Name:
    GobsDataSource

    Contract Source Code:

    File 1 of 1 : GobsDataSource

    /**
     *Submitted for verification at curtis.apescan.io on 2024-11-21
     */
    
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    /**
     * @title GobsDataSource
     * @notice Stores RGBA pixel data and generates SVGs for on-chain Goblin images.
     */
    contract GobsDataSource {
        address payable internal deployer;
        bool private contractSealed = false;
        string internal constant SVG_HEADER =
            'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 29 29"><rect width="100%" height="100%" fill="#0047B1"/>';
        string internal constant SVG_FOOTER = "</svg>";
        bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
        uint256 private constant PIXEL_DATA_SIZE = 29 * 29 * 3;
    
        struct Trait {
            string traitType;
            string value;
        }
    
        struct GobData {
            bytes pixelData; // Row-major RGBA data (29x29 pixels, 3364 bytes)
            Trait[] traits; // Array of traits for the token
        }
    
        mapping(uint256 => GobData) private gobData; // Stores data for each Goblin
    
        modifier onlyDeployer() {
            require(msg.sender == deployer, "Only deployer.");
            _;
        }
    
        modifier unsealed() {
            require(!contractSealed, "Contract is sealed.");
            _;
        }
    
        constructor() {
            deployer = payable(msg.sender);
        }
    
        /**
         * @notice Seal the contract to prevent further modifications.
         */
        function sealContract() external onlyDeployer unsealed {
            contractSealed = true;
        }
    
        /**
         * @notice Store RGBA pixel data for a Goblin.
         * @param tokenId The ID of the Goblin (0 <= tokenId < 2222).
         * @param pixelData The RGBA data (row-major order, 29x29 pixels, 3364 bytes).
         */
        function storePixelData(uint256 tokenId, bytes memory pixelData)
            external
            onlyDeployer
            unsealed
        {
            require(tokenId < 2222, "Invalid tokenId");
            require(
                pixelData.length == PIXEL_DATA_SIZE,
                "Invalid pixel data length"
            );
            gobData[tokenId].pixelData = pixelData;
        }
    
        function batchStorePixelData(
            uint256[] memory tokenIds,
            bytes[] memory pixelDataArray
        ) external onlyDeployer unsealed {
            require(tokenIds.length == pixelDataArray.length, "Mismatched arrays");
    
            for (uint256 i = 0; i < tokenIds.length; i++) {
                uint256 tokenId = tokenIds[i];
                bytes memory pixelData = pixelDataArray[i];
    
                require(tokenId < 2222, "Invalid tokenId");
                require(
                    pixelData.length == 29 * 29 * 3,
                    "Invalid pixel data length"
                );
    
                gobData[tokenId].pixelData = pixelData;
            }
        }
    
        function storeTraits(uint256 tokenId, Trait[] memory traits)
            external
            onlyDeployer
            unsealed
        {
            uint256 len = traits.length;
            require(len > 0, "Traits cannot be empty");
    
            // Resize existing traits array
            delete gobData[tokenId].traits; // Clear previous data
            for (uint256 i = 0; i < len; i++) {
                gobData[tokenId].traits.push(traits[i]);
            }
        }
    
        function batchStoreTraits(
            uint256[] memory tokenIds,
            Trait[][] memory traitsArray
        ) external onlyDeployer unsealed {
            require(tokenIds.length == traitsArray.length, "Mismatched arrays");
    
            for (uint256 i = 0; i < tokenIds.length; i++) {
                uint256 tokenId = tokenIds[i];
                Trait[] memory traits = traitsArray[i];
    
                delete gobData[tokenId].traits; // Clear existing traits
    
                for (uint256 j = 0; j < traits.length; j++) {
                    gobData[tokenId].traits.push(traits[j]);
                }
            }
        }
    
        // Retrieve traits for a token
        function getTraits(uint256 tokenId) external view returns (string memory) {
            require(gobData[tokenId].traits.length > 0, "Traits not set");
    
            string memory jsonTraits = "[";
    
            for (uint256 i = 0; i < gobData[tokenId].traits.length; i++) {
                jsonTraits = string.concat(
                    jsonTraits,
                    '{"trait_type": "',
                    gobData[tokenId].traits[i].traitType,
                    '", "value": "',
                    gobData[tokenId].traits[i].value,
                    '"}'
                );
    
                if (i < gobData[tokenId].traits.length - 1) {
                    jsonTraits = string.concat(jsonTraits, ",");
                }
            }
    
            jsonTraits = string.concat(jsonTraits, "]");
            return jsonTraits;
        }
    
        /**
         * @notice Retrieve RGB pixel data for a Goblin.
         * @param tokenId The ID of the Goblin.
         * @return The RGB pixel data.
         */
        function getGobData(uint256 tokenId) external view returns (bytes memory) {
            require(tokenId < 2222, "Invalid tokenId");
            require(
                gobData[tokenId].pixelData.length == PIXEL_DATA_SIZE,
                "Pixel data not set"
            );
            return gobData[tokenId].pixelData;
        }
    
        /**
         * @notice Generate the SVG for a Goblin from its pixel data.
         * @param tokenId The ID of the Goblin.
         */
        function getGobSVG(uint256 tokenId)
            external
            view
            returns (string memory svg)
        {
            require(tokenId < 2222, "Invalid tokenId");
            bytes memory pixels = gobData[tokenId].pixelData;
    
            require(pixels.length > 0, "Pixel data not set");
    
            bytes memory result = abi.encodePacked(SVG_HEADER); // Start with the header
            bytes memory buffer = new bytes(7); // Buffer for color hex
    
            for (uint256 y = 0; y < 29; y++) {
                for (uint256 x = 0; x < 29; x++) {
                    uint256 p = (y * 29 + x) * 3;
                    if (
                        pixels[p] == 0x00 &&
                        pixels[p + 1] == 0x47 &&
                        pixels[p + 2] == 0xb1
                    ) {
                        continue; // Skip background pixels
                    }
    
                    // Convert RGB to hex
                    buffer[0] = "#";
                    for (uint256 i = 0; i < 3; i++) {
                        uint8 value = uint8(pixels[p + i]);
                        buffer[1 + i * 2] = _HEX_SYMBOLS[value >> 4];
                        buffer[2 + i * 2] = _HEX_SYMBOLS[value & 0xf];
                    }
    
                    // Append rect to the result
                    result = abi.encodePacked(
                        result,
                        '<rect x="',
                        toString(x),
                        '" y="',
                        toString(y),
                        '" width="1" height="1" shape-rendering="crispEdges" fill="',
                        string(buffer),
                        '"/>'
                    );
                }
            }
    
            svg = string(abi.encodePacked(result, SVG_FOOTER)); // Append footer
        }
    
        /// @dev Returns the base 10 decimal representation of `value`.
        function toString(uint256 value) internal pure returns (string memory str) {
            /// @solidity memory-safe-assembly
            assembly {
                // The maximum value of a uint256 contains 78 digits (1 byte per digit), but
                // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
                // We will need 1 word for the trailing zeros padding, 1 word for the length,
                // and 3 words for a maximum of 78 digits.
                str := add(mload(0x40), 0x80)
                // Update the free memory pointer to allocate.
                mstore(0x40, add(str, 0x20))
                // Zeroize the slot after the string.
                mstore(str, 0)
    
                // Cache the end of the memory to calculate the length later.
                let end := str
    
                let w := not(0) // Tsk.
                // We write the string from rightmost digit to leftmost digit.
                // The following is essentially a do-while loop that also handles the zero case.
                for {
                    let temp := value
                } 1 {
    
                } {
                    str := add(str, w) // `sub(str, 1)`.
                    // Write the character to the pointer.
                    // The ASCII index of the '0' character is 48.
                    mstore8(str, add(48, mod(temp, 10)))
                    // Keep dividing `temp` until zero.
                    temp := div(temp, 10)
                    if iszero(temp) {
                        break
                    }
                }
    
                let length := sub(end, str)
                // Move the pointer 32 bytes leftwards to make room for the length.
                str := sub(str, 0x20)
                // Store the length.
                mstore(str, length)
            }
        }
    }

    Please enter a contract address above to load the contract details and source code.

    Context size (optional):