Skip to content

Commit

Permalink
Add equal_nonAligned byte array method (#64)
Browse files Browse the repository at this point in the history
* Add equal_nonAligned method

* Adding comment about the remainder calculation

* Add assertion function + test cases for equality and non-equality

* Fix tests for equal_nonAligned
  • Loading branch information
djb15 committed Dec 13, 2023
1 parent 6458fb2 commit 1dff13e
Show file tree
Hide file tree
Showing 3 changed files with 363 additions and 0 deletions.
97 changes: 97 additions & 0 deletions contracts/AssertBytes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,103 @@ library AssertBytes {
return !returnBool;
}

/*
Function: equal_nonAligned(bytes memory, bytes memory)
Assert that two tightly packed bytes arrays that are not aligned to 32 bytes are equal.
Params:
A (bytes) - The first bytes.
B (bytes) - The second bytes.
message (string) - A message that is sent if the assertion fails.
Returns:
result (bool) - The result.
*/


function _equal_nonAligned(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
bool success = true;

assembly {
let length := mload(_preBytes)

// if lengths don't match the arrays are not equal
switch eq(length, mload(_postBytes))
case 1 {
// cb is a circuit breaker in the for loop since there's
// no said feature for inline assembly loops
// cb = 1 - don't breaker
// cb = 0 - break
let cb := 1

let endMinusWord := add(_preBytes, length)
let mc := add(_preBytes, 0x20)
let cc := add(_postBytes, 0x20)

for {
// the next line is the loop condition:
// while(uint256(mc < endWord) + cb == 2)
} eq(add(lt(mc, endMinusWord), cb), 2) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// if any of these checks fails then arrays are not equal
if iszero(eq(mload(mc), mload(cc))) {
// unsuccess:
success := 0
cb := 0
}
}

// Only if still successful
// For <1 word tail bytes
if gt(success, 0) {
// Get the remainder of length/32
// length % 32 = AND(length, 32 - 1)
let numTailBytes := and(length, 0x1f)
let mcRem := mload(mc)
let ccRem := mload(cc)
for {
let i := 0
// the next line is the loop condition:
// while(uint256(i < numTailBytes) + cb == 2)
} eq(add(lt(i, numTailBytes), cb), 2) {
i := add(i, 1)
} {
if iszero(eq(byte(i, mcRem), byte(i, ccRem))) {
// unsuccess:
success := 0
cb := 0
}
}
}
}
default {
// unsuccess:
success := 0
}
}

return success;
}

function equal_nonAligned(bytes memory _a, bytes memory _b, string memory message) internal returns (bool) {
bool returnBool = _equal_nonAligned(_a, _b);

_report(returnBool, message);

return returnBool;
}

function notEqual_nonAligned(bytes memory _a, bytes memory _b, string memory message) internal returns (bool) {
bool returnBool = _equal_nonAligned(_a, _b);

_report(!returnBool, message);

return !returnBool;
}

/*
Function: equal(bytes storage, bytes memory)
Expand Down
66 changes: 66 additions & 0 deletions contracts/BytesLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,72 @@ library BytesLib {
return success;
}

function equal_nonAligned(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
bool success = true;

assembly {
let length := mload(_preBytes)

// if lengths don't match the arrays are not equal
switch eq(length, mload(_postBytes))
case 1 {
// cb is a circuit breaker in the for loop since there's
// no said feature for inline assembly loops
// cb = 1 - don't breaker
// cb = 0 - break
let cb := 1

let endMinusWord := add(_preBytes, length)
let mc := add(_preBytes, 0x20)
let cc := add(_postBytes, 0x20)

for {
// the next line is the loop condition:
// while(uint256(mc < endWord) + cb == 2)
} eq(add(lt(mc, endMinusWord), cb), 2) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// if any of these checks fails then arrays are not equal
if iszero(eq(mload(mc), mload(cc))) {
// unsuccess:
success := 0
cb := 0
}
}

// Only if still successful
// For <1 word tail bytes
if gt(success, 0) {
// Get the remainder of length/32
// length % 32 = AND(length, 32 - 1)
let numTailBytes := and(length, 0x1f)
let mcRem := mload(mc)
let ccRem := mload(cc)
for {
let i := 0
// the next line is the loop condition:
// while(uint256(i < numTailBytes) + cb == 2)
} eq(add(lt(i, numTailBytes), cb), 2) {
i := add(i, 1)
} {
if iszero(eq(byte(i, mcRem), byte(i, ccRem))) {
// unsuccess:
success := 0
cb := 0
}
}
}
}
default {
// unsuccess:
success := 0
}
}

return success;
}

function equalStorage(
bytes storage _preBytes,
bytes memory _postBytes
Expand Down
200 changes: 200 additions & 0 deletions test/TestBytesLib1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,206 @@ contract TestBytesLib1 {
resetStorage();
}

/**
* Equality Non-aligned Tests
*/

