Skip to content

Commit

Permalink
Merge pull request #6 from waterfall-mkt/mint-authorship-token
Browse files Browse the repository at this point in the history
feat(AuthorshipToken): owner minting
  • Loading branch information
leo4life2 committed Jan 10, 2023
2 parents 3d2be6a + d4186de commit 7a0494c
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 27 deletions.
40 changes: 20 additions & 20 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
CurtaTest:testAddPuzzle() (gas: 296517)
CurtaTest:testAddPuzzleAuthorshipTokenOwnership() (gas: 198598)
CurtaTest:testAuthorshipTokenMarkedUsed() (gas: 291328)
CurtaTest:testCheckDeployAddresses() (gas: 11329)
CurtaTest:testFirstBloodMintsAuthorshipToken() (gas: 420454)
CurtaTest:testFirstSolveTimestampOnlySetOnFirstBlood(uint40) (runs: 256, μ: 528810, ~: 528809)
CurtaTest:testFirstSolveTimestampSetOnFirstBlood(uint40) (runs: 256, μ: 415543, ~: 415543)
CurtaTest:testMintFlagFromSolve() (gas: 417183)
CurtaTest:testPhase1PaymentPaidOutToAuthor(uint256) (runs: 256, μ: 505195, ~: 505195)
CurtaTest:testPhase2PaymentPaidOutToAuthor(uint256) (runs: 256, μ: 505254, ~: 505254)
CurtaTest:testPhase2RequireETH(uint256) (runs: 256, μ: 495758, ~: 495822)
CurtaTest:testPlayerMarkedAsSolved() (gas: 416169)
CurtaTest:testSetPuzzleTokenRenderer() (gas: 614180)
CurtaTest:testSolve() (gas: 617756)
CurtaTest:testSolveCountersUpdated() (gas: 586420)
CurtaTest:testAddPuzzle() (gas: 296605)
CurtaTest:testAddPuzzleAuthorshipTokenOwnership() (gas: 198686)
CurtaTest:testAuthorshipTokenMarkedUsed() (gas: 291416)
CurtaTest:testCheckDeployAddresses() (gas: 11307)
CurtaTest:testFirstBloodMintsAuthorshipToken() (gas: 420696)
CurtaTest:testFirstSolveTimestampOnlySetOnFirstBlood(uint40) (runs: 256, μ: 528876, ~: 528875)
CurtaTest:testFirstSolveTimestampSetOnFirstBlood(uint40) (runs: 256, μ: 415609, ~: 415609)
CurtaTest:testMintFlagFromSolve() (gas: 417249)
CurtaTest:testPhase1PaymentPaidOutToAuthor(uint256) (runs: 256, μ: 505261, ~: 505261)
CurtaTest:testPhase2PaymentPaidOutToAuthor(uint256) (runs: 256, μ: 505320, ~: 505320)
CurtaTest:testPhase2RequireETH(uint256) (runs: 256, μ: 495824, ~: 495888)
CurtaTest:testPlayerMarkedAsSolved() (gas: 416235)
CurtaTest:testSetPuzzleTokenRenderer() (gas: 614357)
CurtaTest:testSolve() (gas: 617866)
CurtaTest:testSolveCountersUpdated() (gas: 586486)
CurtaTest:testSolveNonExistantPuzzle(uint32) (runs: 256, μ: 13735, ~: 13735)
CurtaTest:testSolvePuzzleTwice() (gas: 415418)
CurtaTest:testSubmitDuringPhase3(uint40) (runs: 256, μ: 442037, ~: 442037)
CurtaTest:testSubmitIncorrectSolution(uint256) (runs: 256, μ: 295903, ~: 295903)
CurtaTest:testUnauthorizedSetPuzzleTokenRenderer() (gas: 586586)
CurtaTest:testUseAuthorshipTokenTwice() (gas: 408905)
CurtaTest:testSolvePuzzleTwice() (gas: 415484)
CurtaTest:testSubmitDuringPhase3(uint40) (runs: 256, μ: 442103, ~: 442103)
CurtaTest:testSubmitIncorrectSolution(uint256) (runs: 256, μ: 295991, ~: 295991)
CurtaTest:testUnauthorizedSetPuzzleTokenRenderer() (gas: 586763)
CurtaTest:testUseAuthorshipTokenTwice() (gas: 409103)
OptimizationsTest:testFuzzComputePhaseFromTimestampBranchlessOptimization(uint40,uint40) (runs: 256, μ: 3538, ~: 3537)
96 changes: 90 additions & 6 deletions src/AuthorshipToken.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { Owned } from "solmate/auth/Owned.sol";
import { ERC721 } from "solmate/tokens/ERC721.sol";
import { LibString } from "solmate/utils/LibString.sol";
import { MerkleProofLib } from "solmate/utils/MerkleProofLib.sol";

