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