Description:
Smart contract deployed on Ethereum with Factory features.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/utils/Base64.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/Base64.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides a set of functions to operate with Base64 strings.
*/
library Base64 {
/**
* @dev Base64 Encoding/Decoding Table
* See sections 4 and 5 of https://datatracker.ietf.org/doc/html/rfc4648
*/
string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
string internal constant _TABLE_URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
/**
* @dev Converts a `bytes` to its Bytes64 `string` representation.
*/
function encode(bytes memory data) internal pure returns (string memory) {
return _encode(data, _TABLE, true);
}
/**
* @dev Converts a `bytes` to its Bytes64Url `string` representation.
* Output is not padded with `=` as specified in https://www.rfc-editor.org/rfc/rfc4648[rfc4648].
*/
function encodeURL(bytes memory data) internal pure returns (string memory) {
return _encode(data, _TABLE_URL, false);
}
/**
* @dev Internal table-agnostic conversion
*/
function _encode(bytes memory data, string memory table, bool withPadding) private pure returns (string memory) {
/**
* Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
* https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
*/
if (data.length == 0) return "";
// If padding is enabled, the final length should be `bytes` data length divided by 3 rounded up and then
// multiplied by 4 so that it leaves room for padding the last chunk
// - `data.length + 2` -> Prepare for division rounding up
// - `/ 3` -> Number of 3-bytes chunks (rounded up)
// - `4 *` -> 4 characters for each chunk
// This is equivalent to: 4 * Math.ceil(data.length / 3)
//
// If padding is disabled, the final length should be `bytes` data length multiplied by 4/3 rounded up as
// opposed to when padding is required to fill the last chunk.
// - `4 * data.length` -> 4 characters for each chunk
// - ` + 2` -> Prepare for division rounding up
// - `/ 3` -> Number of 3-bytes chunks (rounded up)
// This is equivalent to: Math.ceil((4 * data.length) / 3)
uint256 resultLength = withPadding ? 4 * ((data.length + 2) / 3) : (4 * data.length + 2) / 3;
string memory result = new string(resultLength);
assembly ("memory-safe") {
// Prepare the lookup table (skip the first "length" byte)
let tablePtr := add(table, 1)
// Prepare result pointer, jump over length
let resultPtr := add(result, 0x20)
let dataPtr := data
let endPtr := add(data, mload(data))
// In some cases, the last iteration will read bytes after the end of the data. We cache the value, and
// set it to zero to make sure no dirty bytes are read in that section.
let afterPtr := add(endPtr, 0x20)
let afterCache := mload(afterPtr)
mstore(afterPtr, 0x00)
// Run over the input, 3 bytes at a time
for {} lt(dataPtr, endPtr) {} {
// Advance 3 bytes
dataPtr := add(dataPtr, 3)
let input := mload(dataPtr)
// To write each character, shift the 3 byte (24 bits) chunk
// 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
// and apply logical AND with 0x3F to bitmask the least significant 6 bits.
// Use this as an index into the lookup table, mload an entire word
// so the desired character is in the least significant byte, and
// mstore8 this least significant byte into the result and continue.
mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
}
// Reset the value that was cached
mstore(afterPtr, afterCache)
if withPadding {
// When data `bytes` is not exactly 3 bytes long
// it is padded with `=` characters at the end
switch mod(mload(data), 3)
case 1 {
mstore8(sub(resultPtr, 1), 0x3d)
mstore8(sub(resultPtr, 2), 0x3d)
}
case 2 {
mstore8(sub(resultPtr, 1), 0x3d)
}
}
}
return result;
}
}
"
},
"contracts/ExecutedPoetryRenderer.sol": {
"content": "// SPDX-License-Identifier: WTFPL
pragma solidity ^0.8.30;
import "solady/src/utils/LibString.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
contract ExecutedPoetryRenderer {
function renderImage(uint256 tokenId, string[] memory files, string[] memory fileContents, uint256 defaultFileIndex) external pure returns (string memory) {
string memory html = _generateEditorHtml(tokenId, files, fileContents, defaultFileIndex);
return string.concat("data:text/html;charset=utf-8;base64,", Base64.encode(bytes(html)));
}
function renderThumbnail(uint256 tokenId, string[] memory files, string[] memory fileContents, uint256 defaultFileIndex) external pure returns (string memory) {
string memory svg = _generateThumbnailSvg(tokenId, files, fileContents, defaultFileIndex);
return string.concat("data:image/svg+xml;base64,", Base64.encode(bytes(svg)));
}
function _generateEditorHtml(uint256 tokenId, string[] memory files, string[] memory fileContents, uint256 defaultFileIndex) internal pure returns (string memory) {
string memory fileListHtml = _generateFileListHtml(files, defaultFileIndex);
return string.concat(
"<!DOCTYPE html>",
"<html lang=\"en\">",
"<head>",
"<meta charset=\"UTF-8\">",
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
"<title>Executed Poetry #", _toString(tokenId), "</title>",
"<style>",
"* { margin: 0; padding: 0; box-sizing: border-box; }",
"html, body { width: 100%; height: 100%; margin: 0; padding: 0; }",
"body { font-family: 'Monaco', 'Menlo', 'Courier New', monospace; font-size: 12px; line-height: 1.4; color: #e0e0e0; background: #1e1e1e; display: flex; height: 100vh; overflow: hidden; }",
".file-list { width: 120px; min-width: 120px; max-width: 120px; background: #252526; border-right: 1px solid #3e3e42; overflow-y: auto; overflow-x: hidden; padding: 6px 0; flex-shrink: 0; }",
".file-list::-webkit-scrollbar { width: 4px; }",
".file-list::-webkit-scrollbar-track { background: #252526; }",
".file-list::-webkit-scrollbar-thumb { background: #3e3e42; border-radius: 2px; }",
".file-list::-webkit-scrollbar-thumb:hover { background: #4e4e52; }",
".file-item { padding: 4px 8px; cursor: pointer; color: #cccccc; transition: background 0.15s; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
".file-item:hover { background: #2a2d2e; }",
".file-item.active { background: #094771; color: #ffffff; }",
".editor { flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0; }",
".editor-header { background: #2d2d30; padding: 6px 12px; border-bottom: 1px solid #3e3e42; color: #cccccc; font-size: 11px; flex-shrink: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
".editor-content { flex: 1; overflow: auto; padding: 12px; white-space: pre; color: #d4d4d4; font-size: 12px; }",
".editor-content::-webkit-scrollbar { width: 6px; height: 6px; }",
".editor-content::-webkit-scrollbar-track { background: #1e1e1e; }",
".editor-content::-webkit-scrollbar-thumb { background: #3e3e42; border-radius: 3px; }",
".editor-content::-webkit-scrollbar-thumb:hover { background: #4e4e52; }",
".line-number { color: #858585; margin-right: 12px; user-select: none; display: inline-block; min-width: 32px; text-align: right; font-size: 11px; }",
".code-line { display: flex; min-height: 1.4em; }",
"@media (max-width: 800px) {",
" .file-list { width: 100px; min-width: 100px; max-width: 100px; }",
" .file-item { font-size: 10px; padding: 3px 6px; }",
" .editor-header { padding: 5px 10px; font-size: 10px; }",
" .editor-content { padding: 10px; font-size: 11px; }",
" .line-number { margin-right: 10px; min-width: 28px; font-size: 10px; }",
"}",
"@media (max-width: 500px) {",
" .file-list { width: 90px; min-width: 90px; max-width: 90px; }",
" .file-item { font-size: 9px; padding: 3px 4px; }",
" .editor-header { padding: 4px 8px; font-size: 9px; }",
" .editor-content { padding: 8px; font-size: 10px; }",
" .line-number { margin-right: 8px; min-width: 24px; font-size: 9px; }",
"}",
"@media (max-width: 400px) {",
" .file-list { width: 80px; min-width: 80px; max-width: 80px; }",
" .file-item { font-size: 8px; padding: 2px 4px; }",
" .editor-header { padding: 3px 6px; font-size: 8px; }",
" .editor-content { padding: 6px; font-size: 9px; }",
" .line-number { margin-right: 6px; min-width: 20px; font-size: 8px; }",
"}",
"</style>",
"</head>",
"<body>",
"<div class=\"file-list\">",
fileListHtml,
"</div>",
"<div class=\"editor\">",
"<div class=\"editor-header\">",
"<span id=\"current-file\">", files[defaultFileIndex], "</span>",
"</div>",
"<div class=\"editor-content\" id=\"editor-content\"></div>",
"</div>",
"<script>",
"const files = [", _formatFileArray(files), "];",
"const fileContents = [", _formatFileContentsArray(fileContents), "];",
"let currentIndex = ", _toString(defaultFileIndex), ";",
"const fileItems = document.querySelectorAll('.file-item');",
"const editorContent = document.getElementById('editor-content');",
"const currentFileSpan = document.getElementById('current-file');",
"function formatCodeWithLineNumbers(code) {",
" const lines = code.split('\\
');",
" return lines.map((line, i) => {",
" const escaped = escapeHtml(line || ' ');",
" return '<div class=\"code-line\"><span class=\"line-number\">' + (i + 1) + '</span><span>' + escaped + '</span></div>';",
" }).join('');",
"}",
"function escapeHtml(text) {",
" const div = document.createElement('div');",
" div.textContent = text;",
" return div.innerHTML;",
"}",
"editorContent.innerHTML = formatCodeWithLineNumbers(fileContents[currentIndex]);",
"fileItems.forEach((item, index) => {",
" item.addEventListener('click', () => {",
" fileItems.forEach(i => i.classList.remove('active'));",
" item.classList.add('active');",
" currentIndex = index;",
" currentFileSpan.textContent = files[index];",
" editorContent.innerHTML = formatCodeWithLineNumbers(fileContents[index]);",
" });",
"});",
"</script>",
"</body>",
"</html>"
);
}
function _generateFileListHtml(string[] memory files, uint256 defaultIndex) internal pure returns (string memory) {
string memory result = "";
for (uint256 i = 0; i < files.length; i++) {
string memory activeClass = i == defaultIndex ? " active" : "";
result = string.concat(
result,
"<div class=\"file-item", activeClass, "\" data-index=\"", _toString(i), "\">",
_escapeHtml(files[i]),
"</div>"
);
}
return result;
}
function _extractLine(bytes memory data, uint256 start, uint256 length) internal pure returns (string memory) {
if (length == 0) return " ";
bytes memory line = new bytes(length);
for (uint256 i = 0; i < length; i++) {
line[i] = data[start + i];
}
return string(line);
}
function _formatFileArray(string[] memory files) internal pure returns (string memory) {
string memory result = "";
for (uint256 i = 0; i < files.length; i++) {
if (i > 0) result = string.concat(result, ",");
string memory escaped = _escapeJsString(files[i]);
result = string.concat(result, "\"", escaped, "\"");
}
return result;
}
function _formatFileContentsArray(string[] memory contents) internal pure returns (string memory) {
string memory result = "";
for (uint256 i = 0; i < contents.length; i++) {
if (i > 0) result = string.concat(result, ",");
string memory escaped = _escapeJsString(contents[i]);
result = string.concat(result, "\"", escaped, "\"");
}
return result;
}
function _escapeJsString(string memory input) internal pure returns (string memory) {
bytes memory inputBytes = bytes(input);
bytes memory output = new bytes(inputBytes.length * 2);
uint256 outputIndex = 0;
for (uint256 i = 0; i < inputBytes.length; i++) {
bytes1 char = inputBytes[i];
if (char == 0x22) {
output[outputIndex++] = 0x5C;
output[outputIndex++] = 0x22;
} else if (char == 0x5C) {
output[outputIndex++] = 0x5C;
output[outputIndex++] = 0x5C;
} else if (char == 0x0A) {
output[outputIndex++] = 0x5C;
output[outputIndex++] = 0x6E;
} else if (char == 0x0D) {
output[outputIndex++] = 0x5C;
output[outputIndex++] = 0x72;
} else if (char == 0x09) {
output[outputIndex++] = 0x5C;
output[outputIndex++] = 0x74;
} else {
output[outputIndex++] = char;
}
}
bytes memory resultBytes = new bytes(outputIndex);
for (uint256 i = 0; i < outputIndex; i++) {
resultBytes[i] = output[i];
}
return string(resultBytes);
}
function _escapeHtml(string memory input) internal pure returns (string memory) {
string memory result = LibString.replace(input, "&", "&");
result = LibString.replace(result, "<", "<");
result = LibString.replace(result, ">", ">");
result = LibString.replace(result, "\"", """);
result = LibString.replace(result, "'", "'");
return result;
}
function _generateThumbnailSvg(uint256 /* tokenId */, string[] memory files, string[] memory fileContents, uint256 defaultFileIndex) internal pure returns (string memory) {
string memory fileName = files[defaultFileIndex];
string memory code = fileContents[defaultFileIndex];
string memory codeLines = _generateCodeLinesSvg(code, 20);
return string.concat(
"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"512\" height=\"512\"><rect width=\"512\" height=\"512\" fill=\"#1e1e1e\"/><rect x=\"0\" y=\"0\" width=\"120\" height=\"512\" fill=\"#252526\"/>",
_generateFileListSvgMinimal(files, defaultFileIndex),
"<rect x=\"120\" y=\"0\" width=\"392\" height=\"512\" fill=\"#1e1e1e\"/><rect x=\"120\" y=\"0\" width=\"392\" height=\"30\" fill=\"#2d2d30\"/>",
"<text x=\"130\" y=\"20\" font-family=\"monospace\" font-size=\"11\" fill=\"#ccc\">", _escapeSvgMinimal(fileName), "</text>",
codeLines,
"</svg>"
);
}
function _generateCodeLinesSvg(string memory code, uint256 maxLines) internal pure returns (string memory) {
bytes memory codeBytes = bytes(code);
string memory result = "";
uint256 lineCount = 0;
uint256 y = 50;
uint256 startIndex = 0;
for (uint256 i = 0; i < codeBytes.length && lineCount < maxLines; i++) {
if (codeBytes[i] == 0x0A) {
uint256 lineLength = i - startIndex;
string memory line = _extractLine(codeBytes, startIndex, lineLength);
result = string.concat(
result,
"<text x=\"130\" y=\"", _toString(y), "\" font-family=\"monospace\" font-size=\"11\" fill=\"#d4d4d4\" xml:space=\"preserve\">", _escapeSvgWithSpaces(line), "</text>"
);
y += 16;
lineCount++;
startIndex = i + 1;
}
}
if (lineCount < maxLines && startIndex < codeBytes.length) {
uint256 lastLineLength = codeBytes.length - startIndex;
string memory lastLine = _extractLine(codeBytes, startIndex, lastLineLength);
result = string.concat(
result,
"<text x=\"130\" y=\"", _toString(y), "\" font-family=\"monospace\" font-size=\"11\" fill=\"#d4d4d4\" xml:space=\"preserve\">", _escapeSvgWithSpaces(lastLine), "</text>"
);
}
return result;
}
function _escapeSvgWithSpaces(string memory input) internal pure returns (string memory) {
bytes memory inputBytes = bytes(input);
bytes memory output = new bytes(inputBytes.length * 6);
uint256 outputIndex = 0;
for (uint256 i = 0; i < inputBytes.length; i++) {
bytes1 char = inputBytes[i];
if (char == 0x3c) {
output[outputIndex++] = 0x26;
output[outputIndex++] = 0x6c;
output[outputIndex++] = 0x74;
output[outputIndex++] = 0x3b;
} else if (char == 0x3e) {
output[outputIndex++] = 0x26;
output[outputIndex++] = 0x67;
output[outputIndex++] = 0x74;
output[outputIndex++] = 0x3b;
} else if (char == 0x26) {
output[outputIndex++] = 0x26;
output[outputIndex++] = 0x61;
output[outputIndex++] = 0x6d;
output[outputIndex++] = 0x70;
output[outputIndex++] = 0x3b;
} else if (char == 0x20) {
output[outputIndex++] = 0x26;
output[outputIndex++] = 0x23;
output[outputIndex++] = 0x31;
output[outputIndex++] = 0x36;
output[outputIndex++] = 0x30;
output[outputIndex++] = 0x3b;
} else {
output[outputIndex++] = char;
}
}
bytes memory resultBytes = new bytes(outputIndex);
for (uint256 i = 0; i < outputIndex; i++) {
resultBytes[i] = output[i];
}
return string(resultBytes);
}
function _generateFileListSvgMinimal(string[] memory files, uint256 defaultFileIndex) internal pure returns (string memory) {
string memory result = "";
string[12] memory yPositions = ["8", "28", "48", "68", "88", "108", "128", "148", "168", "188", "208", "228"];
string[12] memory yTexts = ["20", "40", "60", "80", "100", "120", "140", "160", "180", "200", "220", "240"];
for (uint256 i = 0; i < files.length && i < 12; i++) {
string memory fill = i == defaultFileIndex ? "#094771" : "#252526";
string memory color = i == defaultFileIndex ? "#fff" : "#ccc";
result = string.concat(
result,
"<rect x=\"0\" y=\"", yPositions[i], "\" width=\"120\" height=\"20\" fill=\"", fill, "\"/>",
"<text x=\"8\" y=\"", yTexts[i], "\" font-family=\"monospace\" font-size=\"11\" fill=\"", color, "\">", _escapeSvgMinimal(files[i]), "</text>"
);
}
return result;
}
function _escapeSvgMinimal(string memory input) internal pure returns (string memory) {
bytes memory inputBytes = bytes(input);
bytes memory output = new bytes(inputBytes.length * 6);
uint256 outputIndex = 0;
for (uint256 i = 0; i < inputBytes.length; i++) {
bytes1 char = inputBytes[i];
if (char == 0x3c) {
output[outputIndex++] = 0x26;
output[outputIndex++] = 0x6c;
output[outputIndex++] = 0x74;
output[outputIndex++] = 0x3b;
} else if (char == 0x3e) {
output[outputIndex++] = 0x26;
output[outputIndex++] = 0x67;
output[outputIndex++] = 0x74;
output[outputIndex++] = 0x3b;
} else if (char == 0x26) {
output[outputIndex++] = 0x26;
output[outputIndex++] = 0x61;
output[outputIndex++] = 0x6d;
output[outputIndex++] = 0x70;
output[outputIndex++] = 0x3b;
} else {
output[outputIndex++] = char;
}
}
bytes memory resultBytes = new bytes(outputIndex);
for (uint256 i = 0; i < outputIndex; i++) {
resultBytes[i] = output[i];
}
return string(resultBytes);
}
function _generateFileListSvg(string[] memory files, uint256 defaultFileIndex) internal pure returns (string memory) {
string memory result = "";
uint256 y = 20;
for (uint256 i = 0; i < files.length; i++) {
string memory fillColor = i == defaultFileIndex ? "#094771" : "#252526";
string memory textColor = i == defaultFileIndex ? "#ffffff" : "#cccccc";
result = string.concat(
result,
"<rect x=\"0\" y=\"", _toString(y - 12), "\" width=\"120\" height=\"20\" fill=\"", fillColor, "\"/>",
"<text x=\"8\" y=\"", _toString(y), "\" font-family=\"Monaco, Menlo, monospace\" font-size=\"11\" fill=\"", textColor, "\">", _escapeSvg(files[i]), "</text>"
);
y += 20;
}
return result;
}
function _truncateCode(string memory code, uint256 maxLines) internal pure returns (string memory) {
bytes memory codeBytes = bytes(code);
uint256 lineCount = 0;
uint256 endIndex = codeBytes.length;
for (uint256 i = 0; i < codeBytes.length && lineCount < maxLines; i++) {
if (codeBytes[i] == 0x0A) {
lineCount++;
if (lineCount >= maxLines) {
endIndex = i;
break;
}
}
}
if (endIndex < codeBytes.length) {
bytes memory truncated = new bytes(endIndex);
for (uint256 i = 0; i < endIndex; i++) {
truncated[i] = codeBytes[i];
}
return string(truncated);
}
return code;
}
function _escapeSvg(string memory input) internal pure returns (string memory) {
string memory result = LibString.replace(input, "&", "&");
result = LibString.replace(result, "<", "<");
result = LibString.replace(result, ">", ">");
result = LibString.replace(result, "\"", """);
result = LibString.replace(result, "'", "'");
return result;
}
function _toString(uint256 value) internal pure returns (string memory str) {
if (value == 0) return "0";
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
str = string(buffer);
}
}
"
},
"solady/src/utils/LibBytes.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for byte related operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBytes.sol)
library LibBytes {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRUCTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Goated bytes storage struct that totally MOGs, no cap, fr.
/// Uses less gas and bytecode than Solidity's native bytes storage. It's meta af.
/// Packs length with the first 31 bytes if <255 bytes, so it’s mad tight.
struct BytesStorage {
bytes32 _spacer;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The constant returned when the `search` is not found in the bytes.
uint256 internal constant NOT_FOUND = type(uint256).max;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BYTE STORAGE OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sets the value of the bytes storage `$` to `s`.
function set(BytesStorage storage $, bytes memory s) internal {
/// @solidity memory-safe-assembly
assembly {
let n := mload(s)
let packed := or(0xff, shl(8, n))
for { let i := 0 } 1 {} {
if iszero(gt(n, 0xfe)) {
i := 0x1f
packed := or(n, shl(8, mload(add(s, i))))
if iszero(gt(n, i)) { break }
}
let o := add(s, 0x20)
mstore(0x00, $.slot)
for { let p := keccak256(0x00, 0x20) } 1 {} {
sstore(add(p, shr(5, i)), mload(add(o, i)))
i := add(i, 0x20)
if iszero(lt(i, n)) { break }
}
break
}
sstore($.slot, packed)
}
}
/// @dev Sets the value of the bytes storage `$` to `s`.
function setCalldata(BytesStorage storage $, bytes calldata s) internal {
/// @solidity memory-safe-assembly
assembly {
let packed := or(0xff, shl(8, s.length))
for { let i := 0 } 1 {} {
if iszero(gt(s.length, 0xfe)) {
i := 0x1f
packed := or(s.length, shl(8, shr(8, calldataload(s.offset))))
if iszero(gt(s.length, i)) { break }
}
mstore(0x00, $.slot)
for { let p := keccak256(0x00, 0x20) } 1 {} {
sstore(add(p, shr(5, i)), calldataload(add(s.offset, i)))
i := add(i, 0x20)
if iszero(lt(i, s.length)) { break }
}
break
}
sstore($.slot, packed)
}
}
/// @dev Sets the value of the bytes storage `$` to the empty bytes.
function clear(BytesStorage storage $) internal {
delete $._spacer;
}
/// @dev Returns whether the value stored is `$` is the empty bytes "".
function isEmpty(BytesStorage storage $) internal view returns (bool) {
return uint256($._spacer) & 0xff == uint256(0);
}
/// @dev Returns the length of the value stored in `$`.
function length(BytesStorage storage $) internal view returns (uint256 result) {
result = uint256($._spacer);
/// @solidity memory-safe-assembly
assembly {
let n := and(0xff, result)
result := or(mul(shr(8, result), eq(0xff, n)), mul(n, iszero(eq(0xff, n))))
}
}
/// @dev Returns the value stored in `$`.
function get(BytesStorage storage $) internal view returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let o := add(result, 0x20)
let packed := sload($.slot)
let n := shr(8, packed)
for { let i := 0 } 1 {} {
if iszero(eq(or(packed, 0xff), packed)) {
mstore(o, packed)
n := and(0xff, packed)
i := 0x1f
if iszero(gt(n, i)) { break }
}
mstore(0x00, $.slot)
for { let p := keccak256(0x00, 0x20) } 1 {} {
mstore(add(o, i), sload(add(p, shr(5, i))))
i := add(i, 0x20)
if iszero(lt(i, n)) { break }
}
break
}
mstore(result, n) // Store the length of the memory.
mstore(add(o, n), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(o, n), 0x20)) // Allocate memory.
}
}
/// @dev Returns the uint8 at index `i`. If out-of-bounds, returns 0.
function uint8At(BytesStorage storage $, uint256 i) internal view returns (uint8 result) {
/// @solidity memory-safe-assembly
assembly {
for { let packed := sload($.slot) } 1 {} {
if iszero(eq(or(packed, 0xff), packed)) {
if iszero(gt(i, 0x1e)) {
result := byte(i, packed)
break
}
if iszero(gt(i, and(0xff, packed))) {
mstore(0x00, $.slot)
let j := sub(i, 0x1f)
result := byte(and(j, 0x1f), sload(add(keccak256(0x00, 0x20), shr(5, j))))
}
break
}
if iszero(gt(i, shr(8, packed))) {
mstore(0x00, $.slot)
result := byte(and(i, 0x1f), sload(add(keccak256(0x00, 0x20), shr(5, i))))
}
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BYTES OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `subject` all occurrences of `needle` replaced with `replacement`.
function replace(bytes memory subject, bytes memory needle, bytes memory replacement)
internal
pure
returns (bytes memory result)
{
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let needleLen := mload(needle)
let replacementLen := mload(replacement)
let d := sub(result, subject) // Memory difference.
let i := add(subject, 0x20) // Subject bytes pointer.
mstore(0x00, add(i, mload(subject))) // End of subject.
if iszero(gt(needleLen, mload(subject))) {
let subjectSearchEnd := add(sub(mload(0x00), needleLen), 1)
let h := 0 // The hash of `needle`.
if iszero(lt(needleLen, 0x20)) { h := keccak256(add(needle, 0x20), needleLen) }
let s := mload(add(needle, 0x20))
for { let m := shl(3, sub(0x20, and(needleLen, 0x1f))) } 1 {} {
let t := mload(i)
// Whether the first `needleLen % 32` bytes of `subject` and `needle` matches.
if iszero(shr(m, xor(t, s))) {
if h {
if iszero(eq(keccak256(i, needleLen), h)) {
mstore(add(i, d), t)
i := add(i, 1)
if iszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
// Copy the `replacement` one word at a time.
for { let j := 0 } 1 {} {
mstore(add(add(i, d), j), mload(add(add(replacement, 0x20), j)))
j := add(j, 0x20)
if iszero(lt(j, replacementLen)) { break }
}
d := sub(add(d, replacementLen), needleLen)
if needleLen {
i := add(i, needleLen)
if iszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
mstore(add(i, d), t)
i := add(i, 1)
if iszero(lt(i, subjectSearchEnd)) { break }
}
}
let end := mload(0x00)
let n := add(sub(d, add(result, 0x20)), end)
// Copy the rest of the bytes one word at a time.
for {} lt(i, end) { i := add(i, 0x20) } { mstore(add(i, d), mload(i)) }
let o := add(i, d)
mstore(o, 0) // Zeroize the slot after the bytes.
mstore(0x40, add(o, 0x20)) // Allocate memory.
mstore(result, n) // Store the length.
}
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from left to right, starting from `from`.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function indexOf(bytes memory subject, bytes memory needle, uint256 from)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
result := not(0) // Initialize to `NOT_FOUND`.
for { let subjectLen := mload(subject) } 1 {} {
if iszero(mload(needle)) {
result := from
if iszero(gt(from, subjectLen)) { break }
result := subjectLen
break
}
let needleLen := mload(needle)
let subjectStart := add(subject, 0x20)
subject := add(subjectStart, from)
let end := add(sub(add(subjectStart, subjectLen), needleLen), 1)
let m := shl(3, sub(0x20, and(needleLen, 0x1f)))
let s := mload(add(needle, 0x20))
if iszero(and(lt(subject, end), lt(from, subjectLen))) { break }
if iszero(lt(needleLen, 0x20)) {
for { let h := keccak256(add(needle, 0x20), needleLen) } 1 {} {
if iszero(shr(m, xor(mload(subject), s))) {
if eq(keccak256(subject, needleLen), h) {
result := sub(subject, subjectStart)
break
}
}
subject := add(subject, 1)
if iszero(lt(subject, end)) { break }
}
break
}
for {} 1 {} {
if iszero(shr(m, xor(mload(subject), s))) {
result := sub(subject, subjectStart)
break
}
subject := add(subject, 1)
if iszero(lt(subject, end)) { break }
}
break
}
}
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from left to right, starting from `from`. Optimized for byte needles.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function indexOfByte(bytes memory subject, bytes1 needle, uint256 from)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
result := not(0) // Initialize to `NOT_FOUND`.
if gt(mload(subject), from) {
let start := add(subject, 0x20)
let end := add(start, mload(subject))
let m := div(not(0), 255) // `0x0101 ... `.
let h := mul(byte(0, needle), m) // Replicating needle mask.
m := not(shl(7, m)) // `0x7f7f ... `.
for { let i := add(start, from) } 1 {} {
let c := xor(mload(i), h) // Load 32-byte chunk and xor with mask.
c := not(or(or(add(and(c, m), m), c), m)) // Each needle byte will be `0x80`.
if c {
c := and(not(shr(shl(3, sub(end, i)), not(0))), c) // Truncate bytes past the end.
if c {
let r := shl(7, lt(0x8421084210842108cc6318c6db6d54be, c)) // Save bytecode.
r := or(shl(6, lt(0xffffffffffffffff, shr(r, c))), r)
// forgefmt: disable-next-item
result := add(sub(i, start), shr(3, xor(byte(and(0x1f, shr(byte(24,
mul(0x02040810204081, shr(r, c))), 0x8421084210842108cc6318c6db6d54be)),
0xc0c8c8d0c8e8d0d8c8e8e0e8d0d8e0f0c8d0e8d0e0e0d8f0d0d0e0d8f8f8f8f8), r)))
break
}
}
i := add(i, 0x20)
if iszero(lt(i, end)) { break }
}
}
}
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from left to right. Optimized for byte needles.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function indexOfByte(bytes memory subject, bytes1 needle)
internal
pure
returns (uint256 result)
{
return indexOfByte(subject, needle, 0);
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from left to right.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function indexOf(bytes memory subject, bytes memory needle) internal pure returns (uint256) {
return indexOf(subject, needle, 0);
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from right to left, starting from `from`.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function lastIndexOf(bytes memory subject, bytes memory needle, uint256 from)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
for {} 1 {} {
result := not(0) // Initialize to `NOT_FOUND`.
let needleLen := mload(needle)
if gt(needleLen, mload(subject)) { break }
let w := result
let fromMax := sub(mload(subject), needleLen)
if iszero(gt(fromMax, from)) { from := fromMax }
let end := add(add(subject, 0x20), w)
subject := add(add(subject, 0x20), from)
if iszero(gt(subject, end)) { break }
// As this function is not too often used,
// we shall simply use keccak256 for smaller bytecode size.
for { let h := keccak256(add(needle, 0x20), needleLen) } 1 {} {
if eq(keccak256(subject, needleLen), h) {
result := sub(subject, add(end, 1))
break
}
subject := add(subject, w) // `sub(subject, 1)`.
if iszero(gt(subject, end)) { break }
}
break
}
}
}
/// @dev Returns the byte index of the first location of `needle` in `subject`,
/// needleing from right to left.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found.
function lastIndexOf(bytes memory subject, bytes memory needle)
internal
pure
returns (uint256)
{
return lastIndexOf(subject, needle, type(uint256).max);
}
/// @dev Returns true if `needle` is found in `subject`, false otherwise.
function contains(bytes memory subject, bytes memory needle) internal pure returns (bool) {
return indexOf(subject, needle) != NOT_FOUND;
}
/// @dev Returns whether `subject` starts with `needle`.
function startsWith(bytes memory subject, bytes memory needle)
internal
pure
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(needle)
// Just using keccak256 directly is actually cheaper.
let t := eq(keccak256(add(subject, 0x20), n), keccak256(add(needle, 0x20), n))
result := lt(gt(n, mload(subject)), t)
}
}
/// @dev Returns whether `subject` ends with `needle`.
function endsWith(bytes memory subject, bytes memory needle)
internal
pure
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(needle)
let notInRange := gt(n, mload(subject))
// `subject + 0x20 + max(subject.length - needle.length, 0)`.
let t := add(add(subject, 0x20), mul(iszero(notInRange), sub(mload(subject), n)))
// Just using keccak256 directly is actually cheaper.
result := gt(eq(keccak256(t, n), keccak256(add(needle, 0x20), n)), notInRange)
}
}
/// @dev Returns `subject` repeated `times`.
function repeat(bytes memory subject, uint256 times)
internal
pure
returns (bytes memory result)
{
/// @solidity memory-safe-assembly
assembly {
let l := mload(subject) // Subject length.
if iszero(or(iszero(times), iszero(l))) {
result := mload(0x40)
subject := add(subject, 0x20)
let o := add(result, 0x20)
for {} 1 {} {
// Copy the `subject` one word at a time.
for { let j := 0 } 1 {} {
mstore(add(o, j), mload(add(subject, j)))
j := add(j, 0x20)
if iszero(lt(j, l)) { break }
}
o := add(o, l)
times := sub(times, 1)
if iszero(times) { break }
}
mstore(o, 0) // Zeroize the slot after the bytes.
mstore(0x40, add(o, 0x20)) // Allocate memory.
mstore(result, sub(o, add(result, 0x20))) // Store the length.
}
}
}
/// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
/// `start` and `end` are byte offsets.
function slice(bytes memory subject, uint256 start, uint256 end)
internal
pure
returns (bytes memory result)
{
/// @solidity memory-safe-assembly
assembly {
let l := mload(subject) // Subject length.
if iszero(gt(l, end)) { end := l }
if iszero(gt(l, start)) { start := l }
if lt(start, end) {
result := mload(0x40)
let n := sub(end, start)
let i := add(subject, start)
let w := not(0x1f)
// Copy the `subject` one word at a time, backwards.
for { let j := and(add(n, 0x1f), w) } 1 {} {
mstore(add(result, j), mload(add(i, j)))
j := add(j, w) // `sub(j, 0x20)`.
if iszero(j) { break }
}
let o := add(add(result, 0x20), n)
mstore(o, 0) // Zeroize the slot after the bytes.
mstore(0x40, add(o, 0x20)) // Allocate memory.
mstore(result, n) // Store the length.
}
}
}
/// @dev Returns a copy of `subject` sliced from `start` to the end of the bytes.
/// `start` is a byte offset.
function slice(bytes memory subject, uint256 start)
internal
pure
returns (bytes memory result)
{
result = slice(subject, start, type(uint256).max);
}
/// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
/// `start` and `end` are byte offsets. Faster than Solidity's native slicing.
function sliceCalldata(bytes calldata subject, uint256 start, uint256 end)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
end := xor(end, mul(xor(end, subject.length), lt(subject.length, end)))
start := xor(start, mul(xor(start, subject.length), lt(subject.length, start)))
result.offset := add(subject.offset, start)
result.length := mul(lt(start, end), sub(end, start))
}
}
/// @dev Returns a copy of `subject` sliced from `start` to the end of the bytes.
/// `start` is a byte offset. Faster than Solidity's native slicing.
function sliceCalldata(bytes calldata subject, uint256 start)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
start := xor(start, mul(xor(start, subject.length), lt(subject.length, start)))
result.offset := add(subject.offset, start)
result.length := mul(lt(start, subject.length), sub(subject.length, start))
}
}
/// @dev Reduces the size of `subject` to `n`.
/// If `n` is greater than the size of `subject`, this will be a no-op.
function truncate(bytes memory subject, uint256 n)
internal
pure
returns (bytes memory result)
{
/// @solidity memory-safe-assembly
assembly {
result := subject
mstore(mul(lt(n, mload(result)), result), n)
}
}
/// @dev Returns a copy of `subject`, with the length reduced to `n`.
/// If `n` is greater than the size of `subject`, this will be a no-op.
function truncatedCalldata(bytes calldata subject, uint256 n)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
result.offset := subject.offset
result.length := xor(n, mul(xor(n, subject.length), lt(subject.length, n)))
}
}
/// @dev Returns all the indices of `needle` in `subject`.
/// The indices are byte offsets.
function indicesOf(bytes memory subject, bytes memory needle)
internal
pure
returns (uint256[] memory result)
{
/// @solidity memory-safe-assembly
assembly {
let searchLen := mload(needle)
if iszero(gt(searchLen, mload(subject))) {
result := mload(0x40)
let i := add(subject, 0x20)
let o := add(result, 0x20)
let subjectSearchEnd := add(sub(add(i, mload(subject)), searchLen), 1)
let h := 0 // The hash of `needle`.
if iszero(lt(searchLen, 0x20)) { h := keccak256(add(needle, 0x20), searchLen) }
let s := mload(add(needle, 0x20))
for { let m := shl(3, sub(0x20, and(searchLen, 0x1f))) } 1 {} {
let t := mload(i)
// Whether the first `searchLen % 32` bytes of `subject` and `needle` matches.
if iszero(shr(m, xor(t, s))) {
if h {
if iszero(eq(keccak256(i, searchLen), h)) {
i := add(i, 1)
if iszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
mstore(o, sub(i, add(subject, 0x20))) // Append to `result`.
o := add(o, 0x20)
i := add(i, searchLen) // Advance `i` by `searchLen`.
if searchLen {
if iszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
i := add(i, 1)
if iszero(lt(i, subjectSearchEnd)) { break }
}
mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store the length of `result`.
// Allocate memory for result.
// We allocate one more word, so this array can be recycled for {split}.
mstore(0x40, add(o, 0x20))
}
}
}
/// @dev Returns an arrays of bytess based on the `delimiter` inside of the `subject` bytes.
function split(bytes memory subject, bytes memory delimiter)
internal
pure
returns (bytes[] memory result)
{
uint256[] memory indices = indicesOf(subject, delimiter);
/// @solidity memory-safe-assembly
assembly {
let w := not(0x1f)
let indexPtr := add(indices, 0x20)
let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1)))
mstore(add(indicesEnd, w), mload(subject))
mstore(indices, add(mload(indices), 1))
for { let prevIndex := 0 } 1 {} {
let index := mload(indexPtr)
mstore(indexPtr, 0x60)
if iszero(eq(index, prevIndex)) {
let element := mload(0x40)
let l := sub(index, prevIndex)
mstore(element, l) // Store the length of the element.
// Copy the `subject` one word at a time, backwards.
for { let o := and(add(l, 0x1f), w) } 1 {} {
mstore(add(element, o), mload(add(add(subject, prevIndex), o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
mstore(add(add(element, 0x20), l), 0) // Zeroize the slot after the bytes.
// Allocate memory for the length and the bytes, rounded up to a multiple of 32.
mstore(0x40, add(element, and(add(l, 0x3f), w)))
mstore(indexPtr, element) // Store the `element` into the array.
}
prevIndex := add(index, mload(delimiter))
indexPtr := add(indexPtr, 0x20)
if iszero(lt(indexPtr, indicesEnd)) { break }
}
result := indices
if iszero(mload(delimiter)) {
result := add(indices, 0x20)
mstore(result, sub(mload(indices), 2))
}
}
}
/// @dev Returns a concatenated bytes of `a` and `b`.
/// Cheaper than `bytes.concat()` and does not de-align the free memory pointer.
function concat(bytes memory a, bytes memory b) internal pure returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let w := not(0x1f)
let aLen := mload(a)
// Copy `a` one word at a time, backwards.
for { let o := and(add(aLen, 0x20), w) } 1 {} {
mstore(add(result, o), mload(add(a, o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
let bLen := mload(b)
let output := add(result, aLen)
// Copy `b` one word at a time, backwards.
for { let o := and(add(bLen, 0x20), w) } 1 {} {
mstore(add(output, o), mload(add(b, o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
let totalLen := add(aLen, bLen)
let last := add(add(result, 0x20), totalLen)
mstore(last, 0) // Zeroize the slot after the bytes.
mstore(result, totalLen) // Store the length.
mstore(0x40, add(last, 0x20)) // Allocate memory.
}
}
/// @dev Returns whether `a` equals `b`.
function eq(bytes memory a, bytes memory b) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b)))
}
}
/// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small bytes.
function eqs(bytes memory a, bytes32 b) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
// These should be evaluated on compile time, as far as possible.
let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`.
let x := not(or(m, or(b, add(m, and(b, m)))))
let r := shl(7, iszero(iszero(shr(128, x))))
r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x))))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))),
xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20)))))
}
}
/// @dev Returns 0 if `a == b`, -1 if `a < b`, +1 if `a > b`.
/// If `a` == b[:a.length]`, and `a.length < b.length`, returns -1.
function cmp(bytes memory a, bytes memory b) internal pure returns (int256 result) {
/// @solidity memory-safe-assembly
assembly {
let aLen := mload(a)
let bLen := mload(b)
let n := and(xor(aLen, mul(xor(aLen, bLen), lt(bLen, aLen))), not(0x1f))
if n {
for { let i := 0x20 } 1 {} {
let x := mload(add(a, i))
let y := mload(add(b, i))
if iszero(or(xor(x, y), eq(i, n))) {
i := add(i, 0x20)
continue
}
result := sub(gt(x, y), lt(x, y))
break
}
}
// forgefmt: disable-next-item
if iszero(result) {
let l := 0x201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201
let x := and(mload(add(add(a, 0x20), n)), shl(shl(3, byte(sub(aLen, n), l)), not(0)))
let y := and(mload(add(add(b, 0x20), n)), shl(shl(3, byte(sub(bLen, n), l)), not(0)))
result := sub(gt(x, y), lt(x, y))
if iszero(result) { result := sub(gt(aLen, bLen), lt(aLen, bLen)) }
}
}
}
/// @dev Directly returns `a` without copying.
function directReturn(bytes memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
// Assumes that the bytes does not start from the scratch space.
let retStart := sub(a, 0x20)
let retUnpaddedSize := add(mload(a), 0x40)
// Right pad with zeroes. Just in case the bytes is produced
// by a method that doesn't zero right pad.
mstore(add(retStart, retUnpaddedSize), 0)
mstore(retStart, 0x20) // Store the return offset.
// End the transaction, returning the bytes.
return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize)))
}
}
/// @dev Directly returns `a` with minimal copying.
function directReturn(bytes[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let n := mload(a) // `a.length`.
let o := add(a, 0x20) // Start of elements in `a`.
let u := a // Highest memory slot.
let w := not(0x1f)
for { let i := 0 } iszero(eq(i, n)) { i := add(i, 1) } {
let c := add(o, shl(5, i)) // Location of pointer to `a[i]`.
let s := mload(c) // `a[i]`.
let l := mload(s) // `a[i].length`.
let r := and(l, 0x1f) // `a[i].length % 32`.
let z := add(0x20, and(l, w)) // Offset of last word in `a[i]` from `s`.
// If `s` comes before `o`, or `s` is not zero right padded.
if iszero(lt(lt(s, o), or(iszero(r), iszero(shl(shl(3, r), mload(add(s, z))))))) {
let m := mload(0x40)
mstore(m, l) // Copy `a[i].length`.
for {} 1 {} {
mstore(add(m, z), mload(add(s, z))) // Copy `a[i]`, backwards.
z := add(z, w) // `sub(z, 0x20)`.
if iszero(z) { break }
}
let e := add(add(m, 0x20), l)
mstore(e, 0) // Zeroize the slot after the copied bytes.
mstore(0x40, add(e, 0x20)) // Allocate memory.
s := m
}
mstore(c, sub(s, o)) // Convert to calldata offset.
let t := add(l, add(s, 0x20))
if iszero(lt(t, u)) { u := t }
}
let retStart := add(a, w) // Assumes `a` doesn't start from scratch space.
mstore(retStart, 0x20) // Store the return offset.
return(retStart, add(0x40, sub(u, retStart))) // End the transaction.
}
}
/// @dev Returns the word at `offset`, without any bounds checks.
function load(bytes memory a, uint256 offset) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(add(add(a, 0x20), offset))
}
}
/// @dev Returns the word at `offset`, without any bounds checks.
function loadCalldata(bytes calldata a, uint256 offset)
internal
pure
returns (bytes32 result)
{
/// @solidity memory-safe-assembly
assembly {
result := calldataload(add(a.offset, offset))
}
}
/// @dev Returns a slice representing a static struct in the calldata. Performs bounds checks.
function staticStructInCalldata(bytes calldata a, uint256 offset)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
let l := sub(a.length, 0x20)
result.offset := add(a.offset, offset)
result.length := sub(a.length, offset)
if or(shr(64, or(l, a.offset)), gt(offset, l)) { revert(l, 0x00) }
}
}
/// @dev Returns a slice representing a dynamic struct in the calldata. Performs bounds checks.
function dynamicStructInCalldata(bytes calldata a, uint256 offset)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
let l := sub(a.length, 0x20)
let s := calldataload(add(a.offset, offset)) // Relative offset of `result` from `a.offset`.
result.offset := add(a.offset, s)
result.length := sub(a.length, s)
if or(shr(64, or(s, or(l, a.offset))), gt(offset, l)) { revert(l, 0x00) }
}
}
/// @dev Returns bytes in calldata. Performs bounds checks.
function bytesInCalldata(bytes calldata a, uint256 offset)
internal
pure
returns (bytes calldata result)
{
/// @solidity memory-safe-assembly
assembly {
let l := sub(a.length, 0x20)
let s := calldataload(add(a.offset, offset)) // Relative offset of `result` from `a.offset`.
result.offset := add(add(a.offset, s), 0x20)
result.length := calldataload(add(a.offset, s))
// forgefmt: disable-next-item
if or(shr(64, or(result.length, or(s, or(l, a.offset)))),
or(gt(add(s, result.length), l), gt(offset, l))) { revert(l, 0x00) }
}
}
/// @dev Checks if `x` is in `a`. Assumes `a` has been checked.
function checkInCalldata(bytes calldata x, bytes calldata a) internal pure {
/// @solidity memory-safe-assembly
assembly {
if or(
or(lt(x.offset, a.offset), gt(add(x.offset, x.length), add(a.length, a.offset))),
shr(64, or(x.length, x.offset))
) { revert(0x00, 0x00) }
}
}
/// @dev Checks if `x` is in `a`. Assumes `a` has been checked.
function checkInCalldata(bytes[] calldata x, bytes calldata a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let e := sub(add(a.length, a.offset), 0x20)
if or(lt(x.offset, a.offset), shr(64, x.offset)) { revert(0x00, 0x00) }
for { let i := 0 } iszero(eq(x.length, i)) { i := add(i, 1) } {
let o := calldataload(add(x.offset, shl(5, i)))
let t := add(o, x.offset)
let l := calldataload(t)
if or(shr(64, or(l, o)), gt(add(t, l), e)) { revert(0x00, 0x00) }
}
}
}
/// @dev Returns empty calldata bytes. For silencing the compiler.
function emptyCalldata() internal pure returns (bytes calldata result) {
/// @solidity memory-safe-assembly
assembly {
result.length := 0
}
}
/// @dev Returns the most significant 20 bytes as an address.
function msbToAddress(bytes32 x) internal pure returns (address) {
return address(bytes20(x));
}
/// @dev Returns the least significant 20 bytes as an address.
function lsbToAddress(bytes32 x) internal pure returns (address) {
return address(uint160(uint256(x))
Submitted on: 2025-11-04 12:15:46
Comments
Log in to comment.
No comments yet.