Created
June 26, 2020 16:51
-
-
Save skyfly200/92de0bcb214e6e790ef0aabf39fbb77d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//SPDX-License-Identifier: Unlicensed | |
pragma solidity ^0.6.4; | |
pragma experimental ABIEncoderV2; | |
// needed for upgradability | |
//import "@openzeppelin/upgrades/contracts/Initializable.sol"; | |
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | |
import "@openzeppelin/contracts/utils/Strings.sol"; | |
//import "@openzeppelin/contracts/math/SafeMath.sol"; | |
library Buffer { | |
function hasCapacityFor(bytes memory buffer, uint256 needed) | |
internal | |
pure | |
returns (bool) | |
{ | |
uint256 size; | |
uint256 used; | |
assembly { | |
size := mload(buffer) | |
used := mload(add(buffer, 32)) | |
} | |
return size >= 32 && used <= size - 32 && used + needed <= size - 32; | |
} | |
function toString(bytes memory buffer) | |
internal | |
pure | |
returns (string memory) | |
{ | |
require(hasCapacityFor(buffer, 0), "Buffer.toString: invalid buffer"); | |
string memory ret; | |
assembly { | |
ret := add(buffer, 32) | |
} | |
return ret; | |
} | |
function append(bytes memory buffer, string memory str) internal view { | |
require( | |
hasCapacityFor(buffer, bytes(str).length), | |
"Buffer.append: no capacity" | |
); | |
assembly { | |
let len := mload(add(buffer, 32)) | |
pop( | |
staticcall( | |
gas(), | |
0x4, | |
add(str, 32), | |
mload(str), | |
add(len, add(buffer, 64)), | |
mload(str) | |
) | |
) | |
mstore(add(buffer, 32), add(len, mload(str))) | |
} | |
} | |
function rect( | |
bytes memory buffer, | |
int256[2] memory positions, | |
uint256[2] memory size, | |
uint256 rgb | |
) internal pure { | |
require(hasCapacityFor(buffer, 102), "Buffer.rect: no capacity"); | |
int256 xpos = positions[0]; | |
int256 ypos = positions[1]; | |
uint256 width = size[0]; | |
uint256 height = size[1]; | |
assembly { | |
function numbx1(x, v) -> y { | |
// v must be in the closed interval [0, 9] | |
// otherwise it outputs junk | |
mstore8(x, add(v, 48)) | |
y := add(x, 1) | |
} | |
function numbx2(x, v) -> y { | |
// v must be in the closed interval [0, 99] | |
// otherwise it outputs junk | |
y := numbx1(numbx1(x, div(v, 10)), mod(v, 10)) | |
} | |
function numbu3(x, v) -> y { | |
// v must be in the closed interval [0, 999] | |
// otherwise only the last 3 digits will be converted | |
switch lt(v, 100) | |
case 0 { | |
// without input value sanitation: y := numbx2(numbx1(x, div(v, 100)), mod(v, 100)) | |
y := numbx2( | |
numbx1(x, mod(div(v, 100), 10)), | |
mod(v, 100) | |
) | |
} | |
default { | |
switch lt(v, 10) | |
case 0 { | |
y := numbx2(x, v) | |
} | |
default { | |
y := numbx1(x, v) | |
} | |
} | |
} | |
function numbi3(x, v) -> y { | |
// v must be in the closed interval [-999, 999] | |
// otherwise only the last 3 digits will be converted | |
if slt(v, 0) { | |
v := add(not(v), 1) | |
mstore8(x, 45) // minus sign | |
x := add(x, 1) | |
} | |
y := numbu3(x, v) | |
} | |
function hexrgb(x, v) -> y { | |
let blo := and(v, 0xf) | |
let bhi := and(shr(4, v), 0xf) | |
let glo := and(shr(8, v), 0xf) | |
let ghi := and(shr(12, v), 0xf) | |
let rlo := and(shr(16, v), 0xf) | |
let rhi := and(shr(20, v), 0xf) | |
mstore8(x, add(add(rhi, mul(div(rhi, 10), 39)), 48)) | |
mstore8(add(x, 1), add(add(rlo, mul(div(rlo, 10), 39)), 48)) | |
mstore8(add(x, 2), add(add(ghi, mul(div(ghi, 10), 39)), 48)) | |
mstore8(add(x, 3), add(add(glo, mul(div(glo, 10), 39)), 48)) | |
mstore8(add(x, 4), add(add(bhi, mul(div(bhi, 10), 39)), 48)) | |
mstore8(add(x, 5), add(add(blo, mul(div(blo, 10), 39)), 48)) | |
y := add(x, 6) | |
} | |
function append(x, str, len) -> y { | |
mstore(x, str) | |
y := add(x, len) | |
} | |
let strIdx := add(mload(add(buffer, 32)), add(buffer, 64)) | |
strIdx := append(strIdx, '<rect x="', 9) | |
strIdx := numbi3(strIdx, xpos) | |
strIdx := append(strIdx, '" y="', 5) | |
strIdx := numbi3(strIdx, ypos) | |
strIdx := append(strIdx, '" width="', 9) | |
strIdx := numbu3(strIdx, width) | |
strIdx := append(strIdx, '" height="', 10) | |
strIdx := numbu3(strIdx, height) | |
strIdx := append(strIdx, '" style="fill:#', 15) | |
strIdx := hexrgb(strIdx, rgb) | |
strIdx := append(strIdx, '; fill-opacity:1.0;"/>\n', 23) | |
mstore(add(buffer, 32), sub(sub(strIdx, buffer), 64)) | |
} | |
} | |
} | |
library Random { | |
/** | |
* Initialize the pool with the entropy of the blockhashes of the blocks in the closed interval [earliestBlock, latestBlock] | |
* The argument "seed" is optional and can be left zero in most cases. | |
* This extra seed allows you to select a different sequence of random numbers for the same block range. | |
*/ | |
function init( | |
uint256 earliestBlock, | |
uint256 latestBlock, | |
uint256 seed | |
) internal view returns (bytes32[] memory) { | |
//require(block.number-1 >= latestBlock && latestBlock >= earliestBlock && earliestBlock >= block.number-256, "Random.init: invalid block interval"); | |
require( | |
block.number - 1 >= latestBlock && latestBlock >= earliestBlock, | |
"Random.init: invalid block interval" | |
); | |
bytes32[] memory pool = new bytes32[](latestBlock - earliestBlock + 2); | |
bytes32 salt = keccak256(abi.encodePacked(earliestBlock, seed)); | |
for (uint256 i = 0; i <= latestBlock - earliestBlock; i++) { | |
// Add some salt to each blockhash so that we don't reuse those hash chains | |
// when this function gets called again in another block. | |
pool[i + 1] = keccak256( | |
abi.encodePacked(blockhash(earliestBlock + i), salt) | |
); | |
} | |
return pool; | |
} | |
/** | |
* Initialize the pool from the latest "num" blocks. | |
*/ | |
function initLatest(uint256 num, uint256 seed) | |
internal | |
view | |
returns (bytes32[] memory) | |
{ | |
return init(block.number - num, block.number - 1, seed); | |
} | |
/** | |
* Advances to the next 256-bit random number in the pool of hash chains. | |
*/ | |
function next(bytes32[] memory pool) internal pure returns (uint256) { | |
require(pool.length > 1, "Random.next: invalid pool"); | |
uint256 roundRobinIdx = (uint256(pool[0]) % (pool.length - 1)) + 1; | |
bytes32 hash = keccak256(abi.encodePacked(pool[roundRobinIdx])); | |
pool[0] = bytes32(uint256(pool[0]) + 1); | |
pool[roundRobinIdx] = hash; | |
return uint256(hash); | |
} | |
function stringToUint(string memory s) | |
internal | |
pure | |
returns (uint256 result) | |
{ | |
bytes memory b = bytes(s); | |
uint256 i; | |
result = 0; | |
for (i = 0; i < b.length; i++) { | |
uint256 c = uint256(uint8(b[i])); | |
if (c >= 48 && c <= 57) { | |
result = result * 10 + (c - 48); | |
} | |
} | |
} | |
/** | |
* Produces random integer values, uniformly distributed on the closed interval [a, b] | |
*/ | |
function uniform( | |
bytes32[] memory pool, | |
int256 a, | |
int256 b | |
) internal pure returns (int256) { | |
require(a <= b, "Random.uniform: invalid interval"); | |
return int256(next(pool) % uint256(b - a + 1)) + a; | |
} | |
} | |
contract TinyBoxes is ERC721 { | |
//using SafeMath for uint256; | |
uint256 public constant TOKEN_LIMIT = 1024; | |
uint256 public constant ARTIST_PRINTS = 0; | |
int256 public constant ANIMATION_COUNT = 1; | |
address public creator; | |
string header = '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 2600 2600" style="stroke-width:0; background-color:#121212;">\n\n<symbol id="upperleftquad4">\n<symbol id="upperleftquad3">\n<symbol id="upperleftquad2">\n<symbol id="upperleftquad">\n\n'; | |
address payable artmuseum = 0x027Fb48bC4e3999DCF88690aEbEBCC3D1748A0Eb; //lolz | |
struct TinyBox { | |
uint256 seed; | |
uint256 animation; | |
uint256 shapes; | |
uint256 colors; | |
uint256 hatching; | |
uint256 scale; | |
uint256[2] widthRange; | |
uint256[2] heightRange; | |
uint256[2] segments; | |
uint256[2] spacing; | |
int256[3] mirrorPositions; | |
bool[3] mirrors; | |
} | |
mapping(uint256 => TinyBox) internal boxes; | |
/** | |
* @dev Contract constructor. | |
*/ | |
constructor() public ERC721("TinyBoxes", "[#][#]") { | |
creator = msg.sender; | |
} | |
/** | |
* @dev generate a color | |
* @param pool randomn numbers | |
* @param _id of token to render | |
* @return color value | |
*/ | |
function _generateColor(bytes32[] memory pool, uint256 _id) | |
internal | |
view | |
returns (uint256) | |
{ | |
uint256 red = uint256(Random.uniform(pool, 0x000012, 0x0000ff)); | |
uint256 green = uint256(Random.uniform(pool, 0x000012, 0x0000ff) * 256); | |
uint256 blue = uint256( | |
Random.uniform(pool, 0x000012, 0x0000ff) * 65536 | |
); | |
uint256 colorscheme = uint256(Random.uniform(pool, 0, 99)); | |
if (_id == 0) { | |
return 0x000000; // all black | |
} else if (_id > 73 && _id < 80) { | |
return uint256(0xffffff); // all white | |
} else if (colorscheme < 7) { | |
return blue; | |
} else if (colorscheme < 14) { | |
return green; | |
} else if (colorscheme < 21) { | |
return red; | |
} else if (colorscheme < 35) { | |
return green + blue; | |
} else if (colorscheme < 49) { | |
return red + blue; | |
} else if (colorscheme < 63) { | |
return red + green; | |
} else if (colorscheme < 66) { | |
uint256 brightness = uint256( | |
Random.uniform(pool, 0x000022, 0x0000ee) | |
); // random greys | |
return (brightness * 65536) + (brightness * 256) + brightness; | |
} else { | |
return blue; | |
} | |
} | |
/** | |
* @dev generate a shape | |
* @param pool randomn numbers | |
* @param spacing for segments | |
* @param segments of shape positions | |
* @param widthRange for shapes | |
* @param heightRange for shapes | |
* @param hatch mode on | |
* @return positions of shape | |
*/ | |
function _generateShape( | |
bytes32[] memory pool, | |
uint256[2] memory spacing, | |
uint256[2] memory segments, | |
uint256[2] memory widthRange, | |
uint256[2] memory heightRange, | |
bool hatch | |
) | |
internal | |
view | |
returns (int256[2] memory positions, uint256[2] memory size) | |
{ | |
positions = [ | |
Random.uniform(pool, -(int256(spacing[0])), int256(spacing[0])) + | |
((Random.uniform(pool, 0, int256(segments[0]) - 1) * 800) / | |
int256(segments[0])), | |
Random.uniform(pool, -(int256(spacing[1])), int256(spacing[1])) + | |
((Random.uniform(pool, 0, int256(segments[1]) - 1) * 800) / | |
int256(segments[1])) | |
]; | |
if (hatch) { | |
uint256 horizontal = uint256(Random.uniform(pool, 0, 1)); | |
// size[0] = uint(Random.uniform(pool, dials[4], dials[5])) + horizontal * uint(dials[6]); | |
// size[1] = uint(dials[6]) + uint(dials[5]) - size[0] + uint256(Random.uniform(pool, dials[7], dials[4])); | |
uint256 width = uint256(Random.uniform(pool, 25, 40)) + | |
uint256(700 * horizontal); | |
size = [ | |
width, | |
uint256(Random.uniform(pool, 10, 25)) + uint256(740 - width) | |
]; | |
} else | |
size = [ | |
uint256( | |
Random.uniform( | |
pool, | |
int256(widthRange[0]), | |
int256(widthRange[1]) | |
) | |
), | |
uint256( | |
Random.uniform( | |
pool, | |
int256(heightRange[0]), | |
int256(heightRange[1]) | |
) | |
) | |
]; | |
} | |
/** | |
* @dev render the footer string for mirring effects | |
* @param switches for each mirroring stage | |
* @param mirrorPositions for generator settings | |
* @param scale for each mirroring stage | |
* @return footer string | |
*/ | |
function _generateFooter( | |
bool[3] memory switches, | |
int256[3] memory mirrorPositions, | |
uint256 scale | |
) internal view returns (string memory footer) { | |
bytes memory buffer = new bytes(8192); | |
string[3] memory scales = ["-1 1", "-1 -1", "1 -1"]; | |
string[7] memory template = [ | |
"\n<g>", | |
'\n<g transform="scale(', | |
") translate(", | |
')">', | |
'\n<use xlink:href="#upperleftquad', | |
'"/>\n</g>', | |
"\n</symbol>" | |
]; | |
for (uint8 s = 0; s < 3; s++) { | |
// loop through mirroring effects | |
Buffer.append(buffer, template[6]); | |
if (!switches[s]) { | |
// turn off this level of mirroring | |
// add a scale transform | |
Buffer.append(buffer, template[0]); | |
// denote what quad the transform should be used for | |
Buffer.append(buffer, template[4]); | |
if (s > 0) | |
Buffer.append(buffer, Strings.toString(uint256(s + 1))); | |
Buffer.append(buffer, template[5]); | |
} else { | |
string memory value = Strings.toString( | |
uint256(mirrorPositions[s]) | |
); | |
for (uint8 i = 0; i < 4; i++) { | |
// loop through transforms | |
if (i == 0) Buffer.append(buffer, template[0]); | |
else { | |
Buffer.append(buffer, template[1]); | |
Buffer.append(buffer, scales[i - 1]); | |
Buffer.append(buffer, template[2]); | |
if (i <= 2) Buffer.append(buffer, "-"); | |
Buffer.append(buffer, i <= 2 ? value : "0"); | |
Buffer.append(buffer, " "); | |
if (i >= 2) Buffer.append(buffer, "-"); | |
Buffer.append(buffer, i >= 2 ? value : "0"); | |
Buffer.append(buffer, template[3]); | |
} | |
// denote what quad the transformsshould be used for | |
Buffer.append(buffer, template[4]); | |
if (s > 0) | |
Buffer.append(buffer, Strings.toString(uint256(s + 1))); | |
Buffer.append(buffer, template[5]); | |
} | |
} | |
} | |
// add final scaling | |
string memory scaleWhole = Strings.toString(scale.div(100)); | |
string memory scaleDecimals = Strings.toString(scale.mod(100)); | |
Buffer.append(buffer, template[6]); | |
Buffer.append(buffer, template[1]); | |
Buffer.append(buffer, scaleWhole); | |
Buffer.append(buffer, "."); | |
Buffer.append(buffer, scaleDecimals); | |
Buffer.append(buffer, " "); | |
Buffer.append(buffer, scaleWhole); | |
Buffer.append(buffer, "."); | |
Buffer.append(buffer, scaleDecimals); | |
Buffer.append(buffer, template[3]); | |
Buffer.append(buffer, template[4]); | |
Buffer.append(buffer, "4"); | |
Buffer.append(buffer, template[5]); | |
Buffer.append(buffer, "\n</svg>"); // add closing svg tag | |
return Buffer.toString(buffer); | |
} | |
/** | |
* @dev render a token's art | |
* @param _id of token | |
* @param box TinyBox data structure | |
* @param frame number to render | |
* @return the SVG graphiccs of the token | |
*/ | |
function perpetualRenderer( | |
uint256 _id, | |
TinyBox memory box, | |
uint256 frame | |
) public view returns (string memory) { | |
bytes memory buffer = new bytes(8192); | |
Buffer.append(buffer, header); | |
// initilized RNG with the seed and blocks 0 through 1 | |
bytes32[] memory pool = Random.init(0, 1, box.seed); | |
// generate colors | |
uint256[] memory colorValues = new uint256[](box.colors); | |
for (uint256 i = 0; i < box.colors; i++) | |
colorValues[i] = _generateColor(pool, _id); | |
// generate shapes | |
for (uint256 i = 0; i < box.shapes; i++) { | |
uint256 colorRand = uint256( | |
Random.uniform(pool, 0, int256(box.shapes.sub(1))) | |
); | |
bool hatched = (box.hatching > 0 && i.mod(box.hatching) == 0); // hatching mod. 1 in hybrid shapes will be hatching type | |
( | |
int256[2] memory positions, | |
uint256[2] memory size | |
) = _generateShape( | |
pool, | |
box.spacing, | |
box.segments, | |
box.widthRange, | |
box.heightRange, | |
hatched | |
); | |
Buffer.rect(buffer, positions, size, colorValues[colorRand]); | |
} | |
// generate the footer with mirroring and scale transforms | |
Buffer.append( | |
buffer, | |
_generateFooter(box.mirrors, box.mirrorPositions, box.scale) | |
); | |
return Buffer.toString(buffer); | |
} | |
/** | |
* @dev Create a new TinyBox Token | |
* @param _seed of token | |
* @param shapes of token | |
* @param colors of token | |
* @param hatching of token | |
* @param widthRange of token | |
* @param heightRange of token | |
* @param segments of token | |
* @param spacing of token | |
* @param mirrorPositions of token | |
* @param mirrors of token | |
* @param scale of token | |
*/ | |
function createBox( | |
string calldata _seed, | |
uint256 shapes, | |
uint256 colors, | |
uint256 hatching, | |
uint256[2] calldata widthRange, | |
uint256[2] calldata heightRange, | |
uint256[2] calldata segments, | |
uint256[2] calldata spacing, | |
int256[3] calldata mirrorPositions, | |
bool[3] calldata mirrors, | |
uint256 scale | |
) external payable { | |
require( | |
msg.sender != address(0), | |
"token recipient man not be the zero address" | |
); | |
require( | |
totalSupply() < TOKEN_LIMIT, | |
"ART SALE IS OVER. Tinyboxes are now only available on the secondary market." | |
); | |
if (totalSupply() < ARTIST_PRINTS) { | |
require( | |
msg.sender == address(creator), | |
"Only the creator can mint the alpha token. Wait your turn FFS" | |
); | |
} else { | |
uint256 amount = currentPrice(); | |
require(msg.value >= amount, "insuficient payment"); // return if they dont pay enough | |
if (msg.value > amount) msg.sender.transfer(msg.value - amount); // give change if they over pay | |
artmuseum.transfer(amount); // send the payment amount to the artmuseum account | |
} | |
// convert seed from string to uint | |
uint256 seed = Random.stringToUint(_seed); | |
// initilized RNG with the seed and blocks 0 through 1 | |
bytes32[] memory pool = Random.init(0, 1, seed); | |
uint256 id = totalSupply(); | |
// TODO - generate animation with RNG weighted non uniformly for varying rarity | |
// maybe use log base 2 of a number in a range 2 to the animation counts | |
boxes[id] = TinyBox({ | |
seed: seed, | |
animation: uint256(Random.uniform(pool, 0, ANIMATION_COUNT - 1)), | |
shapes: shapes, | |
colors: colors, | |
hatching: hatching, | |
scale: scale, | |
widthRange: widthRange, | |
heightRange: heightRange, | |
segments: segments, | |
spacing: spacing, | |
mirrorPositions: mirrorPositions, | |
mirrors: mirrors | |
}); | |
_safeMint(msg.sender, id); | |
} | |
/** | |
* @dev Get the current price of a token | |
* @return price in wei of a token currently | |
*/ | |
function currentPrice() public view returns (uint256 price) { | |
price = priceAt(totalSupply()); | |
} | |
/** | |
* @dev Get the price of a specific token id | |
* @param _id of the token | |
* @return price in wei of that token | |
*/ | |
function priceAt(uint256 _id) public view returns (uint256 price) { | |
uint256 tokeninflation = (_id / 2) * 1000000000000000; // add .001 eth inflation per token | |
price = tokeninflation + 160000000000000000; // in wei, starting price .16 eth, ending price .2 eth | |
} | |
/** | |
* @dev Lookup the seed | |
* @param _id for which we want the seed | |
* @return seed value of _id. | |
*/ | |
function tokenSeed(uint256 _id) external view returns (uint256) { | |
return boxes[_id].seed; | |
} | |
/** | |
* @dev Lookup the animation | |
* @param _id for which we want the animation | |
* @return animation value of _id. | |
*/ | |
function tokenAnimation(uint256 _id) external view returns (uint256) { | |
return boxes[_id].animation; | |
} | |
/** | |
* @dev Lookup all token data in one call | |
* @param _id for which we want token data | |
* @return seed of token | |
* @return animation of token | |
* @return colors of token | |
* @return shapes of token | |
* @return hatching of token | |
* @return widthRange of token | |
* @return heightRange of token | |
* @return segments of token | |
* @return spacing of token | |
* @return mirrorPositions of token | |
* @return mirrors of token | |
* @return scale of token | |
*/ | |
function tokenData(uint256 _id) | |
external | |
view | |
returns ( | |
uint256 seed, | |
uint256 animation, | |
uint256 colors, | |
uint256 shapes, | |
uint256 hatching, | |
uint256[2] memory widthRange, | |
uint256[2] memory heightRange, | |
uint256[2] memory segments, | |
uint256[2] memory spacing, | |
int256[3] memory mirrorPositions, | |
bool[3] memory mirrors, | |
uint256 scale | |
) | |
{ | |
TinyBox memory box = boxes[_id]; | |
seed = box.seed; | |
animation = box.animation; | |
colors = box.colors; | |
shapes = box.shapes; | |
hatching = box.hatching; | |
widthRange = box.widthRange; | |
heightRange = box.heightRange; | |
segments = box.segments; | |
spacing = box.spacing; | |
mirrorPositions = box.mirrorPositions; | |
mirrors = box.mirrors; | |
scale = box.scale; | |
} | |
/** | |
* @dev Generate the token SVG art of a specified frame | |
* @param _id for which we want art | |
* @param _frame for which we want art | |
* @return animated SVG art of token _id at _frame. | |
*/ | |
function tokenFrame(uint256 _id, uint256 _frame) | |
public | |
view | |
returns (string memory) | |
{ | |
TinyBox memory box = boxes[_id]; | |
return perpetualRenderer(_id, box, _frame); | |
} | |
/** | |
* @dev Generate the static token SVG art | |
* @param _id for which we want art | |
* @return URI of _id. | |
*/ | |
function tokenArt(uint256 _id) external view returns (string memory) { | |
// render frame 0 of the token animation | |
return tokenFrame(_id, 0); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment