// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.20;
import {SSTORE2} from "./solady/utils/SSTORE2.sol";
import {IContentStore} from "./IContentStore.sol";
contract ContentStore is IContentStore {
// content checksum => sstore2 pointer
mapping(bytes32 => address) public pointers;
function checksumExists(bytes32 checksum) public view returns (bool) {
return pointers[checksum] != address(0);
}
function contentLength(bytes32 checksum)
public
view
returns (uint256 size)
{
if (!checksumExists(checksum)) {
revert ChecksumNotFound(checksum);
}
return SSTORE2.read(pointers[checksum]).length;
}
function addPointer(address pointer) public returns (bytes32 checksum) {
bytes memory content = SSTORE2.read(pointer);
checksum = keccak256(content);
if (pointers[checksum] != address(0)) {
return checksum;
}
pointers[checksum] = pointer;
emit NewChecksum(checksum, content.length);
return checksum;
}
function addContent(bytes memory content)
public
returns (bytes32 checksum, address pointer)
{
checksum = keccak256(content);
if (pointers[checksum] != address(0)) {
return (checksum, pointers[checksum]);
}
pointer = SSTORE2.write(content);
pointers[checksum] = pointer;
emit NewChecksum(checksum, content.length);
return (checksum, pointer);
}
function getPointer(bytes32 checksum)
public
view
returns (address pointer)
{
if (!checksumExists(checksum)) {
revert ChecksumNotFound(checksum);
}
return pointers[checksum];
}
}
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.20;
interface IContentStore {
event NewChecksum(bytes32 indexed checksum, uint256 contentSize);
error ChecksumExists(bytes32 checksum);
error ChecksumNotFound(bytes32 checksum);
function pointers(bytes32 checksum)
external
view
returns (address pointer);
function checksumExists(bytes32 checksum) external view returns (bool);
function contentLength(bytes32 checksum)
external
view
returns (uint256 size);
function addPointer(address pointer) external returns (bytes32 checksum);
function addContent(bytes memory content)
external
returns (bytes32 checksum, address pointer);
function getPointer(bytes32 checksum)
external
view
returns (address pointer);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solady (https://github.com/vectorized/solmady/blob/main/src/utils/SSTORE2.sol)
/// @author Saw-mon-and-Natalie (https://github.com/Saw-mon-and-Natalie)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
library SSTORE2 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
error DeploymentFailed();
error InvalidPointer();
error ReadOutOfBounds();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* WRITE LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function write(bytes memory data) internal returns (address pointer) {
// Note: The assembly block below does not expand the memory.
assembly {
let originalDataLength := mload(data)
// Add 1 to data size since we are prefixing it with a STOP opcode.
let dataSize := add(originalDataLength, 1)
/**
* ------------------------------------------------------------------------------+
* Opcode | Mnemonic | Stack | Memory |
* ------------------------------------------------------------------------------|
* 61 codeSize | PUSH2 codeSize | codeSize | |
* 80 | DUP1 | codeSize codeSize | |
* 60 0xa | PUSH1 0xa | 0xa codeSize codeSize | |
* 3D | RETURNDATASIZE | 0 0xa codeSize codeSize | |
* 39 | CODECOPY | codeSize | [0..codeSize): code |
* 3D | RETURNDATASZIE | 0 codeSize | [0..codeSize): code |
* F3 | RETURN | | [0..codeSize): code |
* 00 | STOP | | |
* ------------------------------------------------------------------------------+
* @dev Prefix the bytecode with a STOP opcode to ensure it cannot be called.
* Also PUSH2 is used since max contract size cap is 24,576 bytes which is less than 2 ** 16.
*/
mstore(
data,
or(
0x61000080600a3d393df300,
// Left shift `dataSize` by 64 so that it lines up with the 0000 after PUSH2.
shl(0x40, dataSize)
)
)
// Deploy a new contract with the generated creation code.
pointer := create(0, add(data, 0x15), add(dataSize, 0xa))
// If `pointer` is zero, revert.
if iszero(pointer) {
// Store the function selector of `DeploymentFailed()`.
mstore(0x00, 0x30116425)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore original length of the variable size `data`.
mstore(data, originalDataLength)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* READ LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function read(address pointer) internal view returns (bytes memory data) {
assembly {
let pointerCodesize := extcodesize(pointer)
if iszero(pointerCodesize) {
// Store the function selector of `InvalidPointer()`.
mstore(0x00, 0x11052bb4)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Offset all indices by 1 to skip the STOP opcode.
let size := sub(pointerCodesize, 1)
// Get the pointer to the free memory and allocate
// enough 32-byte words for the data and the length of the data,
// then copy the code to the allocated memory.
// Masking with 0xffe0 will suffice, since contract size is less than 16 bits.
data := mload(0x40)
mstore(0x40, add(data, and(add(size, 0x3f), 0xffe0)))
mstore(data, size)
extcodecopy(pointer, add(data, 0x20), 1, size)
}
}
function read(address pointer, uint256 start) internal view returns (bytes memory data) {
assembly {
let pointerCodesize := extcodesize(pointer)
if iszero(pointerCodesize) {
// Store the function selector of `InvalidPointer()`.
mstore(0x00, 0x11052bb4)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// If `!(pointer.code.size > start)`, reverts.
// This also handles the case where `start + 1` overflows.
if iszero(gt(pointerCodesize, start)) {
// Store the function selector of `ReadOutOfBounds()`.
mstore(0x00, 0x84eb0dd1)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
let size := sub(pointerCodesize, add(start, 1))
// Get the pointer to the free memory and allocate
// enough 32-byte words for the data and the length of the data,
// then copy the code to the allocated memory.
// Masking with 0xffe0 will suffice, since contract size is less than 16 bits.
data := mload(0x40)
mstore(0x40, add(data, and(add(size, 0x3f), 0xffe0)))
mstore(data, size)
extcodecopy(pointer, add(data, 0x20), add(start, 1), size)
}
}
function read(
address pointer,
uint256 start,
uint256 end
) internal view returns (bytes memory data) {
assembly {
let pointerCodesize := extcodesize(pointer)
if iszero(pointerCodesize) {
// Store the function selector of `InvalidPointer()`.
mstore(0x00, 0x11052bb4)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// If `!(pointer.code.size > end) || (start > end)`, revert.
// This also handles the cases where `end + 1` or `start + 1` overflow.
if iszero(
and(
gt(pointerCodesize, end), // Within bounds.
iszero(gt(start, end)) // Valid range.
)
) {
// Store the function selector of `ReadOutOfBounds()`.
mstore(0x00, 0x84eb0dd1)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
let size := sub(end, start)
// Get the pointer to the free memory and allocate
// enough 32-byte words for the data and the length of the data,
// then copy the code to the allocated memory.
// Masking with 0xffe0 will suffice, since contract size is less than 16 bits.
data := mload(0x40)
mstore(0x40, add(data, and(add(size, 0x3f), 0xffe0)))
mstore(data, size)
extcodecopy(pointer, add(data, 0x20), add(start, 1), size)
}
}
}
{
"compilationTarget": {
"contracts/ethfs/ContentStore.sol": "ContentStore"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"bytes32","name":"checksum","type":"bytes32"}],"name":"ChecksumExists","type":"error"},{"inputs":[{"internalType":"bytes32","name":"checksum","type":"bytes32"}],"name":"ChecksumNotFound","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"checksum","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"contentSize","type":"uint256"}],"name":"NewChecksum","type":"event"},{"inputs":[{"internalType":"bytes","name":"content","type":"bytes"}],"name":"addContent","outputs":[{"internalType":"bytes32","name":"checksum","type":"bytes32"},{"internalType":"address","name":"pointer","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pointer","type":"address"}],"name":"addPointer","outputs":[{"internalType":"bytes32","name":"checksum","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"checksum","type":"bytes32"}],"name":"checksumExists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"checksum","type":"bytes32"}],"name":"contentLength","outputs":[{"internalType":"uint256","name":"size","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"checksum","type":"bytes32"}],"name":"getPointer","outputs":[{"internalType":"address","name":"pointer","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"pointers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]