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
dc56c5e9
Unverified
Commit
dc56c5e9
authored
Aug 23, 2023
by
OptimismBot
Committed by
GitHub
Aug 23, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6898 from ethereum-optimism/feat/tx-bundle-builder
feat: tx bundle builder
parents
5628ced6
51bfee47
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
1343 additions
and
29 deletions
+1343
-29
batch.go
op-chain-ops/safe/batch.go
+298
-0
batch_file_test.go
op-chain-ops/safe/batch_file_test.go
+0
-29
batch_helpers.go
op-chain-ops/safe/batch_helpers.go
+209
-0
batch_test.go
op-chain-ops/safe/batch_test.go
+156
-0
deposit-tx.json
op-chain-ops/safe/testdata/deposit-tx.json
+56
-0
finalize-withdrawal-tx.json
op-chain-ops/safe/testdata/finalize-withdrawal-tx.json
+64
-0
portal-abi.json
op-chain-ops/safe/testdata/portal-abi.json
+560
-0
No files found.
op-chain-ops/safe/batch
_file
.go
→
op-chain-ops/safe/batch.go
View file @
dc56c5e9
...
@@ -4,46 +4,134 @@
...
@@ -4,46 +4,134 @@
package
safe
package
safe
import
(
import
(
"bytes"
"encoding/json"
"encoding/json"
"fmt"
"fmt"
"math/big"
"math/big"
"strings"
"golang.org/x/exp/maps"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)
)
// Batch
File
represents a Safe tx-builder transaction.
// Batch represents a Safe tx-builder transaction.
type
Batch
File
struct
{
type
Batch
struct
{
Version
string
`json:"version"`
Version
string
`json:"version"`
ChainID
*
big
.
Int
`json:"chainId"`
ChainID
*
big
.
Int
`json:"chainId"`
CreatedAt
uint64
`json:"createdAt"`
CreatedAt
uint64
`json:"createdAt"`
Meta
Batch
FileMeta
`json:"meta"`
Meta
Batch
Meta
`json:"meta"`
Transactions
[]
BatchTransaction
`json:"transactions"`
Transactions
[]
BatchTransaction
`json:"transactions"`
}
}
// AddCall will add a call to the batch. After a series of calls are
// added to the batch, it can be serialized to JSON.
func
(
b
*
Batch
)
AddCall
(
to
common
.
Address
,
value
*
big
.
Int
,
sig
string
,
args
[]
any
,
iface
abi
.
ABI
)
error
{
// Attempt to pull out the signature from the top level methods.
// The abi package uses normalization that we do not want to be
// coupled to, so attempt to search for the raw name if the top
// level name is not found to handle overloading more gracefully.
method
,
ok
:=
iface
.
Methods
[
sig
]
if
!
ok
{
for
_
,
m
:=
range
iface
.
Methods
{
if
m
.
RawName
==
sig
||
m
.
Sig
==
sig
{
method
=
m
ok
=
true
}
}
}
if
!
ok
{
keys
:=
maps
.
Keys
(
iface
.
Methods
)
methods
:=
strings
.
Join
(
keys
,
","
)
return
fmt
.
Errorf
(
"%s not found in abi, options are %s"
,
sig
,
methods
)
}
if
len
(
args
)
!=
len
(
method
.
Inputs
)
{
return
fmt
.
Errorf
(
"requires %d inputs but got %d for %s"
,
len
(
method
.
Inputs
),
len
(
args
),
method
.
RawName
)
}
contractMethod
:=
ContractMethod
{
Name
:
method
.
RawName
,
Payable
:
method
.
Payable
,
}
inputValues
:=
make
(
map
[
string
]
string
)
contractInputs
:=
make
([]
ContractInput
,
0
)
for
i
,
input
:=
range
method
.
Inputs
{
contractInput
,
err
:=
createContractInput
(
input
,
contractInputs
)
if
err
!=
nil
{
return
err
}
contractMethod
.
Inputs
=
append
(
contractMethod
.
Inputs
,
contractInput
...
)
str
,
err
:=
stringifyArg
(
args
[
i
])
if
err
!=
nil
{
return
err
}
inputValues
[
input
.
Name
]
=
str
}
encoded
,
err
:=
method
.
Inputs
.
PackValues
(
args
)
if
err
!=
nil
{
return
err
}
data
:=
make
([]
byte
,
len
(
method
.
ID
)
+
len
(
encoded
))
copy
(
data
,
method
.
ID
)
copy
(
data
[
len
(
method
.
ID
)
:
],
encoded
)
batchTransaction
:=
BatchTransaction
{
To
:
to
,
Value
:
value
,
Method
:
contractMethod
,
Data
:
data
,
InputValues
:
inputValues
,
}
b
.
Transactions
=
append
(
b
.
Transactions
,
batchTransaction
)
return
nil
}
// Check will check the batch for errors
func
(
b
*
Batch
)
Check
()
error
{
for
_
,
tx
:=
range
b
.
Transactions
{
if
err
:=
tx
.
Check
();
err
!=
nil
{
return
err
}
}
return
nil
}
// bathcFileMarshaling is a helper type used for JSON marshaling.
// bathcFileMarshaling is a helper type used for JSON marshaling.
type
batch
File
Marshaling
struct
{
type
batchMarshaling
struct
{
Version
string
`json:"version"`
Version
string
`json:"version"`
ChainID
string
`json:"chainId"`
ChainID
string
`json:"chainId"`
CreatedAt
uint64
`json:"createdAt"`
CreatedAt
uint64
`json:"createdAt"`
Meta
Batch
FileMeta
`json:"meta"`
Meta
Batch
Meta
`json:"meta"`
Transactions
[]
BatchTransaction
`json:"transactions"`
Transactions
[]
BatchTransaction
`json:"transactions"`
}
}
// MarshalJSON will marshal a Batch
File
to JSON.
// MarshalJSON will marshal a Batch to JSON.
func
(
b
*
Batch
File
)
MarshalJSON
()
([]
byte
,
error
)
{
func
(
b
*
Batch
)
MarshalJSON
()
([]
byte
,
error
)
{
return
json
.
Marshal
(
batchFile
Marshaling
{
batch
:=
batch
Marshaling
{
Version
:
b
.
Version
,
Version
:
b
.
Version
,
ChainID
:
b
.
ChainID
.
String
(),
CreatedAt
:
b
.
CreatedAt
,
CreatedAt
:
b
.
CreatedAt
,
Meta
:
b
.
Meta
,
Meta
:
b
.
Meta
,
Transactions
:
b
.
Transactions
,
Transactions
:
b
.
Transactions
,
})
}
if
b
.
ChainID
!=
nil
{
batch
.
ChainID
=
b
.
ChainID
.
String
()
}
return
json
.
Marshal
(
batch
)
}
}
// UnmarshalJSON will unmarshal a Batch
File
from JSON.
// UnmarshalJSON will unmarshal a Batch from JSON.
func
(
b
*
Batch
File
)
UnmarshalJSON
(
data
[]
byte
)
error
{
func
(
b
*
Batch
)
UnmarshalJSON
(
data
[]
byte
)
error
{
var
bf
batch
File
Marshaling
var
bf
batchMarshaling
if
err
:=
json
.
Unmarshal
(
data
,
&
bf
);
err
!=
nil
{
if
err
:=
json
.
Unmarshal
(
data
,
&
bf
);
err
!=
nil
{
return
err
return
err
}
}
...
@@ -59,9 +147,9 @@ func (b *BatchFile) UnmarshalJSON(data []byte) error {
...
@@ -59,9 +147,9 @@ func (b *BatchFile) UnmarshalJSON(data []byte) error {
return
nil
return
nil
}
}
// Batch
FileMeta contains metadata about a BatchFile
. Not all
// Batch
Meta contains metadata about a Batch
. Not all
// of the fields are required.
// of the fields are required.
type
Batch
File
Meta
struct
{
type
BatchMeta
struct
{
TxBuilderVersion
string
`json:"txBuilderVersion,omitempty"`
TxBuilderVersion
string
`json:"txBuilderVersion,omitempty"`
Checksum
string
`json:"checksum,omitempty"`
Checksum
string
`json:"checksum,omitempty"`
CreatedFromSafeAddress
string
`json:"createdFromSafeAddress"`
CreatedFromSafeAddress
string
`json:"createdFromSafeAddress"`
...
@@ -79,6 +167,81 @@ type BatchTransaction struct {
...
@@ -79,6 +167,81 @@ type BatchTransaction struct {
InputValues
map
[
string
]
string
`json:"contractInputsValues"`
InputValues
map
[
string
]
string
`json:"contractInputsValues"`
}
}
// Check will check the batch transaction for errors.
// An error is defined by:
// - incorrectly encoded calldata
// - mismatch in number of arguments
// It does not currently work on structs, will return no error if a "tuple"
// is used as an argument. Need to find a generic way to work with structs.
func
(
bt
*
BatchTransaction
)
Check
()
error
{
if
len
(
bt
.
Method
.
Inputs
)
!=
len
(
bt
.
InputValues
)
{
return
fmt
.
Errorf
(
"expected %d inputs but got %d"
,
len
(
bt
.
Method
.
Inputs
),
len
(
bt
.
InputValues
))
}
if
len
(
bt
.
Data
)
>
0
&&
bt
.
Method
.
Name
!=
"fallback"
{
if
len
(
bt
.
Data
)
<
4
{
return
fmt
.
Errorf
(
"must have at least 4 bytes of calldata, got %d"
,
len
(
bt
.
Data
))
}
sig
:=
bt
.
Signature
()
selector
:=
crypto
.
Keccak256
([]
byte
(
sig
))[
0
:
4
]
if
!
bytes
.
Equal
(
bt
.
Data
[
0
:
4
],
selector
)
{
return
fmt
.
Errorf
(
"data does not match signature"
)
}
// Check the calldata
values
:=
make
([]
any
,
len
(
bt
.
Method
.
Inputs
))
for
i
,
input
:=
range
bt
.
Method
.
Inputs
{
value
,
ok
:=
bt
.
InputValues
[
input
.
Name
]
if
!
ok
{
return
fmt
.
Errorf
(
"missing input %s"
,
input
.
Name
)
}
// Need to figure out better way to handle tuples in a generic way
if
input
.
Type
==
"tuple"
{
return
nil
}
arg
,
err
:=
unstringifyArg
(
value
,
input
.
Type
)
if
err
!=
nil
{
return
err
}
values
[
i
]
=
arg
}
calldata
,
err
:=
bt
.
Arguments
()
.
PackValues
(
values
)
if
err
!=
nil
{
return
err
}
if
!
bytes
.
Equal
(
bt
.
Data
[
4
:
],
calldata
)
{
return
fmt
.
Errorf
(
"calldata does not match inputs, expected %s, got %s"
,
hexutil
.
Encode
(
bt
.
Data
[
4
:
]),
hexutil
.
Encode
(
calldata
))
}
}
return
nil
}
// Signature returns the function signature of the batch transaction.
func
(
bt
*
BatchTransaction
)
Signature
()
string
{
types
:=
make
([]
string
,
len
(
bt
.
Method
.
Inputs
))
for
i
,
input
:=
range
bt
.
Method
.
Inputs
{
types
[
i
]
=
buildFunctionSignature
(
input
)
}
return
fmt
.
Sprintf
(
"%s(%s)"
,
bt
.
Method
.
Name
,
strings
.
Join
(
types
,
","
))
}
func
(
bt
*
BatchTransaction
)
Arguments
()
abi
.
Arguments
{
arguments
:=
make
(
abi
.
Arguments
,
len
(
bt
.
Method
.
Inputs
))
for
i
,
input
:=
range
bt
.
Method
.
Inputs
{
serialized
,
err
:=
json
.
Marshal
(
input
)
if
err
!=
nil
{
panic
(
err
)
}
var
arg
abi
.
Argument
if
err
:=
json
.
Unmarshal
(
serialized
,
&
arg
);
err
!=
nil
{
panic
(
err
)
}
arguments
[
i
]
=
arg
}
return
arguments
}
// UnmarshalJSON will unmarshal a BatchTransaction from JSON.
// UnmarshalJSON will unmarshal a BatchTransaction from JSON.
func
(
b
*
BatchTransaction
)
UnmarshalJSON
(
data
[]
byte
)
error
{
func
(
b
*
BatchTransaction
)
UnmarshalJSON
(
data
[]
byte
)
error
{
var
bt
batchTransactionMarshaling
var
bt
batchTransactionMarshaling
...
@@ -87,6 +250,9 @@ func (b *BatchTransaction) UnmarshalJSON(data []byte) error {
...
@@ -87,6 +250,9 @@ func (b *BatchTransaction) UnmarshalJSON(data []byte) error {
}
}
b
.
To
=
common
.
HexToAddress
(
bt
.
To
)
b
.
To
=
common
.
HexToAddress
(
bt
.
To
)
b
.
Value
=
new
(
big
.
Int
)
.
SetUint64
(
bt
.
Value
)
b
.
Value
=
new
(
big
.
Int
)
.
SetUint64
(
bt
.
Value
)
if
bt
.
Data
!=
nil
{
b
.
Data
=
common
.
CopyBytes
(
*
bt
.
Data
)
}
b
.
Method
=
bt
.
Method
b
.
Method
=
bt
.
Method
b
.
InputValues
=
bt
.
InputValues
b
.
InputValues
=
bt
.
InputValues
return
nil
return
nil
...
@@ -101,8 +267,8 @@ func (b *BatchTransaction) MarshalJSON() ([]byte, error) {
...
@@ -101,8 +267,8 @@ func (b *BatchTransaction) MarshalJSON() ([]byte, error) {
InputValues
:
b
.
InputValues
,
InputValues
:
b
.
InputValues
,
}
}
if
len
(
b
.
Data
)
!=
0
{
if
len
(
b
.
Data
)
!=
0
{
hex
:=
hexutil
.
Encode
(
b
.
Data
)
data
:=
hexutil
.
Bytes
(
b
.
Data
)
batch
.
Data
=
&
hex
batch
.
Data
=
&
data
}
}
return
json
.
Marshal
(
batch
)
return
json
.
Marshal
(
batch
)
}
}
...
@@ -111,7 +277,7 @@ func (b *BatchTransaction) MarshalJSON() ([]byte, error) {
...
@@ -111,7 +277,7 @@ func (b *BatchTransaction) MarshalJSON() ([]byte, error) {
type
batchTransactionMarshaling
struct
{
type
batchTransactionMarshaling
struct
{
To
string
`json:"to"`
To
string
`json:"to"`
Value
uint64
`json:"value,string"`
Value
uint64
`json:"value,string"`
Data
*
string
`json:"data"`
Data
*
hexutil
.
Bytes
`json:"data"`
Method
ContractMethod
`json:"contractMethod"`
Method
ContractMethod
`json:"contractMethod"`
InputValues
map
[
string
]
string
`json:"contractInputsValues"`
InputValues
map
[
string
]
string
`json:"contractInputsValues"`
}
}
...
...
op-chain-ops/safe/batch_file_test.go
deleted
100644 → 0
View file @
5628ced6
package
safe
import
(
"bytes"
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/require"
)
func
TestBatchFileJSONPrepareBedrock
(
t
*
testing
.
T
)
{
testBatchFileJSON
(
t
,
"testdata/batch-prepare-bedrock.json"
)
}
func
TestBatchFileJSONL2OO
(
t
*
testing
.
T
)
{
testBatchFileJSON
(
t
,
"testdata/l2-output-oracle.json"
)
}
func
testBatchFileJSON
(
t
*
testing
.
T
,
path
string
)
{
b
,
err
:=
os
.
ReadFile
(
path
)
require
.
NoError
(
t
,
err
)
dec
:=
json
.
NewDecoder
(
bytes
.
NewReader
(
b
))
decoded
:=
new
(
BatchFile
)
require
.
NoError
(
t
,
dec
.
Decode
(
decoded
))
data
,
err
:=
json
.
Marshal
(
decoded
)
require
.
NoError
(
t
,
err
)
require
.
JSONEq
(
t
,
string
(
b
),
string
(
data
))
}
op-chain-ops/safe/batch_helpers.go
0 → 100644
View file @
dc56c5e9
package
safe
import
(
"fmt"
"math/big"
"reflect"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// stringifyArg converts a Go type to a string that is representable by ABI.
// To do so, this function must be recursive to handle nested tuples.
func
stringifyArg
(
argument
any
)
(
string
,
error
)
{
switch
arg
:=
argument
.
(
type
)
{
case
common
.
Address
:
return
arg
.
String
(),
nil
case
*
common
.
Address
:
return
arg
.
String
(),
nil
case
*
big
.
Int
:
return
arg
.
String
(),
nil
case
big
.
Int
:
return
arg
.
String
(),
nil
case
bool
:
if
arg
{
return
"true"
,
nil
}
return
"false"
,
nil
case
int64
:
return
strconv
.
FormatInt
(
arg
,
10
),
nil
case
int32
:
return
strconv
.
FormatInt
(
int64
(
arg
),
10
),
nil
case
int16
:
return
strconv
.
FormatInt
(
int64
(
arg
),
10
),
nil
case
int8
:
return
strconv
.
FormatInt
(
int64
(
arg
),
10
),
nil
case
int
:
return
strconv
.
FormatInt
(
int64
(
arg
),
10
),
nil
case
uint64
:
return
strconv
.
FormatUint
(
uint64
(
arg
),
10
),
nil
case
uint32
:
return
strconv
.
FormatUint
(
uint64
(
arg
),
10
),
nil
case
uint16
:
return
strconv
.
FormatUint
(
uint64
(
arg
),
10
),
nil
case
uint8
:
return
strconv
.
FormatUint
(
uint64
(
arg
),
10
),
nil
case
uint
:
return
strconv
.
FormatUint
(
uint64
(
arg
),
10
),
nil
case
[]
byte
:
return
hexutil
.
Encode
(
arg
),
nil
case
[]
any
:
ret
:=
make
([]
string
,
len
(
arg
))
for
i
,
v
:=
range
arg
{
str
,
err
:=
stringifyArg
(
v
)
if
err
!=
nil
{
return
""
,
err
}
ret
[
i
]
=
str
}
return
"["
+
strings
.
Join
(
ret
,
","
)
+
"]"
,
nil
default
:
typ
:=
reflect
.
TypeOf
(
argument
)
if
typ
.
Kind
()
==
reflect
.
Ptr
{
typ
=
typ
.
Elem
()
}
if
typ
.
Kind
()
==
reflect
.
Struct
{
v
:=
reflect
.
ValueOf
(
argument
)
numField
:=
v
.
NumField
()
ret
:=
make
([]
string
,
numField
)
for
i
:=
0
;
i
<
numField
;
i
++
{
val
:=
v
.
Field
(
i
)
.
Interface
()
str
,
err
:=
stringifyArg
(
val
)
if
err
!=
nil
{
return
""
,
err
}
ret
[
i
]
=
str
}
return
"["
+
strings
.
Join
(
ret
,
","
)
+
"]"
,
nil
}
return
""
,
fmt
.
Errorf
(
"unknown type as argument: %T"
,
arg
)
}
}
// unstringifyArg converts a string to a Go type.
func
unstringifyArg
(
arg
string
,
typ
string
)
(
any
,
error
)
{
switch
typ
{
case
"address"
:
return
common
.
HexToAddress
(
arg
),
nil
case
"bool"
:
return
strconv
.
ParseBool
(
arg
)
case
"uint8"
:
val
,
err
:=
strconv
.
ParseUint
(
arg
,
10
,
8
)
return
uint8
(
val
),
err
case
"uint16"
:
val
,
err
:=
strconv
.
ParseUint
(
arg
,
10
,
16
)
return
uint16
(
val
),
err
case
"uint32"
:
val
,
err
:=
strconv
.
ParseUint
(
arg
,
10
,
32
)
return
uint32
(
val
),
err
case
"uint64"
:
val
,
err
:=
strconv
.
ParseUint
(
arg
,
10
,
64
)
return
val
,
err
case
"int8"
:
val
,
err
:=
strconv
.
ParseInt
(
arg
,
10
,
8
)
return
val
,
err
case
"int16"
:
val
,
err
:=
strconv
.
ParseInt
(
arg
,
10
,
16
)
return
val
,
err
case
"int32"
:
val
,
err
:=
strconv
.
ParseInt
(
arg
,
10
,
32
)
return
val
,
err
case
"int64"
:
val
,
err
:=
strconv
.
ParseInt
(
arg
,
10
,
64
)
return
val
,
err
case
"uint256"
,
"int256"
:
val
,
ok
:=
new
(
big
.
Int
)
.
SetString
(
arg
,
10
)
if
!
ok
{
return
nil
,
fmt
.
Errorf
(
"failed to parse %s as big.Int"
,
arg
)
}
return
val
,
nil
case
"string"
:
return
arg
,
nil
case
"bytes"
:
return
hexutil
.
Decode
(
arg
)
default
:
return
nil
,
fmt
.
Errorf
(
"unknown type: %s"
,
typ
)
}
}
// createContractInput converts an abi.Argument to one or more ContractInputs.
func
createContractInput
(
input
abi
.
Argument
,
inputs
[]
ContractInput
)
([]
ContractInput
,
error
)
{
inputType
,
err
:=
stringifyType
(
input
.
Type
)
if
err
!=
nil
{
return
nil
,
err
}
// TODO: could probably do better than string comparison?
internalType
:=
input
.
Type
.
String
()
if
inputType
==
"tuple"
{
internalType
=
input
.
Type
.
TupleRawName
}
components
:=
make
([]
ContractInput
,
0
)
for
i
,
elem
:=
range
input
.
Type
.
TupleElems
{
e
:=
*
elem
arg
:=
abi
.
Argument
{
Name
:
input
.
Type
.
TupleRawNames
[
i
],
Type
:
e
,
}
component
,
err
:=
createContractInput
(
arg
,
inputs
)
if
err
!=
nil
{
return
nil
,
err
}
components
=
append
(
components
,
component
...
)
}
contractInput
:=
ContractInput
{
InternalType
:
internalType
,
Name
:
input
.
Name
,
Type
:
inputType
,
Components
:
components
,
}
inputs
=
append
(
inputs
,
contractInput
)
return
inputs
,
nil
}
// stringifyType turns an abi.Type into a string
func
stringifyType
(
t
abi
.
Type
)
(
string
,
error
)
{
switch
t
.
T
{
case
abi
.
TupleTy
:
return
"tuple"
,
nil
case
abi
.
BoolTy
:
return
t
.
String
(),
nil
case
abi
.
AddressTy
:
return
t
.
String
(),
nil
case
abi
.
UintTy
:
return
t
.
String
(),
nil
case
abi
.
IntTy
:
return
t
.
String
(),
nil
case
abi
.
StringTy
:
return
t
.
String
(),
nil
case
abi
.
BytesTy
:
return
t
.
String
(),
nil
default
:
return
""
,
fmt
.
Errorf
(
"unknown type: %d"
,
t
.
T
)
}
}
// buildFunctionSignature builds a function signature from a ContractInput.
// It is recursive to handle tuples.
func
buildFunctionSignature
(
input
ContractInput
)
string
{
if
input
.
Type
==
"tuple"
{
types
:=
make
([]
string
,
len
(
input
.
Components
))
for
i
,
component
:=
range
input
.
Components
{
types
[
i
]
=
buildFunctionSignature
(
component
)
}
return
fmt
.
Sprintf
(
"(%s)"
,
strings
.
Join
(
types
,
","
))
}
return
input
.
InternalType
}
op-chain-ops/safe/batch_test.go
0 → 100644
View file @
dc56c5e9
package
safe
import
(
"bytes"
"encoding/json"
"errors"
"math/big"
"os"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func
TestBatchJSONPrepareBedrock
(
t
*
testing
.
T
)
{
testBatchJSON
(
t
,
"testdata/batch-prepare-bedrock.json"
)
}
func
TestBatchJSONL2OO
(
t
*
testing
.
T
)
{
testBatchJSON
(
t
,
"testdata/l2-output-oracle.json"
)
}
func
testBatchJSON
(
t
*
testing
.
T
,
path
string
)
{
b
,
err
:=
os
.
ReadFile
(
path
)
require
.
NoError
(
t
,
err
)
dec
:=
json
.
NewDecoder
(
bytes
.
NewReader
(
b
))
decoded
:=
new
(
Batch
)
require
.
NoError
(
t
,
dec
.
Decode
(
decoded
))
data
,
err
:=
json
.
Marshal
(
decoded
)
require
.
NoError
(
t
,
err
)
require
.
JSONEq
(
t
,
string
(
b
),
string
(
data
))
}
// TestBatchAddCallFinalizeWithdrawalTransaction ensures that structs can be serialized correctly.
func
TestBatchAddCallFinalizeWithdrawalTransaction
(
t
*
testing
.
T
)
{
file
,
err
:=
os
.
ReadFile
(
"testdata/portal-abi.json"
)
require
.
NoError
(
t
,
err
)
portalABI
,
err
:=
abi
.
JSON
(
bytes
.
NewReader
(
file
))
require
.
NoError
(
t
,
err
)
sig
:=
"finalizeWithdrawalTransaction"
argument
:=
[]
any
{
bindings
.
TypesWithdrawalTransaction
{
Nonce
:
big
.
NewInt
(
0
),
Sender
:
common
.
Address
{
19
:
0x01
},
Target
:
common
.
Address
{
19
:
0x02
},
Value
:
big
.
NewInt
(
1
),
GasLimit
:
big
.
NewInt
(
2
),
Data
:
[]
byte
{},
},
}
batch
:=
new
(
Batch
)
to
:=
common
.
Address
{
19
:
0x01
}
value
:=
big
.
NewInt
(
222
)
require
.
NoError
(
t
,
batch
.
AddCall
(
to
,
value
,
sig
,
argument
,
portalABI
))
require
.
NoError
(
t
,
batch
.
Check
())
require
.
Equal
(
t
,
batch
.
Transactions
[
0
]
.
Signature
(),
"finalizeWithdrawalTransaction((uint256,address,address,uint256,uint256,bytes))"
)
expected
,
err
:=
os
.
ReadFile
(
"testdata/finalize-withdrawal-tx.json"
)
require
.
NoError
(
t
,
err
)
serialized
,
err
:=
json
.
Marshal
(
batch
)
require
.
NoError
(
t
,
err
)
require
.
JSONEq
(
t
,
string
(
expected
),
string
(
serialized
))
}
// TestBatchAddCallDespostTransaction ensures that simple calls can be serialized correctly.
func
TestBatchAddCallDespositTransaction
(
t
*
testing
.
T
)
{
file
,
err
:=
os
.
ReadFile
(
"testdata/portal-abi.json"
)
require
.
NoError
(
t
,
err
)
portalABI
,
err
:=
abi
.
JSON
(
bytes
.
NewReader
(
file
))
require
.
NoError
(
t
,
err
)
batch
:=
new
(
Batch
)
to
:=
common
.
Address
{
19
:
0x01
}
value
:=
big
.
NewInt
(
222
)
sig
:=
"depositTransaction"
argument
:=
[]
any
{
common
.
Address
{
01
},
big
.
NewInt
(
2
),
uint64
(
100
),
false
,
[]
byte
{},
}
require
.
NoError
(
t
,
batch
.
AddCall
(
to
,
value
,
sig
,
argument
,
portalABI
))
require
.
NoError
(
t
,
batch
.
Check
())
require
.
Equal
(
t
,
batch
.
Transactions
[
0
]
.
Signature
(),
"depositTransaction(address,uint256,uint64,bool,bytes)"
)
expected
,
err
:=
os
.
ReadFile
(
"testdata/deposit-tx.json"
)
require
.
NoError
(
t
,
err
)
serialized
,
err
:=
json
.
Marshal
(
batch
)
require
.
NoError
(
t
,
err
)
require
.
JSONEq
(
t
,
string
(
expected
),
string
(
serialized
))
}
// TestBatchCheck checks for the various failure cases of Batch.Check
// as well as a simple check for a valid batch.
func
TestBatchCheck
(
t
*
testing
.
T
)
{
cases
:=
[]
struct
{
name
string
bt
BatchTransaction
err
error
}{
{
name
:
"bad-input-count"
,
bt
:
BatchTransaction
{
Method
:
ContractMethod
{},
InputValues
:
map
[
string
]
string
{
"foo"
:
"bar"
,
},
},
err
:
errors
.
New
(
"expected 0 inputs but got 1"
),
},
{
name
:
"bad-calldata-too-small"
,
bt
:
BatchTransaction
{
Data
:
[]
byte
{
0x01
},
},
err
:
errors
.
New
(
"must have at least 4 bytes of calldata, got 1"
),
},
{
name
:
"bad-calldata-mismatch"
,
bt
:
BatchTransaction
{
Data
:
[]
byte
{
0x01
,
0x02
,
0x03
,
0x04
},
Method
:
ContractMethod
{
Name
:
"foo"
,
},
},
err
:
errors
.
New
(
"data does not match signature"
),
},
{
name
:
"good-calldata"
,
bt
:
BatchTransaction
{
Data
:
[]
byte
{
0xc2
,
0x98
,
0x55
,
0x78
},
Method
:
ContractMethod
{
Name
:
"foo"
,
},
},
err
:
nil
,
},
}
for
_
,
tc
:=
range
cases
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
require
.
Equal
(
t
,
tc
.
err
,
tc
.
bt
.
Check
())
})
}
}
op-chain-ops/safe/testdata/deposit-tx.json
0 → 100644
View file @
dc56c5e9
{
"version"
:
""
,
"chainId"
:
""
,
"createdAt"
:
0
,
"meta"
:
{
"createdFromSafeAddress"
:
""
,
"createdFromOwnerAddress"
:
""
,
"name"
:
""
,
"description"
:
""
},
"transactions"
:
[
{
"to"
:
"0x0000000000000000000000000000000000000001"
,
"value"
:
"222"
,
"data"
:
"0xe9e05c42000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"
,
"contractMethod"
:
{
"inputs"
:
[
{
"internalType"
:
"address"
,
"name"
:
"_to"
,
"type"
:
"address"
},
{
"internalType"
:
"uint256"
,
"name"
:
"_value"
,
"type"
:
"uint256"
},
{
"internalType"
:
"uint64"
,
"name"
:
"_gasLimit"
,
"type"
:
"uint64"
},
{
"internalType"
:
"bool"
,
"name"
:
"_isCreation"
,
"type"
:
"bool"
},
{
"internalType"
:
"bytes"
,
"name"
:
"_data"
,
"type"
:
"bytes"
}
],
"name"
:
"depositTransaction"
,
"payable"
:
false
},
"contractInputsValues"
:
{
"_data"
:
"0x"
,
"_gasLimit"
:
"100"
,
"_isCreation"
:
"false"
,
"_to"
:
"0x0100000000000000000000000000000000000000"
,
"_value"
:
"2"
}
}
]
}
op-chain-ops/safe/testdata/finalize-withdrawal-tx.json
0 → 100644
View file @
dc56c5e9
{
"version"
:
""
,
"chainId"
:
""
,
"createdAt"
:
0
,
"meta"
:
{
"createdFromSafeAddress"
:
""
,
"createdFromOwnerAddress"
:
""
,
"name"
:
""
,
"description"
:
""
},
"transactions"
:
[
{
"to"
:
"0x0000000000000000000000000000000000000001"
,
"value"
:
"222"
,
"data"
:
"0x8c3152e900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000"
,
"contractMethod"
:
{
"inputs"
:
[
{
"internalType"
:
"TypesWithdrawalTransaction"
,
"name"
:
"_tx"
,
"type"
:
"tuple"
,
"components"
:
[
{
"internalType"
:
"uint256"
,
"name"
:
"nonce"
,
"type"
:
"uint256"
},
{
"internalType"
:
"address"
,
"name"
:
"sender"
,
"type"
:
"address"
},
{
"internalType"
:
"address"
,
"name"
:
"target"
,
"type"
:
"address"
},
{
"internalType"
:
"uint256"
,
"name"
:
"value"
,
"type"
:
"uint256"
},
{
"internalType"
:
"uint256"
,
"name"
:
"gasLimit"
,
"type"
:
"uint256"
},
{
"internalType"
:
"bytes"
,
"name"
:
"data"
,
"type"
:
"bytes"
}
]
}
],
"name"
:
"finalizeWithdrawalTransaction"
,
"payable"
:
false
},
"contractInputsValues"
:
{
"_tx"
:
"[0,0x0000000000000000000000000000000000000001,0x0000000000000000000000000000000000000002,1,2,0x]"
}
}
]
}
op-chain-ops/safe/testdata/portal-abi.json
0 → 100644
View file @
dc56c5e9
This diff is collapsed.
Click to expand it.
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