@0x:contracts-utils Added RichErrors to LibBytes

This commit is contained in:
James Towle 2019-07-08 21:27:17 -05:00 committed by Amir Bandeali
parent c788db785b
commit d3db2dcfbb
8 changed files with 479 additions and 166 deletions

View File

@ -13,7 +13,7 @@ import {
import { BlockchainLifecycle } from '@0x/dev-utils';
import { transactionHashUtils } from '@0x/order-utils';
import { EIP712DomainWithDefaultSchema, RevertReason, SignatureType, SignedOrder } from '@0x/types';
import { BigNumber, providerUtils } from '@0x/utils';
import { BigNumber, LibBytesRevertErrors, providerUtils } from '@0x/utils';
import * as chai from 'chai';
import * as ethUtil from 'ethereumjs-util';
@ -206,10 +206,12 @@ describe('Mixins tests', () => {
});
it('should revert if data is less than 4 bytes long', async () => {
const data = '0x010203';
await expectContractCallFailedAsync(
mixins.decodeOrdersFromFillData.callAsync(data),
RevertReason.LibBytesGreaterOrEqualTo4LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsFourRequired,
new BigNumber(3), // the length of data
new BigNumber(4),
);
return expect(mixins.decodeOrdersFromFillData.callAsync(data)).to.revertWith(expectedError);
});
});

View File

