Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add code #2

Merged
merged 3 commits into from
Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"editor.rulers": [80, 100],
"editor.tabSize": 4,
"editor.formatOnSave": false,
"editor.inlineSuggest.enabled": true
}

12 changes: 0 additions & 12 deletions script/Counter.s.sol

This file was deleted.

83 changes: 83 additions & 0 deletions src/BaseRenderer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { LibString } from "solmate/utils/LibString.sol";
import { ITokenRenderer } from "./interfaces/ITokenRenderer.sol";

contract BaseRenderer is ITokenRenderer {
// -------------------------------------------------------------------------
// Constants
// -------------------------------------------------------------------------

uint256 private constant UINT_LUT = 0x46454443424139383736353433323130;

/// @notice The SVG header.
string constant SVG_HEADER = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8">';

/// @notice The SVG footer.
string constant SVG_FOOTER = "</svg>";

// -------------------------------------------------------------------------
// Functions
// -------------------------------------------------------------------------

/// @inheritdoc ITokenRenderer
function render(uint256 _id, uint8 _phase) external pure override returns (string memory) {
uint256 seed = uint256(keccak256(abi.encodePacked(_id)));
// TODO: if (_phase == 1) return shields;

uint256 fill = seed & 0xFFFFFF;
seed >>= 24;

string memory svg = SVG_HEADER;
for (uint256 i = 0xEFAE78CF2C70AEAA688E28606DA6584D24502CA2480C2040; i != 0; i >>= 6) {
if (seed & 1 == 1) {
(uint256 x, uint256 y) = (i & 7, (i >> 3) & 7);
uint256 darkenedFill = darkenColor(fill, _phase == 2 ? seed & 3 : 0);

svg = string.concat(svg, rect(x, y, darkenedFill));
unchecked {
svg = string.concat(svg, rect(7 - x, y, darkenedFill));
}
}

seed >>= 1;
}

return string.concat(svg, SVG_FOOTER);
}

function rect(uint256 _x, uint256 _y, uint256 _fill) internal pure returns (string memory) {
return string.concat(
'<rect width="1" height="1" x="',
LibString.toString(_x),
'" y="',
LibString.toString(_y),
'" fill="#',
toHexString(_fill),
'" />'
);
}

function toHexString(uint256 _a) internal pure returns (string memory) {
bytes memory b = new bytes(32);

uint256 data = (((UINT_LUT >> (((_a >> 20) & 0xF) << 3)) & 0xFF) << 40)
| (((UINT_LUT >> (((_a >> 16) & 0xF) << 3)) & 0xFF) << 32)
| (((UINT_LUT >> (((_a >> 12) & 0xF) << 3)) & 0xFF) << 24)
| (((UINT_LUT >> (((_a >> 8) & 0xF) << 3)) & 0xFF) << 16)
| (((UINT_LUT >> (((_a >> 4) & 0xF) << 3)) & 0xFF) << 8)
| ((UINT_LUT >> ((_a & 0xF) << 3)) & 0xFF);

assembly {
mstore(add(b, 32), data)
}

return string(b);
}

function darkenColor(uint256 _color, uint256 _num) internal pure returns (uint256) {
return (((_color >> 0x10) >> _num) << 0x10) | ((((_color >> 8) & 0xFF) >> _num) << 8)
| ((_color & 0xFF) >> _num);
}
}
14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

274 changes: 274 additions & 0 deletions src/Curta.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.17;

import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";

import { ICurta } from "./interfaces/ICurta.sol";
import { IMinimalERC721 } from "./interfaces/IMinimalERC721.sol";
import { IPuzzle } from "./interfaces/IPuzzle.sol";
import { ITokenRenderer } from "./interfaces/ITokenRenderer.sol";
import "./FlagsERC721.sol";

// .===========================================================================.
// | 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. |
// '==========================================================================='