function testEqualNonAligned4Bytes() public {
bytes memory memBytes1; // hex"f00dfeed"
bytes memory memBytes2; // hex"f00dfeed"

// We need to make sure that the bytes are not aligned to a 32 byte boundary
// so we need to use assembly to allocate the bytes in contiguous memory
// Solidity will not let us do this normally, this equality method exists
// to test the edge case of non-aligned bytes created in assembly
assembly {
// Fetch free memory pointer
let freePointer := mload(0x40)

// We first store the length of the byte array (4 bytes)
// And then we write a byte at a time
memBytes1 := freePointer
mstore(freePointer, 0x04)
freePointer := add(freePointer, 0x20)
mstore8(freePointer, 0xf0)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0x0d)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xfe)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xed)
freePointer := add(freePointer, 0x1)

// We do the same for memBytes2 in contiguous memory
memBytes2 := freePointer
mstore(freePointer, 0x04)
freePointer := add(freePointer, 0x20)
mstore8(freePointer, 0xf0)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0x0d)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xfe)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xed)
freePointer := add(freePointer, 0x1)

// We add some garbage bytes in contiguous memory
mstore8(freePointer, 0xde)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xad)
freePointer := add(freePointer, 0x1)

// now, just for completeness sake we'll update the free-memory pointer accordingly
mstore(0x40, freePointer)
}

AssertBytes.equal_nonAligned(memBytes1, memBytes2, "The equality check for the non-aligned equality 4-bytes-long test failed.");
// The equality check for aligned byte arrays should fail for non-aligned bytes
AssertBytes.notEqual(memBytes1, memBytes2, "The equality check for the non-aligned equality 4-bytes-long test failed.");
}

function testEqualNonAligned4BytesFail() public {
bytes memory memBytes1; // hex"f00dfeed"
bytes memory memBytes2; // hex"feedf00d"

// We need to make sure that the bytes are not aligned to a 32 byte boundary
// so we need to use assembly to allocate the bytes in contiguous memory
// Solidity will not let us do this normally, this equality method exists
// to test the edge case of non-aligned bytes created in assembly
assembly {
// Fetch free memory pointer
let freePointer := mload(0x40)

// We first store the length of the byte array (4 bytes)
// And then we write a byte at a time
memBytes1 := freePointer
mstore(freePointer, 0x04)
freePointer := add(freePointer, 0x20)
mstore8(freePointer, 0xf0)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0x0d)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xfe)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xed)
freePointer := add(freePointer, 0x1)

// We do the same for memBytes2 in contiguous memory
memBytes2 := freePointer
mstore(freePointer, 0x04)
freePointer := add(freePointer, 0x20)
mstore8(freePointer, 0xfe)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xed)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xf0)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0x0d)
freePointer := add(freePointer, 0x1)

// We add some garbage bytes in contiguous memory
mstore8(freePointer, 0xde)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xad)
freePointer := add(freePointer, 0x1)

// now, just for completeness sake we'll update the free-memory pointer accordingly
mstore(0x40, freePointer)
}

AssertBytes.notEqual_nonAligned(memBytes1, memBytes2, "The non equality check for the non-aligned equality 4-bytes-long test failed.");
}

