Skip to content

Commit 2cf5aeb

Browse files
✨ toNibbles (#1461)
Co-authored-by: clandestine.eth <[email protected]>
1 parent 813c66f commit 2cf5aeb

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

src/utils/LibBit.sol

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,30 @@ library LibBit {
190190
}
191191
}
192192

193+
/// @dev hex"ABCD" -> hex"0A0B0C0D".
194+
function toNibbles(bytes memory s) internal pure returns (bytes memory result) {
195+
/// @solidity memory-safe-assembly
196+
assembly {
197+
result := mload(0x40)
198+
let n := mload(s)
199+
mstore(result, add(n, n)) // Store the new length.
200+
s := add(s, 0x20)
201+
let o := add(result, 0x20)
202+
// forgefmt: disable-next-item
203+
for { let i := 0 } lt(i, n) { i := add(i, 0x10) } {
204+
let x := shr(128, mload(add(s, i)))
205+
x := and(0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff, or(shl(64, x), x))
206+
x := and(0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff, or(shl(32, x), x))
207+
x := and(0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff, or(shl(16, x), x))
208+
x := and(0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff, or(shl(8, x), x))
209+
mstore(add(o, add(i, i)),
210+
and(0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f, or(shl(4, x), x)))
211+
}
212+
mstore(add(o, add(s, s)), 0) // Zeroize slot after result.
213+
mstore(0x40, add(0x40, add(o, add(s, s)))) // Allocate memory.
214+
}
215+
}
216+
193217
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
194218
/* BOOLEAN OPERATIONS */
195219
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

test/LibBit.t.sol

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,4 +339,41 @@ contract LibBitTest is SoladyTest {
339339
}
340340
return s;
341341
}
342+
343+
function testToNibblesGas() public {
344+
bytes memory s = hex"123456789abcdef123456789abcdef123456789abcdef123456789abcdef";
345+
bytes memory expected =
346+
hex"0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f";
347+
assertEq(LibBit.toNibbles(s), expected);
348+
}
349+
350+
function testToNibblesDifferential(uint256 r, bytes memory s) public {
351+
if (r & 0x01 == 0) {
352+
_brutalizeMemory();
353+
_misalignFreeMemoryPointer();
354+
}
355+
bytes memory computed = LibBit.toNibbles(s);
356+
_checkMemory(computed);
357+
assertEq(computed, _toNibblesOriginal(s));
358+
}
359+
360+
// Original code from Optimism (MIT-licensed): https://github.com/ethereum-optimism/optimism/blob/1bfc93f7c1fe1846217795a1f6051e1b0260f597/packages/contracts-bedrock/src/libraries/Bytes.sol#L94
361+
function _toNibblesOriginal(bytes memory input) internal pure returns (bytes memory result) {
362+
/// @solidity memory-safe-assembly
363+
assembly {
364+
result := mload(0x40)
365+
let bytesLength := mload(input)
366+
let nibblesLength := shl(0x01, bytesLength)
367+
mstore(0x40, add(result, and(not(0x1f), add(nibblesLength, 0x3f))))
368+
mstore(result, nibblesLength)
369+
let bytesStart := add(input, 0x20)
370+
let nibblesStart := add(result, 0x20)
371+
for { let i := 0x00 } lt(i, bytesLength) { i := add(i, 0x01) } {
372+
let offset := add(nibblesStart, shl(0x01, i))
373+
let b := byte(0x00, mload(add(bytesStart, i)))
374+
mstore8(offset, shr(0x04, b))
375+
mstore8(add(offset, 0x01), and(b, 0x0F))
376+
}
377+
}
378+
}
342379
}

0 commit comments

Comments
 (0)