Git Product home page Git Product logo

solady's Introduction

solady

NPM CI Solidity

Gas optimized Solidity snippets.

I'm sooooooOooooooooOoooOoooooooooooooooo...

Installation

To install with Foundry:

forge install vectorized/solady

To install with Hardhat:

npm install solady

Contracts

The Solidity smart contracts are located in the src directory.

accounts
├─ Receiver"Receiver mixin for ETH and safe-transferred ERC721 and ERC1155 tokens"
├─ ERC1271"ERC1271 mixin with nested EIP-712 approach"
├─ ERC4337"Simple ERC4337 account implementation"
├─ ERC4337Factory"Simple ERC4337 account factory implementation"
├─ ERC6551"Simple ERC6551 account implementation"
├─ ERC6551Proxy"Relay proxy for upgradeable ERC6551 accounts"
├─ LibERC6551"Library for interacting with ERC6551 accounts"
auth
├─ Ownable"Simple single owner authorization mixin"
├─ OwnableRoles"Simple single owner and multiroles authorization mixin"
tokens
├─ WETH"Simple Wrapped Ether implementation"
├─ ERC20"Simple ERC20 + EIP-2612 implementation"
├─ ERC4626"Simple ERC4626 tokenized Vault implementation"
├─ ERC721"Simple ERC721 implementation with storage hitchhiking"
├─ ERC2981"Simple ERC2981 NFT Royalty Standard implementation"
├─ ERC1155"Simple ERC1155 implementation"
├─ ERC6909"Simple EIP-6909 minimal multi-token implementation"
utils
├─ MerkleProofLib"Library for verification of Merkle proofs"
├─ SignatureCheckerLib"Library for verification of ECDSA and ERC1271 signatures"
├─ ECDSA"Library for verification of ECDSA signatures"
├─ EIP712"Contract for EIP-712 typed structured data hashing and signing"
├─ DeploylessPredeployQueryer"Deployless queryer for predeploys"
├─ ERC1967Factory"Factory for deploying and managing ERC1967 proxy contracts"
├─ ERC1967FactoryConstants"The address and bytecode of the canonical ERC1967Factory"
├─ JSONParserLib"Library for parsing JSONs"
├─ LibSort"Library for efficient sorting of memory arrays"
├─ LibPRNG"Library for generating pseudorandom numbers"
├─ Base64"Library for Base64 encoding and decoding"
├─ SSTORE2"Library for cheaper reads and writes to persistent storage"
├─ CREATE3"Deploy to deterministic addresses without an initcode factor"
├─ LibRLP"Library for computing contract addresses from their deployer and nonce"
├─ LibBit"Library for bit twiddling and boolean operations"
├─ LibZip"Library for compressing and decompressing bytes"
├─ Clone"Class with helper read functions for clone with immutable args"
├─ LibClone"Minimal proxy library"
├─ Initializable"Initializable mixin for the upgradeable contracts"
├─ UUPSUpgradeable"UUPS proxy mixin"
├─ LibString"Library for converting numbers into strings and other string operations"
├─ LibBitmap"Library for storage of packed booleans"
├─ LibMap"Library for storage of packed unsigned integers"
├─ EnumerableSetLib"Library for managing enumerable sets in storage"
├─ MinHeapLib"Library for managing a min-heap in storage or memory"
├─ RedBlackTreeLib"Library for managing a red-black-tree in storage"
├─ ReentrancyGuard"Reentrancy guard mixin"
├─ Multicallable"Contract that enables a single call to call multiple methods on itself"
├─ GasBurnerLib"Library for burning gas without reverting"
├─ SafeTransferLib"Safe ERC20/ETH transfer lib that handles missing return values"
├─ DynamicBufferLib"Library for buffers with automatic capacity resizing"
├─ MetadataReaderLib"Library for reading contract metadata robustly"
├─ FixedPointMathLib"Arithmetic library with operations for fixed-point numbers"
├─ SafeCastLib"Library for integer casting that reverts on overflow"
└─ DateTimeLib"Library for date time operations"

Directories

src — "Solidity smart contracts"
test — "Foundry Forge tests"
js — "Accompanying JavaScript helper library"
ext — "Extra tests"
audits — "Audit reports"

Contributing

This repository serves as a laboratory for cutting edge snippets that may be merged into Solmate.

Feel free to make a pull request.

Do refer to the contribution guidelines for more details.

Safety

This is experimental software and is provided on an "as is" and "as available" basis.

We do not give any warranties and will not be liable for any loss incurred through any use of this codebase.

While Solady has been heavily tested, there may be parts that may exhibit unexpected emergent behavior when used with other code, or may break in future Solidity versions.

Please always include your own thorough tests when using Solady to make sure it works correctly with your code.

Upgradability

Most contracts in Solady are compatible with both upgradeable and non-upgradeable (i.e. regular) contracts.

Please call any required internal initialization methods accordingly.

EVM Compatibility

Some parts of Solady may not be compatible with chains with partial EVM equivalence.

Please always check and test for compatibility accordingly.

Acknowledgements

This repository is inspired by or directly modified from many sources, primarily:

solady's People

Contributors

0xbrito avatar 0xkitetsu-smdk avatar 0xth0mas avatar 8times4 avatar adhusson avatar atarpara avatar banteg avatar bizzyvinci avatar chlwys avatar colinplatt avatar danipopes avatar de33 avatar devtooligan avatar engn33r avatar eugenioclrc avatar jorgeatpaladin avatar jtriley-eth avatar kadenzipfel avatar mdehoog avatar mswat5 avatar outdoteth avatar pcaversaccio avatar philogy avatar rogerpodacter avatar romeroadrian avatar sennett-lau avatar sevenswen avatar vectorized avatar z0r0z avatar zerosnacks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

solady's Issues

📝 Bad Solmate Reference Link

The ECDSA file has a broken link to a solmate implementation, found in line 6.

The link in question: https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol.

After some digging in the solmate repo, I could not find an implementation of ECDSA, in either the main or v7 branch.

Add support for git tags to be used in `forge install`

I was looking at Damn Vulnerable DeFi v3 and that CTFs uses Solady v0.0.67.

I'm using foundry as my test suite, so I tried to install the dependency via forge install Vectorized/[email protected] but this specific command is looking for a tag and as far as I can see Solady do not push tag/releases.

Foundry support different installation ways with forge install

  • branch
  • tag
  • commit

And I could install that specific v0.0.67 by looking at the commit history, but I think that it would be much better for projects if they could pin to a specific version by using the tag.

Feature Request: Add `BokkyPooBahsDateTimeLibrary`

This is a longish-shot because the lib is moderately niche, but just to stir the pot and get some ideas out there, a snippet I use that's not on NPM and hasn't been updated in a while and is probably unoptimized is BokkyPooBahsDateTimeLibrary.

Use-case example: The CryptoPhunk Auction Flywheel buys NFTs on-demand. The Flywheel uses its ETH balance to buy, but it can't use the entire balance in one week. Instead there is a weekly allowance.

I used BokkyPooBahsDateTimeLibrary to create a getStartOfWeek function that works out this logic.

You could imagine this being used in other throttling-type situations, e.g., preventing someone from making 10 DAO proposals in one sitting.

Actually maybe this is a pretty useful library after all...

Feature Request: Add strict ERC1271 Signature Checking to `SignatureCheckerLib`

What this issue is about?

Motivation

In the current SignatureCheckerLib, there is support to check if a signature is valid with the isValidSignatureNow(..) function that checks the following:

  • If the hash and signature recover an address that is the same as the signer, it returns true.
  • When it's not the case, it calls isValidSignature(..) on the signer assuming it's a contract.

What would be a great addition to the library is to have another internal function that checks if a signature is valid with ERC1271 only. So calling isValidSignature(..) directly on the signer implementing ERC1271.

This function will be useful for functions that want to interact only with wallets such as argent, gnosis, etc .. and check the signature only via ERC1271.

Then this internal function can be re-used in isValidSignatureNow(..).

Details

Developers can interact with other smart contract wallets via calling isValidSignature(..) function directly. Still, they need to handle the return value, so the new function can be a wrapper named isValidERC1271SignatureNow(address,bytes32,bytes) that calls isValidSignature(..) on the address with the hash and the signature with staticcall and don't revert if the function doesn't exist and return true or false, depending if the function returns the isValidSignature.selector. Exactly like what the code do in the second part of the isValidSignatureNow(..) function.

The code in solidity would be something like this:

function isValidERC1271SignatureNow(address signer, bytes32 dataHash, bytes memory signature) internal view returns (bool) {
  
        (bool success, bytes memory result) = signer.staticcall(
            abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature)
        );
        return (success &&
            result.length == 32 &&
            abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
}

If you find this addition relevant, I can work on creating the new one in yul (splitting the isValidSignatureNow function into a new one).

⚡️ Optimize sort further

Some stuff I've considered:

  • Radix sort.

    Not really very generalizable to large numbers.
    Need O(n) auxillary space for the straightforward implementation,
    which can cost more gas if the array is big (cuz of EVM's quadratic memory expansion costs)

  • Bucket Sort

    Same pitfalls as Radix sort.

  • Sorting networks / Unrolled sorts

    Large bytecode, needs access to the jump, swap, dup opcodes.

This sample code is 3rd fastest compared to le wild submissions on https://g.solidity.cc/.

  • First submission uses bucket sort, which works well for small numbers, medium sized arrays, and uniformly distributed inputs.
    (maybe create two different kinds of sorts and let the user pick the one that best fit their case?)
    It uses sorting networks to sort the buckets.

  • Second submission uses unrolled insertion sorts and jumps.
    But this comes at a cost of big bytecode size, like 5x more lol.
    Also, we can't do jumps now.

If you have any suggestions, comment / make a PR, or DM me on Twitter.

If we can cut the gas down by another 50%, many on-chain possibilities can be unlocked.

Feature Request: Add `ReentrancyGuard`

This is a long shot but i think it may be worth a try.

So the idea here is to have solady with all the "standard" contracts much like oz and solmate have.

This way when working with solady as your library of choice you can have all the tools at your disposal instead of having to import from different libraries (unless its very specific). This would make the project have less "bloat" and be cleaner overall.

The code bellow passes on all of solmate's tests as well as saving 100 gas on protected calls.

abstract contract ReentrancyGuard {

    uint256 private constant _UNLOCK = 1;
    uint256 private constant _LOCKED = 2;
    uint256 private constant _REENTRANCY_SELECTOR = 0xab143c06;

    uint256 private _locked = _UNLOCK;
    
    error Reentrancy();

    modifier nonReentrant() virtual {
    
        /// @solidity memory-safe-assembly
        assembly{
            if iszero(eq(sload(_locked.slot), _UNLOCK)) {
                mstore(0x00, _REENTRANCY_SELECTOR)
                revert(0x1c, 0x04)
            }

            sstore(_locked.slot, _LOCKED)
        }

        _;
        
        /// @solidity memory-safe-assembly
        assembly{
            sstore(_locked.slot, _UNLOCK)
        }
    }
}

✨ LibSort additional functions

Planning to add the following:

/// @dev Returns whether `a` contains `needle`,
/// and the index of the nearest element less than or equal to `needle`.
function searchSorted(uint256[] a, uint256 needle) internal view
returns (bool found, uint256 index)
/// @dev Removes duplicate elements from a ascendingly sorted list.
function uniquifySorted(uint256[] a) internal view
/// @dev Removes duplicate elements from a ascendingly sorted list.
function uniquifySorted(address[] a) internal view

for performance reasons, all functions will not revert if the array is not sorted; it will simply be undefined behavior.

✨ Uniquify

If https://notes.ethereum.org/@vbuterin/proposals_to_adjust_memory_gas_costs hits (preferably scenario 2, which makes things simple), we can probably abuse a gigantic in-memory bitmap to uniquify an array.

If you are game, you can make a PR for that now lol.

That gas proposal will also unlock many possibilities, such as bucket-sort.

You can also make a PR for that now lol.

For contingency, we can probably use some gas testing to see if we can use the new giant hashmap method or the old sort + scan method. :P

✨ DynamicBufferLib

DynamicBuffer is a snippet that makes concatenating big strings cheaper. For example if you want to construct an SVG out of <rect>s this is way better than abi.encodePacked or string.concat or, dare I say, even better than SOLADY concat?

There are different versions of this all over the place. It would be cool to have one true optimized version in this great library!

✨ FixPointMathLib new functions

  • log2Up.
    • Basically log2 + iszero(isPo2). Don't import LibBit pls. Just inline that shit.
  • fullMulDiv.
    • No unsafe variant will be added.
      Rationale: if performance is of priority, users should restrict their arithmetic to a smaller precision such that overflow is impossible.
  • fullMulDivUp.
  • All xxDown functions renamed to simply xx.

📝 Philosophy

Design

  • Balance between runtime gas and bytecode size.

    • Strong preference for expressions that can be adaptively evaluated on compile time depending on the optimizer runs, which can be tweaked by users.
  • Reduce reverts if possible.

    • Consider returning special values for edge cases. Let users decide on how they want to handle it.

    • Reverts require branching, which makes functions non-inlinable.

  • Best is an elegant API that is safe, intuitive, and saves gas.

  • Do not over modularize.

  • Libraries should be as independent from each other as possible.

  • If you want a new feature, do give a convincing, well-designed use case for the feature.

    • Great low-level library, but bad high level approach = bad code.
  • Suppose we have two standard-conforming implementations:

    A: more gas efficient, but harder to use.

    B: less gas efficient, but easier to use.

    We will prefer A.

  • Optimize for performance and flexibility for experts.

Safety and Testing

  • Mask inputs that are less than 256 in bit width.

  • Brutalize memory in tests if the code allocates memory.

  • Brutalize upper unused bits of inputs if possible.

  • Test with paranoia. The code is only as good as the tests.

  • Prefer a few general concise fuzz tests that cover every possibility, over many verbose small unit tests that each fail only for specific cases. General fuzz tests help catch the unknown unknowns, the blind spots. Safety first.

  • Test until you are absolutely certain of correctness and safety, even in adversarial conditions.

Video

https://www.youtube.com/watch?v=brPHcAJn7ZU

Small Idea (or is it): `reverseSort()`

Basically a more efficient shortcut for:

ary.sort();
ary.reverse();

My use-case is to use this in conjunction with pop() to remove the smallest element of an array.

Actually, the really useful thing here might be removeElementAtIndex(uint256 idx) that deletes the array element and shifts everything over. There are many arguably-jank snippets for this.

Just a thought!

Discussion: Should _GAS_STIPEND_NO_GRIEF be 30,000?

Iconic NFT collection Azuki recently used this value in their heavily-memed Skateboard Auction.

The Nouns auction house does the same.

Given that auctions are a major use-case for safe ETH transfers, does this mean that Solady should take its cues from these major players? Or does it mean that the major players are just intra-player copy-pasting and there's nothing special about 30k.

After all, Azuki didn't use a fallback of any kind, which is certainly something Solady should not copy.

Just a thought!

✨ New LibString functions

I heard that some people are using LibString in their tests, or tokenURIs.

uint256 internal constant NOT_FOUND = type(uint256).max;

function slice(string memory s, uint256 start) internal pure returns (string memory result)

function slice(string memory s, uint256 start, uint256 end) internal pure returns (string memory result)

function repeat(string memory s, uint256 times) internal pure returns (string memory result)

function indexOf(string memory s, string memory needle) internal pure returns (uint256 result)

function indexOf(string memory s, string memory needle, uint256 from) internal pure returns (uint256 result)

function lastIndexOf(string memory s, string memory needle) internal pure returns (uint256 result)

function lastIndexOf(string memory s, string memory needle, uint256 from) internal pure returns (uint256 result)

function startsWith(string memory s, string memory needle) internal pure returns (bool result)

function endsWith(string memory s, string memory needle) internal pure returns (bool result)

Don't need to max optimize for run-time gas.

Small code footprint and bytecode size is of priority.

Other common string functions in languages like JS, Ruby, Python have been considered, but are not planned to be added due to the following reasons:

  • heavy code needed
  • lack of on-chain usefulness
  • can be implemented efficiently with native Solidity functions and few lines of code, with the proposed functions

❔ ERC165 support in Ownable/OwnableRoles

Arguments for: EIP says "should"
Arguments against: OZ and Solmate don't implement.

image

At very least, I am curious if we can describe an optimized yul version of supportsInterface that can be weaved into contemporary use of ERC165, like NFTs.

This issue is opened up for discussion. I personally find ERC165 a bit crufty.

Could be good? Add `appendBase64()`

Ethier's DynamicBuffer has a function called appendSafeBase64() that appends and base64-izes data simultaneously. I don't know assembly but this seems potentially more efficient than one at a time!

Interestingly Solady is given an author credit on the function.

✨ Multicaller

Port of the famous multicall everyone uses.

Probably better if in a new repo, deployed to multiple chains via create2, mined with 6 leading zeros.

Use the direct return trick in Multicallable. Seriously, it can save thousands of gas for certain use cases.

I'm too lazy and busy to do this. Lmk if u want to do it.

Idea: add LCG to `LibPRNG`

shuffle is great, but if you're trying to pseudo-randomize more than ~10k tokens in tokenURI it will exceed 30M gas.

In a project I worked on called Punk Pixels we wanted to shuffle every non-background pixel in every CryptoPunk (which was about 2M tokens).

To accomplish this I used an LCG of the form:

function tokenIdToPixelIndex(uint tokenId) private pure returns (uint) {   
    return ((tokenId + offset) * bigPrimeNumber) % maxSupply;
}

It makes sense that LibPRNG shouldn't store big prime numbers, but if it took a BYOP ("Bring Your Own Prime") approach it could offer a shuffling solution for much larger sequences.

Thoughts?

🐞 DynamicBufferLib memory underallocation

I ran into an issue using DynamicBufferLib on this code (punkDataContract.punkImage(punkId) refers to the punkImage function on this contract):

bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

function tokenImage(uint16 punkId) public view returns (string memory) {
    bytes memory pixels = punkDataContract.punkImage(punkId);
    DynamicBufferLib.DynamicBuffer memory svgBytes;
    
    svgBytes.append('<svg width="1200" height="1200" shape-rendering="crispEdges" xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 24 24"><style>rect{width:1px;height:1px}</style>');
    
    svgBytes.append('<g><rect x="0" y="0" style="width:100%;height:100%" fill="#d6dde4" />');
    
    bytes memory buffer = new bytes(8);
    for (uint256 y = 0; y < 24; y++) {
        for (uint256 x = 0; x < 24; x++) {
            uint256 p = (y * 24 + x) * 4;
            if (uint8(pixels[p + 3]) > 0) {
                for (uint256 i = 0; i < 4; i++) {
                    uint8 value = uint8(pixels[p + i]);
                    
                    buffer[i * 2 + 1] = _HEX_SYMBOLS[value & 0xf];
                    value >>= 4;
                    buffer[i * 2] = _HEX_SYMBOLS[value & 0xf];
                }
                
                string memory oldColor = string(buffer);
                string memory newColor;
                
                if (oldColor.endsWith("00")) {
                    newColor = "d6dde4";
                } else {
                    newColor = "788a97";
                }
                
                svgBytes.append(
                    abi.encodePacked(
                        '<rect x="',
                        x.toString(),
                        '" y="',
                        y.toString(),
                        '" fill="#',
                        newColor,
                        '"/>'
                    )
                );
            }
        }
    }
    
    svgBytes.append('</g>');
    svgBytes.append('</svg>');
    return string(svgBytes.data);
}

For some (but not all) punks, for example id = 6, I was getting an Error: VM Exception while processing transaction: reverted with panic code 0x32 (Array accessed at an out-of-bounds or negative index) error at the buffer[i * 2 + 1] = _HEX_SYMBOLS[value & 0xf]; line.

When I went back to my old DynamicBuffer library it started working consistently again.

Not sure how a bug like this could work and maybe I'm doing something dumb but wanted to report just in case!

✨ Data poisoning

Let's make a folder called hehe.

We will litter it with incorrect Yul snippets to throw off the Github co-pilot data.

PRs welcomed.

Increase the entropy for `_OWNER_SLOT_NOT`

I am wondering what is the motivation to choose a short _OWNER_SLOT_NOT. Is it for reducing bytecode size?

I think in practice it is very hard for collision to happen. However, with the current 4-byte length, the collision probability is still not negligible.

For this reason, I think it's probably better to use a slot that uses full 32 bytes entropy.

Legit Suggestion: add `length()` to `LibString`

String length is one of the classic "chain shame" topics because it's so complicated how could Solidity ever get it right and you better stick to JS.

On the other hand, there are many instances where you need on-chain string length to validate, most-famously in the case of ENS. They use this function:

function strlen(string memory s) internal pure returns (uint256) {
    uint256 len;
    uint256 i = 0;
    uint256 bytelength = bytes(s).length;
    for (len = 0; i < bytelength; len++) {
        bytes1 b = bytes(s)[i];
        if (b < 0x80) {
            i += 1;
        } else if (b < 0xE0) {
            i += 2;
        } else if (b < 0xF0) {
            i += 3;
        } else if (b < 0xF8) {
            i += 4;
        } else if (b < 0xFC) {
            i += 5;
        } else {
            i += 6;
        }
    }
    return len;
}

Users should be able to build contracts that validate and register ENS names. In addition, they should be able to build ENS-like services, or services that merely exist in a length-aware world.

It would be cool to duplicate the functionality of this code but in a more efficient way, AND ALSO to have a string length function that does a better job of faithfully computing what a human would call string length than does this code (so the next time someone builds ENS they will do it better).

Thoughts?

Medium Suggestion: add `toLower()` to `LibString`

I'm sorry but ENS really should validate on the contract-level whether the person is scamming with upper-case letters!

I'm sure it comes up in other contexts as well. Solady already has toHexStringChecksumed, so we've already crossed the "case sensitivity frontier."

Here is the ancient snippet you find if you google.

function _toLower(string memory str) internal pure returns (string memory) {
    bytes memory bStr = bytes(str);
    bytes memory bLower = new bytes(bStr.length);
    for (uint i = 0; i < bStr.length; i++) {
        // Uppercase character...
        if ((uint8(bStr[i]) >= 65) && (uint8(bStr[i]) <= 90)) {
            // So we add 32 to make it lowercase
            bLower[i] = bytes1(uint8(bStr[i]) + 32);
        } else {
            bLower[i] = bStr[i];
        }
    }
    return string(bLower);
}

This is of course just ascii and I assume it's difficult to "truly lowercase" things in Unicode, but there must be something better than the ENS approach which let me register BOOOOM.eth!

Just a thought!

Proposal: `toHexString` should not prefix with "0x" (or add `toHexStringNoPrefix`)

The "0x" prefix is purely stylistic and should be the user's responsibility to add if they want it.

This is consistent with JS behavior: (255).toString(16) == 'ff'

Alternatively, if this breaks too many things, it could be good to add a toHexStringNoPrefix function like Uniswap has.

function toHexStringNoPrefix(uint256 value, uint256 length) internal pure returns (string memory) {
    bytes memory buffer = new bytes(2 * length);
    for (uint256 i = buffer.length; i > 0; i--) {
        buffer[i - 1] = ALPHABET[value & 0xf];
        value >>= 4;
    }
    return string(buffer);
}

Just a thought!

🐞 Bug in SearchSorted in LibSort

I found one bug in searchSorted function.

when needle == array.length and needle is not in array then it return always true. It happen due underflowing high := sub(high,0x20).

NOTE: All Element must be greater than needle to reproduce is issue.

Example :

a = [2]
needle  = 1
LibSort.searchSorted(a,1) -> (true,0)

a = [45,46]
needle  = 2
LibSort.searchSorted(a,1) -> (true,0)


Idea: Add `ENS` resolution and reverse resolution

Ok don't kill me for this, but there is no good copy-paste!

If you don't believe me, just ask ENS:

Solidity libraries for on-chain resolution are not yet available, but ENS resolution is straightforward enough it can be done trivially without a library.

Then they go on to offer this snippet:

abstract contract ENS {
    function resolver(bytes32 node) public virtual view returns (Resolver);
}

abstract contract Resolver {
    function addr(bytes32 node) public virtual view returns (address);
}

contract MyContract {
    // Same address for Mainet, Ropsten, Rinkerby, Gorli and other networks;
    ENS ens = ENS(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e);

    function resolve(bytes32 node) public view returns(address) {
        Resolver resolver = ens.resolver(node);
        return resolver.addr(node);
    }
}

And say:

While it is possible for a contract to process a human-readable name into a node hash, we highly recommend working with node hashes instead, as they are easier and more efficient to work with, and allow contracts to leave the complex work of normalizing the name to their callers outside the blockchain.

More "chain shame"! Why can't I resolve a string ENS name to an address on chain like a human being?

And with REVERSE resolution, ie going from address => .eth name, I haven't been able to find a canonical method anywhere! I use this but I'm honestly not sure where it comes from:

interface DefaultReverseResolver {
  function name(bytes32 node) external view returns (string memory);
}

interface ENS {
  function resolver(bytes32 node) external view returns (address);
}

interface ReverseRegistrar {
  function node(address addr) external pure returns (bytes32);
}

ENS public ens = ENS(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e);
ReverseRegistrar public reverseResolver = ReverseRegistrar(0x084b1c3C81545d370f3634392De611CaaBFf8148);

function addressToEnsName(address addr) public view returns (string memory) {
    bytes32 node = reverseResolver.node(addr);
    address resolvedAddr = ens.resolver(node);
    
    if (resolvedAddr == address(0)) return addr.toHexString();
    
    return DefaultReverseResolver(resolvedAddr).name(node);
}

Anyway, just an idea! Don't kill me!

Possible subtle `LibSort` bug

Or maybe I'm just losing my mind.

The setup is I'm using Diamond Storage:

struct PunkDataStorage {
    bytes palette;
    mapping(uint8 => bytes) assets;
    mapping(uint8 => string) assetNames;
    mapping(uint8 => Gender) baseToGender;
    mapping(Gender => uint[]) genderToBases;

    uint[][][6] genderedAttributes;
    
    mapping(uint8 => bool) isHat;
    string[10] assetSlotToTraitType;
    
    mapping(uint80 => uint16) packedAssetsToOldPunksIdPlusOneMap;
    uint32 imageScaleUpFactor;
    address[] backgroundImagePointersByLevel;
    
    uint[] bases;
}

LibStorage {
    function punkDataStorage() internal pure returns (PunkDataStorage storage gs) {
        bytes32 position = keccak256("punk.data.storage");
        assembly {
            gs.slot := position
        }
    }
}

contract WithStorage {
    function ps() internal pure returns (PunkDataStorage storage) {
        return LibStorage.punkDataStorage();
    }
}

I initialize the storage by doing this:

        uint8[11] memory baseRanges = [1,2,3,4,5,6,7,8,9,10,11];
        
        for (uint i = 0; i < baseRanges.length; i++) {
            ps().bases.push(baseRanges[i]);
        }

Then I have this contract:

pragma solidity 0.8.17;

import "hardhat/console.sol";
import "solady/src/utils/LibSort.sol";

import "./LibStorage.sol";

contract SharedFacet is WithStorage {
    using LibSort for *;

    function _punkHasHiddenAttribute() public pure returns (bool) {
        return false;
    }
    
    function _punkIsValid() public view returns (bool) {
        // _punkHasHiddenAttribute();
        console.log(_punkHasHiddenAttribute());
        
        (bool found, uint idx) = ps().bases.searchSorted(6);
        
        for (uint i; i < 11; ++i) {
            console.log("bases[", i, "]", ps().bases[i]);
        }
        
        console.log("found", found);
        console.log("idx", idx);
        
        return found;
    }
}

And searchSorted() doesn't find 6, BUT, to my incredible surprise, when I replace console.log(_punkHasHiddenAttribute()) with _punkHasHiddenAttribute() it works correctly!

Not sure how this is possible. I suspect it either relates to assembly or perhaps I'm making some other dumb mistake I'm not aware of.

Reporting just in case!

✨ LibBytemap -> LibMap

We will have:

  • LibMap.Bytemap -> LibMap.Uint8Map
  • LibMap.Uint16Map
  • LibMap.Uint32Map
  • LibMap.Uint64Map
  • LibMap.Uint128Map

📌 Contribution guidelines

Most of the stuff here are similar to solmate's.

Points that are specific to solady are marked with a ♡.

Emoji key for Issues and PRs

Format: <emoji><space><Title>

Type Emoji
readme/docs 📝
new feature
refactor/cleanup ♻️
nit 🥢
security fix 🔒
optimization ⚡️
configuration 👷‍♂️
events 🔊
bug fix 🐞

Styling

  • Comments must have periods after every sentence.
  • Underscore prefix are reserved for private and internal functions and variables in contracts.
  • Variables and code expressions in comments should be backquoted (e.g. `b`). ♡
  • Memory addresses and memory related constants should be in hexadecimal format (e.g. 0x20). ♡
    This is to convey semantic meaning, and aid readability for binary / hexadecimal natives.
  • Please keep the maximum line length, including comments to 100 characters or below. This is a balance between the old-school 80 character limit and the newer 120 character limit in the Solidity style guide. This makes it easier to read code on small or split screens. ♡
  • Constants must be in ALL_CAPS. If it is a private or internal constant in a contract, prefix it with an underscore. ♡
  • Fuzz tests are simply testDescription. Let's normalize fuzz testing. ♡

File naming

Let's stick to solmate's, to maintain drop-in compatibility for the select few classes and libraries we have.

It's a mouthfeel over consistency thing.

Compiler / Transpiler safety

Make sure your PR's are compilable with --via-ir. ♡

Care must be taken when using the slot keyword in Yul,
to ensure that the code is compatible with upgradeable contract transpilers. ♡

Others

Our SafeTransferLib library accepts address instead of ERC20. ♡

This is intentional for better cross-compatibility with OpenZeppelin.

✨ LibOp

Thinking of adding a library of very basic branchless operations:

function min(uint256 x, uint256 y) internal pure returns (uint256 z)
function max(uint256 x, uint256 y) internal pure returns (uint256 z)
function clamp(uint256 x, uint256 minValue, uint256 maxValue) internal pure 
returns (uint256 z)
function avg(uint256 x, uint256 y) internal pure returns (uint256 z)
function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z)
function uncheckedDiv(uint256 x, uint256 y) internal pure returns (uint256 z)
function uncheckedDivUp(uint256 x, uint256 y) internal pure returns (uint256 z)
function uncheckedSDiv(int256 x, int256 y) internal pure returns (int256 z)
function uncheckedMod(uint256 x, uint256 y) internal pure returns (uint256 z)
function uncheckedAddMod(uint256 x, uint256 y, uint256 m) internal pure returns (uint256 z)
function uncheckedMulMod(uint256 x, uint256 y, uint256 m) internal pure returns (uint256 z)
function toUint256(bool x) internal pure returns (uint256 z)
function byteAt(uint256 x, uint256 i) internal pure returns (uint256 z)

📝 Dual license plans

Right now, a gigantic problem with the web3 space is code reuse without bringing value back to the maintainers.

We want to create a 2nd license which inherits from the MIT license that applies to any NFT collection using Solady on the Ethereum mainnet:

  • The top 10 contributors to Solady, and the top 10 contributors to Solmate, will each be entitled to a minimum of 10 free NFTs in the collection.

  • Each of the attribute traits must be within the 5% rarest options. If there are less than 20 different options for a particular trait, then the rarest option must be assigned to the trait.

  • The metadata must be immutably frozen after the NFTs have been awarded.

  • No restrictions to sell or transfer must be placed on the NFTs awarded.

  • The NFTs awarded must not be transferable by anyone except their owners and the authorized accounts by the owners, as defined in the ERC721 standard.

  • Honoring real life utility tied to the NFT allocations for the developers is optional.

Feature Request: `sortBy` functionality

Suppose you have an array of uint24 colors and you want to sort them by uint24 luminance, darkest to lightest. Would it be cool for Solady to support this? Or too niche?

Here is the code I've used for this:

function quickSort(uint24[] memory items, uint24[] memory data, int left, int right) public pure {
    int i = left;
    int j = right;
    if (i == j) return;
    uint pivot = data[uint(left + (right - left) / 2)];
    while (i <= j) {
        while (data[uint(i)] < pivot) i++;
        while (pivot < data[uint(j)]) j--;
        if (i <= j) {
            (items[uint(i)], items[uint(j)]) = (items[uint(j)], items[uint(i)]);
            (data[uint(i)], data[uint(j)]) = (data[uint(j)], data[uint(i)]);
            i++;
            j--;
        }
    }
    if (left < j)
        quickSort(items, data, left, j);
    if (i < right)
        quickSort(items, data, i, right);
}

