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
85d8c86e
Unverified
Commit
85d8c86e
authored
Aug 22, 2024
by
protolambda
Committed by
GitHub
Aug 23, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
op-chain-ops: forge script fixes and improvements (#11577)
parent
36279371
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
524 additions
and
186 deletions
+524
-186
go.mod
go.mod
+1
-1
go.sum
go.sum
+2
-2
bindings.go
op-chain-ops/script/bindings.go
+2
-1
cheatcodes_environment.go
op-chain-ops/script/cheatcodes_environment.go
+15
-2
cheatcodes_external.go
op-chain-ops/script/cheatcodes_external.go
+4
-3
cheatcodes_state.go
op-chain-ops/script/cheatcodes_state.go
+2
-20
cheatcodes_utilities.go
op-chain-ops/script/cheatcodes_utilities.go
+1
-1
context.go
op-chain-ops/script/context.go
+20
-18
prank.go
op-chain-ops/script/prank.go
+150
-0
precompile.go
op-chain-ops/script/precompile.go
+34
-4
script.go
op-chain-ops/script/script.go
+215
-132
script_test.go
op-chain-ops/script/script_test.go
+2
-2
with.go
op-chain-ops/script/with.go
+76
-0
No files found.
go.mod
View file @
85d8c86e
...
...
@@ -234,7 +234,7 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect
)
replace github.com/ethereum/go-ethereum v1.14.8 => github.com/ethereum-optimism/op-geth v1.101408.0-rc.4
replace github.com/ethereum/go-ethereum v1.14.8 => github.com/ethereum-optimism/op-geth v1.101408.0-rc.4
.0.20240822213944-6c8de76e0720
// replace github.com/ethereum/go-ethereum => ../op-geth
...
...
go.sum
View file @
85d8c86e
...
...
@@ -176,8 +176,8 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/
github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.101408.0-rc.4
h1:OhiSpP+IOoKe+9chfHYjQFFwGruLT9Uh52+LFk4y6ms
=
github.com/ethereum-optimism/op-geth v1.101408.0-rc.4/go.mod h1:Mk8AhvlqFbjI9oW2ymThSSoqc6kiEH0/tCmHGMEu6ac=
github.com/ethereum-optimism/op-geth v1.101408.0-rc.4
.0.20240822213944-6c8de76e0720 h1:PlMldvODGzwEBLRpK/mVUdrVa9LEN1cC0j5nKk5q7Jg
=
github.com/ethereum-optimism/op-geth v1.101408.0-rc.4
.0.20240822213944-6c8de76e0720
/go.mod h1:Mk8AhvlqFbjI9oW2ymThSSoqc6kiEH0/tCmHGMEu6ac=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240821192748-42bd03ba8313 h1:SVSFg8ccdRBJxOdRS1pK8oIHvMufiPAQz1gkQsEPnZc=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240821192748-42bd03ba8313/go.mod h1:XaVXL9jg8BcyOeugECgIUGa9Y3DjYJj71RHmb5qon6M=
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
...
...
op-chain-ops/script/bindings.go
View file @
85d8c86e
...
...
@@ -153,7 +153,8 @@ func hydrateBindingsField(
// Decodes the result of the backend into values to return as function, including error/revert handling.
outDecodeFn
:=
func
(
result
[]
byte
,
resultErr
error
)
[]
reflect
.
Value
{
if
resultErr
!=
nil
{
if
errors
.
Is
(
resultErr
,
vm
.
ErrExecutionReverted
)
{
// Empty return-data might happen on a regular description-less revert. No need to unpack in that case.
if
len
(
result
)
>
0
&&
errors
.
Is
(
resultErr
,
vm
.
ErrExecutionReverted
)
{
msg
,
err
:=
abi
.
UnpackRevert
(
result
)
if
err
!=
nil
{
return
returnErr
(
fmt
.
Errorf
(
"failed to unpack result args: %w"
,
err
))
...
...
op-chain-ops/script/cheatcodes_environment.go
View file @
85d8c86e
package
script
import
(
"bytes"
"math/big"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
)
...
...
@@ -64,7 +66,7 @@ func (c *CheatCodesPrecompile) Load(account common.Address, slot [32]byte) [32]b
// Etch implements https://book.getfoundry.sh/cheatcodes/etch
func
(
c
*
CheatCodesPrecompile
)
Etch
(
who
common
.
Address
,
code
[]
byte
)
{
c
.
h
.
state
.
SetCode
(
who
,
code
)
c
.
h
.
state
.
SetCode
(
who
,
bytes
.
Clone
(
code
))
// important to clone; geth EVM will reuse the calldata memory.
}
// Deal implements https://book.getfoundry.sh/cheatcodes/deal
...
...
@@ -138,6 +140,17 @@ func (c *CheatCodesPrecompile) GetNonce(addr common.Address) uint64 {
return
c
.
h
.
state
.
GetNonce
(
addr
)
}
func
(
c
*
CheatCodesPrecompile
)
ResetNonce
(
addr
common
.
Address
)
{
// Undocumented cheatcode of forge, but used a lot.
// Resets nonce to 0 if EOA, or 1 if contract.
// In scripts often set code to empty first when using it, it then becomes 0.
if
c
.
h
.
state
.
GetCodeHash
(
addr
)
==
types
.
EmptyCodeHash
{
c
.
h
.
state
.
SetNonce
(
addr
,
0
)
}
else
{
c
.
h
.
state
.
SetNonce
(
addr
,
1
)
}
}
// MockCall_b96213e4 implements https://book.getfoundry.sh/cheatcodes/mock-call
func
(
c
*
CheatCodesPrecompile
)
MockCall_b96213e4
(
where
common
.
Address
,
data
[]
byte
,
retdata
[]
byte
)
error
{
panic
(
"mockCall not supported"
)
...
...
@@ -189,7 +202,7 @@ func (c *CheatCodesPrecompile) StartBroadcast_7fb5297f() error {
// StartBroadcast_7fec2a8d implements https://book.getfoundry.sh/cheatcodes/start-broadcast
func
(
c
*
CheatCodesPrecompile
)
StartBroadcast_7fec2a8d
(
who
common
.
Address
)
error
{
c
.
h
.
log
.
Info
(
"starting repeat-broadcast"
,
"who"
,
who
)
return
c
.
h
.
Prank
(
nil
,
nil
,
true
,
true
)
return
c
.
h
.
Prank
(
&
who
,
nil
,
true
,
true
)
}
// StopBroadcast implements https://book.getfoundry.sh/cheatcodes/stop-broadcast
...
...
op-chain-ops/script/cheatcodes_external.go
View file @
85d8c86e
package
script
import
(
"bytes"
"encoding/json"
"errors"
"fmt"
...
...
@@ -36,7 +37,7 @@ func (c *CheatCodesPrecompile) ProjectRoot() string {
func
(
c
*
CheatCodesPrecompile
)
getArtifact
(
input
string
)
(
*
foundry
.
Artifact
,
error
)
{
// fetching by relative file path, or using a contract version, is not supported
parts
:=
strings
.
SplitN
(
input
,
":"
,
1
)
parts
:=
strings
.
SplitN
(
input
,
":"
,
2
)
name
:=
parts
[
0
]
+
".sol"
contract
:=
parts
[
0
]
if
len
(
parts
)
==
2
{
...
...
@@ -52,7 +53,7 @@ func (c *CheatCodesPrecompile) GetCode(input string) ([]byte, error) {
if
err
!=
nil
{
return
nil
,
err
}
return
artifact
.
Bytecode
.
Object
,
nil
return
bytes
.
Clone
(
artifact
.
Bytecode
.
Object
)
,
nil
}
// GetDeployedCode implements https://book.getfoundry.sh/cheatcodes/get-deployed-code
...
...
@@ -61,7 +62,7 @@ func (c *CheatCodesPrecompile) GetDeployedCode(input string) ([]byte, error) {
if
err
!=
nil
{
return
nil
,
err
}
return
artifact
.
DeployedBytecode
.
Object
,
nil
return
bytes
.
Clone
(
artifact
.
DeployedBytecode
.
Object
)
,
nil
}
// Sleep implements https://book.getfoundry.sh/cheatcodes/sleep
...
...
op-chain-ops/script/cheatcodes_state.go
View file @
85d8c86e
...
...
@@ -2,11 +2,6 @@ package script
import
(
"errors"
"fmt"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
)
func
(
c
*
CheatCodesPrecompile
)
LoadAllocs
(
pathToAllocsJson
string
)
error
{
...
...
@@ -17,23 +12,10 @@ func (c *CheatCodesPrecompile) LoadAllocs(pathToAllocsJson string) error {
func
(
c
*
CheatCodesPrecompile
)
DumpState
(
pathToStateJson
string
)
error
{
c
.
h
.
log
.
Info
(
"dumping state"
,
"target"
,
pathToStateJson
)
// We have to commit the existing state to the trie,
// for all the state-changes to be captured by the trie iterator.
root
,
err
:=
c
.
h
.
state
.
Commit
(
c
.
h
.
env
.
Context
.
BlockNumber
.
Uint64
(),
true
)
allocs
,
err
:=
c
.
h
.
StateDump
()
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to commit state: %w"
,
err
)
return
err
}
// We need a state object around the state DB
st
,
err
:=
state
.
New
(
root
,
c
.
h
.
stateDB
,
nil
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to create state object for state-dumping: %w"
,
err
)
}
// After Commit we cannot reuse the old State, so we update the host to use the new one
c
.
h
.
state
=
st
c
.
h
.
env
.
StateDB
=
st
var
allocs
foundry
.
ForgeAllocs
allocs
.
FromState
(
st
)
// This may be written somewhere in the future (or run some callback to collect the state dump)
_
=
allocs
c
.
h
.
log
.
Info
(
"state-dumping is not supported, but have state"
,
...
...
op-chain-ops/script/cheatcodes_utilities.go
View file @
85d8c86e
...
...
@@ -37,7 +37,7 @@ func (c *CheatCodesPrecompile) Skip() error {
// Label implements https://book.getfoundry.sh/cheatcodes/label
func
(
c
*
CheatCodesPrecompile
)
Label
(
addr
common
.
Address
,
label
string
)
{
c
.
h
.
labels
[
addr
]
=
label
c
.
h
.
Label
(
addr
,
label
)
}
// GetLabel implements https://book.getfoundry.sh/cheatcodes/get-label
...
...
op-chain-ops/script/context.go
View file @
85d8c86e
...
...
@@ -17,6 +17,8 @@ var (
// ConsoleAddr is known as CONSOLE, "console.log" in ascii.
// Utils like console.sol and console2.sol work by executing a staticcall to this address.
ConsoleAddr
=
common
.
HexToAddress
(
"0x000000000000000000636F6e736F6c652e6c6f67"
)
// ScriptDeployer is used for temporary scripts address(uint160(uint256(keccak256("op-stack script deployer"))))
ScriptDeployer
=
common
.
HexToAddress
(
"0x76Ce131128F3616871f8CDA86d18fAB44E4d0D8B"
)
)
const
(
...
...
@@ -25,25 +27,25 @@ const (
)
type
Context
struct
{
c
hainID
*
big
.
Int
s
ender
common
.
Address
o
rigin
common
.
Address
f
eeRecipient
common
.
Address
g
asLimit
uint64
b
lockNum
uint64
t
imestamp
uint64
p
revRandao
common
.
Hash
b
lobHashes
[]
common
.
Hash
C
hainID
*
big
.
Int
S
ender
common
.
Address
O
rigin
common
.
Address
F
eeRecipient
common
.
Address
G
asLimit
uint64
B
lockNum
uint64
T
imestamp
uint64
P
revRandao
common
.
Hash
B
lobHashes
[]
common
.
Hash
}
var
DefaultContext
=
Context
{
c
hainID
:
big
.
NewInt
(
1337
),
s
ender
:
DefaultSenderAddr
,
o
rigin
:
DefaultSenderAddr
,
f
eeRecipient
:
common
.
Address
{},
g
asLimit
:
DefaultFoundryGasLimit
,
b
lockNum
:
0
,
t
imestamp
:
0
,
p
revRandao
:
common
.
Hash
{},
b
lobHashes
:
[]
common
.
Hash
{},
C
hainID
:
big
.
NewInt
(
1337
),
S
ender
:
DefaultSenderAddr
,
O
rigin
:
DefaultSenderAddr
,
F
eeRecipient
:
common
.
Address
{},
G
asLimit
:
DefaultFoundryGasLimit
,
B
lockNum
:
0
,
T
imestamp
:
0
,
P
revRandao
:
common
.
Hash
{},
B
lobHashes
:
[]
common
.
Hash
{},
}
op-chain-ops/script/prank.go
0 → 100644
View file @
85d8c86e
package
script
import
(
"errors"
"math/big"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
)
// Prank represents an active prank task for the next sub-call.
// This is embedded into a call-frame, to then influence the sub-call through a caller-override.
type
Prank
struct
{
// Sender overrides msg.sender
Sender
*
common
.
Address
// Origin overrides tx.origin (set to actual origin if not part of the prank)
Origin
*
common
.
Address
// PrevOrigin is the tx.origin to restore after the prank
PrevOrigin
common
.
Address
// Repeat is true if the prank persists after returning from a sub-call
Repeat
bool
// A Prank may be a broadcast also.
Broadcast
bool
}
// prankRef implements the vm.ContractRef interface, to mock a caller.
type
prankRef
struct
{
prank
common
.
Address
ref
vm
.
ContractRef
}
var
_
vm
.
ContractRef
=
(
*
prankRef
)(
nil
)
func
(
p
*
prankRef
)
Address
()
common
.
Address
{
return
p
.
prank
}
// Value returns the value send into this contract context.
// The delegate call tracer implicitly relies on this being implemented on ContractRef
func
(
p
*
prankRef
)
Value
()
*
uint256
.
Int
{
return
p
.
ref
.
(
interface
{
Value
()
*
uint256
.
Int
})
.
Value
()
}
func
(
h
*
Host
)
handleCaller
(
caller
vm
.
ContractRef
)
vm
.
ContractRef
{
// apply prank, if top call-frame had set up a prank
if
len
(
h
.
callStack
)
>
0
{
parentCallFrame
:=
h
.
callStack
[
len
(
h
.
callStack
)
-
1
]
if
parentCallFrame
.
Prank
!=
nil
&&
caller
.
Address
()
!=
VMAddr
{
// pranks do not apply to the cheatcode precompile
if
parentCallFrame
.
Prank
.
Sender
!=
nil
{
return
&
prankRef
{
prank
:
*
parentCallFrame
.
Prank
.
Sender
,
ref
:
caller
,
}
}
if
parentCallFrame
.
Prank
.
Origin
!=
nil
{
h
.
env
.
TxContext
.
Origin
=
*
parentCallFrame
.
Prank
.
Origin
}
}
}
return
caller
}
// Prank applies a prank to the current call-frame.
// Any sub-call will apply the prank to their frame context.
func
(
h
*
Host
)
Prank
(
msgSender
*
common
.
Address
,
txOrigin
*
common
.
Address
,
repeat
bool
,
broadcast
bool
)
error
{
if
len
(
h
.
callStack
)
==
0
{
h
.
log
.
Warn
(
"no call stack"
)
return
nil
// cannot prank while not in a call.
}
cf
:=
&
h
.
callStack
[
len
(
h
.
callStack
)
-
1
]
if
cf
.
Prank
!=
nil
{
if
cf
.
Prank
.
Broadcast
&&
!
broadcast
{
return
errors
.
New
(
"you have an active broadcast; broadcasting and pranks are not compatible"
)
}
if
!
cf
.
Prank
.
Broadcast
&&
broadcast
{
return
errors
.
New
(
"you have an active prank; broadcasting and pranks are not compatible"
)
}
}
h
.
log
.
Warn
(
"prank"
,
"sender"
,
msgSender
)
cf
.
Prank
=
&
Prank
{
Sender
:
msgSender
,
Origin
:
txOrigin
,
PrevOrigin
:
h
.
env
.
TxContext
.
Origin
,
Repeat
:
repeat
,
Broadcast
:
broadcast
,
}
return
nil
}
// StopPrank disables the current prank. Any sub-call will not be pranked.
func
(
h
*
Host
)
StopPrank
(
broadcast
bool
)
error
{
if
len
(
h
.
callStack
)
==
0
{
return
nil
}
cf
:=
&
h
.
callStack
[
len
(
h
.
callStack
)
-
1
]
if
cf
.
Prank
==
nil
{
if
broadcast
{
return
errors
.
New
(
"no broadcast in progress to stop"
)
}
return
nil
}
if
cf
.
Prank
.
Broadcast
&&
!
broadcast
{
// stopPrank on active broadcast is silent and no-op
return
nil
}
if
!
cf
.
Prank
.
Broadcast
&&
broadcast
{
return
errors
.
New
(
"no broadcast in progress to stop"
)
}
cf
.
Prank
=
nil
return
nil
}
// CallerMode returns the type of the top-most callframe,
// i.e. if we are in regular operation, a prank, or a broadcast (special kind of prank).
func
(
h
*
Host
)
CallerMode
()
CallerMode
{
if
len
(
h
.
callStack
)
==
0
{
return
CallerModeNone
}
cf
:=
&
h
.
callStack
[
len
(
h
.
callStack
)
-
1
]
if
cf
.
Prank
!=
nil
{
if
cf
.
Prank
.
Broadcast
{
if
cf
.
Prank
.
Repeat
{
return
CallerModeRecurrentBroadcast
}
return
CallerModeBroadcast
}
if
cf
.
Prank
.
Repeat
{
return
CallerModeRecurrentPrank
}
return
CallerModePrank
}
return
CallerModeNone
}
// CallerMode matches the CallerMode forge cheatcode enum.
type
CallerMode
uint8
func
(
cm
CallerMode
)
Big
()
*
big
.
Int
{
return
big
.
NewInt
(
int64
(
cm
))
}
const
(
CallerModeNone
CallerMode
=
iota
CallerModeBroadcast
CallerModeRecurrentBroadcast
CallerModePrank
CallerModeRecurrentPrank
)
op-chain-ops/script/precompile.go
View file @
85d8c86e
...
...
@@ -10,6 +10,7 @@ import (
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
)
...
...
@@ -55,22 +56,37 @@ func rightPad32(data []byte) []byte {
type
Precompile
[
E
any
]
struct
{
Precompile
E
fieldsOnly
bool
// abiMethods is effectively the jump-table for 4-byte ABI calls to the precompile.
abiMethods
map
[[
4
]
byte
]
*
precompileFunc
}
var
_
vm
.
PrecompiledContract
=
(
*
Precompile
[
struct
{}])(
nil
)
type
PrecompileOption
[
E
any
]
func
(
p
*
Precompile
[
E
])
func
WithFieldsOnly
[
E
any
](
p
*
Precompile
[
E
])
{
p
.
fieldsOnly
=
true
}
// NewPrecompile wraps a Go object into a Precompile.
// All exported fields and methods will have a corresponding ABI interface.
// Fields with a tag `evm:"-"` will be ignored.
// Fields with a tag `evm:"-"` will be ignored
, or can override their ABI name to x with this tag: `evm:"x"`
.
// Field names and method names are adjusted to start with a lowercase character in the ABI signature.
// Method names may end with a `_X` where X must be the 4byte selector (this is sanity-checked),
// to support multiple variants of the same method with different ABI input parameters.
// Methods may return an error, which will result in a revert, rather than become an ABI encoded arg, if not nil.
// All precompile methods have 0 gas cost.
func
NewPrecompile
[
E
any
](
e
E
)
(
*
Precompile
[
E
],
error
)
{
out
:=
&
Precompile
[
E
]{
Precompile
:
e
,
abiMethods
:
make
(
map
[[
4
]
byte
]
*
precompileFunc
)}
func
NewPrecompile
[
E
any
](
e
E
,
opts
...
PrecompileOption
[
E
])
(
*
Precompile
[
E
],
error
)
{
out
:=
&
Precompile
[
E
]{
Precompile
:
e
,
abiMethods
:
make
(
map
[[
4
]
byte
]
*
precompileFunc
),
fieldsOnly
:
false
,
}
for
_
,
opt
:=
range
opts
{
opt
(
out
)
}
elemVal
:=
reflect
.
ValueOf
(
e
)
// setup methods (and if pointer, the indirect methods also)
if
err
:=
out
.
setupMethods
(
&
elemVal
);
err
!=
nil
{
...
...
@@ -85,6 +101,9 @@ func NewPrecompile[E any](e E) (*Precompile[E], error) {
// setupMethods iterates through all exposed methods of val, and sets them all up as ABI methods.
func
(
p
*
Precompile
[
E
])
setupMethods
(
val
*
reflect
.
Value
)
error
{
if
p
.
fieldsOnly
{
return
nil
}
typ
:=
val
.
Type
()
methodCount
:=
val
.
NumMethod
()
for
i
:=
0
;
i
<
methodCount
;
i
++
{
...
...
@@ -431,6 +450,10 @@ func (p *Precompile[E]) setupStructField(fieldDef *reflect.StructField, fieldVal
if
lo
:=
strings
.
ToLower
(
abiFunctionName
[
:
1
]);
lo
!=
abiFunctionName
[
:
1
]
{
abiFunctionName
=
lo
+
abiFunctionName
[
1
:
]
}
// The tag can override the field name
if
v
,
ok
:=
fieldDef
.
Tag
.
Lookup
(
"evm"
);
ok
{
abiFunctionName
=
v
}
// The ABI signature of public fields in solidity is simply a getter function of the same name.
// The return type is not part of the ABI signature. So we just append "()" to turn it into a function.
methodSig
:=
abiFunctionName
+
"()"
...
...
@@ -455,7 +478,14 @@ func (p *Precompile[E]) setupStructField(fieldDef *reflect.StructField, fieldVal
if
len
(
input
)
!=
0
{
// 4 byte selector is already trimmed
return
nil
,
fmt
.
Errorf
(
"unexpected input: %x"
,
input
)
}
outData
,
err
:=
outArgs
.
PackValues
([]
any
{
fieldVal
.
Interface
()})
v
:=
fieldVal
.
Interface
()
if
abiVal
,
ok
:=
v
.
(
interface
{
ToABI
()
[]
byte
});
ok
{
return
abiVal
.
ToABI
(),
nil
}
if
bigInt
,
ok
:=
v
.
(
*
hexutil
.
Big
);
ok
{
// We can change this to use convertType later, if we need more generic type handling.
v
=
(
*
big
.
Int
)(
bigInt
)
}
outData
,
err
:=
outArgs
.
PackValues
([]
any
{
v
})
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"method %s failed to pack return data: %w"
,
methodSig
,
err
)
}
...
...
op-chain-ops/script/script.go
View file @
85d8c86e
package
script
import
(
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math/big"
...
...
@@ -26,26 +26,21 @@ import (
"github.com/ethereum/go-ethereum/triedb/hashdb"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/srcmap"
)
// Prank represents an active prank task for the next sub-call.
type
Prank
struct
{
// Sender overrides msg.sender
Sender
*
common
.
Address
// Origin overrides tx.origin (set to actual origin if not part of the prank)
Origin
*
common
.
Address
// PrevOrigin is the tx.origin to restore after the prank
PrevOrigin
common
.
Address
// Repeat is true if the prank persists after returning from a sub-call
Repeat
bool
// A Prank may be a broadcast also.
Broadcast
bool
}
// CallFrame encodes the scope context of the current call
type
CallFrame
struct
{
Depth
int
Opener
vm
.
OpCode
LastOp
vm
.
OpCode
LastPC
uint64
// Reverts often happen in generated code.
// We want to fallback to logging the source-map position of
// the non-generated code, i.e. the origin of the last successful jump.
LastJumpPC
uint64
Ctx
*
vm
.
ScopeContext
// Prank overrides the msg.sender, and optionally the origin.
...
...
@@ -67,6 +62,8 @@ type Host struct {
cheatcodes
*
Precompile
[
*
CheatCodesPrecompile
]
console
*
Precompile
[
*
ConsolePrecompile
]
precompiles
map
[
common
.
Address
]
vm
.
PrecompiledContract
callStack
[]
CallFrame
// serializerStates are in-progress JSON payloads by name,
...
...
@@ -76,22 +73,32 @@ type Host struct {
envVars
map
[
string
]
string
labels
map
[
common
.
Address
]
string
// srcFS enables src-map loading;
// this is a bit more expensive, but provides useful debug information.
// src-maps are disabled if this is nil.
srcFS
*
foundry
.
SourceMapFS
srcMaps
map
[
common
.
Address
]
*
srcmap
.
SourceMap
}
// NewHost creates a Host that can load contracts from the given Artifacts FS,
// and with an EVM initialized to the given executionContext.
func
NewHost
(
logger
log
.
Logger
,
fs
*
foundry
.
ArtifactsFS
,
executionContext
Context
)
*
Host
{
// Optionally src-map loading may be enabled, by providing a non-nil srcFS to read sources from.
func
NewHost
(
logger
log
.
Logger
,
fs
*
foundry
.
ArtifactsFS
,
srcFS
*
foundry
.
SourceMapFS
,
executionContext
Context
)
*
Host
{
h
:=
&
Host
{
log
:
logger
,
af
:
fs
,
serializerStates
:
make
(
map
[
string
]
json
.
RawMessage
),
envVars
:
make
(
map
[
string
]
string
),
labels
:
make
(
map
[
common
.
Address
]
string
),
precompiles
:
make
(
map
[
common
.
Address
]
vm
.
PrecompiledContract
),
srcFS
:
srcFS
,
srcMaps
:
make
(
map
[
common
.
Address
]
*
srcmap
.
SourceMap
),
}
// Init a default chain config, with all the mainnet L1 forks activated
h
.
chainCfg
=
&
params
.
ChainConfig
{
ChainID
:
executionContext
.
c
hainID
,
ChainID
:
executionContext
.
C
hainID
,
// Ethereum forks in proof-of-work era.
HomesteadBlock
:
big
.
NewInt
(
0
),
EIP150Block
:
big
.
NewInt
(
0
),
...
...
@@ -149,22 +156,22 @@ func NewHost(logger log.Logger, fs *foundry.ArtifactsFS, executionContext Contex
return
crypto
.
Keccak256Hash
(
out
[
:
])
},
L1CostFunc
:
nil
,
Coinbase
:
executionContext
.
f
eeRecipient
,
GasLimit
:
executionContext
.
g
asLimit
,
BlockNumber
:
new
(
big
.
Int
)
.
SetUint64
(
executionContext
.
b
lockNum
),
Time
:
executionContext
.
t
imestamp
,
Coinbase
:
executionContext
.
F
eeRecipient
,
GasLimit
:
executionContext
.
G
asLimit
,
BlockNumber
:
new
(
big
.
Int
)
.
SetUint64
(
executionContext
.
B
lockNum
),
Time
:
executionContext
.
T
imestamp
,
Difficulty
:
nil
,
// not used anymore post-merge
BaseFee
:
big
.
NewInt
(
0
),
BlobBaseFee
:
big
.
NewInt
(
0
),
Random
:
&
executionContext
.
p
revRandao
,
Random
:
&
executionContext
.
P
revRandao
,
}
// Initialize a transaction-context for the EVM to access environment variables.
// The transaction context (after embedding inside of the EVM environment) may be mutated later.
txContext
:=
vm
.
TxContext
{
Origin
:
executionContext
.
o
rigin
,
Origin
:
executionContext
.
O
rigin
,
GasPrice
:
big
.
NewInt
(
0
),
BlobHashes
:
executionContext
.
b
lobHashes
,
BlobHashes
:
executionContext
.
B
lobHashes
,
BlobFeeCap
:
big
.
NewInt
(
0
),
AccessEvents
:
state
.
NewAccessEvents
(
h
.
stateDB
.
PointCache
()),
}
...
...
@@ -183,6 +190,7 @@ func NewHost(logger log.Logger, fs *foundry.ArtifactsFS, executionContext Contex
NoBaseFee
:
true
,
Tracer
:
trHooks
,
PrecompileOverrides
:
h
.
getPrecompile
,
CallerOverride
:
h
.
handleCaller
,
}
h
.
env
=
vm
.
NewEVM
(
blockContext
,
txContext
,
h
.
state
,
h
.
chainCfg
,
vmCfg
)
...
...
@@ -201,6 +209,7 @@ func (h *Host) EnableCheats() error {
// We need to insert some placeholder code to prevent it from aborting calls.
// Emulates Forge script: https://github.com/foundry-rs/foundry/blob/224fe9cbf76084c176dabf7d3b2edab5df1ab818/crates/evm/evm/src/executors/mod.rs#L108
h
.
state
.
SetCode
(
VMAddr
,
[]
byte
{
0x00
})
h
.
precompiles
[
VMAddr
]
=
h
.
cheatcodes
consolePrecompile
,
err
:=
NewPrecompile
[
*
ConsolePrecompile
](
&
ConsolePrecompile
{
logger
:
h
.
log
,
...
...
@@ -210,6 +219,7 @@ func (h *Host) EnableCheats() error {
return
fmt
.
Errorf
(
"failed to init console precompile: %w"
,
err
)
}
h
.
console
=
consolePrecompile
h
.
precompiles
[
ConsoleAddr
]
=
h
.
console
// The Console precompile does not need bytecode,
// calls all go through a console lib, which avoids the EXTCODESIZE.
return
nil
...
...
@@ -234,44 +244,106 @@ func (h *Host) LoadContract(artifactName, contractName string) (common.Address,
if
err
!=
nil
{
return
common
.
Address
{},
fmt
.
Errorf
(
"failed to load %s / %s: %w"
,
artifactName
,
contractName
,
err
)
}
h
.
prelude
(
h
.
env
.
TxContext
.
Origin
,
nil
)
ret
,
addr
,
_
,
err
:=
h
.
env
.
Create
(
vm
.
AccountRef
(
h
.
env
.
TxContext
.
Origin
),
artifact
.
Bytecode
.
Object
,
DefaultFoundryGasLimit
,
uint256
.
NewInt
(
0
))
addr
,
err
:=
h
.
Create
(
h
.
TxOrigin
(),
artifact
.
Bytecode
.
Object
)
if
err
!=
nil
{
return
common
.
Address
{},
err
}
h
.
RememberArtifact
(
addr
,
artifact
,
contractName
)
return
addr
,
nil
}
// RememberArtifact registers an address as originating from a particular artifact.
// This register a source-map, if the Host is configured with a source-map FS.
func
(
h
*
Host
)
RememberArtifact
(
addr
common
.
Address
,
artifact
*
foundry
.
Artifact
,
contract
string
)
{
if
h
.
srcFS
==
nil
{
return
}
code
:=
h
.
state
.
GetCode
(
addr
)
if
!
bytes
.
Equal
(
code
,
artifact
.
DeployedBytecode
.
Object
)
{
h
.
log
.
Warn
(
"src map warning: state bytecode does not match artifact deployed bytecode"
,
"addr"
,
addr
)
}
srcMap
,
err
:=
h
.
srcFS
.
SourceMap
(
artifact
,
contract
)
if
err
!=
nil
{
h
.
log
.
Warn
(
"failed to load srcmap"
,
"addr"
,
addr
,
"err"
,
err
)
return
}
h
.
srcMaps
[
addr
]
=
srcMap
}
// Create a contract with unlimited gas, and 0 ETH value.
// This create function helps deploy contracts quickly for scripting etc.
func
(
h
*
Host
)
Create
(
from
common
.
Address
,
initCode
[]
byte
)
(
common
.
Address
,
error
)
{
h
.
prelude
(
from
,
nil
)
ret
,
addr
,
_
,
err
:=
h
.
env
.
Create
(
vm
.
AccountRef
(
from
),
initCode
,
DefaultFoundryGasLimit
,
uint256
.
NewInt
(
0
))
if
err
!=
nil
{
return
common
.
Address
{},
fmt
.
Errorf
(
"failed to create contract, return: %x, err: %w"
,
ret
,
err
)
retStr
:=
fmt
.
Sprintf
(
"%x"
,
ret
)
if
len
(
retStr
)
>
20
{
retStr
=
retStr
[
:
20
]
+
"..."
}
return
common
.
Address
{},
fmt
.
Errorf
(
"failed to create contract, return: %s, err: %w"
,
retStr
,
err
)
}
return
addr
,
nil
}
// Wipe an account: removing the code, and setting address and balance to 0. This makes the account "empty".
// Note that storage is not removed.
func
(
h
*
Host
)
Wipe
(
addr
common
.
Address
)
{
if
h
.
state
.
GetCodeSize
(
addr
)
>
0
{
h
.
state
.
SetCode
(
addr
,
nil
)
}
h
.
state
.
SetNonce
(
addr
,
0
)
h
.
state
.
SetBalance
(
addr
,
uint256
.
NewInt
(
0
),
tracing
.
BalanceChangeUnspecified
)
}
// getPrecompile overrides any accounts during runtime, to insert special precompiles, if activated.
func
(
h
*
Host
)
getPrecompile
(
rules
params
.
Rules
,
original
vm
.
PrecompiledContract
,
addr
common
.
Address
)
vm
.
PrecompiledContract
{
switch
addr
{
case
VMAddr
:
return
h
.
cheatcodes
// nil if cheats are not enabled
case
ConsoleAddr
:
return
h
.
console
// nil if cheats are not enabled
default
:
if
p
,
ok
:=
h
.
precompiles
[
addr
];
ok
{
return
p
}
return
original
}
// SetPrecompile inserts a precompile at the given address.
// If the precompile is nil, it removes the precompile override from that address, and wipes the account.
func
(
h
*
Host
)
SetPrecompile
(
addr
common
.
Address
,
precompile
vm
.
PrecompiledContract
)
{
if
precompile
==
nil
{
h
.
log
.
Debug
(
"removing precompile"
,
"addr"
,
addr
)
delete
(
h
.
precompiles
,
addr
)
h
.
Wipe
(
addr
)
return
}
h
.
log
.
Debug
(
"adding precompile"
,
"addr"
,
addr
)
h
.
precompiles
[
addr
]
=
precompile
// insert non-empty placeholder bytecode, so EXTCODESIZE checks pass
h
.
state
.
SetCode
(
addr
,
[]
byte
{
0
})
}
// HasPrecompileOverride inspects if there exists an active precompile-override at the given address.
func
(
h
*
Host
)
HasPrecompileOverride
(
addr
common
.
Address
)
bool
{
_
,
ok
:=
h
.
precompiles
[
addr
]
return
ok
}
// onExit is a trace-hook, which we use to maintain an accurate view of functions, and log any revert warnings.
func
(
h
*
Host
)
onExit
(
depth
int
,
output
[]
byte
,
gasUsed
uint64
,
err
error
,
reverted
bool
)
{
// Note: onExit runs also when going deeper, exiting the context into a nested context.
addr
:=
h
.
SelfAddress
()
h
.
unwindCallstack
(
depth
)
if
reverted
{
h
.
LogCallStack
()
if
msg
,
revertInspectErr
:=
abi
.
UnpackRevert
(
output
);
revertInspectErr
==
nil
{
h
.
log
.
Warn
(
"Revert"
,
"addr"
,
addr
,
"err"
,
err
,
"revertMsg"
,
msg
)
h
.
log
.
Warn
(
"Revert"
,
"addr"
,
addr
,
"err"
,
err
,
"revertMsg"
,
msg
,
"depth"
,
depth
)
}
else
{
h
.
log
.
Warn
(
"Revert"
,
"addr"
,
addr
,
"err"
,
err
,
"revertData"
,
hexutil
.
Bytes
(
output
))
h
.
log
.
Warn
(
"Revert"
,
"addr"
,
addr
,
"err"
,
err
,
"revertData"
,
hexutil
.
Bytes
(
output
)
,
"depth"
,
depth
)
}
}
h
.
unwindCallstack
(
depth
)
}
// onFault is a trace-hook, catches things more generic than regular EVM reverts.
func
(
h
*
Host
)
onFault
(
pc
uint64
,
op
byte
,
gas
,
cost
uint64
,
scope
tracing
.
OpContext
,
depth
int
,
err
error
)
{
h
.
log
.
Warn
(
"Fault"
,
"addr"
,
scope
.
Address
(),
"err"
,
err
)
h
.
log
.
Warn
(
"Fault"
,
"addr"
,
scope
.
Address
(),
"err"
,
err
,
"depth"
,
depth
)
}
// unwindCallstack is a helper to remove call-stack entries.
...
...
@@ -307,26 +379,22 @@ func (h *Host) onOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpCo
if
len
(
h
.
callStack
)
==
0
||
h
.
callStack
[
len
(
h
.
callStack
)
-
1
]
.
Depth
<
depth
{
h
.
callStack
=
append
(
h
.
callStack
,
CallFrame
{
Depth
:
depth
,
Opener
:
vm
.
OpCode
(
op
),
LastOp
:
vm
.
OpCode
(
op
),
LastPC
:
pc
,
LastJumpPC
:
pc
,
Ctx
:
scopeCtx
,
})
// apply prank, if parent call-frame set up a prank
if
len
(
h
.
callStack
)
>
1
{
parentCallFrame
:=
h
.
callStack
[
len
(
h
.
callStack
)
-
2
]
if
parentCallFrame
.
Prank
!=
nil
{
if
parentCallFrame
.
Prank
.
Sender
!=
nil
{
scopeCtx
.
Contract
.
CallerAddress
=
*
parentCallFrame
.
Prank
.
Sender
}
if
parentCallFrame
.
Prank
.
Origin
!=
nil
{
h
.
env
.
TxContext
.
Origin
=
*
parentCallFrame
.
Prank
.
Origin
}
}
}
}
// Sanity check that top of the call-stack matches the scope context now
if
len
(
h
.
callStack
)
==
0
||
h
.
callStack
[
len
(
h
.
callStack
)
-
1
]
.
Ctx
!=
scopeCtx
{
panic
(
"scope context changed without call-frame pop/push"
)
}
cf
:=
&
h
.
callStack
[
len
(
h
.
callStack
)
-
1
]
if
vm
.
OpCode
(
op
)
==
vm
.
JUMPDEST
{
// remember the last PC before successful jump
cf
.
LastJumpPC
=
cf
.
LastPC
}
cf
.
LastOp
=
vm
.
OpCode
(
op
)
cf
.
LastPC
=
pc
}
// onStorageChange is a trace-hook to capture state changes
...
...
@@ -374,95 +442,110 @@ func (h *Host) SelfAddress() common.Address {
return
cf
.
Ctx
.
Address
()
}
// Prank applies a prank to the current call-frame.
// Any sub-call will apply the prank to their frame context.
func
(
h
*
Host
)
Prank
(
msgSender
*
common
.
Address
,
txOrigin
*
common
.
Address
,
repeat
bool
,
broadcast
bool
)
error
{
if
len
(
h
.
callStack
)
==
0
{
h
.
log
.
Warn
(
"no call stack"
)
return
nil
// cannot prank while not in a call.
}
cf
:=
&
h
.
callStack
[
len
(
h
.
callStack
)
-
1
]
if
cf
.
Prank
!=
nil
{
if
cf
.
Prank
.
Broadcast
&&
!
broadcast
{
return
errors
.
New
(
"you have an active broadcast; broadcasting and pranks are not compatible"
)
}
if
!
cf
.
Prank
.
Broadcast
&&
broadcast
{
return
errors
.
New
(
"you have an active prank; broadcasting and pranks are not compatible"
)
}
}
cf
.
Prank
=
&
Prank
{
Sender
:
msgSender
,
Origin
:
txOrigin
,
PrevOrigin
:
h
.
env
.
TxContext
.
Origin
,
Repeat
:
repeat
,
Broadcast
:
broadcast
,
}
return
nil
func
(
h
*
Host
)
GetEnvVar
(
key
string
)
(
value
string
,
ok
bool
)
{
value
,
ok
=
h
.
envVars
[
key
]
return
}
// StopPrank disables the current prank. Any sub-call will not be pranked.
func
(
h
*
Host
)
StopPrank
(
broadcast
bool
)
error
{
if
len
(
h
.
callStack
)
==
0
{
return
nil
}
cf
:=
&
h
.
callStack
[
len
(
h
.
callStack
)
-
1
]
if
cf
.
Prank
==
nil
{
if
broadcast
{
return
errors
.
New
(
"no broadcast in progress to stop"
)
}
return
nil
}
if
cf
.
Prank
.
Broadcast
&&
!
broadcast
{
// stopPrank on active broadcast is silent and no-op
return
nil
}
if
!
cf
.
Prank
.
Broadcast
&&
broadcast
{
return
errors
.
New
(
"no broadcast in progress to stop"
)
}
cf
.
Prank
=
nil
return
nil
func
(
h
*
Host
)
SetEnvVar
(
key
string
,
value
string
)
{
h
.
envVars
[
key
]
=
value
}
func
(
h
*
Host
)
CallerMode
()
CallerMode
{
if
len
(
h
.
callStack
)
==
0
{
return
CallerModeNone
}
cf
:=
&
h
.
callStack
[
len
(
h
.
callStack
)
-
1
]
if
cf
.
Prank
!=
nil
{
if
cf
.
Prank
.
Broadcast
{
if
cf
.
Prank
.
Repeat
{
return
CallerModeRecurrentBroadcast
// StateDump turns the current EVM state into a foundry-allocs dump
// (wrapping a geth Account allocs type). This is used to export the state.
// Note that upon dumping, the state-DB is committed and flushed.
// This affects any remaining self-destructs, as all accounts are flushed to persistent state.
// After flushing the EVM state also cannot revert to a previous snapshot state:
// the state should not be dumped within contract-execution that needs to revert.
func
(
h
*
Host
)
StateDump
()
(
*
foundry
.
ForgeAllocs
,
error
)
{
// We have to commit the existing state to the trie,
// for all the state-changes to be captured by the trie iterator.
root
,
err
:=
h
.
state
.
Commit
(
h
.
env
.
Context
.
BlockNumber
.
Uint64
(),
true
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to commit state: %w"
,
err
)
}
return
CallerModeBroadcast
// We need a state object around the state DB
st
,
err
:=
state
.
New
(
root
,
h
.
stateDB
,
nil
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to create state object for state-dumping: %w"
,
err
)
}
if
cf
.
Prank
.
Repeat
{
return
CallerModeRecurrentPrank
// After Commit we cannot reuse the old State, so we update the host to use the new one
h
.
state
=
st
h
.
env
.
StateDB
=
st
var
allocs
foundry
.
ForgeAllocs
allocs
.
FromState
(
st
)
// Sanity check we have no lingering scripts.
for
i
:=
uint64
(
0
);
i
<=
allocs
.
Accounts
[
ScriptDeployer
]
.
Nonce
;
i
++
{
scriptAddr
:=
crypto
.
CreateAddress
(
ScriptDeployer
,
i
)
h
.
log
.
Info
(
"removing script from state-dump"
,
"addr"
,
scriptAddr
,
"label"
,
h
.
labels
[
scriptAddr
])
delete
(
allocs
.
Accounts
,
scriptAddr
)
}
return
CallerModePrank
// Remove the script deployer from the output
delete
(
allocs
.
Accounts
,
ScriptDeployer
)
// The cheatcodes VM has a placeholder bytecode,
// because solidity checks if the code exists prior to regular EVM-calls to it.
delete
(
allocs
.
Accounts
,
VMAddr
)
// Precompile overrides come with temporary state account placeholders. Ignore those.
for
addr
:=
range
h
.
precompiles
{
delete
(
allocs
.
Accounts
,
addr
)
}
return
CallerModeNone
return
&
allocs
,
nil
}
type
CallerMode
uint8
func
(
h
*
Host
)
SetTxOrigin
(
addr
common
.
Address
)
{
h
.
env
.
TxContext
.
Origin
=
addr
}
func
(
cm
CallerMode
)
Big
()
*
big
.
Int
{
return
big
.
NewInt
(
int64
(
cm
))
func
(
h
*
Host
)
TxOrigin
()
common
.
Address
{
return
h
.
env
.
TxContext
.
Origin
}
// CallerMode matches the CallerMode forge cheatcode enum.
const
(
CallerModeNone
CallerMode
=
iota
CallerModeBroadcast
CallerModeRecurrentBroadcast
CallerModePrank
CallerModeRecurrentPrank
)
// ScriptBackendFn is a convenience method for scripts to attach to the Host.
// It return a function pre-configured with the given destination-address,
// to call the destination script.
func
(
h
*
Host
)
ScriptBackendFn
(
to
common
.
Address
)
CallBackendFn
{
return
func
(
data
[]
byte
)
([]
byte
,
error
)
{
ret
,
_
,
err
:=
h
.
Call
(
h
.
env
.
TxContext
.
Origin
,
to
,
data
,
DefaultFoundryGasLimit
,
uint256
.
NewInt
(
0
))
return
ret
,
err
}
}
func
(
h
*
Host
)
GetEnvVar
(
key
string
)
(
value
string
,
ok
bool
)
{
value
,
ok
=
h
.
envVars
[
key
]
return
// EnforceMaxCodeSize configures the EVM to enforce (if true), or not enforce (if false),
// the maximum contract bytecode size.
func
(
h
*
Host
)
EnforceMaxCodeSize
(
v
bool
)
{
h
.
env
.
Config
.
NoMaxCodeSize
=
!
v
}
func
(
h
*
Host
)
SetEnvVar
(
key
string
,
value
string
)
{
h
.
envVars
[
key
]
=
value
// LogCallStack is a convenience method for debugging,
// to log details of each call-frame (from bottom to top) to the logger.
func
(
h
*
Host
)
LogCallStack
()
{
for
_
,
cf
:=
range
h
.
callStack
{
callsite
:=
""
if
srcMap
,
ok
:=
h
.
srcMaps
[
cf
.
Ctx
.
Address
()];
ok
{
callsite
=
srcMap
.
FormattedInfo
(
cf
.
LastPC
)
if
callsite
==
"unknown:0:0"
{
callsite
=
srcMap
.
FormattedInfo
(
cf
.
LastJumpPC
)
}
}
input
:=
cf
.
Ctx
.
CallInput
()
byte4
:=
""
if
len
(
input
)
>=
4
{
byte4
=
fmt
.
Sprintf
(
"0x%x"
,
input
[
:
4
])
}
h
.
log
.
Debug
(
"callframe"
,
"depth"
,
cf
.
Depth
,
"input"
,
hexutil
.
Bytes
(
input
),
"pc"
,
cf
.
LastPC
,
"op"
,
cf
.
LastOp
)
h
.
log
.
Warn
(
"callframe"
,
"depth"
,
cf
.
Depth
,
"byte4"
,
byte4
,
"addr"
,
cf
.
Ctx
.
Address
(),
"callsite"
,
callsite
,
"label"
,
h
.
labels
[
cf
.
Ctx
.
Address
()])
}
}
// Label an address with a name, like the foundry vm.label cheatcode.
func
(
h
*
Host
)
Label
(
addr
common
.
Address
,
label
string
)
{
h
.
log
.
Debug
(
"labeling"
,
"addr"
,
addr
,
"label"
,
label
)
h
.
labels
[
addr
]
=
label
}
op-chain-ops/script/script_test.go
View file @
85d8c86e
...
...
@@ -19,7 +19,7 @@ func TestScript(t *testing.T) {
af
:=
foundry
.
OpenArtifactsDir
(
"./testdata/test-artifacts"
)
scriptContext
:=
DefaultContext
h
:=
NewHost
(
logger
,
af
,
scriptContext
)
h
:=
NewHost
(
logger
,
af
,
nil
,
scriptContext
)
addr
,
err
:=
h
.
LoadContract
(
"ScriptExample.s.sol"
,
"ScriptExample"
)
require
.
NoError
(
t
,
err
)
...
...
@@ -27,7 +27,7 @@ func TestScript(t *testing.T) {
h
.
SetEnvVar
(
"EXAMPLE_BOOL"
,
"true"
)
input
:=
bytes4
(
"run()"
)
returnData
,
_
,
err
:=
h
.
Call
(
scriptContext
.
s
ender
,
addr
,
input
[
:
],
DefaultFoundryGasLimit
,
uint256
.
NewInt
(
0
))
returnData
,
_
,
err
:=
h
.
Call
(
scriptContext
.
S
ender
,
addr
,
input
[
:
],
DefaultFoundryGasLimit
,
uint256
.
NewInt
(
0
))
require
.
NoError
(
t
,
err
,
"call failed: %x"
,
string
(
returnData
))
require
.
NotNil
(
t
,
captLog
.
FindLog
(
testlog
.
NewAttributesFilter
(
"p0"
,
"sender nonce"
),
...
...
op-chain-ops/script/with.go
0 → 100644
View file @
85d8c86e
package
script
import
(
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func
checkABI
(
abiData
*
abi
.
ABI
,
methodSignature
string
)
bool
{
for
_
,
m
:=
range
abiData
.
Methods
{
if
m
.
Sig
==
methodSignature
{
return
true
}
}
return
false
}
// WithScript deploys a script contract, at a create-address based on the ScriptDeployer.
// The returned cleanup function wipes the script account again (but not the storage).
func
WithScript
[
B
any
](
h
*
Host
,
name
string
,
contract
string
)
(
b
*
B
,
cleanup
func
(),
err
error
)
{
// load contract artifact
artifact
,
err
:=
h
.
af
.
ReadArtifact
(
name
,
contract
)
if
err
!=
nil
{
return
nil
,
nil
,
fmt
.
Errorf
(
"could not load script artifact: %w"
,
err
)
}
deployer
:=
ScriptDeployer
deployNonce
:=
h
.
state
.
GetNonce
(
deployer
)
// compute address of script contract to be deployed
addr
:=
crypto
.
CreateAddress
(
deployer
,
deployNonce
)
// init bindings (with ABI check)
bindings
,
err
:=
MakeBindings
[
B
](
h
.
ScriptBackendFn
(
addr
),
func
(
abiDef
string
)
bool
{
return
checkABI
(
&
artifact
.
ABI
,
abiDef
)
})
if
err
!=
nil
{
return
nil
,
nil
,
fmt
.
Errorf
(
"failed to make bindings: %w"
,
err
)
}
// Scripts can be very large
h
.
EnforceMaxCodeSize
(
false
)
defer
h
.
EnforceMaxCodeSize
(
true
)
// deploy the script contract
deployedAddr
,
err
:=
h
.
Create
(
deployer
,
artifact
.
Bytecode
.
Object
)
if
err
!=
nil
{
return
nil
,
nil
,
fmt
.
Errorf
(
"failed to deploy script: %w"
,
err
)
}
if
deployedAddr
!=
addr
{
return
nil
,
nil
,
fmt
.
Errorf
(
"deployed to unexpected address %s, expected %s"
,
deployedAddr
,
addr
)
}
h
.
RememberArtifact
(
addr
,
artifact
,
contract
)
h
.
Label
(
addr
,
contract
)
return
bindings
,
func
()
{
h
.
Wipe
(
addr
)
},
nil
}
// WithPrecompileAtAddress turns a struct into a precompile,
// and inserts it as override at the given address in the host.
// A cleanup function is returned, to remove the precompile override again.
func
WithPrecompileAtAddress
[
E
any
](
h
*
Host
,
addr
common
.
Address
,
elem
E
,
opts
...
PrecompileOption
[
E
])
(
cleanup
func
(),
err
error
)
{
if
h
.
HasPrecompileOverride
(
addr
)
{
return
nil
,
fmt
.
Errorf
(
"already have existing precompile override at %s"
,
addr
)
}
precompile
,
err
:=
NewPrecompile
[
E
](
elem
,
opts
...
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to construct precompile: %w"
,
err
)
}
h
.
SetPrecompile
(
addr
,
precompile
)
h
.
Label
(
addr
,
fmt
.
Sprintf
(
"%T"
,
precompile
.
Precompile
))
return
func
()
{
h
.
SetPrecompile
(
addr
,
nil
)
},
nil
}
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