Utilize scratch space for hashing one ore two value types

Solidity should allow use of the keccak256 function to hash one or two value types (for the purpose of this discussion a value type would be considered a value that could be cast to a bytes32) without requiring the values be converted into a bytes array to be hashed.

Simple hashes as shown below reduce the contract size, memory expansion and runtime cost and would reduce the amount of assembly code required in smart contracts. This would be useful in libraries and contracts that are implementing merkle trees and similar constructs.

While writing out the efficiency test I considered if the scratch space may be already in use when resolving the storage slot for nested mappings and did not find any conflicts where the hashEfficient function using assembly to mock a keccak256(valueType,valueType) would be not “memory safe”.

//SPDX-License-Identifer: MIT
pragma solidity 0.8.25;

contract TestHash {
    mapping(bytes32 => mapping(bytes32 => uint256)) public test;

    function setValue(bytes32 a, uint256 b) external {
        test[a][a] = b;
    }

    function setValue(uint256 a, uint256 b) external {
        test[hash(a)][hash(a)] = b;
    }

    function setValueEfficient(uint256 a, uint256 b) external {
        test[hashEfficient(a)][hashEfficient(a)] = b;
    }

    // 340 gas - selector b189fd4c
    function hash(uint256 a) public pure returns(bytes32 val) {
        val = keccak256(abi.encode(a));
    }

    // 364 gas - selector a78dac0d
    function hash(uint256 a, uint256 b) public pure returns(bytes32 val) {
        val = keccak256(abi.encode(a, b));
    }

    // 278 gas - selector c6531b92 (-84 gas relative to hash(uint256) when adjusted for selector priority)
    function hashEfficient(uint256 a) public pure returns(bytes32 val) {
        assembly {
            mstore(0x00, a)
            val := keccak256(0x00, 0x20)
        }
    }

    // 210 gas - selector 502920c7 (-132 gas relative to hash(uint256,uint256) when adjusted for selector priority)
    function hashEfficient(uint256 a, uint256 b) public pure returns(bytes32 val) {
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            val := keccak256(0x00, 0x40)
        }
    }
}
1 Like

I think this is a great suggestion and would like to see it implemented. Anyone out there agree? Buehler…buehler…buehler?

I wanted to add on here the potential gas savings for revamping keccak256 in general high-level Solidity to accept any number of value types is significant even in a post-Cancun EVM. Hashing of many values is common in EIP712 signatures and could be a benefit to the ecosystem as a whole.

 * @notice Gas savings for EfficientHash compared to keccak256(abi.encode(...)):
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 1 / 67 / 67
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 5 / 66 / 66
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 10 / 58 / 58
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 15 / 1549 / 565
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 20 / 3379 / 1027
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 25 / 5807 / 1650
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 50 / 23691 / 10107
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 75 / 69164 / 41620
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 99 / 172694 / 126646

hey bro what’s up! :tophat::clinking_glasses: