Improve Contract Importability/Create modular SDKs for smaller and optimized contracts

Gas and Size savings: Offer a OpenZeppelin Lite distro SDK to the mainline SDK offering

I was looking at including OZ ECDSA contracts for a project, and comparing the releases saw a sizable difference in compiled output.

I am not asserting that the changes are superfluous, what I am asserting is that potentially it be made easier to inport only the dependencies that are needed. solidity import mechanics being what they are, maybe offering a stripped down version of the main branch for contracts that do not see much changes release to release, or maybe an opinionated categorial release flavour (eg “cryptography sdk, token sdk, etc). I understand that this introduces additional maintenance requirements, which is why I would think contracts not seeing much changes (such as signature related ones) would be more palatable for maintaining on a LTS basis.

I would think making the contracts better partially importable would be the easiest way to achieve the performance KPIs over releases.

Below is a totally not scientific nor statistically significant benchmark of what was casually observed. Repeated and reproducible testings should be made before any action be taken ofc. I post this only to start a conversation to see if such changes would be considered/better testing implemented for the project maintainers to use for consideration etc.

Thanks for yalls awesome work, I always learn something from the people that work at OZ!

Repository, note this includes non OZ contracts as well

Motivation

Reduce gas and size for ECDSA Signature Methods, reduce imported bloat, etc.

Benchmarks

Openzeppelin

Contract Size (kB) Margin (kB)
ECDSA 0.086 24.49
Math 0.086 24.49
Strings 0.086 24.49

Disco

Contract Size (kB) Margin (kB)
ECDSA 0.045 24.531
Strings 0.045 24.531

Changes

Following changes have been made and tested

Strings

Strings.sol includes the needed SafeMath logic that was removed in 4.8.0 of OpenZeppelin/Contracts. This reduces the size of the compiled contract by 50%.

ECDSA

Assembly changes

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, "\x19\x01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            data := keccak256(ptr, 66)
        }
    }

Basically @openzeppelin/encryption , @openzeppelin/utils, etc would help modularize and reduce import size and having better imortabikity for base contracts that touch lots of contracts or providing monolithic contracts with inlined imports for certain contracts like Strings etc

If you have made changes to the code, can you remove "from OpenZeppelin" from the description of your repository so that people aren't misled to think the code is the same?

If you've identified places where the code size and/or gas efficiency can be improved, we appreciate if you can report those in issues so that we can act on it.

Simply importing a Solidity file does not increase the size of the bytecode. Any unused functionality is not included in the bytecode at all. So if you're seeing any differences, that isn't the reason.

Strings doesn't use SafeMath. Do you mean the Math library? The reason it is now using the Math library is for the logarithm functions to get the number of digits of a number. Any increases you see are not because of the new import, they are because these functions trade off larger code size for more gas efficient execution. But even then if I compare the toString function I see that its bytecode is smaller in 4.8.0 than it was in 4.7.3, so it's more complex.

Splitting the package into several smaller packages will have no effect on the size of contracts.