// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;import {ERC721ConduitPreapproved_Solady, ERC721} from"./tokens/erc721/ERC721ConduitPreapproved_Solady.sol";
import {json} from"./onchain/json.sol";
import {Metadata} from"./onchain/Metadata.sol";
import {LibString} from"solady/utils/LibString.sol";
import {Solarray} from"solarray/Solarray.sol";
import {OnchainTraits, DynamicTraits} from"./dynamic-traits/OnchainTraits.sol";
// @authors Modified from: https://github.com/ProjectOpenSea/shipyard-core/blob/main/src/reference/AbstractNFT.solabstractcontractAbstractNFTisOnchainTraits, ERC721ConduitPreapproved_Solady{
string _name;
string _symbol;
constructor(stringmemory name_, stringmemory symbol_) {
_name = name_;
_symbol = symbol_;
}
functionname() publicviewvirtualoverridereturns (stringmemory) {
return _name;
}
functionsymbol() publicviewvirtualoverridereturns (stringmemory) {
return _symbol;
}
/**
* @notice Get the metdata URI for a given token ID
* @param tokenId The token ID to get the tokenURI for
*/functiontokenURI(uint256 tokenId) publicviewvirtualoverridereturns (stringmemory) {
return _stringURI(tokenId);
}
/**
* @notice Helper function to get the raw JSON metadata representing a given token ID
* @param tokenId The token ID to get URI for
*/function_stringURI(uint256 tokenId) internalviewvirtualreturns (stringmemory) {
return"";
}
/**
* @notice Helper function to get both the static and dynamic attributes for a given token ID
* @param tokenId The token ID to get the static and dynamic attributes for
*/function_attributes(uint256 tokenId) internalviewvirtualreturns (stringmemory) {
// get the static attributesstring[] memory staticTraits = _staticAttributes(tokenId);
// get the dynamic attributesstring[] memory dynamicTraits = _dynamicAttributes(tokenId);
// return the combined attributes as a property containing an arrayreturn json.rawProperty("attributes", json.arrayOf(staticTraits, dynamicTraits));
}
/**
* @notice Helper function to get the static attributes for a given token ID
* @param tokenId The token ID to get the static attributes for
*/function_staticAttributes(uint256 tokenId) internalviewvirtualreturns (string[] memory);
/**
* @notice Helper function to get the raw SVG image for a given token ID
* @param tokenId The token ID to get the dynamic attributes for
*/function_image(uint256 tokenId) internalviewvirtualreturns (stringmemory);
functionsupportsInterface(bytes4 interfaceId) publicviewvirtualoverride(DynamicTraits, ERC721) returns (bool) {
return DynamicTraits.supportsInterface(interfaceId) || ERC721.supportsInterface(interfaceId);
}
}
Contract Source Code
File 2 of 24: Base64.sol
// SPDX-License-Identifier: MITpragmasolidity ^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>.libraryBase64{
/// @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.functionencode(bytesmemory data, bool fileSafe, bool noPadding)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
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.ifiszero(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)`.functionencode(bytesmemory data) internalpurereturns (stringmemory result) {
result = encode(data, false, false);
}
/// @dev Encodes `data` using the base64 encoding described in RFC 4648./// Equivalent to `encode(data, fileSafe, false)`.functionencode(bytesmemory data, bool fileSafe)
internalpurereturns (stringmemory 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.functiondecode(stringmemory data) internalpurereturns (bytesmemory result) {
/// @solidity memory-safe-assemblyassembly {
let dataLength :=mload(data)
if dataLength {
let decodedLength :=mul(shr(2, dataLength), 3)
for {} 1 {} {
// If padded.ifiszero(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 :=0xfc000000fc00686c7074787c8084888c9094989ca0a4a8acb0b4b8bcc0c4c8ccmstore(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-itemmstore(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)
ifiszero(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.
}
}
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @notice Library for date time operations./// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/DateTimeLib.sol)/// @author Modified from BokkyPooBahsDateTimeLibrary (https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary)/// @dev/// Conventions:/// --------------------------------------------------------------------+/// Unit | Range | Notes |/// --------------------------------------------------------------------|/// timestamp | 0..0x1e18549868c76ff | Unix timestamp. |/// epochDay | 0..0x16d3e098039 | Days since 1970-01-01. |/// year | 1970..0xffffffff | Gregorian calendar year. |/// month | 1..12 | Gregorian calendar month. |/// day | 1..31 | Gregorian calendar day of month. |/// weekday | 1..7 | The day of the week (1-indexed). |/// --------------------------------------------------------------------+/// All timestamps of days are rounded down to 00:00:00 UTC.libraryDateTimeLib{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CONSTANTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/// Weekdays are 1-indexed, adhering to ISO 8601.uint256internalconstant MON =1;
uint256internalconstant TUE =2;
uint256internalconstant WED =3;
uint256internalconstant THU =4;
uint256internalconstant FRI =5;
uint256internalconstant SAT =6;
uint256internalconstant SUN =7;
// Months and days of months are 1-indexed, adhering to ISO 8601.uint256internalconstant JAN =1;
uint256internalconstant FEB =2;
uint256internalconstant MAR =3;
uint256internalconstant APR =4;
uint256internalconstant MAY =5;
uint256internalconstant JUN =6;
uint256internalconstant JUL =7;
uint256internalconstant AUG =8;
uint256internalconstant SEP =9;
uint256internalconstant OCT =10;
uint256internalconstant NOV =11;
uint256internalconstant DEC =12;
// These limits are large enough for most practical purposes.// Inputs that exceed these limits result in undefined behavior.uint256internalconstant MAX_SUPPORTED_YEAR =0xffffffff;
uint256internalconstant MAX_SUPPORTED_EPOCH_DAY =0x16d3e098039;
uint256internalconstant MAX_SUPPORTED_TIMESTAMP =0x1e18549868c76ff;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* DATE TIME OPERATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns the number of days since 1970-01-01 from (`year`,`month`,`day`)./// See: https://howardhinnant.github.io/date_algorithms.html/// Note: Inputs outside the supported ranges result in undefined behavior./// Use {isSupportedDate} to check if the inputs are supported.functiondateToEpochDay(uint256 year, uint256 month, uint256 day)
internalpurereturns (uint256 epochDay)
{
/// @solidity memory-safe-assemblyassembly {
year :=sub(year, lt(month, 3))
let doy :=add(shr(11, add(mul(62719, mod(add(month, 9), 12)), 769)), day)
let yoe :=mod(year, 400)
let doe :=sub(add(add(mul(yoe, 365), shr(2, yoe)), doy), div(yoe, 100))
epochDay :=sub(add(mul(div(year, 400), 146097), doe), 719469)
}
}
/// @dev Returns (`year`,`month`,`day`) from the number of days since 1970-01-01./// Note: Inputs outside the supported ranges result in undefined behavior./// Use {isSupportedDays} to check if the inputs is supported.functionepochDayToDate(uint256 epochDay)
internalpurereturns (uint256 year, uint256 month, uint256 day)
{
/// @solidity memory-safe-assemblyassembly {
epochDay :=add(epochDay, 719468)
let doe :=mod(epochDay, 146097)
let yoe :=div(sub(sub(add(doe, div(doe, 36524)), div(doe, 1460)), eq(doe, 146096)), 365)
let doy :=sub(doe, sub(add(mul(365, yoe), shr(2, yoe)), div(yoe, 100)))
let mp :=div(add(mul(5, doy), 2), 153)
day :=add(sub(doy, shr(11, add(mul(mp, 62719), 769))), 1)
month :=byte(mp, shl(160, 0x030405060708090a0b0c0102))
year :=add(add(yoe, mul(div(epochDay, 146097), 400)), lt(month, 3))
}
}
/// @dev Returns the unix timestamp from (`year`,`month`,`day`)./// Note: Inputs outside the supported ranges result in undefined behavior./// Use {isSupportedDate} to check if the inputs are supported.functiondateToTimestamp(uint256 year, uint256 month, uint256 day)
internalpurereturns (uint256 result)
{
unchecked {
result = dateToEpochDay(year, month, day) *86400;
}
}
/// @dev Returns (`year`,`month`,`day`) from the given unix timestamp./// Note: Inputs outside the supported ranges result in undefined behavior./// Use {isSupportedTimestamp} to check if the inputs are supported.functiontimestampToDate(uint256 timestamp)
internalpurereturns (uint256 year, uint256 month, uint256 day)
{
(year, month, day) = epochDayToDate(timestamp /86400);
}
/// @dev Returns the unix timestamp from/// (`year`,`month`,`day`,`hour`,`minute`,`second`)./// Note: Inputs outside the supported ranges result in undefined behavior./// Use {isSupportedDateTime} to check if the inputs are supported.functiondateTimeToTimestamp(uint256 year,
uint256 month,
uint256 day,
uint256 hour,
uint256 minute,
uint256 second
) internalpurereturns (uint256 result) {
unchecked {
result = dateToEpochDay(year, month, day) *86400+ hour *3600+ minute *60+ second;
}
}
/// @dev Returns (`year`,`month`,`day`,`hour`,`minute`,`second`)/// from the given unix timestamp./// Note: Inputs outside the supported ranges result in undefined behavior./// Use {isSupportedTimestamp} to check if the inputs are supported.functiontimestampToDateTime(uint256 timestamp)
internalpurereturns (uint256 year,
uint256 month,
uint256 day,
uint256 hour,
uint256 minute,
uint256 second
)
{
unchecked {
(year, month, day) = epochDayToDate(timestamp /86400);
uint256 secs = timestamp %86400;
hour = secs /3600;
secs = secs %3600;
minute = secs /60;
second = secs %60;
}
}
/// @dev Returns if the `year` is leap.functionisLeapYear(uint256 year) internalpurereturns (bool leap) {
/// @solidity memory-safe-assemblyassembly {
leap :=iszero(and(add(mul(iszero(mod(year, 25)), 12), 3), year))
}
}
/// @dev Returns number of days in given `month` of `year`.functiondaysInMonth(uint256 year, uint256 month) internalpurereturns (uint256 result) {
bool flag = isLeapYear(year);
/// @solidity memory-safe-assemblyassembly {
// `daysInMonths = [31,28,31,30,31,30,31,31,30,31,30,31]`.// `result = daysInMonths[month - 1] + isLeapYear(year)`.
result :=add(byte(month, shl(152, 0x1f1c1f1e1f1e1f1f1e1f1e1f)), and(eq(month, 2), flag))
}
}
/// @dev Returns the weekday from the unix timestamp./// Monday: 1, Tuesday: 2, ....., Sunday: 7.functionweekday(uint256 timestamp) internalpurereturns (uint256 result) {
unchecked {
result = ((timestamp /86400+3) %7) +1;
}
}
/// @dev Returns if (`year`,`month`,`day`) is a supported date./// - `1970 <= year <= MAX_SUPPORTED_YEAR`./// - `1 <= month <= 12`./// - `1 <= day <= daysInMonth(year, month)`.functionisSupportedDate(uint256 year, uint256 month, uint256 day)
internalpurereturns (bool result)
{
uint256 md = daysInMonth(year, month);
/// @solidity memory-safe-assemblyassembly {
result :=and(
lt(sub(year, 1970), sub(MAX_SUPPORTED_YEAR, 1969)),
and(lt(sub(month, 1), 12), lt(sub(day, 1), md))
)
}
}
/// @dev Returns if (`year`,`month`,`day`,`hour`,`minute`,`second`) is a supported date time./// - `1970 <= year <= MAX_SUPPORTED_YEAR`./// - `1 <= month <= 12`./// - `1 <= day <= daysInMonth(year, month)`./// - `hour < 24`./// - `minute < 60`./// - `second < 60`.functionisSupportedDateTime(uint256 year,
uint256 month,
uint256 day,
uint256 hour,
uint256 minute,
uint256 second
) internalpurereturns (bool result) {
if (isSupportedDate(year, month, day)) {
/// @solidity memory-safe-assemblyassembly {
result :=and(lt(hour, 24), and(lt(minute, 60), lt(second, 60)))
}
}
}
/// @dev Returns if `epochDay` is a supported unix epoch day.functionisSupportedEpochDay(uint256 epochDay) internalpurereturns (bool result) {
unchecked {
result = epochDay < MAX_SUPPORTED_EPOCH_DAY +1;
}
}
/// @dev Returns if `timestamp` is a supported unix timestamp.functionisSupportedTimestamp(uint256 timestamp) internalpurereturns (bool result) {
unchecked {
result = timestamp < MAX_SUPPORTED_TIMESTAMP +1;
}
}
/// @dev Returns the unix timestamp of the given `n`th weekday `wd`, in `month` of `year`./// Example: 3rd Friday of Feb 2022 is `nthWeekdayInMonthOfYearTimestamp(2022, 2, 3, 5)`/// Note: `n` is 1-indexed for traditional consistency./// Invalid weekdays (i.e. `wd == 0 || wd > 7`) result in undefined behavior.functionnthWeekdayInMonthOfYearTimestamp(uint256 year, uint256 month, uint256 n, uint256 wd)
internalpurereturns (uint256 result)
{
uint256 d = dateToEpochDay(year, month, 1);
uint256 md = daysInMonth(year, month);
/// @solidity memory-safe-assemblyassembly {
let diff :=sub(wd, add(mod(add(d, 3), 7), 1))
let date :=add(mul(sub(n, 1), 7), add(mul(gt(diff, 6), 7), diff))
result :=mul(mul(86400, add(date, d)), and(lt(date, md), iszero(iszero(n))))
}
}
/// @dev Returns the unix timestamp of the most recent Monday.functionmondayTimestamp(uint256 timestamp) internalpurereturns (uint256 result) {
uint256 t = timestamp;
/// @solidity memory-safe-assemblyassembly {
let day :=div(t, 86400)
result :=mul(mul(sub(day, mod(add(day, 3), 7)), 86400), gt(t, 345599))
}
}
/// @dev Returns whether the unix timestamp falls on a Saturday or Sunday./// To check whether it is a week day, just take the negation of the result.functionisWeekEnd(uint256 timestamp) internalpurereturns (bool result) {
result = weekday(timestamp) > FRI;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* DATE TIME ARITHMETIC OPERATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Adds `numYears` to the unix timestamp, and returns the result./// Note: The result will share the same Gregorian calendar month,/// but different Gregorian calendar years for non-zero `numYears`./// If the Gregorian calendar month of the result has less days/// than the Gregorian calendar month day of the `timestamp`,/// the result's month day will be the maximum possible value for the month./// (e.g. from 29th Feb to 28th Feb)functionaddYears(uint256 timestamp, uint256 numYears) internalpurereturns (uint256 result) {
(uint256 year, uint256 month, uint256 day) = epochDayToDate(timestamp /86400);
result = _offsetted(year + numYears, month, day, timestamp);
}
/// @dev Adds `numMonths` to the unix timestamp, and returns the result./// Note: If the Gregorian calendar month of the result has less days/// than the Gregorian calendar month day of the `timestamp`,/// the result's month day will be the maximum possible value for the month./// (e.g. from 29th Feb to 28th Feb)functionaddMonths(uint256 timestamp, uint256 numMonths)
internalpurereturns (uint256 result)
{
(uint256 year, uint256 month, uint256 day) = epochDayToDate(timestamp /86400);
month = _sub(month + numMonths, 1);
result = _offsetted(year + month /12, _add(month %12, 1), day, timestamp);
}
/// @dev Adds `numDays` to the unix timestamp, and returns the result.functionaddDays(uint256 timestamp, uint256 numDays) internalpurereturns (uint256 result) {
result = timestamp + numDays *86400;
}
/// @dev Adds `numHours` to the unix timestamp, and returns the result.functionaddHours(uint256 timestamp, uint256 numHours) internalpurereturns (uint256 result) {
result = timestamp + numHours *3600;
}
/// @dev Adds `numMinutes` to the unix timestamp, and returns the result.functionaddMinutes(uint256 timestamp, uint256 numMinutes)
internalpurereturns (uint256 result)
{
result = timestamp + numMinutes *60;
}
/// @dev Adds `numSeconds` to the unix timestamp, and returns the result.functionaddSeconds(uint256 timestamp, uint256 numSeconds)
internalpurereturns (uint256 result)
{
result = timestamp + numSeconds;
}
/// @dev Subtracts `numYears` from the unix timestamp, and returns the result./// Note: The result will share the same Gregorian calendar month,/// but different Gregorian calendar years for non-zero `numYears`./// If the Gregorian calendar month of the result has less days/// than the Gregorian calendar month day of the `timestamp`,/// the result's month day will be the maximum possible value for the month./// (e.g. from 29th Feb to 28th Feb)functionsubYears(uint256 timestamp, uint256 numYears) internalpurereturns (uint256 result) {
(uint256 year, uint256 month, uint256 day) = epochDayToDate(timestamp /86400);
result = _offsetted(year - numYears, month, day, timestamp);
}
/// @dev Subtracts `numYears` from the unix timestamp, and returns the result./// Note: If the Gregorian calendar month of the result has less days/// than the Gregorian calendar month day of the `timestamp`,/// the result's month day will be the maximum possible value for the month./// (e.g. from 29th Feb to 28th Feb)functionsubMonths(uint256 timestamp, uint256 numMonths)
internalpurereturns (uint256 result)
{
(uint256 year, uint256 month, uint256 day) = epochDayToDate(timestamp /86400);
uint256 yearMonth = _totalMonths(year, month) - _add(numMonths, 1);
result = _offsetted(yearMonth /12, _add(yearMonth %12, 1), day, timestamp);
}
/// @dev Subtracts `numDays` from the unix timestamp, and returns the result.functionsubDays(uint256 timestamp, uint256 numDays) internalpurereturns (uint256 result) {
result = timestamp - numDays *86400;
}
/// @dev Subtracts `numHours` from the unix timestamp, and returns the result.functionsubHours(uint256 timestamp, uint256 numHours) internalpurereturns (uint256 result) {
result = timestamp - numHours *3600;
}
/// @dev Subtracts `numMinutes` from the unix timestamp, and returns the result.functionsubMinutes(uint256 timestamp, uint256 numMinutes)
internalpurereturns (uint256 result)
{
result = timestamp - numMinutes *60;
}
/// @dev Subtracts `numSeconds` from the unix timestamp, and returns the result.functionsubSeconds(uint256 timestamp, uint256 numSeconds)
internalpurereturns (uint256 result)
{
result = timestamp - numSeconds;
}
/// @dev Returns the difference in Gregorian calendar years/// between `fromTimestamp` and `toTimestamp`./// Note: Even if the true time difference is less than a year,/// the difference can be non-zero is the timestamps are/// from different Gregorian calendar yearsfunctiondiffYears(uint256 fromTimestamp, uint256 toTimestamp)
internalpurereturns (uint256 result)
{
toTimestamp - fromTimestamp;
(uint256 fromYear,,) = epochDayToDate(fromTimestamp /86400);
(uint256 toYear,,) = epochDayToDate(toTimestamp /86400);
result = _sub(toYear, fromYear);
}
/// @dev Returns the difference in Gregorian calendar months/// between `fromTimestamp` and `toTimestamp`./// Note: Even if the true time difference is less than a month,/// the difference can be non-zero is the timestamps are/// from different Gregorian calendar months.functiondiffMonths(uint256 fromTimestamp, uint256 toTimestamp)
internalpurereturns (uint256 result)
{
toTimestamp - fromTimestamp;
(uint256 fromYear, uint256 fromMonth,) = epochDayToDate(fromTimestamp /86400);
(uint256 toYear, uint256 toMonth,) = epochDayToDate(toTimestamp /86400);
result = _sub(_totalMonths(toYear, toMonth), _totalMonths(fromYear, fromMonth));
}
/// @dev Returns the difference in days between `fromTimestamp` and `toTimestamp`.functiondiffDays(uint256 fromTimestamp, uint256 toTimestamp)
internalpurereturns (uint256 result)
{
result = (toTimestamp - fromTimestamp) /86400;
}
/// @dev Returns the difference in hours between `fromTimestamp` and `toTimestamp`.functiondiffHours(uint256 fromTimestamp, uint256 toTimestamp)
internalpurereturns (uint256 result)
{
result = (toTimestamp - fromTimestamp) /3600;
}
/// @dev Returns the difference in minutes between `fromTimestamp` and `toTimestamp`.functiondiffMinutes(uint256 fromTimestamp, uint256 toTimestamp)
internalpurereturns (uint256 result)
{
result = (toTimestamp - fromTimestamp) /60;
}
/// @dev Returns the difference in seconds between `fromTimestamp` and `toTimestamp`.functiondiffSeconds(uint256 fromTimestamp, uint256 toTimestamp)
internalpurereturns (uint256 result)
{
result = toTimestamp - fromTimestamp;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* PRIVATE HELPERS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Unchecked arithmetic for computing the total number of months.function_totalMonths(uint256 numYears, uint256 numMonths)
privatepurereturns (uint256 total)
{
unchecked {
total = numYears *12+ numMonths;
}
}
/// @dev Unchecked arithmetic for adding two numbers.function_add(uint256 a, uint256 b) privatepurereturns (uint256 c) {
unchecked {
c = a + b;
}
}
/// @dev Unchecked arithmetic for subtracting two numbers.function_sub(uint256 a, uint256 b) privatepurereturns (uint256 c) {
unchecked {
c = a - b;
}
}
/// @dev Returns the offsetted timestamp.function_offsetted(uint256 year, uint256 month, uint256 day, uint256 timestamp)
privatepurereturns (uint256 result)
{
uint256 dm = daysInMonth(year, month);
if (day >= dm) {
day = dm;
}
result = dateToEpochDay(year, month, day) *86400+ (timestamp %86400);
}
}
Contract Source Code
File 5 of 24: DynamicTraits.sol
// SPDX-License-Identifier: CC0-1.0pragmasolidity ^0.8.19;import {IERC7496} from"./interfaces/IERC7496.sol";
libraryDynamicTraitsStorage{
structLayout {
/// @dev A mapping of token ID to a mapping of trait key to trait value.mapping(uint256 tokenId =>mapping(bytes32 traitKey =>bytes32 traitValue)) _traits;
/// @dev An offchain string URI that points to a JSON file containing trait metadata.string _traitMetadataURI;
}
bytes32internalconstant STORAGE_SLOT =keccak256("contracts.storage.erc7496-dynamictraits");
functionlayout() internalpurereturns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot:= slot
}
}
}
/**
* @title DynamicTraits
*
* @dev Implementation of [ERC-7496](https://eips.ethereum.org/EIPS/eip-7496) Dynamic Traits.
* Uses a storage layout pattern for upgradeable contracts.
*
* Requirements:
* - Overwrite `setTrait` with access role restriction.
* - Expose a function for `setTraitMetadataURI` with access role restriction if desired.
*/contractDynamicTraitsisIERC7496{
usingDynamicTraitsStorageforDynamicTraitsStorage.Layout;
/**
* @notice Get the value of a trait for a given token ID.
* @param tokenId The token ID to get the trait value for
* @param traitKey The trait key to get the value of
*/functiongetTraitValue(uint256 tokenId, bytes32 traitKey) publicviewvirtualreturns (bytes32 traitValue) {
// Return the trait value.return DynamicTraitsStorage.layout()._traits[tokenId][traitKey];
}
/**
* @notice Get the values of traits for a given token ID.
* @param tokenId The token ID to get the trait values for
* @param traitKeys The trait keys to get the values of
*/functiongetTraitValues(uint256 tokenId, bytes32[] calldata traitKeys)
publicviewvirtualreturns (bytes32[] memory traitValues)
{
// Set the length of the traitValues return array.uint256 length = traitKeys.length;
traitValues =newbytes32[](length);
// Assign each trait value to the corresopnding key.for (uint256 i =0; i < length;) {
bytes32 traitKey = traitKeys[i];
traitValues[i] = getTraitValue(tokenId, traitKey);
unchecked {
++i;
}
}
}
/**
* @notice Get the URI for the trait metadata
*/functiongetTraitMetadataURI() externalviewvirtualreturns (stringmemory labelsURI) {
// Return the trait metadata URI.return DynamicTraitsStorage.layout()._traitMetadataURI;
}
/**
* @notice Set the value of a trait for a given token ID.
* Reverts if the trait value is unchanged.
* @dev IMPORTANT: Override this method with access role restriction.
* @param tokenId The token ID to set the trait value for
* @param traitKey The trait key to set the value of
* @param newValue The new trait value to set
*/functionsetTrait(uint256 tokenId, bytes32 traitKey, bytes32 newValue) publicvirtual{
// Revert if the new value is the same as the existing value.bytes32 existingValue = DynamicTraitsStorage.layout()._traits[tokenId][traitKey];
if (existingValue == newValue) {
revert TraitValueUnchanged();
}
// Set the new trait value.
_setTrait(tokenId, traitKey, newValue);
// Emit the event noting the update.emit TraitUpdated(traitKey, tokenId, newValue);
}
/**
* @notice Set the trait value (without emitting an event).
* @param tokenId The token ID to set the trait value for
* @param traitKey The trait key to set the value of
* @param newValue The new trait value to set
*/function_setTrait(uint256 tokenId, bytes32 traitKey, bytes32 newValue) internalvirtual{
// Set the new trait value.
DynamicTraitsStorage.layout()._traits[tokenId][traitKey] = newValue;
}
/**
* @notice Set the URI for the trait metadata.
* @param uri The new URI to set.
*/function_setTraitMetadataURI(stringmemory uri) internalvirtual{
// Set the new trait metadata URI.
DynamicTraitsStorage.layout()._traitMetadataURI = uri;
// Emit the event noting the update.emit TraitMetadataURIUpdated();
}
/**
* @dev See {IERC165-supportsInterface}.
*/functionsupportsInterface(bytes4 interfaceId) publicviewvirtualreturns (bool) {
return interfaceId ==type(IERC7496).interfaceId;
}
}
Contract Source Code
File 6 of 24: ECDSA.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @notice Gas optimized ECDSA wrapper./// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol)/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol)/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol)////// @dev Note:/// - The recovery functions use the ecrecover precompile (0x1)./// - As of Solady version 0.0.68, the `recover` variants will revert upon recovery failure./// This is for more safety by default./// Use the `tryRecover` variants if you need to get the zero address back/// upon recovery failure instead./// - As of Solady version 0.0.134, all `bytes signature` variants accept both/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures./// See: https://eips.ethereum.org/EIPS/eip-2098/// This is for calldata efficiency on smart accounts prevalent on L2s.////// WARNING! Do NOT directly use signatures as unique identifiers:/// - The recovery operations do NOT check if a signature is non-malleable./// - Use a nonce in the digest to prevent replay attacks on the same contract./// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts./// EIP-712 also enables readable signing of typed data for better user safety./// - If you need a unique hash from a signature, please use the `canonicalHash` functions.libraryECDSA{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CONSTANTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The order of the secp256k1 elliptic curve.uint256internalconstant N =0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
/// @dev `N/2 + 1`. Used for checking the malleability of the signature.uint256privateconstant _HALF_N_PLUS_1 =0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CUSTOM ERRORS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The signature is invalid.errorInvalidSignature();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* RECOVERY OPERATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.functionrecover(bytes32 hash, bytesmemory signature) internalviewreturns (address result) {
/// @solidity memory-safe-assemblyassembly {
result :=1let m :=mload(0x40) // Cache the free memory pointer.for {} 1 {} {
mstore(0x00, hash)
mstore(0x40, mload(add(signature, 0x20))) // `r`.ifeq(mload(signature), 64) {
let vs :=mload(add(signature, 0x40))
mstore(0x20, add(shr(255, vs), 27)) // `v`.mstore(0x60, shr(1, shl(1, vs))) // `s`.break
}
ifeq(mload(signature), 65) {
mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.mstore(0x60, mload(add(signature, 0x40))) // `s`.break
}
result :=0break
}
result :=mload(
staticcall(
gas(), // Amount of gas left for the transaction.
result, // Address of `ecrecover`.0x00, // Start of input.0x80, // Size of input.0x01, // Start of output.0x20// Size of output.
)
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.ifiszero(returndatasize()) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot.mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.functionrecoverCalldata(bytes32 hash, bytescalldata signature)
internalviewreturns (address result)
{
/// @solidity memory-safe-assemblyassembly {
result :=1let m :=mload(0x40) // Cache the free memory pointer.mstore(0x00, hash)
for {} 1 {} {
ifeq(signature.length, 64) {
let vs :=calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.mstore(0x40, calldataload(signature.offset)) // `r`.mstore(0x60, shr(1, shl(1, vs))) // `s`.break
}
ifeq(signature.length, 65) {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`.break
}
result :=0break
}
result :=mload(
staticcall(
gas(), // Amount of gas left for the transaction.
result, // Address of `ecrecover`.0x00, // Start of input.0x80, // Size of input.0x01, // Start of output.0x20// Size of output.
)
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.ifiszero(returndatasize()) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot.mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Recovers the signer's address from a message digest `hash`,/// and the EIP-2098 short form signature defined by `r` and `vs`.functionrecover(bytes32 hash, bytes32 r, bytes32 vs) internalviewreturns (address result) {
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40) // Cache the free memory pointer.mstore(0x00, hash)
mstore(0x20, add(shr(255, vs), 27)) // `v`.mstore(0x40, r)
mstore(0x60, shr(1, shl(1, vs))) // `s`.
result :=mload(
staticcall(
gas(), // Amount of gas left for the transaction.1, // Address of `ecrecover`.0x00, // Start of input.0x80, // Size of input.0x01, // Start of output.0x20// Size of output.
)
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.ifiszero(returndatasize()) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot.mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Recovers the signer's address from a message digest `hash`,/// and the signature defined by `v`, `r`, `s`.functionrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internalviewreturns (address result)
{
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40) // Cache the free memory pointer.mstore(0x00, hash)
mstore(0x20, and(v, 0xff))
mstore(0x40, r)
mstore(0x60, s)
result :=mload(
staticcall(
gas(), // Amount of gas left for the transaction.1, // Address of `ecrecover`.0x00, // Start of input.0x80, // Size of input.0x01, // Start of output.0x20// Size of output.
)
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.ifiszero(returndatasize()) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot.mstore(0x40, m) // Restore the free memory pointer.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* TRY-RECOVER OPERATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/// WARNING!// These functions will NOT revert upon recovery failure.// Instead, they will return the zero address upon recovery failure.// It is critical that the returned address is NEVER compared against// a zero address (e.g. an uninitialized address variable)./// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.functiontryRecover(bytes32 hash, bytesmemory signature)
internalviewreturns (address result)
{
/// @solidity memory-safe-assemblyassembly {
result :=1let m :=mload(0x40) // Cache the free memory pointer.for {} 1 {} {
mstore(0x00, hash)
mstore(0x40, mload(add(signature, 0x20))) // `r`.ifeq(mload(signature), 64) {
let vs :=mload(add(signature, 0x40))
mstore(0x20, add(shr(255, vs), 27)) // `v`.mstore(0x60, shr(1, shl(1, vs))) // `s`.break
}
ifeq(mload(signature), 65) {
mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.mstore(0x60, mload(add(signature, 0x40))) // `s`.break
}
result :=0break
}
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
result, // Address of `ecrecover`.0x00, // Start of input.0x80, // Size of input.0x40, // Start of output.0x20// Size of output.
)
)
mstore(0x60, 0) // Restore the zero slot.// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result :=mload(xor(0x60, returndatasize()))
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.functiontryRecoverCalldata(bytes32 hash, bytescalldata signature)
internalviewreturns (address result)
{
/// @solidity memory-safe-assemblyassembly {
result :=1let m :=mload(0x40) // Cache the free memory pointer.mstore(0x00, hash)
for {} 1 {} {
ifeq(signature.length, 64) {
let vs :=calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.mstore(0x40, calldataload(signature.offset)) // `r`.mstore(0x60, shr(1, shl(1, vs))) // `s`.break
}
ifeq(signature.length, 65) {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`.break
}
result :=0break
}
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
result, // Address of `ecrecover`.0x00, // Start of input.0x80, // Size of input.0x40, // Start of output.0x20// Size of output.
)
)
mstore(0x60, 0) // Restore the zero slot.// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result :=mload(xor(0x60, returndatasize()))
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Recovers the signer's address from a message digest `hash`,/// and the EIP-2098 short form signature defined by `r` and `vs`.functiontryRecover(bytes32 hash, bytes32 r, bytes32 vs)
internalviewreturns (address result)
{
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40) // Cache the free memory pointer.mstore(0x00, hash)
mstore(0x20, add(shr(255, vs), 27)) // `v`.mstore(0x40, r)
mstore(0x60, shr(1, shl(1, vs))) // `s`.pop(
staticcall(
gas(), // Amount of gas left for the transaction.1, // Address of `ecrecover`.0x00, // Start of input.0x80, // Size of input.0x40, // Start of output.0x20// Size of output.
)
)
mstore(0x60, 0) // Restore the zero slot.// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result :=mload(xor(0x60, returndatasize()))
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Recovers the signer's address from a message digest `hash`,/// and the signature defined by `v`, `r`, `s`.functiontryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internalviewreturns (address result)
{
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40) // Cache the free memory pointer.mstore(0x00, hash)
mstore(0x20, and(v, 0xff))
mstore(0x40, r)
mstore(0x60, s)
pop(
staticcall(
gas(), // Amount of gas left for the transaction.1, // Address of `ecrecover`.0x00, // Start of input.0x80, // Size of input.0x40, // Start of output.0x20// Size of output.
)
)
mstore(0x60, 0) // Restore the zero slot.// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result :=mload(xor(0x60, returndatasize()))
mstore(0x40, m) // Restore the free memory pointer.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* HASHING OPERATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns an Ethereum Signed Message, created from a `hash`./// This produces a hash corresponding to the one signed with the/// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign)/// JSON-RPC method as part of EIP-191.functiontoEthSignedMessageHash(bytes32 hash) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x20, hash) // Store into scratch space for keccak256.mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
result :=keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
}
}
/// @dev Returns an Ethereum Signed Message, created from `s`./// This produces a hash corresponding to the one signed with the/// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign)/// JSON-RPC method as part of EIP-191./// Note: Supports lengths of `s` up to 999999 bytes.functiontoEthSignedMessageHash(bytesmemory s) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
let sLength :=mload(s)
let o :=0x20mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.mstore(0x00, 0x00)
// Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.for { let temp := sLength } 1 {} {
o :=sub(o, 1)
mstore8(o, add(48, mod(temp, 10)))
temp :=div(temp, 10)
ifiszero(temp) { break }
}
let n :=sub(0x3a, o) // Header length: `26 + 32 - o`.// Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
result :=keccak256(add(s, sub(0x20, n)), add(n, sLength))
mstore(s, sLength) // Restore the length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CANONICAL HASH FUNCTIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/// The following functions returns the hash of the signature in it's canonicalized format,// which is the 65-byte `abi.encodePacked(r, s, uint8(v))`, where `v` is either 27 or 28.// If `s` is greater than `N / 2` then it will be converted to `N - s`// and the `v` value will be flipped.// If the signature has an invalid length, or if `v` is invalid,// a uniquely corrupt hash will be returned.// These functions are useful for "poor-mans-VRF"./// @dev Returns the canonical hash of `signature`.functioncanonicalHash(bytesmemory signature) internalpurereturns (bytes32 result) {
// @solidity memory-safe-assemblyassembly {
let l :=mload(signature)
for {} 1 {} {
mstore(0x00, mload(add(signature, 0x20))) // `r`.let s :=mload(add(signature, 0x40))
let v :=mload(add(signature, 0x41))
ifeq(l, 64) {
v :=add(shr(255, s), 27)
s :=shr(1, shl(1, s))
}
ifiszero(lt(s, _HALF_N_PLUS_1)) {
v :=xor(v, 7)
s :=sub(N, s)
}
mstore(0x21, v)
mstore(0x20, s)
result :=keccak256(0x00, 0x41)
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.break
}
// If the length is neither 64 nor 65, return a uniquely corrupted hash.ifiszero(lt(sub(l, 64), 2)) {
// `bytes4(keccak256("InvalidSignatureLength"))`.
result :=xor(keccak256(add(signature, 0x20), l), 0xd62f1ab2)
}
}
}
/// @dev Returns the canonical hash of `signature`.functioncanonicalHashCalldata(bytescalldata signature)
internalpurereturns (bytes32 result)
{
// @solidity memory-safe-assemblyassembly {
let l := signature.lengthfor {} 1 {} {
mstore(0x00, calldataload(signature.offset)) // `r`.let s :=calldataload(add(signature.offset, 0x20))
let v :=calldataload(add(signature.offset, 0x21))
ifeq(l, 64) {
v :=add(shr(255, s), 27)
s :=shr(1, shl(1, s))
}
ifiszero(lt(s, _HALF_N_PLUS_1)) {
v :=xor(v, 7)
s :=sub(N, s)
}
mstore(0x21, v)
mstore(0x20, s)
result :=keccak256(0x00, 0x41)
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.break
}
// If the length is neither 64 nor 65, return a uniquely corrupted hash.ifiszero(lt(sub(l, 64), 2)) {
calldatacopy(mload(0x40), signature.offset, l)
// `bytes4(keccak256("InvalidSignatureLength"))`.
result :=xor(keccak256(mload(0x40), l), 0xd62f1ab2)
}
}
}
/// @dev Returns the canonical hash of `signature`.functioncanonicalHash(bytes32 r, bytes32 vs) internalpurereturns (bytes32 result) {
// @solidity memory-safe-assemblyassembly {
mstore(0x00, r) // `r`.let v :=add(shr(255, vs), 27)
let s :=shr(1, shl(1, vs))
mstore(0x21, v)
mstore(0x20, s)
result :=keccak256(0x00, 0x41)
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the canonical hash of `signature`.functioncanonicalHash(uint8 v, bytes32 r, bytes32 s) internalpurereturns (bytes32 result) {
// @solidity memory-safe-assemblyassembly {
mstore(0x00, r) // `r`.ifiszero(lt(s, _HALF_N_PLUS_1)) {
v :=xor(v, 7)
s :=sub(N, s)
}
mstore(0x21, v)
mstore(0x20, s)
result :=keccak256(0x00, 0x41)
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* EMPTY CALLDATA HELPERS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns an empty calldata bytes.functionemptySignature() internalpurereturns (bytescalldata signature) {
/// @solidity memory-safe-assemblyassembly {
signature.length:=0
}
}
}
Contract Source Code
File 7 of 24: ERC721.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @notice Simple ERC721 implementation with storage hitchhiking./// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC721.sol)/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC721/ERC721.sol)////// @dev Note:/// - The ERC721 standard allows for self-approvals./// For performance, this implementation WILL NOT revert for such actions./// Please add any checks with overrides if desired./// - For performance, methods are made payable where permitted by the ERC721 standard./// - The `safeTransfer` functions use the identity precompile (0x4)/// to copy memory internally.////// If you are overriding:/// - NEVER violate the ERC721 invariant:/// the balance of an owner MUST always be equal to their number of ownership slots./// The transfer functions do not have an underflow guard for user token balances./// - Make sure all variables written to storage are properly cleaned// (e.g. the bool value for `isApprovedForAll` MUST be either 1 or 0 under the hood)./// - Check that the overridden function is actually used in the function you want to/// change the behavior of. Much of the code has been manually inlined for performance.abstractcontractERC721{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CONSTANTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev An account can hold up to 4294967295 tokens.uint256internalconstant _MAX_ACCOUNT_BALANCE =0xffffffff;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CUSTOM ERRORS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Only the token owner or an approved account can manage the token.errorNotOwnerNorApproved();
/// @dev The token does not exist.errorTokenDoesNotExist();
/// @dev The token already exists.errorTokenAlreadyExists();
/// @dev Cannot query the balance for the zero address.errorBalanceQueryForZeroAddress();
/// @dev Cannot mint or transfer to the zero address.errorTransferToZeroAddress();
/// @dev The token must be owned by `from`.errorTransferFromIncorrectOwner();
/// @dev The recipient's balance has overflowed.errorAccountBalanceOverflow();
/// @dev Cannot safely transfer to a contract that does not implement/// the ERC721Receiver interface.errorTransferToNonERC721ReceiverImplementer();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* EVENTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Emitted when token `id` is transferred from `from` to `to`.eventTransfer(addressindexedfrom, addressindexed to, uint256indexed id);
/// @dev Emitted when `owner` enables `account` to manage the `id` token.eventApproval(addressindexed owner, addressindexed account, uint256indexed id);
/// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens.eventApprovalForAll(addressindexed owner, addressindexed operator, bool isApproved);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.uint256privateconstant _TRANSFER_EVENT_SIGNATURE =0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.uint256privateconstant _APPROVAL_EVENT_SIGNATURE =0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/// @dev `keccak256(bytes("ApprovalForAll(address,address,bool)"))`.uint256privateconstant _APPROVAL_FOR_ALL_EVENT_SIGNATURE =0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* STORAGE *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The ownership data slot of `id` is given by:/// ```/// mstore(0x00, id)/// mstore(0x1c, _ERC721_MASTER_SLOT_SEED)/// let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))/// ```/// Bits Layout:/// - [0..159] `addr`/// - [160..255] `extraData`////// The approved address slot is given by: `add(1, ownershipSlot)`.////// See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip////// The balance slot of `owner` is given by:/// ```/// mstore(0x1c, _ERC721_MASTER_SLOT_SEED)/// mstore(0x00, owner)/// let balanceSlot := keccak256(0x0c, 0x1c)/// ```/// Bits Layout:/// - [0..31] `balance`/// - [32..255] `aux`////// The `operator` approval slot of `owner` is given by:/// ```/// mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))/// mstore(0x00, owner)/// let operatorApprovalSlot := keccak256(0x0c, 0x30)/// ```uint256privateconstant _ERC721_MASTER_SLOT_SEED =0x7d8825530a5a2e7a<<192;
/// @dev Pre-shifted and pre-masked constant.uint256privateconstant _ERC721_MASTER_SLOT_SEED_MASKED =0x0a5a2e7a00000000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* ERC721 METADATA *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns the token collection name.functionname() publicviewvirtualreturns (stringmemory);
/// @dev Returns the token collection symbol.functionsymbol() publicviewvirtualreturns (stringmemory);
/// @dev Returns the Uniform Resource Identifier (URI) for token `id`.functiontokenURI(uint256 id) publicviewvirtualreturns (stringmemory);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* ERC721 *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns the owner of token `id`.////// Requirements:/// - Token `id` must exist.functionownerOf(uint256 id) publicviewvirtualreturns (address result) {
result = _ownerOf(id);
/// @solidity memory-safe-assemblyassembly {
ifiszero(result) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.revert(0x1c, 0x04)
}
}
}
/// @dev Returns the number of tokens owned by `owner`.////// Requirements:/// - `owner` must not be the zero address.functionbalanceOf(address owner) publicviewvirtualreturns (uint256 result) {
/// @solidity memory-safe-assemblyassembly {
// Revert if the `owner` is the zero address.ifiszero(owner) {
mstore(0x00, 0x8f4eb604) // `BalanceQueryForZeroAddress()`.revert(0x1c, 0x04)
}
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
result :=and(sload(keccak256(0x0c, 0x1c)), _MAX_ACCOUNT_BALANCE)
}
}
/// @dev Returns the account approved to manage token `id`.////// Requirements:/// - Token `id` must exist.functiongetApproved(uint256 id) publicviewvirtualreturns (address result) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot :=add(id, add(id, keccak256(0x00, 0x20)))
ifiszero(shl(96, sload(ownershipSlot))) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.revert(0x1c, 0x04)
}
result :=sload(add(1, ownershipSlot))
}
}
/// @dev Sets `account` as the approved account to manage token `id`.////// Requirements:/// - Token `id` must exist./// - The caller must be the owner of the token,/// or an approved operator for the token owner.////// Emits an {Approval} event.functionapprove(address account, uint256 id) publicpayablevirtual{
_approve(msg.sender, account, id);
}
/// @dev Returns whether `operator` is approved to manage the tokens of `owner`.functionisApprovedForAll(address owner, address operator)
publicviewvirtualreturns (bool result)
{
/// @solidity memory-safe-assemblyassembly {
mstore(0x1c, operator)
mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
mstore(0x00, owner)
result :=sload(keccak256(0x0c, 0x30))
}
}
/// @dev Sets whether `operator` is approved to manage the tokens of the caller.////// Emits an {ApprovalForAll} event.functionsetApprovalForAll(address operator, bool isApproved) publicvirtual{
/// @solidity memory-safe-assemblyassembly {
// Convert to 0 or 1.
isApproved :=iszero(iszero(isApproved))
// Update the `isApproved` for (`msg.sender`, `operator`).mstore(0x1c, operator)
mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x30), isApproved)
// Emit the {ApprovalForAll} event.mstore(0x00, isApproved)
// forgefmt: disable-next-itemlog3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), shr(96, shl(96, operator)))
}
}
/// @dev Transfers token `id` from `from` to `to`.////// Requirements:////// - Token `id` must exist./// - `from` must be the owner of the token./// - `to` cannot be the zero address./// - The caller must be the owner of the token, or be approved to manage the token.////// Emits a {Transfer} event.functiontransferFrom(addressfrom, address to, uint256 id) publicpayablevirtual{
_beforeTokenTransfer(from, to, id);
/// @solidity memory-safe-assemblyassembly {
// Clear the upper 96 bits.let bitmaskAddress :=shr(96, not(0))
from :=and(bitmaskAddress, from)
to :=and(bitmaskAddress, to)
// Load the ownership data.mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, caller()))
let ownershipSlot :=add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked :=sload(ownershipSlot)
let owner :=and(bitmaskAddress, ownershipPacked)
// Revert if the token does not exist, or if `from` is not the owner.ifiszero(mul(owner, eq(owner, from))) {
// `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
revert(0x1c, 0x04)
}
// Load, check, and update the token approval.
{
mstore(0x00, from)
let approvedAddress :=sload(add(1, ownershipSlot))
// Revert if the caller is not the owner, nor approved.ifiszero(or(eq(caller(), from), eq(caller(), approvedAddress))) {
ifiszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.revert(0x1c, 0x04)
}
}
// Delete the approved address if any.if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Update with the new owner.sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
// Decrement the balance of `from`.
{
let fromBalanceSlot :=keccak256(0x0c, 0x1c)
sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
}
// Increment the balance of `to`.
{
mstore(0x00, to)
let toBalanceSlot :=keccak256(0x0c, 0x1c)
let toBalanceSlotPacked :=add(sload(toBalanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.ifiszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(toBalanceSlot, toBalanceSlotPacked)
}
// Emit the {Transfer} event.log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
_afterTokenTransfer(from, to, id);
}
/// @dev Equivalent to `safeTransferFrom(from, to, id, "")`.functionsafeTransferFrom(addressfrom, address to, uint256 id) publicpayablevirtual{
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.////// Requirements:////// - Token `id` must exist./// - `from` must be the owner of the token./// - `to` cannot be the zero address./// - The caller must be the owner of the token, or be approved to manage the token./// - If `to` refers to a smart contract, it must implement/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.////// Emits a {Transfer} event.functionsafeTransferFrom(addressfrom, address to, uint256 id, bytescalldata data)
publicpayablevirtual{
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/// @dev Returns true if this contract implements the interface defined by `interfaceId`./// See: https://eips.ethereum.org/EIPS/eip-165/// This function call must use less than 30000 gas.functionsupportsInterface(bytes4 interfaceId) publicviewvirtualreturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
let s :=shr(224, interfaceId)
// ERC165: 0x01ffc9a7, ERC721: 0x80ac58cd, ERC721Metadata: 0x5b5e139f.
result :=or(or(eq(s, 0x01ffc9a7), eq(s, 0x80ac58cd)), eq(s, 0x5b5e139f))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* INTERNAL QUERY FUNCTIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns if token `id` exists.function_exists(uint256 id) internalviewvirtualreturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result :=iszero(iszero(shl(96, sload(add(id, add(id, keccak256(0x00, 0x20)))))))
}
}
/// @dev Returns the owner of token `id`./// Returns the zero address instead of reverting if the token does not exist.function_ownerOf(uint256 id) internalviewvirtualreturns (address result) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result :=shr(96, shl(96, sload(add(id, add(id, keccak256(0x00, 0x20))))))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* INTERNAL DATA HITCHHIKING FUNCTIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/// For performance, no events are emitted for the hitchhiking setters.// Please emit your own events if required./// @dev Returns the auxiliary data for `owner`./// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data./// Auxiliary data can be set for any address, even if it does not have any tokens.function_getAux(address owner) internalviewvirtualreturns (uint224 result) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
result :=shr(32, sload(keccak256(0x0c, 0x1c)))
}
}
/// @dev Set the auxiliary data for `owner` to `value`./// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data./// Auxiliary data can be set for any address, even if it does not have any tokens.function_setAux(address owner, uint224 value) internalvirtual{
/// @solidity memory-safe-assemblyassembly {
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
let balanceSlot :=keccak256(0x0c, 0x1c)
let packed :=sload(balanceSlot)
sstore(balanceSlot, xor(packed, shl(32, xor(value, shr(32, packed)))))
}
}
/// @dev Returns the extra data for token `id`./// Minting, transferring, burning a token will not change the extra data./// The extra data can be set on a non-existent token.function_getExtraData(uint256 id) internalviewvirtualreturns (uint96 result) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result :=shr(160, sload(add(id, add(id, keccak256(0x00, 0x20)))))
}
}
/// @dev Sets the extra data for token `id` to `value`./// Minting, transferring, burning a token will not change the extra data./// The extra data can be set on a non-existent token.function_setExtraData(uint256 id, uint96 value) internalvirtual{
/// @solidity memory-safe-assemblyassembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot :=add(id, add(id, keccak256(0x00, 0x20)))
let packed :=sload(ownershipSlot)
sstore(ownershipSlot, xor(packed, shl(160, xor(value, shr(160, packed)))))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* INTERNAL MINT FUNCTIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Mints token `id` to `to`.////// Requirements:////// - Token `id` must not exist./// - `to` cannot be the zero address.////// Emits a {Transfer} event.function_mint(address to, uint256 id) internalvirtual{
_beforeTokenTransfer(address(0), to, id);
/// @solidity memory-safe-assemblyassembly {
// Clear the upper 96 bits.
to :=shr(96, shl(96, to))
// Load the ownership data.mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot :=add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked :=sload(ownershipSlot)
// Revert if the token already exists.ifshl(96, ownershipPacked) {
mstore(0x00, 0xc991cbb1) // `TokenAlreadyExists()`.revert(0x1c, 0x04)
}
// Update with the owner.sstore(ownershipSlot, or(ownershipPacked, to))
// Increment the balance of the owner.
{
mstore(0x00, to)
let balanceSlot :=keccak256(0x0c, 0x1c)
let balanceSlotPacked :=add(sload(balanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.ifiszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(balanceSlot, balanceSlotPacked)
}
// Emit the {Transfer} event.log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}
/// @dev Mints token `id` to `to`, and updates the extra data for token `id` to `value`./// Does NOT check if token `id` already exists (assumes `id` is auto-incrementing).////// Requirements:////// - `to` cannot be the zero address.////// Emits a {Transfer} event.function_mintAndSetExtraDataUnchecked(address to, uint256 id, uint96 value) internalvirtual{
_beforeTokenTransfer(address(0), to, id);
/// @solidity memory-safe-assemblyassembly {
// Clear the upper 96 bits.
to :=shr(96, shl(96, to))
// Update with the owner and extra data.mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
sstore(add(id, add(id, keccak256(0x00, 0x20))), or(shl(160, value), to))
// Increment the balance of the owner.
{
mstore(0x00, to)
let balanceSlot :=keccak256(0x0c, 0x1c)
let balanceSlotPacked :=add(sload(balanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.ifiszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(balanceSlot, balanceSlotPacked)
}
// Emit the {Transfer} event.log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}
/// @dev Equivalent to `_safeMint(to, id, "")`.function_safeMint(address to, uint256 id) internalvirtual{
_safeMint(to, id, "");
}
/// @dev Mints token `id` to `to`.////// Requirements:////// - Token `id` must not exist./// - `to` cannot be the zero address./// - If `to` refers to a smart contract, it must implement/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.////// Emits a {Transfer} event.function_safeMint(address to, uint256 id, bytesmemory data) internalvirtual{
_mint(to, id);
if (_hasCode(to)) _checkOnERC721Received(address(0), to, id, data);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* INTERNAL BURN FUNCTIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Equivalent to `_burn(address(0), id)`.function_burn(uint256 id) internalvirtual{
_burn(address(0), id);
}
/// @dev Destroys token `id`, using `by`.////// Requirements:////// - Token `id` must exist./// - If `by` is not the zero address,/// it must be the owner of the token, or be approved to manage the token.////// Emits a {Transfer} event.function_burn(address by, uint256 id) internalvirtual{
address owner = ownerOf(id);
_beforeTokenTransfer(owner, address(0), id);
/// @solidity memory-safe-assemblyassembly {
// Clear the upper 96 bits.
by :=shr(96, shl(96, by))
// Load the ownership data.mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot :=add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked :=sload(ownershipSlot)
// Reload the owner in case it is changed in `_beforeTokenTransfer`.
owner :=shr(96, shl(96, ownershipPacked))
// Revert if the token does not exist.ifiszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.revert(0x1c, 0x04)
}
// Load and check the token approval.
{
mstore(0x00, owner)
let approvedAddress :=sload(add(1, ownershipSlot))
// If `by` is not the zero address, do the authorization check.// Revert if the `by` is not the owner, nor approved.ifiszero(or(iszero(by), or(eq(by, owner), eq(by, approvedAddress)))) {
ifiszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.revert(0x1c, 0x04)
}
}
// Delete the approved address if any.if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Clear the owner.sstore(ownershipSlot, xor(ownershipPacked, owner))
// Decrement the balance of `owner`.
{
let balanceSlot :=keccak256(0x0c, 0x1c)
sstore(balanceSlot, sub(sload(balanceSlot), 1))
}
// Emit the {Transfer} event.log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, owner, 0, id)
}
_afterTokenTransfer(owner, address(0), id);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* INTERNAL APPROVAL FUNCTIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns whether `account` is the owner of token `id`, or is approved to manage it.////// Requirements:/// - Token `id` must exist.function_isApprovedOrOwner(address account, uint256 id)
internalviewvirtualreturns (bool result)
{
/// @solidity memory-safe-assemblyassembly {
result :=1// Clear the upper 96 bits.
account :=shr(96, shl(96, account))
// Load the ownership data.mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, account))
let ownershipSlot :=add(id, add(id, keccak256(0x00, 0x20)))
let owner :=shr(96, shl(96, sload(ownershipSlot)))
// Revert if the token does not exist.ifiszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.revert(0x1c, 0x04)
}
// Check if `account` is the `owner`.ifiszero(eq(account, owner)) {
mstore(0x00, owner)
// Check if `account` is approved to manage the token.ifiszero(sload(keccak256(0x0c, 0x30))) {
result :=eq(account, sload(add(1, ownershipSlot)))
}
}
}
}
/// @dev Returns the account approved to manage token `id`./// Returns the zero address instead of reverting if the token does not exist.function_getApproved(uint256 id) internalviewvirtualreturns (address result) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result :=sload(add(1, add(id, add(id, keccak256(0x00, 0x20)))))
}
}
/// @dev Equivalent to `_approve(address(0), account, id)`.function_approve(address account, uint256 id) internalvirtual{
_approve(address(0), account, id);
}
/// @dev Sets `account` as the approved account to manage token `id`, using `by`.////// Requirements:/// - Token `id` must exist./// - If `by` is not the zero address, `by` must be the owner/// or an approved operator for the token owner.////// Emits a {Approval} event.function_approve(address by, address account, uint256 id) internalvirtual{
assembly {
// Clear the upper 96 bits.let bitmaskAddress :=shr(96, not(0))
account :=and(bitmaskAddress, account)
by :=and(bitmaskAddress, by)
// Load the owner of the token.mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot :=add(id, add(id, keccak256(0x00, 0x20)))
let owner :=and(bitmaskAddress, sload(ownershipSlot))
// Revert if the token does not exist.ifiszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.revert(0x1c, 0x04)
}
// If `by` is not the zero address, do the authorization check.// Revert if `by` is not the owner, nor approved.ifiszero(or(iszero(by), eq(by, owner))) {
mstore(0x00, owner)
ifiszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.revert(0x1c, 0x04)
}
}
// Sets `account` as the approved account to manage `id`.sstore(add(1, ownershipSlot), account)
// Emit the {Approval} event.log4(codesize(), 0x00, _APPROVAL_EVENT_SIGNATURE, owner, account, id)
}
}
/// @dev Approve or remove the `operator` as an operator for `by`,/// without authorization checks.////// Emits an {ApprovalForAll} event.function_setApprovalForAll(address by, address operator, bool isApproved) internalvirtual{
/// @solidity memory-safe-assemblyassembly {
// Clear the upper 96 bits.
by :=shr(96, shl(96, by))
operator :=shr(96, shl(96, operator))
// Convert to 0 or 1.
isApproved :=iszero(iszero(isApproved))
// Update the `isApproved` for (`by`, `operator`).mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
mstore(0x00, by)
sstore(keccak256(0x0c, 0x30), isApproved)
// Emit the {ApprovalForAll} event.mstore(0x00, isApproved)
log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, by, operator)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* INTERNAL TRANSFER FUNCTIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Equivalent to `_transfer(address(0), from, to, id)`.function_transfer(addressfrom, address to, uint256 id) internalvirtual{
_transfer(address(0), from, to, id);
}
/// @dev Transfers token `id` from `from` to `to`.////// Requirements:////// - Token `id` must exist./// - `from` must be the owner of the token./// - `to` cannot be the zero address./// - If `by` is not the zero address,/// it must be the owner of the token, or be approved to manage the token.////// Emits a {Transfer} event.function_transfer(address by, addressfrom, address to, uint256 id) internalvirtual{
_beforeTokenTransfer(from, to, id);
/// @solidity memory-safe-assemblyassembly {
// Clear the upper 96 bits.let bitmaskAddress :=shr(96, not(0))
from :=and(bitmaskAddress, from)
to :=and(bitmaskAddress, to)
by :=and(bitmaskAddress, by)
// Load the ownership data.mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot :=add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked :=sload(ownershipSlot)
let owner :=and(bitmaskAddress, ownershipPacked)
// Revert if the token does not exist, or if `from` is not the owner.ifiszero(mul(owner, eq(owner, from))) {
// `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
revert(0x1c, 0x04)
}
// Load, check, and update the token approval.
{
mstore(0x00, from)
let approvedAddress :=sload(add(1, ownershipSlot))
// If `by` is not the zero address, do the authorization check.// Revert if the `by` is not the owner, nor approved.ifiszero(or(iszero(by), or(eq(by, from), eq(by, approvedAddress)))) {
ifiszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.revert(0x1c, 0x04)
}
}
// Delete the approved address if any.if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Update with the new owner.sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
// Decrement the balance of `from`.
{
let fromBalanceSlot :=keccak256(0x0c, 0x1c)
sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
}
// Increment the balance of `to`.
{
mstore(0x00, to)
let toBalanceSlot :=keccak256(0x0c, 0x1c)
let toBalanceSlotPacked :=add(sload(toBalanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.ifiszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(toBalanceSlot, toBalanceSlotPacked)
}
// Emit the {Transfer} event.log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
_afterTokenTransfer(from, to, id);
}
/// @dev Equivalent to `_safeTransfer(from, to, id, "")`.function_safeTransfer(addressfrom, address to, uint256 id) internalvirtual{
_safeTransfer(from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.////// Requirements:////// - Token `id` must exist./// - `from` must be the owner of the token./// - `to` cannot be the zero address./// - The caller must be the owner of the token, or be approved to manage the token./// - If `to` refers to a smart contract, it must implement/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.////// Emits a {Transfer} event.function_safeTransfer(addressfrom, address to, uint256 id, bytesmemory data)
internalvirtual{
_transfer(address(0), from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/// @dev Equivalent to `_safeTransfer(by, from, to, id, "")`.function_safeTransfer(address by, addressfrom, address to, uint256 id) internalvirtual{
_safeTransfer(by, from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.////// Requirements:////// - Token `id` must exist./// - `from` must be the owner of the token./// - `to` cannot be the zero address./// - If `by` is not the zero address,/// it must be the owner of the token, or be approved to manage the token./// - If `to` refers to a smart contract, it must implement/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.////// Emits a {Transfer} event.function_safeTransfer(address by, addressfrom, address to, uint256 id, bytesmemory data)
internalvirtual{
_transfer(by, from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* HOOKS FOR OVERRIDING *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Hook that is called before any token transfers, including minting and burning.function_beforeTokenTransfer(addressfrom, address to, uint256 id) internalvirtual{}
/// @dev Hook that is called after any token transfers, including minting and burning.function_afterTokenTransfer(addressfrom, address to, uint256 id) internalvirtual{}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* PRIVATE HELPERS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns if `a` has bytecode of non-zero length.function_hasCode(address a) privateviewreturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
result :=extcodesize(a) // Can handle dirty upper bits.
}
}
/// @dev Perform a call to invoke {IERC721Receiver-onERC721Received} on `to`./// Reverts if the target does not support the function correctly.function_checkOnERC721Received(addressfrom, address to, uint256 id, bytesmemory data)
private{
/// @solidity memory-safe-assemblyassembly {
// Prepare the calldata.let m :=mload(0x40)
let onERC721ReceivedSelector :=0x150b7a02mstore(m, onERC721ReceivedSelector)
mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`.mstore(add(m, 0x40), shr(96, shl(96, from)))
mstore(add(m, 0x60), id)
mstore(add(m, 0x80), 0x80)
let n :=mload(data)
mstore(add(m, 0xa0), n)
if n { pop(staticcall(gas(), 4, add(data, 0x20), n, add(m, 0xc0), n)) }
// Revert if the call reverts.ifiszero(call(gas(), to, 0, add(m, 0x1c), add(n, 0xa4), m, 0x20)) {
ifreturndatasize() {
// Bubble up the revert if the call reverts.returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
}
// Load the returndata and compare it.ifiszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) {
mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`.revert(0x1c, 0x04)
}
}
}
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.pragmasolidity ^0.8.20;/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/libraryEnumerableSet{
// To implement this library for multiple types with as little code// repetition as possible, we write it in terms of a generic Set type with// bytes32 values.// The Set implementation uses private functions, and user-facing// implementations (such as AddressSet) are just wrappers around the// underlying Set.// This means that we can only create new EnumerableSets for types that fit// in bytes32.structSet {
// Storage of set valuesbytes32[] _values;
// Position is the index of the value in the `values` array plus 1.// Position 0 is used to mean a value is not in the set.mapping(bytes32 value =>uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/function_add(Set storage set, bytes32 value) privatereturns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes// and use 0 as a sentinel value
set._positions[value] = set._values.length;
returntrue;
} else {
returnfalse;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/function_remove(Set storage set, bytes32 value) privatereturns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slotuint256 position = set._positions[value];
if (position !=0) {
// Equivalent to contains(set, value)// To delete an element from the _values array in O(1), we swap the element to delete with the last one in// the array, and then remove the last element (sometimes called as 'swap and pop').// This modifies the order of the array, as noted in {at}.uint256 valueIndex = position -1;
uint256 lastIndex = set._values.length-1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slotdelete set._positions[value];
returntrue;
} else {
returnfalse;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/function_contains(Set storage set, bytes32 value) privateviewreturns (bool) {
return set._positions[value] !=0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/function_length(Set storage set) privateviewreturns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/function_at(Set storage set, uint256 index) privateviewreturns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/function_values(Set storage set) privateviewreturns (bytes32[] memory) {
return set._values;
}
// Bytes32SetstructBytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/functionadd(Bytes32Set storage set, bytes32 value) internalreturns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/functionremove(Bytes32Set storage set, bytes32 value) internalreturns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/functioncontains(Bytes32Set storage set, bytes32 value) internalviewreturns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/functionlength(Bytes32Set storage set) internalviewreturns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/functionat(Bytes32Set storage set, uint256 index) internalviewreturns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/functionvalues(Bytes32Set storage set) internalviewreturns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assemblyassembly {
result := store
}
return result;
}
// AddressSetstructAddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/functionadd(AddressSet storage set, address value) internalreturns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/functionremove(AddressSet storage set, address value) internalreturns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/functioncontains(AddressSet storage set, address value) internalviewreturns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/functionlength(AddressSet storage set) internalviewreturns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/functionat(AddressSet storage set, uint256 index) internalviewreturns (address) {
returnaddress(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/functionvalues(AddressSet storage set) internalviewreturns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assemblyassembly {
result := store
}
return result;
}
// UintSetstructUintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/functionadd(UintSet storage set, uint256 value) internalreturns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/functionremove(UintSet storage set, uint256 value) internalreturns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/functioncontains(UintSet storage set, uint256 value) internalviewreturns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/functionlength(UintSet storage set) internalviewreturns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/functionat(UintSet storage set, uint256 index) internalviewreturns (uint256) {
returnuint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/functionvalues(UintSet storage set) internalviewreturns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assemblyassembly {
result := store
}
return result;
}
}
Contract Source Code
File 10 of 24: IERC5192.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;interfaceIERC5192{
/// @notice Emitted when the locking status is changed to locked./// @dev If a token is minted and the status is locked, this event should be emitted./// @param tokenId The identifier for a token.eventLocked(uint256 tokenId);
/// @notice Emitted when the locking status is changed to unlocked./// @dev If a token is minted and the status is unlocked, this event should be emitted./// @param tokenId The identifier for a token.eventUnlocked(uint256 tokenId);
/// @notice Returns the locking status of an Soulbound Token/// @dev SBTs assigned to zero address are considered invalid, and queries/// about them do throw./// @param tokenId The identifier for an SBT.functionlocked(uint256 tokenId) externalviewreturns (bool);
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;interfaceIPreapprovalForAll{
/// @notice Emitted when a token contract preapproves (or revokes) all token transfers from a specific address, if/// the preapproval is configurable. This allows offchain indexers to correctly reflect token approvals/// which can later be revoked.eventPreapprovalForAll(addressindexed operator, boolindexed approved);
}
Contract Source Code
File 13 of 24: LibString.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @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.libraryLibString{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CUSTOM ERRORS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The length of the output is too small to contain all the hex digits.errorHexLengthInsufficient();
/// @dev The length of the string is more than 32 bytes.errorTooBigForSmallString();
/// @dev The input string must be a 7-bit ASCII.errorStringNot7BitASCII();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CONSTANTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The constant returned when the `search` is not found in the string.uint256internalconstant NOT_FOUND =type(uint256).max;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.uint128internalconstant ALPHANUMERIC_7_BIT_ASCII =0x7fffffe07fffffe03ff000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.uint128internalconstant LETTERS_7_BIT_ASCII =0x7fffffe07fffffe0000000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyz'.uint128internalconstant LOWERCASE_7_BIT_ASCII =0x7fffffe000000000000000000000000;
/// @dev Lookup for 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.uint128internalconstant UPPERCASE_7_BIT_ASCII =0x7fffffe0000000000000000;
/// @dev Lookup for '0123456789'.uint128internalconstant DIGITS_7_BIT_ASCII =0x3ff000000000000;
/// @dev Lookup for '0123456789abcdefABCDEF'.uint128internalconstant HEXDIGITS_7_BIT_ASCII =0x7e0000007e03ff000000000000;
/// @dev Lookup for '01234567'.uint128internalconstant OCTDIGITS_7_BIT_ASCII =0xff000000000000;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'.uint128internalconstant PRINTABLE_7_BIT_ASCII =0x7fffffffffffffffffffffff00003e00;
/// @dev Lookup for '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.uint128internalconstant PUNCTUATION_7_BIT_ASCII =0x78000001f8000001fc00fffe00000000;
/// @dev Lookup for ' \t\n\r\x0b\x0c'.uint128internalconstant WHITESPACE_7_BIT_ASCII =0x100003e00;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* DECIMAL OPERATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns the base 10 decimal representation of `value`.functiontoString(uint256 value) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
// 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.ifiszero(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`.functiontoString(int256 value) internalpurereturns (stringmemory result) {
if (value >=0) return toString(uint256(value));
unchecked {
result = toString(~uint256(value) +1);
}
/// @solidity memory-safe-assemblyassembly {
// 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 `length` bytes./// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,/// giving a total length of `length * 2 + 2` bytes./// Reverts if `length` is too small for the output to contain all the digits.functiontoHexString(uint256 value, uint256 length)
internalpurereturns (stringmemory result)
{
result = toHexStringNoPrefix(value, length);
/// @solidity memory-safe-assemblyassembly {
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 `length` bytes./// The output is not prefixed with "0x" and is encoded using 2 hexadecimal digits per byte,/// giving a total length of `length * 2` bytes./// Reverts if `length` is too small for the output to contain all the digits.functiontoHexStringNoPrefix(uint256 value, uint256 length)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
// We need 0x20 bytes for the trailing zeros padding, `length * 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, length), 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(length, length))
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)
ifiszero(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.functiontoHexString(uint256 value) internalpurereturns (stringmemory result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
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"`.functiontoMinimalHexString(uint256 value) internalpurereturns (stringmemory result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
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"`.functiontoMinimalHexStringNoPrefix(uint256 value)
internalpurereturns (stringmemory result)
{
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
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.functiontoHexStringNoPrefix(uint256 value) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
// 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)
ifiszero(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-55functiontoHexStringChecksummed(address value) internalpurereturns (stringmemory result) {
result = toHexString(value);
/// @solidity memory-safe-assemblyassembly {
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)
ifeq(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.functiontoHexString(address value) internalpurereturns (stringmemory result) {
result = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
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.functiontoHexStringNoPrefix(address value) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
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)
ifeq(i, 20) { break }
}
}
}
/// @dev Returns the hex encoded string from the raw bytes./// The output is encoded using 2 hexadecimal digits per byte.functiontoHexString(bytesmemory raw) internalpurereturns (stringmemory result) {
result = toHexStringNoPrefix(raw);
/// @solidity memory-safe-assemblyassembly {
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.functiontoHexStringNoPrefix(bytesmemory raw) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
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.functionruneCount(stringmemory s) internalpurereturns (uint256 result) {
/// @solidity memory-safe-assemblyassembly {
ifmload(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)))))
ifiszero(lt(o, end)) { break }
}
}
}
}
/// @dev Returns if this string is a 7-bit ASCII string./// (i.e. all characters codes are in [0..127])functionis7BitASCII(stringmemory s) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
result :=1let 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 {} {
ifand(mask, mload(o)) {
result :=0break
}
o :=add(o, 0x20)
ifiszero(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`.functionis7BitASCII(stringmemory s, uint128 allowed) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
result :=1ifmload(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)
ifiszero(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.functionto7BitASCIIAllowedLookup(stringmemory s) internalpurereturns (uint128 result) {
/// @solidity memory-safe-assemblyassembly {
ifmload(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)
ifiszero(lt(o, end)) { break }
}
ifshr(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`.functionreplace(stringmemory subject, stringmemory needle, stringmemory replacement)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
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.let end :=add(i, mload(subject))
ifiszero(gt(needleLen, mload(subject))) {
let subjectSearchEnd :=add(sub(end, needleLen), 1)
let h :=0// The hash of `needle`.ifiszero(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.ifiszero(shr(m, xor(t, s))) {
if h {
ifiszero(eq(keccak256(i, needleLen), h)) {
mstore(add(i, d), t)
i :=add(i, 1)
ifiszero(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)
ifiszero(lt(j, replacementLen)) { break }
}
d :=sub(add(d, replacementLen), needleLen)
if needleLen {
i :=add(i, needleLen)
ifiszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
mstore(add(i, d), t)
i :=add(i, 1)
ifiszero(lt(i, subjectSearchEnd)) { break }
}
}
let n :=add(sub(d, add(result, 0x20)), end)
// Copy the rest of the string 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 string.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.functionindexOf(stringmemory subject, stringmemory needle, uint256from)
internalpurereturns (uint256 result)
{
/// @solidity memory-safe-assemblyassembly {
result :=not(0) // Initialize to `NOT_FOUND`.for { let subjectLen :=mload(subject) } 1 {} {
ifiszero(mload(needle)) {
result := from
ifiszero(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))
ifiszero(and(lt(subject, end), lt(from, subjectLen))) { break }
ifiszero(lt(needleLen, 0x20)) {
for { let h :=keccak256(add(needle, 0x20), needleLen) } 1 {} {
ifiszero(shr(m, xor(mload(subject), s))) {
ifeq(keccak256(subject, needleLen), h) {
result :=sub(subject, subjectStart)
break
}
}
subject :=add(subject, 1)
ifiszero(lt(subject, end)) { break }
}
break
}
for {} 1 {} {
ifiszero(shr(m, xor(mload(subject), s))) {
result :=sub(subject, subjectStart)
break
}
subject :=add(subject, 1)
ifiszero(lt(subject, end)) { break }
}
break
}
}
}
/// @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.functionindexOf(stringmemory subject, stringmemory needle)
internalpurereturns (uint256 result)
{
result = 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.functionlastIndexOf(stringmemory subject, stringmemory needle, uint256from)
internalpurereturns (uint256 result)
{
/// @solidity memory-safe-assemblyassembly {
for {} 1 {} {
result :=not(0) // Initialize to `NOT_FOUND`.let needleLen :=mload(needle)
ifgt(needleLen, mload(subject)) { break }
let w := result
let fromMax :=sub(mload(subject), needleLen)
ifiszero(gt(fromMax, from)) { from := fromMax }
let end :=add(add(subject, 0x20), w)
subject :=add(add(subject, 0x20), from)
ifiszero(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 {} {
ifeq(keccak256(subject, needleLen), h) {
result :=sub(subject, add(end, 1))
break
}
subject :=add(subject, w) // `sub(subject, 1)`.ifiszero(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.functionlastIndexOf(stringmemory subject, stringmemory needle)
internalpurereturns (uint256 result)
{
result = lastIndexOf(subject, needle, type(uint256).max);
}
/// @dev Returns true if `needle` is found in `subject`, false otherwise.functioncontains(stringmemory subject, stringmemory needle) internalpurereturns (bool) {
return indexOf(subject, needle) != NOT_FOUND;
}
/// @dev Returns whether `subject` starts with `needle`.functionstartsWith(stringmemory subject, stringmemory needle)
internalpurereturns (bool result)
{
/// @solidity memory-safe-assemblyassembly {
let needleLen :=mload(needle)
// Just using keccak256 directly is actually cheaper.// forgefmt: disable-next-item
result :=and(
iszero(gt(needleLen, mload(subject))),
eq(
keccak256(add(subject, 0x20), needleLen),
keccak256(add(needle, 0x20), needleLen)
)
)
}
}
/// @dev Returns whether `subject` ends with `needle`.functionendsWith(stringmemory subject, stringmemory needle)
internalpurereturns (bool result)
{
/// @solidity memory-safe-assemblyassembly {
let needleLen :=mload(needle)
// Whether `needle` is not longer than `subject`.let inRange :=iszero(gt(needleLen, mload(subject)))
// Just using keccak256 directly is actually cheaper.// forgefmt: disable-next-item
result :=and(
eq(
keccak256(
// `subject + 0x20 + max(subjectLen - needleLen, 0)`.add(add(subject, 0x20), mul(inRange, sub(mload(subject), needleLen))),
needleLen
),
keccak256(add(needle, 0x20), needleLen)
),
inRange
)
}
}
/// @dev Returns `subject` repeated `times`.functionrepeat(stringmemory subject, uint256 times)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
let subjectLen :=mload(subject)
ifiszero(or(iszero(times), iszero(subjectLen))) {
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)
ifiszero(lt(j, subjectLen)) { break }
}
o :=add(o, subjectLen)
times :=sub(times, 1)
ifiszero(times) { break }
}
mstore(o, 0) // Zeroize the slot after the string.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.functionslice(stringmemory subject, uint256 start, uint256 end)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
let subjectLen :=mload(subject)
ifiszero(gt(subjectLen, end)) { end := subjectLen }
ifiszero(gt(subjectLen, start)) { start := subjectLen }
iflt(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)`.ifiszero(j) { break }
}
let o :=add(add(result, 0x20), n)
mstore(o, 0) // Zeroize the slot after the string.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 string./// `start` is a byte offset.functionslice(stringmemory subject, uint256 start)
internalpurereturns (stringmemory result)
{
result = slice(subject, start, type(uint256).max);
}
/// @dev Returns all the indices of `needle` in `subject`./// The indices are byte offsets.functionindicesOf(stringmemory subject, stringmemory needle)
internalpurereturns (uint256[] memory result)
{
/// @solidity memory-safe-assemblyassembly {
let searchLen :=mload(needle)
ifiszero(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`.ifiszero(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.ifiszero(shr(m, xor(t, s))) {
if h {
ifiszero(eq(keccak256(i, searchLen), h)) {
i :=add(i, 1)
ifiszero(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 {
ifiszero(lt(i, subjectSearchEnd)) { break }
continue
}
}
i :=add(i, 1)
ifiszero(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 a arrays of strings based on the `delimiter` inside of the `subject` string.functionsplit(stringmemory subject, stringmemory delimiter)
internalpurereturns (string[] memory result)
{
uint256[] memory indices = indicesOf(subject, delimiter);
/// @solidity memory-safe-assemblyassembly {
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)
ifiszero(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)`.ifiszero(o) { break }
}
mstore(add(add(element, 0x20), l), 0) // Zeroize the slot after the string.// 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)
ifiszero(lt(indexPtr, indicesEnd)) { break }
}
result := indices
ifiszero(mload(delimiter)) {
result :=add(indices, 0x20)
mstore(result, sub(mload(indices), 2))
}
}
}
/// @dev Returns a concatenated string of `a` and `b`./// Cheaper than `string.concat()` and does not de-align the free memory pointer.functionconcat(stringmemory a, stringmemory b)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
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)`.ifiszero(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)`.ifiszero(o) { break }
}
let totalLen :=add(aLen, bLen)
let last :=add(add(result, 0x20), totalLen)
mstore(last, 0) // Zeroize the slot after the string.mstore(result, totalLen) // Store the length.mstore(0x40, add(last, 0x20)) // Allocate memory.
}
}
/// @dev Returns a copy of the string in either lowercase or UPPERCASE./// WARNING! This function is only compatible with 7-bit ASCII strings.functiontoCase(stringmemory subject, bool toUpper)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
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)
ifeq(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.functionfromSmallString(bytes32 s) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
result :=mload(0x40)
let n :=0for {} 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.functionnormalizeSmallString(bytes32 s) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
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.functiontoSmallString(stringmemory s) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
result :=mload(s)
ifiszero(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.functionlower(stringmemory subject) internalpurereturns (stringmemory result) {
result = toCase(subject, false);
}
/// @dev Returns an UPPERCASED copy of the string./// WARNING! This function is only compatible with 7-bit ASCII strings.functionupper(stringmemory subject) internalpurereturns (stringmemory result) {
result = toCase(subject, true);
}
/// @dev Escapes the string to be used within HTML tags.functionescapeHTML(stringmemory s) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
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 `["\"","'","&","<",">"]`.ifiszero(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.functionescapeJSON(stringmemory s, bool addDoubleQuotes)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
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)
ifiszero(lt(c, 0x20)) {
ifiszero(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
}
ifiszero(and(shl(c, 1), 0x3700)) {
// Not in `["\b","\t","\n","\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.functionescapeJSON(stringmemory s) internalpurereturns (stringmemory 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.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent/// See: https://datatracker.ietf.org/doc/html/rfc2396/// See: https://datatracker.ietf.org/doc/html/rfc3986functionencodeURIComponent(stringmemory s) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
result :=mload(0x40)
// Store "0123456789ABCDEF" in scratch space.// Uppercased to be consistent with JavaScript's implementation.mstore(0x0f, 0x30313233343536373839414243444546)
let o :=add(result, 0x20)
for { let end :=add(s, mload(s)) } iszero(eq(s, end)) {} {
s :=add(s, 1)
let c :=and(mload(s), 0xff)
// If not in `[0-9A-Z-a-z-_.!~*'()]`.ifiszero(and(1, shr(c, 0x47fffffe87fffffe03ff678200000000))) {
mstore8(o, 0x25) // '%'.mstore8(add(o, 1), mload(and(shr(4, c), 15)))
mstore8(add(o, 2), mload(and(c, 15)))
o :=add(o, 3)
continue
}
mstore8(o, c)
o :=add(o, 1)
}
mstore(result, sub(o, add(result, 0x20))) // Store the length.mstore(o, 0) // Zeroize the slot after the string.mstore(0x40, add(o, 0x20)) // Allocate memory.
}
}
/// @dev Returns whether `a` equals `b`.functioneq(stringmemory a, stringmemory b) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
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 string.functioneqs(stringmemory a, bytes32 b) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
// 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 Packs a single string with its length into a single word./// Returns `bytes32(0)` if the length is zero or greater than 31.functionpackOne(stringmemory a) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
// We don't need to zero right pad the string,// since this is our own custom non-standard packing scheme.
result :=mul(
// Load the length and the bytes.mload(add(a, 0x1f)),
// `length != 0 && length < 32`. Abuses underflow.// Assumes that the length is valid and within the block gas limit.lt(sub(mload(a), 1), 0x1f)
)
}
}
/// @dev Unpacks a string packed using {packOne}./// Returns the empty string if `packed` is `bytes32(0)`./// If `packed` is not an output of {packOne}, the output behavior is undefined.functionunpackOne(bytes32 packed) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
result :=mload(0x40) // Grab the free memory pointer.mstore(0x40, add(result, 0x40)) // Allocate 2 words (1 for the length, 1 for the bytes).mstore(result, 0) // Zeroize the length slot.mstore(add(result, 0x1f), packed) // Store the length and bytes.mstore(add(add(result, 0x20), mload(result)), 0) // Right pad with zeroes.
}
}
/// @dev Packs two strings with their lengths into a single word./// Returns `bytes32(0)` if combined length is zero or greater than 30.functionpackTwo(stringmemory a, stringmemory b) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
let aLen :=mload(a)
// We don't need to zero right pad the strings,// since this is our own custom non-standard packing scheme.
result :=mul(
or( // Load the length and the bytes of `a` and `b`.shl(shl(3, sub(0x1f, aLen)), mload(add(a, aLen))), mload(sub(add(b, 0x1e), aLen))),
// `totalLen != 0 && totalLen < 31`. Abuses underflow.// Assumes that the lengths are valid and within the block gas limit.lt(sub(add(aLen, mload(b)), 1), 0x1e)
)
}
}
/// @dev Unpacks strings packed using {packTwo}./// Returns the empty strings if `packed` is `bytes32(0)`./// If `packed` is not an output of {packTwo}, the output behavior is undefined.functionunpackTwo(bytes32 packed)
internalpurereturns (stringmemory resultA, stringmemory resultB)
{
/// @solidity memory-safe-assemblyassembly {
resultA :=mload(0x40) // Grab the free memory pointer.
resultB :=add(resultA, 0x40)
// Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words.mstore(0x40, add(resultB, 0x40))
// Zeroize the length slots.mstore(resultA, 0)
mstore(resultB, 0)
// Store the lengths and bytes.mstore(add(resultA, 0x1f), packed)
mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA))))
// Right pad with zeroes.mstore(add(add(resultA, 0x20), mload(resultA)), 0)
mstore(add(add(resultB, 0x20), mload(resultB)), 0)
}
}
/// @dev Directly returns `a` without copying.functiondirectReturn(stringmemory a) internalpure{
assembly {
// Assumes that the string 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 string 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 string.return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize)))
}
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;import {DynamicTraits} from"./DynamicTraits.sol";
import {Metadata} from"../onchain/Metadata.sol";
import {Ownable} from"solady/auth/Ownable.sol";
import {SSTORE2} from"solady/utils/SSTORE2.sol";
import {EnumerableSet} from"openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol";
import {
TraitLabelStorage,
TraitLabelStorageLib,
TraitLabel,
TraitLabelLib,
Editors,
StoredTraitLabel,
AllowedEditor,
EditorsLib,
StoredTraitLabelLib
} from"./lib/TraitLabelLib.sol";
libraryOnchainTraitsStorage{
structLayout {
/// @notice An enumerable set of all trait keys that have been set.
EnumerableSet.Bytes32Set _traitKeys;
/// @notice A mapping of traitKey to OnchainTraitsStorage.layout()._traitLabelStorage metadata.mapping(bytes32 traitKey => TraitLabelStorage traitLabelStorage) _traitLabelStorage;
/// @notice An enumerable set of all accounts allowed to edit traits with a "Custom" editor privilege.
EnumerableSet.AddressSet _customEditors;
}
bytes32internalconstant STORAGE_SLOT =keccak256("contracts.storage.erc7496-dynamictraits.onchaintraits");
functionlayout() internalpurereturns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot:= slot
}
}
}
abstractcontractOnchainTraitsisOwnable, DynamicTraits{
usingOnchainTraitsStorageforOnchainTraitsStorage.Layout;
usingEnumerableSetforEnumerableSet.Bytes32Set;
usingEnumerableSetforEnumerableSet.AddressSet;
/// @notice Thrown when the caller does not have the privilege to set a traiterrorInsufficientPrivilege();
/// @notice Thrown when trying to set a trait that does not existerrorTraitDoesNotExist(bytes32 traitKey);
constructor() {
_initializeOwner(msg.sender);
}
// ABSTRACT/// @notice Helper to determine if a given address has the AllowedEditor.TokenOwner privilege.function_isOwnerOrApproved(uint256 tokenId, address addr) internalviewvirtualreturns (bool);
// CUSTOM EDITORS/**
* @notice Check if an address is a custom editor
* @param editor The address to check
*/functionisCustomEditor(address editor) externalviewreturns (bool) {
return OnchainTraitsStorage.layout()._customEditors.contains(editor);
}
/**
* @notice Add or remove an address as a custom editor
* @param editor The address to add or remove
* @param insert Whether to add or remove the address
*/functionupdateCustomEditor(address editor, bool insert) externalonlyOwner{
if (insert) {
OnchainTraitsStorage.layout()._customEditors.add(editor);
} else {
OnchainTraitsStorage.layout()._customEditors.remove(editor);
}
}
/**
* @notice Get the list of custom editors. This may revert if there are too many editors.
*/functiongetCustomEditors() externalviewreturns (address[] memory) {
return OnchainTraitsStorage.layout()._customEditors.values();
}
/**
* @notice Get the number of custom editors
*/functiongetCustomEditorsLength() externalviewreturns (uint256) {
return OnchainTraitsStorage.layout()._customEditors.length();
}
/**
* @notice Get the custom editor at a given index
* @param index The index of the custom editor to get
*/functiongetCustomEditorAt(uint256 index) externalviewreturns (address) {
return OnchainTraitsStorage.layout()._customEditors.at(index);
}
// LABELS URI/**
* @notice Get the onchain URI for the trait metadata, encoded as a JSON data URI
*/functiongetTraitMetadataURI() externalviewvirtualoverridereturns (stringmemory) {
return Metadata.jsonDataURI(_getTraitMetadataJson());
}
/**
* @notice Get the raw JSON for the trait metadata
*/function_getTraitMetadataJson() internalviewreturns (stringmemory) {
bytes32[] memory keys = OnchainTraitsStorage.layout()._traitKeys.values();
return TraitLabelStorageLib.toLabelJson(OnchainTraitsStorage.layout()._traitLabelStorage, keys);
}
/**
* @notice Return trait label storage information at a given key.
*/functiontraitLabelStorage(bytes32 traitKey) externalviewreturns (TraitLabelStorage memory) {
return OnchainTraitsStorage.layout()._traitLabelStorage[traitKey];
}
/**
* @notice Set a trait for a given traitKey and tokenId. Checks that the caller has permission to set the trait,
* and, if the TraitLabel specifies that the trait value must be validated, checks that the trait value
* is valid.
* @param tokenId The token ID to get the trait value for
* @param traitKey The trait key to get the value of
* @param newValue The new trait value
*/functionsetTrait(uint256 tokenId, bytes32 traitKey, bytes32 newValue) publicvirtualoverride{
TraitLabelStorage memory labelStorage = OnchainTraitsStorage.layout()._traitLabelStorage[traitKey];
StoredTraitLabel storedTraitLabel = labelStorage.storedLabel;
if (!StoredTraitLabelLib.exists(storedTraitLabel)) {
revert TraitDoesNotExist(traitKey);
}
_verifySetterPrivilege(labelStorage.allowedEditors, tokenId);
if (labelStorage.valuesRequireValidation) {
TraitLabelLib.validateAcceptableValue(StoredTraitLabelLib.load(storedTraitLabel), traitKey, newValue);
}
DynamicTraits.setTrait(tokenId, traitKey, newValue);
}
/**
* @notice Set the TraitLabel for a given traitKey. This will overwrite any existing TraitLabel for the traitKey.
* Traits may not be set without a corresponding TraitLabel. OnlyOwner.
* @param traitKey The trait key to set the value of
* @param _traitLabel The trait label to set
*/functionsetTraitLabel(bytes32 traitKey, TraitLabel calldata _traitLabel) externalvirtualonlyOwner{
_setTraitLabel(traitKey, _traitLabel);
}
/**
* @notice Set the OnchainTraitsStorage.layout()._traitLabelStorage for a traitKey. Packs SSTORE2 value along with allowedEditors, required?, and
* valuesRequireValidation? into a single storage slot for more efficient validation when setting trait values.
*/function_setTraitLabel(bytes32 traitKey, TraitLabel memory _traitLabel) internalvirtual{
OnchainTraitsStorage.layout()._traitKeys.add(traitKey);
OnchainTraitsStorage.layout()._traitLabelStorage[traitKey] = TraitLabelStorage({
allowedEditors: _traitLabel.editors,
required: _traitLabel.required,
valuesRequireValidation: _traitLabel.acceptableValues.length>0,
storedLabel: TraitLabelLib.store(_traitLabel)
});
}
/**
* @notice Checks that the caller has permission to set a trait for a given allowed Editors set and token ID.
* Reverts with InsufficientPrivilege if the caller does not have permission.
* @param editors The allowed editors for this trait
* @param tokenId The token ID the trait is being set for
*/function_verifySetterPrivilege(Editors editors, uint256 tokenId) internalview{
// anyoneif (EditorsLib.contains(editors, AllowedEditor.Anyone)) {
// short circuitreturn;
}
// tokenOwnerif (EditorsLib.contains(editors, AllowedEditor.TokenOwner)) {
if (_isOwnerOrApproved(tokenId, msg.sender)) {
// short circuitreturn;
}
}
// customEditorif (EditorsLib.contains(editors, AllowedEditor.Custom)) {
if (OnchainTraitsStorage.layout()._customEditors.contains(msg.sender)) {
// short circuitreturn;
}
}
// contractOwnerif (EditorsLib.contains(editors, AllowedEditor.ContractOwner)) {
if (owner() ==msg.sender) {
// short circuitreturn;
}
}
if (EditorsLib.contains(editors, AllowedEditor.Self)) {
if (address(this) ==msg.sender) {
// short circuitreturn;
}
}
revert InsufficientPrivilege();
}
/**
* @notice Gets the individual JSON objects for each dynamic trait set on this token by iterating over all
* possible traitKeys and checking if the trait is set on the token. This is extremely inefficient
* and should only be called offchain when rendering metadata.
* @param tokenId The token ID to get the dynamic trait attributes for
* @return An array of JSON objects, each representing a dynamic trait set on the token
*/function_dynamicAttributes(uint256 tokenId) internalviewvirtualreturns (string[] memory) {
bytes32[] memory keys = OnchainTraitsStorage.layout()._traitKeys.values();
uint256 keysLength = keys.length;
string[] memory attributes =newstring[](keysLength);
// keep track of how many traits are actually setuint256 num;
for (uint256 i =0; i < keysLength;) {
bytes32 key = keys[i];
bytes32 trait = getTraitValue(tokenId, key);
// check that the trait is set, otherwise, skip itif (trait !=bytes32(0)) {
attributes[num] =
TraitLabelStorageLib.toAttributeJson(OnchainTraitsStorage.layout()._traitLabelStorage, key, trait);
unchecked {
++num;
}
}
unchecked {
++i;
}
}
///@solidity memory-safe-assemblyassembly {
// update attributes with actual lengthmstore(attributes, num)
}
return attributes;
}
}
Contract Source Code
File 16 of 24: Ownable.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @notice Simple single owner authorization mixin./// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)////// @dev Note:/// This implementation does NOT auto-initialize the owner to `msg.sender`./// You MUST call the `_initializeOwner` in the constructor / initializer.////// While the ownable portion follows/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,/// the nomenclature for the 2-step ownership handover may be unique to this codebase.abstractcontractOwnable{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CUSTOM ERRORS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The caller is not authorized to call the function.errorUnauthorized();
/// @dev The `newOwner` cannot be the zero address.errorNewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.errorNoHandoverRequest();
/// @dev Cannot double-initialize.errorAlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* EVENTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The ownership is transferred from `oldOwner` to `newOwner`./// This event is intentionally kept the same as OpenZeppelin's Ownable to be/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),/// despite it not being as lightweight as a single argument event.eventOwnershipTransferred(addressindexed oldOwner, addressindexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.eventOwnershipHandoverRequested(addressindexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.eventOwnershipHandoverCanceled(addressindexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.uint256privateconstant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.uint256privateconstant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.uint256privateconstant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* STORAGE *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The owner slot is given by:/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`./// It is intentionally chosen to be a high value/// to avoid collision with lower slots./// The choice of manual storage layout is to enable compatibility/// with both regular and upgradeable contracts.bytes32internalconstant _OWNER_SLOT =0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:/// ```/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))/// let handoverSlot := keccak256(0x00, 0x20)/// ```/// It stores the expiry timestamp of the two-step ownership handover.uint256privateconstant _HANDOVER_SLOT_SEED =0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* INTERNAL FUNCTIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Override to return true to make `_initializeOwner` prevent double-initialization.function_guardInitializeOwner() internalpurevirtualreturns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard./// This function must be called upon initialization,/// regardless of whether the contract is upgradeable or not./// This is to enable generalization to both regular and upgradeable contracts,/// and to save gas in case the initial owner is not the caller./// For performance reasons, this function will not check if there/// is an existing owner.function_initializeOwner(address newOwner) internalvirtual{
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assemblyassembly {
let ownerSlot := _OWNER_SLOT
ifsload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner :=shr(96, shl(96, newOwner))
// Store the new value.sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assemblyassembly {
// Clean the upper 96 bits.
newOwner :=shr(96, shl(96, newOwner))
// Store the new value.sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.function_setOwner(address newOwner) internalvirtual{
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assemblyassembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner :=shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assemblyassembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner :=shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.function_checkOwner() internalviewvirtual{
/// @solidity memory-safe-assemblyassembly {
// If the caller is not the stored owner, revert.ifiszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds./// Override to return a different value if needed./// Made internal to conserve bytecode. Wrap it in a public function if needed.function_ownershipHandoverValidFor() internalviewvirtualreturns (uint64) {
return48*3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* PUBLIC UPDATE FUNCTIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Allows the owner to transfer the ownership to `newOwner`.functiontransferOwnership(address newOwner) publicpayablevirtualonlyOwner{
/// @solidity memory-safe-assemblyassembly {
ifiszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.functionrenounceOwnership() publicpayablevirtualonlyOwner{
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller./// The request will automatically expire in 48 hours (172800 seconds) by default.functionrequestOwnershipHandover() publicpayablevirtual{
unchecked {
uint256 expires =block.timestamp+ _ownershipHandoverValidFor();
/// @solidity memory-safe-assemblyassembly {
// Compute and set the handover slot to `expires`.mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.functioncancelOwnershipHandover() publicpayablevirtual{
/// @solidity memory-safe-assemblyassembly {
// Compute and set the handover slot to 0.mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`./// Reverts if there is no existing ownership handover requested by `pendingOwner`.functioncompleteOwnershipHandover(address pendingOwner) publicpayablevirtualonlyOwner{
/// @solidity memory-safe-assemblyassembly {
// Compute and set the handover slot to 0.mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot :=keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.ifgt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.revert(0x1c, 0x04)
}
// Set the handover slot to 0.sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* PUBLIC READ FUNCTIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns the owner of the contract.functionowner() publicviewvirtualreturns (address result) {
/// @solidity memory-safe-assemblyassembly {
result :=sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.functionownershipHandoverExpiresAt(address pendingOwner)
publicviewvirtualreturns (uint256 result)
{
/// @solidity memory-safe-assemblyassembly {
// Compute the handover slot.mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result :=sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* MODIFIERS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Marks a function as only callable by the owner.modifieronlyOwner() virtual{
_checkOwner();
_;
}
}
Contract Source Code
File 17 of 24: ReentrancyGuard.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @notice Reentrancy guard mixin./// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ReentrancyGuard.sol)abstractcontractReentrancyGuard{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CUSTOM ERRORS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Unauthorized reentrant call.errorReentrancy();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* STORAGE *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Equivalent to: `uint72(bytes9(keccak256("_REENTRANCY_GUARD_SLOT")))`./// 9 bytes is large enough to avoid collisions with lower slots,/// but not too large to result in excessive bytecode bloat.uint256privateconstant _REENTRANCY_GUARD_SLOT =0x929eee149b4bd21268;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* REENTRANCY GUARD *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Guards a function from reentrancy.modifiernonReentrant() virtual{
/// @solidity memory-safe-assemblyassembly {
ifeq(sload(_REENTRANCY_GUARD_SLOT), address()) {
mstore(0x00, 0xab143c06) // `Reentrancy()`.revert(0x1c, 0x04)
}
sstore(_REENTRANCY_GUARD_SLOT, address())
}
_;
/// @solidity memory-safe-assemblyassembly {
sstore(_REENTRANCY_GUARD_SLOT, codesize())
}
}
/// @dev Guards a view function from read-only reentrancy.modifiernonReadReentrant() virtual{
/// @solidity memory-safe-assemblyassembly {
ifeq(sload(_REENTRANCY_GUARD_SLOT), address()) {
mstore(0x00, 0xab143c06) // `Reentrancy()`.revert(0x1c, 0x04)
}
}
_;
}
}
Contract Source Code
File 18 of 24: SSTORE2.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @notice Read and write to persistent storage at a fraction of the cost./// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SSTORE2.sol)/// @author Saw-mon-and-Natalie (https://github.com/Saw-mon-and-Natalie)/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)/// @author Modified from SSTORE3 (https://github.com/Philogy/sstore3)librarySSTORE2{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CONSTANTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The proxy initialization code.uint256privateconstant _CREATE3_PROXY_INITCODE =0x67363d3d37363d34f03d5260086018f3;
/// @dev Hash of the `_CREATE3_PROXY_INITCODE`./// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.bytes32internalconstant CREATE3_PROXY_INITCODE_HASH =0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CUSTOM ERRORS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Unable to deploy the storage contract.errorDeploymentFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* WRITE LOGIC *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Writes `data` into the bytecode of a storage contract and returns its address.functionwrite(bytesmemory data) internalreturns (address pointer) {
/// @solidity memory-safe-assemblyassembly {
let n :=mload(data) // Let `l` be `n + 1`. +1 as we prefix a STOP opcode./**
* ---------------------------------------------------+
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------|
* 61 l | PUSH2 l | l | |
* 80 | DUP1 | l l | |
* 60 0xa | PUSH1 0xa | 0xa l l | |
* 3D | RETURNDATASIZE | 0 0xa l l | |
* 39 | CODECOPY | l | [0..l): code |
* 3D | RETURNDATASIZE | 0 l | [0..l): code |
* F3 | RETURN | | [0..l): code |
* 00 | STOP | | |
* ---------------------------------------------------+
* @dev Prefix the bytecode with a STOP opcode to ensure it cannot be called.
* Also PUSH2 is used since max contract size cap is 24,576 bytes which is less than 2 ** 16.
*/// Do a out-of-gas revert if `n + 1` is more than 2 bytes.mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
// Deploy a new contract with the generated creation code.
pointer :=create(0, add(data, 0x15), add(n, 0xb))
ifiszero(pointer) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Writes `data` into the bytecode of a storage contract with `salt`/// and returns its normal CREATE2 deterministic address.functionwriteCounterfactual(bytesmemory data, bytes32 salt)
internalreturns (address pointer)
{
/// @solidity memory-safe-assemblyassembly {
let n :=mload(data)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
// Deploy a new contract with the generated creation code.
pointer :=create2(0, add(data, 0x15), add(n, 0xb), salt)
ifiszero(pointer) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Writes `data` into the bytecode of a storage contract and returns its address./// This uses the so-called "CREATE3" workflow,/// which means that `pointer` is agnostic to `data, and only depends on `salt`.functionwriteDeterministic(bytesmemory data, bytes32 salt)
internalreturns (address pointer)
{
/// @solidity memory-safe-assemblyassembly {
let n :=mload(data)
mstore(0x00, _CREATE3_PROXY_INITCODE) // Store the `_PROXY_INITCODE`.let proxy :=create2(0, 0x10, 0x10, salt)
ifiszero(proxy) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.revert(0x1c, 0x04)
}
mstore(0x14, proxy) // Store the proxy's address.// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).mstore(0x00, 0xd694)
mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
pointer :=keccak256(0x1e, 0x17)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
ifiszero(
mul( // The arguments of `mul` are evaluated last to first.extcodesize(pointer),
call(gas(), proxy, 0, add(data, 0x15), add(n, 0xb), codesize(), 0x00)
)
) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* ADDRESS CALCULATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns the initialization code hash of the storage contract for `data`./// Used for mining vanity addresses with create2crunch.functioninitCodeHash(bytesmemory data) internalpurereturns (bytes32 hash) {
/// @solidity memory-safe-assemblyassembly {
let n :=mload(data)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.returndatacopy(returndatasize(), returndatasize(), gt(n, 0xfffe))
mstore(data, add(0x61000180600a3d393df300, shl(0x40, n)))
hash :=keccak256(add(data, 0x15), add(n, 0xb))
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Equivalent to `predictCounterfactualAddress(data, salt, address(this))`functionpredictCounterfactualAddress(bytesmemory data, bytes32 salt)
internalviewreturns (address pointer)
{
pointer = predictCounterfactualAddress(data, salt, address(this));
}
/// @dev Returns the CREATE2 address of the storage contract for `data`/// deployed with `salt` by `deployer`./// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.functionpredictCounterfactualAddress(bytesmemory data, bytes32 salt, address deployer)
internalpurereturns (address predicted)
{
bytes32 hash = initCodeHash(data);
/// @solidity memory-safe-assemblyassembly {
// Compute and store the bytecode hash.mstore8(0x00, 0xff) // Write the prefix.mstore(0x35, hash)
mstore(0x01, shl(96, deployer))
mstore(0x15, salt)
predicted :=keccak256(0x00, 0x55)
// Restore the part of the free memory pointer that has been overwritten.mstore(0x35, 0)
}
}
/// @dev Equivalent to `predictDeterministicAddress(salt, address(this))`.functionpredictDeterministicAddress(bytes32 salt) internalviewreturns (address pointer) {
pointer = predictDeterministicAddress(salt, address(this));
}
/// @dev Returns the "CREATE3" deterministic address for `salt` with `deployer`.functionpredictDeterministicAddress(bytes32 salt, address deployer)
internalpurereturns (address pointer)
{
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40) // Cache the free memory pointer.mstore(0x00, deployer) // Store `deployer`.mstore8(0x0b, 0xff) // Store the prefix.mstore(0x20, salt) // Store the salt.mstore(0x40, CREATE3_PROXY_INITCODE_HASH) // Store the bytecode hash.mstore(0x14, keccak256(0x0b, 0x55)) // Store the proxy's address.mstore(0x40, m) // Restore the free memory pointer.// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).mstore(0x00, 0xd694)
mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
pointer :=keccak256(0x1e, 0x17)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* READ LOGIC *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Equivalent to `read(pointer, 0, 2 ** 256 - 1)`.functionread(address pointer) internalviewreturns (bytesmemory data) {
/// @solidity memory-safe-assemblyassembly {
data :=mload(0x40)
let n :=and(sub(extcodesize(pointer), 0x01), 0xffffffffff)
extcodecopy(pointer, add(data, 0x1f), 0x00, add(n, 0x21))
mstore(data, n) // Store the length.mstore(0x40, add(n, add(data, 0x40))) // Allocate memory.
}
}
/// @dev Equivalent to `read(pointer, start, 2 ** 256 - 1)`.functionread(address pointer, uint256 start) internalviewreturns (bytesmemory data) {
/// @solidity memory-safe-assemblyassembly {
data :=mload(0x40)
let n :=and(sub(extcodesize(pointer), 0x01), 0xffffffffff)
extcodecopy(pointer, add(data, 0x1f), start, add(n, 0x21))
mstore(data, mul(sub(n, start), lt(start, n))) // Store the length.mstore(0x40, add(data, add(0x40, mload(data)))) // Allocate memory.
}
}
/// @dev Returns a slice of the data on `pointer` from `start` to `end`./// `start` and `end` will be clamped to the range `[0, args.length]`./// The `pointer` MUST be deployed via the SSTORE2 write functions./// Otherwise, the behavior is undefined./// Out-of-gas reverts if `pointer` does not have any code.functionread(address pointer, uint256 start, uint256 end)
internalviewreturns (bytesmemory data)
{
/// @solidity memory-safe-assemblyassembly {
data :=mload(0x40)
ifiszero(lt(end, 0xffff)) { end :=0xffff }
let d :=mul(sub(end, start), lt(start, end))
extcodecopy(pointer, add(data, 0x1f), start, add(d, 0x01))
ifiszero(and(0xff, mload(add(data, d)))) {
let n :=sub(extcodesize(pointer), 0x01)
returndatacopy(returndatasize(), returndatasize(), shr(64, n))
d :=mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
}
mstore(data, d) // Store the length.mstore(add(add(data, 0x20), d), 0) // Zeroize the slot after the bytes.mstore(0x40, add(add(data, 0x40), d)) // Allocate memory.
}
}
}
Contract Source Code
File 19 of 24: SafeTransferLib.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values./// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)////// @dev Note:/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection./// - For ERC20s, this implementation won't check that a token has code,/// responsibility is delegated to the caller.librarySafeTransferLib{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CUSTOM ERRORS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The ETH transfer has failed.errorETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.errorTransferFromFailed();
/// @dev The ERC20 `transfer` has failed.errorTransferFailed();
/// @dev The ERC20 `approve` has failed.errorApproveFailed();
/// @dev The Permit2 operation has failed.errorPermit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.errorPermit2AmountOverflow();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CONSTANTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.uint256internalconstant GAS_STIPEND_NO_STORAGE_WRITES =2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few/// storage reads and writes, but low enough to prevent griefing.uint256internalconstant GAS_STIPEND_NO_GRIEF =100000;
/// @dev The unique EIP-712 domain domain separator for the DAI token contract.bytes32internalconstant DAI_DOMAIN_SEPARATOR =0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.addressinternalconstant WETH9 =0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address./// [Github](https://github.com/Uniswap/permit2)/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)addressinternalconstant PERMIT2 =0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* ETH OPERATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.//// The regular variants:// - Forwards all remaining gas to the target.// - Reverts if the target reverts.// - Reverts if the current contract has insufficient balance.//// The force variants:// - Forwards with an optional gas stipend// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).// - If the target reverts, or if the gas stipend is exhausted,// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.// - Reverts if the current contract has insufficient balance.//// The try variants:// - Forwards with a mandatory gas stipend.// - Instead of reverting, returns whether the transfer succeeded./// @dev Sends `amount` (in wei) ETH to `to`.functionsafeTransferETH(address to, uint256 amount) internal{
/// @solidity memory-safe-assemblyassembly {
ifiszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.functionsafeTransferAllETH(address to) internal{
/// @solidity memory-safe-assemblyassembly {
// Transfer all the ETH and check if it succeeded or not.ifiszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.functionforceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal{
/// @solidity memory-safe-assemblyassembly {
iflt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.revert(0x1c, 0x04)
}
ifiszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.mstore8(0x0b, 0x73) // Opcode `PUSH20`.mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.ifiszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.functionforceSafeTransferAllETH(address to, uint256 gasStipend) internal{
/// @solidity memory-safe-assemblyassembly {
ifiszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.mstore8(0x0b, 0x73) // Opcode `PUSH20`.mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.ifiszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.functionforceSafeTransferETH(address to, uint256 amount) internal{
/// @solidity memory-safe-assemblyassembly {
iflt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.revert(0x1c, 0x04)
}
ifiszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.mstore8(0x0b, 0x73) // Opcode `PUSH20`.mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.ifiszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.functionforceSafeTransferAllETH(address to) internal{
/// @solidity memory-safe-assemblyassembly {
// forgefmt: disable-next-itemifiszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.mstore8(0x0b, 0x73) // Opcode `PUSH20`.mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.ifiszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.functiontrySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internalreturns (bool success)
{
/// @solidity memory-safe-assemblyassembly {
success :=call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.functiontrySafeTransferAllETH(address to, uint256 gasStipend)
internalreturns (bool success)
{
/// @solidity memory-safe-assemblyassembly {
success :=call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* ERC20 OPERATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Sends `amount` of ERC20 `token` from `from` to `to`./// Reverts upon failure.////// The `from` account must have at least `amount` approved for/// the current contract to manage.functionsafeTransferFrom(address token, addressfrom, address to, uint256 amount) internal{
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40) // Cache the free memory pointer.mstore(0x60, amount) // Store the `amount` argument.mstore(0x40, to) // Store the `to` argument.mstore(0x2c, shl(96, from)) // Store the `from` argument.mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.// Perform the transfer, reverting upon failure.ifiszero(
and( // The arguments of `and` are evaluated from right to left.or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.////// The `from` account must have at least `amount` approved for the current contract to manage.functiontrySafeTransferFrom(address token, addressfrom, address to, uint256 amount)
internalreturns (bool success)
{
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40) // Cache the free memory pointer.mstore(0x60, amount) // Store the `amount` argument.mstore(0x40, to) // Store the `to` argument.mstore(0x2c, shl(96, from)) // Store the `from` argument.mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success :=and( // The arguments of `and` are evaluated from right to left.or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
mstore(0x60, 0) // Restore the zero slot to zero.mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`./// Reverts upon failure.////// The `from` account must have their entire balance approved for the current contract to manage.functionsafeTransferAllFrom(address token, addressfrom, address to)
internalreturns (uint256 amount)
{
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40) // Cache the free memory pointer.mstore(0x40, to) // Store the `to` argument.mstore(0x2c, shl(96, from)) // Store the `from` argument.mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.// Read the balance, reverting upon failure.ifiszero(
and( // The arguments of `and` are evaluated from right to left.gt(returndatasize(), 0x1f), // At least 32 bytes returned.staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount :=mload(0x60) // The `amount` is already at 0x60. We'll need to return it.// Perform the transfer, reverting upon failure.ifiszero(
and( // The arguments of `and` are evaluated from right to left.or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`./// Reverts upon failure.functionsafeTransfer(address token, address to, uint256 amount) internal{
/// @solidity memory-safe-assemblyassembly {
mstore(0x14, to) // Store the `to` argument.mstore(0x34, amount) // Store the `amount` argument.mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.// Perform the transfer, reverting upon failure.ifiszero(
and( // The arguments of `and` are evaluated from right to left.or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`./// Reverts upon failure.functionsafeTransferAll(address token, address to) internalreturns (uint256 amount) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.mstore(0x20, address()) // Store the address of the current contract.// Read the balance, reverting upon failure.ifiszero(
and( // The arguments of `and` are evaluated from right to left.gt(returndatasize(), 0x1f), // At least 32 bytes returned.staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount :=mload(0x34) // The `amount` is already at 0x34. We'll need to return it.mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.// Perform the transfer, reverting upon failure.ifiszero(
and( // The arguments of `and` are evaluated from right to left.or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract./// Reverts upon failure.functionsafeApprove(address token, address to, uint256 amount) internal{
/// @solidity memory-safe-assemblyassembly {
mstore(0x14, to) // Store the `to` argument.mstore(0x34, amount) // Store the `amount` argument.mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.// Perform the approval, reverting upon failure.ifiszero(
and( // The arguments of `and` are evaluated from right to left.or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract./// If the initial attempt to approve fails, attempts to reset the approved amount to zero,/// then retries the approval again (some tokens, e.g. USDT, requires this)./// Reverts upon failure.functionsafeApproveWithRetry(address token, address to, uint256 amount) internal{
/// @solidity memory-safe-assemblyassembly {
mstore(0x14, to) // Store the `to` argument.mstore(0x34, amount) // Store the `amount` argument.mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.// Perform the approval, retrying upon failure.ifiszero(
and( // The arguments of `and` are evaluated from right to left.or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x34, 0) // Store 0 for the `amount`.mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.mstore(0x34, amount) // Store back the original `amount`.// Retry the approval, reverting upon failure.ifiszero(
and(
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`./// Returns zero if the `token` does not exist.functionbalanceOf(address token, address account) internalviewreturns (uint256 amount) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x14, account) // Store the `account` argument.mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=mul( // The arguments of `mul` are evaluated from right to left.mload(0x20),
and( // The arguments of `and` are evaluated from right to left.gt(returndatasize(), 0x1f), // At least 32 bytes returned.staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`./// If the initial attempt fails, try to use Permit2 to transfer the token./// Reverts upon failure.////// The `from` account must have at least `amount` approved for the current contract to manage.functionsafeTransferFrom2(address token, addressfrom, address to, uint256 amount) internal{
if (!trySafeTransferFrom(token, from, to, amount)) {
permit2TransferFrom(token, from, to, amount);
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2./// Reverts upon failure.functionpermit2TransferFrom(address token, addressfrom, address to, uint256 amount)
internal{
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40)
mstore(add(m, 0x74), shr(96, shl(96, token)))
mstore(add(m, 0x54), amount)
mstore(add(m, 0x34), to)
mstore(add(m, 0x20), shl(96, from))
// `transferFrom(address,address,uint160,address)`.mstore(m, 0x36c78516000000000000000000000000)
let p := PERMIT2
let exists :=eq(chainid(), 1)
ifiszero(exists) { exists :=iszero(iszero(extcodesize(p))) }
ifiszero(and(call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00), exists)) {
mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
}
}
}
/// @dev Permit a user to spend a given amount of/// another user's tokens via native EIP-2612 permit if possible, falling/// back to Permit2 if native permit fails or is not implemented on the token.functionpermit2(address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
for {} shl(96, xor(token, WETH9)) {} {
mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.ifiszero(
and( // The arguments of `and` are evaluated from right to left.lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.// Gas stipend to limit gas burn for tokens that don't refund gas when// an non-existing function is called. 5K should be enough for a SLOAD.staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
)
) { break }
// After here, we can be sure that token is a contract.let m :=mload(0x40)
mstore(add(m, 0x34), spender)
mstore(add(m, 0x20), shl(96, owner))
mstore(add(m, 0x74), deadline)
ifeq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
mstore(0x14, owner)
mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.// `nonces` is already at `add(m, 0x54)`.// `1` is already stored at `add(m, 0x94)`.mstore(add(m, 0xb4), and(0xff, v))
mstore(add(m, 0xd4), r)
mstore(add(m, 0xf4), s)
success :=call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
break
}
mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.mstore(add(m, 0x54), amount)
mstore(add(m, 0x94), and(0xff, v))
mstore(add(m, 0xb4), r)
mstore(add(m, 0xd4), s)
success :=call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
break
}
}
if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
}
/// @dev Simple permit on the Permit2 contract.functionsimplePermit2(address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal{
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40)
mstore(m, 0x927da105) // `allowance(address,address,address)`.
{
let addressMask :=shr(96, not(0))
mstore(add(m, 0x20), and(addressMask, owner))
mstore(add(m, 0x40), and(addressMask, token))
mstore(add(m, 0x60), and(addressMask, spender))
mstore(add(m, 0xc0), and(addressMask, spender))
}
let p :=mul(PERMIT2, iszero(shr(160, amount)))
ifiszero(
and( // The arguments of `and` are evaluated from right to left.gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
)
) {
mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.revert(add(0x18, shl(2, iszero(p))), 0x04)
}
mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).// `owner` is already `add(m, 0x20)`.// `token` is already at `add(m, 0x40)`.mstore(add(m, 0x60), amount)
mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.// `nonce` is already at `add(m, 0xa0)`.// `spender` is already at `add(m, 0xc0)`.mstore(add(m, 0xe0), deadline)
mstore(add(m, 0x100), 0x100) // `signature` offset.mstore(add(m, 0x120), 0x41) // `signature` length.mstore(add(m, 0x140), r)
mstore(add(m, 0x160), s)
mstore(add(m, 0x180), shl(248, v))
ifiszero(call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00)) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.revert(0x1c, 0x04)
}
}
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;// External librariesimport {LibString} from"solady/utils/LibString.sol";
import {ReentrancyGuard} from"solady/utils/ReentrancyGuard.sol";
import {SafeTransferLib} from"solady/utils/SafeTransferLib.sol";
import {ECDSA} from"solady/utils/ECDSA.sol";
import {Solarray} from"solarray/Solarray.sol";
// Internal importsimport {json} from"./onchain/json.sol";
import {Metadata, DisplayType} from"./onchain/Metadata.sol";
import {TraitLib} from"./dynamic-traits/lib/TraitLabelLib.sol";
import {AbstractNFT} from"./AbstractNFT.sol";
import {StackScoreRenderer} from"./StackScoreRenderer.sol";
import {IERC5192} from"./interfaces/IERC5192.sol";
/// @title StackScore/// @notice A fully onchain, dynamic, soulbound token for reputation scores/// @notice Each score is rendered as an onchain SVG./// @author strangechances (g@stack.so)contractStackScoreisAbstractNFT, IERC5192, ReentrancyGuard{
/// @notice The current version of the contract.stringpublicconstant version ="1";
/// @notice The name of the token.stringinternalconstant _tokenName ="Stack Score";
/// @notice The description of the token.stringinternalconstant _tokenDescription ="A dynamic, onchain, soulbound reputation score";
/// @notice The signer address./// @dev The signer address is used to verify the score signature.addresspublic signer;
/// @notice The mint fee.uint256public mintFee =0.001ether;
/// @notice The referral fee percentage, in basis points./// @dev This is a percentage of the mint fee, in basis points (100 basis points is 1%).uint256public referralBps =5000;
/// @notice Address to token ID mapping./// @dev Prevents multiple tokens from being minted for the same address.mapping(address=>uint256) public addressToTokenId;
/// @notice Signature mapping, to prevent replay attacks./// @dev This is used to prevent replay attacks.mapping(bytes32=>bool) internal signatures;
/// @notice The current token ID.uint256internal currentId;
/// @notice The renderer contract./// @dev This contract is used to render the SVG image.
StackScoreRenderer internal renderer;
/// @notice The mint fee recipient./// @dev This address receives the mint fee, minus any referral fee.addresspublic mintFeeRecipient;
/// @notice Emitted when the score is updated.eventScoreUpdated(address account, uint256 tokenId, uint256 oldScore, uint256 newScore);
/// @notice Emitted when a token is minted.eventMinted(address to, uint256 tokenId);
/// @notice Emitted when a referral is paid.eventReferralPaid(address referrer, address referred, uint256 amount);
/// @notice Emitted when the mint fee is updated.eventMintFeeUpdated(uint256 oldFee, uint256 newFee);
/// @notice Emitted when the mint fee recipient is updated.eventMintFeeRecipientUpdated(address oldRecipient, address newRecipient);
/// @notice Emitted when the referral fee is updated./// @dev This is a percentage of the mint fee, in basis points (100 basis points is 1%).eventReferralBpsUpdated(uint256 oldBps, uint256 newBps);
/// @notice Emitted when the renderer contract is updated.eventRendererUpdated(address oldRenderer, address newRenderer);
/// @notice Emitted when the signer address is updated.eventSignerUpdated(address oldSigner, address newSigner);
/// @notice Error thrown when the token is locked upon transfer./// @dev The token is Soulbound according to the ERC-5192 standard.errorTokenLocked(uint256 tokenId);
/// @notice Error thrown when the signature is invalid.errorInvalidSignature();
/// @notice Error thrown when the signature is already used.errorSignatureAlreadyUsed();
/// @notice Error thrown when the mint fee is insufficient.errorInsufficientFee();
/// @notice Error thrown when the sender is not the token owner./// @dev For example, when a non-owner tries to update a score's color palette.errorOnlyTokenOwner();
/// @notice Error thrown when a given timestamp is older than the last update for a score.errorTimestampTooOld();
/// @notice Error thrown if mint called for the second time for the same address.errorOneTokenPerAddress();
/// @notice Constructor/// @dev Set the name and symbol of the token.constructor(address initialOwner) AbstractNFT(_tokenName, "STACK_SCORE") {
_initializeOwner(initialOwner);
}
/// @notice Mint a new soulbound token./// @dev Mint a new token and lock it./// @dev The mint fee is sent to the mint fee recipient./// @dev Does not require a signature, since there is no score./// @param to The address to mint the token to./// @return The token ID.functionmint(address to) payablepublicnonReentrantreturns (uint256) {
_assertSufficientFee();
SafeTransferLib.safeTransferETH(mintFeeRecipient, msg.value);
_mintTo(to);
return currentId;
}
/// @notice Mint a new soulbound token with a referral./// @dev Mint a new token, lock it, and send a referral fee./// @dev Does not need to check a signature, since there is no score./// @param to The address to mint the token to./// @param referrer The address to send the referral fee to./// @return The token ID.functionmintWithReferral(address to,
address referrer
) payablepublicnonReentrantreturns (uint256) {
_assertSufficientFee();
uint256 referralAmount = _getReferralAmount(msg.value);
SafeTransferLib.safeTransferETH(mintFeeRecipient, msg.value- referralAmount);
emit ReferralPaid(referrer, to, referralAmount);
SafeTransferLib.safeTransferETH(referrer, referralAmount);
_mintTo(to);
return currentId;
}
/// @notice Mint a new soulbound token with a score./// @dev Mint a new token, lock it, and update the score./// @param to The address to mint the token to./// @param score The score to set./// @param signature The signature to verify./// @return The token ID.functionmintWithScore(address to,
uint256 score,
uint256 timestamp,
bytesmemory signature
) payablepublicreturns (uint256) {
mint(to);
updateScore(currentId, score, timestamp, signature);
return currentId;
}
/// @notice Mint a new soulbound token with a score and palette./// @dev Mint a new token, lock it, update the score, and update the palette./// @param to The address to mint the token to./// @param score The score to set./// @param palette The palette index to set./// @param signature The signature to verify./// @return The token ID.functionmintWithScoreAndPalette(address to,
uint256 score,
uint256 timestamp,
uint256 palette,
bytesmemory signature
) payablepublicreturns (uint256) {
mint(to);
updateScore(currentId, score, timestamp, signature);
updatePalette(currentId, palette);
return currentId;
}
/// @notice Mint a new soulbound token with a score and referral./// @dev Mint a new token, lock it, update the score, and send a referral fee./// @param to The address to mint the token to./// @param score The score to set./// @param referrer The address to send the referral fee to./// @param signature The signature to verify./// @return The token ID.functionmintWithScoreAndReferral(address to,
uint256 score,
uint256 timestamp,
address referrer,
bytesmemory signature
) payablepublicreturns (uint256) {
mintWithReferral(to, referrer);
updateScore(currentId, score, timestamp, signature);
return currentId;
}
/// @notice Mint a new soulbound token with a score, referral, and palette./// @dev Mint a new token, lock it, update the score, send a referral fee, and update the palette./// @param to The address to mint the token to./// @param score The score to set./// @param referrer The address to send the referral fee to./// @param palette The palette index to set./// @param signature The signature to verify./// @return The token ID.functionmintWithScoreAndReferralAndPalette(address to,
uint256 score,
uint256 timestamp,
address referrer,
uint256 palette,
bytesmemory signature
) payablepublicreturns (uint256) {
mintWithReferral(to, referrer);
updateScore(currentId, score, timestamp, signature);
updatePalette(currentId, palette);
return currentId;
}
/// @notice Mint a new soulbound token with a referral and palette./// @dev Mint a new token, lock it, send a referral fee, and update the palette./// @dev Does not require a signature, since there is no score./// @param to The address to mint the token to./// @param referrer The address to send the referral fee to./// @param palette The palette index to set./// @return The token ID.functionmintWithReferralAndPalette(address to,
address referrer,
uint256 palette
) payablepublicnonReentrantreturns (uint256) {
mintWithReferral(to, referrer);
updatePalette(currentId, palette);
return currentId;
}
/// @notice Mint a new soulbound token with a palette./// @dev Mint a new token, lock it, and update the palette./// @dev Does not require a signature, since there is no score./// @param to The address to mint the token to./// @param palette The palette index to set.functionmintWithPalette(address to,
uint256 palette
) payablepublicnonReentrantreturns (uint256) {
mint(to);
updatePalette(currentId, palette);
return currentId;
}
/// @notice Update the score for a given token ID./// @dev The score is signed by the signer for the account./// @param tokenId The token ID to update./// @param newScore The new score./// @param signature The signature to verify.functionupdateScore(uint256 tokenId, uint256 newScore, uint256 timestamp, bytesmemory signature) public{
uint256 oldScore =uint256(getTraitValue(tokenId, "score"));
if (newScore ==0&& oldScore ==0) {
// No need to update the score if it's already 0.return;
}
address account = ownerOf(tokenId);
_assertValidTimestamp(tokenId, timestamp);
_assertValidScoreSignature(account, newScore, timestamp, signature);
this.setTrait(tokenId, "updatedAt", bytes32(timestamp));
this.setTrait(tokenId, "score", bytes32(newScore));
emit ScoreUpdated(account, tokenId, oldScore, newScore);
}
/// @notice Update the palette index for a given token ID./// @dev The palette index is the index of the palette to use for rendering./// @dev Only the owner can update the palette index./// @param tokenId The token ID to update.functionupdatePalette(uint256 tokenId, uint256 paletteIndex) public{
_assertTokenOwner(tokenId);
this.setTrait(tokenId, "paletteIndex", bytes32(paletteIndex));
}
/// @notice Check if a token is locked./// @dev The token is Soulbound according to the ERC-5192 standard./// @param tokenId The token ID to check.functionlocked(uint256 tokenId) publicpureoverridereturns (bool) {
returntrue;
}
/// @notice Get the score for a given account./// @param account The account to get the score for./// @return The score.functiongetScore(address account) publicviewreturns (uint256) {
returnuint256(getTraitValue(addressToTokenId[account], "score"));
}
/// @notice Get the updated at timestamp for a given token ID./// @dev The updated at timestamp is the timestamp of the last score update./// @param tokenId The token ID to get the updated at timestamp for./// @return The updated at timestamp.functiongetUpdatedAt(uint256 tokenId) publicviewreturns (uint256) {
returnuint256(getTraitValue(tokenId, "updatedAt"));
}
/// @notice Get the score and last updated timestamp for a given account./// @dev The score is the reputation score aggregated from Stack leaderboards, and the last updated timestamp./// @param account The account to get the score and last updated timestamp for./// @return The score and last updated timestamp.functiongetScoreAndLastUpdated(address account) publicviewreturns (uint256, uint256) {
uint256 tokenId = addressToTokenId[account];
return (
uint256(getTraitValue(tokenId, "score")),
uint256(getTraitValue(tokenId, "updatedAt"))
);
}
/// @notice Get the palette index for a given token ID./// @param tokenId The token ID to get the palette index for./// @return The palette index.functiongetPaletteIndex(uint256 tokenId) publicviewreturns (uint256) {
returnuint256(getTraitValue(tokenId, "paletteIndex"));
}
/// @notice Get the current token ID./// @return The current token ID.functiongetCurrentId() publicviewreturns (uint256) {
return currentId;
}
/// @notice Get the renderer contract address./// @return The renderer contract address.functiongetRenderer() publicviewreturns (address) {
returnaddress(renderer);
}
/// @notice Set the renderer contract address./// @dev Only the owner can set the renderer contract address./// @param _renderer The renderer contract address.functionsetRenderer(address _renderer) publiconlyOwner{
address oldRenderer =address(renderer);
renderer = StackScoreRenderer(_renderer);
emit RendererUpdated(oldRenderer, _renderer);
}
/// @notice Set the signer address./// @dev Only the owner can set the signer address./// @param _signer The signer address.functionsetSigner(address _signer) publiconlyOwner{
address oldSigner = signer;
signer = _signer;
emit SignerUpdated(oldSigner, _signer);
}
/// @notice Set the mint fee./// @dev Only the owner can set the mint fee.functionsetMintFee(uint256 fee) publiconlyOwner{
uint256 oldFee = mintFee;
mintFee = fee;
emit MintFeeUpdated(oldFee, mintFee);
}
/// @notice Set the referral fee percentage./// @dev Only the owner can set the referral fee percentage./// @param bps The referral fee percentage, in basis points.functionsetReferralBps(uint256 bps) publiconlyOwner{
referralBps = bps;
emit ReferralBpsUpdated(referralBps, bps);
}
/// @notice Set the mint fee recipient./// @dev Only the owner can set the mint fee recipient./// @param _mintFeeRecipient The mint fee recipient address.functionsetMintFeeRecipient(address _mintFeeRecipient) publiconlyOwner{
address oldFeeRecipient = mintFeeRecipient;
mintFeeRecipient = _mintFeeRecipient;
emit MintFeeRecipientUpdated(oldFeeRecipient, mintFeeRecipient);
}
function_getReferralAmount(uint256 amount) internalviewreturns (uint256) {
return amount * referralBps /10000;
}
function_assertOneTokenPerAddress(address to) internalview{
if (balanceOf(to) >0) {
revert OneTokenPerAddress();
}
}
/// @notice Mint a new soulbound token./// @dev Mint a new token, lock it./// @param to The address to mint the token to.function_mintTo(address to) internal{
_assertOneTokenPerAddress(to);
unchecked {
_mint(to, ++currentId);
}
addressToTokenId[to] = currentId;
emit Minted(to, currentId);
emit Locked(currentId);
}
/// @notice Verify the signature for the score./// @dev The function throws an error if the signature is invalid, or has been used before./// @param account The account to verify the score for./// @param score The score to verify./// @param signature The signature to verify.function_assertValidScoreSignature(address account, uint256 score, uint256 timestamp, bytesmemory signature) internal{
if (signatures[keccak256(signature)]) {
revert SignatureAlreadyUsed();
}
signatures[keccak256(signature)] =true;
bytes32 hash = ECDSA.toEthSignedMessageHash(
keccak256(abi.encodePacked(account, score, timestamp))
);
if (ECDSA.recover(hash, signature) != signer) {
revert InvalidSignature();
}
}
/// @notice Verify the sender is the owner of the token./// @dev The function throws an error if the sender is not the owner of the token./// @param tokenId The token ID to verify the owner for.function_assertTokenOwner(uint256 tokenId) internalview{
if (msg.sender!= ownerOf(tokenId)) {
revert OnlyTokenOwner();
}
}
/// @notice Verify the timestamp is not too old./// @dev The function throws an error if the timestamp is too old./// @param tokenId The token ID to verify the timestamp for./// @param timestamp The timestamp to verify.function_assertValidTimestamp(uint256 tokenId, uint256 timestamp) internalview{
uint256 lastUpdatedAt =uint256(getTraitValue(tokenId, "updatedAt"));
// Ensure the score is newer than the last update.if (lastUpdatedAt > timestamp) {
revert TimestampTooOld();
}
}
function_assertSufficientFee() internalview{
if (msg.value< mintFee) {
revert InsufficientFee();
}
}
/// @notice Get the URI for the trait metadata/// @param tokenId The token ID to get URI for/// @return The trait metadata URI.function_stringURI(uint256 tokenId) internalviewoverridereturns (stringmemory) {
return json.objectOf(
Solarray.strings(
json.property("name", _tokenName),
json.property("description", _tokenDescription),
json.property("image", Metadata.base64SvgDataURI(_image(tokenId))),
_attributes(tokenId)
)
);
}
/// @notice Helper function to get the static attributes for a given token ID/// @dev The static attributes are the name and description./// @param tokenId The token ID to get the static attributes for/// @return The static attributes.function_staticAttributes(uint256 tokenId) internalviewvirtualoverridereturns (string[] memory) {
return Solarray.strings(
Metadata.attribute({traitType: "Score Version", value: version})
);
}
/// @notice Run checks before token transfers/// @dev Only allow transfers from the zero address, since the token is soulbound./// @param from The address the token is being transferred from/// @param tokenId The token IDfunction_beforeTokenTransfer(addressfrom, address, uint256 tokenId) internalpureoverride{
// if the token is being transferred from an addressif (from!=address(0)) {
revert TokenLocked(tokenId);
}
}
/// @notice Helper function to get the raw SVG image for a given token ID/// @dev The SVG image is rendered by the renderer contract./// @param tokenId The token ID to get the dynamic attributes for/// @return The SVG image.function_image(uint256 tokenId) internalviewvirtualoverridereturns (stringmemory) {
address account = ownerOf(tokenId);
uint256 paletteIndex =uint256(getTraitValue(tokenId, "paletteIndex"));
uint256 score =uint256(getTraitValue(tokenId, "score"));
uint256 updatedAt =uint256(getTraitValue(tokenId, "updatedAt"));
return renderer.getSVG(score, account, paletteIndex, updatedAt);
}
/// @notice Check if the sender is the owner of the token or an approved operator./// @param tokenId The token ID to check./// @param addr The address to check./// @return True if the address is the owner or an approved operator.function_isOwnerOrApproved(uint256 tokenId, address addr) internalviewoverridereturns (bool) {
return addr == ownerOf(tokenId);
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;import {DisplayType, Metadata} from"../../onchain/Metadata.sol";
import {json} from"../../onchain/json.sol";
import {Solarray} from"solarray/Solarray.sol";
import {LibString} from"solady/utils/LibString.sol";
import {SSTORE2} from"solady/utils/SSTORE2.sol";
///@notice Bitmap type for storing allowed editorstype Editors isuint8;
///@notice alias type for storing and loading TraitLabels using SSTORE2type StoredTraitLabel isaddress;
///@notice Enumeration of allowed editor rolesenumAllowedEditor {
Anyone,
Self,
TokenOwner,
Custom,
ContractOwner
}
///@notice Struct associating a bytes32 traitValue to its string representationstructFullTraitValue {
bytes32 traitValue;
string fullTraitValue;
}
///@notice Struct representing a trait labelstructTraitLabel {
// The full trait key string if different from the bytes32 traitKeystring fullTraitKey;
// The string label for the traitstring traitLabel;
// The list of acceptable values for the trait, if it must be validatedstring[] acceptableValues;
// The list of full trait values, if the trait value should be converted to a different string
FullTraitValue[] fullTraitValues;
// The display type for the trait
DisplayType displayType;
// The list of editors allowed to set the trait
Editors editors;
// Whether the trait is required to have a valuebool required;
}
// Pack allowedEditors and valueRequiresValidation (for writes) plus storageAddress (for reads) into a single slotstructTraitLabelStorage {
// The bitmap of editors allowed to set the trait
Editors allowedEditors;
// true if TraitLabel.required == truebool required;
// true if TraitLabel.acceptableValues.length != 0bool valuesRequireValidation;
// The address of the TraitLabel in contract storage, aliased as a StoredTraitLabel
StoredTraitLabel storedLabel;
}
libraryTraitLabelStorageLib{
/**
* @notice Decode a TraitLabel from contract storage
* @param labelStorage TraitLabelStorage
*/functiontoTraitLabel(TraitLabelStorage memory labelStorage) internalviewreturns (TraitLabel memory) {
return StoredTraitLabelLib.load(labelStorage.storedLabel);
}
/**
* @notice Given a trait key and value, render it as a properly formatted JSON attribute
* @param traitLabelStorage Storage mapping of trait keys to TraitLabelStorage
* @param traitKey Trait key
* @param traitValue Trait value
*/functiontoAttributeJson(mapping(bytes32 traitKey => TraitLabelStorage traitLabelStorage) storage traitLabelStorage,
bytes32 traitKey,
bytes32 traitValue
) internalviewreturns (stringmemory) {
// read and decode the trait label from contract storage
TraitLabelStorage storage labelStorage = traitLabelStorage[traitKey];
TraitLabel memory traitLabel = toTraitLabel(labelStorage);
stringmemory actualTraitValue;
// convert traitValue if possibleif (traitLabel.fullTraitValues.length!=0) {
// try to find matching FullTraitValueuint256 length = traitLabel.fullTraitValues.length;
for (uint256 i =0; i < length;) {
FullTraitValue memory fullTraitValue = traitLabel.fullTraitValues[i];
if (fullTraitValue.traitValue == traitValue) {
actualTraitValue = fullTraitValue.fullTraitValue;
break;
}
unchecked {
++i;
}
}
}
// if no match, use traitValue as-isif (bytes(actualTraitValue).length==0) {
actualTraitValue = TraitLib.toString(traitValue, traitLabel.displayType);
}
// render the attribute as JSONreturn Metadata.attribute({
traitType: traitLabel.traitLabel,
value: actualTraitValue,
displayType: traitLabel.displayType
});
}
/**
* @notice Given trait keys, render their labels as a properly formatted JSON array
* @param traitLabelStorage Storage mapping of trait keys to TraitLabelStorage
* @param keys Trait keys to render labels for
*/functiontoLabelJson(mapping(bytes32 => TraitLabelStorage) storage traitLabelStorage, bytes32[] memory keys)
internalviewreturns (stringmemory)
{
string[] memory result =newstring[](keys.length);
uint256 i;
for (i; i < keys.length;) {
bytes32 key = keys[i];
TraitLabel memory traitLabel = TraitLabelStorageLib.toTraitLabel(traitLabelStorage[key]); //.toTraitLabel();
result[i] = TraitLabelLib.toLabelJson(traitLabel, key);
unchecked {
++i;
}
}
return json.arrayOf(result);
}
}
libraryFullTraitValueLib{
/**
* @notice Convert a FullTraitValue to a JSON object
*/functiontoJson(FullTraitValue memory fullTraitValue) internalpurereturns (stringmemory) {
return json.objectOf(
Solarray.strings(
// TODO: is hex string appropriate here? doesn't make sense to render hashes as strings otherwise
json.property("traitValue", LibString.toHexString(uint256(fullTraitValue.traitValue))),
json.property("fullTraitValue", fullTraitValue.fullTraitValue)
)
);
}
/**
* @notice Convert an array of FullTraitValues to a JSON array of objects
*/functiontoJson(FullTraitValue[] memory fullTraitValues) internalpurereturns (stringmemory) {
string[] memory result =newstring[](fullTraitValues.length);
for (uint256 i =0; i < fullTraitValues.length;) {
result[i] = toJson(fullTraitValues[i]);
unchecked {
++i;
}
}
return json.arrayOf(result);
}
}
libraryTraitLabelLib{
errorInvalidTraitValue(bytes32 traitKey, bytes32 traitValue);
/**
* @notice Store a TraitLabel in contract storage using SSTORE2 and return the StoredTraitLabel
*/functionstore(TraitLabel memoryself) internalreturns (StoredTraitLabel) {
return StoredTraitLabel.wrap(SSTORE2.write(abi.encode(self)));
}
/**
* @notice Validate a trait value against a TraitLabel's acceptableValues
* @param label TraitLabel
* @param traitKey Trait key
* @param traitValue Trait value
*/functionvalidateAcceptableValue(TraitLabel memory label, bytes32 traitKey, bytes32 traitValue) internalpure{
string[] memory acceptableValues = label.acceptableValues;
uint256 length = acceptableValues.length;
DisplayType displayType = label.displayType;
if (length !=0) {
stringmemory stringValue = TraitLib.toString(traitValue, displayType);
bytes32 hashedValue =keccak256(abi.encodePacked(stringValue));
for (uint256 i =0; i < length;) {
if (hashedValue ==keccak256(abi.encodePacked(acceptableValues[i]))) {
return;
}
unchecked {
++i;
}
}
revert InvalidTraitValue(traitKey, traitValue);
}
}
/**
* @notice Convert a TraitLabel to a JSON object
* @param label TraitLabel
* @param traitKey Trait key for the label
*/functiontoLabelJson(TraitLabel memory label, bytes32 traitKey) internalpurereturns (stringmemory) {
return json.objectOf(
Solarray.strings(
json.property("traitKey", TraitLib.asString(traitKey)),
json.property(
"fullTraitKey",
bytes(label.fullTraitKey).length==0 ? TraitLib.asString(traitKey) : label.fullTraitKey
),
json.property("traitLabel", label.traitLabel),
json.rawProperty("acceptableValues", TraitLib.toJson(label.acceptableValues)),
json.rawProperty("fullTraitValues", FullTraitValueLib.toJson(label.fullTraitValues)),
json.property("displayType", Metadata.toString(label.displayType)),
json.rawProperty("editors", EditorsLib.toJson(label.editors))
)
);
}
}
libraryTraitLib{
/**
* @notice Convert a bytes32 trait value to a string
* @param key The trait value to convert
* @param displayType The display type of the trait value
*/functiontoString(bytes32 key, DisplayType displayType) internalpurereturns (stringmemory) {
if (
displayType == DisplayType.Number || displayType == DisplayType.BoostNumber
|| displayType == DisplayType.BoostPercent
) {
return LibString.toString(uint256(key));
} else {
return asString(key);
}
}
/**
* @notice Convert a bytes32 to a string
* @param key The bytes32 to convert
*/functionasString(bytes32 key) internalpurereturns (stringmemory) {
uint256 len = _bytes32StringLength(key);
stringmemory result;
///@solidity memory-safe-assemblyassembly {
// assign result to free memory pointer
result :=mload(0x40)
// increment free memory pointer by two wordsmstore(0x40, add(0x40, result))
// store length at resultmstore(result, len)
// store key at next wordmstore(add(result, 0x20), key)
}
return result;
}
/**
* @notice Get the "length" of a bytes32 by counting number of non-zero leading bytes
*/function_bytes32StringLength(bytes32 str) internalpurereturns (uint256) {
// only meant to be called in a view context, so this optimizes for bytecode size over performancefor (uint256 i; i <32;) {
if (str[i] ==0) {
return i;
}
unchecked {
++i;
}
}
return32;
}
/**
* @notice Convert an array of strings to a JSON array of strings
*/functiontoJson(string[] memory acceptableValues) internalpurereturns (stringmemory) {
return json.arrayOf(json.quote(acceptableValues));
}
}
libraryEditorsLib{
/**
* @notice Convert an array of AllowedEditor enum values to an Editors bitmap
*/functionaggregate(AllowedEditor[] memory editors) internalpurereturns (Editors) {
uint256 editorsLength = editors.length;
uint256 result;
for (uint256 i =0; i < editorsLength;) {
result |=1<<uint8(editors[i]);
unchecked {
++i;
}
}
return Editors.wrap(uint8(result));
}
/**
* @notice Convert an Editors bitmap to an array of AllowedEditor enum values
*/functionexpand(Editors editors) internalpurereturns (AllowedEditor[] memory allowedEditors) {
uint8 _editors = Editors.unwrap(editors);
if (_editors &1==1) {
allowedEditors =new AllowedEditor[](1);
allowedEditors[0] = AllowedEditor.Anyone;
return allowedEditors;
}
// optimistically allocate 4 slots
AllowedEditor[] memory result =new AllowedEditor[](4);
uint256 num;
for (uint256 i =1; i <5;) {
bool set = _editors & (1<< i) !=0;
if (set) {
result[num] = AllowedEditor(i);
unchecked {
++num;
}
}
unchecked {
++i;
}
}
///@solidity memory-safe-assemblyassembly {
mstore(result, num)
}
return result;
}
/**
* @notice Convert an AllowedEditor enum value to its corresponding bit in an Editors bitmap
*/functiontoBitMap(AllowedEditor editor) internalpurereturns (uint8) {
returnuint8(1<<uint256(editor));
}
/**
* @notice Check if an Editors bitmap contains a given AllowedEditor
*/functioncontains(Editors self, AllowedEditor editor) internalpurereturns (bool) {
return Editors.unwrap(self) & toBitMap(editor) !=0;
}
/**
* @notice Convert an Editors bitmap to a JSON array of numbers
*/functiontoJson(Editors editors) internalpurereturns (stringmemory) {
return toJson(expand(editors));
}
/**
* @notice Convert an array of AllowedEditors to a JSON array of numbers
*/functiontoJson(AllowedEditor[] memory editors) internalpurereturns (stringmemory) {
string[] memory result =newstring[](editors.length);
for (uint256 i =0; i < editors.length;) {
result[i] = LibString.toString(uint8(editors[i]));
unchecked {
++i;
}
}
return json.arrayOf(result);
}
}
libraryStoredTraitLabelLib{
/**
* @notice Check that a StoredTraitLabel is not the zero address, ie, that it exists
*/functionexists(StoredTraitLabel storedTraitLabel) internalpurereturns (bool) {
return StoredTraitLabel.unwrap(storedTraitLabel) !=address(0);
}
/**
* @notice Load a TraitLabel from contract storage using SSTORE2
*/functionload(StoredTraitLabel storedTraitLabel) internalviewreturns (TraitLabel memory) {
bytesmemory data = SSTORE2.read(StoredTraitLabel.unwrap(storedTraitLabel));
returnabi.decode(data, (TraitLabel));
}
}
usingEditorsLibforEditorsglobal;
usingStoredTraitLabelLibforStoredTraitLabelglobal;
Contract Source Code
File 24 of 24: json.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;import {LibString} from"solady/utils/LibString.sol";
/**
* @title JSON
* @author emo.eth
* @notice TODO: overrides for common types that automatically stringify
*/libraryjson{
stringprivateconstant NULL ="";
usingLibStringforstring;
/**
* @notice enclose a string in {braces}
* Note: does not escape quotes in value
* @param value string to enclose in braces
* @return string of {value}
*/functionobject(stringmemory value) internalpurereturns (stringmemory) {
returnstring.concat("{", value, "}");
}
/**
* @notice enclose a string in [brackets]
* Note: does not escape quotes in value
* @param value string to enclose in brackets
* @return string of [value]
*/functionarray(stringmemory value) internalpurereturns (stringmemory) {
returnstring.concat("[", value, "]");
}
/**
* @notice enclose name and value with quotes, and place a colon "between":"them".
* Note: escapes quotes in name and value
* @param name name of property
* @param value value of property
* @return string of "name":"value"
*/functionproperty(stringmemory name, stringmemory value) internalpurereturns (stringmemory) {
returnstring.concat('"', name.escapeJSON(), '":"', value.escapeJSON(), '"');
}
/**
* @notice enclose name with quotes, but not rawValue, and place a colon "between":them
* Note: escapes quotes in name, but not value (which may itself be a JSON object, array, etc)
* @param name name of property
* @param rawValue raw value of property, which will not be enclosed in quotes
* @return string of "name":value
*/functionrawProperty(stringmemory name, stringmemory rawValue) internalpurereturns (stringmemory) {
returnstring.concat('"', name.escapeJSON(), '":', rawValue);
}
/**
* @notice comma-join an array of properties and {"enclose":"them","in":"braces"}
* Note: does not escape quotes in properties, as it assumes they are already escaped
* @param properties array of '"name":"value"' properties to join
* @return string of {"name":"value","name":"value",...}
*/functionobjectOf(string[] memory properties) internalpurereturns (stringmemory) {
return object(_commaJoin(properties));
}
/**
* @notice comma-join an array of values and enclose them [in,brackets]
* Note: does not escape quotes in values, as it assumes they are already escaped
* @param values array of values to join
* @return string of [value,value,...]
*/functionarrayOf(string[] memory values) internalpurereturns (stringmemory) {
return array(_commaJoin(values));
}
/**
* @notice comma-join two arrays of values and [enclose,them,in,brackets]
* Note: does not escape quotes in values, as it assumes they are already escaped
* @param values1 first array of values to join
* @param values2 second array of values to join
* @return string of [values1_0,values1_1,values2_0,values2_1...]
*/functionarrayOf(string[] memory values1, string[] memory values2) internalpurereturns (stringmemory) {
if (values1.length==0) {
return arrayOf(values2);
} elseif (values2.length==0) {
return arrayOf(values1);
}
return array(string.concat(_commaJoin(values1), ",", _commaJoin(values2)));
}
/**
* @notice enclose a string in double "quotes", escaping any existing quotes
* @param str string to enclose in quotes
* @return string of "value"
*/functionquote(stringmemory str) internalpurereturns (stringmemory) {
returnstring.concat('"', str.escapeJSON(), '"');
}
/**
* @notice enclose each string in an array in double "quotes", escaping any existing quotes
* @param strs array of strings, each to escape and enclose in quotes
*/functionquote(string[] memory strs) internalpurereturns (string[] memory) {
string[] memory result =newstring[](strs.length);
for (uint256 i =0; i < strs.length;) {
result[i] = quote(strs[i]);
unchecked {
++i;
}
}
return result;
}
/**
* @notice comma-join an array of strings
* @param values array of strings to join
* @return string of value,value,...
*/function_commaJoin(string[] memory values) internalpurereturns (stringmemory) {
return _join(values, ",");
}
/**
* @notice join two strings with a comma
* @param value1 first string
* @param value2 second string
* @return string of value1,value2
*/function_commaJoin(stringmemory value1, stringmemory value2) internalpurereturns (stringmemory) {
returnstring.concat(value1, ",", value2);
}
/**
* @notice join an array of strings with a specified separator
* @param values array of strings to join
* @param separator separator to join with
* @return string of value<separator>value<separator>...
*/function_join(string[] memory values, stringmemory separator) internalpurereturns (stringmemory) {
if (values.length==0) {
return NULL;
}
stringmemory result = values[0];
for (uint256 i =1; i < values.length; ++i) {
result =string.concat(result, separator, values[i]);
}
return result;
}
}