Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
TokenTracker
Latest 6 from a total of 6 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Solve | 16769974 | 536 days ago | IN | 0.02 ETH | 0.00652086 | ||||
Solve | 16737948 | 540 days ago | IN | 0 ETH | 0.00349266 | ||||
Solve | 16737572 | 540 days ago | IN | 0 ETH | 0.00472112 | ||||
Add Puzzle | 16737179 | 540 days ago | IN | 0 ETH | 0.003581 | ||||
Transfer Ownersh... | 16736796 | 540 days ago | IN | 0 ETH | 0.00105894 | ||||
0x60c06040 | 16736795 | 540 days ago | IN | 0 ETH | 0.09090837 |
Loading...
Loading
Contract Name:
Curta
Compiler Version
v0.8.17+commit.8df45f5f
Optimization Enabled:
Yes with 875 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; // .===========================================================================. // | The Curta is a hand-held mechanical calculator designed by Curt | // | Herzstark. It is known for its extremely compact design: a small cylinder | // | that fits in the palm of the hand. | // |---------------------------------------------------------------------------| // | The nines' complement math breakthrough eliminated the significant | // | mechanical complexity created when ``borrowing'' during subtraction. This | // | drum was the key to miniaturizing the Curta. | // '===========================================================================' import { Owned } from "solmate/auth/Owned.sol"; import { LibString } from "solmate/utils/LibString.sol"; import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol"; import { FlagRenderer } from "./FlagRenderer.sol"; import { FlagsERC721 } from "./FlagsERC721.sol"; import { AuthorshipToken } from "@/contracts/AuthorshipToken.sol"; import { ICurta } from "@/contracts/interfaces/ICurta.sol"; import { IPuzzle } from "@/contracts/interfaces/IPuzzle.sol"; import { Base64 } from "@/contracts/utils/Base64.sol"; /// @title Curta /// @author fiveoutofnine /// @notice A CTF protocol, where players create and solve EVM puzzles to earn /// NFTs (``Flag''). contract Curta is ICurta, FlagsERC721, Owned { using LibString for uint256; // ------------------------------------------------------------------------- // Constants // ------------------------------------------------------------------------- /// @notice The length of Phase 1 in seconds. uint256 constant PHASE_ONE_LENGTH = 2 days; /// @notice The length of Phase 1 and Phase 2 combined (i.e. the solving /// period) in seconds. uint256 constant SUBMISSION_LENGTH = 5 days; /// @notice The minimum fee required to submit a solution during Phase 2. /// @dev This fee is transferred to the author of the relevant puzzle. Any /// excess fees will also be transferred to the author. Note that the author /// will receive at least 0.01 ether per Phase 2 solve. uint256 constant PHASE_TWO_MINIMUM_FEE = 0.02 ether; /// @notice The protocol fee required to submit a solution during Phase 2. /// @dev This fee is transferred to the address returned by `owner`. uint256 constant PHASE_TWO_PROTOCOL_FEE = 0.01 ether; /// @notice The default Flag colors. uint120 constant DEFAULT_FLAG_COLORS = 0x181E28181E2827303DF0F6FC94A3B3; // ------------------------------------------------------------------------- // Immutable Storage // ------------------------------------------------------------------------- /// @inheritdoc ICurta AuthorshipToken public immutable override authorshipToken; /// @inheritdoc ICurta FlagRenderer public immutable override flagRenderer; // ------------------------------------------------------------------------- // Storage // ------------------------------------------------------------------------- /// @inheritdoc ICurta uint32 public override puzzleId = 0; /// @inheritdoc ICurta Fermat public override fermat; /// @inheritdoc ICurta mapping(uint32 => PuzzleColorsAndSolves) public override getPuzzleColorsAndSolves; /// @inheritdoc ICurta mapping(uint32 => PuzzleData) public override getPuzzle; /// @inheritdoc ICurta mapping(uint32 => address) public override getPuzzleAuthor; /// @inheritdoc ICurta mapping(address => mapping(uint32 => bool)) public override hasSolvedPuzzle; /// @inheritdoc ICurta mapping(uint256 => bool) public override hasUsedAuthorshipToken; // ------------------------------------------------------------------------- // Constructor + Functions // ------------------------------------------------------------------------- /// @param _authorshipToken The address of the Authorship Token contract. /// @param _flagRenderer The address of the Flag metadata and art renderer /// contract. constructor(AuthorshipToken _authorshipToken, FlagRenderer _flagRenderer) FlagsERC721("Curta", "CTF") Owned(msg.sender) { authorshipToken = _authorshipToken; flagRenderer = _flagRenderer; } /// @inheritdoc ICurta function solve(uint32 _puzzleId, uint256 _solution) external payable { // Revert if `msg.sender` has already solved the puzzle. if (hasSolvedPuzzle[msg.sender][_puzzleId]) { revert PuzzleAlreadySolved(_puzzleId); } PuzzleData memory puzzleData = getPuzzle[_puzzleId]; IPuzzle puzzle = puzzleData.puzzle; // Revert if the puzzle does not exist. if (address(puzzle) == address(0)) revert PuzzleDoesNotExist(_puzzleId); // Revert if submissions are closed. uint40 firstSolveTimestamp = puzzleData.firstSolveTimestamp; uint40 solveTimestamp = uint40(block.timestamp); uint8 phase = _computePhase(firstSolveTimestamp, solveTimestamp); if (phase == 3) revert SubmissionClosed(_puzzleId); // Revert if the solution is incorrect. if (!puzzle.verify(puzzle.generate(msg.sender), _solution)) { revert IncorrectSolution(); } // Update the puzzle's first solve timestamp if it was previously unset. if (firstSolveTimestamp == 0) { getPuzzle[_puzzleId].firstSolveTimestamp = solveTimestamp; ++getPuzzleColorsAndSolves[_puzzleId].phase0Solves; // Give first solver an Authorship Token authorshipToken.curtaMint(msg.sender); } // Mark the puzzle as solved. hasSolvedPuzzle[msg.sender][_puzzleId] = true; uint256 ethRemaining = msg.value; unchecked { // Mint NFT. _mint({ _to: msg.sender, _id: (uint256(_puzzleId) << 128) | getPuzzleColorsAndSolves[_puzzleId].solves++, _solveMetadata: uint56(((uint160(msg.sender) >> 132) << 28) | (_solution & 0xFFFFFFF)), _phase: phase }); if (phase == 1) { ++getPuzzleColorsAndSolves[_puzzleId].phase1Solves; } else if (phase == 2) { // Revert if the puzzle is in Phase 2, and insufficient funds // were sent. if (ethRemaining < PHASE_TWO_MINIMUM_FEE) revert InsufficientFunds(); ++getPuzzleColorsAndSolves[_puzzleId].phase2Solves; // Transfer protocol fee to `owner`. SafeTransferLib.safeTransferETH(owner, PHASE_TWO_PROTOCOL_FEE); // Subtract protocol fee from total value. ethRemaining -= PHASE_TWO_PROTOCOL_FEE; } } // Transfer untransferred funds to the puzzle author. Refunds are not // checked, in case someone wants to ``tip'' the author. SafeTransferLib.safeTransferETH(getPuzzleAuthor[_puzzleId], ethRemaining); // Emit event emit SolvePuzzle({ id: _puzzleId, solver: msg.sender, solution: _solution, phase: phase }); } /// @inheritdoc ICurta function addPuzzle(IPuzzle _puzzle, uint256 _tokenId) external { // Revert if the Authorship Token doesn't belong to sender. if (msg.sender != authorshipToken.ownerOf(_tokenId)) revert Unauthorized(); // Revert if the puzzle has already been used. if (hasUsedAuthorshipToken[_tokenId]) revert AuthorshipTokenAlreadyUsed(_tokenId); // Mark token as used. hasUsedAuthorshipToken[_tokenId] = true; unchecked { uint32 curPuzzleId = ++puzzleId; // Add puzzle. getPuzzle[curPuzzleId] = PuzzleData({ puzzle: _puzzle, addedTimestamp: uint40(block.timestamp), firstSolveTimestamp: 0 }); // Add puzzle author. getPuzzleAuthor[curPuzzleId] = msg.sender; // Add puzzle Flag colors with default colors. getPuzzleColorsAndSolves[curPuzzleId].colors = DEFAULT_FLAG_COLORS; // Emit events. emit AddPuzzle(curPuzzleId, msg.sender, _puzzle); } } /// @inheritdoc ICurta function setPuzzleColors(uint32 _puzzleId, uint120 _colors) external { // Revert if `msg.sender` is not the author of the puzzle. if (getPuzzleAuthor[_puzzleId] != msg.sender) revert Unauthorized(); // Set puzzle colors. getPuzzleColorsAndSolves[_puzzleId].colors = _colors; // Emit events. emit UpdatePuzzleColors(_puzzleId, _colors); } /// @inheritdoc ICurta function setFermat(uint32 _puzzleId) external { // Revert if the puzzle has never been solved. PuzzleData memory puzzleData = getPuzzle[_puzzleId]; if (puzzleData.firstSolveTimestamp == 0) revert PuzzleNotSolved(_puzzleId); // Revert if the puzzle is already Fermat. if (fermat.puzzleId == _puzzleId) revert PuzzleAlreadyFermat(_puzzleId); unchecked { uint40 timeTaken = puzzleData.firstSolveTimestamp - puzzleData.addedTimestamp; // Revert if the puzzle is not Fermat. if (timeTaken < fermat.timeTaken) revert PuzzleNotFermat(_puzzleId); // Set Fermat. fermat.puzzleId = _puzzleId; fermat.timeTaken = timeTaken; } // Transfer Fermat to puzzle author. address puzzleAuthor = getPuzzleAuthor[_puzzleId]; address currentOwner = getTokenData[0].owner; unchecked { // Delete ownership information about Fermat, if the owner is not // `address(0)`. if (currentOwner != address(0)) { getUserBalances[currentOwner].balance--; delete getApproved[0]; // Emit burn event. emit Transfer(currentOwner, address(0), 0); } // Increment new Fermat author's balance. getUserBalances[puzzleAuthor].balance++; } // Set new Fermat owner. getTokenData[0].owner = puzzleAuthor; // Emit mint event. emit Transfer(address(0), puzzleAuthor, 0); } // ------------------------------------------------------------------------- // ERC721Metadata // ------------------------------------------------------------------------- /// @inheritdoc FlagsERC721 function tokenURI(uint256 _tokenId) external view override returns (string memory) { TokenData memory tokenData = getTokenData[_tokenId]; require(tokenData.owner != address(0), "NOT_MINTED"); // Puzzle is Fermat. if (_tokenId == 0) { return "data:application/json;base64,eyJuYW1lIjoiRmVybWF0IiwiZGVzY3JpcHRpb24iOiJMb25nZX" "N0IHVuc29sdmVkIHB1enpsZS4iLCJpbWFnZV9kYXRhIjoiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4y" "WnlCM2FXUjBhRDBpTlRVd0lpQm9aV2xuYUhROUlqVTFNQ0lnZG1sbGQwSnZlRDBpTUNBd0lEVTFNQ0ExTlRBaU" "lHWnBiR3c5SW01dmJtVWlJSGh0Ykc1elBTSm9kSFJ3T2k4dmQzZDNMbmN6TG05eVp5OHlNREF3TDNOMlp5SStQ" "SEJoZEdnZ1ptbHNiRDBpSXpFNE1VVXlPQ0lnWkQwaVRUQWdNR2czTlRCMk56VXdTREI2SWk4K1BISmxZM1FnZU" "QwaU1UUXpJaUI1UFNJMk9TSWdkMmxrZEdnOUlqSTJOQ0lnYUdWcFoyaDBQU0kwTVRJaUlISjRQU0k0SWlCbWFX" "eHNQU0lqTWpjek1ETkVJaTgrUEhKbFkzUWdlRDBpTVRRM0lpQjVQU0kzTXlJZ2MzUnliMnRsUFNJak1UQXhNek" "ZESWlCM2FXUjBhRDBpTWpVMklpQm9aV2xuYUhROUlqUXdOQ0lnY25nOUlqUWlJR1pwYkd3OUlpTXdaREV3TVRj" "aUx6NDhMM04yWno0PSJ9"; } // Retrieve information about the puzzle. uint32 _puzzleId = uint32(_tokenId >> 128); PuzzleData memory puzzleData = getPuzzle[_puzzleId]; address author = getPuzzleAuthor[_puzzleId]; uint32 solves = getPuzzleColorsAndSolves[_puzzleId].solves; uint120 colors = getPuzzleColorsAndSolves[_puzzleId].colors; return flagRenderer.render({ _puzzleData: puzzleData, _tokenId: _tokenId, _author: author, _solveTime: tokenData.solveTimestamp - puzzleData.addedTimestamp, _solveMetadata: tokenData.solveMetadata, _phase: _computePhase(puzzleData.firstSolveTimestamp, tokenData.solveTimestamp), _solves: solves, _colors: colors }); } // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- /// @notice Computes the phase the puzzle was at at some timestamp. /// @param _firstSolveTimestamp The timestamp of the first solve. /// @param _solveTimestamp The timestamp of the solve. /// @return phase The phase of the puzzle: ``Phase 0'' refers to the period /// before the puzzle has been solved, ``Phase 1'' refers to the period 2 /// days after the first solve, ``Phase 2'' refers to the period 3 days /// after the end of ``Phase 1,'' and ``Phase 3'' is when submissions are /// closed. function _computePhase(uint40 _firstSolveTimestamp, uint40 _solveTimestamp) internal pure returns (uint8 phase) { // Equivalent to: // ```sol // if (_firstSolveTimestamp == 0) { // phase = 0; // } else { // if (_solveTimestamp > _firstSolveTimestamp + SUBMISSION_LENGTH) { // phase = 3; // } else if (_solveTimestamp > _firstSolveTimestamp + PHASE_ONE_LENGTH) { // phase = 2; // } else { // phase = 1; // } // } // ``` assembly { phase := mul( iszero(iszero(_firstSolveTimestamp)), add( 1, add( gt(_solveTimestamp, add(_firstSolveTimestamp, PHASE_ONE_LENGTH)), gt(_solveTimestamp, add(_firstSolveTimestamp, SUBMISSION_LENGTH)) ) ) ) } } }
// SPDX-License-Identifier: The Unlicense pragma solidity ^0.8.9; import "./IShields.sol"; import "./IFrameGenerator.sol"; import "./IFieldGenerator.sol"; import "./IHardwareGenerator.sol"; import "./IShieldBadgeSVGs.sol"; interface IEmblemWeaver { function fieldGenerator() external returns (IFieldGenerator); function hardwareGenerator() external returns (IHardwareGenerator); function frameGenerator() external returns (IFrameGenerator); function shieldBadgeSVGGenerator() external returns (IShieldBadgeSVGs); function generateShieldURI(IShields.Shield memory shield) external view returns (string memory); function generateShieldBadgeURI(IShields.ShieldBadge shieldBadge) external view returns (string memory); }
// SPDX-License-Identifier: The Unlicense pragma solidity ^0.8.9; interface IFieldGenerator { enum FieldCategories { MYTHIC, HERALDIC } struct FieldData { string title; FieldCategories fieldType; string svgString; } function generateField(uint16 field, uint24[4] memory colors) external view returns (FieldData memory); }
// SPDX-License-Identifier: The Unlicense pragma solidity ^0.8.9; interface IFrameGenerator { struct FrameData { string title; uint256 fee; string svgString; } function generateFrame(uint16 Frame) external view returns (FrameData memory); }
// SPDX-License-Identifier: The Unlicense pragma solidity ^0.8.9; interface IHardwareGenerator { enum HardwareCategories { STANDARD, SPECIAL } struct HardwareData { string title; HardwareCategories hardwareType; string svgString; } function generateHardware(uint16 hardware) external view returns (HardwareData memory); }
// SPDX-License-Identifier: The Unlicense pragma solidity ^0.8.9; import "./IShields.sol"; interface IShieldBadgeSVGs { function generateShieldBadgeSVG(IShields.ShieldBadge shieldBadge) external view returns (string memory); }
// SPDX-License-Identifier: The Unlicense pragma solidity ^0.8.9; import "./IEmblemWeaver.sol"; interface IShields { enum ShieldBadge { MAKER, STANDARD } struct Shield { bool built; uint16 field; uint16 hardware; uint16 frame; ShieldBadge shieldBadge; uint24[4] colors; } function emblemWeaver() external view returns (IEmblemWeaver); function shields(uint256 tokenId) external view returns ( uint16 field, uint16 hardware, uint16 frame, uint24 color1, uint24 color2, uint24 color3, uint24 color4, ShieldBadge shieldBadge ); }
// SPDX-License-Identifier: The Unlicense pragma solidity ^0.8.9; import "./IShields.sol"; import "./IFieldGenerator.sol"; import "./IHardwareGenerator.sol"; import "./IFrameGenerator.sol"; interface IShieldsAPI { function getShield(uint256 shieldId) external view returns (IShields.Shield memory); function getShieldSVG(uint256 shieldId) external view returns (string memory); function getShieldSVG( uint16 field, uint24[4] memory colors, uint16 hardware, uint16 frame ) external view returns (string memory); function isShieldBuilt(uint256 shieldId) external view returns (bool); function getField(uint16 field, uint24[4] memory colors) external view returns (IFieldGenerator.FieldData memory); function getFieldTitle(uint16 field, uint24[4] memory colors) external view returns (string memory); function getFieldSVG(uint16 field, uint24[4] memory colors) external view returns (string memory); function getHardware(uint16 hardware) external view returns (IHardwareGenerator.HardwareData memory); function getHardwareTitle(uint16 hardware) external view returns (string memory); function getHardwareSVG(uint16 hardware) external view returns (string memory); function getFrame(uint16 frame) external view returns (IFrameGenerator.FrameData memory); function getFrameTitle(uint16 frame) external view returns (string memory); function getFrameSVG(uint16 frame) external view returns (string memory); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Library for converting numbers into strings and other string operations. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol) library LibString { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The `length` of the output is too small to contain all the hex digits. error HexLengthInsufficient(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The constant returned when the `search` is not found in the string. uint256 internal constant NOT_FOUND = type(uint256).max; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* DECIMAL OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the base 10 decimal representation of `value`. function toString(uint256 value) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { // The maximum value of a uint256 contains 78 digits (1 byte per digit), but // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned. // We will need 1 word for the trailing zeros padding, 1 word for the length, // and 3 words for a maximum of 78 digits. str := add(mload(0x40), 0x80) // Update the free memory pointer to allocate. mstore(0x40, add(str, 0x20)) // Zeroize the slot after the string. mstore(str, 0) // Cache the end of the memory to calculate the length later. let end := str let w := not(0) // Tsk. // We write the string from rightmost digit to leftmost digit. // The following is essentially a do-while loop that also handles the zero case. for { let temp := value } 1 {} { str := add(str, w) // `sub(str, 1)`. // Write the character to the pointer. // The ASCII index of the '0' character is 48. mstore8(str, add(48, mod(temp, 10))) // Keep dividing `temp` until zero. temp := div(temp, 10) if iszero(temp) { break } } let length := sub(end, str) // Move the pointer 32 bytes leftwards to make room for the length. str := sub(str, 0x20) // Store the length. mstore(str, length) } } /// @dev Returns the base 10 decimal representation of `value`. function toString(int256 value) internal pure returns (string memory str) { if (value >= 0) { return toString(uint256(value)); } unchecked { str = toString(uint256(-value)); } /// @solidity memory-safe-assembly assembly { // We still have some spare memory space on the left, // as we have allocated 3 words (96 bytes) for up to 78 digits. let length := mload(str) // Load the string length. mstore(str, 0x2d) // Store the '-' character. str := sub(str, 1) // Move back the string pointer by a byte. mstore(str, add(length, 1)) // Update the string length. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HEXADECIMAL OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the hexadecimal representation of `value`, /// left-padded to an input length of `length` bytes. /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, /// giving a total length of `length * 2 + 2` bytes. /// Reverts if `length` is too small for the output to contain all the digits. function toHexString(uint256 value, uint256 length) internal pure returns (string memory str) { str = toHexStringNoPrefix(value, length); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hexadecimal representation of `value`, /// left-padded to an input length of `length` bytes. /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, /// giving a total length of `length * 2` bytes. /// Reverts if `length` is too small for the output to contain all the digits. function toHexStringNoPrefix(uint256 value, uint256 length) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { // We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes // for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length. // We add 0x20 to the total and round down to a multiple of 0x20. // (0x20 + 0x20 + 0x02 + 0x20) = 0x62. str := add(mload(0x40), and(add(shl(1, length), 0x42), not(0x1f))) // Allocate the memory. mstore(0x40, add(str, 0x20)) // Zeroize the slot after the string. mstore(str, 0) // Cache the end to calculate the length later. let end := str // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) let start := sub(str, add(length, length)) let w := not(1) // Tsk. let temp := value // We write the string from rightmost digit to leftmost digit. // The following is essentially a do-while loop that also handles the zero case. for {} 1 {} { str := add(str, w) // `sub(str, 2)`. mstore8(add(str, 1), mload(and(temp, 15))) mstore8(str, mload(and(shr(4, temp), 15))) temp := shr(8, temp) if iszero(xor(str, start)) { break } } if temp { // Store the function selector of `HexLengthInsufficient()`. mstore(0x00, 0x2194895a) // Revert with (offset, size). revert(0x1c, 0x04) } // Compute the string's length. let strLength := sub(end, str) // Move the pointer and write the length. str := sub(str, 0x20) mstore(str, strLength) } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. /// As address are 20 bytes long, the output will left-padded to have /// a length of `20 * 2 + 2` bytes. function toHexString(uint256 value) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hexadecimal representation of `value`. /// The output is encoded using 2 hexadecimal digits per byte. /// As address are 20 bytes long, the output will left-padded to have /// a length of `20 * 2` bytes. function toHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, // 0x02 bytes for the prefix, and 0x40 bytes for the digits. // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0. str := add(mload(0x40), 0x80) // Allocate the memory. mstore(0x40, add(str, 0x20)) // Zeroize the slot after the string. mstore(str, 0) // Cache the end to calculate the length later. let end := str // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) let w := not(1) // Tsk. // We write the string from rightmost digit to leftmost digit. // The following is essentially a do-while loop that also handles the zero case. for { let temp := value } 1 {} { str := add(str, w) // `sub(str, 2)`. mstore8(add(str, 1), mload(and(temp, 15))) mstore8(str, mload(and(shr(4, temp), 15))) temp := shr(8, temp) if iszero(temp) { break } } // Compute the string's length. let strLength := sub(end, str) // Move the pointer and write the length. str := sub(str, 0x20) mstore(str, strLength) } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte, /// and the alphabets are capitalized conditionally according to /// https://eips.ethereum.org/EIPS/eip-55 function toHexStringChecksumed(address value) internal pure returns (string memory str) { str = toHexString(value); /// @solidity memory-safe-assembly assembly { let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...` let o := add(str, 0x22) let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... ` let t := shl(240, 136) // `0b10001000 << 240` for { let i := 0 } 1 {} { mstore(add(i, i), mul(t, byte(i, hashed))) i := add(i, 1) if eq(i, 20) { break } } mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask))))) o := add(o, 0x20) mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask))))) } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. function toHexString(address value) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hexadecimal representation of `value`. /// The output is encoded using 2 hexadecimal digits per byte. function toHexStringNoPrefix(address value) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { str := mload(0x40) // Allocate the memory. // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, // 0x02 bytes for the prefix, and 0x28 bytes for the digits. // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80. mstore(0x40, add(str, 0x80)) // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) str := add(str, 2) mstore(str, 40) let o := add(str, 0x20) mstore(add(o, 40), 0) value := shl(96, value) // We write the string from rightmost digit to leftmost digit. // The following is essentially a do-while loop that also handles the zero case. for { let i := 0 } 1 {} { let p := add(o, add(i, i)) let temp := byte(i, value) mstore8(add(p, 1), mload(and(temp, 15))) mstore8(p, mload(shr(4, temp))) i := add(i, 1) if eq(i, 20) { break } } } } /// @dev Returns the hex encoded string from the raw bytes. /// The output is encoded using 2 hexadecimal digits per byte. function toHexString(bytes memory raw) internal pure returns (string memory str) { str = toHexStringNoPrefix(raw); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hex encoded string from the raw bytes. /// The output is encoded using 2 hexadecimal digits per byte. function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { let length := mload(raw) str := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix. mstore(str, add(length, length)) // Store the length of the output. // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) let o := add(str, 0x20) let end := add(raw, length) for {} iszero(eq(raw, end)) {} { raw := add(raw, 1) mstore8(add(o, 1), mload(and(mload(raw), 15))) mstore8(o, mload(and(shr(4, mload(raw)), 15))) o := add(o, 2) } mstore(o, 0) // Zeroize the slot after the string. mstore(0x40, and(add(o, 31), not(31))) // Allocate the memory. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* RUNE STRING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the number of UTF characters in the string. function runeCount(string memory s) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { if mload(s) { mstore(0x00, div(not(0), 255)) mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506) let o := add(s, 0x20) let end := add(o, mload(s)) for { result := 1 } 1 { result := add(result, 1) } { o := add(o, byte(0, mload(shr(250, mload(o))))) if iszero(lt(o, end)) { break } } } } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* BYTE STRING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // For performance and bytecode compactness, all indices of the following operations // are byte (ASCII) offsets, not UTF character offsets. /// @dev Returns `subject` all occurrences of `search` replaced with `replacement`. function replace(string memory subject, string memory search, string memory replacement) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) let searchLength := mload(search) let replacementLength := mload(replacement) subject := add(subject, 0x20) search := add(search, 0x20) replacement := add(replacement, 0x20) result := add(mload(0x40), 0x20) let subjectEnd := add(subject, subjectLength) if iszero(gt(searchLength, subjectLength)) { let subjectSearchEnd := add(sub(subjectEnd, searchLength), 1) let h := 0 if iszero(lt(searchLength, 32)) { h := keccak256(search, searchLength) } let m := shl(3, sub(32, and(searchLength, 31))) let s := mload(search) for {} 1 {} { let t := mload(subject) // Whether the first `searchLength % 32` bytes of // `subject` and `search` matches. if iszero(shr(m, xor(t, s))) { if h { if iszero(eq(keccak256(subject, searchLength), h)) { mstore(result, t) result := add(result, 1) subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } continue } } // Copy the `replacement` one word at a time. for { let o := 0 } 1 {} { mstore(add(result, o), mload(add(replacement, o))) o := add(o, 0x20) if iszero(lt(o, replacementLength)) { break } } result := add(result, replacementLength) subject := add(subject, searchLength) if searchLength { if iszero(lt(subject, subjectSearchEnd)) { break } continue } } mstore(result, t) result := add(result, 1) subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } } } let resultRemainder := result result := add(mload(0x40), 0x20) let k := add(sub(resultRemainder, result), sub(subjectEnd, subject)) // Copy the rest of the string one word at a time. for {} lt(subject, subjectEnd) {} { mstore(resultRemainder, mload(subject)) resultRemainder := add(resultRemainder, 0x20) subject := add(subject, 0x20) } result := sub(result, 0x20) // Zeroize the slot after the string. let last := add(add(result, 0x20), k) mstore(last, 0) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, and(add(last, 31), not(31))) // Store the length of the result. mstore(result, k) } } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from left to right, starting from `from`. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function indexOf(string memory subject, string memory search, uint256 from) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { for { let subjectLength := mload(subject) } 1 {} { if iszero(mload(search)) { if iszero(gt(from, subjectLength)) { result := from break } result := subjectLength break } let searchLength := mload(search) let subjectStart := add(subject, 0x20) result := not(0) // Initialize to `NOT_FOUND`. subject := add(subjectStart, from) let end := add(sub(add(subjectStart, subjectLength), searchLength), 1) let m := shl(3, sub(32, and(searchLength, 31))) let s := mload(add(search, 0x20)) if iszero(and(lt(subject, end), lt(from, subjectLength))) { break } if iszero(lt(searchLength, 32)) { for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { if iszero(shr(m, xor(mload(subject), s))) { if eq(keccak256(subject, searchLength), h) { result := sub(subject, subjectStart) break } } subject := add(subject, 1) if iszero(lt(subject, end)) { break } } break } for {} 1 {} { if iszero(shr(m, xor(mload(subject), s))) { result := sub(subject, subjectStart) break } subject := add(subject, 1) if iszero(lt(subject, end)) { break } } break } } } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from left to right. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function indexOf(string memory subject, string memory search) internal pure returns (uint256 result) { result = indexOf(subject, search, 0); } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from right to left, starting from `from`. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function lastIndexOf(string memory subject, string memory search, uint256 from) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { for {} 1 {} { result := not(0) // Initialize to `NOT_FOUND`. let searchLength := mload(search) if gt(searchLength, mload(subject)) { break } let w := result let fromMax := sub(mload(subject), searchLength) if iszero(gt(fromMax, from)) { from := fromMax } let end := add(add(subject, 0x20), w) subject := add(add(subject, 0x20), from) if iszero(gt(subject, end)) { break } // As this function is not too often used, // we shall simply use keccak256 for smaller bytecode size. for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { if eq(keccak256(subject, searchLength), h) { result := sub(subject, add(end, 1)) break } subject := add(subject, w) // `sub(subject, 1)`. if iszero(gt(subject, end)) { break } } break } } } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from right to left. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function lastIndexOf(string memory subject, string memory search) internal pure returns (uint256 result) { result = lastIndexOf(subject, search, uint256(int256(-1))); } /// @dev Returns whether `subject` starts with `search`. function startsWith(string memory subject, string memory search) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { let searchLength := mload(search) // Just using keccak256 directly is actually cheaper. // forgefmt: disable-next-item result := and( iszero(gt(searchLength, mload(subject))), eq( keccak256(add(subject, 0x20), searchLength), keccak256(add(search, 0x20), searchLength) ) ) } } /// @dev Returns whether `subject` ends with `search`. function endsWith(string memory subject, string memory search) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { let searchLength := mload(search) let subjectLength := mload(subject) // Whether `search` is not longer than `subject`. let withinRange := iszero(gt(searchLength, subjectLength)) // Just using keccak256 directly is actually cheaper. // forgefmt: disable-next-item result := and( withinRange, eq( keccak256( // `subject + 0x20 + max(subjectLength - searchLength, 0)`. add(add(subject, 0x20), mul(withinRange, sub(subjectLength, searchLength))), searchLength ), keccak256(add(search, 0x20), searchLength) ) ) } } /// @dev Returns `subject` repeated `times`. function repeat(string memory subject, uint256 times) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) if iszero(or(iszero(times), iszero(subjectLength))) { subject := add(subject, 0x20) result := mload(0x40) let output := add(result, 0x20) for {} 1 {} { // Copy the `subject` one word at a time. for { let o := 0 } 1 {} { mstore(add(output, o), mload(add(subject, o))) o := add(o, 0x20) if iszero(lt(o, subjectLength)) { break } } output := add(output, subjectLength) times := sub(times, 1) if iszero(times) { break } } // Zeroize the slot after the string. mstore(output, 0) // Store the length. let resultLength := sub(output, add(result, 0x20)) mstore(result, resultLength) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, add(result, and(add(resultLength, 63), not(31)))) } } } /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). /// `start` and `end` are byte offsets. function slice(string memory subject, uint256 start, uint256 end) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) if iszero(gt(subjectLength, end)) { end := subjectLength } if iszero(gt(subjectLength, start)) { start := subjectLength } if lt(start, end) { result := mload(0x40) let resultLength := sub(end, start) mstore(result, resultLength) subject := add(subject, start) let w := not(31) // Copy the `subject` one word at a time, backwards. for { let o := and(add(resultLength, 31), w) } 1 {} { mstore(add(result, o), mload(add(subject, o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } // Zeroize the slot after the string. mstore(add(add(result, 0x20), resultLength), 0) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, add(result, and(add(resultLength, 63), w))) } } } /// @dev Returns a copy of `subject` sliced from `start` to the end of the string. /// `start` is a byte offset. function slice(string memory subject, uint256 start) internal pure returns (string memory result) { result = slice(subject, start, uint256(int256(-1))); } /// @dev Returns all the indices of `search` in `subject`. /// The indices are byte offsets. function indicesOf(string memory subject, string memory search) internal pure returns (uint256[] memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) let searchLength := mload(search) if iszero(gt(searchLength, subjectLength)) { subject := add(subject, 0x20) search := add(search, 0x20) result := add(mload(0x40), 0x20) let subjectStart := subject let subjectSearchEnd := add(sub(add(subject, subjectLength), searchLength), 1) let h := 0 if iszero(lt(searchLength, 32)) { h := keccak256(search, searchLength) } let m := shl(3, sub(32, and(searchLength, 31))) let s := mload(search) for {} 1 {} { let t := mload(subject) // Whether the first `searchLength % 32` bytes of // `subject` and `search` matches. if iszero(shr(m, xor(t, s))) { if h { if iszero(eq(keccak256(subject, searchLength), h)) { subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } continue } } // Append to `result`. mstore(result, sub(subject, subjectStart)) result := add(result, 0x20) // Advance `subject` by `searchLength`. subject := add(subject, searchLength) if searchLength { if iszero(lt(subject, subjectSearchEnd)) { break } continue } } subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } } let resultEnd := result // Assign `result` to the free memory pointer. result := mload(0x40) // Store the length of `result`. mstore(result, shr(5, sub(resultEnd, add(result, 0x20)))) // Allocate memory for result. // We allocate one more word, so this array can be recycled for {split}. mstore(0x40, add(resultEnd, 0x20)) } } } /// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string. function split(string memory subject, string memory delimiter) internal pure returns (string[] memory result) { uint256[] memory indices = indicesOf(subject, delimiter); /// @solidity memory-safe-assembly assembly { let w := not(31) let indexPtr := add(indices, 0x20) let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1))) mstore(add(indicesEnd, w), mload(subject)) mstore(indices, add(mload(indices), 1)) let prevIndex := 0 for {} 1 {} { let index := mload(indexPtr) mstore(indexPtr, 0x60) if iszero(eq(index, prevIndex)) { let element := mload(0x40) let elementLength := sub(index, prevIndex) mstore(element, elementLength) // Copy the `subject` one word at a time, backwards. for { let o := and(add(elementLength, 31), w) } 1 {} { mstore(add(element, o), mload(add(add(subject, prevIndex), o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } // Zeroize the slot after the string. mstore(add(add(element, 0x20), elementLength), 0) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, add(element, and(add(elementLength, 63), w))) // Store the `element` into the array. mstore(indexPtr, element) } prevIndex := add(index, mload(delimiter)) indexPtr := add(indexPtr, 0x20) if iszero(lt(indexPtr, indicesEnd)) { break } } result := indices if iszero(mload(delimiter)) { result := add(indices, 0x20) mstore(result, sub(mload(indices), 2)) } } } /// @dev Returns a concatenated string of `a` and `b`. /// Cheaper than `string.concat()` and does not de-align the free memory pointer. function concat(string memory a, string memory b) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let w := not(31) result := mload(0x40) let aLength := mload(a) // Copy `a` one word at a time, backwards. for { let o := and(add(mload(a), 32), w) } 1 {} { mstore(add(result, o), mload(add(a, o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } let bLength := mload(b) let output := add(result, mload(a)) // Copy `b` one word at a time, backwards. for { let o := and(add(bLength, 32), w) } 1 {} { mstore(add(output, o), mload(add(b, o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } let totalLength := add(aLength, bLength) let last := add(add(result, 0x20), totalLength) // Zeroize the slot after the string. mstore(last, 0) // Stores the length. mstore(result, totalLength) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, and(add(last, 31), w)) } } /// @dev Returns a copy of the string in either lowercase or UPPERCASE. function toCase(string memory subject, bool toUpper) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let length := mload(subject) if length { result := add(mload(0x40), 0x20) subject := add(subject, 1) let flags := shl(add(70, shl(5, toUpper)), 67108863) let w := not(0) for { let o := length } 1 {} { o := add(o, w) let b := and(0xff, mload(add(subject, o))) mstore8(add(result, o), xor(b, and(shr(b, flags), 0x20))) if iszero(o) { break } } // Restore the result. result := mload(0x40) // Stores the string length. mstore(result, length) // Zeroize the slot after the string. let last := add(add(result, 0x20), length) mstore(last, 0) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, and(add(last, 31), not(31))) } } } /// @dev Returns a lowercased copy of the string. function lower(string memory subject) internal pure returns (string memory result) { result = toCase(subject, false); } /// @dev Returns an UPPERCASED copy of the string. function upper(string memory subject) internal pure returns (string memory result) { result = toCase(subject, true); } /// @dev Escapes the string to be used within HTML tags. function escapeHTML(string memory s) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { for { let end := add(s, mload(s)) result := add(mload(0x40), 0x20) // Store the bytes of the packed offsets and strides into the scratch space. // `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6. mstore(0x1f, 0x900094) mstore(0x08, 0xc0000000a6ab) // Store ""&'<>" into the scratch space. mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b)) } iszero(eq(s, end)) {} { s := add(s, 1) let c := and(mload(s), 0xff) // Not in `["\"","'","&","<",">"]`. if iszero(and(shl(c, 1), 0x500000c400000000)) { mstore8(result, c) result := add(result, 1) continue } let t := shr(248, mload(c)) mstore(result, mload(and(t, 31))) result := add(result, shr(5, t)) } let last := result // Zeroize the slot after the string. mstore(last, 0) // Restore the result to the start of the free memory. result := mload(0x40) // Store the length of the result. mstore(result, sub(last, add(result, 0x20))) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, and(add(last, 31), not(31))) } } /// @dev Escapes the string to be used within double-quotes in a JSON. function escapeJSON(string memory s) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { for { let end := add(s, mload(s)) result := add(mload(0x40), 0x20) // Store "\\u0000" in scratch space. // Store "0123456789abcdef" in scratch space. // Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`. // into the scratch space. mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672) // Bitmask for detecting `["\"","\\"]`. let e := or(shl(0x22, 1), shl(0x5c, 1)) } iszero(eq(s, end)) {} { s := add(s, 1) let c := and(mload(s), 0xff) if iszero(lt(c, 0x20)) { if iszero(and(shl(c, 1), e)) { // Not in `["\"","\\"]`. mstore8(result, c) result := add(result, 1) continue } mstore8(result, 0x5c) // "\\". mstore8(add(result, 1), c) result := add(result, 2) continue } if iszero(and(shl(c, 1), 0x3700)) { // Not in `["\b","\t","\n","\f","\d"]`. mstore8(0x1d, mload(shr(4, c))) // Hex value. mstore8(0x1e, mload(and(c, 15))) // Hex value. mstore(result, mload(0x19)) // "\\u00XX". result := add(result, 6) continue } mstore8(result, 0x5c) // "\\". mstore8(add(result, 1), mload(add(c, 8))) result := add(result, 2) } let last := result // Zeroize the slot after the string. mstore(last, 0) // Restore the result to the start of the free memory. result := mload(0x40) // Store the length of the result. mstore(result, sub(last, add(result, 0x20))) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, and(add(last, 31), not(31))) } } /// @dev Returns whether `a` equals `b`. function eq(string memory a, string memory b) internal pure returns (bool result) { assembly { result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b))) } } /// @dev Packs a single string with its length into a single word. /// Returns `bytes32(0)` if the length is zero or greater than 31. function packOne(string memory a) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { // We don't need to zero right pad the string, // since this is our own custom non-standard packing scheme. result := mul( // Load the length and the bytes. mload(add(a, 0x1f)), // `length != 0 && length < 32`. Abuses underflow. // Assumes that the length is valid and within the block gas limit. lt(sub(mload(a), 1), 0x1f) ) } } /// @dev Unpacks a string packed using {packOne}. /// Returns the empty string if `packed` is `bytes32(0)`. /// If `packed` is not an output of {packOne}, the output behaviour is undefined. function unpackOne(bytes32 packed) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { // Grab the free memory pointer. result := mload(0x40) // Allocate 2 words (1 for the length, 1 for the bytes). mstore(0x40, add(result, 0x40)) // Zeroize the length slot. mstore(result, 0) // Store the length and bytes. mstore(add(result, 0x1f), packed) // Right pad with zeroes. mstore(add(add(result, 0x20), mload(result)), 0) } } /// @dev Packs two strings with their lengths into a single word. /// Returns `bytes32(0)` if combined length is zero or greater than 30. function packTwo(string memory a, string memory b) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { let aLength := mload(a) // We don't need to zero right pad the strings, // since this is our own custom non-standard packing scheme. result := mul( // Load the length and the bytes of `a` and `b`. or( shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))), mload(sub(add(b, 0x1e), aLength)) ), // `totalLength != 0 && totalLength < 31`. Abuses underflow. // Assumes that the lengths are valid and within the block gas limit. lt(sub(add(aLength, mload(b)), 1), 0x1e) ) } } /// @dev Unpacks strings packed using {packTwo}. /// Returns the empty strings if `packed` is `bytes32(0)`. /// If `packed` is not an output of {packTwo}, the output behaviour is undefined. function unpackTwo(bytes32 packed) internal pure returns (string memory resultA, string memory resultB) { /// @solidity memory-safe-assembly assembly { // Grab the free memory pointer. resultA := mload(0x40) resultB := add(resultA, 0x40) // Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words. mstore(0x40, add(resultB, 0x40)) // Zeroize the length slots. mstore(resultA, 0) mstore(resultB, 0) // Store the lengths and bytes. mstore(add(resultA, 0x1f), packed) mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA)))) // Right pad with zeroes. mstore(add(add(resultA, 0x20), mload(resultA)), 0) mstore(add(add(resultB, 0x20), mload(resultB)), 0) } } /// @dev Directly returns `a` without copying. function directReturn(string memory a) internal pure { assembly { // Assumes that the string does not start from the scratch space. let retStart := sub(a, 0x20) let retSize := add(mload(a), 0x40) // Right pad with zeroes. Just in case the string is produced // by a method that doesn't zero right pad. mstore(add(retStart, retSize), 0) // Store the return offset. mstore(retStart, 0x20) // End the transaction, returning the string. return(retStart, retSize) } } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Simple single owner authorization mixin. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol) abstract contract Owned { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event OwnershipTransferred(address indexed user, address indexed newOwner); /*////////////////////////////////////////////////////////////// OWNERSHIP STORAGE //////////////////////////////////////////////////////////////*/ address public owner; modifier onlyOwner() virtual { require(msg.sender == owner, "UNAUTHORIZED"); _; } /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor(address _owner) { owner = _owner; emit OwnershipTransferred(address(0), _owner); } /*////////////////////////////////////////////////////////////// OWNERSHIP LOGIC //////////////////////////////////////////////////////////////*/ function transferOwnership(address newOwner) public virtual onlyOwner { owner = newOwner; emit OwnershipTransferred(msg.sender, newOwner); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); /*////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor(string memory _name, string memory _symbol, uint8 _decimals) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern, minimalist, and gas efficient ERC-721 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) abstract contract ERC721 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 indexed id); event Approval(address indexed owner, address indexed spender, uint256 indexed id); event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /*////////////////////////////////////////////////////////////// METADATA STORAGE/LOGIC //////////////////////////////////////////////////////////////*/ string public name; string public symbol; function tokenURI(uint256 id) public view virtual returns (string memory); /*////////////////////////////////////////////////////////////// ERC721 BALANCE/OWNER STORAGE //////////////////////////////////////////////////////////////*/ mapping(uint256 => address) internal _ownerOf; mapping(address => uint256) internal _balanceOf; function ownerOf(uint256 id) public view virtual returns (address owner) { require((owner = _ownerOf[id]) != address(0), "NOT_MINTED"); } function balanceOf(address owner) public view virtual returns (uint256) { require(owner != address(0), "ZERO_ADDRESS"); return _balanceOf[owner]; } /*////////////////////////////////////////////////////////////// ERC721 APPROVAL STORAGE //////////////////////////////////////////////////////////////*/ mapping(uint256 => address) public getApproved; mapping(address => mapping(address => bool)) public isApprovedForAll; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor(string memory _name, string memory _symbol) { name = _name; symbol = _symbol; } /*////////////////////////////////////////////////////////////// ERC721 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 id) public virtual { address owner = _ownerOf[id]; require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED"); getApproved[id] = spender; emit Approval(owner, spender, id); } function setApprovalForAll(address operator, bool approved) public virtual { isApprovedForAll[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } function transferFrom(address from, address to, uint256 id) public virtual { require(from == _ownerOf[id], "WRONG_FROM"); require(to != address(0), "INVALID_RECIPIENT"); require( msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id], "NOT_AUTHORIZED" ); // Underflow of the sender's balance is impossible because we check for // ownership above and the recipient's balance can't realistically overflow. unchecked { _balanceOf[from]--; _balanceOf[to]++; } _ownerOf[id] = to; delete getApproved[id]; emit Transfer(from, to, id); } function safeTransferFrom(address from, address to, uint256 id) public virtual { transferFrom(from, to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } function safeTransferFrom(address from, address to, uint256 id, bytes calldata data) public virtual { transferFrom(from, to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } /*////////////////////////////////////////////////////////////// ERC165 LOGIC //////////////////////////////////////////////////////////////*/ function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165 || interfaceId == 0x80ac58cd // ERC165 Interface ID for ERC721 || interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 id) internal virtual { require(to != address(0), "INVALID_RECIPIENT"); require(_ownerOf[id] == address(0), "ALREADY_MINTED"); // Counter overflow is incredibly unrealistic. unchecked { _balanceOf[to]++; } _ownerOf[id] = to; emit Transfer(address(0), to, id); } function _burn(uint256 id) internal virtual { address owner = _ownerOf[id]; require(owner != address(0), "NOT_MINTED"); // Ownership check above ensures no underflow. unchecked { _balanceOf[owner]--; } delete _ownerOf[id]; delete getApproved[id]; emit Transfer(owner, address(0), id); } /*////////////////////////////////////////////////////////////// INTERNAL SAFE MINT LOGIC //////////////////////////////////////////////////////////////*/ function _safeMint(address to, uint256 id) internal virtual { _mint(to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } function _safeMint(address to, uint256 id, bytes memory data) internal virtual { _mint(to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } } /// @notice A generic interface for a contract which properly accepts ERC721 tokens. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) abstract contract ERC721TokenReceiver { function onERC721Received(address, address, uint256, bytes calldata) external virtual returns (bytes4) { return ERC721TokenReceiver.onERC721Received.selector; } }
// SPDX-License-Identifier: MIT pragma solidity >=0.8.0; /// @notice Efficient library for creating string representations of integers. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol) /// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibString.sol) library LibString { function toString(uint256 value) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { // The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes // to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the // trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes. let newFreeMemoryPointer := add(mload(0x40), 160) // Update the free memory pointer to avoid overriding our string. mstore(0x40, newFreeMemoryPointer) // Assign str to the end of the zone of newly allocated memory. str := sub(newFreeMemoryPointer, 32) // Clean the last word of memory it may not be overwritten. mstore(str, 0) // Cache the end of the memory to calculate the length later. let end := str // We write the string from rightmost digit to leftmost digit. // The following is essentially a do-while loop that also handles the zero case. // prettier-ignore for { let temp := value } 1 { } { // Move the pointer 1 byte to the left. str := sub(str, 1) // Write the character to the pointer. // The ASCII index of the '0' character is 48. mstore8(str, add(48, mod(temp, 10))) // Keep dividing temp until zero. temp := div(temp, 10) // prettier-ignore if iszero(temp) { break } } // Compute and cache the final total length of the string. let length := sub(end, str) // Move the pointer 32 bytes leftwards to make room for the length. str := sub(str, 32) // Store the string's length at the start of memory allocated for our string. mstore(str, length) } } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import { ERC20 } from "../tokens/ERC20.sol"; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. /// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. library SafeTransferLib { /*////////////////////////////////////////////////////////////// ETH OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferETH(address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Transfer the ETH and store if it succeeded or not. success := call(gas(), to, amount, 0, 0, 0, 0) } require(success, "ETH_TRANSFER_FAILED"); } /*////////////////////////////////////////////////////////////// ERC20 OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferFrom(ERC20 token, address from, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore( freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000 ) mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument. mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) ) } require(success, "TRANSFER_FROM_FAILED"); } function safeTransfer(ERC20 token, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore( freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000 ) mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "TRANSFER_FAILED"); } function safeApprove(ERC20 token, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore( freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000 ) mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "APPROVE_FAILED"); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import { IShieldsAPI } from "shields-api/interfaces/IShieldsAPI.sol"; import { Owned } from "solmate/auth/Owned.sol"; import { ERC721 } from "solmate/tokens/ERC721.sol"; import { LibString } from "solmate/utils/LibString.sol"; import { ICurta } from "@/contracts/interfaces/ICurta.sol"; import { Base64 } from "@/contracts/utils/Base64.sol"; /// @title The Authorship Token ERC-721 token contract /// @author fiveoutofnine /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) /// @notice ``Authorship Tokens'' are ERC-721 tokens that are required to add /// puzzles to Curta. Each Authorship Token may be used like a ticket once. /// After an Authorship Token has been used to add a puzzle, it can never be /// used again to add another puzzle. As soon as a puzzle has been deployed and /// added to Curta, anyone may attempt to solve it. /// @dev Other than the initial distribution, the only way to obtain an /// Authorship Token will be to be the first solver to any puzzle on Curta. contract AuthorshipToken is ERC721, Owned { using LibString for uint256; // ------------------------------------------------------------------------- // Constants // ------------------------------------------------------------------------- /// @notice The shields API contract. /// @dev This is the mainnet address. IShieldsAPI constant shieldsAPI = IShieldsAPI(0x740CBbF0116a82F64e83E1AE68c92544870B0C0F); /// @notice Salt used to compute the seed in {AuthorshipToken.tokenURI}. bytes32 constant SALT = bytes32("Curta.AuthorshipToken"); // ------------------------------------------------------------------------- // Errors // ------------------------------------------------------------------------- /// @notice Emitted when there are no tokens available to claim. error NoTokensAvailable(); /// @notice Emitted when `msg.sender` is not authorized. error Unauthorized(); // ------------------------------------------------------------------------- // Immutable Storage // ------------------------------------------------------------------------- /// @notice The Curta / Flags contract. address public immutable curta; /// @notice The number of seconds until an additional token is made /// available for minting by the author. uint256 public immutable issueLength; /// @notice The timestamp of when the contract was deployed. uint256 public immutable deployTimestamp; // ------------------------------------------------------------------------- // Storage // ------------------------------------------------------------------------- /// @notice The number of tokens that have been claimed by the owner. uint256 public numClaimedByOwner; /// @notice The total supply of tokens. uint256 public totalSupply; /// @notice Mapping to keep track of which addresses have claimed from // the mint list. mapping(address => bool) public hasClaimed; // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- /// @param _curta The Curta / Flags contract. /// @param _issueLength The number of seconds until an additional token is /// made available for minting by the author. /// @param _authors The list of authors in the initial batch. constructor(address _curta, uint256 _issueLength, address[] memory _authors) ERC721("Authorship Token", "AUTH") Owned(msg.sender) { curta = _curta; issueLength = _issueLength; deployTimestamp = block.timestamp; // Mint tokens to the initial batch of authors. uint256 length = _authors.length; for (uint256 i; i < length;) { _mint(_authors[i], i + 1); unchecked { ++i; } } totalSupply = length; } // ------------------------------------------------------------------------- // Functions // ------------------------------------------------------------------------- /// @notice Mints a token to `_to`. /// @dev Only the Curta contract can call this function. /// @param _to The address to mint the token to. function curtaMint(address _to) external { // Revert if the sender is not the Curta contract. if (msg.sender != curta) revert Unauthorized(); unchecked { uint256 tokenId = ++totalSupply; _mint(_to, tokenId); } } /// @notice Mints a token to `_to`. /// @dev Only the owner can call this function. The owner may claim a token /// every `issueLength` seconds. /// @param _to The address to mint the token to. function ownerMint(address _to) external onlyOwner { unchecked { uint256 numIssued = (block.timestamp - deployTimestamp) / issueLength; uint256 numMintable = numIssued - numClaimedByOwner++; // Revert if no tokens are available to mint. if (numMintable == 0) revert NoTokensAvailable(); // Mint token uint256 tokenId = ++totalSupply; _mint(_to, tokenId); } } // ------------------------------------------------------------------------- // ERC721Metadata // ------------------------------------------------------------------------- /// @notice A distinct Uniform Resource Identifier (URI) for a given asset. /// @param _tokenId The token ID. /// @return URI for the token. function tokenURI(uint256 _tokenId) public view override returns (string memory) { require(_ownerOf[_tokenId] != address(0), "NOT_MINTED"); // Generate seed. uint256 seed = uint256(keccak256(abi.encodePacked(_tokenId, SALT))); // Bitpacked colors. uint256 colors = 0x6351CEFF00FFB300FF6B00B5000A007FFF78503C323232FE7FFF6C28A2FF007A; // Shuffle `colors` by performing 4 iterations of Fisher-Yates shuffle. // We do this to pick 4 unique colors from `colors`. unchecked { uint256 shift = 24 * (seed % 11); colors = (colors & ((type(uint256).max ^ (0xFFFFFF << shift)) ^ 0xFFFFFF)) | ((colors & 0xFFFFFF) << shift) | ((colors >> shift) & 0xFFFFFF); seed >>= 4; shift = 24 * (seed % 10); colors = (colors & ((type(uint256).max ^ (0xFFFFFF << shift)) ^ (0xFFFFFF << 24))) | (((colors >> 24) & 0xFFFFFF) << shift) | (((colors >> shift) & 0xFFFFFF) << 24); seed >>= 4; shift = 24 * (seed % 9); colors = (colors & ((type(uint256).max ^ (0xFFFFFF << shift)) ^ (0xFFFFFF << 48))) | (((colors >> 48) & 0xFFFFFF) << shift) | (((colors >> shift) & 0xFFFFFF) << 48); seed >>= 4; shift = 24 * (seed & 7); colors = (colors & ((type(uint256).max ^ (0xFFFFFF << shift)) ^ (0xFFFFFF << 72))) | (((colors >> 72) & 0xFFFFFF) << shift) | (((colors >> shift) & 0xFFFFFF) << 72); seed >>= 3; } return string.concat( "data:application/json;base64,", Base64.encode( abi.encodePacked( '{"name":"Authorship Token #', _tokenId.toString(), '","description":"This token allows 1 puzzle to be added to Curta. Once it has ' 'been used, it can never be used again.","image_data":"data:image/svg+xml;base6' "4,", Base64.encode( abi.encodePacked( '<svg width="750" height="750" xmlns="http://www.w3.org/2000/svg" fill=' '"none" viewBox="0 0 750 750"><style>.a{filter:url(#c)drop-shadow(0 0 2' "px #007fff);fill:#fff;width:4px}.b{filter:drop-shadow(0 0 .5px #007fff" ");fill:#000;width:3px}.c{height:13px}.d{height:6px}.e{height:4px}.f{he" "ight:12px}.g{height:5px}.h{height:3px}.i{width:320px;height:620px}.j{c" "x:375px;r:20px}.k{stroke:#27303d}.l{fill:#000}.n{fill:#0d1017}.o{strok" 'e-width:2px}@font-face{font-family:"A";src:url(data:font/woff2;charset' "=utf-8;base64,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" 'MW6dCcHc6I9atPW9msuGc/bptop2dPAAA=)}</style><defs><radialGradient id="' 'b"><stop stop-color="#007FFF"/><stop offset="100%" stop-opacity="0"/><' '/radialGradient><filter id="c"><feGaussianBlur stdDeviation="8" in="So' 'urceGraphic" result="offset-blur"/><feComposite operator="out" in="Sou' 'rceGraphic" in2="offset-blur" result="inverse"/><feFlood flood-color="' '#007FFF" flood-opacity=".95" result="color"/><feComposite operator="in' '" in="color" in2="inverse" result="shadow"/><feComposite in="shadow" i' 'n2="SourceGraphic"/><feComposite operator="atop" in="shadow" in2="Sour' 'ceGraphic"/></filter><mask id="a"><path fill="#000" d="M0 0h750v750H0z' '"/><rect class="i" x="215" y="65" rx="20" fill="#FFF"/><circle class="' 'j l" cy="65"/><circle class="j l" cy="685"/></mask></defs><path fill="' '#10131C" d="M0 0h750v750H0z"/><rect class="i n" x="215" y="65" mask="u' 'rl(#a)" rx="20"/><circle mask="url(#a)" fill="url(#b)" cx="375" cy="38' '1" r="180"/><circle class="j k n" cy="125"/><g transform="translate(35' '9 110)"><circle class="n" cy="16" cx="16" r="16"/><rect class="a c" x=' '"8" y="7" rx="2"/><rect class="b f" x="8.5" y="7.5" rx="1.5"/><rect cl' 'ass="a e" x="8" y="21" rx="2"/><rect class="b h" x="8.5" y="21.5" rx="' '1.5"/><rect class="a d" x="14" y="7" rx="2"/><rect class="b g" x="14.5' '" y="7.5" rx="1.5"/><rect class="a e" x="14" y="14" rx="2"/><rect clas' 's="b h" x="14.5" y="14.5" rx="1.5"/><rect class="a d" x="14" y="19" rx' '="2"/><rect class="b g" x="14.5" y="19.5" rx="1.5"/><rect class="a c" ' 'x="20" y="12" rx="2"/><rect class="b f" x="20.5" y="12.5" rx="1.5"/><r' 'ect class="a e" x="20" y="7" rx="2"/><rect class="b h" x="20.5" y="7.5' '" rx="1.5"/></g><path d="M338.814 168.856c-.373 0-.718-.063-1.037-.191' "a2.829 2.829 0 0 1-.878-.606 2.828 2.828 0 0 1-.606-.878 2.767 2.767 0" " 0 1-.193-1.037v-.336c0-.372.064-.723.192-1.053.138-.319.34-.611.606-." "877a2.59 2.59 0 0 1 .878-.59 2.58 2.58 0 0 1 1.038-.208h4.26c.245 0 .4" "8.032.703.096.212.053.425.143.638.27.223.118.415.256.574.416.16.16.304" ".345.431.558.043.064.07.133.08.208a.301.301 0 0 1-.016.095.346.346 0 0" " 1-.175.256.42.42 0 0 1-.32.032.333.333 0 0 1-.239-.192 3.016 3.016 0 " "0 0-.303-.399 2.614 2.614 0 0 0-.415-.303 1.935 1.935 0 0 0-.463-.191 " "1.536 1.536 0 0 0-.495-.048c-.712 0-1.42-.006-2.122-.016-.713 0-1.425." "005-2.138.016-.266 0-.51.042-.734.127-.234.096-.442.24-.623.431a1.988 " "1.988 0 0 0-.43.623 1.961 1.961 0 0 0-.144.75v.335a1.844 1.844 0 0 0 ." "574 1.356 1.844 1.844 0 0 0 1.356.574h4.261c.17 0 .33-.015.48-.047a2.0" "2 2.02 0 0 0 .446-.192c.149-.074.282-.165.399-.271.106-.107.207-.229.3" "03-.367a.438.438 0 0 1 .255-.144c.096-.01.187.01.272.064a.35.35 0 0 1 " ".16.24.306.306 0 0 1-.033.27 2.653 2.653 0 0 1-.43.527c-.16.139-.346.2" "66-.559.383-.213.117-.42.197-.622.24-.213.053-.436.08-.67.08h-4.262Zm1" "7.553 0c-.713 0-1.324-.266-1.835-.797a2.69 2.69 0 0 1-.766-1.931v-2.66" "5c0-.117.037-.213.112-.287a.37.37 0 0 1 .27-.112c.118 0 .214.037.288.1" "12a.39.39 0 0 1 .112.287v2.664c0 .533.18.99.542 1.373a1.71 1.71 0 0 0 " "1.293.559h3.878c.51 0 .941-.187 1.292-.559a1.93 1.93 0 0 0 .543-1.372v" "-2.665a.39.39 0 0 1 .111-.287.389.389 0 0 1 .288-.112.37.37 0 0 1 .271" ".112.39.39 0 0 1 .112.287v2.664c0 .756-.256 1.4-.766 1.932-.51.531-1.1" "28.797-1.851.797h-3.894Zm23.824-.718a.456.456 0 0 1 .16.192c.01.042.01" "6.09.016.143a.47.47 0 0 1-.016.112.355.355 0 0 1-.143.208.423.423 0 0 " "1-.24.063h-.048a.141.141 0 0 1-.064-.016c-.02 0-.037-.005-.047-.016a10" "4.86 104.86 0 0 1-1.18-.83c-.374-.265-.746-.531-1.118-.797-.011 0-.016" "-.006-.016-.016-.01 0-.016-.005-.016-.016-.01 0-.016-.005-.016-.016h-5" ".553v1.324a.39.39 0 0 1-.112.288.425.425 0 0 1-.287.111.37.37 0 0 1-.2" "72-.111.389.389 0 0 1-.111-.288v-4.946c0-.054.005-.107.016-.16a.502.50" "2 0 0 1 .095-.128.374.374 0 0 1 .128-.08.316.316 0 0 1 .144-.031h6.893" "c.256 0 .49.048.702.143.224.085.42.218.59.4.182.18.32.377.416.59.085.2" "23.127.457.127.702v.335c0 .223-.032.43-.095.622a2.107 2.107 0 0 1-.32." "527c-.138.18-.292.319-.462.415-.17.106-.362.186-.575.24l.702.51c.234.1" "7.469.345.703.526Zm-8.281-4.228v2.425h6.494a.954.954 0 0 0 .4-.08.776." "776 0 0 0 .334-.223c.107-.106.186-.218.24-.335.053-.128.08-.266.08-.41" "5v-.32a.954.954 0 0 0-.08-.398 1.232 1.232 0 0 0-.224-.351 1.228 1.228" " 0 0 0-.35-.224.954.954 0 0 0-.4-.08h-6.494Zm24.67-.782c.106 0 .202.03" "7.287.111a.37.37 0 0 1 .112.272.39.39 0 0 1-.112.287.425.425 0 0 1-.28" "7.112h-3.64v4.579a.37.37 0 0 1-.111.272.348.348 0 0 1-.271.127.397.397" " 0 0 1-.288-.127.37.37 0 0 1-.111-.272v-4.579h-3.639a.37.37 0 0 1-.271" "-.111.39.39 0 0 1-.112-.287.37.37 0 0 1 .112-.272.37.37 0 0 1 .271-.11" "1h8.058Zm15.782-.048c.723 0 1.34.266 1.85.798.511.532.767 1.17.767 1.9" "15v2.68a.37.37 0 0 1-.112.272.397.397 0 0 1-.287.127.348.348 0 0 1-.27" "2-.127.348.348 0 0 1-.127-.272v-1.196h-7.532v1.196a.348.348 0 0 1-.128" ".272.348.348 0 0 1-.271.127.348.348 0 0 1-.271-.127.348.348 0 0 1-.128" "-.272v-2.68c0-.745.255-1.383.766-1.915.51-.532 1.128-.798 1.851-.798h3" ".894Zm-5.697 3.415h7.548v-.702c0-.532-.176-.984-.527-1.357-.362-.383-." "792-.574-1.292-.574H408.5c-.51 0-.942.191-1.293.574a1.875 1.875 0 0 0-" ".542 1.357v.702ZM297.898 204.5h4.16l1.792-5.152h9.408l1.824 5.152h4.44" "8l-8.704-23.2h-4.288l-8.64 23.2Zm10.624-18.496 3.52 9.952h-7.008l3.488" "-9.952Zm22.81 18.496h3.807v-17.216h-3.808v9.184c0 3.104-1.024 5.344-3." "872 5.344s-3.168-2.272-3.168-4.608v-9.92h-3.808v10.848c0 4.096 1.664 6" ".784 5.76 6.784 2.336 0 4.096-.992 5.088-2.784v2.368Zm7.678-17.216h-2." "56v2.752h2.56v9.952c0 3.52.736 4.512 4.416 4.512h2.816v-2.912h-1.376c-" "1.632 0-2.048-.416-2.048-2.176v-9.376h3.456v-2.752h-3.456v-4.544h-3.80" "8v4.544Zm13.179-5.984h-3.809v23.2h3.808v-9.152c0-3.104 1.088-5.344 4-5" ".344s3.264 2.272 3.264 4.608v9.888h3.808v-10.816c0-4.096-1.696-6.784-5" ".856-6.784-2.4 0-4.224.992-5.216 2.784V181.3Zm16.86 14.624c0-3.968 2.1" "44-5.92 4.544-5.92 2.4 0 4.544 1.952 4.544 5.92s-2.144 5.888-4.544 5.8" "88c-2.4 0-4.544-1.92-4.544-5.888Zm4.544-9.024c-4.192 0-8.48 2.816-8.48" " 9.024 0 6.208 4.288 8.992 8.48 8.992s8.48-2.784 8.48-8.992c0-6.208-4." "288-9.024-8.48-9.024Zm20.057.416a10.32 10.32 0 0 0-.992-.064c-2.08.032" "-3.744 1.184-4.672 3.104v-3.072h-3.744V204.5h3.808v-9.024c0-3.456 1.37" "6-4.416 3.776-4.416.576 0 1.184.032 1.824.096v-3.84Zm14.665 4.672c-.70" "4-3.456-3.776-5.088-7.136-5.088-3.744 0-7.008 1.952-7.008 4.992 0 3.13" "6 2.272 4.448 5.184 5.024l2.592.512c1.696.32 2.976.96 2.976 2.368s-1.4" "72 2.24-3.456 2.24c-2.24 0-3.52-1.024-3.872-2.784h-3.712c.416 3.264 3." "232 5.664 7.456 5.664 3.904 0 7.296-1.984 7.296-5.568 0-3.36-2.656-4.4" "48-6.144-5.12l-2.432-.48c-1.472-.288-2.304-.896-2.304-2.048 0-1.152 1." "536-1.888 3.2-1.888 1.92 0 3.36.608 3.776 2.176h3.584Zm6.284-10.688h-3" ".808v23.2h3.808v-9.152c0-3.104 1.088-5.344 4-5.344s3.264 2.272 3.264 4" ".608v9.888h3.808v-10.816c0-4.096-1.696-6.784-5.856-6.784-2.4 0-4.224.9" "92-5.216 2.784V181.3Zm14.076 0v3.84h3.808v-3.84h-3.808Zm0 5.984V204.5h" "3.808v-17.216h-3.808Zm10.781 8.608c0-3.968 1.952-5.888 4.448-5.888 2.6" "56 0 4.256 2.272 4.256 5.888 0 3.648-1.6 5.92-4.256 5.92-2.496 0-4.448" "-1.952-4.448-5.92Zm-3.648-8.608V210.1h3.808v-7.872c1.024 1.696 2.816 2" ".688 5.12 2.688 4.192 0 7.392-3.488 7.392-9.024 0-5.504-3.2-8.992-7.39" '2-8.992-2.304 0-4.096.992-5.12 2.688v-2.304h-3.808Z" fill="#F0F6FC"/><' 'path class="k" stroke-dashoffset="5" stroke-dasharray="10" d="M215 545' 'h320"/><g transform="translate(231 237) scale(0.384)">', shieldsAPI.getShieldSVG({ field: uint16(seed % 300), colors: [ uint24(colors & 0xFFFFFF), uint24((colors >> 24) & 0xFFFFFF), uint24((colors >> 48) & 0xFFFFFF), uint24((colors >> 72) & 0xFFFFFF) ], hardware: uint16((seed >> 9) % 120), frame: uint16((seed >> 17) % 5) }), '</g><text font-family="A" x="50%" y="605" fill="#F0F6FC" font-size="40' '" dominant-baseline="central" text-anchor="middle">#', _zfill(_tokenId), '</text><rect class="i k o" x="215" y="65" mask="url(#a)" rx="20"/><cir' 'cle class="j k o" cy="65" mask="url(#a)"/><circle class="j k o" cy="68' '5" mask="url(#a)"/></svg>' ) ), '","attributes":[{"trait_type":"Used","value":', ICurta(curta).hasUsedAuthorshipToken(_tokenId) ? "true" : "false", "}]}" ) ) ); } // ------------------------------------------------------------------------- // Helper Functions // ------------------------------------------------------------------------- /// @notice Converts `_value` to a string with leading zeros to reach a /// minimum of 7 characters. /// @param _value Number to convert. /// @return string memory The string representation of `_value` with leading /// zeros. function _zfill(uint256 _value) internal pure returns (string memory) { string memory result = _value.toString(); if (_value < 10) return string.concat("000000", result); else if (_value < 100) return string.concat("00000", result); else if (_value < 1000) return string.concat("0000", result); else if (_value < 10_000) return string.concat("000", result); else if (_value < 100_000) return string.concat("00", result); else if (_value < 1_000_000) return string.concat("0", result); return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import { LibString } from "solady/utils/LibString.sol"; import { ICurta } from "@/contracts/interfaces/ICurta.sol"; import { IColormapRegistry } from "@/contracts/interfaces/IColormapRegistry.sol"; import { Base64 } from "@/contracts/utils/Base64.sol"; /// @title Curta Flag Renderer /// @author fiveoutofnine /// @notice A contract that renders the JSON and SVG for a Flag token. contract FlagRenderer { using LibString for uint256; using LibString for address; using LibString for string; // ------------------------------------------------------------------------- // Constants // ------------------------------------------------------------------------- /// @notice The colormap registry. IColormapRegistry constant colormapRegistry = IColormapRegistry(0x0000000012883D1da628e31c0FE52e35DcF95D50); /// @notice Render the JSON and SVG for a Flag token. /// @param _puzzleData The puzzle data. /// @param _tokenId The token ID. /// @param _author The author of the puzzle. /// @param _solveTime The time it took to solve the puzzle. /// @param _solveMetadata The metadata associated with the solve. /// @param _phase The phase of the puzzle. /// @param _solves The number of solves the puzzle has. /// @param _colors The colors of the Flag. /// @return string memory The JSON and SVG for the Flag token. function render( ICurta.PuzzleData memory _puzzleData, uint256 _tokenId, address _author, uint40 _solveTime, uint56 _solveMetadata, uint8 _phase, uint32 _solves, uint120 _colors ) external view returns (string memory) { // Generate the puzzle's attributes. string memory attributes; { attributes = string.concat( '[{"trait_type":"Puzzle","value":"', _puzzleData.puzzle.name(), '"},{"trait_type":"Puzzle ID","value":', uint256(_tokenId >> 128).toString(), '},{"trait_type":"Author","value":"', _author.toHexStringChecksumed(), '"},{"trait_type":"Phase","value":"', uint256(_phase).toString(), '"},{"trait_type":"Solver","value":"', _formatValueAsAddress(uint256(_solveMetadata & 0xFFFFFFF)), '"},{"trait_type":"Solve time","value":', uint256(_solveTime).toString(), '},{"trait_type":"Rank","value":', uint256(uint128(_tokenId)).toString(), "}]" ); } // Generate the puzzle's SVG. string memory image; { image = string.concat( '<svg xmlns="http://www.w3.org/2000/svg" width="550" height="550" viewBox="0 0 550 ' '550"><style>@font-face{font-family:A;src:url(data:font/woff2;charset-utf-8;base64,' "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)}@font-face{font-family:B;src:u" "rl(data:font/woff2;charset-utf-8;base64,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" "z+3axYAg==)}text,tspan{dominant-baseline:central}.a{font-family:A;letter-spacing:-" ".05em}.b{font-family:B}.c{font-size:16px}.d{font-size:12px}.f{fill:#", uint256((_colors >> 72) & 0xFFFFFF).toHexStringNoPrefix(3), // Fill "}.h{fill:#", uint256((_colors >> 24) & 0xFFFFFF).toHexStringNoPrefix(3), // Primary text "}.i{fill:#", uint256(_colors & 0xFFFFFF).toHexStringNoPrefix(3), // Secondary text "}.j{fill:none;stroke-linejoin:round;stroke-linecap:round;stroke:#", uint256(_colors & 0xFFFFFF).toHexStringNoPrefix(3) // Secondary text ); } { image = string.concat( image, '}.x{width:1px;height:1px}</style><mask id="m"><rect width="20" height="20" rx="0.3' '70370" fill="#FFF"/></mask><path d="M0 0h550v550H0z" style="fill:#', uint256((_colors >> 96) & 0xFFFFFF).toHexStringNoPrefix(3), // Background '"/><rect x="143" y="69" width="264" height="412" rx="8" fill="#', uint256((_colors >> 48) & 0xFFFFFF).toHexStringNoPrefix(3), // Border '"/><rect class="f" x="147" y="73" width="256" height="404" rx="4"/>', _drawStars(_phase) ); } { image = string.concat( image, '<text class="a h" x="163" y="101" font-size="20">Puzzle #', (_tokenId >> 128).toString(), '</text><text x="163" y="121"><tspan class="b d i">Created by </tspan><tspan class=' '"a d h">' ); } { uint256 luma = ((_colors >> 88) & 0xFF) + ((_colors >> 80) & 0xFF) + ((_colors >> 72) & 0xFF); image = string.concat( image, _formatValueAsAddress(uint160(_author) >> 132), // Authors '</tspan></text><rect x="163" y="137" width="224" height="224" fill="rgba(', luma < ((255 * 3) >> 1) ? "255,255,255" : "0,0,0", // Background behind the heatmap ',0.2)" rx="8"/>', _drawDrunkenBishop(_solveMetadata, _tokenId), '<path class="j" d="M176.988 387.483A4.992 4.992 0 0 0 173 385.5a4.992 4.992 0 0 0-' "3.988 1.983m7.975 0a6 6 0 1 0-7.975 0m7.975 0A5.977 5.977 0 0 1 173 389a5.977 5.97" '7 0 0 1-3.988-1.517M175 381.5a2 2 0 1 1-4 0 2 2 0 0 1 4 0z"/><text class="a c h" x' '="187" y="383">', _formatValueAsAddress(_solveMetadata >> 28), // Captured by '</text><text class="b d i" x="187" y="403">Captured by</text><path class="j" d="m2' "85.5 380 2 1.5-2 1.5m3 0h2m-6 5.5h9a1.5 1.5 0 0 0 1.5-1.5v-8a1.5 1.5 0 0 0-1.5-1.5" 'h-9a1.5 1.5 0 0 0-1.5 1.5v8a1.5 1.5 0 0 0 1.5 1.5z"/><text class="a c h" x="303" y' '="383">', _formatValueAsAddress(_solveMetadata & 0xFFFFFFF), // Solution '</text><text class="b d i" x="303" y="403">Solution</text><path class="j" d="M176 ' "437.5h-6m6 0a2 2 0 0 1 2 2h-10a2 2 0 0 1 2-2m6 0v-2.25a.75.75 0 0 0-.75-.75h-.58m-" "4.67 3v-2.25a.75.75 0 0 1 .75-.75h.581m3.338 0h-3.338m3.338 0a4.97 4.97 0 0 1-.654" "-2.115m-2.684 2.115a4.97 4.97 0 0 0 .654-2.115m-3.485-4.561c-.655.095-1.303.211-1." "944.347a4.002 4.002 0 0 0 3.597 3.314m-1.653-3.661V428a4.49 4.49 0 0 0 1.653 3.485" "m-1.653-3.661v-1.01a32.226 32.226 0 0 1 4.5-.314c1.527 0 3.03.107 4.5.313v1.011m-7" ".347 3.661a4.484 4.484 0 0 0 1.832.9m5.515-4.561V428a4.49 4.49 0 0 1-1.653 3.485m1" ".653-3.661a30.88 30.88 0 0 1 1.944.347 4.002 4.002 0 0 1-3.597 3.314m0 0a4.484 4.4" '84 0 0 1-1.832.9m0 0a4.515 4.515 0 0 1-2.03 0"/><text><tspan class="a c h" x="187"' ' y="433">' ); } { image = string.concat( image, uint256(uint128(_tokenId)).toString(), // Rank ' </tspan><tspan class="a d i" y="435">/ ', uint256(_solves).toString(), // Solvers '</tspan></text><text class="b d i" x="187" y="453">Rank</text><path class="j" d="M' '289 429v4h3m3 0a6 6 0 1 1-12 0 6 6 0 0 1 12 0z"/><text class="a c h" x="303" y="43' '3">', _formatTime(_solveTime), // Solve time '</text><text class="b d i" x="303" y="453">Solve time</text></svg>' ); } return string.concat( "data:application/json;base64,", Base64.encode( abi.encodePacked( '{"name":"', _puzzleData.puzzle.name(), ": Flag #", uint256(uint128(_tokenId)).toString(), '","description":"This token represents solve #', uint256(uint128(_tokenId)).toString(), " in puzzle #", uint256(_tokenId >> 128).toString(), '.","image_data": "data:image/svg+xml;base64,', Base64.encode(abi.encodePacked(image)), '","attributes":', attributes, "}" ) ) ); } /// @notice Returns the SVG component for the ``heatmap'' generated by /// applying the Drunken Bishop algorithm. /// @param _solveMetadata A bitpacked `uint56` containing metadata of the /// solver and solution. /// @param _tokenId The token ID of the Flag. /// @return string memory The SVG for the heatmap. function _drawDrunkenBishop(uint56 _solveMetadata, uint256 _tokenId) internal view returns (string memory) { uint256 seed = uint256(keccak256(abi.encodePacked(_tokenId, _solveMetadata))); // Select the colormap. bytes32 colormapHash = [ bytes32(0xfd29b65966772202ffdb08f653439b30c849f91409915665d99dbfa5e5dab938), bytes32(0x850ce48e7291439b1e41d21fc3f75dddd97580a4ff94aa9ebdd2bcbd423ea1e8), bytes32(0x4f5e8ea8862eff315c110b682ee070b459ba8983a7575c9a9c4c25007039109d), bytes32(0xf2e92189cb6903b98d854cd74ece6c3fafdb2d3472828a950633fdaa52e05032), bytes32(0xa33e6c7c5627ecabfd54c4d85f9bf04815fe89a91379fcf56ccd8177e086db21), bytes32(0xaa84b30df806b46f859a413cb036bc91466307aec5903fc4635c00a421f25d5c), bytes32(0x864a6ee98b9b21ac0291523750d637250405c24a6575e1f75cfbd7209a810ce6), bytes32(0xfd60cd3811f002814944a7d36167b7c9436187a389f2ee476dc883e37dc76bd2), bytes32(0xa8309447f8bd3b5e5e88a0abc05080b7682e4456c388b8636d45f5abb2ad2587), bytes32(0x3be719b0c342797212c4cb33fde865ed9cbe486eb67176265bc0869b54dee925), bytes32(0xca0da6b6309ed2117508207d68a59a18ccaf54ba9aa329f4f60a77481fcf2027), bytes32(0x5ccb29670bb9de0e3911d8e47bde627b0e3640e49c3d6a88d51ff699160dfbe1), bytes32(0x3de8f27f386dab3dbab473f3cc16870a717fe5692b4f6a45003d175c559dfcba), bytes32(0x026736ef8439ebcf8e7b8006bf8cb7482ced84d71b900407a9ed63e1b7bfe234), bytes32(0xc1806ea961848ac00c1f20aa0611529da522a7bd125a3036fe4641b07ee5c61c), bytes32(0x87970b686eb726750ec792d49da173387a567764d691294d764e53439359c436), bytes32(0xaa6277ab923279cf59d78b9b5b7fb5089c90802c353489571fca3c138056fb1b), bytes32(0xdc1cecffc00e2f3196daaf53c27e53e6052a86dc875adb91607824d62469b2bf) ][seed % 18]; // We start at the middle of the board. uint256 index = 210; uint256 max = 1; uint8[] memory counts = new uint8[](400); counts[index] = 1; // Apply Drunken Bishop algorithm. unchecked { while (seed != 0) { (uint256 x, uint256 y) = (index % 20, index / 20); assembly { // Read down/up switch and(shr(1, seed), 1) // Up case case 0 { index := add(index, mul(20, iszero(eq(y, 19)))) } // Down case default { index := sub(index, mul(20, iszero(eq(y, 0)))) } // Read left/right switch and(seed, 1) // Left case case 0 { index := add(index, iszero(eq(x, 19))) } // Right case default { index := sub(index, iszero(eq(y, 0))) } } if (++counts[index] > max) max = counts[index]; seed >>= 2; } } // Draw heatmap from counts. string memory image = '<g transform="translate(167 141) scale(10.8)" mask="url(#m)">'; unchecked { for (uint256 i; i < 400; ++i) { image = string.concat( image, '<rect class="x" x="', (i % 20).toString(), '" y="', (i / 20).toString(), '" fill="#', colormapRegistry.getValueAsHexString( colormapHash, uint8((uint256(counts[i]) * 255) / max) ), '"/>' ); } } return string.concat(image, "</g>"); } /// @notice Returns the SVG component for the stars corresponding to the /// phase, including the background pill. /// @dev Phase 0 = 3 stars; Phase 1 = 2 stars; Phase 2 = 1 star. Also, note /// that the SVGs are returned positioned relative to the whole SVG for the /// Flag. /// @param _phase The phase of the solve. /// @return string memory The SVG for the stars. function _drawStars(uint8 _phase) internal pure returns (string memory) { // This will never underflow because `_phase` is always in the range // [0, 4]. unchecked { uint256 width = ((4 - _phase) << 4); return string.concat( '<rect class="h" x="', (383 - width).toString(), '" y="97" width="', width.toString(), '" height="24" rx="12"/><path id="s" d="M366.192 103.14c.299-.718 1.317-.718 1.616 ' "0l1.388 3.338 3.603.289c.776.062 1.09 1.03.499 1.536l-2.745 2.352.838 3.515c.181.7" "57-.642 1.355-1.306.95L367 113.236l-3.085 1.884c-.664.405-1.487-.193-1.306-.95l.83" '8-3.515-2.745-2.352c-.591-.506-.277-1.474.5-1.536l3.602-.289 1.388-3.337z"/>', _phase < 2 ? '<use href="#s" x="-16" />' : "", _phase < 1 ? '<use href="#s" x="-32" />' : "" ); } } /// @notice Helper function to format the last 28 bits of a value as a /// hexstring of length 7. If the value is less than 24 bits, it is padded /// with leading zeros. /// @param _value The value to format. /// @return string memory The formatted string. function _formatValueAsAddress(uint256 _value) internal pure returns (string memory) { return string.concat( string(abi.encodePacked(bytes32("0123456789ABCDEF")[(_value >> 24) & 0xF])), (_value & 0xFFFFFF).toHexStringNoPrefix(3).toCase(true) ); } /// @notice Helper function to format seconds into a length string. In order /// to fit the solve time in the card, we format it as follows: /// * 0:00:00 to 95:59:59 /// * 96 hours to 983 hours /// * 41 days to 729 days /// * 2 years onwards /// @param _solveTime The solve time in seconds. /// @return string memory The formatted string. function _formatTime(uint40 _solveTime) internal pure returns (string memory) { if (_solveTime < 96 hours) { uint256 numHours = _solveTime / (1 hours); uint256 numMinutes = (_solveTime % (1 hours)) / (1 minutes); uint256 numSeconds = _solveTime % (1 minutes); return string.concat( _zeroPadOne(numHours), ":", _zeroPadOne(numMinutes), ":", _zeroPadOne(numSeconds) ); } else if (_solveTime < 41 days) { return string.concat(uint256(_solveTime / (1 hours)).toString(), " hours"); } else if (_solveTime < 730 days) { return string.concat(uint256(_solveTime / (1 days)).toString(), " days"); } return string.concat(uint256(_solveTime / (365 days)).toString(), " years"); } /// @notice Helper function to zero pad a number by 1 if it is less than 10. /// @param _value The number to zero pad. /// @return string memory The zero padded string. function _zeroPadOne(uint256 _value) internal pure returns (string memory) { if (_value < 10) { return string.concat("0", _value.toString()); } return _value.toString(); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import { ERC721TokenReceiver } from "solmate/tokens/ERC721.sol"; /// @title The Flags ERC-721 token contract /// @author fiveoutofnine /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) /// @notice A ``Flag'' is an NFT minted to a player when they successfuly solve /// a puzzle. /// @dev The NFT with token ID 0 is reserved to denote ``Fermat''—the author's /// whose puzzle went the longest unsolved. abstract contract FlagsERC721 { // ------------------------------------------------------------------------- // Events // ------------------------------------------------------------------------- event Approval(address indexed owner, address indexed spender, uint256 indexed id); event ApprovalForAll(address indexed owner, address indexed operator, bool approved); event Transfer(address indexed from, address indexed to, uint256 indexed id); // ------------------------------------------------------------------------- // Structs // ------------------------------------------------------------------------- /// @param owner The owner of the token. /// @param solveTimestamp The timestamp of when the token was solved/minted. /// @param solveMetadata A bitpacked `uint56` containing the following /// information: /// * The first 28 bits are the first 28 bits of the solver. /// * The last 28 bits are the last 28 bits of the solution. struct TokenData { address owner; uint40 solveTimestamp; uint56 solveMetadata; } /// @param phase0Solves The number of puzzles someone solved during Phase 0. /// @param phase1Solves The number of puzzles someone solved during Phase 1. /// @param phase2Solves The number of puzzles someone solved during Phase 2. /// @param solves The total number of solves someone has. /// @param balance The number of tokens someone owns. struct UserBalance { uint32 phase0Solves; uint32 phase1Solves; uint32 phase2Solves; uint32 solves; uint32 balance; } // ------------------------------------------------------------------------- // ERC721Metadata Storage // ------------------------------------------------------------------------- /// @notice The name of the contract. string public name; /// @notice An abbreviated name for the contract. string public symbol; // ------------------------------------------------------------------------- // ERC721 Storage (+ Custom) // ------------------------------------------------------------------------- mapping(uint256 => address) public getApproved; mapping(address => mapping(address => bool)) public isApprovedForAll; mapping(uint256 => TokenData) public getTokenData; mapping(address => UserBalance) public getUserBalances; // ------------------------------------------------------------------------- // Constructor + Functions // ------------------------------------------------------------------------- /// @param _name The name of the contract. /// @param _symbol An abbreviated name for the contract. constructor(string memory _name, string memory _symbol) { name = _name; symbol = _symbol; } /// @notice Mints a Flag token to `_to`. /// @dev This function is only called by {Curta}, so it makes a few /// assumptions. For example, the ID of the token is always in the form /// `(puzzleId << 128) + zeroIndexedSolveRanking`. /// @param _to The address to mint the token to. /// @param _id The ID of the token. /// @param _solveMetadata The metadata for the solve (see /// {FlagsERC721.TokenData}). /// @param _phase The phase the token was solved in. function _mint(address _to, uint256 _id, uint56 _solveMetadata, uint8 _phase) internal { // We do not check whether the `_to` is `address(0)` or that the token // was previously minted because {Curta} ensures these conditions are // never true. unchecked { ++getUserBalances[_to].balance; // `_mint` is only called when a puzzle is solved, so we can safely // increment the solve count. ++getUserBalances[_to].solves; // Same logic as the previous comment here. if (_phase == 0) ++getUserBalances[_to].phase0Solves; else if (_phase == 1) ++getUserBalances[_to].phase1Solves; else ++getUserBalances[_to].phase2Solves; } getTokenData[_id] = TokenData({ owner: _to, solveMetadata: _solveMetadata, solveTimestamp: uint40(block.timestamp) }); // Emit event. emit Transfer(address(0), _to, _id); } // ------------------------------------------------------------------------- // ERC721 // ------------------------------------------------------------------------- function ownerOf(uint256 _id) public view returns (address owner) { require((owner = getTokenData[_id].owner) != address(0), "NOT_MINTED"); } function balanceOf(address _owner) external view returns (uint256) { require(_owner != address(0), "ZERO_ADDRESS"); return getUserBalances[_owner].balance; } function approve(address _spender, uint256 _id) external { address owner = getTokenData[_id].owner; // Revert if the sender is not the owner, or the owner has not approved // the sender to operate the token. require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED"); // Set the spender as approved for the token. getApproved[_id] = _spender; // Emit event. emit Approval(owner, _spender, _id); } function setApprovalForAll(address _operator, bool _approved) external { // Set the operator as approved for the sender. isApprovedForAll[msg.sender][_operator] = _approved; // Emit event. emit ApprovalForAll(msg.sender, _operator, _approved); } function transferFrom(address _from, address _to, uint256 _id) public virtual { // Revert if the token is not being transferred from the current owner. require(_from == getTokenData[_id].owner, "WRONG_FROM"); // Revert if the recipient is the zero address. require(_to != address(0), "INVALID_RECIPIENT"); // Revert if the sender is not the owner, or the owner has not approved // the sender to operate the token. require( msg.sender == _from || isApprovedForAll[_from][msg.sender] || msg.sender == getApproved[_id], "NOT_AUTHORIZED" ); // Update balances. unchecked { // Will never underflow because of the token ownership check above. getUserBalances[_from].balance--; getUserBalances[_to].balance++; } // Set new owner. getTokenData[_id].owner = _to; // Clear previous approval data for the token. delete getApproved[_id]; // Emit event. emit Transfer(_from, _to, _id); } function safeTransferFrom(address _from, address _to, uint256 _id) external { transferFrom(_from, _to, _id); require( _to.code.length == 0 || ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _id, "") == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } function safeTransferFrom(address _from, address _to, uint256 _id, bytes calldata _data) external { transferFrom(_from, _to, _id); require( _to.code.length == 0 || ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _id, _data) == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } // ------------------------------------------------------------------------- // ERC721Metadata // ------------------------------------------------------------------------- /// @notice A distinct Uniform Resource Identifier (URI) for a given asset. /// @param _tokenId The token ID. /// @return URI for the token. function tokenURI(uint256 _tokenId) external view virtual returns (string memory); // ------------------------------------------------------------------------- // ERC165 // ------------------------------------------------------------------------- function supportsInterface(bytes4 _interfaceId) external pure returns (bool) { return _interfaceId == 0x01FFC9A7 // ERC165 Interface ID for ERC165 || _interfaceId == 0x80AC58CD // ERC165 Interface ID for ERC721 || _interfaceId == 0x5B5E139F; // ERC165 Interface ID for ERC721Metadata } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import { IPaletteGenerator } from "@/contracts/interfaces/IPaletteGenerator.sol"; /// @title The interface for the colormap registry. /// @author fiveoutofnine /// @dev A colormap may be defined in 2 ways: (1) via segment data and (2) via a /// ``palette generator.'' /// 1. via segment data /// 2. or via a palette generator ({IPaletteGenerator}). /// Segment data contains 1 `uint256` each for red, green, and blue describing /// their intensity values along the colormap. Each `uint256` contains 24-bit /// words bitpacked together with the following structure (bits are /// right-indexed): /// | Bits | Meaning | /// | --------- | ---------------------------------------------------- | /// | `23 - 16` | Position in the colormap the segment begins from | /// | `15 - 08` | Intensity of R, G, or B the previous segment ends at | /// | `07 - 00` | Intensity of R, G, or B the next segment starts at | /// Given some position, the output will be computed via linear interpolations /// on the segment data for R, G, and B. A maximum of 10 of these segments fit /// within 256 bits, so up to 9 segments can be defined. If you need more /// granularity or a nonlinear palette function, you may implement /// {IPaletteGenerator} and define a colormap with that. interface IColormapRegistry { // ------------------------------------------------------------------------- // Errors // ------------------------------------------------------------------------- /// @notice Emitted when a colormap already exists. /// @param _colormapHash Hash of the colormap's definition. error ColormapAlreadyExists(bytes32 _colormapHash); /// @notice Emitted when a colormap does not exist. /// @param _colormapHash Hash of the colormap's definition. error ColormapDoesNotExist(bytes32 _colormapHash); /// @notice Emitted when a segment data used to define a colormap does not /// follow the representation outlined in {IColormapRegistry}. /// @param _segmentData Segment data for 1 of R, G, or B. See /// {IColormapRegistry} for its representation. error SegmentDataInvalid(uint256 _segmentData); // ------------------------------------------------------------------------- // Structs // ------------------------------------------------------------------------- /// @notice Segment data that defines a colormap when read via piece-wise /// linear interpolation. /// @dev Each param contains 24-bit words, so each one may contain at most /// 9 (24*10 - 1) segments. See {IColormapRegistry} for how the segment data /// should be structured. /// @param r Segment data for red's color value along the colormap. /// @param g Segment data for green's color value along the colormap. /// @param b Segment data for blue's color value along the colormap. struct SegmentData { uint256 r; uint256 g; uint256 b; } // ------------------------------------------------------------------------- // Events // ------------------------------------------------------------------------- /// @notice Emitted when a colormap is registered via a palette generator /// function. /// @param _hash Hash of `_paletteGenerator`. /// @param _paletteGenerator Instance of {IPaletteGenerator} for the /// colormap. event RegisterColormap(bytes32 _hash, IPaletteGenerator _paletteGenerator); /// @notice Emitted when a colormap is registered via segment data. /// @param _hash Hash of `_segmentData`. /// @param _segmentData Segment data defining the colormap. event RegisterColormap(bytes32 _hash, SegmentData _segmentData); // ------------------------------------------------------------------------- // Storage // ------------------------------------------------------------------------- /// @param _colormapHash Hash of the colormap's definition (segment data). /// @return uint256 Segment data for red's color value along the colormap. /// @return uint256 Segment data for green's color value along the colormap. /// @return uint256 Segment data for blue's color value along the colormap. function segments(bytes32 _colormapHash) external view returns (uint256, uint256, uint256); /// @param _colormapHash Hash of the colormap's definition (palette /// generator). /// @return IPaletteGenerator Instance of {IPaletteGenerator} for the /// colormap. function paletteGenerators(bytes32 _colormapHash) external view returns (IPaletteGenerator); // ------------------------------------------------------------------------- // Actions // ------------------------------------------------------------------------- /// @notice Register a colormap with a palette generator. /// @param _paletteGenerator Instance of {IPaletteGenerator} for the /// colormap. function register(IPaletteGenerator _paletteGenerator) external; /// @notice Register a colormap with segment data that will be read via /// piece-wise linear interpolation. /// @dev See {IColormapRegistry} for how the segment data should be /// structured. /// @param _segmentData Segment data defining the colormap. function register(SegmentData memory _segmentData) external; // ------------------------------------------------------------------------- // View // ------------------------------------------------------------------------- /// @notice Get the red, green, and blue color values of a color in a /// colormap at some position. /// @dev Each color value will be returned as a 18 decimal fixed-point /// number in [0, 1]. Note that the function *will not* revert if /// `_position` is an invalid input (i.e. greater than 1e18). This /// responsibility is left to the implementation of {IPaletteGenerator}s. /// @param _colormapHash Hash of the colormap's definition. /// @param _position 18 decimal fixed-point number in [0, 1] representing /// the position in the colormap (i.e. 0 being min, and 1 being max). /// @return uint256 Intensity of red in that color at the position /// `_position`. /// @return uint256 Intensity of green in that color at the position /// `_position`. /// @return uint256 Intensity of blue in that color at the position /// `_position`. function getValue(bytes32 _colormapHash, uint256 _position) external view returns (uint256, uint256, uint256); /// @notice Get the red, green, and blue color values of a color in a /// colormap at some position. /// @dev Each color value will be returned as a `uint8` number in [0, 255]. /// @param _colormapHash Hash of the colormap's definition. /// @param _position Position in the colormap (i.e. 0 being min, and 255 /// being max). /// @return uint8 Intensity of red in that color at the position /// `_position`. /// @return uint8 Intensity of green in that color at the position /// `_position`. /// @return uint8 Intensity of blue in that color at the position /// `_position`. function getValueAsUint8(bytes32 _colormapHash, uint8 _position) external view returns (uint8, uint8, uint8); /// @notice Get the hexstring for a color in a colormap at some position. /// @param _colormapHash Hash of the colormap's definition. /// @param _position Position in the colormap (i.e. 0 being min, and 255 /// being max). /// @return string Hexstring excluding ``#'' (e.g. `007CFF`) of the color /// at the position `_position`. function getValueAsHexString(bytes32 _colormapHash, uint8 _position) external view returns (string memory); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import { IPuzzle } from "./IPuzzle.sol"; import { AuthorshipToken } from "@/contracts/AuthorshipToken.sol"; import { FlagRenderer } from "@/contracts/FlagRenderer.sol"; /// @title The interface for Curta /// @notice A CTF protocol, where players create and solve EVM puzzles to earn /// NFTs. /// @dev Each solve is represented by an NFT. However, the NFT with token ID 0 /// is reserved to denote ``Fermat''—the author's whose puzzle went the longest /// unsolved. interface ICurta { // ------------------------------------------------------------------------- // Errors // ------------------------------------------------------------------------- /// @notice Emitted when an Authorship Token has already been used to add a /// puzzle to Curta. /// @param _tokenId The ID of an Authorship Token. error AuthorshipTokenAlreadyUsed(uint256 _tokenId); /// @notice Emitted when a puzzle's solution is incorrect. error IncorrectSolution(); /// @notice Emitted when insufficient funds are sent during "Phase 2" /// submissions. error InsufficientFunds(); /// @notice Emitted when a puzzle is already marked as Fermat. /// @param _puzzleId The ID of a puzzle. error PuzzleAlreadyFermat(uint32 _puzzleId); /// @notice Emitted when a solver has already solved a puzzle. /// @param _puzzleId The ID of a puzzle. error PuzzleAlreadySolved(uint32 _puzzleId); /// @notice Emitted when a puzzle does not exist. /// @param _puzzleId The ID of a puzzle. error PuzzleDoesNotExist(uint32 _puzzleId); /// @notice Emitted when the puzzle was not the one that went longest /// unsolved. /// @param _puzzleId The ID of a puzzle. error PuzzleNotFermat(uint32 _puzzleId); /// @notice Emitted when a puzzle has not been solved yet. /// @param _puzzleId The ID of a puzzle. error PuzzleNotSolved(uint32 _puzzleId); /// @notice Emitted when submissions for a puzzle is closed. /// @param _puzzleId The ID of a puzzle. error SubmissionClosed(uint32 _puzzleId); /// @notice Emitted when `msg.sender` is not authorized. error Unauthorized(); // ------------------------------------------------------------------------- // Structs // ------------------------------------------------------------------------- /// @notice A struct containing data about the puzzle corresponding to /// Fermat (i.e. the puzzle that went the longest unsolved). /// @param puzzleId The ID of the puzzle. /// @param timeTaken The number of seconds it took to first solve the /// puzzle. struct Fermat { uint32 puzzleId; uint40 timeTaken; } /// @notice A struct containing data about a puzzle. /// @param puzzle The address of the puzzle. /// @param addedTimestamp The timestamp at which the puzzle was added. /// @param firstSolveTimestamp The timestamp at which the first valid /// solution was submitted. struct PuzzleData { IPuzzle puzzle; uint40 addedTimestamp; uint40 firstSolveTimestamp; } /// @notice A struct containing the number of solves a puzzle has. /// @param colors A bitpacked `uint120` of 5 24-bit colors for the puzzle's /// Flags in the following order (left-to-right): /// * Background color /// * Fill color /// * Border color /// * Primary text color /// * Secondary text color /// @param phase0Solves The total number of Phase 0 solves a puzzle has. /// @param phase1Solves The total number of Phase 1 solves a puzzle has. /// @param phase2Solves The total number of Phase 2 solves a puzzle has. /// @param solves The total number of solves a puzzle has. struct PuzzleColorsAndSolves { uint120 colors; uint32 phase0Solves; uint32 phase1Solves; uint32 phase2Solves; uint32 solves; } // ------------------------------------------------------------------------- // Events // ------------------------------------------------------------------------- /// @notice Emitted when a puzzle is added. /// @param id The ID of the puzzle. /// @param author The address of the puzzle author. /// @param puzzle The address of the puzzle. event AddPuzzle(uint32 indexed id, address indexed author, IPuzzle puzzle); /// @notice Emitted when a puzzle is solved. /// @param id The ID of the puzzle. /// @param solver The address of the solver. /// @param solution The solution. /// @param phase The phase in which the puzzle was solved. event SolvePuzzle(uint32 indexed id, address indexed solver, uint256 solution, uint8 phase); /// @notice Emitted when a puzzle's colors are updated. /// @param id The ID of the puzzle. /// @param colors A bitpacked `uint120` of 5 24-bit colors for the puzzle's /// Flags. event UpdatePuzzleColors(uint32 indexed id, uint256 colors); // ------------------------------------------------------------------------- // Immutable Storage // ------------------------------------------------------------------------- /// @notice The Flag metadata and art renderer contract. function flagRenderer() external view returns (FlagRenderer); /// @return The Authorship Token contract. function authorshipToken() external view returns (AuthorshipToken); // ------------------------------------------------------------------------- // Storage // ------------------------------------------------------------------------- /// @return The total number of puzzles. function puzzleId() external view returns (uint32); /// @return puzzleId The ID of the puzzle corresponding to Fermat. /// @return timeTaken The number of seconds it took to solve the puzzle. function fermat() external view returns (uint32 puzzleId, uint40 timeTaken); /// @param _puzzleId The ID of a puzzle. /// @return colors A bitpacked `uint120` of 5 24-bit colors for the puzzle's /// Flags. /// @return phase0Solves The total number of Phase 0 solves a puzzle has. /// @return phase1Solves The total number of Phase 1 solves a puzzle has. /// @return phase2Solves The total number of Phase 2 solves a puzzle has. /// @return solves The total number of solves a puzzle has. function getPuzzleColorsAndSolves(uint32 _puzzleId) external view returns ( uint120 colors, uint32 phase0Solves, uint32 phase1Solves, uint32 phase2Solves, uint32 solves ); /// @param _puzzleId The ID of a puzzle. /// @return puzzle The address of the puzzle. /// @return addedTimestamp The timestamp at which the puzzle was added. /// @return firstSolveTimestamp The timestamp at which the first solution /// was submitted. function getPuzzle(uint32 _puzzleId) external view returns (IPuzzle puzzle, uint40 addedTimestamp, uint40 firstSolveTimestamp); /// @param _puzzleId The ID of a puzzle. /// @return The address of the puzzle author. function getPuzzleAuthor(uint32 _puzzleId) external view returns (address); /// @param _solver The address of a solver. /// @param _puzzleId The ID of a puzzle. /// @return Whether `_solver` has solved the puzzle of ID `_puzzleId`. function hasSolvedPuzzle(address _solver, uint32 _puzzleId) external view returns (bool); /// @param _tokenId The ID of an Authorship Token. /// @return Whether the Authorship Token of ID `_tokenId` has been used to /// add a puzzle. function hasUsedAuthorshipToken(uint256 _tokenId) external view returns (bool); // ------------------------------------------------------------------------- // Functions // ------------------------------------------------------------------------- /// @notice Mints a Flag NFT if the provided solution solves the puzzle. /// @param _puzzleId The ID of the puzzle. /// @param _solution The solution. function solve(uint32 _puzzleId, uint256 _solution) external payable; /// @notice Adds a puzzle to the contract. Note that an unused Authorship /// Token is required to add a puzzle (see {AuthorshipToken}). /// @param _puzzle The address of the puzzle. /// @param _id The ID of the Authorship Token to burn. function addPuzzle(IPuzzle _puzzle, uint256 _id) external; /// @notice Set the colors for a puzzle's Flags. /// @dev Only the author of the puzzle of ID `_puzzleId` may set its token /// renderer. /// @param _puzzleId The ID of the puzzle. /// @param _colors A bitpacked `uint120` of 5 24-bit colors for the puzzle's /// Flags. function setPuzzleColors(uint32 _puzzleId, uint120 _colors) external; /// @notice Burns and mints NFT #0 to the author of the puzzle of ID /// `_puzzleId` if it is the puzzle that went longest unsolved. /// @dev The puzzle of ID `_puzzleId` must have been solved at least once. /// @param _puzzleId The ID of the puzzle. function setFermat(uint32 _puzzleId) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /// @title The interface for a palette generator. /// @author fiveoutofnine /// @dev `IPaletteGenerator` contains generator functions for a color's red, /// green, and blue color values. Each of these functions is intended to take in /// a 18 decimal fixed-point number in [0, 1] representing the position in the /// colormap and return the corresponding 18 decimal fixed-point number in /// [0, 1] representing the value of each respective color. interface IPaletteGenerator { // ------------------------------------------------------------------------- // Errors // ------------------------------------------------------------------------- /// @notice Reverts if the position is not a valid input. /// @dev The position is not a valid input if it is greater than 1e18. /// @param _position Position in the colormap. error InvalidPosition(uint256 _position); // ------------------------------------------------------------------------- // Generators // ------------------------------------------------------------------------- /// @notice Computes the intensity of red of the palette at some position. /// @dev The function should revert if `_position` is not a valid input /// (i.e. greater than 1e18). Also, the return value for all inputs must be /// a 18 decimal. /// @param _position Position in the colormap. /// @return uint256 Intensity of red in that color at the position /// `_position`. function r(uint256 _position) external pure returns (uint256); /// @notice Computes the intensity of green of the palette at some position. /// @dev The function should revert if `_position` is not a valid input /// (i.e. greater than 1e18). Also, the return value for all inputs must be /// a 18 decimal. /// @param _position Position in the colormap. /// @return uint256 Intensity of green in that color at the position /// `_position`. function g(uint256 _position) external pure returns (uint256); /// @notice Computes the intensity of blue of the palette at some position. /// @dev The function should revert if `_position` is not a valid input /// (i.e. greater than 1e18). Also, the return value for all inputs must be /// a 18 decimal. /// @param _position Position in the colormap. /// @return uint256 Intensity of blue in that color at the position /// `_position`. function b(uint256 _position) external pure returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /// @title The interface for a puzzle on Curta /// @notice The goal of players is to view the source code of the puzzle (may /// range from just the bytecode to Solidity—whatever the author wishes to /// provide), interpret the code, solve it as if it was a regular puzzle, then /// verify the solution on-chain. /// @dev Since puzzles are on-chain, everyone can view everyone else's /// submissions. The generative aspect prevents front-running and allows for /// multiple winners: even if players view someone else's solution, they still /// have to figure out what the rules/constraints of the puzzle are and apply /// the solution to their respective starting position. interface IPuzzle { /// @notice Returns the puzzle's name. /// @return The puzzle's name. function name() external pure returns (string memory); /// @notice Generates the puzzle's starting position based on a seed. /// @dev The seed is intended to be `msg.sender` of some wrapper function or /// call. /// @param _seed The seed to use to generate the puzzle. /// @return The puzzle's starting position. function generate(address _seed) external returns (uint256); /// @notice Verifies that a solution is valid for the puzzle. /// @dev `_start` is intended to be an output from {IPuzzle-generate}. /// @param _start The puzzle's starting position. /// @param _solution The solution to the puzzle. /// @return Whether the solution is valid. function verify(uint256 _start, uint256 _solution) external returns (bool); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /// @title Base64 /// @author Brecht Devos - <[email protected]> /// @notice Provides a function for encoding some bytes in base64 library Base64 { string internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678" "9+/"; function encode(bytes memory data) internal pure returns (string memory) { if (data.length == 0) return ""; string memory table = TABLE; uint256 encodedLength = ((data.length + 2) / 3) << 2; string memory result = new string(encodedLength + 0x20); assembly { mstore(result, encodedLength) let tablePtr := add(table, 1) let dataPtr := data let endPtr := add(dataPtr, mload(data)) let resultPtr := add(result, 0x20) for { } lt(dataPtr, endPtr) { } { dataPtr := add(dataPtr, 3) let input := mload(dataPtr) mstore(resultPtr, shl(0xF8, mload(add(tablePtr, and(shr(0x12, input), 0x3F))))) resultPtr := add(resultPtr, 1) mstore(resultPtr, shl(0xF8, mload(add(tablePtr, and(shr(0xC, input), 0x3F))))) resultPtr := add(resultPtr, 1) mstore(resultPtr, shl(0xF8, mload(add(tablePtr, and(shr(6, input), 0x3F))))) resultPtr := add(resultPtr, 1) mstore(resultPtr, shl(0xF8, mload(add(tablePtr, and(input, 0x3F))))) resultPtr := add(resultPtr, 1) } switch mod(mload(data), 3) case 1 { mstore(sub(resultPtr, 2), shl(0xF0, 0x3D3D)) } case 2 { mstore(sub(resultPtr, 1), shl(0xF8, 0x3D)) } } return result; } }
{ "remappings": [ "@/contracts/=src/", "@/script/=script/", "@/test/=test/", "colormap-registry/=lib/colormap-registry/src/", "ds-test/=lib/forge-std/lib/ds-test/src/", "forge-std/=lib/forge-std/src/", "shields-api-contract/=lib/shields-api-contract/contracts/", "shields-api/=lib/shields-api-contract/contracts/", "solady/=lib/solady/src/", "solmate/=lib/solmate/src/" ], "optimizer": { "enabled": true, "runs": 875 }, "metadata": { "bytecodeHash": "none" }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "london", "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"contract AuthorshipToken","name":"_authorshipToken","type":"address"},{"internalType":"contract FlagRenderer","name":"_flagRenderer","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"AuthorshipTokenAlreadyUsed","type":"error"},{"inputs":[],"name":"IncorrectSolution","type":"error"},{"inputs":[],"name":"InsufficientFunds","type":"error"},{"inputs":[{"internalType":"uint32","name":"_puzzleId","type":"uint32"}],"name":"PuzzleAlreadyFermat","type":"error"},{"inputs":[{"internalType":"uint32","name":"_puzzleId","type":"uint32"}],"name":"PuzzleAlreadySolved","type":"error"},{"inputs":[{"internalType":"uint32","name":"_puzzleId","type":"uint32"}],"name":"PuzzleDoesNotExist","type":"error"},{"inputs":[{"internalType":"uint32","name":"_puzzleId","type":"uint32"}],"name":"PuzzleNotFermat","type":"error"},{"inputs":[{"internalType":"uint32","name":"_puzzleId","type":"uint32"}],"name":"PuzzleNotSolved","type":"error"},{"inputs":[{"internalType":"uint32","name":"_puzzleId","type":"uint32"}],"name":"SubmissionClosed","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"id","type":"uint32"},{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"contract IPuzzle","name":"puzzle","type":"address"}],"name":"AddPuzzle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","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":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"id","type":"uint32"},{"indexed":true,"internalType":"address","name":"solver","type":"address"},{"indexed":false,"internalType":"uint256","name":"solution","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"phase","type":"uint8"}],"name":"SolvePuzzle","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":"id","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"id","type":"uint32"},{"indexed":false,"internalType":"uint256","name":"colors","type":"uint256"}],"name":"UpdatePuzzleColors","type":"event"},{"inputs":[{"internalType":"contract IPuzzle","name":"_puzzle","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"addPuzzle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_id","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"authorshipToken","outputs":[{"internalType":"contract AuthorshipToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fermat","outputs":[{"internalType":"uint32","name":"puzzleId","type":"uint32"},{"internalType":"uint40","name":"timeTaken","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"flagRenderer","outputs":[{"internalType":"contract FlagRenderer","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"getPuzzle","outputs":[{"internalType":"contract IPuzzle","name":"puzzle","type":"address"},{"internalType":"uint40","name":"addedTimestamp","type":"uint40"},{"internalType":"uint40","name":"firstSolveTimestamp","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"getPuzzleAuthor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"getPuzzleColorsAndSolves","outputs":[{"internalType":"uint120","name":"colors","type":"uint120"},{"internalType":"uint32","name":"phase0Solves","type":"uint32"},{"internalType":"uint32","name":"phase1Solves","type":"uint32"},{"internalType":"uint32","name":"phase2Solves","type":"uint32"},{"internalType":"uint32","name":"solves","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"getTokenData","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint40","name":"solveTimestamp","type":"uint40"},{"internalType":"uint56","name":"solveMetadata","type":"uint56"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"getUserBalances","outputs":[{"internalType":"uint32","name":"phase0Solves","type":"uint32"},{"internalType":"uint32","name":"phase1Solves","type":"uint32"},{"internalType":"uint32","name":"phase2Solves","type":"uint32"},{"internalType":"uint32","name":"solves","type":"uint32"},{"internalType":"uint32","name":"balance","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint32","name":"","type":"uint32"}],"name":"hasSolvedPuzzle","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"hasUsedAuthorshipToken","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","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":[],"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":"_id","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"puzzleId","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_id","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":"_id","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":"uint32","name":"_puzzleId","type":"uint32"}],"name":"setFermat","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_puzzleId","type":"uint32"},{"internalType":"uint120","name":"_colors","type":"uint120"}],"name":"setPuzzleColors","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_puzzleId","type":"uint32"},{"internalType":"uint256","name":"_solution","type":"uint256"}],"name":"solve","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","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":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_id","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Contract Creation Code
60c06040526006805463ffffffff60a01b191690553480156200002157600080fd5b5060405162002da138038062002da183398101604081905262000044916200011f565b3360405180604001604052806005815260200164437572746160d81b8152506040518060400160405280600381526020016221aa2360e91b815250816000908162000090919062000203565b5060016200009f828262000203565b5050600680546001600160a01b0319166001600160a01b0384169081179091556040519091506000907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a3506001600160a01b039182166080521660a052620002cf565b6001600160a01b03811681146200011c57600080fd5b50565b600080604083850312156200013357600080fd5b8251620001408162000106565b6020840151909250620001538162000106565b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806200018957607f821691505b602082108103620001aa57634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620001fe57600081815260208120601f850160051c81016020861015620001d95750805b601f850160051c820191505b81811015620001fa57828155600101620001e5565b5050505b505050565b81516001600160401b038111156200021f576200021f6200015e565b620002378162000230845462000174565b84620001b0565b602080601f8311600181146200026f5760008415620002565750858301515b600019600386901b1c1916600185901b178555620001fa565b600085815260208120601f198616915b82811015620002a0578886015182559484019460019091019084016200027f565b5085821015620002bf5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a051612a976200030a600039600081816103350152611d1c0152600081816101e40152818161100101526113f40152612a976000f3fe6080604052600436106101cd5760003560e01c806376a50464116100f7578063b88d4fde11610095578063e985e9c511610064578063e985e9c5146107cf578063ea620cc71461080a578063f2fde38b1461082a578063f9aa8ff11461084a57600080fd5b8063b88d4fde1461073f578063bb16f8621461075f578063c87b56dd1461077f578063e4ff676d1461079f57600080fd5b806396f6326c116100d157806396f6326c14610535578063a22cb465146105f1578063abd3f61214610611578063b09afec1146106b457600080fd5b806376a50464146104b05780638da5cb5b1461050057806395d89b411461052057600080fd5b8063301cca8f1161016f5780634ddf9d811161013e5780634ddf9d81146103c557806354eed6ac146103e55780636352211e1461046257806370a082311461048257600080fd5b8063301cca8f1461032357806331468f061461035757806340854ed51461036a57806342842e0e146103a557600080fd5b8063081812fc116101ab578063081812fc14610275578063095ea7b3146102ab5780631756aaac146102cd57806323b872dd1461030357600080fd5b8063012583ab146101d257806301ffc9a71461022357806306fdde0314610253575b600080fd5b3480156101de57600080fd5b506102067f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561022f57600080fd5b5061024361023e3660046122a7565b610883565b604051901515815260200161021a565b34801561025f57600080fd5b50610268610920565b60405161021a91906122ef565b34801561028157600080fd5b50610206610290366004612322565b6002602052600090815260409020546001600160a01b031681565b3480156102b757600080fd5b506102cb6102c6366004612350565b6109ae565b005b3480156102d957600080fd5b506102066102e8366004612390565b600a602052600090815260409020546001600160a01b031681565b34801561030f57600080fd5b506102cb61031e3660046123ab565b610a95565b34801561032f57600080fd5b506102067f000000000000000000000000000000000000000000000000000000000000000081565b6102cb6103653660046123ec565b610cb2565b34801561037657600080fd5b50610243610385366004612408565b600b60209081526000928352604080842090915290825290205460ff1681565b3480156103b157600080fd5b506102cb6103c03660046123ab565b6112c0565b3480156103d157600080fd5b506102cb6103e0366004612350565b6113c5565b3480156103f157600080fd5b50610435610400366004612390565b6009602052600090815260409020546001600160a01b0381169064ffffffffff600160a01b8204811691600160c81b90041683565b604080516001600160a01b03909416845264ffffffffff928316602085015291169082015260600161021a565b34801561046e57600080fd5b5061020661047d366004612322565b61166b565b34801561048e57600080fd5b506104a261049d36600461243d565b6116c2565b60405190815260200161021a565b3480156104bc57600080fd5b506007546104de9063ffffffff811690640100000000900464ffffffffff1682565b6040805163ffffffff909316835264ffffffffff90911660208301520161021a565b34801561050c57600080fd5b50600654610206906001600160a01b031681565b34801561052c57600080fd5b50610268611743565b34801561054157600080fd5b506105ac610550366004612390565b6008602052600090815260409020546effffffffffffffffffffffffffffff81169063ffffffff6f010000000000000000000000000000008204811691600160981b8104821691600160b81b8204811691600160d81b90041685565b604080516effffffffffffffffffffffffffffff909616865263ffffffff9485166020870152928416928501929092528216606084015216608082015260a00161021a565b3480156105fd57600080fd5b506102cb61060c366004612468565b611750565b34801561061d57600080fd5b5061067d61062c36600461243d565b60056020526000908152604090205463ffffffff8082169164010000000081048216916801000000000000000082048116916c010000000000000000000000008104821691600160801b9091041685565b6040805163ffffffff968716815294861660208601529285169284019290925283166060830152909116608082015260a00161021a565b3480156106c057600080fd5b5061070b6106cf366004612322565b6004602052600090815260409020546001600160a01b03811690600160a01b810464ffffffffff1690600160c81b900466ffffffffffffff1683565b604080516001600160a01b03909416845264ffffffffff909216602084015266ffffffffffffff169082015260600161021a565b34801561074b57600080fd5b506102cb61075a3660046124a1565b6117bc565b34801561076b57600080fd5b506102cb61077a366004612390565b6118b1565b34801561078b57600080fd5b5061026861079a366004612322565b611bc9565b3480156107ab57600080fd5b506102436107ba366004612322565b600c6020526000908152604090205460ff1681565b3480156107db57600080fd5b506102436107ea366004612540565b600360209081526000928352604080842090915290825290205460ff1681565b34801561081657600080fd5b506102cb61082536600461256e565b611e81565b34801561083657600080fd5b506102cb61084536600461243d565b611f37565b34801561085657600080fd5b5060065461086e90600160a01b900463ffffffff1681565b60405163ffffffff909116815260200161021a565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000006001600160e01b0319831614806108e657507f80ac58cd000000000000000000000000000000000000000000000000000000006001600160e01b03198316145b8061091a57507f5b5e139f000000000000000000000000000000000000000000000000000000006001600160e01b03198316145b92915050565b6000805461092d906125ae565b80601f0160208091040260200160405190810160405280929190818152602001828054610959906125ae565b80156109a65780601f1061097b576101008083540402835291602001916109a6565b820191906000526020600020905b81548152906001019060200180831161098957829003601f168201915b505050505081565b6000818152600460205260409020546001600160a01b0316338114806109f757506001600160a01b038116600090815260036020908152604080832033845290915290205460ff165b610a395760405162461bcd60e51b815260206004820152600e60248201526d1393d517d055551213d49256915160921b60448201526064015b60405180910390fd5b60008281526002602052604080822080546001600160a01b0319166001600160a01b0387811691821790925591518593918516917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a4505050565b6000818152600460205260409020546001600160a01b03848116911614610afe5760405162461bcd60e51b815260206004820152600a60248201527f57524f4e475f46524f4d000000000000000000000000000000000000000000006044820152606401610a30565b6001600160a01b038216610b545760405162461bcd60e51b815260206004820152601160248201527f494e56414c49445f524543495049454e540000000000000000000000000000006044820152606401610a30565b336001600160a01b0384161480610b8e57506001600160a01b038316600090815260036020908152604080832033845290915290205460ff165b80610baf57506000818152600260205260409020546001600160a01b031633145b610bec5760405162461bcd60e51b815260206004820152600e60248201526d1393d517d055551213d49256915160921b6044820152606401610a30565b6001600160a01b038084166000818152600560209081526040808320805460001963ffffffff600160801b8084048216929092018116820263ffffffff60801b19938416179093559689168086528386208054898104851660010190941690980292909116919091179095558583526004825280832080546001600160a01b031990811687179091556002909252808320805490921690915551849392917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b336000908152600b6020908152604080832063ffffffff8616845290915290205460ff1615610d15576040517f36a1d8d400000000000000000000000000000000000000000000000000000000815263ffffffff83166004820152602401610a30565b63ffffffff8216600090815260096020908152604091829020825160608101845290546001600160a01b03811680835264ffffffffff600160a01b8304811694840194909452600160c81b9091049092169281019290925280610dac576040517f684773c300000000000000000000000000000000000000000000000000000000815263ffffffff85166004820152602401610a30565b60408201514262069780820181116202a30083018211016001018215150260ff8116600303610e0f576040517faa5ebdc700000000000000000000000000000000000000000000000000000000815263ffffffff88166004820152602401610a30565b6040517f2fa61cd80000000000000000000000000000000000000000000000000000000081523360048201526001600160a01b038516906341161b10908290632fa61cd8906024016020604051808303816000875af1158015610e76573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e9a91906125e8565b886040518363ffffffff1660e01b8152600401610ec1929190918252602082015260400190565b6020604051808303816000875af1158015610ee0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f049190612601565b610f3a576040517f62cebecf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8264ffffffffff1660000361105e5763ffffffff8781166000908152600960209081526040808320805464ffffffffff60c81b1916600160c81b64ffffffffff8916021790556008909152902080549091600f91610fac916f0100000000000000000000000000000090910416612634565b825463ffffffff9182166101009390930a92830291909202199091161790556040517f49edb4250000000000000000000000000000000000000000000000000000000081523360048201526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906349edb42590602401600060405180830381600087803b15801561104557600080fd5b505af1158015611059573d6000803e3d6000fd5b505050505b336000818152600b6020908152604080832063ffffffff8c811685529083528184208054600160ff1990911681179091556008909352922080547fff00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff8116600160d81b9182900485169384019094160292909217909155349161111e9160808b901b73ffffffff000000000000000000000000000000001617630fffffff8a16606883901c72fffffffffffffffffffffffffffffff0000000161785611fdd565b8160ff166001036111815763ffffffff888116600090815260086020526040902080547fffffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffff8116600160981b91829004841660010190931602919091179055611248565b8160ff166002036112485766470de4df8200008110156111cd576040517f356680b700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b63ffffffff888116600090815260086020526040902080547fffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffff8116600160b81b9182900484166001019093160291909117905560065461123d906001600160a01b0316662386f26fc10000612233565b662386f26fc0ffff19015b63ffffffff88166000908152600a6020526040902054611271906001600160a01b031682612233565b6040805188815260ff84166020820152339163ffffffff8b16917f79550049b42009b11b52956ed9da213919c1417e6980af5e79bf7fffbb28a533910160405180910390a35050505050505050565b6112cb838383610a95565b6001600160a01b0382163b15806113745750604051630a85bd0160e11b8082523360048301526001600160a01b03858116602484015260448301849052608060648401526000608484015290919084169063150b7a029060a4016020604051808303816000875af1158015611344573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113689190612657565b6001600160e01b031916145b6113c05760405162461bcd60e51b815260206004820152601060248201527f554e534146455f524543495049454e54000000000000000000000000000000006044820152606401610a30565b505050565b6040517f6352211e000000000000000000000000000000000000000000000000000000008152600481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690636352211e90602401602060405180830381865afa158015611443573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114679190612674565b6001600160a01b0316336001600160a01b031614611497576040516282b42960e81b815260040160405180910390fd5b6000818152600c602052604090205460ff16156114e3576040517fe75c31ed00000000000000000000000000000000000000000000000000000000815260048101829052602401610a30565b6000818152600c60209081526040808320805460ff191660019081179091556006805463ffffffff600160a01b80830482169094019081168085027fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff9093169290921790925583516060810185526001600160a01b03808a16825264ffffffffff4281168389019081528388018a8152858b5260098a52888b2094518554925191518416600160c81b0264ffffffffff60c81b19929094169098027fffffffffffffff000000000000000000000000000000000000000000000000009092169790931696909617959095171693909317909255600a845282852080546001600160a01b0319163390811790915560089094529382902080546effffffffffffffffffffffffffffff19166e181e28181e2827303df0f6fc94a3b317905590517ff769adab71d9ec16f2a65a83569ce900dfec45bc5f6d39d364d7059d631c95149061165e9087906001600160a01b0391909116815260200190565b60405180910390a3505050565b6000818152600460205260409020546001600160a01b0316806116bd5760405162461bcd60e51b815260206004820152600a6024820152691393d517d3525395115160b21b6044820152606401610a30565b919050565b60006001600160a01b03821661171a5760405162461bcd60e51b815260206004820152600c60248201527f5a45524f5f4144445245535300000000000000000000000000000000000000006044820152606401610a30565b506001600160a01b0316600090815260056020526040902054600160801b900463ffffffff1690565b6001805461092d906125ae565b3360008181526003602090815260408083206001600160a01b03871680855290835292819020805460ff191686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b6117c7858585610a95565b6001600160a01b0384163b158061185e5750604051630a85bd0160e11b808252906001600160a01b0386169063150b7a029061180f9033908a90899089908990600401612691565b6020604051808303816000875af115801561182e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118529190612657565b6001600160e01b031916145b6118aa5760405162461bcd60e51b815260206004820152601060248201527f554e534146455f524543495049454e54000000000000000000000000000000006044820152606401610a30565b5050505050565b63ffffffff81166000908152600960209081526040808320815160608101835290546001600160a01b038116825264ffffffffff600160a01b8204811694830194909452600160c81b9004909216908201819052909103611946576040517f71c4611b00000000000000000000000000000000000000000000000000000000815263ffffffff83166004820152602401610a30565b60075463ffffffff808416911603611992576040517ff31ab1ac00000000000000000000000000000000000000000000000000000000815263ffffffff83166004820152602401610a30565b602081015160408201516007549190039064ffffffffff640100000000909104811690821610156119f7576040517f20012d7300000000000000000000000000000000000000000000000000000000815263ffffffff84166004820152602401610a30565b6007805464ffffffffff9092166401000000000268ffffffffffffffffff1990921663ffffffff85169081179290921790556000908152600a60209081526040822054918052600490527f17ef568e3e12ab5b9c7254a8d58478811de00f9e6eb34345acd53bf8fd09d3ec546001600160a01b0391821691168015611b1c576001600160a01b0381166000818152600560209081526040808320805460001963ffffffff600160801b808404821692909201160263ffffffff60801b1990911617905582805260029091527fac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b80546001600160a01b031916905551909182917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45b6001600160a01b03821660008181526005602090815260408083208054600163ffffffff600160801b808404821692909201160263ffffffff60801b1990911617905582805260049091527f17ef568e3e12ab5b9c7254a8d58478811de00f9e6eb34345acd53bf8fd09d3ec80546001600160a01b031916841790555190919082907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a450505050565b60008181526004602090815260409182902082516060808201855291546001600160a01b038116808352600160a01b820464ffffffffff1694830194909452600160c81b900466ffffffffffffff16938101939093529190611c5a5760405162461bcd60e51b815260206004820152600a6024820152691393d517d3525395115160b21b6044820152606401610a30565b82600003611c8557604051806102e001604052806102bd81526020016127ce6102bd91399392505050565b608083901c63ffffffff8181166000818152600960209081526040808320815160608101835290546001600160a01b038082168352600160a01b820464ffffffffff908116848701908152600160c81b9093041683850152958552600a845282852054600885529290942054935192880151909591851694600160d81b8504909216936effffffffffffffffffffffffffffff16927f000000000000000000000000000000000000000000000000000000000000000090921691631fcc81ff9187918c918891611d54916126e5565b60408c810151908b015160208e015162069780820181116202a3008301909111016001019015150289896040518963ffffffff1660e01b8152600401611e3098979695949392919088516001600160a01b0390811682526020808b015164ffffffffff9081169184019190915260409a8b015181169a83019a909a5260608201989098529590961660808601529290951660a084015266ffffffffffffff1660c083015260ff9390931660e082015263ffffffff929092166101008301526effffffffffffffffffffffffffffff166101208201526101400190565b600060405180830381865afa158015611e4d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611e759190810190612720565b98975050505050505050565b63ffffffff82166000908152600a60205260409020546001600160a01b03163314611ebe576040516282b42960e81b815260040160405180910390fd5b63ffffffff821660008181526008602090815260409182902080546effffffffffffffffffffffffffffff19166effffffffffffffffffffffffffffff861690811790915591519182527f589aaff1c002925ad33e34d092a74d95c823ee866338f474c74a5bd51f511512910160405180910390a25050565b6006546001600160a01b03163314611f915760405162461bcd60e51b815260206004820152600c60248201527f554e415554484f52495a454400000000000000000000000000000000000000006044820152606401610a30565b600680546001600160a01b0319166001600160a01b03831690811790915560405133907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a350565b6001600160a01b038416600090815260056020526040812080546c01000000000000000000000000600163ffffffff600160801b8085048216830182160263ffffffff60801b19851681178490048216909201169091026fffffffff000000000000000000000000199091167fffffffffffffffffffffffff0000000000000000ffffffffffffffffffffffff9092169190911717905560ff821690036120b5576001600160a01b0384166000908152600560205260409020805463ffffffff8082166001011663ffffffff19909116179055612152565b8060ff16600103612107576001600160a01b03841660009081526005602052604090208054600163ffffffff640100000000808404821692909201160267ffffffff0000000019909116179055612152565b6001600160a01b03841660009081526005602052604090208054600163ffffffff6801000000000000000080840482169290920116026bffffffff0000000000000000199091161790555b604080516060810182526001600160a01b0380871680835264ffffffffff428116602080860191825266ffffffffffffff808a1687890190815260008c815260049093528883209751885494519151909216600160c81b0278ffffffffffffffffffffffffffffffffffffffffffffffffff91909516600160a01b027fffffffffffffff00000000000000000000000000000000000000000000000000909416919096161791909117939093161790925591518592907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a450505050565b600080600080600085875af19050806113c05760405162461bcd60e51b815260206004820152601360248201527f4554485f5452414e534645525f4641494c4544000000000000000000000000006044820152606401610a30565b6001600160e01b0319811681146122a457600080fd5b50565b6000602082840312156122b957600080fd5b81356122c48161228e565b9392505050565b60005b838110156122e65781810151838201526020016122ce565b50506000910152565b602081526000825180602084015261230e8160408501602087016122cb565b601f01601f19169190910160400192915050565b60006020828403121561233457600080fd5b5035919050565b6001600160a01b03811681146122a457600080fd5b6000806040838503121561236357600080fd5b823561236e8161233b565b946020939093013593505050565b803563ffffffff811681146116bd57600080fd5b6000602082840312156123a257600080fd5b6122c48261237c565b6000806000606084860312156123c057600080fd5b83356123cb8161233b565b925060208401356123db8161233b565b929592945050506040919091013590565b600080604083850312156123ff57600080fd5b61236e8361237c565b6000806040838503121561241b57600080fd5b82356124268161233b565b91506124346020840161237c565b90509250929050565b60006020828403121561244f57600080fd5b81356122c48161233b565b80151581146122a457600080fd5b6000806040838503121561247b57600080fd5b82356124868161233b565b915060208301356124968161245a565b809150509250929050565b6000806000806000608086880312156124b957600080fd5b85356124c48161233b565b945060208601356124d48161233b565b935060408601359250606086013567ffffffffffffffff808211156124f857600080fd5b818801915088601f83011261250c57600080fd5b81358181111561251b57600080fd5b89602082850101111561252d57600080fd5b9699959850939650602001949392505050565b6000806040838503121561255357600080fd5b823561255e8161233b565b915060208301356124968161233b565b6000806040838503121561258157600080fd5b61258a8361237c565b915060208301356effffffffffffffffffffffffffffff8116811461249657600080fd5b600181811c908216806125c257607f821691505b6020821081036125e257634e487b7160e01b600052602260045260246000fd5b50919050565b6000602082840312156125fa57600080fd5b5051919050565b60006020828403121561261357600080fd5b81516122c48161245a565b634e487b7160e01b600052601160045260246000fd5b600063ffffffff80831681810361264d5761264d61261e565b6001019392505050565b60006020828403121561266957600080fd5b81516122c48161228e565b60006020828403121561268657600080fd5b81516122c48161233b565b60006001600160a01b03808816835280871660208401525084604083015260806060830152826080830152828460a0840137600060a0848401015260a0601f19601f85011683010190509695505050505050565b64ffffffffff8281168282160390808211156127035761270361261e565b5092915050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561273257600080fd5b815167ffffffffffffffff8082111561274a57600080fd5b818401915084601f83011261275e57600080fd5b8151818111156127705761277061270a565b604051601f8201601f19908116603f011681019083821181831017156127985761279861270a565b816040528281528760208487010111156127b157600080fd5b6127c28360208301602088016122cb565b97965050505050505056fe646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c65794a755957316c496a6f69526d567962574630496977695a47567a59334a7063485270623234694f694a4d6232356e5a584e304948567563323973646d566b49484231656e70735a5334694c434a706257466e5a56396b59585268496a6f695a474630595470706257466e5a53397a646d6372654731734f324a68633255324e43785153453479576e6c434d324658556a426852444270546c525664306c70516d396156327875595568524f556c715654464e51306c6e5a47317362475177536e5a6c5244427054554e4264306c455654464e51304578546c524261556c48576e42695233633553573031646d4a7456576c4a53476830596b6331656c4254536d396b53464a3354326b34646d517a5a444e4d626d4e3654473035655670354f486c4e5245463354444e4f4d6c70355353745153454a6f5a45646e5a31707462484e6952444270535870464e45315656586c5051306c6e576b5177615652555157644e5232637a546c52434d6b3536565864545245493253576b344b314249536d785a4d31466e5a555177615531555558704a6155493155464e4a4d6b39545357646b4d6d78725a45646e4f556c7153544a4f51306c6e5955645763466f796144425155306b775456524a61556c49536a525155306b3053576c436257465865484e5155306c715457706a656b3145546b564a615467725545684b62466b7a5557646c52444270545652524d306c70516a565155306b7a54586c4a5a324d7a556e6c694d6e527355464e4a616b31555158684e656b5a4553576c434d324658556a426852444270545770564d6b6c70516d396156327875595568524f556c715558644f51306c6e5932356e4f556c7155576c4a52317077596b64334f556c7054586461524556335456526a615578364e44684d4d303479576e6f3050534a39a164736f6c6343000811000a000000000000000000000000c0ffeee59157d2dd0ee70d4fababca349b3192030000000000000000000000000000000000000000000000000000000000000000
Deployed Bytecode
0x6080604052600436106101cd5760003560e01c806376a50464116100f7578063b88d4fde11610095578063e985e9c511610064578063e985e9c5146107cf578063ea620cc71461080a578063f2fde38b1461082a578063f9aa8ff11461084a57600080fd5b8063b88d4fde1461073f578063bb16f8621461075f578063c87b56dd1461077f578063e4ff676d1461079f57600080fd5b806396f6326c116100d157806396f6326c14610535578063a22cb465146105f1578063abd3f61214610611578063b09afec1146106b457600080fd5b806376a50464146104b05780638da5cb5b1461050057806395d89b411461052057600080fd5b8063301cca8f1161016f5780634ddf9d811161013e5780634ddf9d81146103c557806354eed6ac146103e55780636352211e1461046257806370a082311461048257600080fd5b8063301cca8f1461032357806331468f061461035757806340854ed51461036a57806342842e0e146103a557600080fd5b8063081812fc116101ab578063081812fc14610275578063095ea7b3146102ab5780631756aaac146102cd57806323b872dd1461030357600080fd5b8063012583ab146101d257806301ffc9a71461022357806306fdde0314610253575b600080fd5b3480156101de57600080fd5b506102067f000000000000000000000000c0ffeee59157d2dd0ee70d4fababca349b31920381565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561022f57600080fd5b5061024361023e3660046122a7565b610883565b604051901515815260200161021a565b34801561025f57600080fd5b50610268610920565b60405161021a91906122ef565b34801561028157600080fd5b50610206610290366004612322565b6002602052600090815260409020546001600160a01b031681565b3480156102b757600080fd5b506102cb6102c6366004612350565b6109ae565b005b3480156102d957600080fd5b506102066102e8366004612390565b600a602052600090815260409020546001600160a01b031681565b34801561030f57600080fd5b506102cb61031e3660046123ab565b610a95565b34801561032f57600080fd5b506102067f000000000000000000000000000000000000000000000000000000000000000081565b6102cb6103653660046123ec565b610cb2565b34801561037657600080fd5b50610243610385366004612408565b600b60209081526000928352604080842090915290825290205460ff1681565b3480156103b157600080fd5b506102cb6103c03660046123ab565b6112c0565b3480156103d157600080fd5b506102cb6103e0366004612350565b6113c5565b3480156103f157600080fd5b50610435610400366004612390565b6009602052600090815260409020546001600160a01b0381169064ffffffffff600160a01b8204811691600160c81b90041683565b604080516001600160a01b03909416845264ffffffffff928316602085015291169082015260600161021a565b34801561046e57600080fd5b5061020661047d366004612322565b61166b565b34801561048e57600080fd5b506104a261049d36600461243d565b6116c2565b60405190815260200161021a565b3480156104bc57600080fd5b506007546104de9063ffffffff811690640100000000900464ffffffffff1682565b6040805163ffffffff909316835264ffffffffff90911660208301520161021a565b34801561050c57600080fd5b50600654610206906001600160a01b031681565b34801561052c57600080fd5b50610268611743565b34801561054157600080fd5b506105ac610550366004612390565b6008602052600090815260409020546effffffffffffffffffffffffffffff81169063ffffffff6f010000000000000000000000000000008204811691600160981b8104821691600160b81b8204811691600160d81b90041685565b604080516effffffffffffffffffffffffffffff909616865263ffffffff9485166020870152928416928501929092528216606084015216608082015260a00161021a565b3480156105fd57600080fd5b506102cb61060c366004612468565b611750565b34801561061d57600080fd5b5061067d61062c36600461243d565b60056020526000908152604090205463ffffffff8082169164010000000081048216916801000000000000000082048116916c010000000000000000000000008104821691600160801b9091041685565b6040805163ffffffff968716815294861660208601529285169284019290925283166060830152909116608082015260a00161021a565b3480156106c057600080fd5b5061070b6106cf366004612322565b6004602052600090815260409020546001600160a01b03811690600160a01b810464ffffffffff1690600160c81b900466ffffffffffffff1683565b604080516001600160a01b03909416845264ffffffffff909216602084015266ffffffffffffff169082015260600161021a565b34801561074b57600080fd5b506102cb61075a3660046124a1565b6117bc565b34801561076b57600080fd5b506102cb61077a366004612390565b6118b1565b34801561078b57600080fd5b5061026861079a366004612322565b611bc9565b3480156107ab57600080fd5b506102436107ba366004612322565b600c6020526000908152604090205460ff1681565b3480156107db57600080fd5b506102436107ea366004612540565b600360209081526000928352604080842090915290825290205460ff1681565b34801561081657600080fd5b506102cb61082536600461256e565b611e81565b34801561083657600080fd5b506102cb61084536600461243d565b611f37565b34801561085657600080fd5b5060065461086e90600160a01b900463ffffffff1681565b60405163ffffffff909116815260200161021a565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000006001600160e01b0319831614806108e657507f80ac58cd000000000000000000000000000000000000000000000000000000006001600160e01b03198316145b8061091a57507f5b5e139f000000000000000000000000000000000000000000000000000000006001600160e01b03198316145b92915050565b6000805461092d906125ae565b80601f0160208091040260200160405190810160405280929190818152602001828054610959906125ae565b80156109a65780601f1061097b576101008083540402835291602001916109a6565b820191906000526020600020905b81548152906001019060200180831161098957829003601f168201915b505050505081565b6000818152600460205260409020546001600160a01b0316338114806109f757506001600160a01b038116600090815260036020908152604080832033845290915290205460ff165b610a395760405162461bcd60e51b815260206004820152600e60248201526d1393d517d055551213d49256915160921b60448201526064015b60405180910390fd5b60008281526002602052604080822080546001600160a01b0319166001600160a01b0387811691821790925591518593918516917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a4505050565b6000818152600460205260409020546001600160a01b03848116911614610afe5760405162461bcd60e51b815260206004820152600a60248201527f57524f4e475f46524f4d000000000000000000000000000000000000000000006044820152606401610a30565b6001600160a01b038216610b545760405162461bcd60e51b815260206004820152601160248201527f494e56414c49445f524543495049454e540000000000000000000000000000006044820152606401610a30565b336001600160a01b0384161480610b8e57506001600160a01b038316600090815260036020908152604080832033845290915290205460ff165b80610baf57506000818152600260205260409020546001600160a01b031633145b610bec5760405162461bcd60e51b815260206004820152600e60248201526d1393d517d055551213d49256915160921b6044820152606401610a30565b6001600160a01b038084166000818152600560209081526040808320805460001963ffffffff600160801b8084048216929092018116820263ffffffff60801b19938416179093559689168086528386208054898104851660010190941690980292909116919091179095558583526004825280832080546001600160a01b031990811687179091556002909252808320805490921690915551849392917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b336000908152600b6020908152604080832063ffffffff8616845290915290205460ff1615610d15576040517f36a1d8d400000000000000000000000000000000000000000000000000000000815263ffffffff83166004820152602401610a30565b63ffffffff8216600090815260096020908152604091829020825160608101845290546001600160a01b03811680835264ffffffffff600160a01b8304811694840194909452600160c81b9091049092169281019290925280610dac576040517f684773c300000000000000000000000000000000000000000000000000000000815263ffffffff85166004820152602401610a30565b60408201514262069780820181116202a30083018211016001018215150260ff8116600303610e0f576040517faa5ebdc700000000000000000000000000000000000000000000000000000000815263ffffffff88166004820152602401610a30565b6040517f2fa61cd80000000000000000000000000000000000000000000000000000000081523360048201526001600160a01b038516906341161b10908290632fa61cd8906024016020604051808303816000875af1158015610e76573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e9a91906125e8565b886040518363ffffffff1660e01b8152600401610ec1929190918252602082015260400190565b6020604051808303816000875af1158015610ee0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f049190612601565b610f3a576040517f62cebecf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8264ffffffffff1660000361105e5763ffffffff8781166000908152600960209081526040808320805464ffffffffff60c81b1916600160c81b64ffffffffff8916021790556008909152902080549091600f91610fac916f0100000000000000000000000000000090910416612634565b825463ffffffff9182166101009390930a92830291909202199091161790556040517f49edb4250000000000000000000000000000000000000000000000000000000081523360048201526001600160a01b037f000000000000000000000000c0ffeee59157d2dd0ee70d4fababca349b31920316906349edb42590602401600060405180830381600087803b15801561104557600080fd5b505af1158015611059573d6000803e3d6000fd5b505050505b336000818152600b6020908152604080832063ffffffff8c811685529083528184208054600160ff1990911681179091556008909352922080547fff00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff8116600160d81b9182900485169384019094160292909217909155349161111e9160808b901b73ffffffff000000000000000000000000000000001617630fffffff8a16606883901c72fffffffffffffffffffffffffffffff0000000161785611fdd565b8160ff166001036111815763ffffffff888116600090815260086020526040902080547fffffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffff8116600160981b91829004841660010190931602919091179055611248565b8160ff166002036112485766470de4df8200008110156111cd576040517f356680b700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b63ffffffff888116600090815260086020526040902080547fffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffff8116600160b81b9182900484166001019093160291909117905560065461123d906001600160a01b0316662386f26fc10000612233565b662386f26fc0ffff19015b63ffffffff88166000908152600a6020526040902054611271906001600160a01b031682612233565b6040805188815260ff84166020820152339163ffffffff8b16917f79550049b42009b11b52956ed9da213919c1417e6980af5e79bf7fffbb28a533910160405180910390a35050505050505050565b6112cb838383610a95565b6001600160a01b0382163b15806113745750604051630a85bd0160e11b8082523360048301526001600160a01b03858116602484015260448301849052608060648401526000608484015290919084169063150b7a029060a4016020604051808303816000875af1158015611344573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113689190612657565b6001600160e01b031916145b6113c05760405162461bcd60e51b815260206004820152601060248201527f554e534146455f524543495049454e54000000000000000000000000000000006044820152606401610a30565b505050565b6040517f6352211e000000000000000000000000000000000000000000000000000000008152600481018290527f000000000000000000000000c0ffeee59157d2dd0ee70d4fababca349b3192036001600160a01b031690636352211e90602401602060405180830381865afa158015611443573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114679190612674565b6001600160a01b0316336001600160a01b031614611497576040516282b42960e81b815260040160405180910390fd5b6000818152600c602052604090205460ff16156114e3576040517fe75c31ed00000000000000000000000000000000000000000000000000000000815260048101829052602401610a30565b6000818152600c60209081526040808320805460ff191660019081179091556006805463ffffffff600160a01b80830482169094019081168085027fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff9093169290921790925583516060810185526001600160a01b03808a16825264ffffffffff4281168389019081528388018a8152858b5260098a52888b2094518554925191518416600160c81b0264ffffffffff60c81b19929094169098027fffffffffffffff000000000000000000000000000000000000000000000000009092169790931696909617959095171693909317909255600a845282852080546001600160a01b0319163390811790915560089094529382902080546effffffffffffffffffffffffffffff19166e181e28181e2827303df0f6fc94a3b317905590517ff769adab71d9ec16f2a65a83569ce900dfec45bc5f6d39d364d7059d631c95149061165e9087906001600160a01b0391909116815260200190565b60405180910390a3505050565b6000818152600460205260409020546001600160a01b0316806116bd5760405162461bcd60e51b815260206004820152600a6024820152691393d517d3525395115160b21b6044820152606401610a30565b919050565b60006001600160a01b03821661171a5760405162461bcd60e51b815260206004820152600c60248201527f5a45524f5f4144445245535300000000000000000000000000000000000000006044820152606401610a30565b506001600160a01b0316600090815260056020526040902054600160801b900463ffffffff1690565b6001805461092d906125ae565b3360008181526003602090815260408083206001600160a01b03871680855290835292819020805460ff191686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b6117c7858585610a95565b6001600160a01b0384163b158061185e5750604051630a85bd0160e11b808252906001600160a01b0386169063150b7a029061180f9033908a90899089908990600401612691565b6020604051808303816000875af115801561182e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118529190612657565b6001600160e01b031916145b6118aa5760405162461bcd60e51b815260206004820152601060248201527f554e534146455f524543495049454e54000000000000000000000000000000006044820152606401610a30565b5050505050565b63ffffffff81166000908152600960209081526040808320815160608101835290546001600160a01b038116825264ffffffffff600160a01b8204811694830194909452600160c81b9004909216908201819052909103611946576040517f71c4611b00000000000000000000000000000000000000000000000000000000815263ffffffff83166004820152602401610a30565b60075463ffffffff808416911603611992576040517ff31ab1ac00000000000000000000000000000000000000000000000000000000815263ffffffff83166004820152602401610a30565b602081015160408201516007549190039064ffffffffff640100000000909104811690821610156119f7576040517f20012d7300000000000000000000000000000000000000000000000000000000815263ffffffff84166004820152602401610a30565b6007805464ffffffffff9092166401000000000268ffffffffffffffffff1990921663ffffffff85169081179290921790556000908152600a60209081526040822054918052600490527f17ef568e3e12ab5b9c7254a8d58478811de00f9e6eb34345acd53bf8fd09d3ec546001600160a01b0391821691168015611b1c576001600160a01b0381166000818152600560209081526040808320805460001963ffffffff600160801b808404821692909201160263ffffffff60801b1990911617905582805260029091527fac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b80546001600160a01b031916905551909182917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45b6001600160a01b03821660008181526005602090815260408083208054600163ffffffff600160801b808404821692909201160263ffffffff60801b1990911617905582805260049091527f17ef568e3e12ab5b9c7254a8d58478811de00f9e6eb34345acd53bf8fd09d3ec80546001600160a01b031916841790555190919082907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a450505050565b60008181526004602090815260409182902082516060808201855291546001600160a01b038116808352600160a01b820464ffffffffff1694830194909452600160c81b900466ffffffffffffff16938101939093529190611c5a5760405162461bcd60e51b815260206004820152600a6024820152691393d517d3525395115160b21b6044820152606401610a30565b82600003611c8557604051806102e001604052806102bd81526020016127ce6102bd91399392505050565b608083901c63ffffffff8181166000818152600960209081526040808320815160608101835290546001600160a01b038082168352600160a01b820464ffffffffff908116848701908152600160c81b9093041683850152958552600a845282852054600885529290942054935192880151909591851694600160d81b8504909216936effffffffffffffffffffffffffffff16927f000000000000000000000000000000000000000000000000000000000000000090921691631fcc81ff9187918c918891611d54916126e5565b60408c810151908b015160208e015162069780820181116202a3008301909111016001019015150289896040518963ffffffff1660e01b8152600401611e3098979695949392919088516001600160a01b0390811682526020808b015164ffffffffff9081169184019190915260409a8b015181169a83019a909a5260608201989098529590961660808601529290951660a084015266ffffffffffffff1660c083015260ff9390931660e082015263ffffffff929092166101008301526effffffffffffffffffffffffffffff166101208201526101400190565b600060405180830381865afa158015611e4d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611e759190810190612720565b98975050505050505050565b63ffffffff82166000908152600a60205260409020546001600160a01b03163314611ebe576040516282b42960e81b815260040160405180910390fd5b63ffffffff821660008181526008602090815260409182902080546effffffffffffffffffffffffffffff19166effffffffffffffffffffffffffffff861690811790915591519182527f589aaff1c002925ad33e34d092a74d95c823ee866338f474c74a5bd51f511512910160405180910390a25050565b6006546001600160a01b03163314611f915760405162461bcd60e51b815260206004820152600c60248201527f554e415554484f52495a454400000000000000000000000000000000000000006044820152606401610a30565b600680546001600160a01b0319166001600160a01b03831690811790915560405133907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a350565b6001600160a01b038416600090815260056020526040812080546c01000000000000000000000000600163ffffffff600160801b8085048216830182160263ffffffff60801b19851681178490048216909201169091026fffffffff000000000000000000000000199091167fffffffffffffffffffffffff0000000000000000ffffffffffffffffffffffff9092169190911717905560ff821690036120b5576001600160a01b0384166000908152600560205260409020805463ffffffff8082166001011663ffffffff19909116179055612152565b8060ff16600103612107576001600160a01b03841660009081526005602052604090208054600163ffffffff640100000000808404821692909201160267ffffffff0000000019909116179055612152565b6001600160a01b03841660009081526005602052604090208054600163ffffffff6801000000000000000080840482169290920116026bffffffff0000000000000000199091161790555b604080516060810182526001600160a01b0380871680835264ffffffffff428116602080860191825266ffffffffffffff808a1687890190815260008c815260049093528883209751885494519151909216600160c81b0278ffffffffffffffffffffffffffffffffffffffffffffffffff91909516600160a01b027fffffffffffffff00000000000000000000000000000000000000000000000000909416919096161791909117939093161790925591518592907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a450505050565b600080600080600085875af19050806113c05760405162461bcd60e51b815260206004820152601360248201527f4554485f5452414e534645525f4641494c4544000000000000000000000000006044820152606401610a30565b6001600160e01b0319811681146122a457600080fd5b50565b6000602082840312156122b957600080fd5b81356122c48161228e565b9392505050565b60005b838110156122e65781810151838201526020016122ce565b50506000910152565b602081526000825180602084015261230e8160408501602087016122cb565b601f01601f19169190910160400192915050565b60006020828403121561233457600080fd5b5035919050565b6001600160a01b03811681146122a457600080fd5b6000806040838503121561236357600080fd5b823561236e8161233b565b946020939093013593505050565b803563ffffffff811681146116bd57600080fd5b6000602082840312156123a257600080fd5b6122c48261237c565b6000806000606084860312156123c057600080fd5b83356123cb8161233b565b925060208401356123db8161233b565b929592945050506040919091013590565b600080604083850312156123ff57600080fd5b61236e8361237c565b6000806040838503121561241b57600080fd5b82356124268161233b565b91506124346020840161237c565b90509250929050565b60006020828403121561244f57600080fd5b81356122c48161233b565b80151581146122a457600080fd5b6000806040838503121561247b57600080fd5b82356124868161233b565b915060208301356124968161245a565b809150509250929050565b6000806000806000608086880312156124b957600080fd5b85356124c48161233b565b945060208601356124d48161233b565b935060408601359250606086013567ffffffffffffffff808211156124f857600080fd5b818801915088601f83011261250c57600080fd5b81358181111561251b57600080fd5b89602082850101111561252d57600080fd5b9699959850939650602001949392505050565b6000806040838503121561255357600080fd5b823561255e8161233b565b915060208301356124968161233b565b6000806040838503121561258157600080fd5b61258a8361237c565b915060208301356effffffffffffffffffffffffffffff8116811461249657600080fd5b600181811c908216806125c257607f821691505b6020821081036125e257634e487b7160e01b600052602260045260246000fd5b50919050565b6000602082840312156125fa57600080fd5b5051919050565b60006020828403121561261357600080fd5b81516122c48161245a565b634e487b7160e01b600052601160045260246000fd5b600063ffffffff80831681810361264d5761264d61261e565b6001019392505050565b60006020828403121561266957600080fd5b81516122c48161228e565b60006020828403121561268657600080fd5b81516122c48161233b565b60006001600160a01b03808816835280871660208401525084604083015260806060830152826080830152828460a0840137600060a0848401015260a0601f19601f85011683010190509695505050505050565b64ffffffffff8281168282160390808211156127035761270361261e565b5092915050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561273257600080fd5b815167ffffffffffffffff8082111561274a57600080fd5b818401915084601f83011261275e57600080fd5b8151818111156127705761277061270a565b604051601f8201601f19908116603f011681019083821181831017156127985761279861270a565b816040528281528760208487010111156127b157600080fd5b6127c28360208301602088016122cb565b97965050505050505056fe646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c65794a755957316c496a6f69526d567962574630496977695a47567a59334a7063485270623234694f694a4d6232356e5a584e304948567563323973646d566b49484231656e70735a5334694c434a706257466e5a56396b59585268496a6f695a474630595470706257466e5a53397a646d6372654731734f324a68633255324e43785153453479576e6c434d324658556a426852444270546c525664306c70516d396156327875595568524f556c715654464e51306c6e5a47317362475177536e5a6c5244427054554e4264306c455654464e51304578546c524261556c48576e42695233633553573031646d4a7456576c4a53476830596b6331656c4254536d396b53464a3354326b34646d517a5a444e4d626d4e3654473035655670354f486c4e5245463354444e4f4d6c70355353745153454a6f5a45646e5a31707462484e6952444270535870464e45315656586c5051306c6e576b5177615652555157644e5232637a546c52434d6b3536565864545245493253576b344b314249536d785a4d31466e5a555177615531555558704a6155493155464e4a4d6b39545357646b4d6d78725a45646e4f556c7153544a4f51306c6e5955645763466f796144425155306b775456524a61556c49536a525155306b3053576c436257465865484e5155306c715457706a656b3145546b564a615467725545684b62466b7a5557646c52444270545652524d306c70516a565155306b7a54586c4a5a324d7a556e6c694d6e527355464e4a616b31555158684e656b5a4553576c434d324658556a426852444270545770564d6b6c70516d396156327875595568524f556c715558644f51306c6e5932356e4f556c7155576c4a52317077596b64334f556c7054586461524556335456526a615578364e44684d4d303479576e6f3050534a39a164736f6c6343000811000a
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000c0ffeee59157d2dd0ee70d4fababca349b3192030000000000000000000000000000000000000000000000000000000000000000
-----Decoded View---------------
Arg [0] : _authorshipToken (address): 0xC0fFeEe59157d2dd0Ee70d4FABaBCa349b319203
Arg [1] : _flagRenderer (address): 0x0000000000000000000000000000000000000000
-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 000000000000000000000000c0ffeee59157d2dd0ee70d4fababca349b319203
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000000
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 26 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.