contract AuthorshipToken is ERC721 {
import { ICurta } from "@/interfaces/ICurta.sol";
import { Base64 } from "@/utils/Base64.sol";

/// @title AuthorshipToken
contract AuthorshipToken is ERC721, Owned {
// -------------------------------------------------------------------------
// Constants
// -------------------------------------------------------------------------

/// @notice The number of seconds an additional token is made available for
/// minting by the author.
uint256 constant ISSUE_LENGTH = 1 days;

// -------------------------------------------------------------------------
// Errors
// -------------------------------------------------------------------------
Expand All @@ -16,16 +30,32 @@ contract AuthorshipToken is ERC721 {
/// @notice Emitted if a merkle proof is invalid.
error InvalidProof();

/// @notice Emitted when there are no tokens available to claim.
error NoTokensAvailable();

/// @notice Emitted when `msg.sender` is not authorized.
error Unauthorized();

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

/// @notice The Curta / Flags contract.
address public immutable curta;

/// @notice Merkle root of mint mintlist.
/// @notice Merkle root of addresses on the mintlist.
bytes32 public immutable merkleRoot;

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

Expand All @@ -37,15 +67,24 @@ contract AuthorshipToken is ERC721 {
// Constructor
// -------------------------------------------------------------------------

constructor(address _curta, bytes32 _merkleRoot) ERC721("Authorship Token", "AUTH") {
/// @param _curta The Curta / Flags contract.
/// @param _merkleRoot Merkle root of addresses on the mintlist.
constructor(address _curta, bytes32 _merkleRoot)
ERC721("Authorship Token", "AUTH")
Owned(msg.sender)
{
curta = _curta;
merkleRoot = _merkleRoot;
deployTimestamp = block.timestamp;
}

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

/// @notice Mints a token to `msg.sender` if the merkle proof is valid, and
/// `msg.sender` has not claimed a token yet.
/// @param _proof The merkle proof.
function mint(bytes32[] calldata _proof) external {
// Revert if the user has already claimed.
if (hasClaimed[msg.sender]) revert AlreadyClaimed(msg.sender);
Expand All @@ -60,15 +99,40 @@ contract AuthorshipToken is ERC721 {

unchecked {
uint256 tokenId = ++totalSupply;

_mint(msg.sender, tokenId);
}
}

/// @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 {
require(msg.sender == curta, "Only Curta can mint");
// 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 `ISSUE_LENGTH` seconds.
/// @param _to The address to mint the token to.
function ownerMint(address _to) external onlyOwner {
unchecked {
uint256 numIssued = (block.timestamp - deployTimestamp) / ISSUE_LENGTH;
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);
}
}
Expand All @@ -77,7 +141,27 @@ contract AuthorshipToken is ERC721 {
// ERC721Metadata
// -------------------------------------------------------------------------

/// @inheritdoc ERC721
function tokenURI(uint256 _tokenId) public view override returns (string memory) {
return "";
return string.concat(
"data:application/json;base64,",
Base64.encode(
abi.encodePacked(
'{"name":"Authorship Token #',
LibString.toString(_tokenId),
'","description":"Token that grants user permission to add a puzzle to Curta",',
'"image_data":"data:image/svg+xml;base64,',
// TODO: Update this to use the actual SVG
Base64.encode(
abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><rect width="100%" height="100%"/><text x="8" y="40" style="fill:#fff;font-family:serif;font-size:32px">Authorship Token</text></svg>'
)
),
'","attributes":[{"trait_type":"Used","value":',
ICurta(curta).hasUsedAuthorshipToken(_tokenId) ? "true" : "false",
"]}"
)
)
);
}
}
1 change: 1 addition & 0 deletions src/Curta.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FlagsERC721 } from "./FlagsERC721.sol";
import { ICurta } from "@/interfaces/ICurta.sol";
import { IPuzzle } from "@/interfaces/IPuzzle.sol";
import { ITokenRenderer } from "@/interfaces/ITokenRenderer.sol";
import { Base64 } from "@/utils/Base64.sol";

// .===========================================================================.
// | The Curta is a hand-held mechanical calculator designed by Curt |
Expand Down
2 changes: 1 addition & 1 deletion test/Curta.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ contract CurtaTest is Test {
}

vm.warp(firstBloodTimestamp + 0.5 days);

// `address(0xBEEF)` has not solved the puzzle yet.
assertTrue(!curta.hasSolvedPuzzle(address(0xBEEF), 1));

Expand Down

0 comments on commit 7a0494c

Please sign in to comment.