Skip to content

Commit

Permalink
feat(core): Add support for user defined types
Browse files Browse the repository at this point in the history
  • Loading branch information
RPate97 committed Mar 14, 2023
1 parent b944b94 commit fb9442a
Show file tree
Hide file tree
Showing 11 changed files with 353 additions and 21 deletions.
6 changes: 6 additions & 0 deletions .changeset/fuzzy-emus-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@chugsplash/plugins': patch
'@chugsplash/core': patch
---

Add support for user defined types
15 changes: 15 additions & 0 deletions docs/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This is a reference that explains how to assign values to every variable type in
- [Arrays](#arrays)
- [Structs](#structs)
- [Mappings](#mappings)
- [User-Defined Value Types](#user-defined-types)


## Booleans
Expand Down Expand Up @@ -313,3 +314,17 @@ myMultiNestedMapping: {
...
},
```

## [User-Defined Value Types](https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types)
ChugSplash treats your user defined types as if they were their underlying types.

Define your type in Solidity
```solidity
type UserDefinedType is uint256;
UserDefinedType public userDefined;
```

In your ChugSplash config file:
```ts
userDefined: 1
```
12 changes: 11 additions & 1 deletion packages/core/src/actions/bundle.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { fromHexString, toHexString } from '@eth-optimism/core-utils'
import { ethers, providers } from 'ethers'
import MerkleTree from 'merkletreejs'
import { astDereferencer } from 'solidity-ast/utils'

import {
CanonicalConfigArtifacts,
Expand Down Expand Up @@ -374,9 +375,18 @@ export const makeActionBundleFromConfig = async (
})
}

// Create an AST Dereferencer. We must convert the CompilerOutput type to `any` here because
// because a type error will be thrown otherwise. Coverting to `any` is harmless because we use
// Hardhat's default `CompilerOutput`, which is what OpenZeppelin expects.
const dereferencer = astDereferencer(compilerOutput as any)

// Compute our storage slots.
// TODO: One day we'll need to refactor this to support Vyper.
const slots = computeStorageSlots(storageLayout, contractConfig)
const slots = computeStorageSlots(
storageLayout,
contractConfig,
dereferencer
)

// Add SET_STORAGE actions for each storage slot that we want to modify.
for (const slot of slots) {
Expand Down
109 changes: 96 additions & 13 deletions packages/core/src/languages/solidity/storage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { add0x, fromHexString, remove0x } from '@eth-optimism/core-utils'
import { BigNumber, ethers, utils } from 'ethers'
import { ASTDereferencer } from 'solidity-ast/utils'

import { isPreserveKeyword } from '../../utils'
import { ParsedContractConfig } from '../../config/types'
Expand All @@ -9,6 +10,7 @@ import {
SolidityStorageType,
StorageSlotSegment,
} from './types'

import 'core-js/features/array/at'

/**
Expand Down Expand Up @@ -67,7 +69,8 @@ export const encodeVariable = (
storageTypes: {
[name: string]: SolidityStorageType
},
nestedSlotOffset: string
nestedSlotOffset: string,
dereferencer: ASTDereferencer
): Array<StorageSlotSegment> => {
// The current slot key is the slot key of the current storage object plus the `nestedSlotOffset`.
const slotKey = addStorageSlotKeys(storageObj.slot, nestedSlotOffset)
Expand All @@ -83,7 +86,11 @@ export const encodeVariable = (
]
}

const variableType = storageTypes[storageObj.type]
const variableType = getStorageType(
storageObj.type,
storageTypes,
dereferencer
)

// The Solidity compiler uses four encodings to encode state variables: "inplace", "mapping",
// "dynamic_array", and "bytes". Each state variable is assigned an encoding depending on its
Expand All @@ -104,7 +111,8 @@ export const encodeVariable = (
storageObj,
storageTypes,
elementSlotKey,
nestedSlotOffset
nestedSlotOffset,
dereferencer
)
} else if (
variableType.label === 'address' ||
Expand Down Expand Up @@ -245,7 +253,13 @@ export const encodeVariable = (
)
}
slots = slots.concat(
encodeVariable(varVal, memberStorageObj, storageTypes, slotKey)
encodeVariable(
varVal,
memberStorageObj,
storageTypes,
slotKey,
dereferencer
)
)
}
return slots
Expand Down Expand Up @@ -319,7 +333,11 @@ export const encodeVariable = (
)
}

const mappingKeyStorageType = storageTypes[variableType.key]
const mappingKeyStorageType = getStorageType(
variableType.key,
storageTypes,
dereferencer
)

// Encode the mapping key according to its Solidity compiler encoding. The encoding for the
// mapping key is 'bytes' if the mapping key is a string or dynamic bytes. Otherwise, the
Expand Down Expand Up @@ -368,7 +386,13 @@ export const encodeVariable = (
// `nestedSlotOffset` to '0' because it isn't used when calculating the storage slot
// key (we already calculated the storage slot key above).
slots = slots.concat(
encodeVariable(mappingVal, mappingValStorageObj, storageTypes, '0')
encodeVariable(
mappingVal,
mappingValStorageObj,
storageTypes,
'0',
dereferencer
)
)
}
return slots
Expand All @@ -391,7 +415,8 @@ export const encodeVariable = (
storageObj,
storageTypes,
utils.keccak256(slotKey), // The slot key of the array elements begins at the hash of the `slotKey`.
nestedSlotOffset
nestedSlotOffset,
dereferencer
)
)
return slots
Expand Down Expand Up @@ -423,17 +448,27 @@ export const encodeArrayElements = (
[name: string]: SolidityStorageType
},
elementSlotKey: string,
nestedSlotOffset: string
nestedSlotOffset: string,
dereferencer: ASTDereferencer
): Array<StorageSlotSegment> => {
const elementType = storageTypes[storageObj.type].base
const elementType = getStorageType(
storageObj.type,
storageTypes,
dereferencer
).base

if (elementType === undefined) {
throw new Error(
`Could not encode array elements for: ${storageObj.label}. Should never happen.`
)
}

const bytesPerElement = Number(storageTypes[elementType].numberOfBytes)
const elementStorageType = getStorageType(
elementType,
storageTypes,
dereferencer
)
const bytesPerElement = Number(elementStorageType.numberOfBytes)

// Calculate the number of slots to increment when iterating over the array elements. This
// number is only ever greater than one if `bytesPerElement` > 32, which could happen if the
Expand All @@ -460,7 +495,8 @@ export const encodeArrayElements = (
type: elementType,
},
storageTypes,
nestedSlotOffset
nestedSlotOffset,
dereferencer
)
)
// Increment the bytes offset every time we iterate over an element.
Expand Down Expand Up @@ -528,7 +564,8 @@ export const encodeBytesArrayElements = (
*/
export const computeStorageSlots = (
storageLayout: SolidityStorageLayout,
contractConfig: ParsedContractConfig
contractConfig: ParsedContractConfig,
dereferencer: ASTDereferencer
): Array<StorageSlotSegment> => {
const storageEntries: { [storageObjLabel: string]: SolidityStorageObj } = {}

Expand Down Expand Up @@ -567,7 +604,13 @@ export const computeStorageSlots = (

// Encode this variable as series of storage slot key/value pairs and save it.
segments = segments.concat(
encodeVariable(variableValue, storageObj, storageLayout.types, '0')
encodeVariable(
variableValue,
storageObj,
storageLayout.types,
'0',
dereferencer
)
)
}

Expand Down Expand Up @@ -625,3 +668,43 @@ export const computeStorageSlots = (

return segments
}

export const getStorageType = (
variableType: string,
storageTypes: {
[name: string]: SolidityStorageType
},
dereferencer: ASTDereferencer
): SolidityStorageType => {
if (!variableType.startsWith('t_userDefinedValueType')) {
return storageTypes[variableType]
} else {
const userDefinedValueAstId = variableType.split(')').at(-1)

if (userDefinedValueAstId === undefined) {
throw new Error(
`Could not find AST ID for variable type: ${variableType}. Should never happen.`
)
}

const userDefinedValueNode = dereferencer(
['UserDefinedValueTypeDefinition'],
parseInt(userDefinedValueAstId, 10)
)

const label =
userDefinedValueNode.underlyingType.typeDescriptions.typeString
if (label === undefined || label === null) {
throw new Error(
`Could not find label for user-defined value type: ${variableType}. Should never happen.`
)
}

const { encoding, numberOfBytes } = storageTypes[variableType]
return {
label,
encoding,
numberOfBytes,
}
}
}
1 change: 1 addition & 0 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1687,6 +1687,7 @@ export const getCanonicalConfigArtifacts = async (
for (const { compilerInput, compilerOutput } of solcArray) {
const contractOutput =
compilerOutput.contracts?.[sourceName]?.[contractName]

if (contractOutput !== undefined) {
const creationCodeWithConstructorArgs =
getCreationCodeWithConstructorArgs(
Expand Down
1 change: 1 addition & 0 deletions packages/demo/contracts/HelloChugSplash.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.15;

contract HelloChugSplash {
type UFixed256x18 is uint256;
uint8 public number;
bool public stored;
address public otherStorage;
Expand Down
24 changes: 24 additions & 0 deletions packages/plugins/chugsplash/foundry/deploy.t.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,29 @@ const variables = {
bytes32Test: '0x' + '11'.repeat(32),
longBytesTest:
'0x123456789101112131415161718192021222324252627282930313233343536373839404142434445464',
userDefinedTypeTest: '1000000000000000000',
userDefinedBytesTest: '0x' + '11'.repeat(32),
userDefinedInt: ethers.constants.MinInt256.toString(),
userDefinedInt8: -128,
userDefinedUint8: 255,
userDefinedBool: true,
userDefinedFixedArray: ['1000000000000000000', '1000000000000000000'],
userDefinedFixedNestedArray: [
['1000000000000000000', '1000000000000000000'],
['1000000000000000000', '1000000000000000000'],
],
userDefinedDynamicArray: [
'1000000000000000000',
'1000000000000000000',
'1000000000000000000',
],
stringToUserDefinedMapping: {
testKey: '1000000000000000000',
},
userDefinedToStringMapping: {
// eslint-disable-next-line prettier/prettier
'1000000000000000000': 'testVal',
},
contractTest: '0x' + '11'.repeat(20),
enumTest: 1,
simpleStruct: {
Expand All @@ -24,6 +47,7 @@ const variables = {
b: {
5: 'testVal',
},
c: '1000000000000000000',
},
uint64FixedArray: [1, 10, 100, 1_000, 10_000],
uint128FixedNestedArray: [
Expand Down
22 changes: 22 additions & 0 deletions packages/plugins/contracts/Storage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@
pragma solidity ^0.8.9;

contract Storage {
type UserDefinedType is uint256;
type UserDefinedBytes32 is bytes32;
type UserDefinedInt is int;
type UserDefinedInt8 is int8;
type UserDefinedUint8 is uint8;
type UserDefinedBool is bool;

enum TestEnum { A, B, C }
struct SimpleStruct { bytes32 a; uint128 b; uint128 c; }
struct ComplexStruct {
int32 a;
mapping(uint32 => string) b;
UserDefinedType c;
}

int public minInt256;
Expand All @@ -18,6 +26,20 @@ contract Storage {
bytes public bytesTest;
bytes public longBytesTest;
bytes32 public bytes32Test;

UserDefinedType public userDefinedTypeTest;
UserDefinedBytes32 public userDefinedBytesTest;
UserDefinedInt public userDefinedInt;
UserDefinedInt8 public userDefinedInt8;
UserDefinedUint8 public userDefinedUint8;
UserDefinedBool public userDefinedBool;
mapping(UserDefinedType => string) public userDefinedToStringMapping;
mapping(string => UserDefinedType) public stringToUserDefinedMapping;
UserDefinedType[2] public userDefinedFixedArray;
UserDefinedType[2][2] public userDefinedFixedNestedArray;
UserDefinedType[] public userDefinedDynamicArray;


Storage public contractTest;
TestEnum public enumTest;
SimpleStruct public simpleStruct;
Expand Down
Loading

0 comments on commit fb9442a

Please sign in to comment.