function sortDataAndItems(uint24[] memory items, uint24[] memory data) public pure returns (uint24[] memory, uint24[] memory) {
    quickSort(items, data, int(0), int(data.length - 1));
    return (items, data);
}

Just an idea!

✨ LibSort difference, union, intersection

Kinda like set operations, but inputs and outputs are all sorted.

Does not need to check that inputs are sorted.

All operations must be O(n).

Anyone game, comment below.

Include variants for for uint, address, int.

Testing on address and int variants optional.

The tests would be interesting. Try make the tests as compact but as complete as possible. 🤔

✨ LibString escapeHTML

Now that you're already irritating the "you know, you really shouldn't do this in Solidity" people with escapeJSON, why not go all the way? It's also easier because there is no BELL!

Here is a highly-dumb version I wrote long ago that was adapted from the escapeQuotes() method in Uniswap's NFTDescriptor.sol. It's not even complete because it doesn't escape quotes, but for my use-case I didn't need to put the string in an HTML attribute which I think is the only reason to escape quotes, but IDK.

function escapeHTML(string memory input)
    internal
    pure
    returns (string memory)
{
    bytes memory inputBytes = bytes(input);
    uint extraCharsNeeded = 0;
    
    for (uint i = 0; i < inputBytes.length; i++) {
        bytes1 currentByte = inputBytes[i];
        
        if (currentByte == "&") {
            extraCharsNeeded += 4;
        } else if (currentByte == "<") {
            extraCharsNeeded += 3;
        } else if (currentByte == ">") {
            extraCharsNeeded += 3;
        }
    }
    
    if (extraCharsNeeded > 0) {
        bytes memory escapedBytes = new bytes(
            inputBytes.length + extraCharsNeeded
        );
        
        uint256 index;
        
        for (uint i = 0; i < inputBytes.length; i++) {
            if (inputBytes[i] == "&") {
                escapedBytes[index++] = "&";
                escapedBytes[index++] = "a";
                escapedBytes[index++] = "m";
                escapedBytes[index++] = "p";
                escapedBytes[index++] = ";";
            } else if (inputBytes[i] == "<") {
                escapedBytes[index++] = "&";
                escapedBytes[index++] = "l";
                escapedBytes[index++] = "t";
                escapedBytes[index++] = ";";
            } else if (inputBytes[i] == ">") {
                escapedBytes[index++] = "&";
                escapedBytes[index++] = "g";
                escapedBytes[index++] = "t";
                escapedBytes[index++] = ";";
            } else {
                escapedBytes[index++] = inputBytes[i];
            }
        }
        return string(escapedBytes);
    }
    
    return input;
}

