Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved deposits #305

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions proto/rollup/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
// Msg defines all tx endpoints for the x/rollup module.
service Msg {
option (cosmos.msg.v1.service) = true;
// ApplyL1Txs defines a method for applying applying all L1 system and user deposit txs.
rpc ApplyL1Txs(MsgApplyL1Txs) returns (MsgApplyL1TxsResponse);
rpc SetL1Attributes(MsgSetL1Attributes) returns (MsgSetL1AttributesResponse);

Check failure on line 16 in proto/rollup/v1/tx.proto

View workflow job for this annotation

GitHub Actions / proto

RPC "SetL1Attributes" should have a non-empty comment for documentation.

rpc Mint(MsgMint) returns (MsgMintResponse);

Check failure on line 18 in proto/rollup/v1/tx.proto

View workflow job for this annotation

GitHub Actions / proto

RPC "Mint" should have a non-empty comment for documentation.

rpc ForceInclude(MsgForceInclude) returns (MsgForceIncludeResponse);

Check failure on line 20 in proto/rollup/v1/tx.proto

View workflow job for this annotation

GitHub Actions / proto

RPC "ForceInclude" should have a non-empty comment for documentation.

// InitiateWithdrawal defines a method for initiating a withdrawal from L2 to L1.
rpc InitiateWithdrawal(MsgInitiateWithdrawal) returns (MsgInitiateWithdrawalResponse);
Expand All @@ -26,15 +29,31 @@
rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse);
}

