Comments and inline documentation for dynamic bytes

This commit is contained in:
Greg Hysen
2018-11-25 17:37:14 -08:00
parent ebaf9dd275
commit acd364b71c
4 changed files with 40 additions and 29 deletions

View File

@@ -22,10 +22,6 @@ export class Bool extends PayloadDataType {
} }
} }
public getSignature(): string {
return 'bool';
}
public encodeValue(value: boolean): Buffer { public encodeValue(value: boolean): Buffer {
const encodedValue = value ? '0x1' : '0x0'; const encodedValue = value ? '0x1' : '0x0';
const encodedValueBuf = ethUtil.setLengthLeft( const encodedValueBuf = ethUtil.setLengthLeft(
@@ -47,4 +43,8 @@ export class Bool extends PayloadDataType {
/* tslint:enable boolean-naming */ /* tslint:enable boolean-naming */
return value; return value;
} }
public getSignature(): string {
return 'bool';
}
} }

View File

@@ -17,42 +17,53 @@ export class DynamicBytes extends PayloadDataType {
public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) { public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
super(dataItem, dataTypeFactory, DynamicBytes._SIZE_KNOWN_AT_COMPILE_TIME); super(dataItem, dataTypeFactory, DynamicBytes._SIZE_KNOWN_AT_COMPILE_TIME);
if (!DynamicBytes.matchType(dataItem.type)) { if (!DynamicBytes.matchType(dataItem.type)) {
throw new Error(`Tried to instantiate DynamicBytes with bad input: ${dataItem}`); throw new Error(`Tried to instantiate Dynamic Bytes with bad input: ${dataItem}`);
} }
} }
public encodeValue(value: string | Buffer): Buffer { public encodeValue(value: string | Buffer): Buffer {
if (typeof value === 'string' && !value.startsWith('0x')) { // Encoded value is of the form: <length><value>, with each field padded to be word-aligned.
throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '${value}'`); // 1/3 Construct the length
}
const valueBuf = ethUtil.toBuffer(value); const valueBuf = ethUtil.toBuffer(value);
if (value.length % 2 !== 0) { const wordsToStoreValuePadded = Math.ceil(valueBuf.byteLength / Constants.EVM_WORD_WIDTH_IN_BYTES);
throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`); const bytesToStoreValuePadded = wordsToStoreValuePadded * Constants.EVM_WORD_WIDTH_IN_BYTES;
} const lengthBuf = ethUtil.toBuffer(valueBuf.byteLength);
const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, Constants.EVM_WORD_WIDTH_IN_BYTES);
const wordsForValue = Math.ceil(valueBuf.byteLength / Constants.EVM_WORD_WIDTH_IN_BYTES); // 2/3 Construct the value
const paddedDynamicBytesForValue = wordsForValue * Constants.EVM_WORD_WIDTH_IN_BYTES; this._sanityCheckValue(value);
const paddedValueBuf = ethUtil.setLengthRight(valueBuf, paddedDynamicBytesForValue); const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded);
const paddedLengthBuf = ethUtil.setLengthLeft( // 3/3 Combine length and value
ethUtil.toBuffer(valueBuf.byteLength), const encodedValue = Buffer.concat([lengthBufPadded, valueBufPadded]);
Constants.EVM_WORD_WIDTH_IN_BYTES, return encodedValue;
);
const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]);
return encodedValueBuf;
} }
public decodeValue(calldata: RawCalldata): string { public decodeValue(calldata: RawCalldata): string {
// Encoded value is of the form: <length><value>, with each field padded to be word-aligned.
// 1/2 Decode length
const lengthBuf = calldata.popWord(); const lengthBuf = calldata.popWord();
const lengthHex = ethUtil.bufferToHex(lengthBuf); const lengthHex = ethUtil.bufferToHex(lengthBuf);
const length = parseInt(lengthHex, Constants.HEX_BASE); const length = parseInt(lengthHex, Constants.HEX_BASE);
const wordsForValue = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES); // 2/2 Decode value
const paddedValueBuf = calldata.popWords(wordsForValue); const wordsToStoreValuePadded = Math.ceil(length / Constants.EVM_WORD_WIDTH_IN_BYTES);
const valueBuf = paddedValueBuf.slice(0, length); const valueBufPadded = calldata.popWords(wordsToStoreValuePadded);
const decodedValue = ethUtil.bufferToHex(valueBuf); const valueBuf = valueBufPadded.slice(0, length);
return decodedValue; const value = ethUtil.bufferToHex(valueBuf);
this._sanityCheckValue(value);
return value;
} }
public getSignature(): string { public getSignature(): string {
return 'bytes'; return 'bytes';
} }
private _sanityCheckValue(value: string | Buffer): void {
if (typeof value !== 'string') {
return;
}
if (!value.startsWith('0x')) {
throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`);
} else if (value.length % 2 !== 0) {
throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
}
}
} }

View File

@@ -32,8 +32,8 @@ export class String extends PayloadDataType {
const valueBuf = new Buffer(value); const valueBuf = new Buffer(value);
const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded); const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded);
// 3/3 Combine length and value // 3/3 Combine length and value
const encodedValueBuf = Buffer.concat([lengthBufPadded, valueBufPadded]); const encodedValue = Buffer.concat([lengthBufPadded, valueBufPadded]);
return encodedValueBuf; return encodedValue;
} }
public decodeValue(calldata: RawCalldata): string { public decodeValue(calldata: RawCalldata): string {

View File

@@ -1018,7 +1018,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
// Encode Args and validate result // Encode Args and validate result
expect(() => { expect(() => {
dataType.encode(args); dataType.encode(args);
}).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix. Got '01'"); }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix.");
}); });
it('Should throw when pass in bad hex (include a half-byte)', async () => { it('Should throw when pass in bad hex (include a half-byte)', async () => {
// Create DataType object // Create DataType object