From a5a7217c8f24fe98275fd9927ca8c5e5f140c6f3 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Wed, 9 May 2018 16:27:50 -0700 Subject: Add deepCopyBytes method to LibBytes --- .../current/test/TestLibBytes/TestLibBytes.sol | 17 +++++++++ .../contracts/current/utils/LibBytes/LibBytes.sol | 41 ++++++++++++++++++++++ packages/contracts/test/libraries/lib_bytes.ts | 34 ++++++++++++++++++ 3 files changed, 92 insertions(+) (limited to 'packages') diff --git a/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol b/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol index 5a6801262..b3ed1f620 100644 --- a/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol +++ b/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol @@ -25,6 +25,23 @@ contract TestLibBytes is LibBytes { + /// @dev Performs a deep copy of a section of a byte array. + /// @param b Byte array that will be sliced. + /// @param startIndex Index of first byte to copy. + /// @param endIndex Index of last byte to copy + 1. + /// @return A deep copy of b from startIndex to endIndex. + function publicDeepCopyBytes( + bytes memory b, + uint256 startIndex, + uint256 endIndex) + public + pure + returns (bytes memory copy) + { + copy = deepCopyBytes(b, startIndex, endIndex); + return copy; + } + /// @dev Tests equality of two byte arrays. /// @param lhs First byte array to compare. /// @param rhs Second byte array to compare. diff --git a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol index 975565773..527f7a7a9 100644 --- a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol +++ b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol @@ -21,9 +21,50 @@ pragma solidity ^0.4.24; contract LibBytes { // Revert reasons + string constant GT_ZERO_LENGTH_REQUIRED = "Length must be greater than 0."; string constant GTE_4_LENGTH_REQUIRED = "Length must be greater than or equal to 4."; string constant GTE_20_LENGTH_REQUIRED = "Length must be greater than or equal to 20."; string constant GTE_32_LENGTH_REQUIRED = "Length must be greater than or equal to 32."; + string constant INDEX_OUT_OF_BOUNDS = "Specified array index is out of bounds."; + + /// @dev Performs a deep copy of a section of a byte array. + /// @param b Byte array that will be copied. + /// @param index Index of first byte to copy. + /// @param len Number of bytes to copy starting from index. + /// @return A deep copy `len` bytes of b starting from index. + function deepCopyBytes( + bytes memory b, + uint256 index, + uint256 len) + internal + pure + returns (bytes memory) + { + require( + len > 0, + GT_ZERO_LENGTH_REQUIRED + ); + require( + b.length >= index + len, + INDEX_OUT_OF_BOUNDS + ); + + bytes memory copy = new bytes(len); + + // Arrays are prefixed by a 256 bit length parameter + index += 32; + + assembly { + // Start storing onto copy after length field + let storeStartIndex := add(copy, 32) + // Start loading from b at index + let startLoadIndex := add(b, index) + for {let i := 0} lt(i, len) {i := add(i, 32)} { + mstore(add(storeStartIndex, i), mload(add(startLoadIndex, i))) + } + } + return copy; + } /// @dev Tests equality of two byte arrays. /// @param lhs First byte array to compare. diff --git a/packages/contracts/test/libraries/lib_bytes.ts b/packages/contracts/test/libraries/lib_bytes.ts index 968bac300..1fd30a3e0 100644 --- a/packages/contracts/test/libraries/lib_bytes.ts +++ b/packages/contracts/test/libraries/lib_bytes.ts @@ -60,6 +60,40 @@ describe('LibBytes', () => { await blockchainLifecycle.revertAsync(); }); + describe.only('deepCopyBytes', () => { + const byteArrayLongerThan32BytesLen = (byteArrayLongerThan32Bytes.length - 2) / 2; + it('should throw if length of copy is 0', async () => { + const index = new BigNumber(0); + const len = new BigNumber(0); + return expect( + libBytes.publicDeepCopyBytes.callAsync(byteArrayLongerThan32Bytes, index, len), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if start index + length to copy is greater than length of byte array', async () => { + const index = new BigNumber(0); + const len = new BigNumber(byteArrayLongerThan32BytesLen + 1); + return expect( + libBytes.publicDeepCopyBytes.callAsync(byteArrayLongerThan32Bytes, index, len), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should copy the entire byte array if index = 0 and len = b.length', async () => { + const index = new BigNumber(0); + const len = new BigNumber(byteArrayLongerThan32BytesLen); + const copy = await libBytes.publicDeepCopyBytes.callAsync(byteArrayLongerThan32Bytes, index, len); + expect(copy).to.equal(byteArrayLongerThan32Bytes); + }); + + it('should copy part of the byte array if area to copy is less than b.length', async () => { + const index = new BigNumber(10); + const len = new BigNumber(4); + const copy = await libBytes.publicDeepCopyBytes.callAsync(byteArrayLongerThan32Bytes, index, len); + const expectedCopy = `0x${byteArrayLongerThan32Bytes.slice(22, 30)}`; + expect(copy).to.equal(expectedCopy); + }); + }); + describe('areBytesEqual', () => { it('should return true if byte arrays are equal (both arrays < 32 bytes)', async () => { const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync( -- cgit v1.2.3