Skip to content

Commit

Permalink
Merge pull request #76 from iFrostizz/feat/Bytes
Browse files Browse the repository at this point in the history
feat: Bytes
  • Loading branch information
devtooligan authored Feb 20, 2023
2 parents b6cac47 + d20f765 commit ec5281c
Show file tree
Hide file tree
Showing 4 changed files with 447 additions and 0 deletions.
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ fs_permissions = [

{ access = "read", path = "./test/data-structures/mocks/ArrayWrappers.huff" },
{ access = "read", path = "./test/data-structures/mocks/HashmapWrappers.huff" },
{ access = "read", path = "./test/data-structures/mocks/BytesWrappers.huff" },

{ access = "read", path = "./test/math/mocks/FixedPointMathWrappers.huff" },
{ access = "read", path = "./test/math/mocks/SafeMathWrappers.huff" },
Expand Down
111 changes: 111 additions & 0 deletions src/data-structures/Bytes.huff
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/// @title Bytes
/// @notice SPDX-License-Identifier: MIT
/// @author Franfran <https://github.com/iFrostizz>
/// @notice Low-level operations on bytes
/// @notice Adapted from BytesLib (https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol)

/// @notice Concatenate two bytes arrays
/// @notice Takes in two pointers of the bytes to concatenate that must be sorted
/// @return Pointer of the new appended concatenated bytes array in the memory
/// @dev Warning! This assumes that the pointer in the memory of the second bytes chunk is after mem_ptr1 + 0x20
#define macro CONCAT_MEMORY() = takes(2) returns(1) {
// input stack // [mem_ptr1, mem_ptr2]

// setup stack and memory for the next iterations
dup2 mload swap1 // [mem_ptr1, len2, mem_ptr2]
msize swap1 // [mem_ptr1, free_loc_pos, len2, mem_ptr2]
dup1 mload dup4 // [len2, len1, mem_ptr1, free_loc_pos, len2, mem_ptr2]
dup2 add msize mstore // [len1, mem_ptr1, free_loc_pos, len2, mem_ptr2]

swap1 0x20 add // [index(i), len1, free_loc_pos, len2, mem_ptr2]
msize // [index(j), index(i), len1, free_loc_pos, len2, mem_ptr2]
swap2 0x00 // [is_sec_loop, len1, index(i), index(j), free_loc_pos, len2, mem_ptr2]

// i is the index where we get (mload) the array element and j is the index where we store (mstore) the array at j
loop: // [is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
dup2 iszero empty_slot jumpi // [is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]

dup3 mload // [word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
dup3 0x20 gt iszero // [is_full_slot, word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
full_slot jumpi // [word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]

// else it's not a full slot, we're hitting an end. Then clean memory slot and update j with a partial length
dup3 0x20 sub // [pad_len, word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
0x08 mul swap1 dup2 // [shift, word, shift, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
shr // [left_padded_word, shift, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
swap1 shl // [clean_word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
dup5 mstore // [is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
swap3 add swap2 // [is_sec_loop, index(i), index(j + 1), free_loc_pos, len2, mem_ptr2]

// here we check if current loop is for the 2nd array
swap1 pop // [is_sec_loop, index(j + 1), free_loc_pos, len2, mem_ptr2]
iszero bridge jumpi // [index(j + 1), free_loc_pos, len2, mem_ptr2]
pop break jump // [free_loc_pos, len2, mem_ptr2]

empty_slot: // [is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
swap2 pop pop // [is_sec_loop, index(j), free_loc_pos, len2, mem_ptr2]
iszero bridge jumpi // [index(j), free_loc_pos, len2, mem_ptr2]
pop break jump // [free_loc_pos, len2, mem_ptr2]

bridge: // [index(j), free_loc_pos, len2, mem_ptr2]
dup4 0x20 add // [index(i), index(j), free_loc_pos, len2, mem_ptr2]
dup5 // [len2, index(i), index(j), free_loc_pos, len2, mem_ptr2]
0x01 // [is_sec_loop, len2, index(i), index(j), free_loc_pos, len2, mem_ptr2]
loop jump

full_slot: // [word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
dup5 mstore // [is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
swap1 0x20 swap1 sub // [len_left - 0x20, is_sec_loop, index(i), index(j), free_loc_pos, len2, mem_ptr2]
swap2 0x20 add // [index(i + 1), is_sec_loop, len_left - 0x20, index(j), free_loc_pos, len2, mem_ptr2]
swap3 0x20 add // [index(j + 1), is_sec_loop, len_left - 0x20, index(i + 1), free_loc_pos, len2, mem_ptr2]
swap3 swap2 swap1 // [is_sec_loop, len_left - 0x20, index(i + 1), index(j + 1), free_loc_pos, len2, mem_ptr2]
loop jump

break: // [free_loc_pos, len2, mem_ptr2]
swap2 pop pop // [free_loc_pos]
}

/// @param Pointer in memory of the start of the bytes array
/// @param Start position of the slice relative to the array
/// @param Length of the output slice
/// @return Pointer of the new appended concatenated bytes array in the memory
/// @dev Warning! This assumes that the length of the output slice is less or equal the length of the bytes array (bytes.len < slice.len)
/// @dev Warning! This assumes that the start of the bytes array is not out of bounds (start < len + mem_ptr)
#define macro SLICE_MEMORY() = takes(3) returns(1) {
// input stack // [mem_ptr, start, length]

msize dup4 msize mstore // [free_loc_pos, mem_ptr, start, length]
msize swap4 // [length, free_loc_pos, mem_ptr, start, index(j)]
// index(i) = mem_ptr + start + 0x20
swap1 swap3 // [start, length, mem_ptr, free_loc_pos, index(j)]
swap1 swap2 // [mem_ptr, start, length, free_loc_pos, index(j)]
0x20 add add // [index(i), length, free_loc_pos, index(j)]

// we load our slice chunk at i and store it in a free memory location at j
loop: // [index(i), length_left, free_loc_pos, index(j)]
dup1 mload // [slice_chunk, index(i), length_left, free_loc_pos, index(j)]

// if current is not full slot, then load the last bytes and break
0x20 dup4 lt // [is_not_full_slot, slice_chunk, index(i), length_left, free_loc_pos, index(j)]
break jumpi // [slice_chunk, index(i), length_left, free_loc_pos, index(j)]

dup5 mstore // [index(i), length_left, free_loc_pos, index(j)]

0x20 add swap3 // [free_loc, length_left, free_loc_pos, index(i+1)]
0x20 add swap3 // [index(i+1), length_left, free_loc_pos, free_loc + 1]
swap1 0x20 // [0x20, length_left, index(i+1), free_loc_pos, free_loc + 1]
swap1 sub // [length_left - 1, index(i+1), free_loc_pos, free_loc + 1]
swap1 // [index(i+1), length_left - 1, free_loc_pos, free_loc + 1]

loop jump

break: // [slice_chunk, index(i), length, free_loc_pos, index(j)]
// store the remaining length
dup3 0x20 sub // [zero_length, slice_chunk, index(i), length, free_loc_pos, index(j)]
0x08 mul swap1 dup2 // [shift, slice_chunk, shift, index(i), length, free_loc_pos, index(j)]
shr // [left_pad_slice, shift, index(i), length, free_loc_pos, index(j)]
swap1 shl // [slice_chunk, index(i), length, free_loc_pos, index(j)]
dup5 mstore // [index(i), length, free_loc_pos, index(j)]

pop pop swap1 pop // [free_loc_pos]
}
99 changes: 99 additions & 0 deletions test/data-structures/Bytes.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import "foundry-huff/HuffDeployer.sol";
import "forge-std/Test.sol";
import "forge-std/console2.sol";

interface IBytes {
function concatMemoryAndSet1() external;
function concatMemoryAndSet2() external;
function concatMemoryAndSet3() external;
function concatMemoryAndSet4() external;
function concatMemoryAndSet5() external;
function concatMemoryAndSet6() external;
function sliceMemoryAndSet1() external;
function sliceMemoryAndSet2() external;
function sliceMemoryAndSet3() external;
}

contract BytesTest is Test {
IBytes b;

function setUp() public {
string memory instantiable_code = vm.readFile(
"test/data-structures/mocks/BytesWrappers.huff"
);

// Create an Instantiable Arrays
HuffConfig config = HuffDeployer.config().with_code(instantiable_code);
b = IBytes(config.deploy("data-structures/Bytes"));
}

function testConcat1() public {
b.concatMemoryAndSet1();

assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(64)));
assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(0xbabe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe));
assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(0xbabe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe));
}

function testConcat2() public {
b.concatMemoryAndSet2();
assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(96)));
assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(0xbabe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe));
assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(0xbabe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe));
assertEq(vm.load(address(b), bytes32(uint256(96))), bytes32(0xbabe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe));
}

function testConcat3() public {
b.concatMemoryAndSet3();

assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(32)));
assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(0xbabe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe));
assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(0));
}

function testConcat4() public {
b.concatMemoryAndSet4();

assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(37)));
assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(0xbabe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe));
assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(bytes5(0xbabe2babe2)));
}

function testConcat5() public {
b.concatMemoryAndSet5();

assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(15)));
assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(bytes15(0xbabe1babe1babe1babe1babe2babe2)));
assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(0));
}

function testConcat6() public {
b.concatMemoryAndSet6();

assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(15)));
assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(bytes15(0xbabe1babe1babe1babe1babe2babe2)));
assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(0));
}

function testSlice1() public {
b.sliceMemoryAndSet1();
assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(16)));
assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(bytes16(0xbabe1babe1babe1babe1babe1babe1ba)));
}

function testSlice2() public {
b.sliceMemoryAndSet2();
assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(36)));
assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(0x1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babebabe));
assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(bytes4(0x2babe2ba)));
}

function testSlice3() public {
b.sliceMemoryAndSet3();
assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(4)));
assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(bytes4(0xbabe2bab)));
}
}
Loading

0 comments on commit ec5281c

Please sign in to comment.