-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add vscode config * add: interfaces and utils * add code Co-authored-by: e6f4e37l <66016924+e6f4e37l@users.noreply.github.com>
- Loading branch information
1 parent
d74742a
commit 4ee53e1
Showing
13 changed files
with
819 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
) | ||
) | ||
} | ||
} | ||
} |
Oops, something went wrong.