// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.23;
import "./interfaces/IERC721TokenReceiver.sol";
import "./interfaces/ICastEtherMetadata.sol";
contract CastEtherStatues {
struct Statue {
uint[] structure;
uint mass;
address forger;
address owner;
string name;
}
mapping(bytes32 => bool) uniqueness;
Statue[] statues;
address public owner;
uint melted;
function forged() public view returns(uint){
return statues.length;
}
uint constant BASE_SQUARE_VALUE = 1 ether / 256;
uint constant CREATOR_FEE_PERCENT = 1;
uint constant CREATOR_FEE_MAX = 0.01 ether;
uint creatorEarnings;
event Forge(uint _tokenId, uint[] _structure, address _forger, string _name);
event Melt(uint _tokenId);
function getForgeMass(uint[] memory _structure) public pure returns(uint){
unchecked{
uint totalCost;
for(uint level = 0; level < _structure.length; level++){
uint chunk = _structure[level];
require(chunk > 0,"level_empty");
for(uint i = 0; i < 256; i++){
uint row = i/ 16;
if(chunk%2 != 0){
if(level == 0){
totalCost += BASE_SQUARE_VALUE;
}else{
// totalCost += chunk%2 * BASE_SQUARE_VALUE * (2**(row + (level - 1)*16 ));
totalCost += BASE_SQUARE_VALUE * 2**(( (row + level * 16) - 14)/2) ;
}
}
chunk /= 2;
if(chunk == 0) break;
}
}
return totalCost;
}
}
function forge(uint[] memory _structure, string memory _name) public payable {
unchecked{
bytes32 hash = keccak256(abi.encodePacked(_structure));
require(!uniqueness[hash],"uniqueness");
uniqueness[hash] = true;
uint totalCost = getForgeMass(_structure);
require(totalCost != 0,"empty");
uint fee = totalCost * CREATOR_FEE_PERCENT /100;
if(fee > CREATOR_FEE_MAX){
fee = CREATOR_FEE_MAX;
}
creatorEarnings += fee;
require(msg.value == totalCost + fee,"msg.value");
require(bytes(_name).length <= 32,"name_length");
statues.push(Statue(
_structure,
totalCost,
msg.sender,
msg.sender,
_name
));
_mint(statues.length,msg.sender);
emit Forge(statues.length,_structure,msg.sender,_name);
}
}
function _meltStatue(uint _tokenId) private{
uint mass = statues[_tokenId-1].mass;
address currentOwner = statues[_tokenId-1].owner;
_burn(_tokenId,currentOwner);
payable(currentOwner).transfer(mass);
}
function melt(uint _tokenId) public{
require(msg.sender == ownerOf(_tokenId),"owner");
_meltStatue(_tokenId);
}
function patternUsed(uint[] memory _structure) public view returns(bool){
bytes32 hash = keccak256(abi.encodePacked(_structure));
return uniqueness[hash];
}
function statue(uint _tokenId) public view returns(uint[] memory _structure, uint _mass, address _forger, address _owner, string memory _name){
return (statues[_tokenId-1].structure, statues[_tokenId-1].mass, statues[_tokenId-1].forger, statues[_tokenId-1].owner, statues[_tokenId-1].name);
}
constructor(){
supportsInterface[0x80ac58cd] = true; //ERC721
supportsInterface[0x5b5e139f] = true; //ERC721Metadata
supportsInterface[0x01ffc9a7] = true; //ERC165
owner = msg.sender;
}
//////===721 Standard
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
//////===721 Implementation
mapping(address => uint256) public balanceOf;
mapping (uint256 => address) internal allowance;
mapping (address => mapping (address => bool)) public isApprovedForAll;
// METADATA VARS
string public name = "Forged From Ether";
string public symbol = "STATUE";
address private __metadata;
function _mint(uint _tokenId,address _to) private{
balanceOf[_to]++;
emit Transfer(address(0),_to,_tokenId);
}
function _burn(uint _tokenId,address _owner) private{
melted++;
balanceOf[ _owner ]--;
delete statues[_tokenId - 1].owner;
emit Transfer(_owner,address(0),_tokenId);
}
function isValidToken(uint256 _tokenId) internal view returns(bool){
if(_tokenId == 0 || _tokenId > statues.length) return false;
return statues[_tokenId - 1].owner != address(0);
}
function ownerOf(uint256 _tokenId) public view returns(address){
require(isValidToken(_tokenId),"invalid");
return statues[_tokenId - 1].owner;
}
function approve(address _approved, uint256 _tokenId) external{
address _owner = ownerOf(_tokenId);
require( _owner == msg.sender //Require Sender Owns Token
|| isApprovedForAll[_owner][msg.sender] // or is approved for all.
,"permission");
emit Approval(_owner, _approved, _tokenId);
allowance[_tokenId] = _approved;
}
function getApproved(uint256 _tokenId) external view returns (address) {
require(isValidToken(_tokenId),"invalid");
return allowance[_tokenId];
}
function setApprovalForAll(address _operator, bool _approved) external {
emit ApprovalForAll(msg.sender,_operator, _approved);
isApprovedForAll[msg.sender][_operator] = _approved;
}
function transferFrom(address _from, address _to, uint256 _tokenId) public {
//Check Transferable
//There is a token validity check in ownerOf
address _owner = ownerOf(_tokenId);
require ( _owner == msg.sender //Require sender owns token
//Doing the two below manually instead of referring to the external methods saves gas
|| allowance[_tokenId] == msg.sender //or is approved for this token
|| isApprovedForAll[_owner][msg.sender] //or is approved for all
,"permission");
require(_owner == _from,"owner");
require(_to != address(0),"zero");
emit Transfer(_from, _to, _tokenId);
statues[_tokenId - 1].owner = _to;
balanceOf[_from]--;
balanceOf[_to]++;
//Reset approved if there is one
if(allowance[_tokenId] != address(0)){
delete allowance[_tokenId];
}
}
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) public {
transferFrom(_from, _to, _tokenId);
//Get size of "_to" address, if 0 it's a wallet
uint32 size;
assembly {
size := extcodesize(_to)
}
if(size > 0){
IERC721TokenReceiver receiver = IERC721TokenReceiver(_to);
require(receiver.onERC721Received(msg.sender,_from,_tokenId,data) == bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")),"receiver");
}
}
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
safeTransferFrom(_from,_to,_tokenId,"");
}
function tokenURI(uint256 _tokenId) public view returns (string memory){
require(isValidToken(_tokenId),'invalid');
return ICastEtherMetadata(__metadata).tokenURI(_tokenId);
}
function totalSupply() public view returns (uint256){
return statues.length - melted;
}
///////===165 Implementation
mapping (bytes4 => bool) public supportsInterface;
///==End 165
//Admin
function setOwner(address newOwner) public{
require(msg.sender == owner,"owner");
owner = newOwner;
}
function ownerWithdraw() public{
require(msg.sender == owner,"owner");
uint toWithdraw = creatorEarnings;
creatorEarnings = 0;
payable(msg.sender).transfer(toWithdraw);
}
function setMetadata(address _metadata) public{
require(msg.sender == owner,"owner");
__metadata = _metadata;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.23;
interface ICastEtherMetadata {
function tokenURI(uint256 _tokenId) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.
interface IERC721TokenReceiver {
/// @notice Handle the receipt of an NFT
/// @dev The ERC721 smart contract calls this function on the recipient
/// after a `transfer`. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called `safeTransferFrom` function
/// @param _from The address which previously owned the token
/// @param _tokenId The NFT identifier which is being transferred
/// @param _data Additional data with no specified format
/// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
/// unless throwing
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4);
}
{
"compilationTarget": {
"contracts/CastEtherStatues.sol": "CastEtherStatues"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_operator","type":"address"},{"indexed":false,"internalType":"bool","name":"_approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"_structure","type":"uint256[]"},{"indexed":false,"internalType":"address","name":"_forger","type":"address"},{"indexed":false,"internalType":"string","name":"_name","type":"string"}],"name":"Forge","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"Melt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":true,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"_approved","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_structure","type":"uint256[]"},{"internalType":"string","name":"_name","type":"string"}],"name":"forge","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"forged","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_structure","type":"uint256[]"}],"name":"getForgeMass","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"melt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ownerWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_structure","type":"uint256[]"}],"name":"patternUsed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_metadata","type":"address"}],"name":"setMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"statue","outputs":[{"internalType":"uint256[]","name":"_structure","type":"uint256[]"},{"internalType":"uint256","name":"_mass","type":"uint256"},{"internalType":"address","name":"_forger","type":"address"},{"internalType":"address","name":"_owner","type":"address"},{"internalType":"string","name":"_name","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]