Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
30eb7422
Commit
30eb7422
authored
Jun 14, 2023
by
clabby
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Pull in `toNibbles` optimization
parent
33608dbe
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
80 additions
and
59 deletions
+80
-59
Bytes.sol
packages/contracts-bedrock/contracts/libraries/Bytes.sol
+38
-12
Bytes.t.sol
packages/contracts-bedrock/contracts/test/Bytes.t.sol
+42
-47
No files found.
packages/contracts-bedrock/contracts/libraries/Bytes.sol
View file @
30eb7422
...
@@ -112,20 +112,46 @@ library Bytes {
...
@@ -112,20 +112,46 @@ library Bytes {
* @return Resulting nibble array.
* @return Resulting nibble array.
*/
*/
function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) {
function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) {
uint256 bytesLength = _bytes.length;
bytes memory _nibbles;
bytes memory nibbles = new bytes(bytesLength * 2);
assembly {
bytes1 b;
// Grab a free memory offset for the new array
_nibbles := mload(0x40)
for (uint256 i = 0; i < bytesLength; ) {
b = _bytes[i];
// Load the length of the passed bytes array from memory
nibbles[i * 2] = b >> 4;
let bytesLength := mload(_bytes)
nibbles[i * 2 + 1] = b & 0x0f;
unchecked {
// Calculate the length of the new nibble array
++i;
// This is the length of the input array times 2
let nibblesLength := shl(0x01, bytesLength)
// Update the free memory pointer to allocate memory for the new array.
// To do this, we add the length of the new array + 32 bytes for the array length
// rounded up to the nearest 32 byte boundary to the current free memory pointer.
mstore(0x40, add(_nibbles, and(not(0x1F), add(nibblesLength, 0x3F))))
// Store the length of the new array in memory
mstore(_nibbles, nibblesLength)
// Store the memory offset of the _bytes array's contents on the stack
let bytesStart := add(_bytes, 0x20)
// Store the memory offset of the nibbles array's contents on the stack
let nibblesStart := add(_nibbles, 0x20)
// Loop through each byte in the input array
for { let i := 0x00 } lt(i, bytesLength) { i := add(i, 0x01) } {
// Get the starting offset of the next 2 bytes in the nibbles array
let offset := add(nibblesStart, shl(0x01, i))
// Load the byte at the current index within the `_bytes` array
let b := byte(0x00, mload(add(bytesStart, i)))
// Pull out the first nibble and store it in the new array
mstore8(offset, shr(0x04, b))
// Pull out the second nibble and store it in the new array
mstore8(add(offset, 0x01), and(b, 0x0F))
}
}
}
}
return _nibbles;
return nibbles;
}
}
/**
/**
...
...
packages/contracts-bedrock/contracts/test/Bytes.t.sol
View file @
30eb7422
...
@@ -182,49 +182,6 @@ contract Bytes_slice_Test is Test {
...
@@ -182,49 +182,6 @@ contract Bytes_slice_Test is Test {
}
}
contract Bytes_toNibbles_Test is Test {
contract Bytes_toNibbles_Test is Test {
/**
* @notice Diffs the test Solidity version of `toNibbles` against the Yul version.
*
* @param _bytes The `bytes` array to convert to nibbles.
*
* @return Yul version of `toNibbles` applied to `_bytes`.
*/
function _toNibblesYul(bytes memory _bytes) internal pure returns (bytes memory) {
// Allocate memory for the `nibbles` array.
bytes memory nibbles = new bytes(_bytes.length << 1);
assembly {
// Load the length of the passed bytes array from memory
let bytesLength := mload(_bytes)
// Store the memory offset of the _bytes array's contents on the stack
let bytesStart := add(_bytes, 0x20)
// Store the memory offset of the nibbles array's contents on the stack
let nibblesStart := add(nibbles, 0x20)
// Loop through each byte in the input array
for {
let i := 0x00
} lt(i, bytesLength) {
i := add(i, 0x01)
} {
// Get the starting offset of the next 2 bytes in the nibbles array
let offset := add(nibblesStart, shl(0x01, i))
// Load the byte at the current index within the `_bytes` array
let b := byte(0x00, mload(add(bytesStart, i)))
// Pull out the first nibble and store it in the new array
mstore8(offset, shr(0x04, b))
// Pull out the second nibble and store it in the new array
mstore8(add(offset, 0x01), and(b, 0x0F))
}
}
return nibbles;
}
/**
/**
* @notice Tests that, given an input of 5 bytes, the `toNibbles` function returns an array of
* @notice Tests that, given an input of 5 bytes, the `toNibbles` function returns an array of
* 10 nibbles corresponding to the input data.
* 10 nibbles corresponding to the input data.
...
@@ -272,11 +229,49 @@ contract Bytes_toNibbles_Test is Test {
...
@@ -272,11 +229,49 @@ contract Bytes_toNibbles_Test is Test {
}
}
/**
/**
* @notice Test
that the `toNibbles` function in the `Bytes` library is equivalent to the Yul
* @notice Test
s that the `toNibbles` function correctly updates the free memory pointer depending
*
implementation
.
*
on the length of the resulting array
.
*/
*/
function testDiff_toNibbles_succeeds(bytes memory _input) public {
function testFuzz_toNibbles_memorySafety_succeeds(bytes memory _input) public {
assertEq(Bytes.toNibbles(_input), _toNibblesYul(_input));
// Grab the free memory pointer before the `toNibbles` operation
uint64 initPtr;
assembly {
initPtr := mload(0x40)
}
uint64 expectedPtr = uint64(initPtr + 0x20 + ((_input.length * 2 + 0x1F) & ~uint256(0x1F)));
// Ensure that all memory outside of the expected range is safe.
vm.expectSafeMemory(initPtr, expectedPtr);
// Pull out each individual nibble from the input bytes array
bytes memory nibbles = Bytes.toNibbles(_input);
// Grab the free memory pointer after the `toNibbles` operation
uint64 finalPtr;
assembly {
finalPtr := mload(0x40)
}
// The free memory pointer should have been updated properly
if (_input.length == 0) {
// If the input length is zero, only 32 bytes of memory should have been allocated.
assertEq(finalPtr, initPtr + 0x20);
} else {
// If the input length is greater than zero, the memory allocated should be the
// length of the input * 2 + 32 bytes for the length field.
//
// Note that we use a slightly less efficient, but equivalent method of rounding
// up `_length` to the next multiple of 32 than is used in the `toNibbles` function.
// This is to diff test the method used in `toNibbles`.
uint64 _expectedPtr = uint64(initPtr + 0x20 + (((_input.length * 2 + 0x1F) >> 5) << 5));
assertEq(finalPtr, _expectedPtr);
// Sanity check for equivalence of the rounding methods.
assertEq(_expectedPtr, expectedPtr);
}
// The nibbles length should be equal to `_length * 2`
assertEq(nibbles.length, _input.length << 1);
}
}
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment