Merge pull request #1806 from 0xProject/fix/abiEncoder/multibyteStrings
Support for ABI Encoding multibyte strings (Fixes Issue #1723)
This commit is contained in:
@@ -1,4 +1,12 @@
|
||||
[
|
||||
{
|
||||
"version": "4.3.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Support for ABI encoding multibyte strings (fixes issue #1723)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "4.3.1",
|
||||
"changes": [
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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' };
|
||||
|
||||
Reference in New Issue
Block a user