function testEqualNonAligned33Bytes() public {
bytes memory memBytes1; // hex"f00d00000000000000000000000000000000000000000000000000000000feedcc";
bytes memory memBytes2; // hex"f00d00000000000000000000000000000000000000000000000000000000feedcc";

// We need to make sure that the bytes are not aligned to a 32 byte boundary
// so we need to use assembly to allocate the bytes in contiguous memory
// Solidity will not let us do this normally, this equality method exists
// to test the edge case of non-aligned bytes created in assembly
assembly {
// Fetch free memory pointer
let freePointer := mload(0x40)

// We first store the length of the byte array (33 bytes)
// And then we write a word and then a byte
memBytes1 := freePointer
mstore(freePointer, 0x21)
freePointer := add(freePointer, 0x20)
mstore(freePointer, 0xf00d00000000000000000000000000000000000000000000000000000000feed)
freePointer := add(freePointer, 0x20)
mstore8(freePointer, 0xcc)
freePointer := add(freePointer, 0x1)

// We do the same for memBytes2 in contiguous memory
memBytes2 := freePointer
mstore(freePointer, 0x21)
freePointer := add(freePointer, 0x20)
mstore(freePointer, 0xf00d00000000000000000000000000000000000000000000000000000000feed)
freePointer := add(freePointer, 0x20)
mstore8(freePointer, 0xcc)
freePointer := add(freePointer, 0x1)

// We add some garbage bytes in contiguous memory
mstore8(freePointer, 0xde)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xad)
freePointer := add(freePointer, 0x1)

// now, just for completeness sake we'll update the free-memory pointer accordingly
mstore(0x40, freePointer)
}

AssertBytes.equal_nonAligned(memBytes1, memBytes2, "The equality check for the non-aligned equality 33-bytes-long test failed.");
// The equality check for aligned byte arrays should fail for non-aligned bytes
AssertBytes.notEqual(memBytes1, memBytes2, "The equality check for the non-aligned equality 4-bytes-long test failed.");
}

function testEqualNonAligned33BytesFail() public {
bytes memory memBytes1; // hex"f00d00000000000000000000000000000000000000000000000000000000feedcc";
bytes memory memBytes2; // hex"f00d00000000000000000000000000000000000000000000000000000000feedee";

// We need to make sure that the bytes are not aligned to a 32 byte boundary
// so we need to use assembly to allocate the bytes in contiguous memory
// Solidity will not let us do this normally, this equality method exists
// to test the edge case of non-aligned bytes created in assembly
assembly {
// Fetch free memory pointer
let freePointer := mload(0x40)

// We first store the length of the byte array (33 bytes)
// And then we write a word and then a byte
memBytes1 := freePointer
mstore(freePointer, 0x21)
freePointer := add(freePointer, 0x20)
mstore(freePointer, 0xf00d00000000000000000000000000000000000000000000000000000000feed)
freePointer := add(freePointer, 0x20)
mstore8(freePointer, 0xcc)
freePointer := add(freePointer, 0x1)

// We do the same for memBytes2 in contiguous memory
memBytes2 := freePointer
mstore(freePointer, 0x21)
freePointer := add(freePointer, 0x20)
mstore(freePointer, 0xf00d00000000000000000000000000000000000000000000000000000000feed)
freePointer := add(freePointer, 0x20)
mstore8(freePointer, 0xee)
freePointer := add(freePointer, 0x1)

// We add some garbage bytes in contiguous memory
mstore8(freePointer, 0xde)
freePointer := add(freePointer, 0x1)
mstore8(freePointer, 0xad)
freePointer := add(freePointer, 0x1)

// now, just for completeness sake we'll update the free-memory pointer accordingly
mstore(0x40, freePointer)
}

AssertBytes.notEqual_nonAligned(memBytes1, memBytes2, "The non equality check for the non-aligned equality 33-bytes-long test failed.");
}

/**
* Edge Cases
*/
Expand Down

0 comments on commit 1dff13e

Please sign in to comment.