/// @title Curta
/// @author fiveoutofnine
/// @notice An extensible CTF, where each part is a generative puzzle, and each
/// solution is minted as an NFT ("Flag").
contract Curta is ICurta, FlagsERC721 {
// -------------------------------------------------------------------------
// 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 fee required to submit a solution during "Phase 2".
uint256 constant PHASE_TWO_FEE = 0.01 ether;

// -------------------------------------------------------------------------
// Immutable Storage
// -------------------------------------------------------------------------

/// @inheritdoc ICurta
ITokenRenderer public immutable override baseRenderer;

/// @inheritdoc ICurta
IMinimalERC721 public immutable override authorshipToken;

// -------------------------------------------------------------------------
// Storage
// -------------------------------------------------------------------------

/// @inheritdoc ICurta
uint32 public override puzzleId = 1;

/// @inheritdoc ICurta
Fermat public override fermat;

/// @inheritdoc ICurta
mapping(uint32 => PuzzleSolves) public override getPuzzleSolves;

/// @inheritdoc ICurta
mapping(uint32 => PuzzleData) public override getPuzzle;

/// @inheritdoc ICurta
mapping(uint32 => address) public override getPuzzleAuthor;

/// @inheritdoc ICurta
mapping(uint32 => ITokenRenderer) public override getPuzzleTokenRenderer;

/// @inheritdoc ICurta
mapping(address => mapping(uint32 => bool)) public override hasSolvedPuzzle;

/// @inheritdoc ICurta
mapping(uint256 => bool) public override hasUsedAuthorshipToken;

// -------------------------------------------------------------------------
// Constructor + Functions
// -------------------------------------------------------------------------

/// @param _baseRenderer The address of the fallback token renderer
/// contract.
constructor(ITokenRenderer _baseRenderer, IMinimalERC721 _authorshipToken)
FlagsERC721("Curta", "CTF")
{
baseRenderer = _baseRenderer;
authorshipToken = _authorshipToken;
}

/// @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();

// 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;
}

// Mark the puzzle as solved.
hasSolvedPuzzle[msg.sender][_puzzleId] = true;

// Mint NFT.
unchecked {
_mint({
_to: msg.sender,
_id: (uint256(_puzzleId) << 128) | getPuzzleSolves[_puzzleId].solves++,
_puzzleId: _puzzleId,
_phase: phase
});

if (phase == 1) {
++getPuzzleSolves[_puzzleId].phase1Solves;
} else if (phase == 2) {
// Revert if the puzzle is in "Phase 2," and insufficient funds
// were sent.
if (msg.value < PHASE_TWO_FEE) revert InsufficientFunds();
++getPuzzleSolves[_puzzleId].phase2Solves;

// Transfer fee to the puzzle author. Refunds are not checked,
// in case someone wants to "tip" the author.
SafeTransferLib.safeTransferETH(getPuzzleAuthor[_puzzleId], msg.value);
}
}

// Emit events.
emit PuzzleSolved({id: _puzzleId, solver: msg.sender, solution: _solution, phase: phase});
}

/// @inheritdoc ICurta
function addPuzzle(IPuzzle _puzzle, uint256 _tokenId) external {
// Revert if the puzzle belongs to `msg.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;

// Emit events.
emit PuzzleAdded(curPuzzleId, msg.sender, _puzzle);
}
}

/// @inheritdoc ICurta
function setPuzzleTokenRenderer(uint32 _puzzleId, ITokenRenderer _tokenRenderer) external {
// Revert if `msg.sender` is not the author of the puzzle.
if (getPuzzleAuthor[_puzzleId] != msg.sender) revert Unauthorized();

// Set token renderer.
getPuzzleTokenRenderer[_puzzleId] = _tokenRenderer;
}

/// @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 = ownerOf(0);

unchecked {
// Delete ownership information about Fermat, if the owner is not
// `address(0)`.
if (currentOwner != address(0)) {
getUserBalances[currentOwner].balance--;

delete getApproved[0];
}

// Increment new Fermat author's balance.
getUserBalances[puzzleAuthor].balance++;

// Emit events.
emit Transfer(currentOwner, address(0), 0);
}

// Set new Fermat owner.
getTokenData[0].owner = puzzleAuthor;

// Emit events.
emit Transfer(address(0), puzzleAuthor, 0);
}

// -------------------------------------------------------------------------
// ERC721Metadata
// -------------------------------------------------------------------------

function tokenURI(uint256 _tokenId) external view override returns (string memory) {
return "";
}

// -------------------------------------------------------------------------
// 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)
{
assembly {
// (_solveTimestamp > _firstSolveTimestamp) Phase 1 Over
// + (_solveTimestamp > _firstSolveTimestamp + PHASE_ONE_LENGTH) Phase 2 Over
// + (_solveTimestamp > _firstSolveTimestamp + SUBMISSION_LENGTH) Phase 3 Over
phase :=
add(
gt(_solveTimestamp, _firstSolveTimestamp),
add(
gt(_solveTimestamp, add(_firstSolveTimestamp, PHASE_ONE_LENGTH)),
gt(_solveTimestamp, add(_firstSolveTimestamp, SUBMISSION_LENGTH))
)
)
}
}
}
Loading