Skip to content

Commit

Permalink
⚡️ Optimize PurityChecker (#22)
Browse files Browse the repository at this point in the history
* chore: apply fmt

* ⚡ Optimize `PurityChecker`

* nit: clean up code

* nit: add comment explaining non-push case
  • Loading branch information
Philogy authored Dec 20, 2023
1 parent 114aaef commit 4ba5d5b
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 177 deletions.
34 changes: 34 additions & 0 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
CurtaGolfTest:test_addCourse() (gas: 74494)
CurtaGolfTest:test_addCourse_NotOwner_Unauthorized(address) (runs: 256, μ: 13559, ~: 13559)
CurtaGolfTest:test_addCourse_ZeroAddress_Reverts() (gas: 11517)
CurtaGolfTest:test_commit(bytes32) (runs: 256, μ: 38350, ~: 38350)
CurtaGolfTest:test_commit_KeyAlreadyCommitted_Reverts(bytes32) (runs: 256, μ: 34803, ~: 34803)
CurtaGolfTest:test_par_Equality() (gas: 8022)
CurtaGolfTest:test_setAllowedOpcodes() (gas: 23461)
CurtaGolfTest:test_setAllowedOpcodes_CourseDoesNotExist_Reverts() (gas: 13931)
CurtaGolfTest:test_setAllowedOpcodes_NotOwner_Unauthorized(address) (runs: 256, μ: 11488, ~: 11488)
CurtaGolfTest:test_setPurityChecker() (gas: 148218)
CurtaGolfTest:test_setPurityChecker_NotOwner_Unauthorized(address) (runs: 256, μ: 13956, ~: 13956)
CurtaGolfTest:test_setPurityChecker_ZeroAddress_Reverts() (gas: 12260)
CurtaGolfTest:test_submit() (gas: 751363)
CurtaGolfTest:test_submitDirectly() (gas: 664460)
CurtaGolfTest:test_submit_CommitTooNew_Reverts(uint256) (runs: 256, μ: 43348, ~: 43392)
CurtaGolfTest:test_submit_IncorrectSolution_Reverts() (gas: 131877)
CurtaGolfTest:test_submit_KeyNotCommitted_Reverts(uint256) (runs: 256, μ: 15489, ~: 15489)
CurtaGolfTest:test_submit_NonexistentCourse_Reverts() (gas: 42342)
CurtaGolfTest:test_submit_PollutedSolution_Reverts() (gas: 65405)
CurtaGolfTest:test_tokenURI_MintedToken_Succeeds() (gas: 45725077)
CurtaGolfTest:test_tokenURI_UnmintedToken_Reverts() (gas: 13480)
MockCourseTest:test_name_ReturnsNonEmptyString() (gas: 6086)
MockCourseTest:test_run_CorrectSolution_DoesNotRevert(uint256) (runs: 256, μ: 10734, ~: 10734)
MockCourseTest:test_run_IncorrectSolution_Reverts(uint256) (runs: 256, μ: 13775, ~: 13775)
ParTest:test_curtaGolf_Equality() (gas: 8080)
ParTest:test_tokenURI_MintedToken_Succeeds() (gas: 1993252)
ParTest:test_tokenURI_UnmintedToken_Reverts() (gas: 13145)
ParTest:test_upmint(address,uint32,uint32) (runs: 256, μ: 67620, ~: 67620)
ParTest:test_upmint_ExistingTokenBeatsRecord_UpdatesStorage() (gas: 65541)
ParTest:test_upmint_ExistingTokenDoesntBeatRecord_DoesntUpdateStorage() (gas: 65490)
ParTest:test_upmint_NotCurtaGolf_Unauthorized(address) (runs: 256, μ: 9996, ~: 9996)
PurityCheckerTest:test_bitmask() (gas: 2460)
PurityCheckerTest:test_check() (gas: 25695)
PurityCheckerTest:test_customBitmap() (gas: 11733)
1 change: 0 additions & 1 deletion script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { Par } from "src/Par.sol";
import { PurityChecker } from "src/utils/PurityChecker.sol";

contract Deploy is Script {

// -------------------------------------------------------------------------
// Deployment addresses
// -------------------------------------------------------------------------
Expand Down
10 changes: 2 additions & 8 deletions src/tokens/KingERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,7 @@ abstract contract KingERC721 {
}

// Set new owner.
_tokenData[_id] = TokenData({
owner: _to,
metadata: uint96(uint160(_to) >> 64)
});
_tokenData[_id] = TokenData({ owner: _to, metadata: uint96(uint160(_to) >> 64) });

// Clear previous approval data for the token.
delete getApproved[_id];
Expand All @@ -136,10 +133,7 @@ abstract contract KingERC721 {
}

// Set new owner
_tokenData[_id] = TokenData({
owner: _to,
metadata: uint96(uint160(_to) >> 64)
});
_tokenData[_id] = TokenData({ owner: _to, metadata: uint96(uint160(_to) >> 64) });

// Emit event.
emit Transfer(address(0), _to, _id);
Expand Down
62 changes: 28 additions & 34 deletions src/utils/PurityChecker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,37 @@ contract PurityChecker is IPurityChecker {
returns (bool isPure)
{
assembly ("memory-safe") {
function perform(code, bitmap) -> ret {
// `offset` is the memory position where the bytecode starts; we
// add `0x20` to skip the portion that stores the length of the
// bytecode.
let offset := add(code, 0x20)
// `end` is the memory position where the bytecode ends.
let end := add(offset, mload(code))
// Code is pure by default unless disallowed byte is found.
isPure := 1

// Iterate through the opcodes in the bytecode until we reach
// the end. For each byte that is an opcode (i.e. not data
// pushed onto the stack via the `PUSH` opcodes), we break the
// loop early if it is not allowed (i.e. the corresponding LSb
// bit in `_allowedOpcodes` is `0`). Since `ret` is set to `0`,
// or `false`, by default, this correctly results in `check`
// returning `false` if the contract is not pure.
for { let i := offset } lt(offset, end) { offset := add(offset, 1) } {
let opcode := byte(0, mload(offset))
// If the opcode is not allowed, the contract is not pure.
if iszero(and(shr(opcode, bitmap), 1)) { leave }
// `0xffffffff000000000000000000000000` is a bitmap where
// LSb bits `[0x5f + 1, 0x7f]` are 1, i.e. the `PUSH1`, ...,
// `PUSH32`, opcodes. We want to skip the number of bytes
// pushed onto the stack because they are arbitrary data,
// and we should not apply the purity check to them.
if and(shr(opcode, 0xffffffff000000000000000000000000), 1) {
// `opcode - 0x5f` is the number of bytes pushed onto
// the stack.
offset := add(offset, sub(opcode, 0x5f))
}
}
// `offset` is the memory position where the bytecode starts; we
// add `0x20` to skip the portion that stores the length of the
// bytecode.
let offset := add(_code, 0x20)
// `end` is the memory position where the bytecode ends.
let end := add(offset, mload(_code))

// If we reached the end of the bytecode, the contract is pure,
// so return `true`.
ret := 1
}
for { } lt(offset, end) { } {
let opcode := byte(0, mload(offset))

// Set `isPure` to 0 if any indexed bit in the loop was ever 0.
isPure := and(isPure, shr(opcode, _allowedOpcodes))

isPure := perform(_code, _allowedOpcodes)
// Always increments the offset by at least 1.
// If an opcode is a PUSH1-PUSH32 opcode (byte ∈ [0x60, 0x80)) the resulting value
// after subtracting 0x60 will be a valid index ∈ [0, 32). This is then indexed into
// a byte array representing the push bytes of the opcodes (1-32). Bytes outside of
// that range will result in an index larger than 31, which for `BYTE(n, x)` always
// returns 0.
offset :=
add(
add(offset, 1),
byte(
sub(opcode, 0x60),
0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
)
)
}
}
}
}
13 changes: 5 additions & 8 deletions src/utils/metadata/KingArt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,7 @@ library KingArt {
'="18" r="4"/><rect x="12" y="24" width="8" height="12" rx="4"/><circle'
' cx="28" cy="4" r="4"/><rect x="24" y="10" width="8" height="26" rx="4'
'"/></g><text x="172" y="120"><tspan class="a">Curta</tspan><tspan clas'
's="b"> | Golf</tspan></text><g transform="matrix(5.12 0 0 5.12 192 228'
'.96)">';
's="b"> | Golf</tspan></text><g transform="matrix(5.12 0 0 5.12 192 228' '.96)">';

/// @notice Starting string for the island's SVG.
/// @dev The island's SVG's width and height are computed to perfectly
Expand Down Expand Up @@ -328,19 +327,17 @@ library KingArt {
'</div><div class="c">King</div></div></div><div class="a d"><svg x'
'mlns="http://www.w3.org/2000/svg" width="40" height="48" viewBox="'
'0 0 24 24" fill="none"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4'
' 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 '
" 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 "
'4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><pat'
'h d="m9 12 2 2 4-4"/></svg><div class="a e"><div class="b">',
_formatNumber(_solves),
'</div><div class="c">Solves</div></div></div><div class="a d"><svg'
' xmlns="http://www.w3.org/2000/svg" width="40" height="48" viewBox'
'="0 0 24 24" fill="none"><path d="M3 22h12M4 9h10m0 13V4a2 2 0 0 0'
'-2-2H6a2 2 0 0 0-2 2v18m10-9h2a2 2 0 0 1 2 2v2a2 2 0 0 0 2 2h0a2 2'
' 0 0 0 2-2V9.83a2 2 0 0 0-.59-1.42L18 5"/></svg><div class="a e"><'
'div class="b">',
"-2-2H6a2 2 0 0 0-2 2v18m10-9h2a2 2 0 0 1 2 2v2a2 2 0 0 0 2 2h0a2 2"
' 0 0 0 2-2V9.83a2 2 0 0 0-.59-1.42L18 5"/></svg><div class="a e"><' 'div class="b">',
_formatNumber(_gasUsed),
'</div><div class="c">Gas used</div></div></div></div></foreignObje'
'ct></svg>'
'</div><div class="c">Gas used</div></div></div></div></foreignObje' "ct></svg>"
);
}

Expand Down
Loading

0 comments on commit 4ba5d5b

Please sign in to comment.