// MsgApplyL1Txs defines the message for applying all L1 system and user deposit txs.
message MsgApplyL1Txs {
// Array of bytes where each bytes is a eth.Transaction.MarshalBinary tx.
// The first tx must be the L1 system deposit tx, and the rest are user txs if present.
repeated bytes tx_bytes = 1;
message MsgSetL1Attributes {

Check failure on line 32 in proto/rollup/v1/tx.proto

View workflow job for this annotation

GitHub Actions / proto

Message "MsgSetL1Attributes" should have a non-empty comment for documentation.
L1BlockInfo l1_block_info = 1;
}

message MsgSetL1AttributesResponse {}

Check failure on line 36 in proto/rollup/v1/tx.proto

View workflow job for this annotation

GitHub Actions / proto

Message "MsgSetL1AttributesResponse" should have a non-empty comment for documentation.

message MsgMint {

Check failure on line 38 in proto/rollup/v1/tx.proto

View workflow job for this annotation

GitHub Actions / proto

Message "MsgMint" should have a non-empty comment for documentation.
uint64 amount = 1;
bytes to = 2;
}

message MsgMintResponse {}

Check failure on line 43 in proto/rollup/v1/tx.proto

View workflow job for this annotation

GitHub Actions / proto

Message "MsgMintResponse" should have a non-empty comment for documentation.

message MsgForceInclude {

Check failure on line 45 in proto/rollup/v1/tx.proto

View workflow job for this annotation

GitHub Actions / proto

Message "MsgForceInclude" should have a non-empty comment for documentation.
bytes from = 1;
// to is null when is_creation is true.
// https://specs.optimism.io/protocol/deposits.html#user-deposited-transactions
bytes to = 2 [(gogoproto.nullable) = true];
uint64 value = 3;
uint64 gas_limit = 4;
bool is_creation = 5;
bytes data = 6;
}

// MsgApplyL1TxsResponse defines the Msg/ApplyL1Txs response type.
message MsgApplyL1TxsResponse {}
message MsgForceIncludeResponse {}

Check failure on line 56 in proto/rollup/v1/tx.proto

View workflow job for this annotation

GitHub Actions / proto

Message "MsgForceIncludeResponse" should have a non-empty comment for documentation.

// MsgInitiateWithdrawal defines the message for initiating an L2 withdrawal.
message MsgInitiateWithdrawal {
Expand Down
4 changes: 2 additions & 2 deletions x/rollup/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (k *Keeper) GetL1BlockInfo(ctx sdk.Context) (*types.L1BlockInfo, error) { /
//
// Persisted data conforms to optimism specs on L1 attributes:
// https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#l1-attributes-predeployed-contract
func (k *Keeper) SetL1BlockInfo(ctx sdk.Context, info types.L1BlockInfo) error { //nolint:gocritic
func (k *Keeper) SetL1BlockInfo(ctx sdk.Context, info *types.L1BlockInfo) error { //nolint:gocritic
infoBytes, err := info.Marshal()
if err != nil {
return types.WrapError(err, "marshal L1 block info")
Expand All @@ -79,7 +79,7 @@ func (k *Keeper) SetL1BlockInfo(ctx sdk.Context, info types.L1BlockInfo) error {
return nil
}

func (k *Keeper) GetParams(ctx sdk.Context) (*types.Params, error) { //nolint:gocritic // hugeParam
func (k *Keeper) GetParams(ctx context.Context) (*types.Params, error) { //nolint:gocritic // hugeParam
paramsBz, err := k.storeService.OpenKVStore(ctx).Get([]byte(types.ParamsKey))
if err != nil {
return nil, fmt.Errorf("get params: %w", err)
Expand Down
149 changes: 149 additions & 0 deletions x/rollup/keeper/msg_server.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,168 @@
package keeper

import (
"bytes"
"context"
"fmt"
"math/big"
"strings"

"cosmossdk.io/math"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
opbindings "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/polymerdao/monomer"
"github.com/polymerdao/monomer/bindings"
"github.com/polymerdao/monomer/x/rollup/types"
"github.com/samber/lo"
)

var _ types.MsgServer = &Keeper{}

func (k *Keeper) SetL1Attributes(ctx context.Context, msg *types.MsgSetL1Attributes) (*types.MsgSetL1AttributesResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
if err := k.SetL1BlockInfo(sdkCtx, msg.L1BlockInfo); err != nil {
return nil, fmt.Errorf("set l1 block info: %v", err)
}
return &types.MsgSetL1AttributesResponse{}, nil
}

// TODO mint needs to always succeed... perhaps we can set something in the MintResponse and panic in the builder?
func (k *Keeper) Mint(ctx context.Context, msg *types.MsgMint) (*types.MsgMintResponse, error) {
to, err := ethAddressBytesToCosmosAddr(msg.To)
if err != nil {
return nil, err
}
if err := k.bankkeeper.SendCoinsFromModuleToAccount(
ctx,
types.ModuleName,
to,
sdk.NewCoins(sdk.NewCoin(types.WEI, math.NewInt(int64(msg.Amount)))), // TODO is the cast a problem?
); err != nil {
return nil, fmt.Errorf("send coins from module to account: %v", err)
}
// TODO emit Mint event
return &types.MsgMintResponse{}, nil
}

func (k *Keeper) ForceInclude(ctx context.Context, msg *types.MsgForceInclude) (*types.MsgForceIncludeResponse, error) {
from, err := ethAddressBytesToCosmosAddr(msg.From)
if err != nil {
return nil, err
}
to, err := ethAddressBytesToCosmosAddr(msg.To)
if err != nil {
return nil, err
}
if msg.Value > 0 {
coins := sdk.NewCoins(sdk.NewCoin(types.WEI, math.NewInt(int64(msg.Value)))) // TODO is the cast a problem?
if err := k.bankkeeper.SendCoinsFromAccountToModule(
ctx,
from,
types.ModuleName,
coins,
); err != nil {
return nil, fmt.Errorf("send coins from account to module: %v", err)
}
if err := k.bankkeeper.SendCoinsFromModuleToAccount(
ctx,
types.ModuleName,
to,
coins,
); err != nil {
return nil, fmt.Errorf("send coins from module to account: %v", err)
}
// TODO emit transfer event
}
// TODO check IsCreation, fail if true

// process crossdomainmessage

params, err := k.GetParams(ctx)
if err != nil {
return nil, types.WrapError(types.ErrParams, "failed to get params: %v", err)
}

// Convert the L1CrossDomainMessenger address to its L2 aliased address
aliasedL1CrossDomainMessengerAddress := crossdomain.ApplyL1ToL2Alias(common.HexToAddress(params.L1CrossDomainMessenger))
// Check if the tx is a cross domain message from the aliased L1CrossDomainMessenger address
if common.Address(msg.From) == aliasedL1CrossDomainMessengerAddress && msg.Data != nil {
// TODO: Investigate when to return an error if a cross domain message can't be parsed or executed - look at OP Spec

crossDomainMessengerABI, err := abi.JSON(strings.NewReader(opbindings.CrossDomainMessengerMetaData.ABI))
if err != nil {
return nil, fmt.Errorf("failed to parse CrossDomainMessenger ABI: %v", err)
}
standardBridgeABI, err := abi.JSON(strings.NewReader(opbindings.StandardBridgeMetaData.ABI))
if err != nil {
return nil, fmt.Errorf("failed to parse StandardBridge ABI: %v", err)
}

var relayMessage bindings.RelayMessageArgs
if err = unpackInputsIntoInterface(&crossDomainMessengerABI, "relayMessage", msg.Data, &relayMessage); err != nil {
return nil, fmt.Errorf("failed to unpack tx data into relayMessage interface: %v", err)
}

// Check if the relayed message is a finalizeBridgeERC20 message from the L1StandardBridge
if !bytes.Equal(relayMessage.Message[:4], standardBridgeABI.Methods["finalizeBridgeERC20"].ID) {
return nil, fmt.Errorf("tx data not recognized as a cross domain message: %v", msg.Data)
}

var finalizeBridgeERC20 bindings.FinalizeBridgeERC20Args
if err = unpackInputsIntoInterface(
&standardBridgeABI,
"finalizeBridgeERC20",
relayMessage.Message,
&finalizeBridgeERC20,
); err != nil {
return nil, fmt.Errorf("failed to unpack relay message into finalizeBridgeERC20 interface: %v", err)
}

// TODO properly process toAddr
toAddr, err := monomer.CosmosETHAddress(finalizeBridgeERC20.To).Encode(sdk.GetConfig().GetBech32AccountAddrPrefix())
if err != nil {
return nil, fmt.Errorf("evm to cosmos address: %v", err)
}
// Mint the ERC-20 token to the specified Cosmos address
erc20Addr := finalizeBridgeERC20.RemoteToken.String()
coin := sdk.NewCoin("erc20/"+erc20Addr[2:], sdkmath.NewIntFromBigInt(finalizeBridgeERC20.Amount))
coins := sdk.NewCoins(coin)
if err := k.bankkeeper.MintCoins(ctx, types.ModuleName, coins); err != nil {
return nil, fmt.Errorf("mint coins: %v", err)
}
if err := k.bankkeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, toAddr, coins); err != nil {
return nil, fmt.Errorf("send coins from module to account: %v", err)
}

mintEvents = append(mintEvents, sdk.NewEvent(
types.EventTypeMintERC20,
sdk.NewAttribute(types.AttributeKeyL1DepositTxType, types.L1UserDepositTxType),
sdk.NewAttribute(types.AttributeKeyToCosmosAddress, to.String()),
sdk.NewAttribute(types.AttributeKeyERC20Address, erc20Addr),
sdk.NewAttribute(types.AttributeKeyValue, hexutil.EncodeBig(coin.Amount.BigInt())),
))
}

return &types.MsgForceIncludeResponse{}, nil
}

func ethAddressBytesToCosmosAddr(ethAddr []byte) (sdk.AccAddress, error) {
cosmosAddrStr, err := monomer.CosmosETHAddress(ethAddr).Encode(sdk.GetConfig().GetBech32AccountAddrPrefix())
if err != nil {
return nil, fmt.Errorf("encode eth address to Cosmos bech32: %v", err)
}
cosmosAddr, err := sdk.AccAddressFromBech32(cosmosAddrStr)
if err != nil {
return nil, fmt.Errorf("account address from bech32: %v", err)
}
return cosmosAddr, nil
}

// ApplyL1Txs implements types.MsgServer.
func (k *Keeper) ApplyL1Txs(goCtx context.Context, msg *types.MsgApplyL1Txs) (*types.MsgApplyL1TxsResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
Expand Down
Loading
Loading