I ran into the following limit with an ERC721Upgradeable:
Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
contract MyERC721 is ERC721PausableUpgradeable, AccessControlUpgradeable {
The following ERC721Upgradeable contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "./MyToken.sol";
import "./PriceList.sol";
contract MyERC721 is ERC721PausableUpgradeable, AccessControlUpgradeable {
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
uint256 private constant DEFAULT_RATE = 100;
// keep the order of Status
enum Status { One, Two, Three, Four, Five, Na}
string private constant BASE_URI_METADATA = 'https://api.mydomain.com/metadata?tokenid=';
string private constant URI_STATUS_PARAM = '&status=';
//EnumerableAddressToUintMapUpgradeable.AddressToUintMap private _tokenOwnersHistoric;
mapping (address => uint256) private _tokenHistory;
MyToken myTOken;
PriceList priceList;
mapping (uint256 => string) public name;
mapping (uint256 => bool) public isSpecial;
mapping (uint256 => uint256) public rate;
mapping (uint256 => Status) public status;
function initialize(address _myTokenAddress, address _priceListAddress) initializer public {
__Context_init_unchained();
__ERC721_init_unchained("My NFT", "MNFT");
__AccessControl_init_unchained();
myToken = F24(_myTokenAddress);
priceList = PriceList(_priceListAddress);
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(OPERATOR_ROLE, _msgSender());
}
function mint(address _to, uint256 _tokenId, bool _isSpecial, uint256 _rate) public {
require(hasRole(OPERATOR_ROLE, msg.sender), "Not an operator");
require(_mintAllowed(_to), "mint not allowed");
_mint(_to, _tokenId);
status[_tokenId] = Status.Invitee;
//_tokenOwnersHistoric.set(_to, _tokenId);
_tokenHistory[_to] = _tokenId;
_isSpecial[_tokenId] = _isSpecial;
if(_isSpecial) {
name[_tokenId] = "Special";
if(_rate == 0) {
rate[_tokenId] = DEFAULT_RATE;
} else {
rate[_tokenId] = _rate;
}
} else {
name[_tokenId] = "Normal";
}
_setTokenURI(_tokenId, string(abi.encodePacked(BASE_URI_METADATA, _tokenId.toString(), URI_STATUS_PARAM, uint256(Status.Invitee).toString())));
}
function mintByMyToken(uint256 _tokenId, bool _isSpecial) public {
uint256 price = priceList.getPrice(_tokenId);
require(price != 0, "TokenId not available");
require(myToken.allowance(_msgSender(), address(this)) >= price);
f24.burnFrom(_msgSender(), price);
if(_isSpecial) {
mint(_msgSender(), _tokenId, _isSpecial, 0);
} else {
mint(_msgSender(), _tokenId, _isSpecial, 0);
}
}
function burn(uint256 tokenId) public {
require(hasRole(OPERATOR_ROLE, msg.sender), "Not an operator");
_burn(tokenId);
}
function transferFrom(address from, address to, uint256 tokenId) public virtual override {
super.transferFrom(from, to, tokenId);
_tokenHistory[to] = tokenId;
}
function removeHistoricOwnership(address owner) public {
require(hasRole(OPERATOR_ROLE, msg.sender), "Not an operator");
//_tokenOwnersHistoric.remove(owner);
delete _tokenHistory[owner];
}
function setStatusActive(uint256 tokenId, string memory name) public {
require(hasRole(OPERATOR_ROLE, msg.sender), "Not an operator");
require(status[tokenId] == Status.Two, "Status not Two");
status[tokenId] = Status.One;
name[tokenId] = name;
_setTokenURI(tokenId, string(abi.encodePacked(BASE_URI_METADATA, tokenId.toString(), URI_STATUS_PARAM, uint256(Status.One).toString())));
}
function changeStatus(uint256 tokenId, Status _status) public {
require(hasRole(OPERATOR_ROLE, msg.sender), "Not an operator");
status[tokenId] = _status;
_setTokenURI(tokenId, string(abi.encodePacked(BASE_URI_METADATA, tokenId.toString(), URI_STATUS_PARAM, uint256(_status).toString())));
}
function setRate(uint256 tokenId, uint256 _rate) public {
require(hasRole(OPERATOR_ROLE, msg.sender), "Not an operator");
rate[tokenId] = _rate;
}
function setName(uint256 tokenId, string memory name) public {
require(_msgSender() == this.ownerOf(tokenId), "Not account owner");
name[tokenId] = name;
}
function pause() public {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not an admin");
_pause();
}
function unpause() public {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not an admin");
_unpause();
}
function _mintAllowed(address to) internal view returns(bool){
//return !(this.balanceOf(to) >= 1) && ! _tokenHistory(to);
return !(this.balanceOf(to) >= 1 || _tokenHistory[to] != 0);
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {
require(!paused(), "Transfers suspended");
if(AddressUpgradeable.isContract(to)) {
require(this.status(tokenId) == Status.Two, "Not allowed to transfer token");
} else {
has already token");
require(_tokenHistory[to] == 0 || _tokenHistory[to] == tokenId, "Receiver has already token");
if(from != address(0) && to != address(0)) {
require(this.status(tokenId) == Status.One || this.status(tokenId) == Status.Two, "Transfer not allowed in this status");
}
}
super._beforeTokenTransfer(from, to, tokenId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
contract MyToken is ERC20PausableUpgradeable, ERC20BurnableUpgradeable, AccessControlUpgradeable {
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
function initialize() initializer public{
__Context_init_unchained();
__ERC20_init_unchained("My Token", "MTK");
__AccessControl_init_unchained();
_setupDecimals(2);
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(OPERATOR_ROLE, _msgSender());
}
function mint(address account, uint256 amount) public {
require(hasRole(OPERATOR_ROLE, msg.sender), "Caller is not an operator");
_mint(account, amount);
}
function burn(address account, uint256 amount) public {
require(hasRole(OPERATOR_ROLE, msg.sender), "Caller is not an operator");
_burn(account, amount);
}
function pause() public {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Caller is not an admin");
_pause();
}
function unpause() public {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Caller is not an admin");
_unpause();
}
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20Upgradeable, ERC20PausableUpgradeable) {
super._beforeTokenTransfer(from, to, amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "./libraries/EnumerableUintToUintMapUpgradeable.sol";
contract Fiat24PriceList is Initializable, AccessControlUpgradeable {
using EnumerableUintToUintMapUpgradeable for EnumerableUintToUintMapUpgradeable.UintToUintMap;
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
EnumerableUintToUintMapUpgradeable.UintToUintMap private _priceList;
function initialize() public initializer {
__AccessControl_init_unchained();
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(OPERATOR_ROLE, _msgSender());
_priceList.set(1,100000000);
_priceList.set(2, 10000000);
_priceList.set(3, 1000000);
_priceList.set(4, 100000);
_priceList.set(5, 10000);
_priceList.set(6, 1000);
_priceList.set(7, 100);
_priceList.set(8, 10);
}
function getPrice(uint256 accountNumber) external view returns(uint256) {
uint8 digits = _numDigits(accountNumber);
if(!_priceList.contains(digits)) {
return 0;
} else {
return _priceList.get(digits);
}
}
function setPrice(uint256 digits, uint256 price) external {
require(hasRole(OPERATOR_ROLE, msg.sender), "Caller is not an operator");
_priceList.set(digits, price);
}
function _numDigits(uint256 number) internal pure returns (uint8) {
uint8 digits = 0;
while (number != 0) {
number /= 10;
digits++;
}
return digits;
}
}
truffle run contract size
│ MyERC721 │ 24.06 K…
I am already using sole optimisation in truffle-config.js:
optimizer: {
enabled: true,
runs: 1
}
Moreover, I’ve tried to use Factories for MyToken and PriceList.
Is there any good advice to split the contract or any other measures to shrink the contract size?