Hi, I have just discovered that I cannot mint SVG's that have really strong strings. The maximum limit is 24576. Here is the code below:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@limitbreak/creator-token-contracts/contracts/erc721c/ERC721C.sol";
import "./SSTORE2.sol";
import "./Base64.sol";
import "./.deps/DefaultOperatorFilterer.sol";
import "./RoyaltySplitter.sol";
contract SVGEditorNFTV4 is ERC721C, IERC2981, Ownable, DefaultOperatorFilterer {
using Strings for uint256;
struct TokenData {
uint256 royalties;
address royaltyRecipient;
address attributesPointer;
}
mapping(uint256 => TokenData) private _tokenData;
struct Config {
uint256 payment;
uint96 platformRoyalty;
uint256 currentTokenId;
bool paused;
}
Config public config = Config({
payment: 96000000000000000,
platformRoyalty: 69,
currentTokenId: 1,
paused: false
});
constructor() ERC721OpenZeppelin("NFT Cubes", "NFT Cubes") {}
modifier whenNotPaused() {
require(!config.paused, "Contract is paused.");
_;
}
// Implement the _requireCallerIsContractOwner function
function _requireCallerIsContractOwner() internal view override {
require(owner() == _msgSender(), "Caller is not the contract owner");
}
function setPayment(uint256 x) external onlyOwner whenNotPaused {
config.payment = x;
}
function returnPayment() external view returns (uint256) {
return config.payment;
}
function setPlatformRoyalty(uint96 x) external onlyOwner whenNotPaused {
config.platformRoyalty = x;
}
function platformRoyalty() external view returns (uint96) {
return config.platformRoyalty;
}
function division(uint256 decimalPlaces, uint256 numerator, uint256 denominator) pure internal returns(string memory result) {
uint256 factor = 10**decimalPlaces;
uint256 quotient = numerator / denominator;
bool rounding = 2 * ((numerator * factor) % denominator) >= denominator;
uint256 remainder = (numerator * factor / denominator) % factor;
if (rounding) {
remainder += 1;
}
// directly incorporate the logic of numToFixedLengthStr here
bytes memory byteString;
for (uint256 i = 0; i < decimalPlaces; i++) {
uint256 rem = remainder % 10;
byteString = abi.encodePacked(rem.toString(), byteString);
remainder = remainder / 10;
}
result = string(abi.encodePacked(quotient.toString(), '.', string(byteString)));
}
function mint(
string calldata svg,
string calldata name,
uint96 percent
) external whenNotPaused {
// Instantiate the RoyaltySplitter contract with two recipients
RoyaltySplitter splitter = new RoyaltySplitter(payable(msg.sender), payable(0xbe94F301cDb6eD1aFae186D79631B112129de041), percent, config.platformRoyalty);
// Store the contract's address
_tokenData[config.currentTokenId].royalties = 100;
_tokenData[config.currentTokenId].royaltyRecipient = address(splitter);
_safeMint(msg.sender, config.currentTokenId);
// Split the SVG string into chunks and store each chunk separately
uint256 svgLength = bytes(svg).length;
uint256 chunkSize = 24576; // Set the chunk size to the maximum contract storage limit
for (uint256 i = 0; i < svgLength; i += chunkSize) {
uint256 end = i + chunkSize < svgLength ? i + chunkSize : svgLength;
string memory chunk = substring(svg, i, end);
_tokenData[config.currentTokenId].svgChunks.push(chunk);
}
string memory percentString = division(2,percent,100);
_tokenData[config.currentTokenId].attributesPointer = SSTORE2.write(abi.encode(name, percentString, msg.sender));
config.currentTokenId++;
}
/**
* @dev See {IERC721-transferFrom}.
* In this example the added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
*/
function transferFrom(address from, address to, uint256 tokenId) public override onlyAllowedOperator(from) {
super.transferFrom(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
* In this example the added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) public override onlyAllowedOperator(from) {
super.safeTransferFrom(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
* In this example the added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
public
override
onlyAllowedOperator(from)
{
super.safeTransferFrom(from, to, tokenId, data);
}
function tokenURI(uint256 tokenId) public whenNotPaused view virtual override returns (string memory) {
(string memory svg, string memory name, string memory percent, address minter) = abi.decode(SSTORE2.read(_tokenData[tokenId].attributesPointer), (string, string, string, address));
// Convert minter address to string
string memory minterString = _toString(minter);
string memory json = Base64.encode(
abi.encodePacked(
'{"name":"', name, '",',
'"image_data":"', svg, '",',
'"attributes":[{"trait_type":"Minter Fees (%)", "value": "', percent, '"},', // Notice that I've added double quotes around the value.
'{"trait_type":"Minter","value":"', minterString, '"}]',
'}'
)
);
return string(abi.encodePacked("data:application/json;base64,", json));
}
// Function to convert address to string
function _toString(address _addr) private pure returns(string memory) {
bytes32 value = bytes32(uint256(uint160(_addr)));
bytes memory alphabet = "0123456789abcdef";
bytes memory str = new bytes(42);
str[0] = '0';
str[1] = 'x';
for (uint256 i = 0; i < 20; i++) {
str[2+i*2] = alphabet[uint8(value[i + 12] >> 4)];
str[3+i*2] = alphabet[uint8(value[i + 12] & 0x0f)];
}
return string(str);
}
function royaltyInfo(uint256 tokenId, uint256 value) external view virtual override returns (address receiver, uint256 royaltyAmount) {
receiver = _tokenData[tokenId].royaltyRecipient;
royaltyAmount = value * _tokenData[tokenId].royalties / 10000;
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721C, IERC165) returns (bool) {
return super.supportsInterface(interfaceId) || interfaceId == type(IERC2981).interfaceId;
}
function pause() external onlyOwner {
config.paused = !config.paused;
}
}
I am currently looking at an on-chain method and not off-chain. The current method that I am using is splitting the svg strings into chunks but the mint function still does not work. I get the following error
"Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
Returned error: gas required exceeds allowance (30000000)".
I am using remix IDE and tried to set the gas as high as possible like 999999999 and optimisations runs up to 1000. But no matter what I do. It still gives me the error above. I am currently aiming to match my code to be like the one in this link https://etherscan.io/address/0x323d8d9ab29731849345b97758ed4c1323a1e487#code but for svg strings. Is there anything that you can do to help me with the mint and tokenURI functions of the code please? My expectation is that the svg is split into chunks when minting and then put them back together in the tokenURI regardless of the size of the string. Thank you.