Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
19468e49
Unverified
Commit
19468e49
authored
Jun 24, 2023
by
OptimismBot
Committed by
GitHub
Jun 24, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6134 from ethereum-optimism/ctb/optimize-spacer-check
contracts-bedrock: optimize storage layout checks
parents
1c00dcbc
326a2aaa
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
183 additions
and
125 deletions
+183
-125
package.json
packages/contracts-bedrock/package.json
+1
-1
validate-spacers.ts
packages/contracts-bedrock/scripts/validate-spacers.ts
+182
-0
deposits.ts
packages/contracts-bedrock/tasks/deposits.ts
+0
-122
index.ts
packages/contracts-bedrock/tasks/index.ts
+0
-2
No files found.
packages/contracts-bedrock/package.json
View file @
19468e49
...
...
@@ -32,7 +32,7 @@
"gas-snapshot"
:
"yarn build:differential && yarn build:fuzz && forge snapshot --no-match-test 'testDiff|testFuzz|invariant|generateArtifact'"
,
"storage-snapshot"
:
"./scripts/storage-snapshot.sh"
,
"validate-deploy-configs"
:
"hardhat compile && hardhat generate-deploy-config && ./scripts/validate-deploy-configs.sh"
,
"validate-spacers"
:
"
hardhat compile && hardhat validate-spacer
s"
,
"validate-spacers"
:
"
forge build && npx ts-node scripts/validate-spacers.t
s"
,
"slither"
:
"./scripts/slither.sh"
,
"slither:triage"
:
"TRIAGE_MODE=1 ./scripts/slither.sh"
,
"clean"
:
"rm -rf ./dist ./artifacts ./forge-artifacts ./cache ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./src/contract-artifacts.ts ./test-case-generator/fuzz"
,
...
...
packages/contracts-bedrock/
task
s/validate-spacers.ts
→
packages/contracts-bedrock/
script
s/validate-spacers.ts
View file @
19468e49
import
{
task
}
from
'
hardhat/config
'
import
{
parseFullyQualifiedName
}
from
'
hardhat/utils/contract-names
'
import
{
HardhatRuntimeEnvironment
}
from
'
hardhat/types
'
import
fs
from
'
fs
'
import
path
from
'
path
'
import
layoutLock
from
'
../layout-lock.json
'
// Artifacts that should be skipped when inspecting their storage layout
const
skipped
=
{
// Both of these are skipped because they are meant to be inherited
// by the CrossDomainMessenger. It is the CrossDomainMessenger that
// should be inspected, not these contracts.
CrossDomainMessengerLegacySpacer0
:
true
,
CrossDomainMessengerLegacySpacer1
:
true
,
/**
* Directory path to the artifacts.
* Can be configured as the first argument to the script or
* defaults to the forge-artifacts directory.
*/
const
directoryPath
=
process
.
argv
[
2
]
||
path
.
join
(
__dirname
,
'
..
'
,
'
forge-artifacts
'
)
/**
* Returns true if the contract should be skipped when inspecting its storage layout.
* This is useful for abstract contracts that are meant to be inherited.
* The two particular targets are:
* - CrossDomainMessengerLegacySpacer0
* - CrossDomainMessengerLegacySpacer1
*/
const
skipped
=
(
contractName
:
string
):
boolean
=>
{
return
contractName
.
includes
(
'
CrossDomainMessengerLegacySpacer
'
)
}
/**
* Parses the fully qualified name of a contract into the name of the contract.
* For example `contracts/Foo.sol:Foo` becomes `Foo`.
*/
const
parseFqn
=
(
name
:
string
):
string
=>
{
const
parts
=
name
.
split
(
'
:
'
)
return
parts
[
parts
.
length
-
1
]
}
/**
...
...
@@ -68,121 +86,97 @@ const parseVariableInfo = (
}
}
task
(
'
validate-spacers
'
,
'
validates that spacer variables are in the correct positions
'
).
setAction
(
async
({},
hre
:
HardhatRuntimeEnvironment
)
=>
{
const
accounted
:
string
[]
=
[]
const
names
=
await
hre
.
artifacts
.
getAllFullyQualifiedNames
()
for
(
const
fqn
of
names
)
{
// Script is remarkably slow because of getBuildInfo, so better to skip test files since they
// don't matter for this check.
if
(
fqn
.
includes
(
'
.t.sol
'
))
{
continue
}
/**
* Main logic of the script
* - Ensures that all of the spacer variables are named correctly
* - Ensures that storage slots in the layout lock file do not change
*/
const
main
=
async
()
=>
{
const
paths
=
[]
console
.
log
(
`Processing
${
fqn
}
`
)
const
parsed
=
parseFullyQualifiedName
(
fqn
)
const
contractName
=
parsed
.
contractName
const
readFilesRecursively
=
(
dir
:
string
)
=>
{
const
files
=
fs
.
readdirSync
(
dir
)
if
(
skipped
[
contractName
]
===
true
)
{
console
.
log
(
`Skipping
${
contractName
}
because it is marked as skippable`
)
continue
for
(
const
file
of
files
)
{
const
filePath
=
path
.
join
(
dir
,
file
)
const
fileStat
=
fs
.
statSync
(
filePath
)
if
(
fileStat
.
isDirectory
())
{
readFilesRecursively
(
filePath
)
}
else
{
paths
.
push
(
filePath
)
}
}
}
// Some files may not have buildInfo (certain libraries). We can safely skip these because we
// make sure that everything is accounted for anyway.
const
buildInfo
=
await
hre
.
artifacts
.
getBuildInfo
(
fqn
)
if
(
buildInfo
===
undefined
)
{
console
.
log
(
`Skipping
${
fqn
}
because it has no buildInfo`
)
readFilesRecursively
(
directoryPath
)
for
(
const
filePath
of
paths
)
{
if
(
filePath
.
includes
(
'
t.sol
'
))
{
continue
}
const
sources
=
buildInfo
.
output
.
contracts
for
(
const
[
sourceName
,
source
]
of
Object
.
entries
(
sources
))
{
// The source file may have our contract
if
(
sourceName
.
includes
(
parsed
.
sourceName
))
{
const
contract
=
source
[
contractName
]
if
(
!
contract
)
{
console
.
log
(
`Skipping
${
contractName
}
as its not found in the source`
)
const
raw
=
fs
.
readFileSync
(
filePath
,
'
utf8
'
).
toString
()
const
artifact
=
JSON
.
parse
(
raw
)
// Handle contracts without storage
const
storageLayout
=
artifact
.
storageLayout
||
{}
if
(
storageLayout
.
storage
)
{
for
(
const
variable
of
storageLayout
.
storage
)
{
const
fqn
=
variable
.
contract
// Skip some abstract contracts
if
(
skipped
(
fqn
))
{
continue
}
const
storageLayout
=
(
contract
as
any
).
storageLayout
if
(
!
storageLayout
)
{
continue
const
contractName
=
parseFqn
(
fqn
)
// Check that the layout lock has not changed
const
lock
=
layoutLock
[
contractName
]
||
{}
if
(
lock
[
variable
.
label
])
{
const
variableInfo
=
parseVariableInfo
(
variable
)
const
expectedInfo
=
lock
[
variable
.
label
]
if
(
variableInfo
.
slot
!==
expectedInfo
.
slot
)
{
throw
new
Error
(
`
${
fqn
}
.
${
variable
.
label
}
slot has changed`
)
}
if
(
variableInfo
.
offset
!==
expectedInfo
.
offset
)
{
throw
new
Error
(
`
${
fqn
}
.
${
variable
.
label
}
offset has changed`
)
}
if
(
variableInfo
.
length
!==
expectedInfo
.
length
)
{
throw
new
Error
(
`
${
fqn
}
.
${
variable
.
label
}
length has changed`
)
}
}
if
(
layoutLock
[
contractName
])
{
console
.
log
(
`Processing layout lock for
${
contractName
}
`
)
const
removed
=
Object
.
entries
(
layoutLock
[
contractName
]).
filter
(
([
key
,
val
]:
any
)
=>
{
const
storage
=
storageLayout
?.
storage
||
[]
return
!
storage
.
some
((
item
:
any
)
=>
{
// Skip anything that doesn't clearly match the key because otherwise we'll get an
// error while parsing the variable info for unsupported variable types.
if
(
!
item
.
label
.
includes
(
key
))
{
return
false
}
// Make sure the variable matches **exactly**.
const
variableInfo
=
parseVariableInfo
(
item
)
return
(
variableInfo
.
name
===
key
&&
variableInfo
.
offset
===
val
.
offset
&&
variableInfo
.
slot
===
val
.
slot
&&
variableInfo
.
length
===
val
.
length
)
})
}
)
if
(
removed
.
length
>
0
)
{
// Check that the spacers are all named correctly
if
(
variable
.
label
.
startsWith
(
'
spacer_
'
))
{
const
[,
slot
,
offset
,
length
]
=
variable
.
label
.
split
(
'
_
'
)
const
variableInfo
=
parseVariableInfo
(
variable
)
// Check that the slot is correct.
if
(
parseInt
(
slot
,
10
)
!==
variableInfo
.
slot
)
{
throw
new
Error
(
`variable(s) removed from
${
contractName
}
:
${
removed
.
join
(
'
,
'
)}
`
`
${
fqn
}
${
variable
.
label
}
is in slot
${
variable
.
slot
}
but should be in
${
slot
}
`
)
}
// Check that the offset is correct.
if
(
parseInt
(
offset
,
10
)
!==
variableInfo
.
offset
)
{
throw
new
Error
(
`
${
fqn
}
${
variable
.
label
}
is at offset
${
variable
.
offset
}
but should be at
${
offset
}
`
)
}
console
.
log
(
`Valid layout lock for
${
contractName
}
`
)
accounted
.
push
(
contractName
)
}
for
(
const
variable
of
storageLayout
?.
storage
||
[])
{
if
(
variable
.
label
.
startsWith
(
'
spacer_
'
))
{
const
[,
slot
,
offset
,
length
]
=
variable
.
label
.
split
(
'
_
'
)
const
variableInfo
=
parseVariableInfo
(
variable
)
// Check that the slot is correct.
if
(
parseInt
(
slot
,
10
)
!==
variableInfo
.
slot
)
{
throw
new
Error
(
`
${
contractName
}
${
variable
.
label
}
is in slot
${
variable
.
slot
}
but should be in
${
slot
}
`
)
}
// Check that the offset is correct.
if
(
parseInt
(
offset
,
10
)
!==
variableInfo
.
offset
)
{
throw
new
Error
(
`
${
contractName
}
${
variable
.
label
}
is at offset
${
variable
.
offset
}
but should be at
${
offset
}
`
)
}
// Check that the length is correct.
if
(
parseInt
(
length
,
10
)
!==
variableInfo
.
length
)
{
throw
new
Error
(
`
${
contractName
}
${
variable
.
label
}
is
${
variableInfo
.
length
}
bytes long but should be
${
length
}
`
)
}
console
.
log
(
`
${
contractName
}
.
${
variable
.
label
}
is valid`
)
// Check that the length is correct.
if
(
parseInt
(
length
,
10
)
!==
variableInfo
.
length
)
{
throw
new
Error
(
`
${
fqn
}
${
variable
.
label
}
is
${
variableInfo
.
length
}
bytes long but should be
${
length
}
`
)
}
console
.
log
(
`
${
fqn
}
.
${
variable
.
label
}
is valid`
)
}
}
}
}
}
for
(
const
name
of
Object
.
keys
(
layoutLock
))
{
if
(
!
accounted
.
includes
(
name
))
{
throw
new
Error
(
`contract
${
name
}
is not accounted for`
)
}
}
})
main
()
packages/contracts-bedrock/tasks/deposits.ts
deleted
100644 → 0
View file @
1c00dcbc
/*
* Copyright (c) 2022, OP Labs PBC (MIT License)
* https://github.com/ethereum-optimism/optimism
*/
import
{
task
,
types
}
from
'
hardhat/config
'
import
{
providers
,
utils
,
Wallet
,
Event
}
from
'
ethers
'
import
dotenv
from
'
dotenv
'
import
'
hardhat-deploy
'
import
'
@nomiclabs/hardhat-ethers
'
import
{
DepositTx
}
from
'
@eth-optimism/core-utils
'
dotenv
.
config
()
const
sleep
=
async
(
ms
:
number
)
=>
{
return
new
Promise
((
resolve
)
=>
setTimeout
(
resolve
,
ms
))
}
task
(
'
deposit
'
,
'
Deposits funds onto L2.
'
)
.
addParam
(
'
l1ProviderUrl
'
,
'
L1 provider URL.
'
,
'
http://localhost:8545
'
,
types
.
string
)
.
addParam
(
'
l2ProviderUrl
'
,
'
L2 provider URL.
'
,
'
http://localhost:9545
'
,
types
.
string
)
.
addParam
(
'
to
'
,
'
Recipient address.
'
,
null
,
types
.
string
)
.
addParam
(
'
amountEth
'
,
'
Amount in ETH to send.
'
,
null
,
types
.
string
)
.
addOptionalParam
(
'
privateKey
'
,
'
Private key to send transaction
'
,
process
.
env
.
PRIVATE_KEY
,
types
.
string
)
.
setAction
(
async
(
args
,
hre
)
=>
{
const
{
l1ProviderUrl
,
l2ProviderUrl
,
to
,
amountEth
,
privateKey
}
=
args
const
proxy
=
await
hre
.
deployments
.
get
(
'
OptimismPortalProxy
'
)
const
OptimismPortal
=
await
hre
.
ethers
.
getContractAt
(
'
OptimismPortal
'
,
proxy
.
address
)
const
l1Provider
=
new
providers
.
JsonRpcProvider
(
l1ProviderUrl
)
const
l2Provider
=
new
providers
.
JsonRpcProvider
(
l2ProviderUrl
)
let
l1Wallet
:
Wallet
|
providers
.
JsonRpcSigner
if
(
privateKey
)
{
l1Wallet
=
new
Wallet
(
privateKey
,
l1Provider
)
}
else
{
l1Wallet
=
l1Provider
.
getSigner
()
}
const
from
=
await
l1Wallet
.
getAddress
()
console
.
log
(
`Sending from
${
from
}
`
)
const
balance
=
await
l1Wallet
.
getBalance
()
if
(
balance
.
eq
(
0
))
{
throw
new
Error
(
`
${
from
}
has no balance`
)
}
const
amountWei
=
utils
.
parseEther
(
amountEth
)
const
value
=
amountWei
.
add
(
utils
.
parseEther
(
'
0.01
'
))
console
.
log
(
`Depositing
${
amountEth
}
ETH to
${
to
}
`
)
const
preL2Balance
=
await
l2Provider
.
getBalance
(
to
)
console
.
log
(
`
${
to
}
has
${
utils
.
formatEther
(
preL2Balance
)}
ETH on L2`
)
// Below adds 0.01 ETH to account for gas.
const
tx
=
await
OptimismPortal
.
depositTransaction
(
to
,
amountWei
,
'
3000000
'
,
false
,
[],
{
value
}
)
console
.
log
(
`Got TX hash
${
tx
.
hash
}
. Waiting...`
)
const
receipt
=
await
tx
.
wait
()
console
.
log
(
`Included in block
${
receipt
.
blockHash
}
`
)
// find the transaction deposited event and derive
// the deposit transaction from it
const
event
=
receipt
.
events
.
find
(
(
e
:
Event
)
=>
e
.
event
===
'
TransactionDeposited
'
)
console
.
log
(
`Deposit has log index
${
event
.
logIndex
}
`
)
const
l2tx
=
DepositTx
.
fromL1Event
(
event
)
const
hash
=
l2tx
.
hash
()
console
.
log
(
`Waiting for L2 TX hash
${
hash
}
`
)
let
i
=
0
while
(
true
)
{
const
expected
=
await
l2Provider
.
send
(
'
eth_getTransactionByHash
'
,
[
hash
])
if
(
expected
)
{
console
.
log
(
'
Deposit success
'
)
console
.
log
(
JSON
.
stringify
(
expected
,
null
,
2
))
console
.
log
(
'
Receipt:
'
)
const
l2Receipt
=
await
l2Provider
.
getTransactionReceipt
(
hash
)
console
.
log
(
JSON
.
stringify
(
l2Receipt
,
null
,
2
))
break
}
if
(
i
%
100
===
0
)
{
const
postL2Balance
=
await
l2Provider
.
getBalance
(
to
)
if
(
postL2Balance
.
gt
(
preL2Balance
))
{
console
.
log
(
`Unexpected balance increase without detecting deposit transaction`
)
}
const
block
=
await
l2Provider
.
getBlock
(
'
latest
'
)
console
.
log
(
`latest block
${
block
.
number
}
:
${
block
.
hash
}
`
)
}
await
sleep
(
500
)
i
++
}
})
packages/contracts-bedrock/tasks/index.ts
View file @
19468e49
import
'
./deposits
'
import
'
./validate-spacers
'
import
'
./solidity
'
import
'
./check-l2
'
import
'
./generate-deploy-config
'
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment