Skip to content

Commit

Permalink
feat(core): Add support for flexible constructors, add support for mu…
Browse files Browse the repository at this point in the history
…table constructor arguments
  • Loading branch information
RPate97 committed May 4, 2023
1 parent def1531 commit 1d54d12
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 39 deletions.
6 changes: 6 additions & 0 deletions .changeset/kind-sheep-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@chugsplash/plugins': patch
'@chugsplash/core': patch
---

Add support for flexible constructors and mutable constructor arguments
125 changes: 92 additions & 33 deletions packages/core/src/config/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,19 @@ export const assertValidUserConfigFields = async (
)
}
}

if (
contractConfig.unsafeAllowFlexibleConstructor === true &&
contractConfig.kind !== 'no-proxy'
) {
logValidationError(
'error',
`Detected the 'unsafeAllowFlexibleConstructor' field set to true in the ChugSplash config file for proxied contract ${contractConfig.contract}. This field can only be used for non-proxied contracts. Please remove this field or set it to false.`,
[],
cre.silent,
cre.stream
)
}
}

if (validationErrors && exitOnFailure) {
Expand Down Expand Up @@ -819,8 +832,8 @@ export const parseDynamicBytes: VariableHandler<UserConfigVariable, string> = (

return parseBytes(
variable,
storageObj.label,
variableType.label,
storageObj.type,
storageObj.offset
)
}
Expand Down Expand Up @@ -1237,27 +1250,61 @@ const parseArrayConstructorArg = (
return parsedValues
}

export const parseStructConstructorArg = (
paramType: ParamType,
constructorArgValue: UserConfigVariable
) => {
if (typeof constructorArgValue !== 'object') {
throw new InputError(
`Expected object for ${
paramType.name
} but got ${typeof constructorArgValue}`
)
}

const parsedValues: ParsedConfigVariable = {}
for (const [key, value] of Object.entries(constructorArgValue)) {
const inputChild = paramType.components.find((component) => {
return component.name === key
})
if (inputChild === undefined) {
throw new InputError(
`User entered incorrect member in ${paramType.name}: ${key}`
)
}
parsedValues[key] = parseAndValidateConstructorArg(inputChild, value)
}

return parsedValues
}

const parseAndValidateConstructorArg = (
input: ParamType,
constructorArgValue: UserConfigVariable
): ParsedConfigVariable => {
const constructorArgType = input.type
if (input.baseType.startsWith('uint') || input.baseType.startsWith('int')) {
// We fetch a new ParamType using the input type even though input is a ParamType object
// This is b/c input is an incomplete object, so fetching the new ParamType yields
// an object with more useful information on it
const paramType =
input.type === 'tuple' ? input : ethers.utils.ParamType.from(input.type)
const name = input.name
if (
paramType.baseType &&
(paramType.baseType.startsWith('uint') ||
paramType.baseType.startsWith('int'))
) {
// Since the number of bytes is not easily accessible, we parse it from the type string.
const suffix = constructorArgType.replace(/u?int/g, '')
const bits = parseInt(suffix, 10)
const numberOfBytes = bits / 8

if (constructorArgType.startsWith('int')) {
return parseInteger(constructorArgValue, input.name, numberOfBytes)
return parseInteger(constructorArgValue, name, numberOfBytes)
} else {
return parseUnsignedInteger(
constructorArgValue,
input.name,
numberOfBytes
)
return parseUnsignedInteger(constructorArgValue, name, numberOfBytes)
}
} else if (input.baseType === 'address') {
} else if (paramType.baseType === 'address') {
// if the value is a contract reference, then we don't parse it and assume it is correct given
// that we handle validating contract references elsewhere.
// Note that references to any proxied contracts will have already been resolved at this point,
Expand All @@ -1270,28 +1317,28 @@ const parseAndValidateConstructorArg = (
) {
return constructorArgValue
} else {
return parseAddress(constructorArgValue, input.name)
return parseAddress(constructorArgValue, name)
}
} else if (input.baseType === 'bool') {
return parseBool(constructorArgValue, input.name)
} else if (input.baseType.startsWith('bytes')) {
} else if (paramType.baseType === 'bool') {
return parseBool(constructorArgValue, name)
} else if (paramType.baseType && paramType.baseType.startsWith('bytes')) {
const suffix = constructorArgType.replace(/bytes/g, '')
const numberOfBytes = parseInt(suffix, 10)

return parseFixedBytes(
constructorArgValue,
constructorArgType,
input.name,
name,
numberOfBytes
)
} else if (input.baseType === 'string') {
return parseBytes(constructorArgValue, input.name, input.type, 0)
} else if (input.baseType === 'array') {
return parseArrayConstructorArg(input, constructorArgValue)
} else if (paramType.baseType === 'string') {
return parseBytes(constructorArgValue, name, paramType.type, 0)
} else if (paramType.baseType === 'array') {
return parseArrayConstructorArg(paramType, constructorArgValue)
} else {
// throw or log error
throw new InputError(
`Unsupported constructor argument type: ${input.type} for argument ${input.name}`
`Unsupported constructor argument type: ${paramType.type} for argument ${name}`
)
}
}
Expand Down Expand Up @@ -1331,33 +1378,45 @@ export const parseContractConstructorArgs = (
}
}

const constructorArgNames = constructorFragment.inputs.map(
(input) => input.name
const functionConstructorArgs = constructorFragment.inputs.filter(
(el) => el.type === 'function'
)
if (functionConstructorArgs.length > 0) {
logValidationError(
'error',
`Detected function type in constructor arguments for ${referenceName}. Function types are not allowed in constructor arugments.`,
[],
cre.silent,
cre.stream
)
}

const constructorArgNames = constructorFragment.inputs
.filter((el) => el.type !== 'function')
.map((input) => input.name)
const incorrectConstructorArgNames = Object.keys(userConstructorArgs).filter(
(argName) => !constructorArgNames.includes(argName)
)
const undefinedConstructorArgNames: string[] = []
const inputFormatErrors: string[] = []

constructorFragment.inputs.forEach((input) => {
if (input.type === 'function') {
return
}

const constructorArgValue = userConstructorArgs[input.name]
if (constructorArgValue === undefined) {
undefinedConstructorArgNames.push(input.name)
return
}

try {
// We fetch a new ParamType using the input type even though input is a ParamType object
// This is b/c input is an incomplete object, so fetching the new ParamType yields
// an object with more useful information on it
const paramType = ethers.utils.ParamType.from(input.type)
parsedConstructorArgs[input.name] = parseAndValidateConstructorArg(
paramType,
input,
constructorArgValue
)
} catch (e) {
throw e
inputFormatErrors.push((e as Error).message)
}
})
Expand Down Expand Up @@ -2047,6 +2106,12 @@ export const assertValidConstructorArgs = (
cachedConstructorArgs[referenceName] = args
}

// Exit if any validation errors were detected up to this point. We exit early here because invalid
// constructor args can cause the rest of the parsing logic to fail with cryptic errors
if (validationErrors && exitOnFailure) {
process.exit(1)
}

// Resolve any no-proxy contract addresses that are left
// We have do this separately b/c we need to parse all the constructor args and resolve all the proxied contract addresses
// before we can resolve the addresses of no-proxy contracts which might include contract references in their contructor args
Expand Down Expand Up @@ -2075,12 +2140,6 @@ export const assertValidConstructorArgs = (
})
)

