Skip to content

Commit

Permalink
Add Support for Mapping Types (sphinx-labs#149)
Browse files Browse the repository at this point in the history
* parent 4262614
author Ryan Pate <ryantpate97@gmail.com> 1667458967 -0700
committer Ryan Pate <ryantpate97@gmail.com> 1667689541 -0700

Add support for mapping types

* add support for 0x prefixed bytes

Co-authored-by: sam-goldman <106038229+sam-goldman@users.noreply.github.com>
  • Loading branch information
RPate97 and sam-goldman authored Nov 6, 2022
1 parent b760077 commit 5e74723
Show file tree
Hide file tree
Showing 9 changed files with 438 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/olive-waves-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@chugsplash/core': patch
'@chugsplash/plugins': patch
---

Add support for mappings
86 changes: 75 additions & 11 deletions packages/core/src/languages/solidity/storage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fromHexString, remove0x } from '@eth-optimism/core-utils'
import { BigNumber, ethers } from 'ethers'
import { BigNumber, ethers, utils } from 'ethers'

import { ContractConfig } from '../../config'
import {
Expand Down Expand Up @@ -64,18 +64,18 @@ const encodeVariable = (
storageTypes: {
[name: string]: SolidityStorageType
},
nestedSlotOffset = 0
): Array<StorageSlotPair> => {
const variableType = storageTypes[storageObj.type]

// Slot key will be the same no matter what so we can just compute it here.
const slotKey =
'0x' +
nestedSlotOffset = 0,
// Slot key will be the same unless we are storing a mapping.
// So default to calculating it here, unless one is passed in.
slotKey = '0x' +
remove0x(
BigNumber.from(
parseInt(storageObj.slot as any, 10) + nestedSlotOffset
).toHexString()
).padStart(64, '0')
).padStart(64, '0'),
mappingType: string | undefined = undefined
): Array<StorageSlotPair> => {
const variableType = storageTypes[mappingType ?? storageObj.type]

if (variableType.encoding === 'inplace') {
if (storageObj.type.startsWith('t_array')) {
Expand Down Expand Up @@ -162,6 +162,7 @@ const encodeVariable = (
]
} else if (
variableType.label.startsWith('uint') ||
variableType.label.startsWith('int') ||
variableType.label.startsWith('enum')
) {
if (
Expand Down Expand Up @@ -221,12 +222,18 @@ const encodeVariable = (
`incorrect member in ${variableType.label}: ${varName}`
)
}
// if this struct is within a mapping, then the key must be calculated
// using the passed in slotkey
const offsetKey = BigNumber.from(slotKey)
.add(parseInt(currMember.slot as any, 10))
.toHexString()
slots = slots.concat(
encodeVariable(
varVal,
currMember,
storageTypes,
nestedSlotOffset + parseInt(storageObj.slot as any, 10)
nestedSlotOffset + parseInt(storageObj.slot as any, 10),
mappingType ? offsetKey : undefined
)
)
}
Expand Down Expand Up @@ -266,7 +273,64 @@ const encodeVariable = (
throw new Error('large strings (>31 bytes) not supported')
}
} else if (variableType.encoding === 'mapping') {
throw new Error('mapping types not yet supported')
let slots = []
for (const [key, value] of Object.entries(variable)) {
// default pack type for value types
let type = variableType.key.split('_')[1]
// default key encoding for value types
let encodedKey: string | Uint8Array = encodeVariable(
key,
storageObj,
storageTypes,
nestedSlotOffset + parseInt(storageObj.slot as any, 10),
undefined,
variableType.key
)[0].val

if (variableType.key.startsWith('t_uint')) {
// all uints must be packed with type uint256
type = 'uint256'
} else if (variableType.key.startsWith('t_int')) {
// all ints must be packed with type int256
type = 'int256'
} else if (variableType.key.startsWith('t_string')) {
// strings do not need to be encoded
// pack type can be pulled from input type
encodedKey = key
} else if (variableType.key.startsWith('t_bytes')) {
// bytes do not need to be encoded, but must be converted from the input string
// pack type can be pulled straight from input type
encodedKey = fromHexString(key)
}

// key for nested mappings is computed by packing and hashing the key of the child mapping
let concatenated
if (mappingType) {
concatenated = ethers.utils.solidityPack(
[type, 'uint256'],
[encodedKey, slotKey]
)
} else {
concatenated = ethers.utils.solidityPack(
[type, 'uint256'],
[encodedKey, BigNumber.from(storageObj.slot).toHexString()]
)
}

const mappingKey = utils.keccak256(concatenated)

slots = slots.concat(
encodeVariable(
value,
storageObj,
storageTypes,
0,
mappingKey,
variableType.value
)
)
}
return slots
} else if (variableType.encoding === 'dynamic_array') {
throw new Error('array types not yet supported')
} else {
Expand Down
4 changes: 4 additions & 0 deletions packages/plugins/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
dist/
.env
.deployed
artifacts
cache
deployments
84 changes: 84 additions & 0 deletions packages/plugins/chugsplash/SimpleStorage.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ChugSplashConfig } from '@chugsplash/core'

const config: ChugSplashConfig = {
// Configuration options for the project:
options: {
projectName: 'My First Project',
projectOwner: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
},
// Below, we define all of the contracts in the deployment along with their state variables.
contracts: {
// First contract config:
FirstSimpleStorage: {
contract: 'SimpleStorage',
variables: {
testInt: 1,
number: 1,
stored: true,
storageName: 'First',
testStruct: {
a: 1,
b: 2,
c: 3,
},
strTest: {
string: 'test',
},
uintTest: {
uint: 1234,
},
boolTest: {
bool: true,
},
addressTest: {
address: '0x1111111111111111111111111111111111111111',
},
structTest: {
test: {
a: 1,
b: 2,
c: 3,
},
},
uintStrTest: {
1: 'test',
},
intStrTest: {
1: 'test',
},
int8StrTest: {
1: 'test',
},
int128StrTest: {
1: 'test',
},
uint8StrTest: {
1: 'test',
},
uint128StrTest: {
1: 'test',
},
addressStrTest: {
'0x1111111111111111111111111111111111111111': 'test',
},
bytesStrTest: {
'0xabcd': 'test',
},
nestedMappingTest: {
test: {
test: 'success',
},
},
multiNestedMapping: {
1: {
test: {
'0x1111111111111111111111111111111111111111': 2,
},
},
},
},
},
},
}

export default config
103 changes: 103 additions & 0 deletions packages/plugins/contracts/SimpleStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract SimpleStorage {
struct S { uint16 a; uint16 b; uint256 c; }
int internal testInt;
uint8 internal number;
bool internal stored;
string internal storageName;
S testStruct;
mapping(string => string) public strTest;
mapping(string => uint) public uintTest;
mapping(string => bool) public boolTest;
mapping(string => address) public addressTest;

mapping(uint => string) public uintStrTest;
mapping(int => string) public intStrTest;
mapping(int8 => string) public int8StrTest;
mapping(int128 => string) public int128StrTest;
mapping(uint8 => string) public uint8StrTest;
mapping(uint128 => string) public uint128StrTest;
mapping(address => string) public addressStrTest;
mapping(bytes => string) public bytesStrTest;
mapping(string => S) structTest;
mapping(string => mapping(string => string)) public nestedMappingTest;
mapping(uint8 => mapping(string => mapping(address => uint))) public multiNestedMapping;

function getNumber() external view returns (uint8) {
return number;
}

function isStored() external view returns (bool) {
return stored;
}

function getStorageName() external view returns (string memory) {
return storageName;
}

function getStruct() external view returns (S memory) {
return testStruct;
}

function getStringTestMappingValue(string memory key) external view returns (string memory) {
return strTest[key];
}

function getIntTestMappingValue(string memory key) external view returns (uint) {
return uintTest[key];
}

function getBoolTestMappingValue(string memory key) external view returns (bool) {
return boolTest[key];
}

function getAddressTestMappingValue(string memory key) external view returns (address) {
return addressTest[key];
}

function getStructTestMappingValue(string memory key) external view returns (S memory) {
return structTest[key];
}

function getUintStringTestMappingValue(uint key) external view returns (string memory) {
return uintStrTest[key];
}

function getIntStringTestMappingValue(int key) external view returns (string memory) {
return intStrTest[key];
}

function getInt8StringTestMappingValue(int8 key) external view returns (string memory) {
return int8StrTest[key];
}

function getInt128StringTestMappingValue(int128 key) external view returns (string memory) {
return int128StrTest[key];
}

function getUint8StringTestMappingValue(uint8 key) external view returns (string memory) {
return uint8StrTest[key];
}

function getUint128StringTestMappingValue(uint128 key) external view returns (string memory) {
return uint128StrTest[key];
}

function getAddressStringTestMappingValue(address key) external view returns (string memory) {
return addressStrTest[key];
}

function getBytesStringTestMappingValue(bytes memory key) external view returns (string memory) {
return bytesStrTest[key];
}

function getNestedTestMappingValue(string memory keyOne, string memory keyTwo) external view returns (string memory) {
return nestedMappingTest[keyOne][keyTwo];
}

function getMultiNestedMappingTestMappingValue(uint8 keyOne, string memory keyTwo, address keyThree) external view returns (uint) {
return multiNestedMapping[keyOne][keyTwo][keyThree];
}
}
56 changes: 56 additions & 0 deletions packages/plugins/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { HardhatUserConfig } from 'hardhat/types'
import * as dotenv from 'dotenv'

// Hardhat plugins
import '@nomiclabs/hardhat-ethers'
import './dist'

// Load environment variables from .env
dotenv.config()

const accounts = process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []

const config: HardhatUserConfig = {
solidity: {
version: '0.8.15',
settings: {
outputSelection: {
'*': {
'*': ['storageLayout'],
},
},
},
},
networks: {
localhost: {
url: 'http://localhost:8545',
},
goerli: {
chainId: 5,
url: `https://goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts,
},
'optimism-goerli': {
chainId: 420,
url: `https://optimism-goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts,
},
optimism: {
chainId: 10,
url: `https://optimism-mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts,
},
arbitrum: {
chainId: 42161,
url: 'https://arb1.arbitrum.io/rpc',
accounts,
},
'arbitrum-goerli': {
chainId: 421613,
url: 'https://goerli-rollup.arbitrum.io/rpc',
accounts,
},
},
}

export default config
3 changes: 2 additions & 1 deletion packages/plugins/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.2.1",
"hardhat": "^2.10.0"
"hardhat": "^2.10.0",
"chai": "^4.3.6"
},
"peerDependencies": {
"@nomiclabs/hardhat-ethers": "^2",
Expand Down
Loading

0 comments on commit 5e74723

Please sign in to comment.