Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
74 changes: 6 additions & 68 deletions cmd/ulxly/balanceandnullifiertreehelper.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package ulxly

import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
"strings"

"github.com/0xPolygon/cdk-rpc/types"
ulxlycommon "github.com/0xPolygon/polygon-cli/cmd/ulxly/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/golang-collections/collections/stack"
"github.com/rs/zerolog/log"
)

Expand All @@ -26,7 +23,7 @@ func (t *TokenInfo) ToBits() []bool {
bits := make([]bool, 192)

// First 32 bits: OriginNetwork
for i := 0; i < 32; i++ {
for i := range 32 {
if t.OriginNetwork.Bit(i) == 1 {
bits[i] = true
}
Expand Down Expand Up @@ -77,14 +74,14 @@ func (n *NullifierKey) ToBits() []bool {
bits := make([]bool, 64)

// First 32 bits: NetworkID
for i := 0; i < 32; i++ {
for i := range 32 {
if (n.NetworkID>>i)&1 == 1 {
bits[i] = true
}
}

// Next 32 bits: Index
for i := 0; i < 32; i++ {
for i := range 32 {
if (n.Index>>i)&1 == 1 {
bits[i+32] = true
}
Expand All @@ -105,7 +102,7 @@ type Balancer struct {

func NewBalanceTree() (*Balancer, error) {
var depth uint8 = 192
zeroHashes := generateZeroHashes(depth)
zeroHashes := ulxlycommon.GenerateZeroHashes(depth)
initRoot := crypto.Keccak256Hash(zeroHashes[depth-1].Bytes(), zeroHashes[depth-1].Bytes())
log.Info().Msg("Initial Root: " + initRoot.String())
return &Balancer{
Expand Down Expand Up @@ -135,7 +132,7 @@ type Nullifier struct {

func NewNullifierTree() (*Nullifier, error) {
var depth uint8 = 64
zeroHashes := generateZeroHashes(depth)
zeroHashes := ulxlycommon.GenerateZeroHashes(depth)
initRoot := crypto.Keccak256Hash(zeroHashes[depth-1].Bytes(), zeroHashes[depth-1].Bytes())
log.Info().Msg("Initial Root: " + initRoot.String())
return &Nullifier{
Expand Down Expand Up @@ -232,65 +229,6 @@ func (t *Tree) insertHelper(
return newHash, nil
}

var methodIDClaimMessage = common.Hex2Bytes("f5efcd79")

func IsMessageClaim(input []byte) (bool, error) {
methodID := input[:4]
// Ignore ClaimAsset method
if bytes.Equal(methodID, methodIDClaimMessage) {
return true, nil
} else {
return false, nil
}
}

type call struct {
To common.Address `json:"to"`
Value *types.ArgBig `json:"value"`
Err *string `json:"error"`
Input types.ArgBytes `json:"input"`
Calls []call `json:"calls"`
}

type tracerCfg struct {
Tracer string `json:"tracer"`
}

func checkClaimCalldata(client *ethclient.Client, bridge common.Address, claimHash common.Hash) (bool, error) {
c := &call{}
err := client.Client().Call(c, "debug_traceTransaction", claimHash, tracerCfg{Tracer: "callTracer"})
if err != nil {
return false, err
}

// find the claim linked to the event using DFS
callStack := stack.New()
callStack.Push(*c)
for {
if callStack.Len() == 0 {
break
}

currentCallInterface := callStack.Pop()
currentCall, ok := currentCallInterface.(call)
if !ok {
return false, fmt.Errorf("unexpected type for 'currentCall'. Expected 'call', got '%T'", currentCallInterface)
}

if currentCall.To == bridge {
isMessage, err := IsMessageClaim(currentCall.Input)
if err != nil {
return false, err
}
return isMessage, err
}
for _, c := range currentCall.Calls {
callStack.Push(c)
}
}
return false, fmt.Errorf("claim not found")
}

func Uint32ToBytesLittleEndian(num uint32) []byte {
bytes := make([]byte, 4) // uint32 is 4 bytes
binary.LittleEndian.PutUint32(bytes, num)
Expand Down
115 changes: 115 additions & 0 deletions cmd/ulxly/bridge/asset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package bridge

import (
_ "embed"
"math/big"
"strings"

"github.com/0xPolygon/polygon-cli/bindings/tokens"
ulxlycommon "github.com/0xPolygon/polygon-cli/cmd/ulxly/common"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)

//go:embed bridgeAssetUsage.md
var bridgeAssetUsage string

// AssetCmd represents the bridge asset command.
var AssetCmd = &cobra.Command{
Use: "asset",
Short: "Move ETH or an ERC20 between to chains.",
Long: bridgeAssetUsage,
PreRunE: ulxlycommon.PrepInputs,
RunE: bridgeAsset,
SilenceUsage: true,
}

func bridgeAsset(cmd *cobra.Command, _ []string) error {
bridgeAddr := ulxlycommon.InputArgs.BridgeAddress
privateKey := ulxlycommon.InputArgs.PrivateKey
gasLimit := ulxlycommon.InputArgs.GasLimit
destinationAddress := ulxlycommon.InputArgs.DestAddress
chainID := ulxlycommon.InputArgs.ChainID
amount := ulxlycommon.InputArgs.Value
tokenAddr := ulxlycommon.InputArgs.TokenAddress
callDataString := ulxlycommon.InputArgs.CallData
destinationNetwork := ulxlycommon.InputArgs.DestNetwork
isForced := ulxlycommon.InputArgs.ForceUpdate
timeoutTxnReceipt := ulxlycommon.InputArgs.Timeout
rpcURL := ulxlycommon.InputArgs.RPCURL

client, err := ulxlycommon.CreateEthClient(cmd.Context(), rpcURL)
if err != nil {
log.Error().Err(err).Msg("Unable to Dial RPC")
return err
}
defer client.Close()

// Initialize and assign variables required to send transaction payload
bridgeV2, toAddress, auth, err := ulxlycommon.GenerateTransactionPayload(cmd.Context(), client, bridgeAddr, privateKey, gasLimit, destinationAddress, chainID)
if err != nil {
log.Error().Err(err).Msg("error generating transaction payload")
return err
}

bridgeAddress := common.HexToAddress(bridgeAddr)
value, _ := big.NewInt(0).SetString(amount, 0)
tokenAddress := common.HexToAddress(tokenAddr)
callData := common.Hex2Bytes(strings.TrimPrefix(callDataString, "0x"))

if tokenAddress == common.HexToAddress("0x0000000000000000000000000000000000000000") {
auth.Value = value
} else {
// in case it's a token transfer, we need to ensure that the bridge contract
// has enough allowance to transfer the tokens on behalf of the user
tokenContract, iErr := tokens.NewERC20(tokenAddress, client)
if iErr != nil {
log.Error().Err(iErr).Msg("error getting token contract")
return iErr
}

allowance, iErr := tokenContract.Allowance(&bind.CallOpts{Pending: false}, auth.From, bridgeAddress)
if iErr != nil {
log.Error().Err(iErr).Msg("error getting token allowance")
return iErr
}

if allowance.Cmp(value) < 0 {
log.Info().
Str("amount", value.String()).
Str("tokenAddress", tokenAddress.String()).
Str("bridgeAddress", bridgeAddress.String()).
Str("userAddress", auth.From.String()).
Msg("approving bridge contract to spend tokens on behalf of user")

// Approve the bridge contract to spend the tokens on behalf of the user
approveTxn, iErr := tokenContract.Approve(auth, bridgeAddress, value)
if iErr = ulxlycommon.LogAndReturnJSONError(cmd.Context(), client, approveTxn, auth, iErr); iErr != nil {
return iErr
}
log.Info().Msg("approveTxn: " + approveTxn.Hash().String())
if iErr = ulxlycommon.WaitMineTransaction(cmd.Context(), client, approveTxn, timeoutTxnReceipt); iErr != nil {
return iErr
}
}
}

bridgeTxn, err := bridgeV2.BridgeAsset(auth, destinationNetwork, toAddress, value, tokenAddress, isForced, callData)
if err = ulxlycommon.LogAndReturnJSONError(cmd.Context(), client, bridgeTxn, auth, err); err != nil {
log.Info().Err(err).Str("calldata", callDataString).Msg("Bridge transaction failed")
return err
}
log.Info().Msg("bridgeTxn: " + bridgeTxn.Hash().String())
if err = ulxlycommon.WaitMineTransaction(cmd.Context(), client, bridgeTxn, timeoutTxnReceipt); err != nil {
return err
}
depositCount, err := parseDepositCountFromTransaction(cmd.Context(), client, bridgeTxn.Hash(), bridgeV2)
if err != nil {
return err
}

log.Info().Uint32("depositCount", depositCount).Msg("Bridge deposit count parsed from logs")
return nil
}
35 changes: 35 additions & 0 deletions cmd/ulxly/bridge/bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Package bridge provides commands for bridging assets and messages between chains.
package bridge

import (
ulxlycommon "github.com/0xPolygon/polygon-cli/cmd/ulxly/common"
"github.com/0xPolygon/polygon-cli/flag"
"github.com/spf13/cobra"
)

// BridgeCmd is the parent command for all bridge operations.
var BridgeCmd = &cobra.Command{
Use: "bridge",
Short: "Commands for moving funds and sending messages from one chain to another.",
Args: cobra.NoArgs,
}

func init() {
// Add subcommands
BridgeCmd.AddCommand(AssetCmd)
BridgeCmd.AddCommand(MessageCmd)
BridgeCmd.AddCommand(WethCmd)

// Add shared transaction flags (rpc-url, bridge-address, private-key, etc.)
ulxlycommon.AddTransactionFlags(BridgeCmd)

// Bridge-specific persistent flags
f := BridgeCmd.PersistentFlags()
f.BoolVar(&ulxlycommon.InputArgs.ForceUpdate, ulxlycommon.ArgForceUpdate, true, "update the new global exit root")
f.StringVar(&ulxlycommon.InputArgs.Value, ulxlycommon.ArgValue, "0", "amount in wei to send with the transaction")
f.Uint32Var(&ulxlycommon.InputArgs.DestNetwork, ulxlycommon.ArgDestNetwork, 0, "rollup ID of the destination network")
f.StringVar(&ulxlycommon.InputArgs.TokenAddress, ulxlycommon.ArgTokenAddress, "0x0000000000000000000000000000000000000000", "address of ERC20 token to use")
f.StringVar(&ulxlycommon.InputArgs.CallData, ulxlycommon.ArgCallData, "0x", "call data to be passed directly with bridge-message or as an ERC20 Permit")
f.StringVar(&ulxlycommon.InputArgs.CallDataFile, ulxlycommon.ArgCallDataFile, "", "a file containing hex encoded call data")
flag.MarkPersistentFlagsRequired(BridgeCmd, ulxlycommon.ArgDestNetwork)
}
File renamed without changes.
File renamed without changes.
80 changes: 80 additions & 0 deletions cmd/ulxly/bridge/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package bridge

import (
_ "embed"
"math/big"
"strings"

ulxlycommon "github.com/0xPolygon/polygon-cli/cmd/ulxly/common"
"github.com/ethereum/go-ethereum/common"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)

//go:embed bridgeMessageUsage.md
var bridgeMessageUsage string

// MessageCmd represents the bridge message command.
var MessageCmd = &cobra.Command{
Use: "message",
Short: "Send some ETH along with data from one chain to another chain.",
Long: bridgeMessageUsage,
PreRunE: ulxlycommon.PrepInputs,
RunE: bridgeMessage,
SilenceUsage: true,
}

func bridgeMessage(cmd *cobra.Command, _ []string) error {
bridgeAddress := ulxlycommon.InputArgs.BridgeAddress
privateKey := ulxlycommon.InputArgs.PrivateKey
gasLimit := ulxlycommon.InputArgs.GasLimit
destinationAddress := ulxlycommon.InputArgs.DestAddress
chainID := ulxlycommon.InputArgs.ChainID
amount := ulxlycommon.InputArgs.Value
tokenAddr := ulxlycommon.InputArgs.TokenAddress
callDataString := ulxlycommon.InputArgs.CallData
destinationNetwork := ulxlycommon.InputArgs.DestNetwork
isForced := ulxlycommon.InputArgs.ForceUpdate
timeoutTxnReceipt := ulxlycommon.InputArgs.Timeout
rpcURL := ulxlycommon.InputArgs.RPCURL

// Dial the Ethereum RPC server.
client, err := ulxlycommon.CreateEthClient(cmd.Context(), rpcURL)
if err != nil {
log.Error().Err(err).Msg("Unable to Dial RPC")
return err
}
defer client.Close()

// Initialize and assign variables required to send transaction payload
bridgeV2, toAddress, auth, err := ulxlycommon.GenerateTransactionPayload(cmd.Context(), client, bridgeAddress, privateKey, gasLimit, destinationAddress, chainID)
if err != nil {
log.Error().Err(err).Msg("error generating transaction payload")
return err
}

value, _ := big.NewInt(0).SetString(amount, 0)
tokenAddress := common.HexToAddress(tokenAddr)
callData := common.Hex2Bytes(strings.TrimPrefix(callDataString, "0x"))

if tokenAddress == common.HexToAddress("0x0000000000000000000000000000000000000000") {
auth.Value = value
}

bridgeTxn, err := bridgeV2.BridgeMessage(auth, destinationNetwork, toAddress, isForced, callData)
if err = ulxlycommon.LogAndReturnJSONError(cmd.Context(), client, bridgeTxn, auth, err); err != nil {
log.Info().Err(err).Str("calldata", callDataString).Msg("Bridge transaction failed")
return err
}
log.Info().Msg("bridgeTxn: " + bridgeTxn.Hash().String())
if err = ulxlycommon.WaitMineTransaction(cmd.Context(), client, bridgeTxn, timeoutTxnReceipt); err != nil {
return err
}
depositCount, err := parseDepositCountFromTransaction(cmd.Context(), client, bridgeTxn.Hash(), bridgeV2)
if err != nil {
return err
}

log.Info().Uint32("depositCount", depositCount).Msg("Bridge deposit count parsed from logs")
return nil
}
Loading