// Exit if any validation errors were detected up to this point. We exit early here because invalid
// constructor args can cause the rest of the parsing logic to fail with cryptic errors
if (validationErrors && exitOnFailure) {
process.exit(1)
}

// We return the cached values so we can use them in later steps without rereading the files
return {
cachedCompilerOutput,
Expand Down
5 changes: 4 additions & 1 deletion packages/plugins/chugsplash/foundry/deploy.t.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ const complexConstructorArgs = {
[7, 8, 9],
[10, 11, 12],
],
[[13, 14, 15]],
[
[13, 14, 15],
[16, 17, 18],
],
],
}

Expand Down
2 changes: 2 additions & 0 deletions packages/plugins/contracts/ComplexConstructorArgs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.9;

contract ComplexConstructorArgs {
type UserDefinedType is uint256;

string public str;
bytes public dynamicBytes;
uint64[5] public uint64FixedArray;
Expand Down
5 changes: 4 additions & 1 deletion packages/plugins/test/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ export const complexConstructorArgs = {
[7, 8, 9],
[10, 11, 12],
],
[[13, 14, 15]],
[
[13, 14, 15],
[16, 17, 18],
],
],
}

Expand Down
34 changes: 30 additions & 4 deletions packages/plugins/test/foundry/ChugSplash.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ contract ChugSplashTest is Test {
myStorage = Storage(chugsplash.getAddress(deployConfig, "MyStorage"));
mySimpleStorage = SimpleStorage(chugsplash.getAddress(deployConfig, "MySimpleStorage"));
myStateless = Stateless(chugsplash.getAddress(deployConfig, "Stateless"));
myComplexConstructorArgs = ComplexConstructorArgs(chugsplash.getAddress(deployConfig, "ComplexConstructorArgs"));

registry = ChugSplashRegistry(chugsplash.getRegistryAddress());
}
Expand Down Expand Up @@ -430,21 +431,21 @@ contract ChugSplashTest is Test {
}

function testSetMutableUint64FixedArrayConstructorArg() public {
uint64[5] memory uint64FixedArray = [1, 10, 100, 1_000, 10_000];
uint16[5] memory uint64FixedArray = [1, 10, 100, 1_000, 10_000];
for (uint i = 0; i < uint64FixedArray.length; i++) {
assertEq(myComplexConstructorArgs.uint64FixedArray(i), uint64FixedArray[i]);
}
}

function testSetMutableInt64DynamicArrayConstructorArg() public {
int64[] memory int64DynamicArray = [-5, 50, -500, 5_000, -50_000, 500_000, -5_000_000];
int24[7] memory int64DynamicArray = [-5, 50, -500, 5_000, -50_000, 500_000, -5_000_000];
for (uint i = 0; i < int64DynamicArray.length; i++) {
assertEq(myComplexConstructorArgs.int64DynamicArray(i), int64DynamicArray[i]);
}
}

function testSetMutableUint64FixedNestedArrayConstructorArg() public {
uint64[] memory uint64FixedNestedArray = [
uint8[5][6] memory uint64FixedNestedArray = [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
Expand All @@ -453,9 +454,34 @@ contract ChugSplashTest is Test {
[26, 27, 28, 29, 30]
];
for (uint i = 0; i < uint64FixedNestedArray.length; i++) {
for (uint j = 0; j < uint64FixedNestedArray[i].length; i++) {
for (uint j = 0; j < uint64FixedNestedArray[i].length; j++) {
assertEq(myComplexConstructorArgs.uint64FixedNestedArray(i, j), uint64FixedNestedArray[i][j]);
}
}
}

function testSetMutableUint64DynamicMultinestedArrayConstructorArg() public {
uint8[3][2][3] memory uint64DynamicMultiNestedArray = [
[
[1, 2, 3],
[4, 5, 6]
],
[
[7, 8, 9],
[10, 11, 12]
],
[
[13, 14, 15],
[16, 17, 18]
]
];

for (uint i = 0; i < uint64DynamicMultiNestedArray.length; i++) {
for (uint j = 0; j < uint64DynamicMultiNestedArray[i].length; j++) {
for (uint k = 0; k < uint64DynamicMultiNestedArray[i][j].length; k++) {
assertEq(myComplexConstructorArgs.uint64DynamicMultiNestedArray(i, j, k), uint64DynamicMultiNestedArray[i][j][k]);
}
}
}
}
}

0 comments on commit 1d54d12

Please sign in to comment.