diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index 0b5fbc88c..a5a38b503 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -45,6 +45,24 @@ library Packing { } } + function pack_2_4(bytes2 left, bytes4 right) internal pure returns (bytes6 result) { + assembly ("memory-safe") { + result := or(left, shr(16, right)) + } + } + + function pack_2_6(bytes2 left, bytes6 right) internal pure returns (bytes8 result) { + assembly ("memory-safe") { + result := or(left, shr(16, right)) + } + } + + function pack_4_2(bytes4 left, bytes2 right) internal pure returns (bytes6 result) { + assembly ("memory-safe") { + result := or(left, shr(32, right)) + } + } + function pack_4_4(bytes4 left, bytes4 right) internal pure returns (bytes8 result) { assembly ("memory-safe") { result := or(left, shr(32, right)) @@ -87,6 +105,18 @@ library Packing { } } + function pack_6_2(bytes6 left, bytes2 right) internal pure returns (bytes8 result) { + assembly ("memory-safe") { + result := or(left, shr(48, right)) + } + } + + function pack_6_6(bytes6 left, bytes6 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + result := or(left, shr(48, right)) + } + } + function pack_8_4(bytes8 left, bytes4 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { result := or(left, shr(64, right)) @@ -255,6 +285,48 @@ library Packing { } } + function extract_6_1(bytes6 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 5) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_6_1(bytes6 self, bytes1 value, uint8 offset) internal pure returns (bytes6 result) { + bytes1 oldValue = extract_6_1(self, offset); + assembly ("memory-safe") { + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_6_2(bytes6 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 4) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_6_2(bytes6 self, bytes2 value, uint8 offset) internal pure returns (bytes6 result) { + bytes2 oldValue = extract_6_2(self, offset); + assembly ("memory-safe") { + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_6_4(bytes6 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_6_4(bytes6 self, bytes4 value, uint8 offset) internal pure returns (bytes6 result) { + bytes4 oldValue = extract_6_4(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") { @@ -297,6 +369,20 @@ library Packing { } } + function extract_8_6(bytes8 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_8_6(bytes8 self, bytes6 value, uint8 offset) internal pure returns (bytes8 result) { + bytes6 oldValue = extract_8_6(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") { @@ -339,6 +425,20 @@ library Packing { } } + function extract_12_6(bytes12 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_12_6(bytes12 self, bytes6 value, uint8 offset) internal pure returns (bytes12 result) { + bytes6 oldValue = extract_12_6(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") { @@ -395,6 +495,20 @@ library Packing { } } + function extract_16_6(bytes16 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_16_6(bytes16 self, bytes6 value, uint8 offset) internal pure returns (bytes16 result) { + bytes6 oldValue = extract_16_6(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") { @@ -465,6 +579,20 @@ library Packing { } } + function extract_20_6(bytes20 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_20_6(bytes20 self, bytes6 value, uint8 offset) internal pure returns (bytes20 result) { + bytes6 oldValue = extract_20_6(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") { @@ -549,6 +677,20 @@ library Packing { } } + function extract_24_6(bytes24 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_24_6(bytes24 self, bytes6 value, uint8 offset) internal pure returns (bytes24 result) { + bytes6 oldValue = extract_24_6(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") { @@ -647,6 +789,20 @@ library Packing { } } + function extract_28_6(bytes28 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 22) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_28_6(bytes28 self, bytes6 value, uint8 offset) internal pure returns (bytes28 result) { + bytes6 oldValue = extract_28_6(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") { @@ -759,6 +915,20 @@ library Packing { } } + function extract_32_6(bytes32 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 26) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_32_6(bytes32 self, bytes6 value, uint8 offset) internal pure returns (bytes32 result) { + bytes6 oldValue = extract_32_6(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") { diff --git a/scripts/generate/templates/Packing.js b/scripts/generate/templates/Packing.js index da329dc47..a5ab2d4b5 100644 --- a/scripts/generate/templates/Packing.js +++ b/scripts/generate/templates/Packing.js @@ -36,43 +36,51 @@ pragma solidity ^0.8.20; `; const errors = `\ - error OutOfRangeAccess();`; +error OutOfRangeAccess(); +`; -const pack = (left, right) => ` +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) => ` +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) => ` +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)]), + format( + [].concat( + 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)]), + ), + ).trimEnd(), '}', ); diff --git a/scripts/generate/templates/Packing.opts.js b/scripts/generate/templates/Packing.opts.js index 8f952ef57..de9ab77ff 100644 --- a/scripts/generate/templates/Packing.opts.js +++ b/scripts/generate/templates/Packing.opts.js @@ -1,5 +1,3 @@ -const { range } = require('../../helpers'); - -const SIZES = range(1, 33).filter(size => size == 1 || size == 2 || size % 4 == 0); - -module.exports = { SIZES }; +module.exports = { + SIZES: [1, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32], +}; diff --git a/scripts/generate/templates/Packing.t.js b/scripts/generate/templates/Packing.t.js index f601cfa28..56e9c0cc7 100644 --- a/scripts/generate/templates/Packing.t.js +++ b/scripts/generate/templates/Packing.t.js @@ -10,13 +10,14 @@ import {Test} from "forge-std/Test.sol"; import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; `; -const testPack = (left, right) => ` +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) => ` +const testReplace = (outer, inner) => `\ function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external { offset = uint8(bound(offset, 0, ${outer - inner})); @@ -24,19 +25,24 @@ function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offs 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(), - '', + header, '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)), + format( + [].concat( + '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)), + ), + ).trimEnd(), '}', ); diff --git a/test/utils/Packing.t.sol b/test/utils/Packing.t.sol index 3fcf04935..9531f1bff 100644 --- a/test/utils/Packing.t.sol +++ b/test/utils/Packing.t.sol @@ -19,6 +19,21 @@ contract PackingTest is Test { assertEq(right, Packing.pack_2_2(left, right).extract_4_2(2)); } + function testPack(bytes2 left, bytes4 right) external { + assertEq(left, Packing.pack_2_4(left, right).extract_6_2(0)); + assertEq(right, Packing.pack_2_4(left, right).extract_6_4(2)); + } + + function testPack(bytes2 left, bytes6 right) external { + assertEq(left, Packing.pack_2_6(left, right).extract_8_2(0)); + assertEq(right, Packing.pack_2_6(left, right).extract_8_6(2)); + } + + function testPack(bytes4 left, bytes2 right) external { + assertEq(left, Packing.pack_4_2(left, right).extract_6_4(0)); + assertEq(right, Packing.pack_4_2(left, right).extract_6_2(4)); + } + 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)); @@ -54,6 +69,16 @@ contract PackingTest is Test { assertEq(right, Packing.pack_4_28(left, right).extract_32_28(4)); } + function testPack(bytes6 left, bytes2 right) external { + assertEq(left, Packing.pack_6_2(left, right).extract_8_6(0)); + assertEq(right, Packing.pack_6_2(left, right).extract_8_2(6)); + } + + function testPack(bytes6 left, bytes6 right) external { + assertEq(left, Packing.pack_6_6(left, right).extract_12_6(0)); + assertEq(right, Packing.pack_6_6(left, right).extract_12_6(6)); + } + 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)); @@ -186,6 +211,33 @@ contract PackingTest is Test { assertEq(container, container.replace_4_2(newValue, offset).replace_4_2(oldValue, offset)); } + function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external { + offset = uint8(bound(offset, 0, 5)); + + bytes1 oldValue = container.extract_6_1(offset); + + assertEq(newValue, container.replace_6_1(newValue, offset).extract_6_1(offset)); + assertEq(container, container.replace_6_1(newValue, offset).replace_6_1(oldValue, offset)); + } + + function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external { + offset = uint8(bound(offset, 0, 4)); + + bytes2 oldValue = container.extract_6_2(offset); + + assertEq(newValue, container.replace_6_2(newValue, offset).extract_6_2(offset)); + assertEq(container, container.replace_6_2(newValue, offset).replace_6_2(oldValue, offset)); + } + + function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external { + offset = uint8(bound(offset, 0, 2)); + + bytes4 oldValue = container.extract_6_4(offset); + + assertEq(newValue, container.replace_6_4(newValue, offset).extract_6_4(offset)); + assertEq(container, container.replace_6_4(newValue, offset).replace_6_4(oldValue, offset)); + } + function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external { offset = uint8(bound(offset, 0, 7)); @@ -213,6 +265,15 @@ contract PackingTest is Test { assertEq(container, container.replace_8_4(newValue, offset).replace_8_4(oldValue, offset)); } + function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external { + offset = uint8(bound(offset, 0, 2)); + + bytes6 oldValue = container.extract_8_6(offset); + + assertEq(newValue, container.replace_8_6(newValue, offset).extract_8_6(offset)); + assertEq(container, container.replace_8_6(newValue, offset).replace_8_6(oldValue, offset)); + } + function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external { offset = uint8(bound(offset, 0, 11)); @@ -240,6 +301,15 @@ contract PackingTest is Test { assertEq(container, container.replace_12_4(newValue, offset).replace_12_4(oldValue, offset)); } + function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external { + offset = uint8(bound(offset, 0, 6)); + + bytes6 oldValue = container.extract_12_6(offset); + + assertEq(newValue, container.replace_12_6(newValue, offset).extract_12_6(offset)); + assertEq(container, container.replace_12_6(newValue, offset).replace_12_6(oldValue, offset)); + } + function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external { offset = uint8(bound(offset, 0, 4)); @@ -276,6 +346,15 @@ contract PackingTest is Test { assertEq(container, container.replace_16_4(newValue, offset).replace_16_4(oldValue, offset)); } + function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external { + offset = uint8(bound(offset, 0, 10)); + + bytes6 oldValue = container.extract_16_6(offset); + + assertEq(newValue, container.replace_16_6(newValue, offset).extract_16_6(offset)); + assertEq(container, container.replace_16_6(newValue, offset).replace_16_6(oldValue, offset)); + } + function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external { offset = uint8(bound(offset, 0, 8)); @@ -321,6 +400,15 @@ contract PackingTest is Test { assertEq(container, container.replace_20_4(newValue, offset).replace_20_4(oldValue, offset)); } + function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external { + offset = uint8(bound(offset, 0, 14)); + + bytes6 oldValue = container.extract_20_6(offset); + + assertEq(newValue, container.replace_20_6(newValue, offset).extract_20_6(offset)); + assertEq(container, container.replace_20_6(newValue, offset).replace_20_6(oldValue, offset)); + } + function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external { offset = uint8(bound(offset, 0, 12)); @@ -375,6 +463,15 @@ contract PackingTest is Test { assertEq(container, container.replace_24_4(newValue, offset).replace_24_4(oldValue, offset)); } + function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external { + offset = uint8(bound(offset, 0, 18)); + + bytes6 oldValue = container.extract_24_6(offset); + + assertEq(newValue, container.replace_24_6(newValue, offset).extract_24_6(offset)); + assertEq(container, container.replace_24_6(newValue, offset).replace_24_6(oldValue, offset)); + } + function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external { offset = uint8(bound(offset, 0, 16)); @@ -438,6 +535,15 @@ contract PackingTest is Test { assertEq(container, container.replace_28_4(newValue, offset).replace_28_4(oldValue, offset)); } + function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external { + offset = uint8(bound(offset, 0, 22)); + + bytes6 oldValue = container.extract_28_6(offset); + + assertEq(newValue, container.replace_28_6(newValue, offset).extract_28_6(offset)); + assertEq(container, container.replace_28_6(newValue, offset).replace_28_6(oldValue, offset)); + } + function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external { offset = uint8(bound(offset, 0, 20)); @@ -510,6 +616,15 @@ contract PackingTest is Test { assertEq(container, container.replace_32_4(newValue, offset).replace_32_4(oldValue, offset)); } + function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external { + offset = uint8(bound(offset, 0, 26)); + + bytes6 oldValue = container.extract_32_6(offset); + + assertEq(newValue, container.replace_32_6(newValue, offset).extract_32_6(offset)); + assertEq(container, container.replace_32_6(newValue, offset).replace_32_6(oldValue, offset)); + } + function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external { offset = uint8(bound(offset, 0, 24));