Just throwing some ideas out there!

✨ New LibString functions (round 2)

  • packOne(string memory a)
  • unpackOne(bytes32 a)
  • packTwo(string memory a, string memory b)
  • unpackTwo(bytes32 a)
  • directReturn(string memory a)

If you know, you know. ;)

Lazy to type the returns for the signatures, cuz i’m in a forest right now.

The pack functions must be branchless (for performance and flexibility reasons). In the cases where inputs can’t fit, return bytes32(0).

Other functions should be branchless if possible. They are all small.

If no PRs by 29 Sep, I’ll probably start on it.

If there are multiple PRs, i’ll just choose the one that meets the requirements + standards.

Feature request: generic / reference ERC721 implementation

Okay don't kill me, but you shouldn't have to mint sequentially to have a payable transferFrom!

More seriously, mainstream ERC721 implementations are missing core "free money" optimizations like data hitchhiking. I wrote a proof of concept Better OpenZeppelin ERC721 but I don't have the clout to get it off the ground!

And this isn't even to mention all the assembly stuff which I cannot even do without at least learning more.

Also there are some "mistakes" (STRICTLY IMO, SORRY!) in ERC721A like startTimestamp which could be fixed in a new base contract that didn't have ERC721A's backwards compatibility requirements.

Finally, I think a move like this would raise Solady's profile which could attract more contributors and improve the other parts of the library, thus potentially offsetting some of the effort involved in developing it.

Someday Solmate's README is going to say that IT is a laboratory for SOLADY!

📝 Natspec for functions

Obviously, no one has the capacity to read through all the Yul.

Ain't nobody got the time for that.

Therefore, we shall break minimalism in the spirit of pragmatism.

No need to add stuff like @param, @return.

Just a simple and clear @dev will do.

If you have high levels of OCD and game for it, feel free to make a PR.

"Bold" Idea? `multicall` shouldn't be `payable`

Multicall is not safe with msg.value. Yet there are very few (if any?) use-cases for a function to accept eth and yet not rely on msg.value in any way.

Removing payable would invalidate this tiny set of use-cases, but on the positive side it would prevent me from losing 300M USD in the majority of cases (as your linked blogpost describes). I guess removing it would also cost more gas (as you famously argued in the case of ERC721A functions).

OZ's version is not payable, and the version you link (https://github.com/transmissions11/solmate/blob/main/src/utils/Multicallable.sol) seems to no longer exist!

If you don't want to break backwards incompatibility (which I think is warranted), perhaps adding SafeMulticallableUseThisOnePlease.sol could work.

Thoughts?

Ps. What would a "a suitable nonce mechanism" even be here? I guess a function could look at msg.sig to determine whether it's in a multicall and therefore cannot trust msg.value? Or maybe the multicall function itself could set a isInMultiCall state variable like reentrancy?

None of these seem good to me!

Feature request: add helper methods to simulate other datatypes in SSTORE2

Suppose you want to store an array of uint16s using SSTORE2. This is annoying because you have to extract the bytes and reinterpret them so that you have two bytes for every "array" entry.

Given that you often want to conceptually store something other than raw bytes in SSTORE2 it would be cool to have functions that allowed you to use SSTORE2 more like the convenient SSTORE-based datatypes.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.