// 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;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { LogosTypes as L } from "./LogosTypes.sol";
interface ILogosTraits {
function charactersByID(uint8) external view returns (L.CharacterInfo memory);
function shapesPrimaryByID(uint8) external view returns (L.ShapeInfo memory);
function shapesSecondaryByID(uint8) external view returns (L.ShapeInfo memory);
function colorPalettesByID(uint16) external view returns (L.ColorPalette memory);
}
interface ERC721 {
function ownerOf(uint256) external view returns (address);
}
contract LogosCrafter is Ownable {
bool public locked;
bool public paused;
ERC721 public logosContract;
ILogosTraits public traitsContract;
struct Hash {
bool taken;
uint16 tokenID;
}
mapping (uint256 => L.Logo) public _logosByTokenID;
mapping (bytes32 => Hash) public _characterHashes;
mapping (bytes32 => Hash) public _shapeHashes;
event TokenUpdated(uint256 tokenID, L.Logo logo, bool isFirstUpdate);
constructor(address logosContractAddress, address traitsContactAddress) Ownable() {
logosContract = ERC721(logosContractAddress);
traitsContract = ILogosTraits(traitsContactAddress);
}
function lock(string memory ack) external onlyOwner {
require(keccak256(abi.encodePacked(ack)) == keccak256(abi.encodePacked("This action is permanent")), "Incorrect ack");
locked = true;
}
function setPaused(bool _paused) external onlyOwner {
paused = _paused;
}
function logosByTokenID(uint256 tokenID) external view returns (L.Logo memory) {
L.Logo memory logo = _logosByTokenID[tokenID];
check(tokenID, logo);
return _logosByTokenID[tokenID];
}
function update(uint256 tokenID, L.Logo memory logo) public {
_update(tokenID, logo, false);
}
function updateAndSkipChecks(uint256 tokenID, L.Logo memory logo) public {
_update(tokenID, logo, true);
}
function check(uint256 tokenID, L.Logo memory logo) public view {
L.CharacterInfo memory character = traitsContract.charactersByID(logo.characterSelections.characterID);
L.ShapeInfo memory primaryShape = traitsContract.shapesPrimaryByID(logo.shapeSelections.primaryShape);
L.ShapeInfo memory secondaryShape = traitsContract.shapesSecondaryByID(logo.shapeSelections.secondaryShape);
L.ColorPalette memory colorPalette = traitsContract.colorPalettesByID(logo.colorPalette);
require(character.enabled, "Character not enabled");
require(primaryShape.enabled, "Primary shape not enabled");
require(primaryShape.numVariants >= logo.shapeSelections.primaryShapeVariant, "Primary shape variant out of range");
require(secondaryShape.enabled, "Secondary shape not enabled");
require(secondaryShape.numVariants >= logo.shapeSelections.secondaryShapeVariant, "Secondary shape variant out of range");
require(colorPalette.enabled, "Color palette not enabled");
require(logo.characterSelections.slotSelections.length == character.slotOffsets.length, "Wrong number of slot selections");
// check that no bonus traits were selected if not a Blitmap base
if (tokenID >= 1700) {
require(!character.bodies[logo.characterSelections.body].isBonus, "Bonus body selected");
require(!character.heads[logo.characterSelections.head].isBonus, "Bonus head selected");
require(!colorPalette.isBonus, "Bonus color palette selected");
}
uint256 slotSelectionLength = logo.characterSelections.slotSelections.length;
for (uint8 i = 0; i < slotSelectionLength; i++) {
// check that slot selection is in range
uint256 lengthOfSlot;
if (i < slotSelectionLength - 1) {
lengthOfSlot = character.slotOffsets[i + 1] - character.slotOffsets[i];
} else {
lengthOfSlot = character.slotOptions.length - character.slotOffsets[i];
}
if (logo.characterSelections.slotSelections[i] >= lengthOfSlot) {
revert("Slot selection out of range");
}
uint8 offset = character.slotOffsets[i] + logo.characterSelections.slotSelections[i];
// Check that the selected trait is not empty
require(bytes(character.slotOptions[offset].name).length > 0, "Empty trait selected");
// check that no bonus traits were selected if not a Blitmap base
if (tokenID >= 1700) {
require(!character.slotOptions[offset].isBonus, "Bonus trait selected");
}
}
}
function _update(uint256 tokenID, L.Logo memory logo, bool skipExternalChecks) internal {
require(!locked, "Locked");
require(!paused, "Paused");
require(logosContract.ownerOf(tokenID) == msg.sender, "Not owner of Logo");
// external checks can be skipped to optimize gas usage, but validity checks (except for collisions) are lost
if (!skipExternalChecks) {
check(tokenID, logo);
}
bytes32 characterHash = keccak256(
abi.encode(
logo.characterSelections.characterID,
logo.characterSelections.slotSelections
)
);
bytes32 shapeHash = keccak256(
abi.encode(
logo.shapeSelections.primaryShape,
logo.shapeSelections.primaryShapeVariant,
logo.shapeSelections.secondaryShape,
logo.shapeSelections.secondaryShapeVariant
)
);
// character/shape hash must be unused or already owned by this token
require(!_characterHashes[characterHash].taken || _characterHashes[characterHash].tokenID == tokenID, "Character combination already used");
require(!_shapeHashes[shapeHash].taken || _shapeHashes[shapeHash].tokenID == tokenID, "Shape combination already used");
// delete old hash if logo was enabled once already
if (_logosByTokenID[tokenID].enabled) {
bytes32 oldCharacterHash = keccak256(
abi.encode(
_logosByTokenID[tokenID].characterSelections.characterID,
_logosByTokenID[tokenID].characterSelections.slotSelections
)
);
bytes32 oldShapeHash = keccak256(
abi.encode(
_logosByTokenID[tokenID].shapeSelections.primaryShape,
_logosByTokenID[tokenID].shapeSelections.primaryShapeVariant,
_logosByTokenID[tokenID].shapeSelections.secondaryShape,
_logosByTokenID[tokenID].shapeSelections.secondaryShapeVariant
)
);
delete _characterHashes[oldCharacterHash];
delete _shapeHashes[oldShapeHash];
}
_characterHashes[characterHash].taken = true;
_characterHashes[characterHash].tokenID = uint16(tokenID);
_shapeHashes[shapeHash].taken = true;
_shapeHashes[shapeHash].tokenID = uint16(tokenID);
logo.enabled = true;
bool isFirstUpdate = !_logosByTokenID[tokenID].enabled;
_logosByTokenID[tokenID] = logo;
emit TokenUpdated(tokenID, logo, isFirstUpdate);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
library LogosTypes {
struct TraitChoice {
string name;
bool isBonus;
}
struct CharacterInfo {
string name;
TraitChoice[] bodies;
TraitChoice[] heads;
string[] slotNames;
uint8[] slotOffsets;
TraitChoice[] slotOptions;
bool enabled;
}
struct ShapeInfo {
string name;
string companyName;
uint8 numVariants; // number of ADDITIONAL variants, not including the base
bool enabled;
}
struct ColorPalette {
string name;
bool isBonus;
string colorA;
string colorB;
bool enabled;
}
struct CharacterSelections {
uint8 characterID;
uint8 body;
uint8 head;
uint8[] slotSelections;
}
struct ShapeSelections {
uint8 primaryShape;
uint8 primaryShapeVariant;
uint8 secondaryShape;
uint8 secondaryShapeVariant;
}
struct Logo {
bool enabled;
CharacterSelections characterSelections;
ShapeSelections shapeSelections;
uint16 colorPalette;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling 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);
}
}
{
"compilationTarget": {
"contracts/LogosCrafter.sol": "LogosCrafter"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 20
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"logosContractAddress","type":"address"},{"internalType":"address","name":"traitsContactAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"tokenID","type":"uint256"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"components":[{"internalType":"uint8","name":"characterID","type":"uint8"},{"internalType":"uint8","name":"body","type":"uint8"},{"internalType":"uint8","name":"head","type":"uint8"},{"internalType":"uint8[]","name":"slotSelections","type":"uint8[]"}],"internalType":"struct LogosTypes.CharacterSelections","name":"characterSelections","type":"tuple"},{"components":[{"internalType":"uint8","name":"primaryShape","type":"uint8"},{"internalType":"uint8","name":"primaryShapeVariant","type":"uint8"},{"internalType":"uint8","name":"secondaryShape","type":"uint8"},{"internalType":"uint8","name":"secondaryShapeVariant","type":"uint8"}],"internalType":"struct LogosTypes.ShapeSelections","name":"shapeSelections","type":"tuple"},{"internalType":"uint16","name":"colorPalette","type":"uint16"}],"indexed":false,"internalType":"struct LogosTypes.Logo","name":"logo","type":"tuple"},{"indexed":false,"internalType":"bool","name":"isFirstUpdate","type":"bool"}],"name":"TokenUpdated","type":"event"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"_characterHashes","outputs":[{"internalType":"bool","name":"taken","type":"bool"},{"internalType":"uint16","name":"tokenID","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"_logosByTokenID","outputs":[{"internalType":"bool","name":"enabled","type":"bool"},{"components":[{"internalType":"uint8","name":"characterID","type":"uint8"},{"internalType":"uint8","name":"body","type":"uint8"},{"internalType":"uint8","name":"head","type":"uint8"},{"internalType":"uint8[]","name":"slotSelections","type":"uint8[]"}],"internalType":"struct LogosTypes.CharacterSelections","name":"characterSelections","type":"tuple"},{"components":[{"internalType":"uint8","name":"primaryShape","type":"uint8"},{"internalType":"uint8","name":"primaryShapeVariant","type":"uint8"},{"internalType":"uint8","name":"secondaryShape","type":"uint8"},{"internalType":"uint8","name":"secondaryShapeVariant","type":"uint8"}],"internalType":"struct LogosTypes.ShapeSelections","name":"shapeSelections","type":"tuple"},{"internalType":"uint16","name":"colorPalette","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"_shapeHashes","outputs":[{"internalType":"bool","name":"taken","type":"bool"},{"internalType":"uint16","name":"tokenID","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenID","type":"uint256"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"components":[{"internalType":"uint8","name":"characterID","type":"uint8"},{"internalType":"uint8","name":"body","type":"uint8"},{"internalType":"uint8","name":"head","type":"uint8"},{"internalType":"uint8[]","name":"slotSelections","type":"uint8[]"}],"internalType":"struct LogosTypes.CharacterSelections","name":"characterSelections","type":"tuple"},{"components":[{"internalType":"uint8","name":"primaryShape","type":"uint8"},{"internalType":"uint8","name":"primaryShapeVariant","type":"uint8"},{"internalType":"uint8","name":"secondaryShape","type":"uint8"},{"internalType":"uint8","name":"secondaryShapeVariant","type":"uint8"}],"internalType":"struct LogosTypes.ShapeSelections","name":"shapeSelections","type":"tuple"},{"internalType":"uint16","name":"colorPalette","type":"uint16"}],"internalType":"struct LogosTypes.Logo","name":"logo","type":"tuple"}],"name":"check","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ack","type":"string"}],"name":"lock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"locked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenID","type":"uint256"}],"name":"logosByTokenID","outputs":[{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"components":[{"internalType":"uint8","name":"characterID","type":"uint8"},{"internalType":"uint8","name":"body","type":"uint8"},{"internalType":"uint8","name":"head","type":"uint8"},{"internalType":"uint8[]","name":"slotSelections","type":"uint8[]"}],"internalType":"struct LogosTypes.CharacterSelections","name":"characterSelections","type":"tuple"},{"components":[{"internalType":"uint8","name":"primaryShape","type":"uint8"},{"internalType":"uint8","name":"primaryShapeVariant","type":"uint8"},{"internalType":"uint8","name":"secondaryShape","type":"uint8"},{"internalType":"uint8","name":"secondaryShapeVariant","type":"uint8"}],"internalType":"struct LogosTypes.ShapeSelections","name":"shapeSelections","type":"tuple"},{"internalType":"uint16","name":"colorPalette","type":"uint16"}],"internalType":"struct LogosTypes.Logo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"logosContract","outputs":[{"internalType":"contract ERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_paused","type":"bool"}],"name":"setPaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"traitsContract","outputs":[{"internalType":"contract ILogosTraits","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenID","type":"uint256"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"components":[{"internalType":"uint8","name":"characterID","type":"uint8"},{"internalType":"uint8","name":"body","type":"uint8"},{"internalType":"uint8","name":"head","type":"uint8"},{"internalType":"uint8[]","name":"slotSelections","type":"uint8[]"}],"internalType":"struct LogosTypes.CharacterSelections","name":"characterSelections","type":"tuple"},{"components":[{"internalType":"uint8","name":"primaryShape","type":"uint8"},{"internalType":"uint8","name":"primaryShapeVariant","type":"uint8"},{"internalType":"uint8","name":"secondaryShape","type":"uint8"},{"internalType":"uint8","name":"secondaryShapeVariant","type":"uint8"}],"internalType":"struct LogosTypes.ShapeSelections","name":"shapeSelections","type":"tuple"},{"internalType":"uint16","name":"colorPalette","type":"uint16"}],"internalType":"struct LogosTypes.Logo","name":"logo","type":"tuple"}],"name":"update","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenID","type":"uint256"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"components":[{"internalType":"uint8","name":"characterID","type":"uint8"},{"internalType":"uint8","name":"body","type":"uint8"},{"internalType":"uint8","name":"head","type":"uint8"},{"internalType":"uint8[]","name":"slotSelections","type":"uint8[]"}],"internalType":"struct LogosTypes.CharacterSelections","name":"characterSelections","type":"tuple"},{"components":[{"internalType":"uint8","name":"primaryShape","type":"uint8"},{"internalType":"uint8","name":"primaryShapeVariant","type":"uint8"},{"internalType":"uint8","name":"secondaryShape","type":"uint8"},{"internalType":"uint8","name":"secondaryShapeVariant","type":"uint8"}],"internalType":"struct LogosTypes.ShapeSelections","name":"shapeSelections","type":"tuple"},{"internalType":"uint16","name":"colorPalette","type":"uint16"}],"internalType":"struct LogosTypes.Logo","name":"logo","type":"tuple"}],"name":"updateAndSkipChecks","outputs":[],"stateMutability":"nonpayable","type":"function"}]