From 9a3a3027546d9a3ca11cf2ce7e8cede7a8e5c4fe Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Sun, 1 Sep 2019 18:32:52 -0700 Subject: [PATCH] Create writeLength function in LibBytes --- contracts/utils/contracts/src/LibBytes.sol | 14 ++++ .../utils/contracts/test/TestLibBytes.sol | 22 ++++++ contracts/utils/test/lib_bytes.ts | 74 +++++++++++++++++-- 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/contracts/utils/contracts/src/LibBytes.sol b/contracts/utils/contracts/src/LibBytes.sol index 6caefb8bac..f9b9a91455 100644 --- a/contracts/utils/contracts/src/LibBytes.sol +++ b/contracts/utils/contracts/src/LibBytes.sol @@ -616,4 +616,18 @@ library LibBytes { sourceLen ); } + + /// @dev Writes a new length to a byte array. + /// Decreasing length will lead to removing the corresponding lower order bytes from the byte array. + /// Increasing length may lead to appending adjacent in-memory bytes to the end of the byte array. + /// @param b Bytes array to write new length to. + /// @param length New length of byte array. + function writeLength(bytes memory b, uint256 length) + internal + pure + { + assembly { + mstore(b, length) + } + } } diff --git a/contracts/utils/contracts/test/TestLibBytes.sol b/contracts/utils/contracts/test/TestLibBytes.sol index 18457cee1a..ab316b08d8 100644 --- a/contracts/utils/contracts/test/TestLibBytes.sol +++ b/contracts/utils/contracts/test/TestLibBytes.sol @@ -303,4 +303,26 @@ contract TestLibBytes { result = LibBytes.sliceDestructive(b, from, to); return (result, b); } + + /// @dev Returns a byte array with an updated length. + /// @dev Writes a new length to a byte array. + /// Decreasing length will lead to removing the corresponding lower order bytes from the byte array. + /// Increasing length may lead to appending adjacent in-memory bytes to the end of the byte array. + /// @param b Bytes array to write new length to. + /// @param length New length of byte array. + /// @param extraBytes Bytes that are appended to end of b in memory. + function publicWriteLength( + bytes memory b, + uint256 length, + bytes memory extraBytes + ) + public + pure + returns (bytes memory) + { + uint256 bEnd = b.contentAddress() + b.length; + LibBytes.memCopy(bEnd, extraBytes.contentAddress(), extraBytes.length); + b.writeLength(length); + return b; + } } diff --git a/contracts/utils/test/lib_bytes.ts b/contracts/utils/test/lib_bytes.ts index 8fdd3065d8..0d79c05852 100644 --- a/contracts/utils/test/lib_bytes.ts +++ b/contracts/utils/test/lib_bytes.ts @@ -668,7 +668,7 @@ describe('LibBytes', () => { describe('writeBytesWithLength', () => { it('should successfully write short, nested array of bytes when it takes up the whole array', async () => { const testBytesOffset = new BigNumber(0); - const emptyByteArray = ethUtil.bufferToHex(new Buffer(shortTestBytesAsBuffer.byteLength)); + const emptyByteArray = ethUtil.bufferToHex(ethUtil.toBuffer(shortTestBytesAsBuffer.byteLength)); const bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync( emptyByteArray, testBytesOffset, @@ -683,7 +683,7 @@ describe('LibBytes', () => { const prefixDataAsBuffer = ethUtil.toBuffer(prefixData); const prefixOffset = new BigNumber(0); const emptyByteArray = ethUtil.bufferToHex( - new Buffer(prefixDataAsBuffer.byteLength + shortTestBytesAsBuffer.byteLength), + ethUtil.toBuffer(prefixDataAsBuffer.byteLength + shortTestBytesAsBuffer.byteLength), ); let bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync( emptyByteArray, @@ -703,7 +703,7 @@ describe('LibBytes', () => { }); it('should successfully write a nested array of bytes - one word in length - when it takes up the whole array', async () => { const testBytesOffset = new BigNumber(0); - const emptyByteArray = ethUtil.bufferToHex(new Buffer(wordOfTestBytesAsBuffer.byteLength)); + const emptyByteArray = ethUtil.bufferToHex(ethUtil.toBuffer(wordOfTestBytesAsBuffer.byteLength)); const bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync( emptyByteArray, testBytesOffset, @@ -718,7 +718,7 @@ describe('LibBytes', () => { const prefixDataAsBuffer = ethUtil.toBuffer(prefixData); const prefixOffset = new BigNumber(0); const emptyByteArray = ethUtil.bufferToHex( - new Buffer(prefixDataAsBuffer.byteLength + wordOfTestBytesAsBuffer.byteLength), + ethUtil.toBuffer(prefixDataAsBuffer.byteLength + wordOfTestBytesAsBuffer.byteLength), ); let bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync( emptyByteArray, @@ -738,7 +738,7 @@ describe('LibBytes', () => { }); it('should successfully write a long, nested bytes when it takes up the whole array', async () => { const testBytesOffset = new BigNumber(0); - const emptyByteArray = ethUtil.bufferToHex(new Buffer(longTestBytesAsBuffer.byteLength)); + const emptyByteArray = ethUtil.bufferToHex(ethUtil.toBuffer(longTestBytesAsBuffer.byteLength)); const bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync( emptyByteArray, testBytesOffset, @@ -753,7 +753,7 @@ describe('LibBytes', () => { const prefixDataAsBuffer = ethUtil.toBuffer(prefixData); const prefixOffset = new BigNumber(0); const emptyByteArray = ethUtil.bufferToHex( - new Buffer(prefixDataAsBuffer.byteLength + longTestBytesAsBuffer.byteLength), + ethUtil.toBuffer(prefixDataAsBuffer.byteLength + longTestBytesAsBuffer.byteLength), ); let bytesWritten = await libBytes.publicWriteBytesWithLength.callAsync( emptyByteArray, @@ -769,7 +769,7 @@ 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)); + const emptyByteArray = ethUtil.bufferToHex(ethUtil.toBuffer(1)); const inputLen = new BigNumber((longData.length - 2) / 2); const expectedError = new LibBytesRevertErrors.InvalidByteOperationError( LibBytesRevertErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsNestedBytesLengthRequired, @@ -781,7 +781,7 @@ describe('LibBytes', () => { ).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 emptyByteArray = ethUtil.bufferToHex(ethUtil.toBuffer(shortTestBytesAsBuffer.byteLength)); const badOffset = new BigNumber(ethUtil.toBuffer(shortTestBytesAsBuffer).byteLength); const inputLen = new BigNumber((shortData.length - 2) / 2); const expectedError = new LibBytesRevertErrors.InvalidByteOperationError( @@ -1074,5 +1074,63 @@ describe('LibBytes', () => { expect(result).to.eq(byteArrayLongerThan32Bytes); }); }); + + describe('writeLength', () => { + it('should return a null byte array if length is set to 0', async () => { + const result = await libBytes.publicWriteLength.callAsync( + byteArrayLongerThan32Bytes, + constants.ZERO_AMOUNT, + constants.NULL_BYTES, + ); + expect(result).to.eq(constants.NULL_BYTES); + }); + it('should return the same byte array if length is unchanged', async () => { + const byteLen = (byteArrayLongerThan32Bytes.length - 2) / 2; + const result = await libBytes.publicWriteLength.callAsync( + byteArrayLongerThan32Bytes, + new BigNumber(byteLen), + constants.NULL_BYTES, + ); + expect(result).to.eq(byteArrayLongerThan32Bytes); + }); + it('should shave off lower order bytes if new length is less than original', async () => { + const byteLen = (byteArrayLongerThan32Bytes.length - 2) / 2; + const newLen = new BigNumber(byteLen).dividedToIntegerBy(2); + const result = await libBytes.publicWriteLength.callAsync( + byteArrayLongerThan32Bytes, + newLen, + constants.NULL_BYTES, + ); + expect(result).to.eq( + byteArrayLongerThan32Bytes.slice( + 0, + newLen + .multipliedBy(2) + .plus(2) + .toNumber(), + ), + ); + }); + it("should right pad with 0's if new length is greater than original and no extra bytes are appended", async () => { + const byteLen = (byteArrayLongerThan32Bytes.length - 2) / 2; + const newLen = new BigNumber(byteLen).multipliedBy(2); + const result = await libBytes.publicWriteLength.callAsync( + byteArrayLongerThan32Bytes, + newLen, + constants.NULL_BYTES, + ); + expect(result).to.eq(`${byteArrayLongerThan32Bytes}${'0'.repeat(byteArrayLongerThan32Bytes.length - 2)}`); + }); + it('should right pad with extra bytes if specified', async () => { + const byteLen = (byteArrayLongerThan32Bytes.length - 2) / 2; + const newLen = new BigNumber(byteLen).multipliedBy(2); + const result = await libBytes.publicWriteLength.callAsync( + byteArrayLongerThan32Bytes, + newLen, + byteArrayLongerThan32Bytes, + ); + expect(result).to.eq(`${byteArrayLongerThan32Bytes}${byteArrayLongerThan32Bytes.slice(2)}`); + }); + }); }); // tslint:disable:max-file-line-count