Skip to content

Instantly share code, notes, and snippets.

@aesthezel
Created May 26, 2023 15:31
Show Gist options
  • Save aesthezel/dfb0e2b416872fd16e1f701e6be06674 to your computer and use it in GitHub Desktop.
Save aesthezel/dfb0e2b416872fd16e1f701e6be06674 to your computer and use it in GitHub Desktop.
Entity DNA storage
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
contract EntityDNA is Ownable
{
using Address for address;
using Strings for uint256;
struct Entity
{
uint256 id;
uint256 baseId;
bool isBase;
bool isGenesis;
uint[] genesisTraits;
uint seed;
uint dna;
address owner;
bool isBurned;
}
mapping(uint256 => Entity) private entities;
mapping(uint256 => address) private originalOwner;
mapping(uint256 => address) private _tokenApprovals;
mapping(address => mapping(address => bool)) private _operatorApprovals;
mapping(address => uint256) private _balances;
// ERC721Enumerable
mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
mapping(uint256 => uint256) private _ownedTokensIndex;
uint256[] private _allTokens;
mapping(uint256 => uint256) private _allTokensIndex;
uint256 public totalEntities;
uint[] private _bitsInitial;
uint[] private _bitsCapacity;
string private _name;
string private _symbol;
string private _tokenURIServer;
event Transfer(address indexed _from, address indexed _to, uint256 indexed _id);
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _id);
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
constructor(string memory name, string memory symbol, string memory serverURI, uint[] memory bits)
{
_name = name;
_symbol = symbol;
_tokenURIServer = serverURI;
require(bits.length == 10, "The bits length doesn't equal to eleven parameters");
_bitsInitial = bits;
for(uint i = 0; i < bits.length; i++)
{
_bitsCapacity.push(log2(bits[i]));
}
}
function transferFrom (address _from, address _to, uint256 _tokenId) external payable
{
require(_isApprovedOrOwner(_msgSender(), _tokenId), "ERC721: transfer caller is not owner nor approved");
_transfer(_from, _to, _tokenId);
}
function mintEntities(address to, uint quantity) public onlyOwner()
{
entities[totalEntities].id = totalEntities;
entities[totalEntities].seed = block.timestamp + block.difficulty * 1;
entities[totalEntities].baseId = totalEntities;
entities[totalEntities].isBase = true;
entities[totalEntities].isGenesis = false;
entities[totalEntities].owner = to;
entities[totalEntities].dna = randomEntity(block.timestamp + block.difficulty * 1, totalEntities * _bitsCapacity.length);
originalOwner[totalEntities] = to;
_balances[to] += quantity;
totalEntities += quantity;
}
function mintSaleEntities(address to, uint quantity, uint baseFamily, uint familyInitialLimit, uint familyFinalLimit, uint baseRarity) public onlyOwner()
{
entities[totalEntities].id = totalEntities;
entities[totalEntities].seed = block.timestamp + block.difficulty * 1;
entities[totalEntities].baseId = totalEntities;
entities[totalEntities].isBase = true;
entities[totalEntities].isGenesis = true;
entities[totalEntities].owner = to;
entities[totalEntities].dna = randomLimitedEntity(block.timestamp + block.difficulty * 1,
totalEntities * _bitsCapacity.length,
baseFamily,
familyInitialLimit,
familyFinalLimit,
baseRarity);
// Genesis Traits
entities[totalEntities].genesisTraits.push(baseFamily);
entities[totalEntities].genesisTraits.push(familyInitialLimit);
entities[totalEntities].genesisTraits.push(familyFinalLimit);
entities[totalEntities].genesisTraits.push(baseRarity);
originalOwner[totalEntities] = to;
_balances[to] += quantity;
totalEntities += quantity;
}
function getEntityDNA(uint id) public view returns (uint dna)
{
Entity memory entity = getVirtualEntity(id);
uint seed = entity.seed;
uint seedExtra = (id - entity.baseId) * _bitsCapacity.length;
return entity.isGenesis ?
randomLimitedEntity(
seed,
seedExtra,
entity.genesisTraits[0],
entity.genesisTraits[1],
entity.genesisTraits[2],
entity.genesisTraits[3]) :
randomEntity(seed, seedExtra);
}
function getVirtualEntity(uint id) public view returns (Entity memory)
{
require(id < totalEntities, "Entity does not exist!");
if(entities[id].seed != 0) return entities[id];
// If the Entity doesnt generated yet
Entity storage baseNFT = entities[0];
for(uint i = id; i >= 0; i--)
{
if(entities[i].isBase)
{
baseNFT = entities[i];
break;
}
}
uint seed = baseNFT.seed;
uint seedExtra = (id - baseNFT.id) * _bitsCapacity.length;
Entity memory entity;
entity.id = id;
entity.baseId = baseNFT.id;
entity.dna = baseNFT.isGenesis ?
randomLimitedEntity(seed,
seedExtra,
baseNFT.genesisTraits[0],
baseNFT.genesisTraits[1],
baseNFT.genesisTraits[2],
baseNFT.genesisTraits[3]) :
randomEntity(seed, seedExtra);
entity.owner = originalOwner[baseNFT.id];
entity.seed = baseNFT.seed;
entity.isBase = false;
entity.isGenesis = baseNFT.isGenesis;
entity.genesisTraits = baseNFT.genesisTraits;
return entity;
}
function randomEntity(uint seed, uint startingSeed) private view returns (uint)
{
uint cromo = 0;
uint bitPosition = 0;
uint lastFamily = 0;
uint randomValue = 0;
for(uint i = 0; i < _bitsCapacity.length; i++)
{
randomValue = random(_bitsInitial[i], seed, startingSeed + i);
if(i % 2 == 0)
{
lastFamily = random(_bitsInitial[i], seed, startingSeed + i);
}
if(i % 2 != 0)
{
if(randomValue > 4 && lastFamily < 12)
{
randomValue = 4 - random(4, seed, startingSeed % i);
}
else if (lastFamily >= 12 && randomValue < 5)
{
randomValue = 9 - random(4, seed, startingSeed % i);
}
}
cromo = cromo | (randomValue << bitPosition);
bitPosition += _bitsCapacity[i];
}
return cromo;
}
// Custom random properties selected by DNA bites
function randomLimitedEntity(uint seed, uint startingSeed, uint baseFamily, uint familyInitialLimit, uint familyFinalLimit, uint baseRarity) private view returns (uint)
{
uint cromo = 0;
uint bitPosition = 0;
uint randomValue = 0;
for(uint i = 0; i < _bitsCapacity.length; i++)
{
if(i % 2 == 0 && i != 0)
{
randomValue = familyInitialLimit + (familyFinalLimit - random(familyFinalLimit, seed, startingSeed + i));
}
else if(i == 0)
{
randomValue = baseFamily;
}
if(i % 2 != 0)
{
randomValue = baseRarity;
}
cromo = cromo | (randomValue << bitPosition);
bitPosition += _bitsCapacity[i];
}
return cromo;
}
function random(uint256 max, uint256 seed, uint seedExtra) private pure returns (uint256)
{
return uint256(
keccak256(
abi.encodePacked(
seed,
seedExtra
)
)
) % max;
}
// Helper function get a base 2 - Economic way gas < 700;
function log2(uint x) private pure returns (uint y)
{
assembly
{
let arg := x
x := sub(x,1)
x := or(x, div(x, 0x02))
x := or(x, div(x, 0x04))
x := or(x, div(x, 0x10))
x := or(x, div(x, 0x100))
x := or(x, div(x, 0x10000))
x := or(x, div(x, 0x100000000))
x := or(x, div(x, 0x10000000000000000))
x := or(x, div(x, 0x100000000000000000000000000000000))
x := add(x, 1)
let m := mload(0x40)
mstore(m, 0xf8f9cbfae6cc78fbefe7cdc3a1793dfcf4f0e8bbd8cec470b6a28a7a5a3e1efd)
mstore(add(m,0x20), 0xf5ecf1b3e9debc68e1d9cfabc5997135bfb7a7a3938b7b606b5b4b3f2f1f0ffe)
mstore(add(m,0x40), 0xf6e4ed9ff2d6b458eadcdf97bd91692de2d4da8fd2d0ac50c6ae9a8272523616)
mstore(add(m,0x60), 0xc8c0b887b0a8a4489c948c7f847c6125746c645c544c444038302820181008ff)
mstore(add(m,0x80), 0xf7cae577eec2a03cf3bad76fb589591debb2dd67e0aa9834bea6925f6a4a2e0e)
mstore(add(m,0xa0), 0xe39ed557db96902cd38ed14fad815115c786af479b7e83247363534337271707)
mstore(add(m,0xc0), 0xc976c13bb96e881cb166a933a55e490d9d56952b8d4e801485467d2362422606)
mstore(add(m,0xe0), 0x753a6d1b65325d0c552a4d1345224105391a310b29122104190a110309020100)
mstore(0x40, add(m, 0x100))
let magic := 0x818283848586878898a8b8c8d8e8f929395969799a9b9d9e9faaeb6bedeeff
let shift := 0x100000000000000000000000000000000000000000000000000000000000000
let a := div(mul(x, magic), shift)
y := div(mload(add(m,sub(255,a))), shift)
y := add(y, mul(256, gt(arg, 0x8000000000000000000000000000000000000000000000000000000000000000)))
}
}
// ERC721
function balanceOf(address _owner) public view returns (uint256)
{
require(_owner != address(0), "ERC721: balance query for the zero address");
return _balances[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address)
{
Entity memory entity = getVirtualEntity(_tokenId);
require(entity.owner != address(0), "ERC721: owner query for nonexistent token");
return entity.owner;
}
function safeTransferFrom(address _from, address _to, uint256 _tokenId) public payable
{
safeTransferFrom(_from, _to, _tokenId, "");
}
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) public payable
{
require(_isApprovedOrOwner(_msgSender(), _tokenId), "ERC721: transfer caller is not owner nor approved");
_safeTransfer(_from, _to, _tokenId, data);
}
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal virtual
{
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
}
function _transfer(address from, address to, uint256 id) internal virtual
{
Entity memory entity = getVirtualEntity(id);
require(entity.owner == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");
address fromQuestion = from.isContract() ? address(0) : from;
_beforeTokenTransfer(fromQuestion, to, id);
// Clear approvals from the previous owner
_approve(address(0), id);
_balances[from] -= 1;
_balances[to] += 1;
entities[id].owner = to;
if(entities[id].seed == 0)
{
uint seed = entity.seed;
uint seedExtra = (id - entity.baseId) * _bitsCapacity.length;
entities[id].id = id;
entities[id].baseId = entity.baseId;
entities[id].seed = entity.seed;
entities[id].dna = entity.isGenesis ?
randomLimitedEntity(seed,
seedExtra,
entity.genesisTraits[0],
entity.genesisTraits[1],
entity.genesisTraits[2],
entity.genesisTraits[3]) :
randomEntity(seed, seedExtra);
entities[id].isBase = false;
entities[id].isGenesis = entity.isGenesis;
entities[id].genesisTraits = entity.genesisTraits;
}
emit Transfer(from, to, id);
_afterTokenTransfer(from, to, id);
}
function _isApprovedOrOwner(address spender, uint256 id) internal view virtual returns (bool)
{
require(_exists(id), "ERC721: operator query for nonexistent token");
address owner = ownerOf(id);
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(id) == spender);
}
function _exists(uint256 id) internal view virtual returns (bool)
{
Entity memory entity = getVirtualEntity(id);
return entity.owner != address(0);
}
function getApproved(uint256 id) public view virtual returns (address)
{
require(_exists(id), "ERC721: approved query for nonexistent token");
return _tokenApprovals[id];
}
function approve(address _approved, uint256 _tokenId) external payable
{
_approve(_approved, _tokenId);
}
function _approve(address to, uint256 id) internal virtual
{
_tokenApprovals[id] = to;
emit Approval(ownerOf(id), to, id);
}
function setApprovalForAll(address _operator, bool _approved) public virtual
{
_setApprovalForAll(_msgSender(), _operator, _approved);
}
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual
{
require(owner != operator, "ERC721: approve to caller");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
function isApprovedForAll(address owner, address operator) public view returns (bool)
{
return _operatorApprovals[owner][operator];
}
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) private returns (bool)
{
if (to.isContract()) {
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
function tokenURI(uint256 id) public view returns (string memory)
{
require(_exists(id), "ERC721: request URI for nonexistent token");
string memory generatedTokenURI = string(abi.encodePacked(_tokenURIServer, Strings.toString(id)));
return generatedTokenURI;
}
// END ERC721
// ERC721Enumerable
function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
return _ownedTokens[owner][index];
}
function totalSupply() public view returns (uint256) {
return totalEntities;
}
function tokenByIndex(uint256 index) public view returns (uint256) {
require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
return _allTokens[index];
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal
{
if (from == address(0)) {
_addTokenToAllTokensEnumeration(tokenId);
} else if (from != to) {
_removeTokenFromOwnerEnumeration(from, tokenId);
}
if (to == address(0)) {
_removeTokenFromAllTokensEnumeration(tokenId);
} else if (to != from) {
_addTokenToOwnerEnumeration(to, tokenId);
}
}
function _afterTokenTransfer(address from, address to, uint256 tokenId) internal {}
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
uint256 length = balanceOf(to);
_ownedTokens[to][length] = tokenId;
_ownedTokensIndex[tokenId] = length;
}
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokensIndex[tokenId] = _allTokens.length;
_allTokens.push(tokenId);
}
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = balanceOf(from) - 1;
uint256 tokenIndex = _ownedTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary
if (tokenIndex != lastTokenIndex) {
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
}
// This also deletes the contents at the last position of the array
delete _ownedTokensIndex[tokenId];
delete _ownedTokens[from][lastTokenIndex];
}
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
// To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = _allTokens.length - 1;
uint256 tokenIndex = _allTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
// rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
// an 'if' statement (like in _removeTokenFromOwnerEnumeration)
uint256 lastTokenId = _allTokens[lastTokenIndex];
_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
// This also deletes the contents at the last position of the array
delete _allTokensIndex[tokenId];
_allTokens.pop();
}
// END ERC721Enumerable
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment