Contract Name:
TokenDNAStorage
Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./utils/Bytecode.sol";
/**
@title A key-value storage with auto-generated keys for storing chunks of data with a lower write & read cost.
@author Agustin Aguilar <[email protected]>
Readme: https://github.com/0xsequence/sstore2#readme
*/
library SSTORE2 {
error WriteError();
/**
@notice Stores `_data` and returns `pointer` as key for later retrieval
@dev The pointer is a contract address with `_data` as code
@param _data to be written
@return pointer Pointer to the written `_data`
*/
function write(bytes memory _data) internal returns (address pointer) {
// Append 00 to _data so contract can't be called
// Build init code
bytes memory code = Bytecode.creationCodeFor(
abi.encodePacked(
hex'00',
_data
)
);
// Deploy contract using create
assembly { pointer := create(0, add(code, 32), mload(code)) }
// Address MUST be non-zero
if (pointer == address(0)) revert WriteError();
}
/**
@notice Reads the contents of the `_pointer` code as data, skips the first byte
@dev The function is intended for reading pointers generated by `write`
@param _pointer to be read
@return data read from `_pointer` contract
*/
function read(address _pointer) internal view returns (bytes memory) {
return Bytecode.codeAt(_pointer, 1, type(uint256).max);
}
/**
@notice Reads the contents of the `_pointer` code as data, skips the first byte
@dev The function is intended for reading pointers generated by `write`
@param _pointer to be read
@param _start number of bytes to skip
@return data read from `_pointer` contract
*/
function read(address _pointer, uint256 _start) internal view returns (bytes memory) {
return Bytecode.codeAt(_pointer, _start + 1, type(uint256).max);
}
/**
@notice Reads the contents of the `_pointer` code as data, skips the first byte
@dev The function is intended for reading pointers generated by `write`
@param _pointer to be read
@param _start number of bytes to skip
@param _end index before which to end extraction
@return data read from `_pointer` contract
*/
function read(address _pointer, uint256 _start, uint256 _end) internal view returns (bytes memory) {
return Bytecode.codeAt(_pointer, _start + 1, _end + 1);
}
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library Bytecode {
error InvalidCodeAtRange(uint256 _size, uint256 _start, uint256 _end);
/**
@notice Generate a creation code that results on a contract with `_code` as bytecode
@param _code The returning value of the resulting `creationCode`
@return creationCode (constructor) for new contract
*/
function creationCodeFor(bytes memory _code) internal pure returns (bytes memory) {
/*
0x00 0x63 0x63XXXXXX PUSH4 _code.length size
0x01 0x80 0x80 DUP1 size size
0x02 0x60 0x600e PUSH1 14 14 size size
0x03 0x60 0x6000 PUSH1 00 0 14 size size
0x04 0x39 0x39 CODECOPY size
0x05 0x60 0x6000 PUSH1 00 0 size
0x06 0xf3 0xf3 RETURN
<CODE>
*/
return abi.encodePacked(
hex"63",
uint32(_code.length),
hex"80_60_0E_60_00_39_60_00_F3",
_code
);
}
/**
@notice Returns the size of the code on a given address
@param _addr Address that may or may not contain code
@return size of the code on the given `_addr`
*/
function codeSize(address _addr) internal view returns (uint256 size) {
assembly { size := extcodesize(_addr) }
}
/**
@notice Returns the code of a given address
@dev It will fail if `_end < _start`
@param _addr Address that may or may not contain code
@param _start number of bytes of code to skip on read
@param _end index before which to end extraction
@return oCode read from `_addr` deployed bytecode
Forked from: https://gist.github.com/KardanovIR/fe98661df9338c842b4a30306d507fbd
*/
function codeAt(address _addr, uint256 _start, uint256 _end) internal view returns (bytes memory oCode) {
uint256 csize = codeSize(_addr);
if (csize == 0) return bytes("");
if (_start > csize) return bytes("");
if (_end < _start) revert InvalidCodeAtRange(csize, _start, _end);
unchecked {
uint256 reqSize = _end - _start;
uint256 maxSize = csize - _start;
uint256 size = maxSize < reqSize ? maxSize : reqSize;
assembly {
// allocate output byte array - this could also be done without assembly
// by using o_code = new bytes(size)
oCode := mload(0x40)
// new "memory end" including padding
mstore(0x40, add(oCode, and(add(add(size, 0x20), 0x1f), not(0x1f))))
// store length in memory
mstore(oCode, size)
// actually retrieve the code, this needs assembly
extcodecopy(_addr, add(oCode, 0x20), _start, size)
}
}
}
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface ITokenDNAStorage {
function getTokenDNA(
uint256 tokenId,
uint256 entropy
) external view returns (bytes16);
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@0xsequence/sstore2/contracts/SSTORE2.sol";
import "./ITokenDNAStorage.sol";
contract TokenDNAStorage is Ownable, ITokenDNAStorage {
event DNAResolved(uint256 indexed fromTokenId, uint256 indexed toTokenId);
// DNA for a token is 16 bytes of data.
uint8 constant TOKEN_DNA_LENGTH = 16;
uint256 constant MAX_DNA_PER_BUCKET = 500; // must be at most 1536
uint256 public MAX_SUPPLY;
struct DNABucket {
address pointer; // An address can at most reference ~24kB (1536 * 16 bytes), which is why we need to split DNA into buckets.
uint256 pieces;
}
DNABucket[] public _DNABuckets;
uint256 public resolved;
mapping(uint16 => uint16) private tokenIdToDNAPosition;
bool public locked;
function lock() external onlyOwner {
locked = true;
}
function getDNABucket(
uint256 idx,
uint256 dnaPosition
)
external
view
onlyOwner
returns (
uint256 total_buckets,
DNABucket memory bucket,
bytes memory dna
)
{
if (!locked) {
total_buckets = _DNABuckets.length;
bucket = _DNABuckets[idx];
dna = SSTORE2.read(bucket.pointer, dnaPosition * TOKEN_DNA_LENGTH);
}
}
// idx = -1 for append.
function setDNABucket(int idx, bytes calldata newDNA) external onlyOwner {
require(!locked, "DNA is finalized."); // If the lock isn't on we can still modify the DNA.
require(idx < int(_DNABuckets.length), "IDX out of bounds");
require(
idx >= 0 ||
_DNABuckets.length == 0 ||
_DNABuckets[_DNABuckets.length - 1].pieces ==
MAX_DNA_PER_BUCKET,
"can't append new bucket after unfilled previous one"
);
uint256 pieces = newDNA.length / 16;
require(pieces <= MAX_DNA_PER_BUCKET, "exceeded bucket width");
require(newDNA.length % 16 == 0, "dna length not multiple of 16");
if (idx < 0) {
_DNABuckets.push(DNABucket(SSTORE2.write(newDNA), pieces));
} else {
delete _DNABuckets[uint(idx)].pointer;
MAX_SUPPLY -= _DNABuckets[uint(idx)].pieces;
_DNABuckets[uint(idx)].pointer = SSTORE2.write(newDNA);
_DNABuckets[uint(idx)].pieces = pieces;
}
MAX_SUPPLY += pieces;
}
function getTokenDNA(
uint256 tokenId,
uint256 entropy
) external view returns (bytes16) {
// This is just a permutation function that works as long as tokenId < 10007 (10007 is the closest prime after 10000).
// note to self: need >= 10007 supplied DNA pieces.
uint256 DNAPosition = (entropy + tokenId * 131) % 10007;
uint256 bucketIdx = DNAPosition / MAX_DNA_PER_BUCKET;
if (bucketIdx >= _DNABuckets.length)
return bytes16(uint128(DNAPosition * entropy));
uint256 DNAIdx = DNAPosition % MAX_DNA_PER_BUCKET;
return
bytes16(
SSTORE2.read(
_DNABuckets[bucketIdx].pointer,
DNAIdx * TOKEN_DNA_LENGTH
)
);
}
}