Skip to content
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
148 changes: 148 additions & 0 deletions contracts/BiscuitFont.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {SSTORE2} from "./libs/SSTORE2.sol";
import {Memory} from "./libs/Memory.sol";
import {IBiscuitFont} from "./interfaces/IBiscuitFont.sol";

/// @notice Stores font data in SSTORE2-backed pages.
contract BiscuitFont is IBiscuitFont, Ownable {
/// @notice SSTORE2 data offset (skip STOP opcode).
uint256 private constant _DATA_OFFSET = 1;

/// @notice Letters trait storage.
Trait private _letters;

/// @notice Digits trait storage.
Trait private _digits;

constructor(address initialOwner) Ownable(initialOwner) {}

/**
* @notice Return the number of stored letters chunks.
*/
function lettersCount() external view returns (uint256) {
return _letters.pages.length;
}

/**
* @notice Return the number of stored digits chunks.
*/
function digitsCount() external view returns (uint256) {
return _digits.pages.length;
}

/**
* @notice Return the concatenated letters binary payload.
*/
function letters() public view override returns (bytes memory) {
return _concatenate(_letters);
}

/**
* @notice Return the concatenated digits binary payload.
*/
function digits() public view override returns (bytes memory) {
return _concatenate(_digits);
}

/**
* @notice Add a batch of Letters images.
* @dev This function can only be called by the owner.
* Uses the “Caveat” typeface by Impallari Type for creating this letters.
* License: https://fonts.google.com/specimen/Caveat/license
*/
function addLetters(bytes calldata data) external override onlyOwner {
_addPage(_letters, data);
emit LettersPageAdded(_letters.pages.length - 1, _letters.totalBytes);
}

/**
* @notice Add a batch of Digits images.
* @dev This function can only be called by the owner.
* Uses the “Inter” typeface by Impallari Type for creating this letters.
* License: https://fonts.google.com/specimen/Inter/license
*/
function addDigits(bytes calldata data) external override onlyOwner {
_addPage(_digits, data);
emit DigitsPageAdded(_digits.pages.length - 1, _digits.totalBytes);
}

/**
* @notice Add a batch of font data from an existing storage contract.
* @dev This function can only be called by the owner.
*/
function addLettersFromPointer(address pointer, uint256 len) external override onlyOwner {
_addPointer(_letters, pointer, len);
emit LettersPageAdded(_letters.pages.length - 1, _letters.totalBytes);
}

/**
* @notice Add a batch of font data from an existing storage contract.
* @dev This function can only be called by the owner.
*/
function addDigitsFromPointer(address pointer, uint256 len) external override onlyOwner {
_addPointer(_digits, pointer, len);
emit DigitsPageAdded(_digits.pages.length - 1, _digits.totalBytes);
}

/**
* @notice Write arbitrary binary data to SSTORE2 and register its pointer to Trait.
* @dev Throws EmptyBytes error if data is empty.
* @param trait Trait structure to write to
* @param data byte sequence to be written
*/
function _addPage(Trait storage trait, bytes calldata data) internal {
if (data.length == 0) revert EmptyBytes();
address pointer = SSTORE2.write(data);
trait.pages.push(pointer);
trait.totalBytes += data.length;
}

/**
* @notice Register an existing SSTORE2 pointer and byte length.
* @dev Throws ZeroPointer or BadLength on invalid inputs.
* @param trait Trait structure to which to add
* @param pointer Address of SSTORE2 contract
* @param len Chunk byte length
*/
function _addPointer(Trait storage trait, address pointer, uint256 len) internal {
if (pointer == address(0)) revert ZeroPointer();
uint256 codeLength = pointer.code.length;
if (codeLength <= _DATA_OFFSET) revert BadLength();
uint256 dataLength = codeLength - _DATA_OFFSET;
if (len == 0 || len != dataLength) revert BadLength();
trait.pages.push(pointer);
trait.totalBytes += len;
}

/**
* @notice Combines all chunks registered in Trait and returns them as a single bytes array.
*/
function _concatenate(Trait storage trait) internal view returns (bytes memory out) {
out = new bytes(trait.totalBytes);

uint256 dst;
assembly {
dst := add(out, 0x20)
}
for (uint256 i; i < trait.pages.length; ) {
bytes memory data = SSTORE2.read(trait.pages[i]);

uint256 src;
uint256 len;
assembly {
src := add(data, 0x20)
len := mload(data)
}

Memory.copy(src, dst, len);
dst += len;

unchecked {
++i;
}
}
}
}
43 changes: 43 additions & 0 deletions contracts/interfaces/IBiscuitFont.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

interface IBiscuitFont {
struct Trait {
address[] pages;
uint256 totalBytes;
}

function lettersCount() external view returns (uint256);

function digitsCount() external view returns (uint256);

function letters() external view returns (bytes memory);

function digits() external view returns (bytes memory);

function addLetters(bytes calldata data) external;

function addDigits(bytes calldata data) external;

/**
* @notice Register letters data from an existing SSTORE2 storage contract.
* @dev The provided length must match the stored byte length.
*/
function addLettersFromPointer(address pointer, uint256 len) external;

/**
* @notice Register digits data from an existing SSTORE2 storage contract.
* @dev The provided length must match the stored byte length.
*/
function addDigitsFromPointer(address pointer, uint256 len) external;

event LettersPageAdded(uint256 indexed pageIndex, uint256 totalBytes);

event DigitsPageAdded(uint256 indexed pageIndex, uint256 totalBytes);

error EmptyBytes();

error ZeroPointer();

error BadLength();
}
39 changes: 39 additions & 0 deletions contracts/libs/Memory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

/// @notice https://github.com/ethereum/solidity-examples/blob/master/src/unsafe/Memory.sol
library Memory {
uint256 internal constant WORD_SIZE = 32;

/**
* @notice Copy `len` bytes from memory address `src` to address `dest`.
*/
function copy(uint256 src, uint256 dest, uint256 len) internal pure {
for (; len >= WORD_SIZE; len -= WORD_SIZE) {
assembly {
mstore(dest, mload(src))
}
dest += WORD_SIZE;
src += WORD_SIZE;
}

if (len == 0) return;

uint256 mask = 256 ** (WORD_SIZE - len) - 1;
assembly {
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}

/**
* @notice Return the data pointer and length for a bytes array.
*/
function fromBytes(bytes memory bts) internal pure returns (uint256 addr, uint256 len) {
len = bts.length;
assembly {
addr := add(bts, 32)
}
}
}
20 changes: 20 additions & 0 deletions contracts/test/SSTORE2Harness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {SSTORE2} from "../libs/SSTORE2.sol";

/// @title SSTORE2 Test Harness
/// @notice Exposes SSTORE2 library functions for unit tests.
contract SSTORE2Harness {
address public lastPointer;

function write(bytes calldata data) external returns (address) {
address pointer = SSTORE2.write(data);
lastPointer = pointer;
return pointer;
}

function read(address pointer) external view returns (bytes memory) {
return SSTORE2.read(pointer);
}
}
13 changes: 13 additions & 0 deletions contracts/test/StopCode.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

/// @title STOP Code Contract
/// @notice Deploys with a 1-byte runtime (0x00).
contract StopCode {
constructor() {
assembly {
mstore(0x00, 0x00)
return(0x00, 0x01)
}
}
}
Loading
Loading