@ -1,6 +1,6 @@
/*
Copyright 2018 ZeroEx Intl.
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -18,6 +18,8 @@
pragma solidity ^0.5.9;
import "./LibBytesRichErrors.sol";
library LibBytes {
@ -38,7 +40,7 @@ library LibBytes {
}
return memoryAddress;
}
/// @dev Gets the memory address for the contents of a byte array.
/// @param input Byte array to lookup.
/// @return memoryAddress Memory address of the contents of the byte array.
@ -121,7 +123,7 @@ library LibBytes {
source := add(source, 32)
dest := add(dest, 32)
}
// Write the last 32 bytes
mstore(dEnd, last)
}
@ -152,7 +154,7 @@ library LibBytes {
sEnd := sub(sEnd, 32)
dEnd := sub(dEnd, 32)
}
// Write the first 32 bytes
mstore(dest, first)
}
@ -174,15 +176,23 @@ library LibBytes {
pure
returns (bytes memory result)
{
require(
from <= to,
"FROM_LESS_THAN_TO_REQUIRED"
);
require(
to <= b.length,
"TO_LESS_THAN_LENGTH_REQUIRED"
);
// Ensure that the from and to positions are valid positions for a slice within
// the byte array that is being used.
if (from > to) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.FromLessThanOrEqualsToRequired,
from,
to
);
}
if (to > b.length) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.ToLessThanOrEqualsLengthRequired,
to,
b.length
);
}
// Create a new bytes structure and copy contents
result = new bytes(to - from);
memCopy(
@ -192,7 +202,7 @@ library LibBytes {
);
return result;
}
/// @dev Returns a slice from a byte array without preserving the input.
/// @param b The byte array to take a slice from. Will be destroyed in the process.
/// @param from The starting index for the slice (inclusive).
@ -208,15 +218,23 @@ library LibBytes {
pure
returns (bytes memory result)
{
require(
from <= to,
"FROM_LESS_THAN_TO_REQUIRED"
);
require(
to <= b.length,
"TO_LESS_THAN_LENGTH_REQUIRED"
);
// Ensure that the from and to positions are valid positions for a slice within
// the byte array that is being used.
if (from > to) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.FromLessThanOrEqualsToRequired,
from,
to
);
}
if (to > b.length) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.ToLessThanOrEqualsLengthRequired,
to,
b.length
);
}
// Create a new bytes structure around [from, to) in-place.
assembly {
result := add(b, from)
@ -233,10 +251,13 @@ library LibBytes {
pure
returns (bytes1 result)
{
require(
b.length > 0,
"GREATER_THAN_ZERO_LENGTH_REQUIRED"
);
if (b.length == 0) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanZeroRequired,
b.length,
0
);
}
// Store last byte.
result = b[b.length - 1];
@ -257,10 +278,13 @@ library LibBytes {
pure
returns (address result)
{
require(
b.length >= 20,
"GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"
);
if (b.length < 20) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired,
b.length,
20 // 20 is length of address
);
}
// Store last 20 bytes.
result = readAddress(b, b.length - 20);
@ -303,10 +327,13 @@ library LibBytes {
pure
returns (address result)
{
require(
b.length >= index + 20, // 20 is length of address
"GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"
);
if (b.length < index + 20) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired,
b.length,
index + 20 // 20 is length of address
);
}
// Add offset to index:
// 1. Arrays are prefixed by 32-byte length parameter (add 32 to index)
@ -335,10 +362,13 @@ library LibBytes {
internal
pure
{
require(
b.length >= index + 20, // 20 is length of address
"GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"
);
if (b.length < index + 20) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired,
b.length,
index + 20 // 20 is length of address
);
}
// Add offset to index:
// 1. Arrays are prefixed by 32-byte length parameter (add 32 to index)
@ -359,7 +389,7 @@ library LibBytes {
mload(add(b, index)),
0xffffffffffffffffffffffff0000000000000000000000000000000000000000
)
// Make sure input address is clean.
// (Solidity does not guarantee this)
input := and(input, 0xffffffffffffffffffffffffffffffffffffffff)
@ -381,10 +411,13 @@ library LibBytes {
pure
returns (bytes32 result)
{
require(
b.length >= index + 32,
"GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED"
);
if (b.length < index + 32) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
b.length,
index + 32
);
}
// Arrays are prefixed by a 256 bit length parameter
index += 32;
@ -408,10 +441,13 @@ library LibBytes {
internal
pure
{
require(
b.length >= index + 32,
"GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED"
);
if (b.length < index + 32) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
b.length,
index + 32
);
}
// Arrays are prefixed by a 256 bit length parameter
index += 32;
@ -465,10 +501,13 @@ library LibBytes {
pure
returns (bytes4 result)
{
require(
b.length >= index + 4,
"GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED"
);
if (b.length < index + 4) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsFourRequired,
b.length,
index + 4
);
}
// Arrays are prefixed by a 32 byte length field
index += 32;
@ -503,11 +542,15 @@ library LibBytes {
// Assert length of <b> is valid, given
// length of nested bytes
require(
b.length >= index + nestedBytesLength,
"GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED"
);
if (b.length < index + nestedBytesLength) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors
.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsNestedBytesLengthRequired,
b.length,
index + nestedBytesLength
);
}
// Return a pointer to the byte array as it exists inside `b`
assembly {
result := add(b, index)
@ -529,10 +572,14 @@ library LibBytes {
{
// Assert length of <b> is valid, given
// length of input
require(
b.length >= index + 32 + input.length, // 32 bytes to store length
"GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED"
);
if (b.length < index + 32 + input.length) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors
.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsNestedBytesLengthRequired,
b.length,
index + 32 + input.length // 32 bytes to store length
);
}
// Copy <input> into <b>
memCopy(
@ -554,10 +601,14 @@ library LibBytes {
{
uint256 sourceLen = source.length;
// Dest length must be >= source length, or some bytes would not be copied.
require(
dest.length >= sourceLen,
"GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED"
);
if (dest.length < sourceLen) {
LibBytesRichErrors.InvalidByteOperationErrorRevert(
LibBytesRichErrors
.InvalidByteOperationErrorCodes.DestinationLengthGreaterThanOrEqualSourceLengthRequired,
dest.length,
sourceLen
);
}
memCopy(
dest.contentAddress(),
source.contentAddress(),

View File

@ -0,0 +1,59 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "./LibRichErrors.sol";
library LibBytesRichErrors {
using LibRichErrors for *;
enum InvalidByteOperationErrorCodes {
FromLessThanOrEqualsToRequired,
ToLessThanOrEqualsLengthRequired,
LengthGreaterThanZeroRequired,
LengthGreaterThanOrEqualsFourRequired,
LengthGreaterThanOrEqualsTwentyRequired,
LengthGreaterThanOrEqualsThirtyTwoRequired,
LengthGreaterThanOrEqualsNestedBytesLengthRequired,
DestinationLengthGreaterThanOrEqualSourceLengthRequired
}
// bytes4(keccak256("InvalidByteOperationError(uint8,uint256,uint256)"))
bytes4 internal constant INVALID_BYTE_OPERATION_ERROR_SELECTOR =
0x28006595;
// solhint-disable func-name-mixedcase
function InvalidByteOperationErrorRevert(
InvalidByteOperationErrorCodes errorCode,
uint256 endpoint,
uint256 required
)
internal
pure
{
abi.encodeWithSelector(
INVALID_BYTE_OPERATION_ERROR_SELECTOR,
errorCode,
endpoint,
required
)._rrevert();
}
}

View File

@ -0,0 +1,58 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
library LibRichErrors {
// bytes4(keccak256("Error(string)"))
bytes4 internal constant STANDARD_ERROR_SELECTOR =
0x08c379a0;
// solhint-disable func-name-mixedcase
/// @dev ABI encode a standard, string revert error payload.
/// This is the same payload that would be included by a `revert(string)`
/// solidity statement. It has the function signature `Error(string)`.
/// @param message The error string.
/// @return The ABI encoded error.
function StandardError(
string memory message
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
STANDARD_ERROR_SELECTOR,
bytes(message)
);
}
// solhint-enable func-name-mixedcase
/// @dev Reverts an encoded rich revert reason `errorData`.
/// @param errorData ABI encoded error data.
function _rrevert(bytes memory errorData)
internal
pure
{
assembly {
revert(add(errorData, 0x20), mload(errorData))
}
}
}

View File

@ -22,9 +22,20 @@ import "../src/LibBytes.sol";
contract TestLibBytes {
using LibBytes for bytes;
/// @dev Pops the last byte off of a byte array by modifying its length.
/// @param b Byte array that will be modified.
/// @return The byte that was popped off.
function publicPopLastByteStateful(bytes memory b)
public
returns (bytes memory, bytes1 result)
{
result = b.popLastByte();
return (b, result);
}
/// @dev Pops the last byte off of a byte array by modifying its length.
/// @param b Byte array that will be modified.
/// @return The byte that was popped off.
@ -61,7 +72,7 @@ contract TestLibBytes {
equal = lhs.equals(rhs);
return equal;
}
function publicEqualsPop1(bytes memory lhs, bytes memory rhs)
public
pure
@ -236,7 +247,7 @@ contract TestLibBytes {
b.writeBytesWithLength(index, input);
return b;
}
/// @dev Copies a block of memory from one location to another.
/// @param mem Memory contents we want to apply memCopy to
/// @param dest Destination offset into <mem>.

View File

@ -1,16 +1,7 @@
import {
chaiSetup,
constants,
expectContractCallFailedAsync,
provider,
txDefaults,
typeEncodingUtils,
web3Wrapper,
} from '@0x/contracts-test-utils';
import { chaiSetup, constants, provider, txDefaults, typeEncodingUtils, web3Wrapper } from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { generatePseudoRandomSalt } from '@0x/order-utils';
import { RevertReason } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { BigNumber, LibBytesRevertErrors } from '@0x/utils';
import BN = require('bn.js');
import * as chai from 'chai';
import ethUtil = require('ethereumjs-util');
@ -106,10 +97,12 @@ describe('LibBytes', () => {
describe('popLastByte', () => {
it('should revert if length is 0', async () => {
return expectContractCallFailedAsync(
libBytes.publicPopLastByte.callAsync(constants.NULL_BYTES),
RevertReason.LibBytesGreaterThanZeroLengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanZeroRequired,
constants.ZERO_AMOUNT,
constants.ZERO_AMOUNT,
);
return expect(libBytes.publicPopLastByte.callAsync(constants.NULL_BYTES)).to.revertWith(expectedError);
});
it('should pop the last byte from the input and return it when array holds more than 1 byte', async () => {
const [newBytes, poppedByte] = await libBytes.publicPopLastByte.callAsync(byteArrayLongerThan32Bytes);
@ -128,9 +121,14 @@ describe('LibBytes', () => {
describe('popLast20Bytes', () => {
it('should revert if length is less than 20', async () => {
return expectContractCallFailedAsync(
libBytes.publicPopLast20Bytes.callAsync(byteArrayShorterThan20Bytes),
RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
const byteLen = (byteArrayShorterThan20Bytes.length - 2) / 2;
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired,
new BigNumber(byteLen), // length of byteArrayShorterThan20Byte
new BigNumber(20),
);
return expect(libBytes.publicPopLast20Bytes.callAsync(byteArrayShorterThan20Bytes)).to.revertWith(
expectedError,
);
});
it('should pop the last 20 bytes from the input and return it when array holds more than 20 bytes', async () => {
@ -203,10 +201,16 @@ describe('LibBytes', () => {
describe('deepCopyBytes', () => {
it('should revert if dest is shorter than source', async () => {
return expectContractCallFailedAsync(
libBytes.publicDeepCopyBytes.callAsync(byteArrayShorterThan32Bytes, byteArrayLongerThan32Bytes),
RevertReason.LibBytesGreaterOrEqualToSourceBytesLengthRequired,
const endpointByteLen = (byteArrayShorterThan20Bytes.length - 2) / 2;
const requiredByteLen = (byteArrayLongerThan32Bytes.length - 2) / 2;
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.DestinationLengthGreaterThanOrEqualSourceLengthRequired,
new BigNumber(endpointByteLen), // length of byteArrayShorterThan20Byte
new BigNumber(requiredByteLen), // length of byteArrayShorterThan20Byte
);
return expect(
libBytes.publicDeepCopyBytes.callAsync(byteArrayShorterThan32Bytes, byteArrayLongerThan32Bytes),
).to.revertWith(expectedError);
});
it('should overwrite dest with source if source and dest have equal length', async () => {
const zeroedByteArrayLongerThan32Bytes = `0x${_.repeat('0', byteArrayLongerThan32Bytes.length - 2)}`;
@ -256,18 +260,22 @@ describe('LibBytes', () => {
it('should fail if the byte array is too short to hold an address', async () => {
const shortByteArray = '0xabcdef';
const offset = new BigNumber(0);
return expectContractCallFailedAsync(
libBytes.publicReadAddress.callAsync(shortByteArray, offset),
RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired,
new BigNumber(3),
new BigNumber(20),
);
return expect(libBytes.publicReadAddress.callAsync(shortByteArray, offset)).to.revertWith(expectedError);
});
it('should fail if the length between the offset and end of the byte array is too short to hold an address', async () => {
const byteArray = testAddress;
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectContractCallFailedAsync(
libBytes.publicReadAddress.callAsync(byteArray, badOffset),
RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired,
new BigNumber(20),
new BigNumber(40),
);
return expect(libBytes.publicReadAddress.callAsync(byteArray, badOffset)).to.revertWith(expectedError);
});
});
@ -300,17 +308,26 @@ describe('LibBytes', () => {
});
it('should fail if the byte array is too short to hold an address', async () => {
const offset = new BigNumber(0);
return expectContractCallFailedAsync(
libBytes.publicWriteAddress.callAsync(byteArrayShorterThan20Bytes, offset, testAddress),
RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
const byteLen = ethUtil.toBuffer(byteArrayShorterThan20Bytes).byteLength;
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired,
new BigNumber(byteLen),
new BigNumber(20),
);
return expect(
libBytes.publicWriteAddress.callAsync(byteArrayShorterThan20Bytes, offset, testAddress),
).to.revertWith(expectedError);
});
it('should fail if the length between the offset and end of the byte array is too short to hold an address', async () => {
const byteArray = byteArrayLongerThan32Bytes;
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectContractCallFailedAsync(
libBytes.publicWriteAddress.callAsync(byteArray, badOffset, testAddress),
RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired,
badOffset,
badOffset.plus(new BigNumber(20)),
);
return expect(libBytes.publicWriteAddress.callAsync(byteArray, badOffset, testAddress)).to.revertWith(
expectedError,
);
});
});
@ -332,17 +349,24 @@ describe('LibBytes', () => {
});
it('should fail if the byte array is too short to hold a bytes32', async () => {
const offset = new BigNumber(0);
return expectContractCallFailedAsync(
libBytes.publicReadBytes32.callAsync(byteArrayShorterThan32Bytes, offset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const byteLen = new BigNumber((byteArrayShorterThan32Bytes.length - 2) / 2);
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
byteLen,
new BigNumber(32),
);
return expect(libBytes.publicReadBytes32.callAsync(byteArrayShorterThan32Bytes, offset)).to.revertWith(
expectedError,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength);
return expectContractCallFailedAsync(
libBytes.publicReadBytes32.callAsync(testBytes32, badOffset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
badOffset,
badOffset.plus(new BigNumber(32)),
);
return expect(libBytes.publicReadBytes32.callAsync(testBytes32, badOffset)).to.revertWith(expectedError);
});
});
@ -375,17 +399,26 @@ describe('LibBytes', () => {
});
it('should fail if the byte array is too short to hold a bytes32', async () => {
const offset = new BigNumber(0);
return expectContractCallFailedAsync(
libBytes.publicWriteBytes32.callAsync(byteArrayShorterThan32Bytes, offset, testBytes32),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const byteLen = new BigNumber((byteArrayShorterThan32Bytes.length - 2) / 2);
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
byteLen,
new BigNumber(32),
);
return expect(
libBytes.publicWriteBytes32.callAsync(byteArrayShorterThan32Bytes, offset, testBytes32),
).to.revertWith(expectedError);
});
it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32', async () => {
const byteArray = byteArrayLongerThan32Bytes;
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectContractCallFailedAsync(
libBytes.publicWriteBytes32.callAsync(byteArray, badOffset, testBytes32),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
badOffset,
badOffset.plus(new BigNumber(32)),
);
return expect(libBytes.publicWriteBytes32.callAsync(byteArray, badOffset, testBytes32)).to.revertWith(
expectedError,
);
});
});
@ -411,9 +444,14 @@ describe('LibBytes', () => {
});
it('should fail if the byte array is too short to hold a uint256', async () => {
const offset = new BigNumber(0);
return expectContractCallFailedAsync(
libBytes.publicReadUint256.callAsync(byteArrayShorterThan32Bytes, offset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const byteLen = new BigNumber((byteArrayShorterThan32Bytes.length - 2) / 2);
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
byteLen,
new BigNumber(32),
);
return expect(libBytes.publicReadUint256.callAsync(byteArrayShorterThan32Bytes, offset)).to.revertWith(
expectedError,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold a uint256', async () => {
@ -421,10 +459,12 @@ describe('LibBytes', () => {
const testUint256AsBuffer = ethUtil.toBuffer(formattedTestUint256);
const byteArray = ethUtil.bufferToHex(testUint256AsBuffer);
const badOffset = new BigNumber(testUint256AsBuffer.byteLength);
return expectContractCallFailedAsync(
libBytes.publicReadUint256.callAsync(byteArray, badOffset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
badOffset,
badOffset.plus(new BigNumber(32)),
);
return expect(libBytes.publicReadUint256.callAsync(byteArray, badOffset)).to.revertWith(expectedError);
});
});
@ -461,17 +501,26 @@ describe('LibBytes', () => {
});
it('should fail if the byte array is too short to hold a uint256', async () => {
const offset = new BigNumber(0);
return expectContractCallFailedAsync(
libBytes.publicWriteUint256.callAsync(byteArrayShorterThan32Bytes, offset, testUint256),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const byteLen = new BigNumber((byteArrayShorterThan32Bytes.length - 2) / 2);
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
byteLen,
new BigNumber(32),
);
return expect(
libBytes.publicWriteUint256.callAsync(byteArrayShorterThan32Bytes, offset, testUint256),
).to.revertWith(expectedError);
});
it('should fail if the length between the offset and end of the byte array is too short to hold a uint256', async () => {
const byteArray = byteArrayLongerThan32Bytes;
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectContractCallFailedAsync(
libBytes.publicWriteUint256.callAsync(byteArray, badOffset, testUint256),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
badOffset,
badOffset.plus(new BigNumber(32)),
);
return expect(libBytes.publicWriteUint256.callAsync(byteArray, badOffset, testUint256)).to.revertWith(
expectedError,
);
});
});
@ -480,10 +529,15 @@ describe('LibBytes', () => {
// AssertionError: expected promise to be rejected with an error including 'revert' but it was fulfilled with '0x08c379a0'
it('should revert if byte array has a length < 4', async () => {
const byteArrayLessThan4Bytes = '0x010101';
const byteLen = (byteArrayLessThan4Bytes.length - 2) / 2;
const offset = new BigNumber(0);
return expectContractCallFailedAsync(
libBytes.publicReadBytes4.callAsync(byteArrayLessThan4Bytes, offset),
RevertReason.LibBytesGreaterOrEqualTo4LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsFourRequired,
new BigNumber(byteLen), // length of byteArrayLessThan4Bytes
new BigNumber(4),
);
return expect(libBytes.publicReadBytes4.callAsync(byteArrayLessThan4Bytes, offset)).to.revertWith(
expectedError,
);
});
it('should return the first 4 bytes of a byte array of arbitrary length', async () => {
@ -507,10 +561,13 @@ describe('LibBytes', () => {
});
it('should fail if the length between the offset and end of the byte array is too short to hold a bytes4', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(testBytes4).byteLength);
return expectContractCallFailedAsync(
libBytes.publicReadBytes4.callAsync(testBytes4, badOffset),
RevertReason.LibBytesGreaterOrEqualTo4LengthRequired,
const byteLen = new BigNumber((testBytes4.length - 2) / 2);
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsFourRequired,
byteLen,
badOffset.plus(new BigNumber(4)),
);
return expect(libBytes.publicReadBytes4.callAsync(testBytes4, badOffset)).to.revertWith(expectedError);
});
});
@ -557,30 +614,48 @@ describe('LibBytes', () => {
it('should fail if the byte array is too short to hold the length of a nested byte array', async () => {
// The length of the nested array is 32 bytes. By storing less than 32 bytes, a length cannot be read.
const offset = new BigNumber(0);
return expectContractCallFailedAsync(
libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, offset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const byteLen = new BigNumber((byteArrayShorterThan32Bytes.length - 2) / 2);
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
byteLen,
new BigNumber(32),
);
return expect(
libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, offset),
).to.revertWith(expectedError);
});
it('should fail if we store a nested byte array length, without a nested byte array', async () => {
const offset = new BigNumber(0);
return expectContractCallFailedAsync(
libBytes.publicReadBytesWithLength.callAsync(testBytes32, offset),
RevertReason.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
const byteLen = new BigNumber((testBytes32.length - 2) / 2);
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsNestedBytesLengthRequired,
byteLen,
(new BigNumber(testBytes32)).plus(32),
);
return expect(libBytes.publicReadBytesWithLength.callAsync(testBytes32, offset)).to.revertWith(
expectedError,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(byteArrayShorterThan32Bytes).byteLength);
return expectContractCallFailedAsync(
libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, badOffset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
badOffset,
badOffset.plus(new BigNumber(32)),
);
return expect(
libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, badOffset),
).to.revertWith(expectedError);
});
it('should fail if the length between the offset and end of the byte array is too short to hold the nested byte array', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength);
return expectContractCallFailedAsync(
libBytes.publicReadBytesWithLength.callAsync(testBytes32, badOffset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
badOffset,
badOffset.plus(new BigNumber(32)),
);
return expect(libBytes.publicReadBytesWithLength.callAsync(testBytes32, badOffset)).to.revertWith(
expectedError,
);
});
});
@ -690,18 +765,28 @@ describe('LibBytes', () => {
it('should fail if the byte array is too short to hold the length of a nested byte array', async () => {
const offset = new BigNumber(0);
const emptyByteArray = ethUtil.bufferToHex(new Buffer(1));
return expectContractCallFailedAsync(
libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, offset, longData),
RevertReason.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
const inputLen = new BigNumber((longData.length - 2) / 2);
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsNestedBytesLengthRequired,
new BigNumber(1),
new BigNumber(32).plus(inputLen),
);
return expect(
libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, offset, longData),
).to.revertWith(expectedError);
});
it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array', async () => {
const emptyByteArray = ethUtil.bufferToHex(new Buffer(shortTestBytesAsBuffer.byteLength));
const badOffset = new BigNumber(ethUtil.toBuffer(shortTestBytesAsBuffer).byteLength);
return expectContractCallFailedAsync(
libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, badOffset, shortData),
RevertReason.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
const inputLen = new BigNumber((shortData.length - 2) / 2);
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsNestedBytesLengthRequired,
badOffset,
badOffset.plus(new BigNumber(32)).plus(inputLen),
);
return expect(
libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, badOffset, shortData),
).to.revertWith(expectedError);
});
});
@ -875,9 +960,13 @@ describe('LibBytes', () => {
it('should revert if from > to', async () => {
const from = new BigNumber(1);
const to = new BigNumber(0);
expectContractCallFailedAsync(
libBytes.publicSlice.callAsync(byteArrayLongerThan32Bytes, from, to),
RevertReason.FromLessThanToRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.FromLessThanOrEqualsToRequired,
from,
to,
);
return expect(libBytes.publicSlice.callAsync(byteArrayLongerThan32Bytes, from, to)).to.revertWith(
expectedError,
);
});
it('should return a byte array of length 0 if from == to', async () => {
@ -896,12 +985,16 @@ describe('LibBytes', () => {
expect(result).to.eq(constants.NULL_BYTES);
});
it('should revert if to > input.length', async () => {
const byteLen = (byteArrayLongerThan32Bytes.length - 2) / 2;
const byteLen: number = (byteArrayLongerThan32Bytes.length - 2) / 2;
const from = new BigNumber(0);
const to = new BigNumber(byteLen).plus(1);
expectContractCallFailedAsync(
libBytes.publicSlice.callAsync(byteArrayLongerThan32Bytes, from, to),
RevertReason.ToLessThanLengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.ToLessThanOrEqualsLengthRequired,
to,
new BigNumber(byteLen),
);
return expect(libBytes.publicSlice.callAsync(byteArrayLongerThan32Bytes, from, to)).to.revertWith(
expectedError,
);
});
it('should slice a section of the input', async () => {
@ -926,9 +1019,13 @@ describe('LibBytes', () => {
it('should revert if from > to', async () => {
const from = new BigNumber(1);
const to = new BigNumber(0);
expectContractCallFailedAsync(
libBytes.publicSliceDestructive.callAsync(byteArrayLongerThan32Bytes, from, to),
RevertReason.FromLessThanToRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.FromLessThanOrEqualsToRequired,
from,
to,
);
return expect(libBytes.publicSlice.callAsync(byteArrayLongerThan32Bytes, from, to)).to.revertWith(
expectedError,
);
});
it('should return a byte array of length 0 if from == to', async () => {
@ -948,10 +1045,14 @@ describe('LibBytes', () => {
const byteLen = (byteArrayLongerThan32Bytes.length - 2) / 2;
const from = new BigNumber(0);
const to = new BigNumber(byteLen).plus(1);
expectContractCallFailedAsync(
libBytes.publicSliceDestructive.callAsync(byteArrayLongerThan32Bytes, from, to),
RevertReason.ToLessThanLengthRequired,
const expectedError = new LibBytesRevertErrors.InvalidByteOperationError(
LibBytesRevertErrors.InvalidByteOperationErrorCodes.ToLessThanOrEqualsLengthRequired,
to,
new BigNumber(byteLen),
);
return expect(
libBytes.publicSliceDestructive.callAsync(byteArrayLongerThan32Bytes, from, to),
).to.revertWith(expectedError);
});
it('should slice a section of the input', async () => {
const from = new BigNumber(1);

View File

@ -1,3 +1,4 @@
import * as LibBytesRevertErrors from './lib_bytes_revert_errors';
import * as OwnableRevertErrors from './ownable_revert_errors';
import * as SafeMathRevertErrors from './safe_math_revert_errors';
@ -28,4 +29,4 @@ export {
AnyRevertError,
} from './revert_error';
export { OwnableRevertErrors, SafeMathRevertErrors };
export { LibBytesRevertErrors, OwnableRevertErrors, SafeMathRevertErrors };

View File

@ -0,0 +1,30 @@
import { BigNumber } from './configured_bignumber';
import { RevertError } from './revert_error';
export enum InvalidByteOperationErrorCodes {
FromLessThanOrEqualsToRequired,
ToLessThanOrEqualsLengthRequired,
LengthGreaterThanZeroRequired,
LengthGreaterThanOrEqualsFourRequired,
LengthGreaterThanOrEqualsTwentyRequired,
LengthGreaterThanOrEqualsThirtyTwoRequired,
LengthGreaterThanOrEqualsNestedBytesLengthRequired,
DestinationLengthGreaterThanOrEqualSourceLengthRequired,
}
export class InvalidByteOperationError extends RevertError {
constructor(error?: InvalidByteOperationErrorCodes, endpoint?: BigNumber, required?: BigNumber) {
super(
'InvalidByteOperationError',
'InvalidByteOperationError(uint8 error, uint256 endpoint, uint256 required)',
{
error,
endpoint,
required,
},
);
}
}
// Register the InvalidByteOperationError type
RevertError.registerType(InvalidByteOperationError);