Merge pull request #1806 from 0xProject/fix/abiEncoder/multibyteStrings

Support for ABI Encoding multibyte strings (Fixes Issue #1723)
This commit is contained in:
Greg Hysz
2019-05-07 13:01:53 -07:00
committed by GitHub
3 changed files with 54 additions and 7 deletions

View File

@@ -1,4 +1,12 @@
[
{
"version": "4.3.2",
"changes": [
{
"note": "Support for ABI encoding multibyte strings (fixes issue #1723)"
}
]
},
{
"version": "4.3.1",
"changes": [

View File

@@ -26,14 +26,15 @@ export class StringDataType extends AbstractBlobDataType {
/* tslint:disable prefer-function-over-method */
public encodeValue(value: string): Buffer {
// Encoded value is of the form: <length><value>, with each field padded to be word-aligned.
// 1/3 Construct the length
const wordsToStoreValuePadded = Math.ceil(value.length / constants.EVM_WORD_WIDTH_IN_BYTES);
const bytesToStoreValuePadded = wordsToStoreValuePadded * constants.EVM_WORD_WIDTH_IN_BYTES;
const lengthBuf = ethUtil.toBuffer(value.length);
const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, constants.EVM_WORD_WIDTH_IN_BYTES);
// 2/3 Construct the value
// 1/3 Construct the value
const valueBuf = new Buffer(value);
const valueLengthInBytes = valueBuf.byteLength;
const wordsToStoreValuePadded = Math.ceil(valueLengthInBytes / constants.EVM_WORD_WIDTH_IN_BYTES);
const bytesToStoreValuePadded = wordsToStoreValuePadded * constants.EVM_WORD_WIDTH_IN_BYTES;
const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded);
// 2/3 Construct the length
const lengthBuf = ethUtil.toBuffer(valueLengthInBytes);
const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, constants.EVM_WORD_WIDTH_IN_BYTES);
// 3/3 Combine length and value
const encodedValue = Buffer.concat([lengthBufPadded, valueBufPadded]);
return encodedValue;
@@ -49,7 +50,7 @@ export class StringDataType extends AbstractBlobDataType {
const wordsToStoreValuePadded = Math.ceil(length / constants.EVM_WORD_WIDTH_IN_BYTES);
const valueBufPadded = calldata.popWords(wordsToStoreValuePadded);
const valueBuf = valueBufPadded.slice(0, length);
const value = valueBuf.toString('ascii');
const value = valueBuf.toString('UTF-8');
return value;
}

View File

@@ -1322,6 +1322,44 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
const argsEncodedFromSignature = dataTypeFromSignature.encode(args);
expect(argsEncodedFromSignature).to.be.deep.equal(expectedEncodedArgs);
});
it('String that has a multibyte UTF-8 character', async () => {
// Create DataType object
const testDataItem = { name: 'String', type: 'string' };
const dataType = new AbiEncoder.String(testDataItem);
// Construct args to be encoded
const args = '👴🏼';
// Encode Args and validate result
const encodedArgs = dataType.encode(args, encodingRules);
const expectedEncodedArgs =
'0x0000000000000000000000000000000000000000000000000000000000000008f09f91b4f09f8fbc000000000000000000000000000000000000000000000000';
expect(encodedArgs).to.be.equal(expectedEncodedArgs);
// Decode Encoded Args and validate result
const decodedArgs = dataType.decode(encodedArgs);
expect(decodedArgs).to.be.deep.equal(args);
// Validate signature
const dataTypeFromSignature = AbiEncoder.create(dataType.getSignature(true));
const argsEncodedFromSignature = dataTypeFromSignature.encode(args);
expect(argsEncodedFromSignature).to.be.deep.equal(expectedEncodedArgs);
});
it('String that combines single and multibyte UTF-8 characters', async () => {
// Create DataType object
const testDataItem = { name: 'String', type: 'string' };
const dataType = new AbiEncoder.String(testDataItem);
// Construct args to be encoded
const args = 'Hello 😀👴🏼😁😂😃 world!';
// Encode Args and validate result
const encodedArgs = dataType.encode(args, encodingRules);
const expectedEncodedArgs =
'0x000000000000000000000000000000000000000000000000000000000000002548656c6c6f20f09f9880f09f91b4f09f8fbcf09f9881f09f9882f09f988320776f726c6421000000000000000000000000000000000000000000000000000000';
expect(encodedArgs).to.be.equal(expectedEncodedArgs);
// Decode Encoded Args and validate result
const decodedArgs = dataType.decode(encodedArgs);
expect(decodedArgs).to.be.deep.equal(args);
// Validate signature
const dataTypeFromSignature = AbiEncoder.create(dataType.getSignature(true));
const argsEncodedFromSignature = dataTypeFromSignature.encode(args);
expect(argsEncodedFromSignature).to.be.deep.equal(expectedEncodedArgs);
});
it('Should decode NULL to empty string', async () => {
// Create DataType object
const testDataItem = { name: 'String', type: 'string' };