Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/ProtocolitesRendererAnimated.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "solady/utils/Base64.sol";
import "solady/utils/LibString.sol";
import "solady/auth/Ownable.sol";
import "./interfaces/IProtocolitesRenderer.sol";
/**
* @title ProtocolitesRendererAnimated
* @notice Advanced renderer with canvas-based animations and temperament system
* @dev Based on the V_ANIMATED HTML template with per-attribute animations
*/
contract ProtocolitesRendererAnimated is Ownable, IProtocolitesRenderer {
string public renderScript;
constructor() {
_initializeOwner(msg.sender);
renderScript = defaultScript();
}
function setRenderScript(string memory _script) external onlyOwner {
renderScript = _script;
}
function tokenURI(uint256 tokenId, TokenData memory data) external view returns (string memory) {
return string.concat("data:application/json;base64,", Base64.encode(bytes(metadata(tokenId, data))));
}
function metadata(uint256 tokenId, TokenData memory data) public view returns (string memory) {
bool isKid = data.isKid;
uint256 size = isKid ? 16 : 24;
// HTML with canvas animation
string memory animation = string.concat(
'<!DOCTYPE html><html><head><meta charset="UTF-8">',
'<meta name="viewport" content="width=device-width,initial-scale=1.0">',
'<title>Protocolite #', LibString.toString(tokenId), '</title>',
'<style>',
'@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@100;200;300;400&display=swap");',
'*{margin:0;padding:0;box-sizing:border-box;}',
'body{font-family:"JetBrains Mono","Courier New",monospace;background:#fff;color:#000;',
'display:flex;align-items:center;justify-content:center;min-height:100vh;overflow:hidden;}',
'canvas{image-rendering:crisp-edges;image-rendering:pixelated;transform:scale(3.5);transform-origin:center;}',
'</style></head><body>',
'<canvas id="c"></canvas>',
'<script>',
'const tokenId=', LibString.toString(tokenId), ';',
'const dna="', LibString.toHexString(data.dna), '";',
'const isKid=', isKid ? 'true' : 'false', ';',
'const parentDna="', LibString.toHexString(data.parentDna), '";',
'const size=', LibString.toString(size), ';',
renderScript,
'</script></body></html>'
);
string memory attributes = string.concat(
'[{"trait_type":"Type","value":"', isKid ? "Child" : "Spreader", '"},',
'{"trait_type":"Size","value":"', LibString.toString(size), 'x', LibString.toString(size), '"},',
'{"trait_type":"DNA","value":"', LibString.toHexString(data.dna), '"},',
'{"trait_type":"Birth Block","value":', LibString.toString(data.birthBlock), '}',
isKid ? string.concat(',{"trait_type":"Parent DNA","value":"', LibString.toHexString(data.parentDna), '"}') : '',
']'
);
string memory json = string.concat(
'{"name":"Protocolite #', LibString.toString(tokenId),
isKid ? " (Child)" : " (Spreader)", '",',
'"description":"Fully on-chain generative ASCII art with advanced canvas animations and temperament system.",',
'"animation_url":"data:text/html;base64,', Base64.encode(bytes(animation)), '",',
'"attributes":', attributes, '}'
);
return json;
}
// Core DNA parsing and family selection
function getScript1() private pure returns (string memory) {
return string.concat(
'function hashCode(s){let h=0;for(let i=0;i<s.length;i++){h=((h<<5)-h)+s.charCodeAt(i);h&=h;}return Math.abs(h);}',
'const families={red:"#cc0000",green:"#008800",blue:"#0044cc",yellow:"#cc9900",purple:"#8800cc",cyan:"#0088aa"};',
'function getFamilyFromDNA(d){const n=BigInt(d);const h=Number((n>>17n)%16777216n);',
'const fams=Object.keys(families);return fams[h%fams.length];}',
'const family=getFamilyFromDNA(dna);const familyColor=families[family];',
'function random(){seed=(seed*9301+49297)%233280;return seed/233280;}let seed=hashCode(dna);'
);
}
// DNA decoding with temperament
function getScript2() private pure returns (string memory) {
return string.concat(
'function decodeDNA(d){const n=BigInt(d);',
'const temps=["calm","balanced","energetic","chaotic","glitchy","unstable"];',
'const tempWeights=[35,30,20,10,4,1];let totalW=100;',
'const tempIdx=Number((n>>20n)&7n)%6;',
'return{',
'bodyType:["square","round","diamond","mushroom","invader","ghost"][Number((n>>0n)&7n)%6],',
'bodyChar:["\\u2588","\\u2593","\\u2592","\\u2591"][Number((n>>3n)&3n)],',
'eyeChar:["\\u25cf","\\u25c9","\\u25ce","\\u25cb"][Number((n>>5n)&3n)],',
'eyeSize:Number((n>>7n)&1n)===1?"mega":"normal",',
'antennaTip:["\\u25cf","\\u25c9","\\u25cb","\\u25ce","\\u2726","\\u2727","\\u2605"][Number((n>>8n)&7n)%7],',
'armStyle:Number((n>>11n)&1n)===1?"line":"block",',
'legStyle:Number((n>>12n)&1n)===1?"line":"block",',
'hatType:["none","top","flat","double","fancy"][Number((n>>13n)&7n)%5],',
'hasCig:Number((n>>16n)&1n)===1,',
'temperament:temps[tempIdx]};}',
'const dnaObj=decodeDNA(dna);'
);
}
// Grid generation with type tracking
function getScript3() private pure returns (string memory) {
return string.concat(
'const grid=Array(size).fill().map(()=>Array(size).fill().map(()=>({char:" ",type:"empty"})));',
'const cx=Math.floor(size/2);const cy=Math.floor(size/2);',
'const bodyWidth=size===24?6:3;const bodyHeight=size===24?8:4;const bodyStartY=size===24?7:6;',
'const bodyType=dnaObj.bodyType;',
'for(let y=0;y<bodyHeight;y++){for(let x=-bodyWidth;x<=bodyWidth;x++){',
'const posY=bodyStartY+y;const posX=cx+x;let inBody=false;',
'const relX=x/bodyWidth;const relY=(y-bodyHeight/2)/(bodyHeight/2);',
'if(bodyType==="square")inBody=true;',
'else if(bodyType==="round"){const d=Math.sqrt(relX*relX+relY*relY);inBody=d<=1.0;}',
'else if(bodyType==="diamond")inBody=Math.abs(relX)+Math.abs(relY)<=1.0;',
'else if(bodyType==="mushroom"){if(isKid){inBody=relY<-0.2?true:Math.abs(relX)<=0.7;}',
'else{inBody=relY<0?true:Math.abs(relX)<=0.6;}}',
'else if(bodyType==="invader"){if(relY<-0.3)inBody=Math.abs(relX)<=0.7;',
'else if(relY<0.3)inBody=true;else inBody=Math.abs(relX)<=0.85;}',
'else if(bodyType==="ghost"){const dg=Math.sqrt(relX*relX+relY*relY);',
'if(relY<0.5)inBody=dg<=1.0;else inBody=Math.abs(relX)<=0.9&&(Math.floor(x+bodyWidth)%2===0||relY<0.8);}',
'if(inBody&&posX>=0&&posX<size&&posY>=0&&posY<size)grid[posY][posX]={char:dnaObj.bodyChar,type:"body"};}}'
);
}
// Eyes with type marking
function getScript4() private pure returns (string memory) {
return string.concat(
'const isBodyChar=c=>c&&c.type==="body";const eyeY=bodyStartY+1;',
'if(isKid){const ec=1+Math.floor(random()*3);',
'if(ec===1){for(let dy=0;dy<2;dy++)for(let dx=-1;dx<=1;dx++)if(isBodyChar(grid[eyeY+dy][cx+dx]))grid[eyeY+dy][cx+dx]={char:" ",type:"empty"};',
'grid[eyeY][cx-1]={char:dnaObj.bodyChar,type:"body"};grid[eyeY][cx]={char:dnaObj.eyeChar,type:"eye"};',
'grid[eyeY][cx+1]={char:dnaObj.bodyChar,type:"body"};grid[eyeY+1][cx]={char:dnaObj.bodyChar,type:"body"};}',
'else if(ec===2){const es=1;for(let dy=0;dy<2;dy++)for(let dx=0;dx<2;dx++){',
'if(isBodyChar(grid[eyeY+dy][cx-es-1+dx]))grid[eyeY+dy][cx-es-1+dx]={char:" ",type:"empty"};',
'if(isBodyChar(grid[eyeY+dy][cx+es+dx]))grid[eyeY+dy][cx+es+dx]={char:" ",type:"empty"};}',
'grid[eyeY][cx-es-1]={char:dnaObj.bodyChar,type:"body"};grid[eyeY][cx-es]={char:dnaObj.eyeChar,type:"eye"};',
'grid[eyeY+1][cx-es-1]={char:dnaObj.bodyChar,type:"body"};grid[eyeY+1][cx-es]={char:dnaObj.bodyChar,type:"body"};',
'grid[eyeY][cx+es]={char:dnaObj.eyeChar,type:"eye"};grid[eyeY][cx+es+1]={char:dnaObj.bodyChar,type:"body"};',
'grid[eyeY+1][cx+es]={char:dnaObj.bodyChar,type:"body"};grid[eyeY+1][cx+es+1]={char:dnaObj.bodyChar,type:"body"};}',
'else{for(let dx of[-2,0,2]){if(isBodyChar(grid[eyeY][cx+dx]))grid[eyeY][cx+dx]={char:" ",type:"empty"};',
'grid[eyeY][cx+dx]={char:dnaObj.eyeChar,type:"eye"};}}}else{'
);
}
function getScript5() private pure returns (string memory) {
return string.concat(
'const ec=1+Math.floor(random()*3);if(ec===1){',
'for(let dy=0;dy<3;dy++)for(let dx=-2;dx<=2;dx++)if(isBodyChar(grid[eyeY+dy][cx+dx]))grid[eyeY+dy][cx+dx]={char:" ",type:"empty"};',
'for(let dx=-2;dx<=2;dx++){grid[eyeY][cx+dx]={char:dnaObj.bodyChar,type:"body"};grid[eyeY+2][cx+dx]={char:dnaObj.bodyChar,type:"body"};}',
'grid[eyeY+1][cx-2]={char:dnaObj.bodyChar,type:"body"};grid[eyeY+1][cx-1]={char:dnaObj.eyeChar,type:"eye"};',
'grid[eyeY+1][cx]={char:dnaObj.eyeChar,type:"eye"};grid[eyeY+1][cx+1]={char:dnaObj.eyeChar,type:"eye"};',
'grid[eyeY+1][cx+2]={char:dnaObj.bodyChar,type:"body"};}else if(ec===2){const bs=2;',
'for(let dy=0;dy<3;dy++)for(let dx=0;dx<3;dx++){',
'if(isBodyChar(grid[eyeY+dy][cx-bs-2+dx]))grid[eyeY+dy][cx-bs-2+dx]={char:" ",type:"empty"};',
'if(isBodyChar(grid[eyeY+dy][cx+bs+dx]))grid[eyeY+dy][cx+bs+dx]={char:" ",type:"empty"};}',
'for(let dy=0;dy<3;dy++)for(let dx=0;dx<3;dx++){const ic=dy===1&&dx===1;',
'grid[eyeY+dy][cx-bs-2+dx]={char:ic?dnaObj.eyeChar:dnaObj.bodyChar,type:ic?"eye":"body"};',
'grid[eyeY+dy][cx+bs+dx]={char:ic?dnaObj.eyeChar:dnaObj.bodyChar,type:ic?"eye":"body"};}}',
'else{for(let i=-3;i<=3;i+=3)for(let dy=0;dy<2;dy++){',
'if(isBodyChar(grid[eyeY+dy][cx+i]))grid[eyeY+dy][cx+i]={char:" ",type:"empty"};',
'if(isBodyChar(grid[eyeY+dy][cx+i-1]))grid[eyeY+dy][cx+i-1]={char:" ",type:"empty"};',
'grid[eyeY+dy][cx+i]={char:dnaObj.eyeChar,type:"eye"};grid[eyeY+dy][cx+i-1]={char:dnaObj.eyeChar,type:"eye"};}}}',
'if(random()>0.3){const my=eyeY+(isKid?2:3);if(my<size&&isBodyChar(grid[my][cx])){',
'grid[my][cx]={char:"\\u2500",type:"mouth"};',
'if(random()>0.5&&isBodyChar(grid[my][cx-1]))grid[my][cx-1]={char:"\\u2500",type:"mouth"};',
'if(random()>0.5&&isBodyChar(grid[my][cx+1]))grid[my][cx+1]={char:"\\u2500",type:"mouth"};}}'
);
}
// Cigarette, arms, legs
function getScript6() private pure returns (string memory) {
return string.concat(
'if(dnaObj.hasCig){const cy=eyeY+(isKid?2:3);const cigChars=["\\u2248","\\u223c","~"];',
'const cigChar=cigChars[Math.floor(random()*cigChars.length)];const cigX=cx+(random()>0.5?3:-3);',
'if(cigX>=0&&cigX<size&&cy>=0&&cy<size){grid[cy][cigX]={char:cigChar,type:"cig"};',
'if(cigX+1<size)grid[cy][cigX+1]={char:"\\u2219",type:"cig"};}}',
'const armCnt=1+Math.floor(random()*4);const armLen=isKid?(1+Math.floor(random()*2)):(2+Math.floor(random()*4));',
'const armChar=dnaObj.armStyle==="block"?"\\u2588":"\\u2500";',
'for(let a=0;a<armCnt;a++){const ay=bodyStartY+2+a*(isKid?1:2);if(ay>=bodyStartY+bodyHeight)break;',
'let lbe=cx,rbe=cx;for(let x=cx;x>=0;x--)if(isBodyChar(grid[ay][x]))lbe=x;else break;',
'for(let x=cx;x<size;x++)if(isBodyChar(grid[ay][x]))rbe=x;else break;',
'for(let i=1;i<=armLen;i++){if(lbe-i>=0)grid[ay][lbe-i]={char:armChar,type:"arm"};',
'if(rbe+i<size)grid[ay][rbe+i]={char:armChar,type:"arm"};}}',
'const legCnt=1+Math.floor(random()*4);const legY=bodyStartY+bodyHeight;',
'const legLen=isKid?(1+Math.floor(random()*2)):(2+Math.floor(random()*3));',
'const legChar=dnaObj.legStyle==="block"?"\\u2588":"\\u2502";const bps=[];',
'for(let x=0;x<size;x++)if(grid[legY-1]&&isBodyChar(grid[legY-1][x]))bps.push(x);',
'const lps=[];if(bps.length>0){if(legCnt===1)lps.push(bps[Math.floor(bps.length/2)]);',
'else if(legCnt===2){lps.push(bps[Math.floor(bps.length*0.25)]);lps.push(bps[Math.floor(bps.length*0.75)]);}',
'else if(legCnt===3){lps.push(bps[0]);lps.push(bps[Math.floor(bps.length/2)]);lps.push(bps[bps.length-1]);}',
'else{lps.push(bps[0]);lps.push(bps[Math.floor(bps.length*0.33)]);',
'lps.push(bps[Math.floor(bps.length*0.66)]);lps.push(bps[bps.length-1]);}}',
'for(let lx of lps)if(lx>=0&&lx<size)for(let i=0;i<legLen;i++)if(legY+i<size)grid[legY+i][lx]={char:legChar,type:"leg"};'
);
}
// Antennas and hat
function getScript7() private pure returns (string memory) {
return string.concat(
'const antCnt=1+Math.floor(random()*4);const antLen=isKid?1:(1+Math.floor(random()*2));const btp=[];',
'for(let x=0;x<size;x++)if(grid[bodyStartY]&&isBodyChar(grid[bodyStartY][x]))btp.push(x);',
'const aps=[];if(btp.length>0){if(antCnt===1)aps.push(btp[Math.floor(btp.length/2)]);',
'else if(antCnt===2){aps.push(btp[Math.floor(btp.length*0.25)]);aps.push(btp[Math.floor(btp.length*0.75)]);}',
'else if(antCnt===3){aps.push(btp[0]);aps.push(btp[Math.floor(btp.length/2)]);aps.push(btp[btp.length-1]);}',
'else{aps.push(btp[0]);aps.push(btp[Math.floor(btp.length*0.33)]);',
'aps.push(btp[Math.floor(btp.length*0.66)]);aps.push(btp[btp.length-1]);}}',
'for(let ax of aps)for(let i=1;i<=antLen;i++){const ay=bodyStartY-i;',
'if(ay>=0)grid[ay][ax]={char:i===antLen?dnaObj.antennaTip:"\\u2502",type:i===antLen?"ant-tip":"ant"};}',
'if(dnaObj.hatType&&dnaObj.hatType!=="none"){const hy=bodyStartY-antLen-1;if(hy>=0){',
'if(dnaObj.hatType==="top"){for(let dx=-2;dx<=2;dx++)if(cx+dx>=0&&cx+dx<size)grid[hy][cx+dx]={char:"\\u2580",type:"hat"};',
'if(hy+1<size)grid[hy+1][cx]={char:"\\u2588",type:"hat"};}',
'else if(dnaObj.hatType==="flat"){for(let dx=-2;dx<=2;dx++)if(cx+dx>=0&&cx+dx<size)grid[hy][cx+dx]={char:"\\u2550",type:"hat"};}',
'else if(dnaObj.hatType==="double"){for(let dx=-2;dx<=2;dx++)if(cx+dx>=0&&cx+dx<size&&hy-1>=0){',
'grid[hy-1][cx+dx]={char:"\\u2580",type:"hat"};grid[hy][cx+dx]={char:"\\u2584",type:"hat"};}}',
'else if(dnaObj.hatType==="fancy"&&cx-2>=0&&cx+2<size){grid[hy][cx-2]={char:"\\u2554",type:"hat"};',
'grid[hy][cx-1]={char:"\\u2550",type:"hat"};grid[hy][cx]={char:"\\u2550",type:"hat"};',
'grid[hy][cx+1]={char:"\\u2550",type:"hat"};grid[hy][cx+2]={char:"\\u2557",type:"hat"};}}}'
);
}
// Canvas animation engine
function getScript8() private pure returns (string memory) {
return string.concat(
'const canvas=document.getElementById("c");const ctx=canvas.getContext("2d");',
'const fontSize=size===24?9:8;ctx.font=fontSize+"px \\"Courier New\\",monospace";',
'ctx.textBaseline="top";ctx.textAlign="left";const metrics=ctx.measureText("\\u2588");',
'const charW=metrics.width;const lineH=fontSize;canvas.width=size*charW;canvas.height=size*lineH;',
'const tempCfg={calm:{spd:1.5,glitch:0.02,corrupt:0},balanced:{spd:2.5,glitch:0.05,corrupt:0},',
'energetic:{spd:3.5,glitch:0.12,corrupt:0.03},chaotic:{spd:5,glitch:0.25,corrupt:0.08},',
'glitchy:{spd:6,glitch:0.4,corrupt:0.15},unstable:{spd:7,glitch:0.6,corrupt:0.25}}[dnaObj.temperament]||{spd:2,glitch:0.05,corrupt:0};',
'let time=0;const corruptChars=["\\u2588","\\u2593","\\u2592","\\u2591","\\u2580","\\u2584"];',
'function animate(){ctx.clearRect(0,0,canvas.width,canvas.height);time+=tempCfg.spd*0.016;',
'const isGlitch=Math.random()<tempCfg.glitch;',
'for(let y=0;y<size;y++){let rowOff=0;',
'if(isGlitch&&(dnaObj.temperament==="chaotic"||dnaObj.temperament==="glitchy"||dnaObj.temperament==="unstable")){',
'if(Math.random()<0.1)rowOff=(Math.random()-0.5)*6;}',
'for(let x=0;x<size;x++){const cell=grid[y][x];if(!cell||cell.char===" ")continue;',
'let char=cell.char;const type=cell.type;',
'if(isGlitch&&Math.random()<tempCfg.corrupt)char=corruptChars[Math.floor(Math.random()*corruptChars.length)];',
'let posX=x*charW;let posY=y*lineH;'
);
}
function getScript9() private pure returns (string memory) {
return string.concat(
'if(type==="body"){const breathe=Math.sin(time+x*0.1)*2;posY+=breathe;}',
'else if(type==="eye"){if(Math.sin(time*0.5)<-0.95)char="\\u2500";',
'if(dnaObj.temperament==="energetic"||dnaObj.temperament==="chaotic"||dnaObj.temperament==="glitchy"||dnaObj.temperament==="unstable")posX+=Math.sin(time*2+y)*1.5;}',
'else if(type==="arm"){const wave=Math.sin(time*1.5+y*0.5)*3;posY+=wave;',
'if(dnaObj.temperament==="chaotic"||dnaObj.temperament==="glitchy"||dnaObj.temperament==="unstable")posX+=Math.cos(time*2+x)*2;}',
'else if(type==="leg"){const march=Math.sin(time*2+x*1.5)*2;posY+=march;}',
'else if(type==="ant"||type==="ant-tip"){const wobble=Math.sin(time+x*0.3)*2;posX+=wobble;}',
'else if(type==="mouth"){if(Math.sin(time*0.7)>0.7&&(dnaObj.temperament==="energetic"||dnaObj.temperament==="chaotic"||dnaObj.temperament==="glitchy"||dnaObj.temperament==="unstable"))char="\\u25cb";}',
'if(isGlitch){const glitchMult=dnaObj.temperament==="unstable"?4:dnaObj.temperament==="glitchy"?3:dnaObj.temperament==="chaotic"?2:dnaObj.temperament==="energetic"?1.5:1;',
'posX+=(Math.random()-0.5)*glitchMult;posY+=(Math.random()-0.5)*glitchMult;}',
'posX+=rowOff;let color=familyColor;',
'if(isGlitch&&(dnaObj.temperament==="chaotic"||dnaObj.temperament==="glitchy"||dnaObj.temperament==="unstable")){',
'const colors=["#ff0000","#00ff00","#0000ff",familyColor];color=colors[Math.floor(Math.random()*colors.length)];}',
'ctx.fillStyle=color;ctx.fillText(char,posX,posY);}}',
'requestAnimationFrame(animate);}animate();'
);
}
function defaultScript() private pure returns (string memory) {
return string.concat(
getScript1(),
getScript2(),
getScript3(),
getScript4(),
getScript5(),
getScript6(),
getScript7(),
getScript8(),
getScript9()
);
}
}
"
},
"lib/solady/src/utils/Base64.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library to encode strings in Base64.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base64.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Base64.sol)
/// @author Modified from (https://github.com/Brechtpd/base64/blob/main/base64.sol) by Brecht Devos - <brecht@loopring.org>.
library Base64 {
/// @dev Encodes `data` using the base64 encoding described in RFC 4648.
/// See: https://datatracker.ietf.org/doc/html/rfc4648
/// @param fileSafe Whether to replace '+' with '-' and '/' with '_'.
/// @param noPadding Whether to strip away the padding.
function encode(bytes memory data, bool fileSafe, bool noPadding)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let dataLength := mload(data)
if dataLength {
// Multiply by 4/3 rounded up.
// The `shl(2, ...)` is equivalent to multiplying by 4.
let encodedLength := shl(2, div(add(dataLength, 2), 3))
// Set `result` to point to the start of the free memory.
result := mload(0x40)
// Store the table into the scratch space.
// Offsetted by -1 byte so that the `mload` will load the character.
// We will rewrite the free memory pointer at `0x40` later with
// the allocated size.
// The magic constant 0x0670 will turn "-_" into "+/".
mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef")
mstore(0x3f, xor("ghijklmnopqrstuvwxyz0123456789-_", mul(iszero(fileSafe), 0x0670)))
// Skip the first slot, which stores the length.
let ptr := add(result, 0x20)
let end := add(ptr, encodedLength)
let dataEnd := add(add(0x20, data), dataLength)
let dataEndValue := mload(dataEnd) // Cache the value at the `dataEnd` slot.
mstore(dataEnd, 0x00) // Zeroize the `dataEnd` slot to clear dirty bits.
// Run over the input, 3 bytes at a time.
for {} 1 {} {
data := add(data, 3) // Advance 3 bytes.
let input := mload(data)
// Write 4 bytes. Optimized for fewer stack operations.
mstore8(0, mload(and(shr(18, input), 0x3F)))
mstore8(1, mload(and(shr(12, input), 0x3F)))
mstore8(2, mload(and(shr(6, input), 0x3F)))
mstore8(3, mload(and(input, 0x3F)))
mstore(ptr, mload(0x00))
ptr := add(ptr, 4) // Advance 4 bytes.
if iszero(lt(ptr, end)) { break }
}
mstore(dataEnd, dataEndValue) // Restore the cached value at `dataEnd`.
mstore(0x40, add(end, 0x20)) // Allocate the memory.
// Equivalent to `o = [0, 2, 1][dataLength % 3]`.
let o := div(2, mod(dataLength, 3))
// Offset `ptr` and pad with '='. We can simply write over the end.
mstore(sub(ptr, o), shl(240, 0x3d3d))
// Set `o` to zero if there is padding.
o := mul(iszero(iszero(noPadding)), o)
mstore(sub(ptr, o), 0) // Zeroize the slot after the string.
mstore(result, sub(encodedLength, o)) // Store the length.
}
}
}
/// @dev Encodes `data` using the base64 encoding described in RFC 4648.
/// Equivalent to `encode(data, false, false)`.
function encode(bytes memory data) internal pure returns (string memory result) {
result = encode(data, false, false);
}
/// @dev Encodes `data` using the base64 encoding described in RFC 4648.
/// Equivalent to `encode(data, fileSafe, false)`.
function encode(bytes memory data, bool fileSafe)
internal
pure
returns (string memory result)
{
result = encode(data, fileSafe, false);
}
/// @dev Decodes base64 encoded `data`.
///
/// Supports:
/// - RFC 4648 (both standard and file-safe mode).
/// - RFC 3501 (63: ',').
///
/// Does not support:
/// - Line breaks.
///
/// Note: For performance reasons,
/// this function will NOT revert on invalid `data` inputs.
/// Outputs for invalid inputs will simply be undefined behaviour.
/// It is the user's responsibility to ensure that the `data`
/// is a valid base64 encoded string.
function decode(string memory data) internal pure returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
let dataLength := mload(data)
if dataLength {
let decodedLength := mul(shr(2, dataLength), 3)
for {} 1 {} {
// If padded.
if iszero(and(dataLength, 3)) {
let t := xor(mload(add(data, dataLength)), 0x3d3d)
// forgefmt: disable-next-item
decodedLength := sub(
decodedLength,
add(iszero(byte(30, t)), iszero(byte(31, t)))
)
break
}
// If non-padded.
decodedLength := add(decodedLength, sub(and(dataLength, 3), 1))
break
}
result := mload(0x40)
// Write the length of the bytes.
mstore(result, decodedLength)
// Skip the first slot, which stores the length.
let ptr := add(result, 0x20)
let end := add(ptr, decodedLength)
// Load the table into the scratch space.
// Constants are optimized for smaller bytecode with zero gas overhead.
// `m` also doubles as the mask of the upper 6 bits.
let m := 0xfc000000fc00686c7074787c8084888c9094989ca0a4a8acb0b4b8bcc0c4c8cc
mstore(0x5b, m)
mstore(0x3b, 0x04080c1014181c2024282c3034383c4044484c5054585c6064)
mstore(0x1a, 0xf8fcf800fcd0d4d8dce0e4e8ecf0f4)
for {} 1 {} {
// Read 4 bytes.
data := add(data, 4)
let input := mload(data)
// Write 3 bytes.
// forgefmt: disable-next-item
mstore(ptr, or(
and(m, mload(byte(28, input))),
shr(6, or(
and(m, mload(byte(29, input))),
shr(6, or(
and(m, mload(byte(30, input))),
shr(6, mload(byte(31, input)))
))
))
))
ptr := add(ptr, 3)
if iszero(lt(ptr, end)) { break }
}
mstore(0x40, add(end, 0x20)) // Allocate the memory.
mstore(end, 0) // Zeroize the slot after the bytes.
mstore(0x60, 0) // Restore the zero slot.
}
}
}
}
"
},
"lib/solady/src/utils/LibString.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {LibBytes} from "./LibBytes.sol";
/// @notice Library for converting numbers into strings and other string operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)
///
/// @dev Note:
/// For performance and bytecode compactness, most of the string operations are restricted to
/// byte strings (7-bit ASCII), except where otherwise specified.
/// Usage of byte string operations on charsets with runes spanning two or more bytes
/// can lead to undefined behavior.
library LibString {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRUCTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Goated string storage struct that totally MOGs, no cap, fr.
/// Uses less gas and bytecode than Solidity's native string storage. It's meta af.
/// Packs length with the first 31 bytes if <255 bytes, so it’s mad tight.
struct StringStorage {
bytes32 _spacer;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The length of the output is too small to contain all the hex digits.
error HexLengthInsufficient();
/// @dev The length of the string is more than 32 bytes.
error TooBigForSmallString();
/// @dev The input string must be a 7-bit ASCII.
error StringNot7BitASCII();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The constant returned when the `search` is not found in the string.
uint256 internal constant NOT_FOUND = type(uint256).max;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant ALPHANUMERIC_7_BIT_ASCII = 0x7fffffe07fffffe03ff000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant LETTERS_7_BIT_ASCII = 0x7fffffe07fffffe0000000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyz'.
uint128 internal constant LOWERCASE_7_BIT_ASCII = 0x7fffffe000000000000000000000000;
/// @dev Lookup for 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant UPPERCASE_7_BIT_ASCII = 0x7fffffe0000000000000000;
/// @dev Lookup for '0123456789'.
uint128 internal constant DIGITS_7_BIT_ASCII = 0x3ff000000000000;
/// @dev Lookup for '0123456789abcdefABCDEF'.
uint128 internal constant HEXDIGITS_7_BIT_ASCII = 0x7e0000007e03ff000000000000;
/// @dev Lookup for '01234567'.
uint128 internal constant OCTDIGITS_7_BIT_ASCII = 0xff000000000000;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \ \
\r\x0b\x0c'.
uint128 internal constant PRINTABLE_7_BIT_ASCII = 0x7fffffffffffffffffffffff00003e00;
/// @dev Lookup for '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.
uint128 internal constant PUNCTUATION_7_BIT_ASCII = 0x78000001f8000001fc00fffe00000000;
/// @dev Lookup for ' \ \
\r\x0b\x0c'.
uint128 internal constant WHITESPACE_7_BIT_ASCII = 0x100003e00;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRING STORAGE OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sets the value of the string storage `$` to `s`.
function set(StringStorage storage $, string memory s) internal {
LibBytes.set(bytesStorage($), bytes(s));
}
/// @dev Sets the value of the string storage `$` to `s`.
function setCalldata(StringStorage storage $, string calldata s) internal {
LibBytes.setCalldata(bytesStorage($), bytes(s));
}
/// @dev Sets the value of the string storage `$` to the empty string.
function clear(StringStorage storage $) internal {
delete $._spacer;
}
/// @dev Returns whether the value stored is `$` is the empty string "".
function isEmpty(StringStorage storage $) internal view returns (bool) {
return uint256($._spacer) & 0xff == uint256(0);
}
/// @dev Returns the length of the value stored in `$`.
function length(StringStorage storage $) internal view returns (uint256) {
return LibBytes.length(bytesStorage($));
}
/// @dev Returns the value stored in `$`.
function get(StringStorage storage $) internal view returns (string memory) {
return string(LibBytes.get(bytesStorage($)));
}
/// @dev Helper to cast `$` to a `BytesStorage`.
function bytesStorage(StringStorage storage $)
internal
pure
returns (LibBytes.BytesStorage storage casted)
{
/// @solidity memory-safe-assembly
assembly {
casted.slot := $.slot
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the base 10 decimal representation of `value`.
function toString(uint256 value) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
// The maximum value of a uint256 contains 78 digits (1 byte per digit), but
// we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
// We will need 1 word for the trailing zeros padding, 1 word for the length,
// and 3 words for a maximum of 78 digits.
result := add(mload(0x40), 0x80)
mstore(0x40, add(result, 0x20)) // Allocate memory.
mstore(result, 0) // Zeroize the slot after the string.
let end := result // Cache the end of the memory to calculate the length later.
let w := not(0) // Tsk.
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let temp := value } 1 {} {
result := add(result, w) // `sub(result, 1)`.
// Store the character to the pointer.
// The ASCII index of the '0' character is 48.
mstore8(result, add(48, mod(temp, 10)))
temp := div(temp, 10) // Keep dividing `temp` until zero.
if iszero(temp) { break }
}
let n := sub(end, result)
result := sub(result, 0x20) // Move the pointer 32 bytes back to make room for the length.
mstore(result, n) // Store the length.
}
}
/// @dev Returns the base 10 decimal representation of `value`.
function toString(int256 value) internal pure returns (string memory result) {
if (value >= 0) return toString(uint256(value));
unchecked {
result = toString(~uint256(value) + 1);
}
/// @solidity memory-safe-assembly
assembly {
// We still have some spare memory space on the left,
// as we have allocated 3 words (96 bytes) for up to 78 digits.
let n := mload(result) // Load the string length.
mstore(result, 0x2d) // Store the '-' character.
result := sub(result, 1) // Move back the string pointer by a byte.
mstore(result, add(n, 1)) // Update the string length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HEXADECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the hexadecimal representation of `value`,
/// left-padded to an input length of `byteCount` bytes.
/// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,
/// giving a total length of `byteCount * 2 + 2` bytes.
/// Reverts if `byteCount` is too small for the output to contain all the digits.
function toHexString(uint256 value, uint256 byteCount)
internal
pure
returns (string memory result)
{
result = toHexStringNoPrefix(value, byteCount);
/// @solidity memory-safe-assembly
assembly {
let n := add(mload(result), 2) // Compute the length.
mstore(result, 0x3078) // Store the "0x" prefix.
result := sub(result, 2) // Move the pointer.
mstore(result, n) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`,
/// left-padded to an input length of `byteCount` bytes.
/// The output is not prefixed with "0x" and is encoded using 2 hexadecimal digits per byte,
/// giving a total length of `byteCount * 2` bytes.
/// Reverts if `byteCount` is too small for the output to contain all the digits.
function toHexStringNoPrefix(uint256 value, uint256 byteCount)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
// We need 0x20 bytes for the trailing zeros padding, `byteCount * 2` bytes
// for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length.
// We add 0x20 to the total and round down to a multiple of 0x20.
// (0x20 + 0x20 + 0x02 + 0x20) = 0x62.
result := add(mload(0x40), and(add(shl(1, byteCount), 0x42), not(0x1f)))
mstore(0x40, add(result, 0x20)) // Allocate memory.
mstore(result, 0) // Zeroize the slot after the string.
let end := result // Cache the end to calculate the length later.
// Store "0123456789abcdef" in scratch space.
mstore(0x0f, 0x30313233343536373839616263646566)
let start := sub(result, add(byteCount, byteCount))
let w := not(1) // Tsk.
let temp := value
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for {} 1 {} {
result := add(result, w) // `sub(result, 2)`.
mstore8(add(result, 1), mload(and(temp, 15)))
mstore8(result, mload(and(shr(4, temp), 15)))
temp := shr(8, temp)
if iszero(xor(result, start)) { break }
}
if temp {
mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`.
revert(0x1c, 0x04)
}
let n := sub(end, result)
result := sub(result, 0x20)
mstore(result, n) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
/// As address are 20 bytes long, the output will left-padded to have
/// a length of `20 * 2 + 2` bytes.
function toHexString(uint256 value) internal pure returns (string memory result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let n := add(mload(result), 2) // Compute the length.
mstore(result, 0x3078) // Store the "0x" prefix.
result := sub(result, 2) // Move the pointer.
mstore(result, n) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x".
/// The output excludes leading "0" from the `toHexString` output.
/// `0x00: "0x0", 0x01: "0x1", 0x12: "0x12", 0x123: "0x123"`.
function toMinimalHexString(uint256 value) internal pure returns (string memory result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let o := eq(byte(0, mload(add(result, 0x20))), 0x30) // Whether leading zero is present.
let n := add(mload(result), 2) // Compute the length.
mstore(add(result, o), 0x3078) // Store the "0x" prefix, accounting for leading zero.
result := sub(add(result, o), 2) // Move the pointer, accounting for leading zero.
mstore(result, sub(n, o)) // Store the length, accounting for leading zero.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output excludes leading "0" from the `toHexStringNoPrefix` output.
/// `0x00: "0", 0x01: "1", 0x12: "12", 0x123: "123"`.
function toMinimalHexStringNoPrefix(uint256 value)
internal
pure
returns (string memory result)
{
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let o := eq(byte(0, mload(add(result, 0x20))), 0x30) // Whether leading zero is present.
let n := mload(result) // Get the length.
result := add(result, o) // Move the pointer, accounting for leading zero.
mstore(result, sub(n, o)) // Store the length, accounting for leading zero.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is encoded using 2 hexadecimal digits per byte.
/// As address are 20 bytes long, the output will left-padded to have
/// a length of `20 * 2` bytes.
function toHexStringNoPrefix(uint256 value) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
// We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
// 0x02 bytes for the prefix, and 0x40 bytes for the digits.
// The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0.
result := add(mload(0x40), 0x80)
mstore(0x40, add(result, 0x20)) // Allocate memory.
mstore(result, 0) // Zeroize the slot after the string.
let end := result // Cache the end to calculate the length later.
mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
let w := not(1) // Tsk.
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let temp := value } 1 {} {
result := add(result, w) // `sub(result, 2)`.
mstore8(add(result, 1), mload(and(temp, 15)))
mstore8(result, mload(and(shr(4, temp), 15)))
temp := shr(8, temp)
if iszero(temp) { break }
}
let n := sub(end, result)
result := sub(result, 0x20)
mstore(result, n) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte,
/// and the alphabets are capitalized conditionally according to
/// https://eips.ethereum.org/EIPS/eip-55
function toHexStringChecksummed(address value) internal pure returns (string memory result) {
result = toHexString(value);
/// @solidity memory-safe-assembly
assembly {
let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...`
let o := add(result, 0x22)
let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... `
let t := shl(240, 136) // `0b10001000 << 240`
for { let i := 0 } 1 {} {
mstore(add(i, i), mul(t, byte(i, hashed)))
i := add(i, 1)
if eq(i, 20) { break }
}
mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask)))))
o := add(o, 0x20)
mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask)))))
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
function toHexString(address value) internal pure returns (string memory result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let n := add(mload(result), 2) // Compute the length.
mstore(result, 0x3078) // Store the "0x" prefix.
result := sub(result, 2) // Move the pointer.
mstore(result, n) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is encoded using 2 hexadecimal digits per byte.
function toHexStringNoPrefix(address value) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
// Allocate memory.
// We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
// 0x02 bytes for the prefix, and 0x28 bytes for the digits.
// The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80.
mstore(0x40, add(result, 0x80))
mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
result := add(result, 2)
mstore(result, 40) // Store the length.
let o := add(result, 0x20)
mstore(add(o, 40), 0) // Zeroize the slot after the string.
value := shl(96, value)
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let i := 0 } 1 {} {
let p := add(o, add(i, i))
let temp := byte(i, value)
mstore8(add(p, 1), mload(and(temp, 15)))
mstore8(p, mload(shr(4, temp)))
i := add(i, 1)
if eq(i, 20) { break }
}
}
}
/// @dev Returns the hex encoded string from the raw bytes.
/// The output is encoded using 2 hexadecimal digits per byte.
function toHexString(bytes memory raw) internal pure returns (string memory result) {
result = toHexStringNoPrefix(raw);
/// @solidity memory-safe-assembly
assembly {
let n := add(mload(result), 2) // Compute the length.
mstore(result, 0x3078) // Store the "0x" prefix.
result := sub(result, 2) // Move the pointer.
mstore(result, n) // Store the length.
}
}
/// @dev Returns the hex encoded string from the raw bytes.
/// The output is encoded using 2 hexadecimal digits per byte.
function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
let n := mload(raw)
result := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix.
mstore(result, add(n, n)) // Store the length of the output.
mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
let o := add(result, 0x20)
let end := add(raw, n)
for {} iszero(eq(raw, end)) {} {
raw := add(raw, 1)
mstore8(add(o, 1), mload(and(mload(raw), 15)))
mstore8(o, mload(and(shr(4, mload(raw)), 15)))
o := add(o, 2)
}
mstore(o, 0) // Zeroize the slot after the string.
mstore(0x40, add(o, 0x20)) // Allocate memory.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RUNE STRING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the number of UTF characters in the string.
function runeCount(string memory s) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
if mload(s) {
mstore(0x00, div(not(0), 255))
mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506)
let o := add(s, 0x20)
let end := add(o, mload(s))
for { result := 1 } 1 { result := add(result, 1) } {
o := add(o, byte(0, mload(shr(250, mload(o)))))
if iszero(lt(o, end)) { break }
}
}
}
}
/// @dev Returns if this string is a 7-bit ASCII string.
/// (i.e. all characters codes are in [0..127])
function is7BitASCII(string memory s) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
let mask := shl(7, div(not(0), 255))
let n := mload(s)
if n {
let o := add(s, 0x20)
let end := add(o, n)
let last := mload(end)
mstore(end, 0)
for {} 1 {} {
if and(mask, mload(o)) {
result := 0
break
}
o := add(o, 0x20)
if iszero(lt(o, end)) { break }
}
mstore(end, last)
}
}
}
/// @dev Returns if this string is a 7-bit ASCII string,
/// AND all characters are in the `allowed` lookup.
/// Note: If `s` is empty, returns true regardless of `allowed`.
function is7BitASCII(string memory s, uint128 allowed) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if mload(s) {
let allowed_ := shr(128, shl(128, allowed))
let o := add(s, 0x20)
for { let end := add(o, mload(s)) } 1 {} {
result := and(result, shr(byte(0, mload(o)), allowed_))
o := add(o, 1)
if iszero(and(result, lt(o, end))) { break }
}
}
}
}
/// @dev Converts the bytes in the 7-bit ASCII string `s` to
/// an allowed lookup for use in `is7BitASCII(s, allowed)`.
/// To save runtime gas, you can cache the result in an immutable variable.
function to7BitASCIIAllowedLookup(string memory s) internal pure returns (uint128 result) {
/// @solidity memory-safe-assembly
assembly {
if mload(s) {
let o := add(s, 0x20)
for { let end := add(o, mload(s)) } 1 {} {
result := or(result, shl(byte(0, mload(o)), 1))
o := add(o, 1)
if iszero(lt(o, end)) { break }
}
if shr(128, result) {
mstore(0x00, 0xc9807e0d) // `StringNot7BitASCII()`.
revert(0x1c, 0x04)
}
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BYTE STRING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance and bytecode compactness, byte string operations are restricted
// to 7-bit ASCII strings. All offsets are byte offsets, not UTF character offsets.
// Usage of byte string operations on charsets with runes spanning two or more bytes
// can lead to undefined behavior.
/// @dev Returns `subject` all occurrences of `needle` replaced with `replacement`.
function replace(string memory subject, string memory needle, string memory replacement)
internal
pure
returns (string memory)
{
return string(LibBytes.replace(bytes(subject), bytes(needle), bytes(replacement)));
}
/// @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(string memory subject, string memory needle, uint256 from)
internal
pure
returns (uint256)
{
return LibBytes.indexOf(bytes(subject), bytes(needle), from);
}
/// @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(string memory subject, string memory needle) internal pure returns (uint256) {
return LibBytes.indexOf(bytes(subject), bytes(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(string memory subject, string memory needle, uint256 from)
internal
pure
returns (uint256)
{
return LibBytes.lastIndexOf(bytes(subject), bytes(needle), from);
}
/// @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(string memory subject, string memory needle)
internal
pure
returns (uint256)
{
return LibBytes.lastIndexOf(bytes(subject), bytes(needle), type(uint256).max);
}
/// @dev Returns true if `needle` is found in `subject`, false otherwise.
function contains(string memory subject, string memory needle) internal pure returns (bool) {
return LibBytes.contains(bytes(subject), bytes(needle));
}
/// @dev Returns whether `subject` starts with `needle`.
function startsWith(string memory subject, string memory needle) internal pure returns (bool) {
return LibBytes.startsWith(bytes(subject), bytes(needle));
}
/// @dev Returns whether `subject` ends with `needle`.
function endsWith(string memory subject, string memory needle) internal pure returns (bool) {
return LibBytes.endsWith(bytes(subject), bytes(needle));
}
/// @dev Returns `subject` repeated `times`.
function repeat(string memory subject, uint256 times) internal pure returns (string memory) {
return string(LibBytes.repeat(bytes(subject), times));
}
/// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
/// `start` and `end` are byte offsets.
function slice(string memory subject, uint256 start, uint256 end)
internal
pure
returns (string memory)
{
return string(LibBytes.slice(bytes(subject), start, end));
}
/// @dev Returns a copy of `subject` sliced from `start` to the end of the string.
/// `start` is a byte offset.
function slice(string memory subject, uint256 start) internal pure returns (string memory) {
return string(LibBytes.slice(bytes(subject), start, type(uint256).max));
}
/// @dev Returns all the indices of `needle` in `subject`.
/// The indices are byte offsets.
function indicesOf(string memory subject, string memory needle)
internal
pure
returns (uint256[] memory)
{
return LibBytes.indicesOf(bytes(subject), bytes(needle));
}
/// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string.
function split(string memory subject, string memory delimiter)
internal
pure
returns (string[] memory result)
{
bytes[] memory a = LibBytes.split(bytes(subject), bytes(delimiter));
/// @solidity memory-safe-assembly
assembly {
result := a
}
}
/// @dev Returns a concatenated string of `a` and `b`.
/// Cheaper than `string.concat()` and does not de-align the free memory pointer.
function concat(string memory a, string memory b) internal pure returns (string memory) {
return string(LibBytes.concat(bytes(a), bytes(b)));
}
/// @dev Returns a copy of the string in either lowercase or UPPERCASE.
/// WARNING! This function is only compatible with 7-bit ASCII strings.
function toCase(string memory subject, bool toUpper)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(subject)
if n {
result := mload(0x40)
let o := add(result, 0x20)
let d := sub(subject, result)
let flags := shl(add(70, shl(5, toUpper)), 0x3ffffff)
for { let end := add(o, n) } 1 {} {
let b := byte(0, mload(add(d, o)))
mstore8(o, xor(and(shr(b, flags), 0x20), b))
o := add(o, 1)
if eq(o, end) { break }
}
mstore(result, n) // Store the length.
mstore(o, 0) // Zeroize the slot after the string.
mstore(0x40, add(o, 0x20)) // Allocate memory.
}
}
}
/// @dev Returns a string from a small bytes32 string.
/// `s` must be null-terminated, or behavior will be undefined.
function fromSmallString(bytes32 s) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let n := 0
for {} byte(n, s) { n := add(n, 1) } {} // Scan for '\0'.
mstore(result, n) // Store the length.
let o := add(result, 0x20)
mstore(o, s) // Store the bytes of the string.
mstore(add(o, n), 0) // Zeroize the slot after the string.
mstore(0x40, add(result, 0x40)) // Allocate memory.
}
}
/// @dev Returns the small string, with all bytes after the first null byte zeroized.
function normalizeSmallString(bytes32 s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
for {} byte(result, s) { result := add(result, 1) } {} // Scan for '\0'.
mstore(0x00, s)
mstore(result, 0x00)
result := mload(0x00)
}
}
/// @dev Returns the string as a normalized null-terminated small string.
function toSmallString(string memory s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(s)
if iszero(lt(result, 33)) {
mstore(0x00, 0xec92f9a3) // `TooBigForSmallString()`.
revert(0x1c, 0x04)
}
result := shl(shl(3, sub(32, result)), mload(add(s, result)))
}
}
/// @dev Returns a lowercased copy of the string.
/// WARNING! This function is only compatible with 7-bit ASCII strings.
function lower(string memory subject) internal pure returns (string memory result) {
result = toCase(subject, false);
}
/// @dev Returns an UPPERCASED copy of the string.
/// WARNING! This function is only compatible with 7-bit ASCII strings.
function upper(string memory subject) internal pure returns (string memory result) {
result = toCase(subject, true);
}
/// @dev Escapes the string to be used within HTML tags.
function escapeHTML(string memory s) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let end := add(s, mload(s))
let o := add(result, 0x20)
// Store the bytes of the packed offsets and strides into the scratch space.
// `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6.
mstore(0x1f, 0x900094)
mstore(0x08, 0xc0000000a6ab)
// Store ""&'<>" into the scratch space.
mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b))
for {} iszero(eq(s, end)) {} {
s := add(s, 1)
let c := and(mload(s), 0xff)
// Not in `["\"","'","&","<",">"]`.
if iszero(and(shl(c, 1), 0x500000c400000000)) {
mstore8(o, c)
o := add(o, 1)
continue
}
let t := shr(248, mload(c))
mstore(o, mload(and(t, 0x1f)))
o := add(o, shr(5, t))
}
mstore(o, 0) // Zeroize the slot after the string.
mstore(result, sub(o, add(result, 0x20))) // Store the length.
mstore(0x40, add(o, 0x20)) // Allocate memory.
}
}
/// @dev Escapes the string to be used within double-quotes in a JSON.
/// If `addDoubleQuotes` is true, the result will be enclosed in double-quotes.
function escapeJSON(string memory s, bool addDoubleQuotes)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let o := add(result, 0x20)
if addDoubleQuotes {
mstore8(o, 34)
o := add(1, o)
}
// Store "\\u0000" in scratch space.
// Store "0123456789abcdef" in scratch space.
// Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`.
// into the scratch space.
mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672)
// Bitmask for detecting `["\"","\\"]`.
let e := or(shl(0x22, 1), shl(0x5c, 1))
for { let end := add(s, mload(s)) } iszero(eq(s, end)) {} {
s := add(s, 1)
let c := and(mload(s), 0xff)
if iszero(lt(c, 0x20)) {
if iszero(and(shl(c, 1), e)) {
// Not in `["\"","\\"]`.
mstore8(o, c)
o := add(o, 1)
continue
}
mstore8(o, 0x5c) // "\\".
mstore8(add(o, 1), c)
o := add(o, 2)
continue
}
if iszero(and(shl(c, 1), 0x3700)) {
// Not in `["\b","\ ","\
","\f","\d"]`.
mstore8(0x1d, mload(shr(4, c))) // Hex value.
mstore8(0x1e, mload(and(c, 15))) // Hex value.
mstore(o, mload(0x19)) // "\\u00XX".
o := add(o, 6)
continue
}
mstore8(o, 0x5c) // "\\".
mstore8(add(o, 1), mload(add(c, 8)))
o := add(o, 2)
}
if addDoubleQuotes {
mstore8(o, 34)
o := add(1, o)
}
mstore(o, 0) // Zeroize the slot after the string.
mstore(result, sub(o, add(result, 0x20))) // Store the length.
mstore(0x40, add(o, 0x20)) // Allocate memory.
}
}
/// @dev Escapes the string to be used within double-quotes in a JSON.
function escapeJSON(string memory s) internal pure returns (string memory result) {
result = escapeJSON(s, false);
}
/// @dev Encodes `s` so that it can be safely used in a URI,
/// just like `encodeURIComponent` in JavaScript.
/// See: https://developer.m
Submitted on: 2025-10-20 14:53:47
Comments
Log in to comment.
No comments yet.