diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index f54d739b45..0c3cedae20 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -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: , 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; } diff --git a/packages/utils/test/abi_encoder/evm_data_types_test.ts b/packages/utils/test/abi_encoder/evm_data_types_test.ts index c802902072..3d034ad014 100644 --- a/packages/utils/test/abi_encoder/evm_data_types_test.ts +++ b/packages/utils/test/abi_encoder/evm_data_types_test.ts @@ -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' };