Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:GNSPS/solidity-bytes-utils into …
Browse files Browse the repository at this point in the history
…develop
  • Loading branch information
GNSPS committed Jan 10, 2018
2 parents 553382e + 293c261 commit 131af34
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 42 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ TODOs:
* Slicing directly from storage
* Implement inline assembly functions for better readability

### Testing

This project uses Truffle for tests. Truffle's version of `solc` needs to be at least 0.4.19 for the contracts to compile. If you encounter compilation errors, try:

$ cd /usr/local/lib/node_modules/truffle
$ npm install solc@latest

To run the tests, start a `testrpc` instance, then run `truffle test`.

## API

* `function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes)`
Expand Down
145 changes: 103 additions & 42 deletions contracts/BytesLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,52 @@ pragma solidity ^0.4.19;
library BytesLib {
function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes) {
bytes memory tempBytes;

assembly {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)

// Store the length of the first bytes array at the beginning of
// the memory for tempBytes.
let length := mload(_preBytes)
mstore(tempBytes, length)


// Maintain a memory counter for the current write location in the
// temp bytes array by adding the 32 bytes for the array length to
// the starting location.
let mc := add(tempBytes, 0x20)
// Stop copying when the memory counter reaches the length of the
// first bytes array.
let end := add(mc, length)

for {
// Initialize a copy counter to the start of the _preBytes data,
// 32 bytes into its memory.
let cc := add(_preBytes, 0x20)
} lt(mc, end) {
// Increase both counters by 32 bytes each iteration.
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// Write the _preBytes data into the tempBytes memory 32 bytes
// at a time.
mstore(mc, mload(cc))
}


// Add the length of _postBytes to the current length of tempBytes
// and store it as the new length in the first 32 bytes of the
// tempBytes memory.
length := mload(_postBytes)
mstore(tempBytes, add(length, mload(tempBytes)))


// Move the memory counter back from a multiple of 0x20 to the
// actual end of the _preBytes data.
mc := end
// Stop copying when the memory counter reaches the new combined
// length of the arrays.
end := add(mc, length)

for {
let cc := add(_postBytes, 0x20)
} lt(mc, end) {
Expand All @@ -37,21 +58,34 @@ library BytesLib {
} {
mstore(mc, mload(cc))
}

//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
//make an additional check for a resulting zero-length array:
// if (sub - end == 0) then end = end + 1
mstore(0x40, and(add(add(end, iszero(add(length, mload(_preBytes)))), 31), not(31)))

// Update the free-memory pointer by padding our last write location
// to 32 bytes: add 31 bytes to the end of tempBytes to move to the
// next 32 byte block, then round down to the nearest multiple of
// 32. If the sum of the length of the two arrays is zero then add
// one before rounding down to leave a blank 32 bytes (the length block with 0).
mstore(0x40, and(
add(add(end, iszero(add(length, mload(_preBytes)))), 31),
not(31) // Round down to the nearest 32 bytes.
))
}

return tempBytes;
}

