Skip to content

Commit f8c2e10

Browse files
Amxxernestognw
andauthored
Add Packing library (#4992)
Co-authored-by: ernestognw <ernestognw@gmail.com>
1 parent 90fd7cc commit f8c2e10

File tree

6 files changed

+103
-0
lines changed

6 files changed

+103
-0
lines changed

.changeset/heavy-baboons-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`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.

contracts/mocks/Stateless.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol";
2424
import {Math} from "../utils/math/Math.sol";
2525
import {MerkleProof} from "../utils/cryptography/MerkleProof.sol";
2626
import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol";
27+
import {Packing} from "../utils/Packing.sol";
2728
import {SafeCast} from "../utils/math/SafeCast.sol";
2829
import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol";
2930
import {ShortStrings} from "../utils/ShortStrings.sol";

contracts/utils/Packing.sol

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
/**
6+
* @dev Helper library packing and unpacking multiple values into bytes32
7+
*/
8+
library Packing {
9+
type Uint128x2 is bytes32;
10+
11+
/// @dev Cast a bytes32 into a Uint128x2
12+
function asUint128x2(bytes32 self) internal pure returns (Uint128x2) {
13+
return Uint128x2.wrap(self);
14+
}
15+
16+
/// @dev Cast a Uint128x2 into a bytes32
17+
function asBytes32(Uint128x2 self) internal pure returns (bytes32) {
18+
return Uint128x2.unwrap(self);
19+
}
20+
21+
/// @dev Pack two uint128 into a Uint128x2
22+
function pack(uint128 first128, uint128 second128) internal pure returns (Uint128x2) {
23+
return Uint128x2.wrap(bytes32(bytes16(first128)) | bytes32(uint256(second128)));
24+
}
25+
26+
/// @dev Split a Uint128x2 into two uint128
27+
function split(Uint128x2 self) internal pure returns (uint128, uint128) {
28+
return (first(self), second(self));
29+
}
30+
31+
/// @dev Get the first element of a Uint128x2 counting from higher to lower bytes
32+
function first(Uint128x2 self) internal pure returns (uint128) {
33+
return uint128(bytes16(Uint128x2.unwrap(self)));
34+
}
35+
36+
/// @dev Get the second element of a Uint128x2 counting from higher to lower bytes
37+
function second(Uint128x2 self) internal pure returns (uint128) {
38+
return uint128(uint256(Uint128x2.unwrap(self)));
39+
}
40+
}

contracts/utils/README.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
3333
* {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. Also include primitives for reading from and writing to transient storage (only value types are currently supported).
3434
* {Multicall}: Abstract contract with an utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once.
3535
* {Context}: An utility for abstracting the sender and calldata in the current execution context.
36+
* {Packing}: A library for packing and unpacking multiple values into bytes32
3637
* {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes].
3738

3839
[NOTE]
@@ -120,4 +121,6 @@ Ethereum contracts have no native concept of an interface, so applications must
120121

121122
{{Context}}
122123

124+
{{Packing}}
125+
123126
{{Panic}}

test/utils/Packing.t.sol

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {Test} from "forge-std/Test.sol";
6+
import {Packing} from "@openzeppelin/contracts/utils/Packing.sol";
7+
8+
contract PackingTest is Test {
9+
using Packing for *;
10+
11+
// Pack a pair of arbitrary uint128, and check that split recovers the correct values
12+
function testUint128x2(uint128 first, uint128 second) external {
13+
Packing.Uint128x2 packed = Packing.pack(first, second);
14+
assertEq(packed.first(), first);
15+
assertEq(packed.second(), second);
16+
17+
(uint128 recoveredFirst, uint128 recoveredSecond) = packed.split();
18+
assertEq(recoveredFirst, first);
19+
assertEq(recoveredSecond, second);
20+
}
21+
22+
// split an arbitrary bytes32 into a pair of uint128, and check that repack matches the input
23+
function testUint128x2(bytes32 input) external {
24+
(uint128 first, uint128 second) = input.asUint128x2().split();
25+
assertEq(Packing.pack(first, second).asBytes32(), input);
26+
}
27+
}

test/utils/Packing.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const { ethers } = require('hardhat');
2+
const { expect } = require('chai');
3+
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
4+
const { generators } = require('../helpers/random');
5+
6+
async function fixture() {
7+
return { mock: await ethers.deployContract('$Packing') };
8+
}
9+
10+
describe('Packing', function () {
11+
beforeEach(async function () {
12+
Object.assign(this, await loadFixture(fixture));
13+
});
14+
15+
it('Uint128x2', async function () {
16+
const first = generators.uint256() % 2n ** 128n;
17+
const second = generators.uint256() % 2n ** 128n;
18+
const packed = ethers.hexlify(ethers.toBeArray((first << 128n) | second));
19+
20+
expect(await this.mock.$asUint128x2(packed)).to.equal(packed);
21+
expect(await this.mock.$asBytes32(packed)).to.equal(packed);
22+
expect(await this.mock.$pack(first, second)).to.equal(packed);
23+
expect(await this.mock.$split(packed)).to.deep.equal([first, second]);
24+
expect(await this.mock.$first(packed)).to.equal(first);
25+
expect(await this.mock.$second(packed)).to.equal(second);
26+
});
27+
});

0 commit comments

Comments
 (0)