Skip to content

Commit

Permalink
Add Base test, remove renderer, and add PurityChecker to `CurtaGo…
Browse files Browse the repository at this point in the history
…lf` constructors (#7)

* tests: add BaseTest util test contract

* feat: remove renderer and add puritychecker to constructor

* fix: emitted event args

* feat: add mock course + solutions

* tests: add mock course to base test

* fix: add  to

* chore: add sample solution bytecodes
  • Loading branch information
fiveoutofnine authored Nov 29, 2023
1 parent 425c8aa commit 10006d3
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 17 deletions.
4 changes: 2 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ solc = "0.8.21"
optimizer_runs = 1_000_000
remappings = [
'forge-std/=lib/forge-std/src/',
'solady/=lib/solady/',
'solmate/=lib/solmate/src',
'solady/=lib/solady/src/',
'solmate/=lib/solmate/src/',
]


Expand Down
18 changes: 10 additions & 8 deletions src/CurtaGolf.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ contract CurtaGolf is ICurtaGolf, KingERC721, Owned {
/// @inheritdoc ICurtaGolf
Par public immutable override par;

/// @inheritdoc ICurtaGolf
address public immutable override renderer;

// -------------------------------------------------------------------------
// Storage
// -------------------------------------------------------------------------
Expand All @@ -59,11 +56,16 @@ contract CurtaGolf is ICurtaGolf, KingERC721, Owned {
// -------------------------------------------------------------------------

/// @param _par The Par contract.
/// @param _renderer The address of the renderer used to render tokens'
/// metadata.
constructor(Par _par, address _renderer) KingERC721("Curta Golf", "KING") Owned(msg.sender) {
/// @param _purityChecker The purity checker contract.
constructor(Par _par, IPurityChecker _purityChecker)
KingERC721("Curta Golf", "KING")
Owned(msg.sender)
{
par = _par;
renderer = _renderer;
purityChecker = _purityChecker;

// Emit event.
emit SetPurityChecker(_purityChecker);
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -124,7 +126,7 @@ contract CurtaGolf is ICurtaGolf, KingERC721, Owned {
CourseData({ course: _course, gasUsed: 0, solutionCount: 0, kingCount: 0 });

// Emit event.
emit AddCourse(curCourseId, ICourse(msg.sender));
emit AddCourse(curCourseId, _course);
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/interfaces/ICourse.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ pragma solidity ^0.8.21;
/// @notice A course is a gas golfing challenge, where the goal is for players
/// to submit solutions that use as little gas as possible.
interface ICourse {
// -------------------------------------------------------------------------
// Errors
// -------------------------------------------------------------------------

/// @notice Emitted when the solution is incorrect.
error IncorrectSolution();

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

/// @notice Returns the course's name.
/// @return The course's name.
function name() external pure returns (string memory);
Expand Down
4 changes: 0 additions & 4 deletions src/interfaces/ICurtaGolf.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,6 @@ interface ICurtaGolf {
/// @notice The Par contract.
function par() external view returns (Par);

/// @return The address of the renderer used to render tokens' metadata
/// returned by {CurtaGolf.tokenURI}.
function renderer() external view returns (address);

// -------------------------------------------------------------------------
// Storage
// -------------------------------------------------------------------------
Expand Down
5 changes: 2 additions & 3 deletions src/interfaces/IPurityChecker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
pragma solidity ^0.8.21;

/// @title The interface for a purity checker
/// @notice A purity checker checks whether a given contract is pure, i.e.
/// that it's restricted to a set of instructions/opcodes, by analyzing its
/// bytecode.
/// @notice A purity checker checks whether a given contract is pure, i.e. that
/// it's restricted to a set of instructions/opcodes, by analyzing its bytecode.
interface IPurityChecker {
/// @notice Checks whether the given bytecode `_code` is pure.
/// @param _code The bytecode to check.
Expand Down
13 changes: 13 additions & 0 deletions src/utils/PurityChecker.sol
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import { IPurityChecker } from "../interfaces/IPurityChecker.sol";

/// @title EVM bytecode purity checker
/// @notice A purity checker checks whether a given contract is pure, i.e. that
/// it's restricted to a set of instructions/opcodes, by analyzing its bytecode.
contract PurityChecker is IPurityChecker {
/// @inheritdoc IPurityChecker
function check(bytes memory _code) external view override returns (bool) {
// TODO
return true;
}
}
41 changes: 41 additions & 0 deletions src/utils/mock/MockCourse.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
import { ICourse } from "../../interfaces/ICourse.sol";

/// @title A mock Curta Golf Course for testing
/// @author fiveoutofnine
contract MockCourse is ICourse {
/// @inheritdoc ICourse
function name() external pure returns (string memory) {
return "Mock Course";
}

/// @inheritdoc ICourse
function run(address _target, uint256 _seed) external view returns (uint32) {
uint256 start = gasleft();

// Generate inputs from `_seed`.
uint256 a = _seed >> 128;
uint256 b = _seed & 0xffffffffffffffffffffffffffffffff;

// Run solution.
uint256 c = IMockCourse(_target).add(a, b);
uint256 end = gasleft();

unchecked {
// Verify solution.
if (c != a + b) revert IncorrectSolution();

// Return gas used.
return uint32(start - end);
}
}
}

/// @title The interface for `MockCourse`, a mock Curta Golf Course for testing
interface IMockCourse {
/// @notice Adds two numbers together.
/// @param _a The first number.
/// @param _b The second number.
/// @return The sum of `_a` and `_b`.
function add(uint256 _a, uint256 _b) external pure returns (uint256);
}
35 changes: 35 additions & 0 deletions src/utils/mock/MockCourseSolution.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
import { IMockCourse } from "./MockCourse.sol";

/// @title An efficient solution to `MockCourse`.
/// @author fiveoutofnine
/// @dev When compiled with `0.8.21+commit.d9974bed` and `1_000_000` optimizer
/// runs, the contract has the following bytecode:
/// ```
/// 0x6080604052348015600f57600080fd5b5060a58061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063771602f714602d575b600080fd5b603c6038366004604e565b0190565b60405190815260200160405180910390f35b60008060408385031215606057600080fd5b5050803592602090910135915056fea264697066735822122053508e1f6f437dc11678aec86f624d167eb4ae75e36f622b6bf518e6edd2a99f64736f6c63430008150033
/// ```
contract MockCourseSolutionEfficient is IMockCourse {
/// @inheritdoc IMockCourse
function add(uint256 _a, uint256 _b) external pure override returns (uint256) {
unchecked {
return _a + _b;
}
}
}

/// @title An inefficient solution to `MockCourse`.
/// @author fiveoutofnine
/// @dev When compiled with `0.8.21+commit.d9974bed` and `1_000_000` optimizer
/// runs, the contract has the following bytecode:
/// ```
/// 0x608060405234801561001057600080fd5b50610158806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063771602f714610030575b600080fd5b61004361003e366004610086565b610055565b60405190815260200160405180910390f35b6000805b60648110156100725761006b816100d7565b9050610059565b5061007d828461010f565b90505b92915050565b6000806040838503121561009957600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610108576101086100a8565b5060010190565b80820180821115610080576100806100a856fea26469706673582212200d824e76c3f79d5d524f08c7f5df03e9eee461a0664d228713dc578a86c02fc564736f6c63430008150033
/// ```
contract MockCourseSolutionInefficient is IMockCourse {
/// @inheritdoc IMockCourse
function add(uint256 _a, uint256 _b) external pure override returns (uint256) {
// Waste some gas.
for (uint256 i; i < 100; ++i) { }

return _a + _b;
}
}
176 changes: 176 additions & 0 deletions test/utils/BaseTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import { Test } from "forge-std/Test.sol";
import { LibRLP } from "solady/utils/LibRLP.sol";

import { CurtaGolf } from "../../src/CurtaGolf.sol";
import { Par } from "../../src/Par.sol";
import { ICourse } from "../../src/interfaces/ICourse.sol";
import { IPurityChecker } from "../../src/interfaces/IPurityChecker.sol";
import { PurityChecker } from "../../src/utils/PurityChecker.sol";
import { MockCourse } from "../../src/utils/mock/MockCourse.sol";

/// @notice A base test contract for Curta Golf with constants for sample
/// solutions, events, labeled addresses, and helper functions for testing. When
/// `BaseTest` is deployed, it sets and labels 3 addresses: `owner` (owner of
/// the `CurtaGolf` deploy), `solver1`, and `solver2`. Then, in `setUp`, it
/// deploys an instance of `CurtaGolf`, `MockCourse`, `Par`, `PurityChecker`,
/// and adds `MockCourse` to `CurtaGolf` as `owner`.
contract BaseTest is Test {
// -------------------------------------------------------------------------
// Constants
// -------------------------------------------------------------------------

/// @notice Bytecode of an efficient solution to `MockCourse`.
/// @dev The bytecode outputted when `MockCourseSolutionEfficient` is
/// compiled with `0.8.21+commit.d9974bed` and `1_000_000` optimizer runs.
bytes constant EFFICIENT_SOLUTION =
hex"6080604052348015600f57600080fd5b5060a58061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063771602f714602d575b600080fd5b603c6038366004604e565b0190565b60405190815260200160405180910390f35b60008060408385031215606057600080fd5b5050803592602090910135915056fea264697066735822122053508e1f6f437dc11678aec86f624d167eb4ae75e36f622b6bf518e6edd2a99f64736f6c63430008150033";

/// @notice Bytecode of an inefficient solution to `MockCourse`.
/// @dev The bytecode outputted when `MockCourseSolutionInefficient` is
/// compiled with `0.8.21+commit.d9974bed` and `1_000_000` optimizer runs.
bytes constant INEFFICIENT_SOLUTION =
hex"608060405234801561001057600080fd5b50610158806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063771602f714610030575b600080fd5b61004361003e366004610086565b610055565b60405190815260200160405180910390f35b6000805b60648110156100725761006b816100d7565b9050610059565b5061007d828461010f565b90505b92915050565b6000806040838503121561009957600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610108576101086100a8565b5060010190565b80820180821115610080576100806100a856fea26469706673582212200d824e76c3f79d5d524f08c7f5df03e9eee461a0664d228713dc578a86c02fc564736f6c63430008150033";

// -------------------------------------------------------------------------
// `CurtaGolf` events
// -------------------------------------------------------------------------

/// @notice Emitted when a course is added to the contract.
/// @param id The ID of the course.
/// @param course The address of the course.
event AddCourse(uint32 indexed id, ICourse indexed course);

/// @notice Emitted when a commit for a solution is made.
/// @param courseId The ID of the course.
/// @param player The address of the player.
/// @param key The key of the commit.
event CommitSolution(uint32 indexed courseId, address indexed player, bytes32 key);

/// @notice Emitted when a valid submission is made.
/// @param courseId The ID of the course.
/// @param recipient The address of the recipient.
/// @param target The address of the deployed solution.
event SubmitSolution(
uint32 indexed courseId, address indexed recipient, address indexed target
);

/// @notice Emitted when a new purity checker is set.
/// @param purityChecker The address of the new purity checker.
event SetPurityChecker(IPurityChecker indexed purityChecker);

/// @notice Emitted when a course gets a new King.
/// @param courseId The ID of the course.
/// @param recipient The address of the recipient.
/// @param gasUsed The amount of gas used.
event UpdateKing(uint32 indexed courseId, address indexed recipient, uint32 indexed gasUsed);

// -------------------------------------------------------------------------
// `ERC721` events
// -------------------------------------------------------------------------

/// @notice Emitted when the address approved to transfer a token is
/// updated.
/// @param owner The owner of the token.
/// @param spender The address approved to transfer the token.
/// @param id The ID of the token.
event Approval(address indexed owner, address indexed spender, uint256 indexed id);

/// @notice Emitted when an operator to transfer all of an owner's tokens is
/// updated.
/// @param owner The owner of the tokens.
/// @param operator The address approved to transfer all of the owner's
/// tokens.
/// @param approved The new approval status of the operator.
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

/// @notice Emitted when a token is transferred.
/// @param from The address the token is transferred from.
/// @param to The address the token is transferred to.
/// @param id The ID of the token.
event Transfer(address indexed from, address indexed to, uint256 indexed id);

// -------------------------------------------------------------------------
// Immutable storage
// -------------------------------------------------------------------------

/// @notice Address of the owner
address internal immutable owner;

/// @notice Address to solver 1.
address internal immutable solver1;

/// @notice Address of a solver.
address internal immutable solver2;

// -------------------------------------------------------------------------
// Contracts
// -------------------------------------------------------------------------

/// @notice The Curta Golf contract.
CurtaGolf internal curtaGolf;

/// @notice A mock Curta Golf Course.
ICourse internal mockCourse;

/// @notice The Par contract.
Par internal par;

/// @notice The purity checker contract.
PurityChecker internal purityChecker;

// -------------------------------------------------------------------------
// Setup
// -------------------------------------------------------------------------

/// @notice Sets and labels addresses for `owner`, `solver1`, `solver2`, and
/// sets the .
constructor() {
// Set addresses.
owner = makeAddr("owner");
solver1 = makeAddr("solver1");
solver2 = makeAddr("solver2");
vm.label(owner, "Curta Golf owner");
vm.label(solver1, "Solver 1");
vm.label(solver2, "Solver 2");

// Deploy solutions to `MockCourse`.
/* MockCourseSolutionEfficient solutionEfficient = new MockCourseSolutionEfficient();
MockCourseSolutionInefficient solutionInefficient = new MockCourseSolutionInefficient();
efficientSolution = address(solutionEfficient).code;
inefficientSolution = address(solutionInefficient).code; */
}

/// @notice Deploys an instance of `CurtaGolf`, `MockCourse`, `Par`,
/// `PurityChecker`, and adds `MockCourse` to `CurtaGolf` as `owner`.
function setUp() public {
// Transaction #1.
purityChecker = new PurityChecker();

// Curta Golf will be deployed on transaction #3, and Par will be
// deployed on transaction #2.
address curtaGolfAddress = LibRLP.computeAddress(address(this), 3);
address parAddress = LibRLP.computeAddress(address(this), 2);

// Transaction #2: Deploy Par.
par = new Par(curtaGolfAddress);
// Transaction #3: Deploy Curta Golf.
curtaGolf = new CurtaGolf(par, purityChecker);
// Transaction #4: Deploy the mock course.
mockCourse = new MockCourse();

// Transfer ownership of Curta Golf to `owner`.
curtaGolf.transferOwnership(owner);

// Add the mock course to Curta Golf.
vm.prank(owner);
curtaGolf.addCourse(mockCourse);

// Label addresses.
vm.label(address(par), "`Par`");
vm.label(address(curtaGolf), "`CurtaGolf`");
vm.label(address(mockCourse), "`MockCourse`");
}
}

0 comments on commit 10006d3

Please sign in to comment.