function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
assembly {
// we know _preBytes_offset is 0
// Read the first 32 bytes of _preBytes storage, which is the length
// of the array. (We don't need to use the offset into the slot
// because arrays use the entire slot.)
let fslot := sload(_preBytes_slot)
// Arrays of 31 bytes or less have an even value in their slot,
// while longer arrays have an odd value. The actual length is
// the slot divided by two for odd values, and the lowest order
// byte divided by two for even values.
// If the slot is even, bitwise and the slot with 255 and divide by
// two to get the length. If the slot is odd, bitwise and the slot
// with -1 and divide by two.
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
let mlength := mload(_postBytes)
let newlength := add(slength, mlength)
Expand All @@ -60,13 +94,15 @@ library BytesLib {
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
switch add(lt(slength, 32), lt(newlength, 32))
case 2 {
// Since the new array still fits in the slot, we just need to
// update the contents of the slot.
// uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
sstore(
_preBytes_slot,
// all the modifications to the slot are inside this
// next block
add(
// we can just add to the slot contents because the
// we can just add to the slot contents because the
// bytes we want to change are the LSBs
fslot,
add(
Expand All @@ -89,18 +125,29 @@ library BytesLib {
)
}
case 1 {
// The stored value fits in the slot, but the combined value
// will exceed it.
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
let sc := add(keccak256(0x0, 0x20), div(slength, 32))

// save new length
sstore(_preBytes_slot, add(mul(newlength, 2), 1))


// The contents of the _postBytes array start 32 bytes into
// the structure. Our first read should obtain the `submod`
// bytes that can fit into the unused space in the last word
// of the stored array. To get this, we read 32 bytes starting
// from `submod`, so the data we read overlaps with the array
// contents by `submod` bytes. Masking the lowest-order
// `submod` bytes allows us to add that value directly to the
// stored value.

let submod := sub(32, slength)
let mc := add(_postBytes, submod)
let end := add(_postBytes, mlength)
let mask := sub(exp(0x100, submod), 1)

sstore(
sc,
add(
Expand All @@ -111,8 +158,8 @@ library BytesLib {
and(mload(mc), mask)
)
)
for {

for {
mc := add(mc, 0x20)
sc := add(sc, 1)
} lt(mc, end) {
Expand All @@ -129,18 +176,21 @@ library BytesLib {
default {
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
// Start copying to the last used word of the stored array.
let sc := add(keccak256(0x0, 0x20), div(slength, 32))

// save new length
sstore(_preBytes_slot, add(mul(newlength, 2), 1))


// Copy over the first `submod` bytes of the new data as in
// case 1 above.
let slengthmod := mod(slength, 32)
let mlengthmod := mod(mlength, 32)
let submod := sub(32, slengthmod)
let mc := add(_postBytes, submod)
let end := add(_postBytes, mlength)
let mask := sub(exp(0x100, submod), 1)

sstore(sc, add(sload(sc), and(mload(mc), mask)))

for {
Expand All @@ -159,22 +209,32 @@ library BytesLib {
}
}
}

function slice(bytes _bytes, uint _start, uint _length) internal pure returns (bytes) {
require(_bytes.length >= (_start + _length));

bytes memory tempBytes;

assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)


// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)

let mc := add(tempBytes, lengthmod)
let end := add(mc, _length)

for {
let cc := add(add(_bytes, lengthmod), _start)
} lt(mc, end) {
Expand All @@ -183,9 +243,9 @@ library BytesLib {
} {
mstore(mc, mload(cc))
}

mstore(tempBytes, _length)

//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
Expand All @@ -197,29 +257,29 @@ library BytesLib {
mstore(0x40, add(tempBytes, 0x20))
}
}

return tempBytes;
}

function toAddress(bytes _bytes, uint _start) internal pure returns (address) {
require(_bytes.length >= (_start + 20));
address tempAddress;

assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}

return tempAddress;
}

function toUint(bytes _bytes, uint _start) internal pure returns (uint256) {
require(_bytes.length >= (_start + 32));
uint256 tempUint;

assembly {
tempUint := mload(add(add(_bytes, 0x20), _start))
}

return tempUint;
}

Expand All @@ -232,7 +292,7 @@ library BytesLib {
// 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
// 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
Expand Down Expand Up @@ -272,6 +332,7 @@ library BytesLib {
assembly {
// we know _preBytes_offset is 0
let fslot := sload(_preBytes_slot)
// Decode the length of the stored array like in concatStorage().
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
let mlength := mload(_postBytes)

Expand All @@ -293,7 +354,7 @@ library BytesLib {
}
}
default {
// cb is a circuit breaker in the for loop since there's
// 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
Expand All @@ -302,7 +363,7 @@ library BytesLib {
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
let sc := keccak256(0x0, 0x20)

let mc := add(_postBytes, 0x20)
let end := add(mc, mlength)

Expand Down

0 comments on commit 131af34

Please sign in to comment.