Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support fixed arrays and tuples #35

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions abis/Fixed.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
256 changes: 207 additions & 49 deletions src/planner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
}

/**
Expand Down Expand Up @@ -172,10 +192,31 @@ export type ContractFunction = (...args: Array<any>) => 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 {
if (isDynamicType(param)) {
return new LiteralValue(
Expand All @@ -197,6 +238,20 @@ function encodeArg(arg: any, param: ParamType): Value {
return arg;
} else if (arg instanceof Planner) {
return new SubplanValue(arg);
} else if (param.baseType === 'array') {
const values: Value[] = [];
for (let i = 0; i < arg.length; i++) {
values.push(encodeArg(arg[i], param.arrayChildren));
}
return new ArrayValue(param, values);
} else if (param.baseType === 'tuple') {
const values: Value[] = [];
for (let i = 0; i < param.components.length; i++) {
values.push(
encodeArg(arg[param.components[i].name], param.components[i])
);
}
return new TupleValue(param, values);
} else {
return abiEncodeSingle(param, arg);
}
Expand Down Expand Up @@ -561,6 +616,60 @@ export class Planner {
this.commands.push(new Command(call, CommandType.RAWCALL));
}

private setVisibility(
arg: Value,
command: Command,
commandVisibility: Map<Command, Command>,
literalVisibility: Map<string, Command>,
seen: Set<Command>,
planners: Set<Planner>
) {
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) {
literalVisibility.set(arg.value, command);
} else if (arg instanceof ArrayValue || arg instanceof TupleValue) {
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++) {
this.setVisibility(
arg.values[i],
command,
commandVisibility,
literalVisibility,
seen,
planners
);
}
} 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<Command>(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<Command, Command>,
literalVisibility: Map<string, Command>,
Expand Down Expand Up @@ -592,61 +701,74 @@ export class Planner {
inargs = [command.call.callvalue].concat(inargs);
}

// Set pointers total in state
const pointers = this.getPointers(inargs);
if (pointers > 0) {
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) {
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<Command>(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 buildCommandArgs(
command: Command,
private getPointers(args: Value[]): number {
let count = 0;
for (let arg of args) {
if (arg instanceof ArrayValue || arg instanceof TupleValue) {
// Tuples can be composed of other tuples or arrays
if (isDynamicType(arg.param)) {
count++;
}
}
}
return count;
}

private getSlots(
arg: Value,
returnSlotMap: Map<Command, number>,
literalSlotMap: Map<string, number>,
state: Array<string>
): Array<number> {
// Build a list of argument value indexes
let inargs = command.call.args;
if (
(command.call.flags & CommandFlags.CALLTYPE_MASK) ===
CommandFlags.CALL_WITH_VALUE
) {
if (!command.call.callvalue) {
throw new Error('Call with value must have a value parameter');
const slots = new Array<number>();
if (arg instanceof ArrayValue || arg instanceof TupleValue) {
// 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);
}
inargs = [command.call.callvalue].concat(inargs);
}

const args = new Array<number>();
inargs.forEach((arg) => {
// Tuples/arrays can be composed of other tuples or arrays
for (let i = 0; i < arg.values.length; i++) {
const subSlots = this.getSlots(
arg.values[i],
returnSlotMap,
literalSlotMap,
state
);
slots.push(...subSlots);
}
if (isDynamicType(arg.param)) {
// add pointer flag after slots
slots.push(0xfc);
}
} else {
let slot: number;
if (arg instanceof ReturnValue) {
slot = returnSlotMap.get(arg.command) as number;
Expand All @@ -663,9 +785,46 @@ export class Planner {
if (isDynamicType(arg.param)) {
slot |= 0x80;
}
args.push(slot);
});
slots.push(slot);
}
return slots;
}

private buildCommandArgs(
command: Command,
returnSlotMap: Map<Command, number>,
literalSlotMap: Map<string, number>,
state: Array<string>
): Array<number> {
// Build a list of argument value indexes
let inargs = command.call.args;
if (
(command.call.flags & CommandFlags.CALLTYPE_MASK) ===
CommandFlags.CALL_WITH_VALUE
) {
if (!command.call.callvalue) {
throw new Error('Call with value must have a value parameter');
}
inargs = [command.call.callvalue].concat(inargs);
}

const args = new Array<number>();
// Set pointers total in state
const pointers = this.getPointers(inargs);
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.getSlots(arg, returnSlotMap, literalSlotMap, state);
args.push(...slots);
});
return args;
}

Expand Down Expand Up @@ -698,7 +857,6 @@ export class Planner {
ps.literalSlotMap,
ps.state
);

if (args.length > 6) {
flags |= CommandFlags.EXTENDED_COMMAND;
}
Expand Down
Loading