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)
}
}
}