Skip to content

Commit

Permalink
v0.8.0 - Solidity >0.8.0 support! 🎉 (#46)
Browse files Browse the repository at this point in the history
* Create 0.8.0 folder structure + initial edits

* Realize the folder structure is moot since we cannot use two different solc in Truffle

* Get stricter about versions + remove constructor visibility

* change MAX_UINT

* Change README to reflect new versioning system

* change version

* Remove overflow checks in typecast methods

* Remove overflow check in slice()
  • Loading branch information
GNSPS authored Apr 12, 2021
1 parent 358c9db commit 5d251ad
Show file tree
Hide file tree
Showing 11 changed files with 6,053 additions and 74 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.sol linguist-language=Solidity
45 changes: 42 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,52 @@

# Solidity Bytes Arrays Utils

Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
Bytes tightly packed arrays' utility library for ethereum contracts written in Solidity.

The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.

Given this library has an all-internal collection of methods it doesn't make sense having it reside in the mainnet. Instead it will only be available in EPM as an installable package.
Given this library has an all-internal collection of methods it doesn't make sense to have it reside in the mainnet. Instead it will only be available on EPM as an installable package.

_Version Notes_:
## Important Fixes Changelog

_**2021-01-07**_

A bug regarding zero-length slices was disclosed by @MrChico following an audit to the Optimism codebase.

The exact bug happened under the following conditions: if memory slots higher then the current free-memory pointer were tainted before calling the `slice` method with a desired length of `0`, the returned bytes array, instead of being a zero-length slice was an array of arbitrary length based on the values that previously populated that memory region.

Overall, the usage of zero-length slices should be pretty unusual and, as such, hopefully, this bug does not have far-reaching implications. Nonetheless, *please update the library to the new version if you're using it in production*.

**TL;DR: if you're using the `slice` method with a length parameter of '0' in your codebase, please update to version 0.1.2 of the bytes library ASAP!**

_**2020-11-01**_

There was a **critical bug** in the `slice` method, reported on an audit to a DXDao codebase.

Previously, no checks were being made on overflows of the `_start` and `_length` parameters since previous reviews of the codebase deemed this overflow "unexploitable" because of an inordinate expansion of memory (i.e., reading an immensely large memory offset causing huge memory expansion) resulting in an out-of-gas exception.

However, as noted in the review mentioned above, this is not the case. The `slice` method in versions `<=0.9.0` actually allows for arbitrary _kind of_ (i.e., it allows memory writes to very specific values) arbitrary memory writes _in the specific case where these parameters are user-supplied inputs and not hardcoded values (which is uncommon).

This made me realize that in permissioned blockchains where gas is also not a limiting factor this could become problematic in other methods and so I updated all typecasting-related methods to include new bound checks as well.

**TL;DR: if you're using the `slice` method with user-supplied inputs in your codebase please update the bytes library immediately!**

## _Version Notes_:

* Starting from version `v0.8.0` the versioning will change to follow compatible Solidity's compiler versions.
This means that now the library will only compile on Solidity versions `>=0.8.0` so, if you need `<0.8.0` support for your project just use `v0.1.2` of the library with:

```
$ truffle install [email protected]
```
or
```
$ npm install [email protected]
```

* Version `v0.1.2` has a major bug fix.

* Version `v0.1.1` has a critical bug fix.

* Version `v0.9.0` now compiles with Solidity compilers `0.5.x` and `0.6.x`.

Expand Down
10 changes: 5 additions & 5 deletions contracts/AssertBytes.sol
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* This library is compliant with the test event convention that the Truffle suite uses.
*/

pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.8.0 <0.9.0;


library AssertBytes {
Expand Down Expand Up @@ -48,7 +48,7 @@ library AssertBytes {
for {
let cc := add(_b, 0x20)
// the next line is the loop condition:
// while(uint(mc < end) + cb == 2)
// while(uint256(mc < end) + cb == 2)
} eq(add(lt(mc, end), cb), 2) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
Expand Down Expand Up @@ -104,7 +104,7 @@ library AssertBytes {

assembly {
// we know _a_offset is 0
let fslot := sload(_a_slot)
let fslot := sload(_a.slot)
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
let mlength := mload(_b)

Expand Down Expand Up @@ -133,14 +133,14 @@ library AssertBytes {
let cb := 1

// get the keccak hash to get the contents of the array
mstore(0x0, _a_slot)
mstore(0x0, _a.slot)
let sc := keccak256(0x0, 0x20)

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

// the next line is the loop condition:
// while(uint(mc < end) + cb == 2)
// while(uint256(mc < end) + cb == 2)
for {} eq(add(lt(mc, end), cb), 2) {
sc := add(sc, 1)
mc := add(mc, 0x20)
Expand Down
68 changes: 36 additions & 32 deletions contracts/BytesLib.sol
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
* The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
*/
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.8.0 <0.9.0;


library BytesLib {
Expand Down Expand Up @@ -93,7 +93,7 @@ library BytesLib {
// 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)
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
Expand All @@ -113,7 +113,7 @@ library BytesLib {
// update the contents of the slot.
// uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
sstore(
_preBytes_slot,
_preBytes.slot,
// all the modifications to the slot are inside this
// next block
add(
Expand Down Expand Up @@ -143,11 +143,11 @@ library BytesLib {
// 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)
mstore(0x0, _preBytes.slot)
let sc := add(keccak256(0x0, 0x20), div(slength, 32))

// save new length
sstore(_preBytes_slot, add(mul(newlength, 2), 1))
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`
Expand Down Expand Up @@ -190,12 +190,12 @@ library BytesLib {
}
default {
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
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))
sstore(_preBytes.slot, add(mul(newlength, 2), 1))

// Copy over the first `submod` bytes of the new data as in
// case 1 above.
Expand Down Expand Up @@ -227,14 +227,15 @@ library BytesLib {

function slice(
bytes memory _bytes,
uint _start,
uint _length
uint256 _start,
uint256 _length
)
internal
pure
returns (bytes memory)
{
require(_bytes.length >= (_start + _length));
require(_length + 31 >= _length, "slice_overflow");
require(_bytes.length >= _start + _length, "slice_outOfBounds");

bytes memory tempBytes;

Expand Down Expand Up @@ -282,6 +283,9 @@ library BytesLib {
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)

mstore(0x40, add(tempBytes, 0x20))
}
Expand All @@ -290,8 +294,8 @@ library BytesLib {
return tempBytes;
}

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

assembly {
Expand All @@ -301,8 +305,8 @@ library BytesLib {
return tempAddress;
}

function toUint8(bytes memory _bytes, uint _start) internal pure returns (uint8) {
require(_bytes.length >= (_start + 1));
function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
require(_bytes.length >= _start + 1 , "toUint8_outOfBounds");
uint8 tempUint;

assembly {
Expand All @@ -312,8 +316,8 @@ library BytesLib {
return tempUint;
}

function toUint16(bytes memory _bytes, uint _start) internal pure returns (uint16) {
require(_bytes.length >= (_start + 2));
function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {
require(_bytes.length >= _start + 2, "toUint16_outOfBounds");
uint16 tempUint;

assembly {
Expand All @@ -323,8 +327,8 @@ library BytesLib {
return tempUint;
}

function toUint32(bytes memory _bytes, uint _start) internal pure returns (uint32) {
require(_bytes.length >= (_start + 4));
function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {
require(_bytes.length >= _start + 4, "toUint32_outOfBounds");
uint32 tempUint;

assembly {
Expand All @@ -334,8 +338,8 @@ library BytesLib {
return tempUint;
}

function toUint64(bytes memory _bytes, uint _start) internal pure returns (uint64) {
require(_bytes.length >= (_start + 8));
function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {
require(_bytes.length >= _start + 8, "toUint64_outOfBounds");
uint64 tempUint;

assembly {
Expand All @@ -345,8 +349,8 @@ library BytesLib {
return tempUint;
}

function toUint96(bytes memory _bytes, uint _start) internal pure returns (uint96) {
require(_bytes.length >= (_start + 12));
function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {
require(_bytes.length >= _start + 12, "toUint96_outOfBounds");
uint96 tempUint;

assembly {
Expand All @@ -356,8 +360,8 @@ library BytesLib {
return tempUint;
}

function toUint128(bytes memory _bytes, uint _start) internal pure returns (uint128) {
require(_bytes.length >= (_start + 16));
function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {
require(_bytes.length >= _start + 16, "toUint128_outOfBounds");
uint128 tempUint;

assembly {
Expand All @@ -367,8 +371,8 @@ library BytesLib {
return tempUint;
}

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

assembly {
Expand All @@ -378,8 +382,8 @@ library BytesLib {
return tempUint;
}

function toBytes32(bytes memory _bytes, uint _start) internal pure returns (bytes32) {
require(_bytes.length >= (_start + 32));
function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {
require(_bytes.length >= _start + 32, "toBytes32_outOfBounds");
bytes32 tempBytes32;

assembly {
Expand Down Expand Up @@ -410,7 +414,7 @@ library BytesLib {
for {
let cc := add(_postBytes, 0x20)
// the next line is the loop condition:
// while(uint(mc < end) + cb == 2)
// while(uint256(mc < end) + cb == 2)
} eq(add(lt(mc, end), cb), 2) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
Expand Down Expand Up @@ -444,7 +448,7 @@ library BytesLib {

assembly {
// we know _preBytes_offset is 0
let fslot := sload(_preBytes_slot)
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 Down Expand Up @@ -474,14 +478,14 @@ library BytesLib {
let cb := 1

// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
mstore(0x0, _preBytes.slot)
let sc := keccak256(0x0, 0x20)

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

// the next line is the loop condition:
// while(uint(mc < end) + cb == 2)
// while(uint256(mc < end) + cb == 2)
for {} eq(add(lt(mc, end), cb), 2) {
sc := add(sc, 1)
mc := add(mc, 0x20)
Expand Down
9 changes: 5 additions & 4 deletions contracts/Migrations.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// SPDX-License-Identifier: Unlicense
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.8.0;



contract Migrations {
address public owner;
uint public last_completed_migration;
uint256 public last_completed_migration;

modifier restricted() {
if (msg.sender == owner) _;
}

constructor () public {
constructor () {
owner = msg.sender;
}

function setCompleted(uint completed) public restricted {
function setCompleted(uint256 completed) public restricted {
last_completed_migration = completed;
}

Expand Down
2 changes: 1 addition & 1 deletion ethpm.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"package_name": "bytes",
"version": "0.0.9",
"version": "0.8.0",
"description": "Solidity bytes tightly packed arrays utility library.",
"authors": [
"Gonçalo Sá <[email protected]>"
Expand Down
Loading

0 comments on commit 5d251ad

Please sign in to comment.