Extended packing and extracting library for value types (#5056)

Co-authored-by: ernestognw <ernestognw@gmail.com>
pull/5076/head
Hadrien Croubois 8 months ago committed by GitHub
parent d8e799db98
commit dc62599257
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .changeset/heavy-baboons-give.md
  2. 845
      contracts/utils/Packing.sol
  3. 30
      docs/modules/ROOT/pages/utilities.adoc
  4. 2
      scripts/generate/run.js
  5. 78
      scripts/generate/templates/Packing.js
  6. 5
      scripts/generate/templates/Packing.opts.js
  7. 42
      scripts/generate/templates/Packing.t.js
  8. 14
      test/helpers/deploy.js
  9. 563
      test/utils/Packing.t.sol
  10. 69
      test/utils/Packing.test.js

@ -2,4 +2,4 @@
'openzeppelin-solidity': minor
---
`Packing`: Added a new utility for packing and unpacking multiple values into a single bytes32. Includes initial support for packing two `uint128` in an `Uint128x2` type.
`Packing`: Added a new utility for packing, extracting and replacing bytesXX values.

@ -1,40 +1,845 @@
// SPDX-License-Identifier: MIT
// This file was procedurally generated from scripts/generate/templates/Packing.js.
pragma solidity ^0.8.20;
/**
* @dev Helper library packing and unpacking multiple values into bytes32
* @dev Helper library packing and unpacking multiple values into bytesXX.
*
* Example usage:
*
* ```solidity
* library MyPacker {
* type MyType is bytes32;
*
* function _pack(address account, bytes4 selector, uint64 period) external pure returns (MyType) {
* bytes12 subpack = Packing.pack_4_8(selector, bytes8(period));
* bytes32 pack = Packing.pack_20_12(bytes20(account), subpack);
* return MyType.wrap(pack);
* }
*
* function _unpack(MyType self) external pure returns (address, bytes4, uint64) {
* bytes32 pack = MyType.unwrap(self);
* return (
* address(Packing.extract_32_20(pack, 0)),
* Packing.extract_32_4(pack, 20),
* uint64(Packing.extract_32_8(pack, 24))
* );
* }
* }
* ```
*/
// solhint-disable func-name-mixedcase
library Packing {
type Uint128x2 is bytes32;
error OutOfRangeAccess();
/// @dev Cast a bytes32 into a Uint128x2
function asUint128x2(bytes32 self) internal pure returns (Uint128x2) {
return Uint128x2.wrap(self);
function pack_1_1(bytes1 left, bytes1 right) internal pure returns (bytes2 result) {
assembly ("memory-safe") {
result := or(left, shr(8, right))
}
}
/// @dev Cast a Uint128x2 into a bytes32
function asBytes32(Uint128x2 self) internal pure returns (bytes32) {
return Uint128x2.unwrap(self);
function pack_2_2(bytes2 left, bytes2 right) internal pure returns (bytes4 result) {
assembly ("memory-safe") {
result := or(left, shr(16, right))
}
}
/// @dev Pack two uint128 into a Uint128x2
function pack(uint128 first128, uint128 second128) internal pure returns (Uint128x2) {
return Uint128x2.wrap(bytes32(bytes16(first128)) | bytes32(uint256(second128)));
function pack_4_4(bytes4 left, bytes4 right) internal pure returns (bytes8 result) {
assembly ("memory-safe") {
result := or(left, shr(32, right))
}
}
/// @dev Split a Uint128x2 into two uint128
function split(Uint128x2 self) internal pure returns (uint128, uint128) {
return (first(self), second(self));
function pack_4_8(bytes4 left, bytes8 right) internal pure returns (bytes12 result) {
assembly ("memory-safe") {
result := or(left, shr(32, right))
}
}
/// @dev Get the first element of a Uint128x2 counting from higher to lower bytes
function first(Uint128x2 self) internal pure returns (uint128) {
return uint128(bytes16(Uint128x2.unwrap(self)));
function pack_4_12(bytes4 left, bytes12 right) internal pure returns (bytes16 result) {
assembly ("memory-safe") {
result := or(left, shr(32, right))
}
}
/// @dev Get the second element of a Uint128x2 counting from higher to lower bytes
function second(Uint128x2 self) internal pure returns (uint128) {
return uint128(uint256(Uint128x2.unwrap(self)));
function pack_4_16(bytes4 left, bytes16 right) internal pure returns (bytes20 result) {
assembly ("memory-safe") {
result := or(left, shr(32, right))
}
}
function pack_4_20(bytes4 left, bytes20 right) internal pure returns (bytes24 result) {
assembly ("memory-safe") {
result := or(left, shr(32, right))
}
}
function pack_4_24(bytes4 left, bytes24 right) internal pure returns (bytes28 result) {
assembly ("memory-safe") {
result := or(left, shr(32, right))
}
}
function pack_4_28(bytes4 left, bytes28 right) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
result := or(left, shr(32, right))
}
}
function pack_8_4(bytes8 left, bytes4 right) internal pure returns (bytes12 result) {
assembly ("memory-safe") {
result := or(left, shr(64, right))
}
}
function pack_8_8(bytes8 left, bytes8 right) internal pure returns (bytes16 result) {
assembly ("memory-safe") {
result := or(left, shr(64, right))
}
}
function pack_8_12(bytes8 left, bytes12 right) internal pure returns (bytes20 result) {
assembly ("memory-safe") {
result := or(left, shr(64, right))
}
}
function pack_8_16(bytes8 left, bytes16 right) internal pure returns (bytes24 result) {
assembly ("memory-safe") {
result := or(left, shr(64, right))
}
}
function pack_8_20(bytes8 left, bytes20 right) internal pure returns (bytes28 result) {
assembly ("memory-safe") {
result := or(left, shr(64, right))
}
}
function pack_8_24(bytes8 left, bytes24 right) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
result := or(left, shr(64, right))
}
}
function pack_12_4(bytes12 left, bytes4 right) internal pure returns (bytes16 result) {
assembly ("memory-safe") {
result := or(left, shr(96, right))
}
}
function pack_12_8(bytes12 left, bytes8 right) internal pure returns (bytes20 result) {
assembly ("memory-safe") {
result := or(left, shr(96, right))
}
}
function pack_12_12(bytes12 left, bytes12 right) internal pure returns (bytes24 result) {
assembly ("memory-safe") {
result := or(left, shr(96, right))
}
}
function pack_12_16(bytes12 left, bytes16 right) internal pure returns (bytes28 result) {
assembly ("memory-safe") {
result := or(left, shr(96, right))
}
}
function pack_12_20(bytes12 left, bytes20 right) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
result := or(left, shr(96, right))
}
}
function pack_16_4(bytes16 left, bytes4 right) internal pure returns (bytes20 result) {
assembly ("memory-safe") {
result := or(left, shr(128, right))
}
}
function pack_16_8(bytes16 left, bytes8 right) internal pure returns (bytes24 result) {
assembly ("memory-safe") {
result := or(left, shr(128, right))
}
}
function pack_16_12(bytes16 left, bytes12 right) internal pure returns (bytes28 result) {
assembly ("memory-safe") {
result := or(left, shr(128, right))
}
}
function pack_16_16(bytes16 left, bytes16 right) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
result := or(left, shr(128, right))
}
}
function pack_20_4(bytes20 left, bytes4 right) internal pure returns (bytes24 result) {
assembly ("memory-safe") {
result := or(left, shr(160, right))
}
}
function pack_20_8(bytes20 left, bytes8 right) internal pure returns (bytes28 result) {
assembly ("memory-safe") {
result := or(left, shr(160, right))
}
}
function pack_20_12(bytes20 left, bytes12 right) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
result := or(left, shr(160, right))
}
}
function pack_24_4(bytes24 left, bytes4 right) internal pure returns (bytes28 result) {
assembly ("memory-safe") {
result := or(left, shr(192, right))
}
}
function pack_24_8(bytes24 left, bytes8 right) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
result := or(left, shr(192, right))
}
}
function pack_28_4(bytes28 left, bytes4 right) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
result := or(left, shr(224, right))
}
}
function extract_2_1(bytes2 self, uint8 offset) internal pure returns (bytes1 result) {
if (offset > 1) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(248, not(0)))
}
}
function replace_2_1(bytes2 self, bytes1 value, uint8 offset) internal pure returns (bytes2 result) {
bytes1 oldValue = extract_2_1(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_4_1(bytes4 self, uint8 offset) internal pure returns (bytes1 result) {
if (offset > 3) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(248, not(0)))
}
}
function replace_4_1(bytes4 self, bytes1 value, uint8 offset) internal pure returns (bytes4 result) {
bytes1 oldValue = extract_4_1(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_4_2(bytes4 self, uint8 offset) internal pure returns (bytes2 result) {
if (offset > 2) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(240, not(0)))
}
}
function replace_4_2(bytes4 self, bytes2 value, uint8 offset) internal pure returns (bytes4 result) {
bytes2 oldValue = extract_4_2(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_8_1(bytes8 self, uint8 offset) internal pure returns (bytes1 result) {
if (offset > 7) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(248, not(0)))
}
}
function replace_8_1(bytes8 self, bytes1 value, uint8 offset) internal pure returns (bytes8 result) {
bytes1 oldValue = extract_8_1(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_8_2(bytes8 self, uint8 offset) internal pure returns (bytes2 result) {
if (offset > 6) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(240, not(0)))
}
}
function replace_8_2(bytes8 self, bytes2 value, uint8 offset) internal pure returns (bytes8 result) {
bytes2 oldValue = extract_8_2(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_8_4(bytes8 self, uint8 offset) internal pure returns (bytes4 result) {
if (offset > 4) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(224, not(0)))
}
}
function replace_8_4(bytes8 self, bytes4 value, uint8 offset) internal pure returns (bytes8 result) {
bytes4 oldValue = extract_8_4(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_12_1(bytes12 self, uint8 offset) internal pure returns (bytes1 result) {
if (offset > 11) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(248, not(0)))
}
}
function replace_12_1(bytes12 self, bytes1 value, uint8 offset) internal pure returns (bytes12 result) {
bytes1 oldValue = extract_12_1(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_12_2(bytes12 self, uint8 offset) internal pure returns (bytes2 result) {
if (offset > 10) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(240, not(0)))
}
}
function replace_12_2(bytes12 self, bytes2 value, uint8 offset) internal pure returns (bytes12 result) {
bytes2 oldValue = extract_12_2(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_12_4(bytes12 self, uint8 offset) internal pure returns (bytes4 result) {
if (offset > 8) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(224, not(0)))
}
}
function replace_12_4(bytes12 self, bytes4 value, uint8 offset) internal pure returns (bytes12 result) {
bytes4 oldValue = extract_12_4(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_12_8(bytes12 self, uint8 offset) internal pure returns (bytes8 result) {
if (offset > 4) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(192, not(0)))
}
}
function replace_12_8(bytes12 self, bytes8 value, uint8 offset) internal pure returns (bytes12 result) {
bytes8 oldValue = extract_12_8(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_16_1(bytes16 self, uint8 offset) internal pure returns (bytes1 result) {
if (offset > 15) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(248, not(0)))
}
}
function replace_16_1(bytes16 self, bytes1 value, uint8 offset) internal pure returns (bytes16 result) {
bytes1 oldValue = extract_16_1(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_16_2(bytes16 self, uint8 offset) internal pure returns (bytes2 result) {
if (offset > 14) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(240, not(0)))
}
}
function replace_16_2(bytes16 self, bytes2 value, uint8 offset) internal pure returns (bytes16 result) {
bytes2 oldValue = extract_16_2(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_16_4(bytes16 self, uint8 offset) internal pure returns (bytes4 result) {
if (offset > 12) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(224, not(0)))
}
}
function replace_16_4(bytes16 self, bytes4 value, uint8 offset) internal pure returns (bytes16 result) {
bytes4 oldValue = extract_16_4(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_16_8(bytes16 self, uint8 offset) internal pure returns (bytes8 result) {
if (offset > 8) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(192, not(0)))
}
}
function replace_16_8(bytes16 self, bytes8 value, uint8 offset) internal pure returns (bytes16 result) {
bytes8 oldValue = extract_16_8(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_16_12(bytes16 self, uint8 offset) internal pure returns (bytes12 result) {
if (offset > 4) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(160, not(0)))
}
}
function replace_16_12(bytes16 self, bytes12 value, uint8 offset) internal pure returns (bytes16 result) {
bytes12 oldValue = extract_16_12(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_20_1(bytes20 self, uint8 offset) internal pure returns (bytes1 result) {
if (offset > 19) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(248, not(0)))
}
}
function replace_20_1(bytes20 self, bytes1 value, uint8 offset) internal pure returns (bytes20 result) {
bytes1 oldValue = extract_20_1(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_20_2(bytes20 self, uint8 offset) internal pure returns (bytes2 result) {
if (offset > 18) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(240, not(0)))
}
}
function replace_20_2(bytes20 self, bytes2 value, uint8 offset) internal pure returns (bytes20 result) {
bytes2 oldValue = extract_20_2(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_20_4(bytes20 self, uint8 offset) internal pure returns (bytes4 result) {
if (offset > 16) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(224, not(0)))
}
}
function replace_20_4(bytes20 self, bytes4 value, uint8 offset) internal pure returns (bytes20 result) {
bytes4 oldValue = extract_20_4(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_20_8(bytes20 self, uint8 offset) internal pure returns (bytes8 result) {
if (offset > 12) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(192, not(0)))
}
}
function replace_20_8(bytes20 self, bytes8 value, uint8 offset) internal pure returns (bytes20 result) {
bytes8 oldValue = extract_20_8(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_20_12(bytes20 self, uint8 offset) internal pure returns (bytes12 result) {
if (offset > 8) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(160, not(0)))
}
}
function replace_20_12(bytes20 self, bytes12 value, uint8 offset) internal pure returns (bytes20 result) {
bytes12 oldValue = extract_20_12(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_20_16(bytes20 self, uint8 offset) internal pure returns (bytes16 result) {
if (offset > 4) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(128, not(0)))
}
}
function replace_20_16(bytes20 self, bytes16 value, uint8 offset) internal pure returns (bytes20 result) {
bytes16 oldValue = extract_20_16(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_24_1(bytes24 self, uint8 offset) internal pure returns (bytes1 result) {
if (offset > 23) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(248, not(0)))
}
}
function replace_24_1(bytes24 self, bytes1 value, uint8 offset) internal pure returns (bytes24 result) {
bytes1 oldValue = extract_24_1(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_24_2(bytes24 self, uint8 offset) internal pure returns (bytes2 result) {
if (offset > 22) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(240, not(0)))
}
}
function replace_24_2(bytes24 self, bytes2 value, uint8 offset) internal pure returns (bytes24 result) {
bytes2 oldValue = extract_24_2(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_24_4(bytes24 self, uint8 offset) internal pure returns (bytes4 result) {
if (offset > 20) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(224, not(0)))
}
}
function replace_24_4(bytes24 self, bytes4 value, uint8 offset) internal pure returns (bytes24 result) {
bytes4 oldValue = extract_24_4(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_24_8(bytes24 self, uint8 offset) internal pure returns (bytes8 result) {
if (offset > 16) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(192, not(0)))
}
}
function replace_24_8(bytes24 self, bytes8 value, uint8 offset) internal pure returns (bytes24 result) {
bytes8 oldValue = extract_24_8(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_24_12(bytes24 self, uint8 offset) internal pure returns (bytes12 result) {
if (offset > 12) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(160, not(0)))
}
}
function replace_24_12(bytes24 self, bytes12 value, uint8 offset) internal pure returns (bytes24 result) {
bytes12 oldValue = extract_24_12(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_24_16(bytes24 self, uint8 offset) internal pure returns (bytes16 result) {
if (offset > 8) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(128, not(0)))
}
}
function replace_24_16(bytes24 self, bytes16 value, uint8 offset) internal pure returns (bytes24 result) {
bytes16 oldValue = extract_24_16(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_24_20(bytes24 self, uint8 offset) internal pure returns (bytes20 result) {
if (offset > 4) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(96, not(0)))
}
}
function replace_24_20(bytes24 self, bytes20 value, uint8 offset) internal pure returns (bytes24 result) {
bytes20 oldValue = extract_24_20(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_28_1(bytes28 self, uint8 offset) internal pure returns (bytes1 result) {
if (offset > 27) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(248, not(0)))
}
}
function replace_28_1(bytes28 self, bytes1 value, uint8 offset) internal pure returns (bytes28 result) {
bytes1 oldValue = extract_28_1(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_28_2(bytes28 self, uint8 offset) internal pure returns (bytes2 result) {
if (offset > 26) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(240, not(0)))
}
}
function replace_28_2(bytes28 self, bytes2 value, uint8 offset) internal pure returns (bytes28 result) {
bytes2 oldValue = extract_28_2(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_28_4(bytes28 self, uint8 offset) internal pure returns (bytes4 result) {
if (offset > 24) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(224, not(0)))
}
}
function replace_28_4(bytes28 self, bytes4 value, uint8 offset) internal pure returns (bytes28 result) {
bytes4 oldValue = extract_28_4(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_28_8(bytes28 self, uint8 offset) internal pure returns (bytes8 result) {
if (offset > 20) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(192, not(0)))
}
}
function replace_28_8(bytes28 self, bytes8 value, uint8 offset) internal pure returns (bytes28 result) {
bytes8 oldValue = extract_28_8(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_28_12(bytes28 self, uint8 offset) internal pure returns (bytes12 result) {
if (offset > 16) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(160, not(0)))
}
}
function replace_28_12(bytes28 self, bytes12 value, uint8 offset) internal pure returns (bytes28 result) {
bytes12 oldValue = extract_28_12(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_28_16(bytes28 self, uint8 offset) internal pure returns (bytes16 result) {
if (offset > 12) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(128, not(0)))
}
}
function replace_28_16(bytes28 self, bytes16 value, uint8 offset) internal pure returns (bytes28 result) {
bytes16 oldValue = extract_28_16(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_28_20(bytes28 self, uint8 offset) internal pure returns (bytes20 result) {
if (offset > 8) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(96, not(0)))
}
}
function replace_28_20(bytes28 self, bytes20 value, uint8 offset) internal pure returns (bytes28 result) {
bytes20 oldValue = extract_28_20(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_28_24(bytes28 self, uint8 offset) internal pure returns (bytes24 result) {
if (offset > 4) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(64, not(0)))
}
}
function replace_28_24(bytes28 self, bytes24 value, uint8 offset) internal pure returns (bytes28 result) {
bytes24 oldValue = extract_28_24(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_32_1(bytes32 self, uint8 offset) internal pure returns (bytes1 result) {
if (offset > 31) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(248, not(0)))
}
}
function replace_32_1(bytes32 self, bytes1 value, uint8 offset) internal pure returns (bytes32 result) {
bytes1 oldValue = extract_32_1(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_32_2(bytes32 self, uint8 offset) internal pure returns (bytes2 result) {
if (offset > 30) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(240, not(0)))
}
}
function replace_32_2(bytes32 self, bytes2 value, uint8 offset) internal pure returns (bytes32 result) {
bytes2 oldValue = extract_32_2(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_32_4(bytes32 self, uint8 offset) internal pure returns (bytes4 result) {
if (offset > 28) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(224, not(0)))
}
}
function replace_32_4(bytes32 self, bytes4 value, uint8 offset) internal pure returns (bytes32 result) {
bytes4 oldValue = extract_32_4(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_32_8(bytes32 self, uint8 offset) internal pure returns (bytes8 result) {
if (offset > 24) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(192, not(0)))
}
}
function replace_32_8(bytes32 self, bytes8 value, uint8 offset) internal pure returns (bytes32 result) {
bytes8 oldValue = extract_32_8(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_32_12(bytes32 self, uint8 offset) internal pure returns (bytes12 result) {
if (offset > 20) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(160, not(0)))
}
}
function replace_32_12(bytes32 self, bytes12 value, uint8 offset) internal pure returns (bytes32 result) {
bytes12 oldValue = extract_32_12(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_32_16(bytes32 self, uint8 offset) internal pure returns (bytes16 result) {
if (offset > 16) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(128, not(0)))
}
}
function replace_32_16(bytes32 self, bytes16 value, uint8 offset) internal pure returns (bytes32 result) {
bytes16 oldValue = extract_32_16(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_32_20(bytes32 self, uint8 offset) internal pure returns (bytes20 result) {
if (offset > 12) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(96, not(0)))
}
}
function replace_32_20(bytes32 self, bytes20 value, uint8 offset) internal pure returns (bytes32 result) {
bytes20 oldValue = extract_32_20(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_32_24(bytes32 self, uint8 offset) internal pure returns (bytes24 result) {
if (offset > 8) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(64, not(0)))
}
}
function replace_32_24(bytes32 self, bytes24 value, uint8 offset) internal pure returns (bytes32 result) {
bytes24 oldValue = extract_32_24(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
function extract_32_28(bytes32 self, uint8 offset) internal pure returns (bytes28 result) {
if (offset > 4) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(32, not(0)))
}
}
function replace_32_28(bytes32 self, bytes28 value, uint8 offset) internal pure returns (bytes32 result) {
bytes28 oldValue = extract_32_28(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}
}

@ -177,6 +177,36 @@ function push(bytes32 leaf) public /* onlyOwner */ {
[[misc]]
== Misc
=== Packing
The storage in the EVM is shaped in chunks of 32 bytes, each of this chunks is known as _slot_, and can hold multiple values together as long as these values don't exceed its size. These properties of the storage allows for a technique known as _packing_, that consists of placing values together on a single storage slot to reduce the costs associated to reading and writing to multiple slots instead of just one.
Commonly, developers pack values using structs that place values together so they fit better in storage. However, this approach requires to load such struct from either calldata or memory. Although sometimes necessary, it may be useful to pack values in a single slot and treat it as a packed value without involving calldata or memory.
The xref:api:utils.adoc#Packing[`Packing`] library is a set of utilities for packing values that fit in 32 bytes. The library includes 3 main functionalities:
* Packing 2 `bytesXX` values
* Extracting a packed `bytesXX` value from a `bytesYY`
* Replacing a packed `bytesXX` value from a `bytesYY`
With these primitives, one can build custom functions to create custom packed types. For example, suppose you need to pack an `address` of 20 bytes with a `bytes4` selector and an `uint64` time period:
[source,solidity]
----
function _pack(address account, bytes4 selector, uint64 period) external pure returns (bytes32) {
bytes12 subpack = Packing.pack_4_8(selector, bytes8(period));
return Packing.pack_20_12(bytes20(account), subpack);
}
function _unpack(bytes32 pack) external pure returns (address, bytes4, uint64) {
return (
address(Packing.extract_32_20(pack, 0)),
Packing.extract_32_4(pack, 20),
uint64(Packing.extract_32_8(pack, 24))
);
}
----
=== Storage Slots
Solidity allocates a storage pointer for each variable declared in a contract. However, there are cases when it's required to access storage pointers that can't be derived by using regular Solidity.

@ -39,6 +39,7 @@ for (const [file, template] of Object.entries({
'utils/SlotDerivation.sol': './templates/SlotDerivation.js',
'utils/StorageSlot.sol': './templates/StorageSlot.js',
'utils/Arrays.sol': './templates/Arrays.js',
'utils/Packing.sol': './templates/Packing.js',
'mocks/StorageSlotMock.sol': './templates/StorageSlotMock.js',
})) {
generateFromTemplate(file, template, './contracts/');
@ -47,6 +48,7 @@ for (const [file, template] of Object.entries({
// Tests
for (const [file, template] of Object.entries({
'utils/structs/Checkpoints.t.sol': './templates/Checkpoints.t.js',
'utils/Packing.t.sol': './templates/Packing.t.js',
'utils/SlotDerivation.t.sol': './templates/SlotDerivation.t.js',
})) {
generateFromTemplate(file, template, './test/');

@ -0,0 +1,78 @@
const format = require('../format-lines');
const { product } = require('../../helpers');
const { SIZES } = require('./Packing.opts');
// TEMPLATE
const header = `\
pragma solidity ^0.8.20;
/**
* @dev Helper library packing and unpacking multiple values into bytesXX.
*
* Example usage:
*
* \`\`\`solidity
* library MyPacker {
* type MyType is bytes32;
*
* function _pack(address account, bytes4 selector, uint64 period) external pure returns (MyType) {
* bytes12 subpack = Packing.pack_4_8(selector, bytes8(period));
* bytes32 pack = Packing.pack_20_12(bytes20(account), subpack);
* return MyType.wrap(pack);
* }
*
* function _unpack(MyType self) external pure returns (address, bytes4, uint64) {
* bytes32 pack = MyType.unwrap(self);
* return (
* address(Packing.extract_32_20(pack, 0)),
* Packing.extract_32_4(pack, 20),
* uint64(Packing.extract_32_8(pack, 24))
* );
* }
* }
* \`\`\`
*/
// solhint-disable func-name-mixedcase
`;
const errors = `\
error OutOfRangeAccess();`;
const pack = (left, right) => `
function pack_${left}_${right}(bytes${left} left, bytes${right} right) internal pure returns (bytes${
left + right
} result) {
assembly ("memory-safe") {
result := or(left, shr(${8 * left}, right))
}
}`;
const extract = (outer, inner) => `
function extract_${outer}_${inner}(bytes${outer} self, uint8 offset) internal pure returns (bytes${inner} result) {
if (offset > ${outer - inner}) revert OutOfRangeAccess();
assembly ("memory-safe") {
result := and(shl(mul(8, offset), self), shl(${256 - 8 * inner}, not(0)))
}
}`;
const replace = (outer, inner) => `
function replace_${outer}_${inner}(bytes${outer} self, bytes${inner} value, uint8 offset) internal pure returns (bytes${outer} result) {
bytes${inner} oldValue = extract_${outer}_${inner}(self, offset);
assembly ("memory-safe") {
result := xor(self, shr(mul(8, offset), xor(oldValue, value)))
}
}`;
// GENERATE
module.exports = format(
header.trimEnd(),
'library Packing {',
errors,
product(SIZES, SIZES)
.filter(([left, right]) => SIZES.includes(left + right))
.map(([left, right]) => pack(left, right)),
product(SIZES, SIZES)
.filter(([outer, inner]) => outer > inner)
.flatMap(([outer, inner]) => [extract(outer, inner), replace(outer, inner)]),
'}',
);

@ -0,0 +1,5 @@
const { range } = require('../../helpers');
const SIZES = range(1, 33).filter(size => size == 1 || size == 2 || size % 4 == 0);
module.exports = { SIZES };

@ -0,0 +1,42 @@
const format = require('../format-lines');
const { product } = require('../../helpers');
const { SIZES } = require('./Packing.opts');
// TEMPLATE
const header = `\
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {Packing} from "@openzeppelin/contracts/utils/Packing.sol";
`;
const testPack = (left, right) => `
function testPack(bytes${left} left, bytes${right} right) external {
assertEq(left, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${left}(0));
assertEq(right, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${right}(${left}));
}`;
const testReplace = (outer, inner) => `
function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, ${outer - inner}));
bytes${inner} oldValue = container.extract_${outer}_${inner}(offset);
assertEq(newValue, container.replace_${outer}_${inner}(newValue, offset).extract_${outer}_${inner}(offset));
assertEq(container, container.replace_${outer}_${inner}(newValue, offset).replace_${outer}_${inner}(oldValue, offset));
}`;
// GENERATE
module.exports = format(
header.trimEnd(),
'',
'contract PackingTest is Test {',
' using Packing for *;',
product(SIZES, SIZES)
.filter(([left, right]) => SIZES.includes(left + right))
.map(([left, right]) => testPack(left, right)),
product(SIZES, SIZES)
.filter(([outer, inner]) => outer > inner)
.map(([outer, inner]) => testReplace(outer, inner)),
'}',
);

@ -0,0 +1,14 @@
const { artifacts, ethers } = require('hardhat');
const { setCode } = require('@nomicfoundation/hardhat-network-helpers');
const { generators } = require('./random');
const forceDeployCode = (name, address = generators.address(), runner = ethers.provider) =>
artifacts
.readArtifact(name)
.then(({ abi, deployedBytecode }) =>
setCode(address, deployedBytecode).then(() => new ethers.Contract(address, abi, runner)),
);
module.exports = {
forceDeployCode,
};

@ -1,4 +1,5 @@
// SPDX-License-Identifier: MIT
// This file was procedurally generated from scripts/generate/templates/Packing.t.js.
pragma solidity ^0.8.20;
@ -8,20 +9,558 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol";
contract PackingTest is Test {
using Packing for *;
// Pack a pair of arbitrary uint128, and check that split recovers the correct values
function testSymbolicUint128x2(uint128 first, uint128 second) external {
Packing.Uint128x2 packed = Packing.pack(first, second);
assertEq(packed.first(), first);
assertEq(packed.second(), second);
function testPack(bytes1 left, bytes1 right) external {
assertEq(left, Packing.pack_1_1(left, right).extract_2_1(0));
assertEq(right, Packing.pack_1_1(left, right).extract_2_1(1));
}
function testPack(bytes2 left, bytes2 right) external {
assertEq(left, Packing.pack_2_2(left, right).extract_4_2(0));
assertEq(right, Packing.pack_2_2(left, right).extract_4_2(2));
}
function testPack(bytes4 left, bytes4 right) external {
assertEq(left, Packing.pack_4_4(left, right).extract_8_4(0));
assertEq(right, Packing.pack_4_4(left, right).extract_8_4(4));
}
function testPack(bytes4 left, bytes8 right) external {
assertEq(left, Packing.pack_4_8(left, right).extract_12_4(0));
assertEq(right, Packing.pack_4_8(left, right).extract_12_8(4));
}
function testPack(bytes4 left, bytes12 right) external {
assertEq(left, Packing.pack_4_12(left, right).extract_16_4(0));
assertEq(right, Packing.pack_4_12(left, right).extract_16_12(4));
}
function testPack(bytes4 left, bytes16 right) external {
assertEq(left, Packing.pack_4_16(left, right).extract_20_4(0));
assertEq(right, Packing.pack_4_16(left, right).extract_20_16(4));
}
function testPack(bytes4 left, bytes20 right) external {
assertEq(left, Packing.pack_4_20(left, right).extract_24_4(0));
assertEq(right, Packing.pack_4_20(left, right).extract_24_20(4));
}
function testPack(bytes4 left, bytes24 right) external {
assertEq(left, Packing.pack_4_24(left, right).extract_28_4(0));
assertEq(right, Packing.pack_4_24(left, right).extract_28_24(4));
}
function testPack(bytes4 left, bytes28 right) external {
assertEq(left, Packing.pack_4_28(left, right).extract_32_4(0));
assertEq(right, Packing.pack_4_28(left, right).extract_32_28(4));
}
function testPack(bytes8 left, bytes4 right) external {
assertEq(left, Packing.pack_8_4(left, right).extract_12_8(0));
assertEq(right, Packing.pack_8_4(left, right).extract_12_4(8));
}
function testPack(bytes8 left, bytes8 right) external {
assertEq(left, Packing.pack_8_8(left, right).extract_16_8(0));
assertEq(right, Packing.pack_8_8(left, right).extract_16_8(8));
}
function testPack(bytes8 left, bytes12 right) external {
assertEq(left, Packing.pack_8_12(left, right).extract_20_8(0));
assertEq(right, Packing.pack_8_12(left, right).extract_20_12(8));
}
function testPack(bytes8 left, bytes16 right) external {
assertEq(left, Packing.pack_8_16(left, right).extract_24_8(0));
assertEq(right, Packing.pack_8_16(left, right).extract_24_16(8));
}
function testPack(bytes8 left, bytes20 right) external {
assertEq(left, Packing.pack_8_20(left, right).extract_28_8(0));
assertEq(right, Packing.pack_8_20(left, right).extract_28_20(8));
}
function testPack(bytes8 left, bytes24 right) external {
assertEq(left, Packing.pack_8_24(left, right).extract_32_8(0));
assertEq(right, Packing.pack_8_24(left, right).extract_32_24(8));
}
function testPack(bytes12 left, bytes4 right) external {
assertEq(left, Packing.pack_12_4(left, right).extract_16_12(0));
assertEq(right, Packing.pack_12_4(left, right).extract_16_4(12));
}
function testPack(bytes12 left, bytes8 right) external {
assertEq(left, Packing.pack_12_8(left, right).extract_20_12(0));
assertEq(right, Packing.pack_12_8(left, right).extract_20_8(12));
}
function testPack(bytes12 left, bytes12 right) external {
assertEq(left, Packing.pack_12_12(left, right).extract_24_12(0));
assertEq(right, Packing.pack_12_12(left, right).extract_24_12(12));
}
function testPack(bytes12 left, bytes16 right) external {
assertEq(left, Packing.pack_12_16(left, right).extract_28_12(0));
assertEq(right, Packing.pack_12_16(left, right).extract_28_16(12));
}
function testPack(bytes12 left, bytes20 right) external {
assertEq(left, Packing.pack_12_20(left, right).extract_32_12(0));
assertEq(right, Packing.pack_12_20(left, right).extract_32_20(12));
}
function testPack(bytes16 left, bytes4 right) external {
assertEq(left, Packing.pack_16_4(left, right).extract_20_16(0));
assertEq(right, Packing.pack_16_4(left, right).extract_20_4(16));
}
function testPack(bytes16 left, bytes8 right) external {
assertEq(left, Packing.pack_16_8(left, right).extract_24_16(0));
assertEq(right, Packing.pack_16_8(left, right).extract_24_8(16));
}
function testPack(bytes16 left, bytes12 right) external {
assertEq(left, Packing.pack_16_12(left, right).extract_28_16(0));
assertEq(right, Packing.pack_16_12(left, right).extract_28_12(16));
}
function testPack(bytes16 left, bytes16 right) external {
assertEq(left, Packing.pack_16_16(left, right).extract_32_16(0));
assertEq(right, Packing.pack_16_16(left, right).extract_32_16(16));
}
function testPack(bytes20 left, bytes4 right) external {
assertEq(left, Packing.pack_20_4(left, right).extract_24_20(0));
assertEq(right, Packing.pack_20_4(left, right).extract_24_4(20));
}
function testPack(bytes20 left, bytes8 right) external {
assertEq(left, Packing.pack_20_8(left, right).extract_28_20(0));
assertEq(right, Packing.pack_20_8(left, right).extract_28_8(20));
}
function testPack(bytes20 left, bytes12 right) external {
assertEq(left, Packing.pack_20_12(left, right).extract_32_20(0));
assertEq(right, Packing.pack_20_12(left, right).extract_32_12(20));
}
function testPack(bytes24 left, bytes4 right) external {
assertEq(left, Packing.pack_24_4(left, right).extract_28_24(0));
assertEq(right, Packing.pack_24_4(left, right).extract_28_4(24));
}
function testPack(bytes24 left, bytes8 right) external {
assertEq(left, Packing.pack_24_8(left, right).extract_32_24(0));
assertEq(right, Packing.pack_24_8(left, right).extract_32_8(24));
}
function testPack(bytes28 left, bytes4 right) external {
assertEq(left, Packing.pack_28_4(left, right).extract_32_28(0));
assertEq(right, Packing.pack_28_4(left, right).extract_32_4(28));
}
(uint128 recoveredFirst, uint128 recoveredSecond) = packed.split();
assertEq(recoveredFirst, first);
assertEq(recoveredSecond, second);
function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 1));
bytes1 oldValue = container.extract_2_1(offset);
assertEq(newValue, container.replace_2_1(newValue, offset).extract_2_1(offset));
assertEq(container, container.replace_2_1(newValue, offset).replace_2_1(oldValue, offset));
}
function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 3));
bytes1 oldValue = container.extract_4_1(offset);
assertEq(newValue, container.replace_4_1(newValue, offset).extract_4_1(offset));
assertEq(container, container.replace_4_1(newValue, offset).replace_4_1(oldValue, offset));
}
function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 2));
bytes2 oldValue = container.extract_4_2(offset);
assertEq(newValue, container.replace_4_2(newValue, offset).extract_4_2(offset));
assertEq(container, container.replace_4_2(newValue, offset).replace_4_2(oldValue, offset));
}
function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 7));
bytes1 oldValue = container.extract_8_1(offset);
assertEq(newValue, container.replace_8_1(newValue, offset).extract_8_1(offset));
assertEq(container, container.replace_8_1(newValue, offset).replace_8_1(oldValue, offset));
}
function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 6));
bytes2 oldValue = container.extract_8_2(offset);
assertEq(newValue, container.replace_8_2(newValue, offset).extract_8_2(offset));
assertEq(container, container.replace_8_2(newValue, offset).replace_8_2(oldValue, offset));
}
function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 4));
bytes4 oldValue = container.extract_8_4(offset);
assertEq(newValue, container.replace_8_4(newValue, offset).extract_8_4(offset));
assertEq(container, container.replace_8_4(newValue, offset).replace_8_4(oldValue, offset));
}
function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 11));
bytes1 oldValue = container.extract_12_1(offset);
assertEq(newValue, container.replace_12_1(newValue, offset).extract_12_1(offset));
assertEq(container, container.replace_12_1(newValue, offset).replace_12_1(oldValue, offset));
}
function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 10));
bytes2 oldValue = container.extract_12_2(offset);
assertEq(newValue, container.replace_12_2(newValue, offset).extract_12_2(offset));
assertEq(container, container.replace_12_2(newValue, offset).replace_12_2(oldValue, offset));
}
// split an arbitrary bytes32 into a pair of uint128, and check that repack matches the input
function testSymbolicUint128x2(bytes32 input) external {
(uint128 first, uint128 second) = input.asUint128x2().split();
assertEq(Packing.pack(first, second).asBytes32(), input);
function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 8));
bytes4 oldValue = container.extract_12_4(offset);
assertEq(newValue, container.replace_12_4(newValue, offset).extract_12_4(offset));
assertEq(container, container.replace_12_4(newValue, offset).replace_12_4(oldValue, offset));
}
function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 4));
bytes8 oldValue = container.extract_12_8(offset);
assertEq(newValue, container.replace_12_8(newValue, offset).extract_12_8(offset));
assertEq(container, container.replace_12_8(newValue, offset).replace_12_8(oldValue, offset));
}
function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 15));
bytes1 oldValue = container.extract_16_1(offset);
assertEq(newValue, container.replace_16_1(newValue, offset).extract_16_1(offset));
assertEq(container, container.replace_16_1(newValue, offset).replace_16_1(oldValue, offset));
}
function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 14));
bytes2 oldValue = container.extract_16_2(offset);
assertEq(newValue, container.replace_16_2(newValue, offset).extract_16_2(offset));
assertEq(container, container.replace_16_2(newValue, offset).replace_16_2(oldValue, offset));
}
function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 12));
bytes4 oldValue = container.extract_16_4(offset);
assertEq(newValue, container.replace_16_4(newValue, offset).extract_16_4(offset));
assertEq(container, container.replace_16_4(newValue, offset).replace_16_4(oldValue, offset));
}
function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 8));
bytes8 oldValue = container.extract_16_8(offset);
assertEq(newValue, container.replace_16_8(newValue, offset).extract_16_8(offset));
assertEq(container, container.replace_16_8(newValue, offset).replace_16_8(oldValue, offset));
}
function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 4));
bytes12 oldValue = container.extract_16_12(offset);
assertEq(newValue, container.replace_16_12(newValue, offset).extract_16_12(offset));
assertEq(container, container.replace_16_12(newValue, offset).replace_16_12(oldValue, offset));
}
function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 19));
bytes1 oldValue = container.extract_20_1(offset);
assertEq(newValue, container.replace_20_1(newValue, offset).extract_20_1(offset));
assertEq(container, container.replace_20_1(newValue, offset).replace_20_1(oldValue, offset));
}
function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 18));
bytes2 oldValue = container.extract_20_2(offset);
assertEq(newValue, container.replace_20_2(newValue, offset).extract_20_2(offset));
assertEq(container, container.replace_20_2(newValue, offset).replace_20_2(oldValue, offset));
}
function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 16));
bytes4 oldValue = container.extract_20_4(offset);
assertEq(newValue, container.replace_20_4(newValue, offset).extract_20_4(offset));
assertEq(container, container.replace_20_4(newValue, offset).replace_20_4(oldValue, offset));
}
function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 12));
bytes8 oldValue = container.extract_20_8(offset);
assertEq(newValue, container.replace_20_8(newValue, offset).extract_20_8(offset));
assertEq(container, container.replace_20_8(newValue, offset).replace_20_8(oldValue, offset));
}
function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 8));
bytes12 oldValue = container.extract_20_12(offset);
assertEq(newValue, container.replace_20_12(newValue, offset).extract_20_12(offset));
assertEq(container, container.replace_20_12(newValue, offset).replace_20_12(oldValue, offset));
}
function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 4));
bytes16 oldValue = container.extract_20_16(offset);
assertEq(newValue, container.replace_20_16(newValue, offset).extract_20_16(offset));
assertEq(container, container.replace_20_16(newValue, offset).replace_20_16(oldValue, offset));
}
function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 23));
bytes1 oldValue = container.extract_24_1(offset);
assertEq(newValue, container.replace_24_1(newValue, offset).extract_24_1(offset));
assertEq(container, container.replace_24_1(newValue, offset).replace_24_1(oldValue, offset));
}
function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 22));
bytes2 oldValue = container.extract_24_2(offset);
assertEq(newValue, container.replace_24_2(newValue, offset).extract_24_2(offset));
assertEq(container, container.replace_24_2(newValue, offset).replace_24_2(oldValue, offset));
}
function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 20));
bytes4 oldValue = container.extract_24_4(offset);
assertEq(newValue, container.replace_24_4(newValue, offset).extract_24_4(offset));
assertEq(container, container.replace_24_4(newValue, offset).replace_24_4(oldValue, offset));
}
function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 16));
bytes8 oldValue = container.extract_24_8(offset);
assertEq(newValue, container.replace_24_8(newValue, offset).extract_24_8(offset));
assertEq(container, container.replace_24_8(newValue, offset).replace_24_8(oldValue, offset));
}
function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 12));
bytes12 oldValue = container.extract_24_12(offset);
assertEq(newValue, container.replace_24_12(newValue, offset).extract_24_12(offset));
assertEq(container, container.replace_24_12(newValue, offset).replace_24_12(oldValue, offset));
}
function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 8));
bytes16 oldValue = container.extract_24_16(offset);
assertEq(newValue, container.replace_24_16(newValue, offset).extract_24_16(offset));
assertEq(container, container.replace_24_16(newValue, offset).replace_24_16(oldValue, offset));
}
function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 4));
bytes20 oldValue = container.extract_24_20(offset);
assertEq(newValue, container.replace_24_20(newValue, offset).extract_24_20(offset));
assertEq(container, container.replace_24_20(newValue, offset).replace_24_20(oldValue, offset));
}
function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 27));
bytes1 oldValue = container.extract_28_1(offset);
assertEq(newValue, container.replace_28_1(newValue, offset).extract_28_1(offset));
assertEq(container, container.replace_28_1(newValue, offset).replace_28_1(oldValue, offset));
}
function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 26));
bytes2 oldValue = container.extract_28_2(offset);
assertEq(newValue, container.replace_28_2(newValue, offset).extract_28_2(offset));
assertEq(container, container.replace_28_2(newValue, offset).replace_28_2(oldValue, offset));
}
function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 24));
bytes4 oldValue = container.extract_28_4(offset);
assertEq(newValue, container.replace_28_4(newValue, offset).extract_28_4(offset));
assertEq(container, container.replace_28_4(newValue, offset).replace_28_4(oldValue, offset));
}
function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 20));
bytes8 oldValue = container.extract_28_8(offset);
assertEq(newValue, container.replace_28_8(newValue, offset).extract_28_8(offset));
assertEq(container, container.replace_28_8(newValue, offset).replace_28_8(oldValue, offset));
}
function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 16));
bytes12 oldValue = container.extract_28_12(offset);
assertEq(newValue, container.replace_28_12(newValue, offset).extract_28_12(offset));
assertEq(container, container.replace_28_12(newValue, offset).replace_28_12(oldValue, offset));
}
function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 12));
bytes16 oldValue = container.extract_28_16(offset);
assertEq(newValue, container.replace_28_16(newValue, offset).extract_28_16(offset));
assertEq(container, container.replace_28_16(newValue, offset).replace_28_16(oldValue, offset));
}
function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 8));
bytes20 oldValue = container.extract_28_20(offset);
assertEq(newValue, container.replace_28_20(newValue, offset).extract_28_20(offset));
assertEq(container, container.replace_28_20(newValue, offset).replace_28_20(oldValue, offset));
}
function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 4));
bytes24 oldValue = container.extract_28_24(offset);
assertEq(newValue, container.replace_28_24(newValue, offset).extract_28_24(offset));
assertEq(container, container.replace_28_24(newValue, offset).replace_28_24(oldValue, offset));
}
function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 31));
bytes1 oldValue = container.extract_32_1(offset);
assertEq(newValue, container.replace_32_1(newValue, offset).extract_32_1(offset));
assertEq(container, container.replace_32_1(newValue, offset).replace_32_1(oldValue, offset));
}
function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 30));
bytes2 oldValue = container.extract_32_2(offset);
assertEq(newValue, container.replace_32_2(newValue, offset).extract_32_2(offset));
assertEq(container, container.replace_32_2(newValue, offset).replace_32_2(oldValue, offset));
}
function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 28));
bytes4 oldValue = container.extract_32_4(offset);
assertEq(newValue, container.replace_32_4(newValue, offset).extract_32_4(offset));
assertEq(container, container.replace_32_4(newValue, offset).replace_32_4(oldValue, offset));
}
function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 24));
bytes8 oldValue = container.extract_32_8(offset);
assertEq(newValue, container.replace_32_8(newValue, offset).extract_32_8(offset));
assertEq(container, container.replace_32_8(newValue, offset).replace_32_8(oldValue, offset));
}
function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 20));
bytes12 oldValue = container.extract_32_12(offset);
assertEq(newValue, container.replace_32_12(newValue, offset).extract_32_12(offset));
assertEq(container, container.replace_32_12(newValue, offset).replace_32_12(oldValue, offset));
}
function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 16));
bytes16 oldValue = container.extract_32_16(offset);
assertEq(newValue, container.replace_32_16(newValue, offset).extract_32_16(offset));
assertEq(container, container.replace_32_16(newValue, offset).replace_32_16(oldValue, offset));
}
function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 12));
bytes20 oldValue = container.extract_32_20(offset);
assertEq(newValue, container.replace_32_20(newValue, offset).extract_32_20(offset));
assertEq(container, container.replace_32_20(newValue, offset).replace_32_20(oldValue, offset));
}
function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 8));
bytes24 oldValue = container.extract_32_24(offset);
assertEq(newValue, container.replace_32_24(newValue, offset).extract_32_24(offset));
assertEq(container, container.replace_32_24(newValue, offset).replace_32_24(oldValue, offset));
}
function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external {
offset = uint8(bound(offset, 0, 4));
bytes28 oldValue = container.extract_32_28(offset);
assertEq(newValue, container.replace_32_28(newValue, offset).extract_32_28(offset));
assertEq(container, container.replace_32_28(newValue, offset).replace_32_28(oldValue, offset));
}
}

@ -1,10 +1,13 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { generators } = require('../helpers/random');
const { forceDeployCode } = require('../helpers/deploy');
const { product } = require('../helpers/iterate');
const { SIZES } = require('../../scripts/generate/templates/Packing.opts');
async function fixture() {
return { mock: await ethers.deployContract('$Packing') };
return { mock: await forceDeployCode('$Packing') };
}
describe('Packing', function () {
@ -12,16 +15,56 @@ describe('Packing', function () {
Object.assign(this, await loadFixture(fixture));
});
it('Uint128x2', async function () {
const first = generators.uint256() % 2n ** 128n;
const second = generators.uint256() % 2n ** 128n;
const packed = ethers.hexlify(ethers.toBeArray((first << 128n) | second));
expect(await this.mock.$asUint128x2(packed)).to.equal(packed);
expect(await this.mock.$asBytes32(packed)).to.equal(packed);
expect(await this.mock.$pack(first, second)).to.equal(packed);
expect(await this.mock.$split(packed)).to.deep.equal([first, second]);
expect(await this.mock.$first(packed)).to.equal(first);
expect(await this.mock.$second(packed)).to.equal(second);
describe('pack', function () {
for (const [size1, size2] of product(SIZES, SIZES).filter(([size1, size2]) => SIZES.includes(size1 + size2))) {
const value1 = ethers.hexlify(ethers.randomBytes(size1));
const value2 = ethers.hexlify(ethers.randomBytes(size2));
const packed = ethers.concat([value1, value2]);
it(`pack bytes${size1} + bytes${size2} => bytes${size1 + size2}`, async function () {
expect(await this.mock[`$pack_${size1}_${size2}`](value1, value2)).to.equal(packed);
expect(await this.mock[`$extract_${size1 + size2}_${size1}`](packed, 0)).to.equal(value1);
expect(await this.mock[`$extract_${size1 + size2}_${size2}`](packed, size1)).to.equal(value2);
});
}
});
describe('extract / replace', function () {
for (const [size1, size2] of product(SIZES, SIZES).filter(([size1, size2]) => size1 > size2)) {
const MAX_OFFSET = size1 - size2;
const offset = ethers.toNumber(ethers.randomBytes(1)) % (MAX_OFFSET + 1);
const outer = ethers.randomBytes(size1);
const value = ethers.randomBytes(size2);
it(`extract bytes${size2} from bytes${size1}`, async function () {
expect(await this.mock[`$extract_${size1}_${size2}`](outer, offset)).to.equal(
ethers.hexlify(outer.slice(offset, offset + size2)),
);
await expect(this.mock[`$extract_${size1}_${size2}`](outer, MAX_OFFSET)).to.not.be.revertedWithCustomError(
this.mock,
'OutOfRangeAccess',
);
await expect(this.mock[`$extract_${size1}_${size2}`](outer, MAX_OFFSET + 1)).to.be.revertedWithCustomError(
this.mock,
'OutOfRangeAccess',
);
});
it(`replace bytes${size2} from bytes${size1}`, async function () {
expect(await this.mock[`$replace_${size1}_${size2}`](outer, value, offset)).to.equal(
ethers.concat([outer.slice(0, offset), value, outer.slice(offset + size2)]),
);
await expect(
this.mock[`$replace_${size1}_${size2}`](outer, value, MAX_OFFSET),
).to.not.be.revertedWithCustomError(this.mock, 'OutOfRangeAccess');
await expect(
this.mock[`$replace_${size1}_${size2}`](outer, value, MAX_OFFSET + 1),
).to.be.revertedWithCustomError(this.mock, 'OutOfRangeAccess');
});
}
});
});

Loading…
Cancel
Save