Commit 4477fe9f authored by Maurelian's avatar Maurelian Committed by GitHub

feat(bedrock): Versioned TransactionDeposited event (#2965)

* feat(bedrock): Versioned TransactionDeposited event

- Index the deposit event version
- Update fuzz test to use new deposit type
- UnmarshalDepositLogEvent update
- Change event version from 1 to 0
- Add version to MarshalDepositLogEvent
- Pack opaque data, abi.encodePacked
- Handle encodePacked data in Marshal and Unmarshal DepositLogEvent
- Refactor for versioned opaqueData marshal/unmarshal methods
- Update op-bindings OptimismPortal

* core-utils: update deposit transaction serialization

* op-node: Add comment to clarify deserialization
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>
parent 7baf49f1
---
'@eth-optimism/core-utils': patch
---
Update deposit transaction serialization
This diff is collapsed.
...@@ -12,35 +12,63 @@ import ( ...@@ -12,35 +12,63 @@ import (
) )
var ( var (
DepositEventABI = "TransactionDeposited(address,address,uint256,uint256,uint64,bool,bytes)" DepositEventABI = "TransactionDeposited(address,address,uint256,bytes)"
DepositEventABIHash = crypto.Keccak256Hash([]byte(DepositEventABI)) DepositEventABIHash = crypto.Keccak256Hash([]byte(DepositEventABI))
DepositEventVersion0 = common.Hash{}
) )
// UnmarshalDepositLogEvent decodes an EVM log entry emitted by the deposit contract into typed deposit data. // UnmarshalDepositLogEvent decodes an EVM log entry emitted by the deposit contract into typed deposit data.
// //
// parse log data for: // parse log data for:
// event TransactionDeposited( // event TransactionDeposited(
// address indexed from, // address indexed from,
// address indexed to, // address indexed to,
// uint256 mint, // uint256 indexed version,
// uint256 value, // bytes opaqueData
// uint64 gasLimit,
// bool isCreation,
// data data
// ); // );
// //
// Additionally, the event log-index and // Additionally, the event log-index and
func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) { func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) {
if len(ev.Topics) != 3 { if len(ev.Topics) != 4 {
return nil, fmt.Errorf("expected 3 event topics (event identity, indexed from, indexed to)") return nil, fmt.Errorf("expected 4 event topics (event identity, indexed from, indexed to, indexed version), got %d", len(ev.Topics))
} }
if ev.Topics[0] != DepositEventABIHash { if ev.Topics[0] != DepositEventABIHash {
return nil, fmt.Errorf("invalid deposit event selector: %s, expected %s", ev.Topics[0], DepositEventABIHash) return nil, fmt.Errorf("invalid deposit event selector: %s, expected %s", ev.Topics[0], DepositEventABIHash)
} }
if len(ev.Data) < 6*32 { if len(ev.Data) < 64 {
return nil, fmt.Errorf("deposit event data too small (%d bytes): %x", len(ev.Data), ev.Data) return nil, fmt.Errorf("incomplate opaqueData slice header (%d bytes): %x", len(ev.Data), ev.Data)
}
if len(ev.Data)%32 != 0 {
return nil, fmt.Errorf("expected log data to be multiple of 32 bytes: got %d bytes", len(ev.Data))
} }
// indexed 0
from := common.BytesToAddress(ev.Topics[1][12:])
// indexed 1
to := common.BytesToAddress(ev.Topics[2][12:])
// indexed 2
version := ev.Topics[3]
// unindexed data
// Solidity serializes the event's Data field as follows:
// abi.encode(abi.encodPacked(uint256 mint, uint256 value, uint64 gasLimit, uint8 isCreation, bytes data))
// Thus the first 32 bytes of the Data will give us the offset of the opaqueData,
// which should always be 0x20.
var opaqueContentOffset uint256.Int
opaqueContentOffset.SetBytes(ev.Data[0:32])
if !opaqueContentOffset.IsUint64() || opaqueContentOffset.Uint64() != 32 {
return nil, fmt.Errorf("invalid opaqueData slice header offset: %d", opaqueContentOffset.Uint64())
}
// The next 32 bytes indicate the length of the opaqueData content.
var opaqueContentLength uint256.Int
opaqueContentLength.SetBytes(ev.Data[32:64])
// Make sure the length is an uint64, it's not larger than the remaining data, and the log is using minimal padding (i.e. can't add 32 bytes without exceeding data)
if !opaqueContentLength.IsUint64() || opaqueContentLength.Uint64() > uint64(len(ev.Data)-64) || opaqueContentLength.Uint64()+32 <= uint64(len(ev.Data)-64) {
return nil, fmt.Errorf("invalid opaqueData slice header length: %d", opaqueContentLength.Uint64())
}
// The remaining data is the opaqueData which is tightly packed
// and then padded to 32 bytes by the EVM.
opaqueData := ev.Data[64 : 64+opaqueContentLength.Uint64()]
var dep types.DepositTx var dep types.DepositTx
source := UserDepositSource{ source := UserDepositSource{
...@@ -48,65 +76,63 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) { ...@@ -48,65 +76,63 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) {
LogIndex: uint64(ev.Index), LogIndex: uint64(ev.Index),
} }
dep.SourceHash = source.SourceHash() dep.SourceHash = source.SourceHash()
dep.From = from
// indexed 0 var err error
dep.From = common.BytesToAddress(ev.Topics[1][12:]) switch version {
// indexed 1 case DepositEventVersion0:
to := common.BytesToAddress(ev.Topics[2][12:]) err = unmarshalDepositVersion0(&dep, to, opaqueData)
default:
return nil, fmt.Errorf("invalid deposit version, got %s", version)
}
if err != nil {
return nil, fmt.Errorf("failed to decode deposit (version %s): %w", version, err)
}
return &dep, nil
}
// unindexed data func unmarshalDepositVersion0(dep *types.DepositTx, to common.Address, opaqueData []byte) error {
if len(opaqueData) < 32+32+8+1 {
return fmt.Errorf("unexpected opaqueData length: %d", len(opaqueData))
}
offset := uint64(0) offset := uint64(0)
dep.Mint = new(big.Int).SetBytes(ev.Data[offset : offset+32]) // uint256 mint
dep.Mint = new(big.Int).SetBytes(opaqueData[offset : offset+32])
// 0 mint is represented as nil to skip minting code // 0 mint is represented as nil to skip minting code
if dep.Mint.Cmp(new(big.Int)) == 0 { if dep.Mint.Cmp(new(big.Int)) == 0 {
dep.Mint = nil dep.Mint = nil
} }
offset += 32 offset += 32
dep.Value = new(big.Int).SetBytes(ev.Data[offset : offset+32]) // uint256 value
dep.Value = new(big.Int).SetBytes(opaqueData[offset : offset+32])
offset += 32 offset += 32
gas := new(big.Int).SetBytes(ev.Data[offset : offset+32]) // uint64 gas
gas := new(big.Int).SetBytes(opaqueData[offset : offset+8])
if !gas.IsUint64() { if !gas.IsUint64() {
return nil, fmt.Errorf("bad gas value: %x", ev.Data[offset:offset+32]) return fmt.Errorf("bad gas value: %x", opaqueData[offset:offset+8])
} }
offset += 32
dep.Gas = gas.Uint64() dep.Gas = gas.Uint64()
offset += 8
// uint8 isCreation
// isCreation: If the boolean byte is 1 then dep.To will stay nil, // isCreation: If the boolean byte is 1 then dep.To will stay nil,
// and it will create a contract using L2 account nonce to determine the created address. // and it will create a contract using L2 account nonce to determine the created address.
if ev.Data[offset+31] == 0 { if opaqueData[offset] == 0 {
dep.To = &to dep.To = &to
} }
offset += 32 offset += 1
// dynamic fields are encoded in three parts. The fixed size portion is the offset of the start of the
// data. The first 32 bytes of a `bytes` object is the length of the bytes. Then are the actual bytes
// padded out to 32 byte increments.
var dataOffset uint256.Int
dataOffset.SetBytes(ev.Data[offset : offset+32])
offset += 32
if !dataOffset.Eq(uint256.NewInt(offset)) {
return nil, fmt.Errorf("incorrect data offset: %v", dataOffset[0])
}
var dataLen uint256.Int // The remainder of the opaqueData is the transaction data (without length prefix).
dataLen.SetBytes(ev.Data[offset : offset+32])
offset += 32
if !dataLen.IsUint64() {
return nil, fmt.Errorf("data too large: %s", dataLen.String())
}
// The data may be padded to a multiple of 32 bytes // The data may be padded to a multiple of 32 bytes
maxExpectedLen := uint64(len(ev.Data)) - offset txDataLen := uint64(len(opaqueData)) - offset
dataLenU64 := dataLen.Uint64()
if dataLenU64 > maxExpectedLen {
return nil, fmt.Errorf("data length too long: %d, expected max %d", dataLenU64, maxExpectedLen)
}
// remaining bytes fill the data // remaining bytes fill the data
dep.Data = ev.Data[offset : offset+dataLenU64] dep.Data = opaqueData[offset : offset+txDataLen]
return &dep, nil return nil
} }
// MarshalDepositLogEvent returns an EVM log entry that encodes a TransactionDeposited event from the deposit contract. // MarshalDepositLogEvent returns an EVM log entry that encodes a TransactionDeposited event from the deposit contract.
...@@ -120,29 +146,24 @@ func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.D ...@@ -120,29 +146,24 @@ func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.D
DepositEventABIHash, DepositEventABIHash,
deposit.From.Hash(), deposit.From.Hash(),
toBytes, toBytes,
DepositEventVersion0,
} }
data := make([]byte, 6*32) data := make([]byte, 64, 64+3*32)
offset := 0
if deposit.Mint != nil {
deposit.Mint.FillBytes(data[offset : offset+32])
}
offset += 32
deposit.Value.FillBytes(data[offset : offset+32]) // opaqueData slice content offset: value will always be 0x20.
offset += 32 binary.BigEndian.PutUint64(data[32-8:32], 32)
binary.BigEndian.PutUint64(data[offset+24:offset+32], deposit.Gas) opaqueData := marshalDepositVersion0(deposit)
offset += 32
if deposit.To == nil { // isCreation // opaqueData slice length
data[offset+31] = 1 binary.BigEndian.PutUint64(data[64-8:64], uint64(len(opaqueData)))
}
offset += 32 // opaqueData slice content
binary.BigEndian.PutUint64(data[offset+24:offset+32], 5*32) data = append(data, opaqueData...)
offset += 32
binary.BigEndian.PutUint64(data[offset+24:offset+32], uint64(len(deposit.Data))) // pad to multiple of 32
data = append(data, deposit.Data...) if len(data)%32 != 0 {
if len(data)%32 != 0 { // pad to multiple of 32
data = append(data, make([]byte, 32-(len(data)%32))...) data = append(data, make([]byte, 32-(len(data)%32))...)
} }
...@@ -160,3 +181,32 @@ func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.D ...@@ -160,3 +181,32 @@ func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.D
Index: 0, Index: 0,
} }
} }
func marshalDepositVersion0(deposit *types.DepositTx) (opaqueData []byte) {
opaqueData = make([]byte, 32+32+8+1, 32+32+8+1+len(deposit.Data))
offset := 0
// uint256 mint
if deposit.Mint != nil {
deposit.Mint.FillBytes(opaqueData[offset : offset+32])
}
offset += 32
// uint256 value
deposit.Value.FillBytes(opaqueData[offset : offset+32])
offset += 32
// uint64 gas
binary.BigEndian.PutUint64(opaqueData[offset:offset+8], deposit.Gas)
offset += 8
// uint8 isCreation
if deposit.To == nil { // isCreation
opaqueData[offset] = 1
}
// Deposit data then fills the remaining event data
opaqueData = append(opaqueData, deposit.Data...)
return opaqueData
}
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
) )
func TestUnmarshalLogEvent(t *testing.T) { func TestUnmarshalLogEvent(t *testing.T) {
// t.Skip("not working because deposit_log_create not working properly")
for i := int64(0); i < 100; i++ { for i := int64(0); i < 100; i++ {
t.Run(fmt.Sprintf("random_deposit_%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("random_deposit_%d", i), func(t *testing.T) {
rng := rand.New(rand.NewSource(1234 + i)) rng := rand.New(rand.NewSource(1234 + i))
...@@ -90,6 +91,7 @@ type DeriveUserDepositsTestCase struct { ...@@ -90,6 +91,7 @@ type DeriveUserDepositsTestCase struct {
} }
func TestDeriveUserDeposits(t *testing.T) { func TestDeriveUserDeposits(t *testing.T) {
// t.Skip("not working because deposit_log_create not working properly")
testCases := []DeriveUserDepositsTestCase{ testCases := []DeriveUserDepositsTestCase{
{"no deposits", []receiptData{}}, {"no deposits", []receiptData{}},
{"other log", []receiptData{{true, []bool{false}}}}, {"other log", []receiptData{{true, []bool{false}}}},
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
...@@ -14,6 +15,7 @@ import ( ...@@ -14,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm/runtime" "github.com/ethereum/go-ethereum/core/vm/runtime"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
) )
var ( var (
...@@ -124,6 +126,47 @@ func FuzzL1InfoAgainstContract(f *testing.F) { ...@@ -124,6 +126,47 @@ func FuzzL1InfoAgainstContract(f *testing.F) {
}) })
} }
// Standard ABI types copied from golang ABI tests
var (
Uint256Type, _ = abi.NewType("uint256", "", nil)
Uint64Type, _ = abi.NewType("uint64", "", nil)
BytesType, _ = abi.NewType("bytes", "", nil)
BoolType, _ = abi.NewType("bool", "", nil)
AddressType, _ = abi.NewType("address", "", nil)
)
// EncodeDepositOpaqueDataV0 performs ABI encoding to create the opaque data field of the deposit event.
func EncodeDepositOpaqueDataV0(t *testing.T, mint *big.Int, value *big.Int, gasLimit uint64, isCreation bool, data []byte) []byte {
// in OptimismPortal.sol:
// bytes memory opaqueData = abi.encodePacked(msg.value, _value, _gasLimit, _isCreation, _data);
// Geth does not support abi.encodePacked, so we emulate it here by slicing of the padding from the individual elements
// See https://github.com/ethereum/go-ethereum/issues/22257
// And https://docs.soliditylang.org/en/v0.8.13/abi-spec.html#non-standard-packed-mode
var out []byte
v, err := abi.Arguments{{Name: "msg.value", Type: Uint256Type}}.Pack(mint)
require.NoError(t, err)
out = append(out, v...)
v, err = abi.Arguments{{Name: "_value", Type: Uint256Type}}.Pack(value)
require.NoError(t, err)
out = append(out, v...)
v, err = abi.Arguments{{Name: "_gasLimit", Type: Uint64Type}}.Pack(gasLimit)
require.NoError(t, err)
out = append(out, v[32-8:]...) // 8 bytes only with abi.encodePacked
v, err = abi.Arguments{{Name: "_isCreation", Type: BoolType}}.Pack(isCreation)
require.NoError(t, err)
out = append(out, v[32-1:]...) // 1 byte only with abi.encodePacked
// no slice header, just the raw data with abi.encodePacked
out = append(out, data...)
return out
}
// FuzzUnmarshallLogEvent runs a deposit event through the EVM and checks that output of the abigen parsing matches // FuzzUnmarshallLogEvent runs a deposit event through the EVM and checks that output of the abigen parsing matches
// what was inputted and what we parsed during the UnmarshalDepositLogEvent function (which turns it into a deposit tx) // what was inputted and what we parsed during the UnmarshalDepositLogEvent function (which turns it into a deposit tx)
// The purpose is to check that we can never create a transaction that emits a log that we cannot parse as well // The purpose is to check that we can never create a transaction that emits a log that we cannot parse as well
...@@ -206,36 +249,33 @@ func FuzzUnmarshallLogEvent(f *testing.F) { ...@@ -206,36 +249,33 @@ func FuzzUnmarshallLogEvent(f *testing.F) {
if err != nil { if err != nil {
t.Fatalf("Could not unmarshal log that was emitted by the deposit contract: %v", err) t.Fatalf("Could not unmarshal log that was emitted by the deposit contract: %v", err)
} }
depMint := common.Big0
if dep.Mint != nil {
depMint = dep.Mint
}
opaqueData := EncodeDepositOpaqueDataV0(t, depMint, dep.Value, dep.Gas, dep.To == nil, dep.Data)
reconstructed := &bindings.OptimismPortalTransactionDeposited{ reconstructed := &bindings.OptimismPortalTransactionDeposited{
From: dep.From, From: dep.From,
Value: dep.Value, Version: common.Big0,
GasLimit: dep.Gas, OpaqueData: opaqueData,
IsCreation: dep.To == nil,
Data: dep.Data,
Raw: types.Log{}, Raw: types.Log{},
} }
if dep.To != nil { if dep.To != nil {
reconstructed.To = *dep.To reconstructed.To = *dep.To
} }
if dep.Mint != nil {
reconstructed.Mint = dep.Mint
} else {
reconstructed.Mint = common.Big0
}
if !cmp.Equal(depositEvent, reconstructed, cmp.Comparer(BigEqual)) { if !cmp.Equal(depositEvent, reconstructed, cmp.Comparer(BigEqual)) {
t.Fatalf("The deposit tx did not match. tx: %v. actual: %v", reconstructed, depositEvent) t.Fatalf("The deposit tx did not match. tx: %v. actual: %v", reconstructed, depositEvent)
} }
opaqueData = EncodeDepositOpaqueDataV0(t, mint, value, l2GasLimit, isCreation, data)
inputArgs := &bindings.OptimismPortalTransactionDeposited{ inputArgs := &bindings.OptimismPortalTransactionDeposited{
From: from, From: from,
To: to, To: to,
Mint: mint, Version: common.Big0,
Value: value, OpaqueData: opaqueData,
GasLimit: l2GasLimit,
IsCreation: isCreation,
Data: data,
Raw: types.Log{}, Raw: types.Log{},
} }
if !cmp.Equal(depositEvent, inputArgs, cmp.Comparer(BigEqual)) { if !cmp.Equal(depositEvent, inputArgs, cmp.Comparer(BigEqual)) {
......
GasBenchMark_L1CrossDomainMessenger:test_L1MessengerSendMessage_benchmark_0() (gas: 262131) GasBenchMark_L1CrossDomainMessenger:test_L1MessengerSendMessage_benchmark_0() (gas: 262086)
GasBenchMark_L1CrossDomainMessenger:test_L1MessengerSendMessage_benchmark_1() (gas: 75115) GasBenchMark_L1CrossDomainMessenger:test_L1MessengerSendMessage_benchmark_1() (gas: 76295)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 353126) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 353197)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 116130) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 117251)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 353148) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 353219)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 116105) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 117226)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 45413) GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 45413)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 68672) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 68672)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 75069) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 74967)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 35373) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 35796)
DeployerWhitelist_Test:test_owner() (gas: 7658) DeployerWhitelist_Test:test_owner() (gas: 7658)
DeployerWhitelist_Test:test_storageSlots() (gas: 33494) DeployerWhitelist_Test:test_storageSlots() (gas: 33494)
Encoding_Test:test_encodeDepositTransaction() (gas: 64610) Encoding_Test:test_encodeDepositTransaction() (gas: 64610)
...@@ -45,16 +45,16 @@ L1CrossDomainMessenger_Test:test_L1MessengerRelayMessageSucceeds() (gas: 77762) ...@@ -45,16 +45,16 @@ L1CrossDomainMessenger_Test:test_L1MessengerRelayMessageSucceeds() (gas: 77762)
L1CrossDomainMessenger_Test:test_L1MessengerRelayMessageToSystemContract() (gas: 67873) L1CrossDomainMessenger_Test:test_L1MessengerRelayMessageToSystemContract() (gas: 67873)
L1CrossDomainMessenger_Test:test_L1MessengerRelayShouldRevertIfPaused() (gas: 60471) L1CrossDomainMessenger_Test:test_L1MessengerRelayShouldRevertIfPaused() (gas: 60471)
L1CrossDomainMessenger_Test:test_L1MessengerReplayMessageWithValue() (gas: 38127) L1CrossDomainMessenger_Test:test_L1MessengerReplayMessageWithValue() (gas: 38127)
L1CrossDomainMessenger_Test:test_L1MessengerSendMessage() (gas: 297369) L1CrossDomainMessenger_Test:test_L1MessengerSendMessage() (gas: 298253)
L1CrossDomainMessenger_Test:test_L1MessengerTwiceSendMessage() (gas: 1489716) L1CrossDomainMessenger_Test:test_L1MessengerTwiceSendMessage() (gas: 1489801)
L1CrossDomainMessenger_Test:test_L1MessengerUnpause() (gas: 40908) L1CrossDomainMessenger_Test:test_L1MessengerUnpause() (gas: 40908)
L1CrossDomainMessenger_Test:test_L1MessengerXDomainSenderReverts() (gas: 24291) L1CrossDomainMessenger_Test:test_L1MessengerXDomainSenderReverts() (gas: 24291)
L1CrossDomainMessenger_Test:test_L1MessengerxDomainMessageSenderResets() (gas: 86269) L1CrossDomainMessenger_Test:test_L1MessengerxDomainMessageSenderResets() (gas: 86269)
L1StandardBridge_Test:test_depositERC20() (gas: 578701) L1StandardBridge_Test:test_depositERC20() (gas: 578715)
L1StandardBridge_Test:test_depositERC20To() (gas: 580882) L1StandardBridge_Test:test_depositERC20To() (gas: 580896)
L1StandardBridge_Test:test_depositETH() (gas: 372623) L1StandardBridge_Test:test_depositETH() (gas: 372578)
L1StandardBridge_Test:test_depositETHTo() (gas: 329767) L1StandardBridge_Test:test_depositETHTo() (gas: 329722)
L1StandardBridge_Test:test_finalizeBridgeERC20FailSendBack() (gas: 681188) L1StandardBridge_Test:test_finalizeBridgeERC20FailSendBack() (gas: 681200)
L1StandardBridge_Test:test_finalizeERC20Withdrawal() (gas: 490817) L1StandardBridge_Test:test_finalizeERC20Withdrawal() (gas: 490817)
L1StandardBridge_Test:test_finalizeETHWithdrawal() (gas: 64453) L1StandardBridge_Test:test_finalizeETHWithdrawal() (gas: 64453)
L1StandardBridge_Test:test_initialize() (gas: 26401) L1StandardBridge_Test:test_initialize() (gas: 26401)
...@@ -62,7 +62,7 @@ L1StandardBridge_Test:test_onlyEOADepositERC20() (gas: 22377) ...@@ -62,7 +62,7 @@ L1StandardBridge_Test:test_onlyEOADepositERC20() (gas: 22377)
L1StandardBridge_Test:test_onlyEOADepositETH() (gas: 40918) L1StandardBridge_Test:test_onlyEOADepositETH() (gas: 40918)
L1StandardBridge_Test:test_onlyL2BridgeFinalizeERC20Withdrawal() (gas: 36330) L1StandardBridge_Test:test_onlyL2BridgeFinalizeERC20Withdrawal() (gas: 36330)
L1StandardBridge_Test:test_onlyPortalFinalizeERC20Withdrawal() (gas: 35614) L1StandardBridge_Test:test_onlyPortalFinalizeERC20Withdrawal() (gas: 35614)
L1StandardBridge_Test:test_receive() (gas: 519340) L1StandardBridge_Test:test_receive() (gas: 519411)
L2CrossDomainMessenger_Test:testCannot_L2MessengerPause() (gas: 10823) L2CrossDomainMessenger_Test:testCannot_L2MessengerPause() (gas: 10823)
L2CrossDomainMessenger_Test:test_L1MessengerRelayMessageRevertsOnReentrancy() (gas: 171968) L2CrossDomainMessenger_Test:test_L1MessengerRelayMessageRevertsOnReentrancy() (gas: 171968)
L2CrossDomainMessenger_Test:test_L2MessengerMessageVersion() (gas: 8455) L2CrossDomainMessenger_Test:test_L2MessengerMessageVersion() (gas: 8455)
...@@ -139,16 +139,16 @@ OptimismPortalUpgradeable_Test:test_initValuesOnProxy() (gas: 15990) ...@@ -139,16 +139,16 @@ OptimismPortalUpgradeable_Test:test_initValuesOnProxy() (gas: 15990)
OptimismPortalUpgradeable_Test:test_upgrading() (gas: 230843) OptimismPortalUpgradeable_Test:test_upgrading() (gas: 230843)
OptimismPortal_Test:test_OptimismPortalConstructor() (gas: 17319) OptimismPortal_Test:test_OptimismPortalConstructor() (gas: 17319)
OptimismPortal_Test:test_OptimismPortalContractCreationReverts() (gas: 14238) OptimismPortal_Test:test_OptimismPortalContractCreationReverts() (gas: 14238)
OptimismPortal_Test:test_OptimismPortalReceiveEth() (gas: 126614) OptimismPortal_Test:test_OptimismPortalReceiveEth() (gas: 127503)
OptimismPortal_Test:test_cannotVerifyRecentWithdrawal() (gas: 31925) OptimismPortal_Test:test_cannotVerifyRecentWithdrawal() (gas: 31925)
OptimismPortal_Test:test_depositTransaction_NoValueContract() (gas: 75820) OptimismPortal_Test:test_depositTransaction_NoValueContract() (gas: 76677)
OptimismPortal_Test:test_depositTransaction_NoValueEOA() (gas: 76099) OptimismPortal_Test:test_depositTransaction_NoValueEOA() (gas: 77131)
OptimismPortal_Test:test_depositTransaction_createWithZeroValueForContract() (gas: 75825) OptimismPortal_Test:test_depositTransaction_createWithZeroValueForContract() (gas: 76682)
OptimismPortal_Test:test_depositTransaction_createWithZeroValueForEOA() (gas: 76146) OptimismPortal_Test:test_depositTransaction_createWithZeroValueForEOA() (gas: 77003)
OptimismPortal_Test:test_depositTransaction_withEthValueAndContractContractCreation() (gas: 82846) OptimismPortal_Test:test_depositTransaction_withEthValueAndContractContractCreation() (gas: 83703)
OptimismPortal_Test:test_depositTransaction_withEthValueAndEOAContractCreation() (gas: 75007) OptimismPortal_Test:test_depositTransaction_withEthValueAndEOAContractCreation() (gas: 75868)
OptimismPortal_Test:test_depositTransaction_withEthValueFromContract() (gas: 82550) OptimismPortal_Test:test_depositTransaction_withEthValueFromContract() (gas: 83407)
OptimismPortal_Test:test_depositTransaction_withEthValueFromEOA() (gas: 83145) OptimismPortal_Test:test_depositTransaction_withEthValueFromEOA() (gas: 84177)
OptimismPortal_Test:test_invalidWithdrawalProof() (gas: 45154) OptimismPortal_Test:test_invalidWithdrawalProof() (gas: 45154)
OptimismPortal_Test:test_isOutputFinalized() (gas: 132228) OptimismPortal_Test:test_isOutputFinalized() (gas: 132228)
OptimismPortal_Test:test_simple_isOutputFinalized() (gas: 24021) OptimismPortal_Test:test_simple_isOutputFinalized() (gas: 24021)
......
...@@ -18,26 +18,25 @@ import { Semver } from "../universal/Semver.sol"; ...@@ -18,26 +18,25 @@ import { Semver } from "../universal/Semver.sol";
* Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface. * Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface.
*/ */
contract OptimismPortal is Initializable, ResourceMetering, Semver { contract OptimismPortal is Initializable, ResourceMetering, Semver {
/**
* @notice Version of the deposit event.
*/
uint256 internal constant DEPOSIT_VERSION = 0;
/** /**
* @notice Emitted when a transaction is deposited from L1 to L2. The parameters of this event * @notice Emitted when a transaction is deposited from L1 to L2. The parameters of this event
* are read by the rollup node and used to derive deposit transactions on L2. * are read by the rollup node and used to derive deposit transactions on L2.
* *
* @param from Address that triggered the deposit transaction. * @param from Address that triggered the deposit transaction.
* @param to Address that the deposit transaction is directed to. * @param to Address that the deposit transaction is directed to.
* @param mint Amount of ETH to mint to the sender on L2. * @param version Version of this deposit transaction event.
* @param value Amount of ETH to send to the recipient. * @param opaqueData ABI encoded deposit data to be parsed off-chain.
* @param gasLimit Minimum gas limit that the message can be executed with.
* @param isCreation Whether the message is a contract creation.
* @param data Data to attach to the message and call the recipient with.
*/ */
event TransactionDeposited( event TransactionDeposited(
address indexed from, address indexed from,
address indexed to, address indexed to,
uint256 mint, uint256 indexed version,
uint256 value, bytes opaqueData
uint64 gasLimit,
bool isCreation,
bytes data
); );
/** /**
...@@ -156,9 +155,17 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -156,9 +155,17 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
from = AddressAliasHelper.applyL1ToL2Alias(msg.sender); from = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
} }
bytes memory opaqueData = abi.encodePacked(
msg.value,
_value,
_gasLimit,
_isCreation,
_data
);
// Emit a TransactionDeposited event so that the rollup node can derive a deposit // Emit a TransactionDeposited event so that the rollup node can derive a deposit
// transaction for this deposit. // transaction for this deposit.
emit TransactionDeposited(from, _to, msg.value, _value, _gasLimit, _isCreation, _data); emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData);
} }
/** /**
......
...@@ -37,6 +37,30 @@ contract CommonTest is Test { ...@@ -37,6 +37,30 @@ contract CommonTest is Test {
bytes32 nonZeroHash = keccak256(abi.encode("NON_ZERO")); bytes32 nonZeroHash = keccak256(abi.encode("NON_ZERO"));
bytes NON_ZERO_DATA = hex"0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff0000"; bytes NON_ZERO_DATA = hex"0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff0000";
event TransactionDeposited(
address indexed from,
address indexed to,
uint256 indexed version,
bytes opaqueData
);
function emitTransactionDeposited(
address _from,
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
) internal {
emit TransactionDeposited(
_from,
_to,
0,
abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data)
);
}
function _setUp() public { function _setUp() public {
// Give alice and bob some ETH // Give alice and bob some ETH
vm.deal(alice, 1 << 16); vm.deal(alice, 1 << 16);
......
...@@ -86,7 +86,7 @@ contract L1CrossDomainMessenger_Test is Messenger_Initializer { ...@@ -86,7 +86,7 @@ contract L1CrossDomainMessenger_Test is Messenger_Initializer {
// TransactionDeposited event // TransactionDeposited event
vm.expectEmit(true, true, true, true); vm.expectEmit(true, true, true, true);
emit TransactionDeposited( emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(L1Messenger)), AddressAliasHelper.applyL1ToL2Alias(address(L1Messenger)),
PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER, PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER,
0, 0,
......
...@@ -10,15 +10,6 @@ import { Hashing } from "../libraries/Hashing.sol"; ...@@ -10,15 +10,6 @@ import { Hashing } from "../libraries/Hashing.sol";
import { Proxy } from "../universal/Proxy.sol"; import { Proxy } from "../universal/Proxy.sol";
contract OptimismPortal_Test is Portal_Initializer { contract OptimismPortal_Test is Portal_Initializer {
event TransactionDeposited(
address indexed from,
address indexed to,
uint256 mint,
uint256 value,
uint64 gasLimit,
bool isCreation,
bytes data
);
function test_OptimismPortalConstructor() external { function test_OptimismPortalConstructor() external {
assertEq(op.FINALIZATION_PERIOD_SECONDS(), 7 days); assertEq(op.FINALIZATION_PERIOD_SECONDS(), 7 days);
...@@ -28,7 +19,7 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -28,7 +19,7 @@ contract OptimismPortal_Test is Portal_Initializer {
function test_OptimismPortalReceiveEth() external { function test_OptimismPortalReceiveEth() external {
vm.expectEmit(true, true, false, true); vm.expectEmit(true, true, false, true);
emit TransactionDeposited(alice, alice, 100, 100, 100_000, false, hex""); emitTransactionDeposited(alice, alice, 100, 100, 100_000, false, hex"");
// give alice money and send as an eoa // give alice money and send as an eoa
vm.deal(alice, 2**64); vm.deal(alice, 2**64);
...@@ -53,7 +44,7 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -53,7 +44,7 @@ contract OptimismPortal_Test is Portal_Initializer {
// EOA emulation // EOA emulation
vm.prank(address(this), address(this)); vm.prank(address(this), address(this));
vm.expectEmit(true, true, false, true); vm.expectEmit(true, true, false, true);
emit TransactionDeposited( emitTransactionDeposited(
address(this), address(this),
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
ZERO_VALUE, ZERO_VALUE,
...@@ -75,7 +66,7 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -75,7 +66,7 @@ contract OptimismPortal_Test is Portal_Initializer {
// Test: depositTransaction should emit the correct log when a contract deposits a tx with 0 value // Test: depositTransaction should emit the correct log when a contract deposits a tx with 0 value
function test_depositTransaction_NoValueContract() external { function test_depositTransaction_NoValueContract() external {
vm.expectEmit(true, true, false, true); vm.expectEmit(true, true, false, true);
emit TransactionDeposited( emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(this)), AddressAliasHelper.applyL1ToL2Alias(address(this)),
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
ZERO_VALUE, ZERO_VALUE,
...@@ -100,7 +91,7 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -100,7 +91,7 @@ contract OptimismPortal_Test is Portal_Initializer {
vm.prank(address(this), address(this)); vm.prank(address(this), address(this));
vm.expectEmit(true, true, false, true); vm.expectEmit(true, true, false, true);
emit TransactionDeposited( emitTransactionDeposited(
address(this), address(this),
ZERO_ADDRESS, ZERO_ADDRESS,
ZERO_VALUE, ZERO_VALUE,
...@@ -116,7 +107,7 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -116,7 +107,7 @@ contract OptimismPortal_Test is Portal_Initializer {
// Test: depositTransaction should emit the correct log when a contract deposits a contract creation with 0 value // Test: depositTransaction should emit the correct log when a contract deposits a contract creation with 0 value
function test_depositTransaction_createWithZeroValueForContract() external { function test_depositTransaction_createWithZeroValueForContract() external {
vm.expectEmit(true, true, false, true); vm.expectEmit(true, true, false, true);
emit TransactionDeposited( emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(this)), AddressAliasHelper.applyL1ToL2Alias(address(this)),
ZERO_ADDRESS, ZERO_ADDRESS,
ZERO_VALUE, ZERO_VALUE,
...@@ -135,7 +126,7 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -135,7 +126,7 @@ contract OptimismPortal_Test is Portal_Initializer {
vm.prank(address(this), address(this)); vm.prank(address(this), address(this));
vm.expectEmit(true, true, false, true); vm.expectEmit(true, true, false, true);
emit TransactionDeposited( emitTransactionDeposited(
address(this), address(this),
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
NON_ZERO_VALUE, NON_ZERO_VALUE,
...@@ -158,7 +149,7 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -158,7 +149,7 @@ contract OptimismPortal_Test is Portal_Initializer {
// Test: depositTransaction should increase its eth balance when a contract deposits a transaction with ETH // Test: depositTransaction should increase its eth balance when a contract deposits a transaction with ETH
function test_depositTransaction_withEthValueFromContract() external { function test_depositTransaction_withEthValueFromContract() external {
vm.expectEmit(true, true, false, true); vm.expectEmit(true, true, false, true);
emit TransactionDeposited( emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(this)), AddressAliasHelper.applyL1ToL2Alias(address(this)),
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
NON_ZERO_VALUE, NON_ZERO_VALUE,
...@@ -183,7 +174,7 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -183,7 +174,7 @@ contract OptimismPortal_Test is Portal_Initializer {
vm.prank(address(this), address(this)); vm.prank(address(this), address(this));
vm.expectEmit(true, true, false, true); vm.expectEmit(true, true, false, true);
emit TransactionDeposited( emitTransactionDeposited(
address(this), address(this),
ZERO_ADDRESS, ZERO_ADDRESS,
NON_ZERO_VALUE, NON_ZERO_VALUE,
...@@ -206,7 +197,7 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -206,7 +197,7 @@ contract OptimismPortal_Test is Portal_Initializer {
// Test: depositTransaction should increase its eth balance when a contract deposits a contract creation with ETH // Test: depositTransaction should increase its eth balance when a contract deposits a contract creation with ETH
function test_depositTransaction_withEthValueAndContractContractCreation() external { function test_depositTransaction_withEthValueAndContractContractCreation() external {
vm.expectEmit(true, true, false, true); vm.expectEmit(true, true, false, true);
emit TransactionDeposited( emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(this)), AddressAliasHelper.applyL1ToL2Alias(address(this)),
ZERO_ADDRESS, ZERO_ADDRESS,
NON_ZERO_VALUE, NON_ZERO_VALUE,
......
...@@ -5,10 +5,13 @@ import { ...@@ -5,10 +5,13 @@ import {
ContractReceipt, ContractReceipt,
ethers, ethers,
Event, Event,
utils,
} from 'ethers' } from 'ethers'
const { hexDataSlice, stripZeros, hexConcat, keccak256, zeroPad } = utils
const formatNumber = (value: BigNumberish, name: string): Uint8Array => { const formatNumber = (value: BigNumberish, name: string): Uint8Array => {
const result = ethers.utils.stripZeros(BigNumber.from(value).toHexString()) const result = stripZeros(BigNumber.from(value).toHexString())
if (result.length > 32) { if (result.length > 32) {
throw new Error(`invalid length for ${name}`) throw new Error(`invalid length for ${name}`)
} }
...@@ -27,7 +30,7 @@ const handleAddress = (value: string): string => { ...@@ -27,7 +30,7 @@ const handleAddress = (value: string): string => {
// @ts-ignore // @ts-ignore
return null return null
} }
return ethers.utils.getAddress(value) return utils.getAddress(value)
} }
export enum SourceHashDomain { export enum SourceHashDomain {
...@@ -88,7 +91,7 @@ export class DepositTx { ...@@ -88,7 +91,7 @@ export class DepositTx {
hash() { hash() {
const encoded = this.encode() const encoded = this.encode()
return ethers.utils.keccak256(encoded) return keccak256(encoded)
} }
sourceHash() { sourceHash() {
...@@ -110,17 +113,11 @@ export class DepositTx { ...@@ -110,17 +113,11 @@ export class DepositTx {
} }
const l1BlockHash = this.l1BlockHash const l1BlockHash = this.l1BlockHash
const input = ethers.utils.hexConcat([ const input = hexConcat([l1BlockHash, zeroPad(marker, 32)])
l1BlockHash, const depositIDHash = keccak256(input)
ethers.utils.zeroPad(marker, 32),
])
const depositIDHash = ethers.utils.keccak256(input)
const domain = BigNumber.from(this.domain).toHexString() const domain = BigNumber.from(this.domain).toHexString()
const domainInput = ethers.utils.hexConcat([ const domainInput = hexConcat([zeroPad(domain, 32), depositIDHash])
ethers.utils.zeroPad(domain, 32), this._sourceHash = keccak256(domainInput)
depositIDHash,
])
this._sourceHash = ethers.utils.keccak256(domainInput)
} }
return this._sourceHash return this._sourceHash
} }
...@@ -128,29 +125,29 @@ export class DepositTx { ...@@ -128,29 +125,29 @@ export class DepositTx {
encode() { encode() {
const fields: any = [ const fields: any = [
this.sourceHash() || '0x', this.sourceHash() || '0x',
ethers.utils.getAddress(this.from) || '0x', utils.getAddress(this.from) || '0x',
this.to != null ? ethers.utils.getAddress(this.to) : '0x', this.to != null ? utils.getAddress(this.to) : '0x',
formatNumber(this.mint || 0, 'mint'), formatNumber(this.mint || 0, 'mint'),
formatNumber(this.value || 0, 'value'), formatNumber(this.value || 0, 'value'),
formatNumber(this.gas || 0, 'gas'), formatNumber(this.gas || 0, 'gas'),
this.data || '0x', this.data || '0x',
] ]
return ethers.utils.hexConcat([ return hexConcat([
BigNumber.from(this.type).toHexString(), BigNumber.from(this.type).toHexString(),
BigNumber.from(this.version).toHexString(), BigNumber.from(this.version).toHexString(),
ethers.utils.RLP.encode(fields), utils.RLP.encode(fields),
]) ])
} }
decode(raw: BytesLike, extra: DepositTxExtraOpts = {}) { decode(raw: BytesLike, extra: DepositTxExtraOpts = {}) {
const payload = ethers.utils.arrayify(raw) const payload = utils.arrayify(raw)
if (payload[0] !== this.type) { if (payload[0] !== this.type) {
throw new Error(`Invalid type ${payload[0]}`) throw new Error(`Invalid type ${payload[0]}`)
} }
this.version = payload[1] this.version = payload[1]
const transaction = ethers.utils.RLP.decode(payload.slice(2)) const transaction = utils.RLP.decode(payload.slice(2))
this._sourceHash = transaction[0] this._sourceHash = transaction[0]
this.from = handleAddress(transaction[1]) this.from = handleAddress(transaction[1])
this.to = handleAddress(transaction[2]) this.to = handleAddress(transaction[2])
...@@ -204,29 +201,36 @@ export class DepositTx { ...@@ -204,29 +201,36 @@ export class DepositTx {
throw new Error('"from" undefined') throw new Error('"from" undefined')
} }
this.from = event.args.from this.from = event.args.from
if (typeof event.args.isCreation === 'undefined') {
throw new Error('"isCreation" undefined')
}
if (typeof event.args.to === 'undefined') { if (typeof event.args.to === 'undefined') {
throw new Error('"to" undefined') throw new Error('"to" undefined')
} }
this.to = event.args.isCreation ? null : event.args.to if (typeof event.args.version === 'undefined') {
if (typeof event.args.mint === 'undefined') { throw new Error(`"verison" undefined`)
throw new Error('"mint" undefined')
} }
this.mint = event.args.mint if (!event.args.version.eq(0)) {
if (typeof event.args.value === 'undefined') { throw new Error(`Unsupported version ${event.args.version.toString()}`)
throw new Error('"value" undefined')
} }
this.value = event.args.value if (typeof event.args.opaqueData === 'undefined') {
if (typeof event.args.gasLimit === 'undefined') { throw new Error(`"opaqueData" undefined`)
throw new Error('"gasLimit" undefined')
} }
this.gas = event.args.gasLimit
if (typeof event.args.data === 'undefined') { const opaqueData = event.args.opaqueData
throw new Error('"data" undefined') if (opaqueData.length < 32 + 32 + 8 + 1) {
throw new Error(`invalid opaqueData size: ${opaqueData.length}`)
} }
this.data = event.args.data
let offset = 0
this.mint = BigNumber.from(hexDataSlice(opaqueData, offset, offset + 32))
offset += 32
this.value = BigNumber.from(hexDataSlice(opaqueData, offset, offset + 32))
offset += 32
this.gas = BigNumber.from(hexDataSlice(opaqueData, offset, offset + 8))
offset += 8
const isCreation = BigNumber.from(opaqueData[offset]).eq(1)
offset += 1
this.to = isCreation === true ? null : event.args.to
const length = opaqueData.length - offset
this.data = hexDataSlice(opaqueData, offset, offset + length)
this.domain = SourceHashDomain.UserDeposit this.domain = SourceHashDomain.UserDeposit
this.l1BlockHash = event.blockHash this.l1BlockHash = event.blockHash
this.logIndex = event.logIndex this.logIndex = event.logIndex
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment