From 52fae4114190757fd6062439030712519fdd2462 Mon Sep 17 00:00:00 2001 From: Peter Phillips Date: Fri, 11 Mar 2022 13:20:47 -0800 Subject: [PATCH 1/3] Support fixed arrays and tuples --- abis/Fixed.json | 61 ++++++++++++++++++++++++++++++++++ src/planner.ts | 76 +++++++++++++++++++++++++++++++++---------- tests/test_planner.ts | 35 ++++++++++++++++++++ 3 files changed, 154 insertions(+), 18 deletions(-) create mode 100644 abis/Fixed.json diff --git a/abis/Fixed.json b/abis/Fixed.json new file mode 100644 index 0000000..7bb6973 --- /dev/null +++ b/abis/Fixed.json @@ -0,0 +1,61 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "Fixed", + "sourceName": "contracts/Libraries/Fixed.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256[2]", + "name": "values", + "type": "uint256[2]" + } + ], + "name": "addArray", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "a", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "b", + "type": "uint256" + } + ], + "internalType": "struct Fixed.Struct", + "name": "values", + "type": "tuple" + } + ], + "name": "addStruct", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b506103b8806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806350527d6e1461003b578063e95366181461006b575b600080fd5b61005560048036038101906100509190610223565b61009b565b604051610062919061025f565b60405180910390f35b610085600480360381019061008091906102a1565b6100b8565b604051610092919061025f565b60405180910390f35b6000816020015182600001516100b191906102fd565b9050919050565b6000816001600281106100ce576100cd610353565b5b6020020135826000600281106100e7576100e6610353565b5b60200201356100f691906102fd565b9050919050565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61015a82610111565b810181811067ffffffffffffffff8211171561017957610178610122565b5b80604052505050565b600061018c6100fd565b90506101988282610151565b919050565b6000819050919050565b6101b08161019d565b81146101bb57600080fd5b50565b6000813590506101cd816101a7565b92915050565b6000604082840312156101e9576101e861010c565b5b6101f36040610182565b90506000610203848285016101be565b6000830152506020610217848285016101be565b60208301525092915050565b60006040828403121561023957610238610107565b5b6000610247848285016101d3565b91505092915050565b6102598161019d565b82525050565b60006020820190506102746000830184610250565b92915050565b600080fd5b60008190508260206002028201111561029b5761029a61027a565b5b92915050565b6000604082840312156102b7576102b6610107565b5b60006102c58482850161027f565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006103088261019d565b91506103138361019d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610348576103476102ce565b5b828201905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fdfea2646970667358221220e9425b7699f274511c172cfff65a57757fc759403c2556e04fe8696671262d4a64736f6c634300080b0033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806350527d6e1461003b578063e95366181461006b575b600080fd5b61005560048036038101906100509190610223565b61009b565b604051610062919061025f565b60405180910390f35b610085600480360381019061008091906102a1565b6100b8565b604051610092919061025f565b60405180910390f35b6000816020015182600001516100b191906102fd565b9050919050565b6000816001600281106100ce576100cd610353565b5b6020020135826000600281106100e7576100e6610353565b5b60200201356100f691906102fd565b9050919050565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61015a82610111565b810181811067ffffffffffffffff8211171561017957610178610122565b5b80604052505050565b600061018c6100fd565b90506101988282610151565b919050565b6000819050919050565b6101b08161019d565b81146101bb57600080fd5b50565b6000813590506101cd816101a7565b92915050565b6000604082840312156101e9576101e861010c565b5b6101f36040610182565b90506000610203848285016101be565b6000830152506020610217848285016101be565b60208301525092915050565b60006040828403121561023957610238610107565b5b6000610247848285016101d3565b91505092915050565b6102598161019d565b82525050565b60006020820190506102746000830184610250565b92915050565b600080fd5b60008190508260206002028201111561029b5761029a61027a565b5b92915050565b6000604082840312156102b7576102b6610107565b5b60006102c58482850161027f565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006103088261019d565b91506103138361019d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610348576103476102ce565b5b828201905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fdfea2646970667358221220e9425b7699f274511c172cfff65a57757fc759403c2556e04fe8696671262d4a64736f6c634300080b0033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/src/planner.ts b/src/planner.ts index 1ade750..f64ddbf 100644 --- a/src/planner.ts +++ b/src/planner.ts @@ -173,7 +173,29 @@ export type ContractFunction = (...args: Array) => FunctionCall; function isDynamicType(param?: ParamType): boolean { if (typeof param === 'undefined') return false; - return ['string', 'bytes', 'array', 'tuple'].includes(param.baseType); + switch (param.baseType) { + case 'array': + // Check if array is fixed or dynamic + return param.arrayLength === -1; + case 'bytes': + return true; + case 'string': + return true; + case 'tuple': + // Check if tuple contains dynamic types + for (let i = 0; i < param.components.length; i++) { + if (isDynamicType(param.components[i])) return true; + } + return false; + default: + return false; + } +} + +function isFixedType(param?: ParamType): boolean { + if (typeof param === 'undefined') return false; + + return ['tuple', 'array'].includes(param.baseType) && !isDynamicType(param) } function abiEncodeSingle(param: ParamType, value: any): LiteralValue { @@ -601,7 +623,17 @@ export class Planner { } commandVisibility.set(arg.command, command); } else if (arg instanceof LiteralValue) { - literalVisibility.set(arg.value, command); + if (isFixedType(arg.param)) { + const objLength = arg.param.baseType === 'tuple' ? arg.param.components.length : arg.param.arrayLength + for (let i = 0; i < objLength; i++) { + literalVisibility.set( + hexDataSlice(arg.value, 32*i, 32*(i + 1)), + command + ); + } + } else { + literalVisibility.set(arg.value, command); + } } else if (arg instanceof SubplanValue) { let subplanSeen = seen; if ( @@ -647,25 +679,33 @@ export class Planner { const args = new Array(); inargs.forEach((arg) => { - let slot: number; - if (arg instanceof ReturnValue) { - slot = returnSlotMap.get(arg.command) as number; - } else if (arg instanceof LiteralValue) { - slot = literalSlotMap.get(arg.value) as number; - } else if (arg instanceof StateValue) { - slot = 0xfe; - } else if (arg instanceof SubplanValue) { - // buildCommands has already built the subplan and put it in the last state slot - slot = state.length - 1; + if (arg instanceof LiteralValue && isFixedType(arg.param)) { + const objLength = arg.param.baseType === 'tuple' ? arg.param.components.length : arg.param.arrayLength + for (let i = 0; i < objLength; i++) { + const value = hexDataSlice(arg.value, 32*i, 32*(i + 1)); + const slot = literalSlotMap.get(value) as number; + args.push(slot); + } } else { - throw new Error(`Unknown function argument type '${typeof arg}'`); - } - if (isDynamicType(arg.param)) { - slot |= 0x80; + let slot: number; + if (arg instanceof ReturnValue) { + slot = returnSlotMap.get(arg.command) as number; + } else if (arg instanceof LiteralValue) { + slot = literalSlotMap.get(arg.value) as number; + } else if (arg instanceof StateValue) { + slot = 0xfe; + } else if (arg instanceof SubplanValue) { + // buildCommands has already built the subplan and put it in the last state slot + slot = state.length - 1; + } else { + throw new Error(`Unknown function argument type '${typeof arg}'`); + } + if (isDynamicType(arg.param)) { + slot |= 0x80; + } + args.push(slot); } - args.push(slot); }); - return args; } diff --git a/tests/test_planner.ts b/tests/test_planner.ts index 577fa9a..a14e1ff 100644 --- a/tests/test_planner.ts +++ b/tests/test_planner.ts @@ -5,6 +5,7 @@ import { defaultAbiCoder } from '@ethersproject/abi'; import { CommandFlags, Contract, Planner } from '../src/planner'; import * as mathABI from '../abis/Math.json'; import * as stringsABI from '../abis/Strings.json'; +import * as fixedABI from '../abis/Fixed.json'; const SAMPLE_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; @@ -39,6 +40,7 @@ describe('Contract', () => { describe('Planner', () => { let Math: Contract; let Strings: Contract; + let Fixed: Contract; before(() => { Math = Contract.createLibrary( @@ -47,6 +49,9 @@ describe('Planner', () => { Strings = Contract.createLibrary( new ethers.Contract(SAMPLE_ADDRESS, stringsABI.abi) ); + Fixed = Contract.createLibrary( + new ethers.Contract(SAMPLE_ADDRESS, fixedABI.abi) + ); }); it('adds function calls to a list of commands', () => { @@ -73,6 +78,36 @@ describe('Planner', () => { expect(state[1]).to.equal(defaultAbiCoder.encode(['uint'], [2])); }); + it('plans a simple program using fixed-size tuple', () => { + const planner = new Planner(); + planner.add(Fixed.addStruct({a: 1, b: 2})); + const { commands, state } = planner.plan(); + + expect(commands.length).to.equal(1); + expect(commands[0]).to.equal( + '0x50527d6e000001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + ); + + expect(state.length).to.equal(2); + expect(state[0]).to.equal(defaultAbiCoder.encode(['uint'], [1])); + expect(state[1]).to.equal(defaultAbiCoder.encode(['uint'], [2])); + }); + + it('plans a simple program using fixed-size array', () => { + const planner = new Planner(); + planner.add(Fixed.addArray([1, 2])); + const { commands, state } = planner.plan(); + + expect(commands.length).to.equal(1); + expect(commands[0]).to.equal( + '0xe9536618000001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + ); + + expect(state.length).to.equal(2); + expect(state[0]).to.equal(defaultAbiCoder.encode(['uint'], [1])); + expect(state[1]).to.equal(defaultAbiCoder.encode(['uint'], [2])); + }); + it('deduplicates identical literals', () => { const planner = new Planner(); planner.add(Math.add(1, 1)); From 8da408b42b9d06466097b02b8bed33f0b794d857 Mon Sep 17 00:00:00 2001 From: Peter Phillips Date: Thu, 8 Sep 2022 12:04:06 -0700 Subject: [PATCH 2/3] wip: TupleValue and ArrayValue types --- src/planner.ts | 302 ++++++++++++++++++++++++++++++++---------- tests/test_planner.ts | 6 +- 2 files changed, 235 insertions(+), 73 deletions(-) diff --git a/src/planner.ts b/src/planner.ts index f64ddbf..466bde1 100644 --- a/src/planner.ts +++ b/src/planner.ts @@ -30,6 +30,26 @@ class LiteralValue implements Value { } } +class ArrayValue implements Value { + readonly param: ParamType; + readonly values: Value[]; + + constructor(param: ParamType, values: Value[]) { + this.param = param; + this.values = values; + } +} + +class TupleValue implements Value { + readonly param: ParamType; + readonly values: Value[]; + + constructor(param: ParamType, values: any) { + this.param = param; + this.values = values; + } +} + class ReturnValue implements Value { readonly param: ParamType; readonly command: Command; // Function call we want the return value of @@ -75,9 +95,9 @@ export enum CommandFlags { /** A bitmask that selects calltype flags */ CALLTYPE_MASK = 0x03, /** Specifies that this is an extended command, with an additional command word for indices. Internal use only. */ - EXTENDED_COMMAND = 0x40, + EXTENDED_COMMAND = 0x80, /** Specifies that the return value of this call should be wrapped in a `bytes`. Internal use only. */ - TUPLE_RETURN = 0x80, + TUPLE_RETURN = 0x40, } /** @@ -172,7 +192,7 @@ export type ContractFunction = (...args: Array) => FunctionCall; function isDynamicType(param?: ParamType): boolean { if (typeof param === 'undefined') return false; - + console.log('isDynamicType: ', param); switch (param.baseType) { case 'array': // Check if array is fixed or dynamic @@ -191,15 +211,16 @@ function isDynamicType(param?: ParamType): boolean { return false; } } - +/* function isFixedType(param?: ParamType): boolean { if (typeof param === 'undefined') return false; - return ['tuple', 'array'].includes(param.baseType) && !isDynamicType(param) + return ['tuple', 'array'].includes(param.baseType) && !isDynamicType(param); } - +*/ function abiEncodeSingle(param: ParamType, value: any): LiteralValue { if (isDynamicType(param)) { + console.log('abiEncodeSingle: is dynamic type'); return new LiteralValue( param, hexDataSlice(defaultAbiCoder.encode([param], [value]), 32) @@ -210,6 +231,7 @@ function abiEncodeSingle(param: ParamType, value: any): LiteralValue { function encodeArg(arg: any, param: ParamType): Value { if (isValue(arg)) { + console.log('encodeArg: is value'); if (arg.param.type !== param.type) { // Todo: type casting rules throw new Error( @@ -218,7 +240,26 @@ function encodeArg(arg: any, param: ParamType): Value { } return arg; } else if (arg instanceof Planner) { + console.log('encodeArg: subplan'); return new SubplanValue(arg); + } else if (param.baseType === 'array') { + console.log('encodeArg: array'); + const values: Value[] = []; + for (let i = 0; i < arg.length; i++) { + console.log('encodeArg: encoding array child'); + values.push(encodeArg(arg[i], param.arrayChildren)); + } + return new ArrayValue(param, values); + } else if (param.baseType === 'tuple') { + console.log('encodeArg: tuple'); + const values: Value[] = []; + for (let i = 0; i < param.components.length; i++) { + console.log('encodeArg: encoding tuple child'); + values.push( + encodeArg(arg[param.components[i].name], param.components[i]) + ); + } + return new TupleValue(param, values); } else { return abiEncodeSingle(param, arg); } @@ -583,6 +624,65 @@ export class Planner { this.commands.push(new Command(call, CommandType.RAWCALL)); } + private setVisibility( + arg: Value, + command: Command, + commandVisibility: Map, + literalVisibility: Map, + seen: Set, + planners: Set + ) { + if (arg instanceof ReturnValue) { + console.log('setVisibility: return'); + if (!seen.has(arg.command)) { + throw new Error( + `Return value from "${arg.command.call.fragment.name}" is not visible here` + ); + } + commandVisibility.set(arg.command, command); + } else if (arg instanceof LiteralValue) { + console.log('setVisibility: literal'); + literalVisibility.set(arg.value, command); + } else if (arg instanceof ArrayValue || arg instanceof TupleValue) { + console.log('setVisibility: tuple or array'); + if (arg instanceof ArrayValue && isDynamicType(arg.param)) { + literalVisibility.set( + defaultAbiCoder.encode(['uint256'], [arg.values.length]), + command + ); + } + for (let i = 0; i < arg.values.length; i++) { + console.log('setVisibility: encoding child visibility'); + this.setVisibility( + arg.values[i], + command, + commandVisibility, + literalVisibility, + seen, + planners + ); + } + } else if (arg instanceof SubplanValue) { + console.log('setVisibility: subplan'); + let subplanSeen = seen; + if ( + !command.call.fragment.outputs || + command.call.fragment.outputs.length === 0 + ) { + // Read-only subplan; return values aren't visible externally + subplanSeen = new Set(seen); + } + arg.planner.preplan( + commandVisibility, + literalVisibility, + subplanSeen, + planners + ); + } else if (!(arg instanceof StateValue)) { + throw new Error(`Unknown function argument type '${typeof arg}'`); + } + } + private preplan( commandVisibility: Map, literalVisibility: Map, @@ -614,50 +714,119 @@ export class Planner { inargs = [command.call.callvalue].concat(inargs); } + // Set pointers total in state + const pointers = this.getPointers(inargs); + literalVisibility.set( + defaultAbiCoder.encode(['uint256'], [pointers]), + command + ); + for (let arg of inargs) { - if (arg instanceof ReturnValue) { - if (!seen.has(arg.command)) { - throw new Error( - `Return value from "${arg.command.call.fragment.name}" is not visible here` - ); - } - commandVisibility.set(arg.command, command); - } else if (arg instanceof LiteralValue) { - if (isFixedType(arg.param)) { - const objLength = arg.param.baseType === 'tuple' ? arg.param.components.length : arg.param.arrayLength - for (let i = 0; i < objLength; i++) { - literalVisibility.set( - hexDataSlice(arg.value, 32*i, 32*(i + 1)), - command - ); - } - } else { - literalVisibility.set(arg.value, command); - } - } else if (arg instanceof SubplanValue) { - let subplanSeen = seen; - if ( - !command.call.fragment.outputs || - command.call.fragment.outputs.length === 0 - ) { - // Read-only subplan; return values aren't visible externally - subplanSeen = new Set(seen); - } - arg.planner.preplan( - commandVisibility, - literalVisibility, - subplanSeen, - planners - ); - } else if (!(arg instanceof StateValue)) { - throw new Error(`Unknown function argument type '${typeof arg}'`); - } + this.setVisibility( + arg, + command, + commandVisibility, + literalVisibility, + seen, + planners + ); } seen.add(command); } return { commandVisibility, literalVisibility }; } + /* + private getPointerSlots( + arg: Value + ): Array { + const slots = new Array(); + if (arg instanceof ArrayValue || arg instanceof TupleValue) { + console.log("getSlots: array or tuple") + // Tuples can be composed of other tuples or arrays + if (isDynamicType(arg.param)) { + console.log("getSlots: tuple/array is dynamic type") + // add pointer + slots.push(0xfd); + } + } + return slots; + } + */ + private getPointers(args: Value[]): number { + let count = 0; + for (let arg of args) { + if (arg instanceof ArrayValue || arg instanceof TupleValue) { + console.log('getPointers: array or tuple'); + // Tuples can be composed of other tuples or arrays + if (isDynamicType(arg.param)) { + count++; + } + } + } + return count; + } + + private getSlots( + arg: Value, + returnSlotMap: Map, + literalSlotMap: Map, + state: Array + ): Array { + const slots = new Array(); + if (arg instanceof ArrayValue || arg instanceof TupleValue) { + console.log('getSlots: array or tuple'); + // Dynamic arrays have a length value + if (arg instanceof ArrayValue && isDynamicType(arg.param)) { + const slot: number = literalSlotMap.get( + defaultAbiCoder.encode(['uint256'], [arg.values.length]) + ) as number; + slots.push(slot); + } + // Tuples/arrays can be composed of other tuples or arrays + for (let i = 0; i < arg.values.length; i++) { + console.log('getSlots: encoding child slots'); + const subSlots = this.getSlots( + arg.values[i], + returnSlotMap, + literalSlotMap, + state + ); + slots.push(...subSlots); + } + if (isDynamicType(arg.param)) { + console.log('getSlots: tuple/array is dynamic type'); + // add pointer flag after slots + slots.push(0xfd); + } + } else { + let slot: number; + if (arg instanceof ReturnValue) { + console.log('getSlots: return'); + slot = returnSlotMap.get(arg.command) as number; + } else if (arg instanceof LiteralValue) { + console.log('getSlots: literal'); + slot = literalSlotMap.get(arg.value) as number; + } else if (arg instanceof StateValue) { + console.log('getSlots: state'); + slot = 0xfe; + } else if (arg instanceof SubplanValue) { + console.log('getSlots: subplan'); + // buildCommands has already built the subplan and put it in the last state slot + slot = state.length - 1; + } else { + throw new Error(`Unknown function argument type '${typeof arg}'`); + } + if (isDynamicType(arg.param)) { + console.log('getSlots: is dynamic type'); + console.log('getSlots - before: ', slot); + slot |= 0x80; + console.log('getSlots - after: ', slot); + } + slots.push(slot); + } + return slots; + } private buildCommandArgs( command: Command, @@ -678,33 +847,21 @@ export class Planner { } const args = new Array(); + // Set pointers total in state + const pointers = this.getPointers(inargs); + const slot: number = literalSlotMap.get( + defaultAbiCoder.encode(['uint256'], [pointers]) + ) as number; + args.push(slot); + /* inargs.forEach((arg) => { - if (arg instanceof LiteralValue && isFixedType(arg.param)) { - const objLength = arg.param.baseType === 'tuple' ? arg.param.components.length : arg.param.arrayLength - for (let i = 0; i < objLength; i++) { - const value = hexDataSlice(arg.value, 32*i, 32*(i + 1)); - const slot = literalSlotMap.get(value) as number; - args.push(slot); - } - } else { - let slot: number; - if (arg instanceof ReturnValue) { - slot = returnSlotMap.get(arg.command) as number; - } else if (arg instanceof LiteralValue) { - slot = literalSlotMap.get(arg.value) as number; - } else if (arg instanceof StateValue) { - slot = 0xfe; - } else if (arg instanceof SubplanValue) { - // buildCommands has already built the subplan and put it in the last state slot - slot = state.length - 1; - } else { - throw new Error(`Unknown function argument type '${typeof arg}'`); - } - if (isDynamicType(arg.param)) { - slot |= 0x80; - } - args.push(slot); - } + const slots = this.getPointerSlots(arg) + args.push(...slots) + }); + */ + inargs.forEach((arg) => { + const slots = this.getSlots(arg, returnSlotMap, literalSlotMap, state); + args.push(...slots); }); return args; } @@ -738,7 +895,7 @@ export class Planner { ps.literalSlotMap, ps.state ); - + console.log('buildCommands - args: ', args); if (args.length > 6) { flags |= CommandFlags.EXTENDED_COMMAND; } @@ -801,6 +958,7 @@ export class Planner { (flags & CommandFlags.EXTENDED_COMMAND) === CommandFlags.EXTENDED_COMMAND ) { + console.log('buildCommands: extended command'); // Extended command encodedCommands.push( hexConcat([ @@ -811,6 +969,7 @@ export class Planner { ); encodedCommands.push(hexConcat([padArray(args, 32, 0xff)])); } else { + console.log('buildCommands: standard command'); // Standard command encodedCommands.push( hexConcat([ @@ -858,6 +1017,9 @@ export class Planner { ); }); + console.log('plan - literalVisiblity: ', literalVisibility); + console.log('plan - state: ', state); + console.log('plan - literalSlotMap: ', literalSlotMap); const ps: PlannerState = { returnSlotMap: new Map(), literalSlotMap, diff --git a/tests/test_planner.ts b/tests/test_planner.ts index a14e1ff..db9fd63 100644 --- a/tests/test_planner.ts +++ b/tests/test_planner.ts @@ -80,7 +80,7 @@ describe('Planner', () => { it('plans a simple program using fixed-size tuple', () => { const planner = new Planner(); - planner.add(Fixed.addStruct({a: 1, b: 2})); + planner.add(Fixed.addStruct({ a: 1, b: 2 })); const { commands, state } = planner.plan(); expect(commands.length).to.equal(1); @@ -119,7 +119,7 @@ describe('Planner', () => { it('plans a program that uses return values', () => { const planner = new Planner(); const sum1 = planner.add(Math.add(1, 2)); - planner.add(Math.add(sum1, 3)); + planner.add(Fixed.addArray([sum1, 3])); const { commands, state } = planner.plan(); expect(commands.length).to.equal(2); @@ -127,7 +127,7 @@ describe('Planner', () => { '0x771602f7000001ffffffff01eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(commands[1]).to.equal( - '0x771602f7000102ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0xe9536618000102ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(state.length).to.equal(3); From a9a76a3631af8039005ce37b61e4e42de1eaf0de Mon Sep 17 00:00:00 2001 From: Peter Phillips Date: Thu, 8 Sep 2022 19:59:24 -0700 Subject: [PATCH 3/3] use no offset flag --- src/planner.ts | 78 ++++++++++--------------------------------- tests/test_planner.ts | 60 ++++++++++++++++----------------- 2 files changed, 47 insertions(+), 91 deletions(-) diff --git a/src/planner.ts b/src/planner.ts index 466bde1..55857be 100644 --- a/src/planner.ts +++ b/src/planner.ts @@ -192,7 +192,6 @@ export type ContractFunction = (...args: Array) => FunctionCall; function isDynamicType(param?: ParamType): boolean { if (typeof param === 'undefined') return false; - console.log('isDynamicType: ', param); switch (param.baseType) { case 'array': // Check if array is fixed or dynamic @@ -220,7 +219,6 @@ function isFixedType(param?: ParamType): boolean { */ function abiEncodeSingle(param: ParamType, value: any): LiteralValue { if (isDynamicType(param)) { - console.log('abiEncodeSingle: is dynamic type'); return new LiteralValue( param, hexDataSlice(defaultAbiCoder.encode([param], [value]), 32) @@ -231,7 +229,6 @@ function abiEncodeSingle(param: ParamType, value: any): LiteralValue { function encodeArg(arg: any, param: ParamType): Value { if (isValue(arg)) { - console.log('encodeArg: is value'); if (arg.param.type !== param.type) { // Todo: type casting rules throw new Error( @@ -240,21 +237,16 @@ function encodeArg(arg: any, param: ParamType): Value { } return arg; } else if (arg instanceof Planner) { - console.log('encodeArg: subplan'); return new SubplanValue(arg); } else if (param.baseType === 'array') { - console.log('encodeArg: array'); const values: Value[] = []; for (let i = 0; i < arg.length; i++) { - console.log('encodeArg: encoding array child'); values.push(encodeArg(arg[i], param.arrayChildren)); } return new ArrayValue(param, values); } else if (param.baseType === 'tuple') { - console.log('encodeArg: tuple'); const values: Value[] = []; for (let i = 0; i < param.components.length; i++) { - console.log('encodeArg: encoding tuple child'); values.push( encodeArg(arg[param.components[i].name], param.components[i]) ); @@ -633,7 +625,6 @@ export class Planner { planners: Set ) { if (arg instanceof ReturnValue) { - console.log('setVisibility: return'); if (!seen.has(arg.command)) { throw new Error( `Return value from "${arg.command.call.fragment.name}" is not visible here` @@ -641,10 +632,8 @@ export class Planner { } commandVisibility.set(arg.command, command); } else if (arg instanceof LiteralValue) { - console.log('setVisibility: literal'); literalVisibility.set(arg.value, command); } else if (arg instanceof ArrayValue || arg instanceof TupleValue) { - console.log('setVisibility: tuple or array'); if (arg instanceof ArrayValue && isDynamicType(arg.param)) { literalVisibility.set( defaultAbiCoder.encode(['uint256'], [arg.values.length]), @@ -652,7 +641,6 @@ export class Planner { ); } for (let i = 0; i < arg.values.length; i++) { - console.log('setVisibility: encoding child visibility'); this.setVisibility( arg.values[i], command, @@ -663,7 +651,6 @@ export class Planner { ); } } else if (arg instanceof SubplanValue) { - console.log('setVisibility: subplan'); let subplanSeen = seen; if ( !command.call.fragment.outputs || @@ -716,10 +703,12 @@ export class Planner { // Set pointers total in state const pointers = this.getPointers(inargs); - literalVisibility.set( - defaultAbiCoder.encode(['uint256'], [pointers]), - command - ); + if (pointers > 0) { + literalVisibility.set( + defaultAbiCoder.encode(['uint256'], [pointers]), + command + ); + } for (let arg of inargs) { this.setVisibility( @@ -736,28 +725,11 @@ export class Planner { return { commandVisibility, literalVisibility }; } - /* - private getPointerSlots( - arg: Value - ): Array { - const slots = new Array(); - if (arg instanceof ArrayValue || arg instanceof TupleValue) { - console.log("getSlots: array or tuple") - // Tuples can be composed of other tuples or arrays - if (isDynamicType(arg.param)) { - console.log("getSlots: tuple/array is dynamic type") - // add pointer - slots.push(0xfd); - } - } - return slots; - } - */ + private getPointers(args: Value[]): number { let count = 0; for (let arg of args) { if (arg instanceof ArrayValue || arg instanceof TupleValue) { - console.log('getPointers: array or tuple'); // Tuples can be composed of other tuples or arrays if (isDynamicType(arg.param)) { count++; @@ -775,7 +747,6 @@ export class Planner { ): Array { const slots = new Array(); if (arg instanceof ArrayValue || arg instanceof TupleValue) { - console.log('getSlots: array or tuple'); // Dynamic arrays have a length value if (arg instanceof ArrayValue && isDynamicType(arg.param)) { const slot: number = literalSlotMap.get( @@ -785,7 +756,6 @@ export class Planner { } // Tuples/arrays can be composed of other tuples or arrays for (let i = 0; i < arg.values.length; i++) { - console.log('getSlots: encoding child slots'); const subSlots = this.getSlots( arg.values[i], returnSlotMap, @@ -795,33 +765,25 @@ export class Planner { slots.push(...subSlots); } if (isDynamicType(arg.param)) { - console.log('getSlots: tuple/array is dynamic type'); // add pointer flag after slots - slots.push(0xfd); + slots.push(0xfc); } } else { let slot: number; if (arg instanceof ReturnValue) { - console.log('getSlots: return'); slot = returnSlotMap.get(arg.command) as number; } else if (arg instanceof LiteralValue) { - console.log('getSlots: literal'); slot = literalSlotMap.get(arg.value) as number; } else if (arg instanceof StateValue) { - console.log('getSlots: state'); slot = 0xfe; } else if (arg instanceof SubplanValue) { - console.log('getSlots: subplan'); // buildCommands has already built the subplan and put it in the last state slot slot = state.length - 1; } else { throw new Error(`Unknown function argument type '${typeof arg}'`); } if (isDynamicType(arg.param)) { - console.log('getSlots: is dynamic type'); - console.log('getSlots - before: ', slot); slot |= 0x80; - console.log('getSlots - after: ', slot); } slots.push(slot); } @@ -849,16 +811,16 @@ export class Planner { const args = new Array(); // Set pointers total in state const pointers = this.getPointers(inargs); - const slot: number = literalSlotMap.get( - defaultAbiCoder.encode(['uint256'], [pointers]) - ) as number; + let slot: number; + if (pointers > 0) { + slot = literalSlotMap.get( + defaultAbiCoder.encode(['uint256'], [pointers]) + ) as number; + } else { + // If no pointers, add IDX_NO_OFFSET flag + slot = 0xfd; + } args.push(slot); - /* - inargs.forEach((arg) => { - const slots = this.getPointerSlots(arg) - args.push(...slots) - }); - */ inargs.forEach((arg) => { const slots = this.getSlots(arg, returnSlotMap, literalSlotMap, state); args.push(...slots); @@ -895,7 +857,6 @@ export class Planner { ps.literalSlotMap, ps.state ); - console.log('buildCommands - args: ', args); if (args.length > 6) { flags |= CommandFlags.EXTENDED_COMMAND; } @@ -958,7 +919,6 @@ export class Planner { (flags & CommandFlags.EXTENDED_COMMAND) === CommandFlags.EXTENDED_COMMAND ) { - console.log('buildCommands: extended command'); // Extended command encodedCommands.push( hexConcat([ @@ -969,7 +929,6 @@ export class Planner { ); encodedCommands.push(hexConcat([padArray(args, 32, 0xff)])); } else { - console.log('buildCommands: standard command'); // Standard command encodedCommands.push( hexConcat([ @@ -1017,9 +976,6 @@ export class Planner { ); }); - console.log('plan - literalVisiblity: ', literalVisibility); - console.log('plan - state: ', state); - console.log('plan - literalSlotMap: ', literalSlotMap); const ps: PlannerState = { returnSlotMap: new Map(), literalSlotMap, diff --git a/tests/test_planner.ts b/tests/test_planner.ts index db9fd63..53b466f 100644 --- a/tests/test_planner.ts +++ b/tests/test_planner.ts @@ -70,7 +70,7 @@ describe('Planner', () => { expect(commands.length).to.equal(1); expect(commands[0]).to.equal( - '0x771602f7000001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x771602f700fd0001ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(state.length).to.equal(2); @@ -85,7 +85,7 @@ describe('Planner', () => { expect(commands.length).to.equal(1); expect(commands[0]).to.equal( - '0x50527d6e000001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x50527d6e00fd0001ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(state.length).to.equal(2); @@ -100,7 +100,7 @@ describe('Planner', () => { expect(commands.length).to.equal(1); expect(commands[0]).to.equal( - '0xe9536618000001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0xe953661800fd0001ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(state.length).to.equal(2); @@ -124,10 +124,10 @@ describe('Planner', () => { expect(commands.length).to.equal(2); expect(commands[0]).to.equal( - '0x771602f7000001ffffffff01eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x771602f700fd0001ffffff01eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(commands[1]).to.equal( - '0xe9536618000102ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0xe953661800fd0102ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(state.length).to.equal(3); @@ -144,10 +144,10 @@ describe('Planner', () => { expect(commands.length).to.equal(2); expect(commands[0]).to.equal( - '0x771602f7000000ffffffff01eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x771602f700fd0000ffffff01eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(commands[1]).to.equal( - '0x771602f7000001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x771602f700fd0001ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(state.length).to.equal(2); @@ -162,7 +162,7 @@ describe('Planner', () => { expect(commands.length).to.equal(1); expect(commands[0]).to.equal( - '0x367bbd780080ffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x367bbd7800fd80ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(state.length).to.equal(1); @@ -178,7 +178,7 @@ describe('Planner', () => { expect(commands.length).to.equal(1); expect(commands[0]).to.equal( - '0xd824ccf3008081ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0xd824ccf300fd8081ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(state.length).to.equal(2); @@ -198,10 +198,10 @@ describe('Planner', () => { expect(commands.length).to.equal(2); expect(commands[0]).to.equal( - '0xd824ccf3008081ffffffff81eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0xd824ccf300fd8081ffffff81eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(commands[1]).to.equal( - '0x367bbd780081ffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x367bbd7800fd81ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(state.length).to.equal(2); @@ -231,7 +231,7 @@ describe('Planner', () => { expect(commands.length).to.equal(1); expect(commands[0]).to.equal( - '0x08f389c800fefffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x08f389c800fdfefffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(state.length).to.equal(0); @@ -259,7 +259,7 @@ describe('Planner', () => { const { commands, state } = planner.plan(); expect(commands).to.deep.equal([ - '0xde792d5f0082fefffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0xde792d5f00fd82fefffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', ]); expect(state.length).to.equal(3); @@ -273,7 +273,7 @@ describe('Planner', () => { ]) )[0]; expect(subcommands).to.deep.equal([ - '0x771602f7000001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0x771602f700fd0001ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', ]); }); @@ -288,9 +288,9 @@ describe('Planner', () => { const { commands } = planner.plan(); expect(commands).to.deep.equal([ // Invoke subplanner - '0xde792d5f0083fefffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0xde792d5f00fd83fefffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', // sum + 3 - '0x771602f7000102ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0x771602f700fd0102ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', ]); }); @@ -312,9 +312,9 @@ describe('Planner', () => { const { commands, state } = planner.plan(); expect(commands).to.deep.equal([ // Invoke subplanner1 - '0xde792d5f0083fefffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0xde792d5f00fd83fefffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', // Invoke subplanner2 - '0xde792d5f0084fefffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0xde792d5f00fd84fefffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', ]); expect(state.length).to.equal(5); @@ -327,7 +327,7 @@ describe('Planner', () => { )[0]; expect(subcommands2).to.deep.equal([ // sum + 3 - '0x771602f7000102ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0x771602f700fd0102ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', ]); }); @@ -431,7 +431,7 @@ describe('Planner', () => { const { commands } = planner.plan(); expect(commands).to.deep.equal([ - '0xde792d5f0082feffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0xde792d5f00fd82feffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', ]); }); @@ -462,7 +462,7 @@ describe('Planner', () => { expect(commands.length).to.equal(1); expect(commands[0]).to.equal( - '0x771602f7010001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x771602f701fd0001ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); }); @@ -478,7 +478,7 @@ describe('Planner', () => { expect(commands.length).to.equal(1); expect(commands[0]).to.equal( - '0x771602f7020001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x771602f702fd0001ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); }); @@ -493,7 +493,7 @@ describe('Planner', () => { expect(commands.length).to.equal(1); expect(commands[0]).to.equal( - '0x771602f7020001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0x771602f702fd0001ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); }); @@ -508,7 +508,7 @@ describe('Planner', () => { const { commands } = planner.plan(); expect(commands.length).to.equal(1); expect(commands[0]).to.equal( - '0xb6b55f25030001ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0xb6b55f2503fd0001ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); }); @@ -524,8 +524,8 @@ describe('Planner', () => { const { commands } = planner.plan(); expect(commands.length).to.equal(2); expect(commands).to.deep.equal([ - '0x771602f7000001ffffffff01eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - '0xb6b55f25030102ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0x771602f700fd0001ffffff01eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0xb6b55f2503fd0102ffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', ]); }); @@ -561,10 +561,10 @@ describe('Planner', () => { const { commands } = planner.plan(); expect(commands.length).to.equal(2); expect(commands[0]).to.equal( - '0xe473580d40000000000000ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + '0xe473580d80000000000000ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ); expect(commands[1]).to.equal( - '0x00010203040506ffffffffffffffffffffffffffffffffffffffffffffffffff' + '0xfd00010203040506ffffffffffffffffffffffffffffffffffffffffffffffff' ); }); @@ -581,8 +581,8 @@ describe('Planner', () => { planner.add(Test.acceptsBytes(ret)); const { commands } = planner.plan(); expect(commands).to.deep.equal([ - '0x61a7e05e80ffffffffffff80eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - '0x3e9ef66a0080ffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0x61a7e05e40fdffffffffff80eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + '0x3e9ef66a00fd80ffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', ]); }); });