Skip to content

Commit

Permalink
Feat: CCTP bridger
Browse files Browse the repository at this point in the history
  • Loading branch information
karacurt committed Nov 28, 2024
1 parent c9f0670 commit 3c03278
Show file tree
Hide file tree
Showing 7 changed files with 6,087 additions and 14 deletions.
5,860 changes: 5,860 additions & 0 deletions bindings/TokenMessenger/TokenMessenger.go

Large diffs are not rendered by default.

12 changes: 0 additions & 12 deletions cmd/arbitrum/aribtrum.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,6 @@ import (
"github.com/spf13/cobra"
)

func CreateBifrostCommand() *cobra.Command {
bifrostCmd := &cobra.Command{
Use: "bifrost",
Short: "Bifrost CLI",
Long: `Bifrost CLI`,
}

bifrostCmd.AddCommand(CreateArbitrumCommand())

return bifrostCmd
}

func CreateArbitrumCommand() *cobra.Command {
var keyFile, password, l1Rpc, l2Rpc, inboxRaw, toRaw, l2CallValueRaw, l2CalldataRaw, safeAddressRaw, safeApi, safeNonceRaw string
var inboxAddress, to, safeAddress common.Address
Expand Down
150 changes: 150 additions & 0 deletions cmd/cctp/cctp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package cctp

import (
"errors"
"fmt"
"math/big"

"github.com/G7DAO/bifrost/bindings/TokenMessenger"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/spf13/cobra"
)

type ChainDomain uint32

const (
ChainDomainEthereum = 0
ChainDomainArbitrum = 3
)

// String returns the string representation of the ChainDomain
func (o ChainDomain) String() string {
switch o {
case ChainDomainEthereum:
return "Ethereum"
case ChainDomainArbitrum:
return "Arbitrum"
default:
return "Unknown"
}
}

func cctpBridge(key *keystore.Key, client *ethclient.Client, recipient common.Address, domain uint32, amount *big.Int, token common.Address, contractAddress common.Address) error {
mintRecipient, err := ParseMintRecipientFrom20BytesTo32Bytes(recipient)
if err != nil {
return err
}

abi, err := TokenMessenger.TokenMessengerMetaData.GetAbi()
if err != nil {
return err
}

packed, err := abi.Pack("depositForBurn", amount, domain, mintRecipient, token)
if err != nil {
return err
}

tx, err := SendTransaction(client, key, packed, contractAddress.Hex(), big.NewInt(0))
if err != nil {
return err
}

fmt.Println("Transaction hash:", tx.Hash().Hex())
return nil
}

// CCTP uses 32 bytes addresses, while EVEM uses 20 bytes addresses
// const mintRecipient = utils.hexlify(utils.zeroPad(recipient, 32)) as Address
func ParseMintRecipientFrom20BytesTo32Bytes(recipient common.Address) ([32]byte, error) {
var mintRecipient [32]byte

// Validate input length
if len(recipient) != 20 {
return mintRecipient, fmt.Errorf("invalid recipient length: expected 20 bytes, got %d", len(recipient))
}

// Copy the 20 bytes to the end of the 32-byte array, leaving the first 12 bytes as zeros
// This is equivalent to zero-padding on the left
copy(mintRecipient[32-20:], recipient.Bytes())

return mintRecipient, nil
}

func CreateCctpCommand() *cobra.Command {
var keyFile, password, rpc, recipientRaw, amountRaw, tokenRaw, contractRaw string
var domain uint32
var token, recipient, contract common.Address
var amount *big.Int

cctpCmd := &cobra.Command{
Use: "cctp",
Short: "Bifrost for CCTP cross-chain messaging protocol",
Long: `Bifrost for CCTP cross-chain messaging protocol`,

PreRunE: func(cmd *cobra.Command, args []string) error {

if !common.IsHexAddress(recipientRaw) {
return errors.New("invalid recipient address")
}
recipient = common.HexToAddress(recipientRaw)

if ChainDomain(domain).String() == "Unknown" {
return errors.New("invalid domain")
}

if amountRaw == "" {
return errors.New("amount is required")
} else {
var ok bool
amount, ok = new(big.Int).SetString(amountRaw, 10)
if !ok {
return errors.New("invalid amount")
}
}
if tokenRaw == "" {
return errors.New("token is required")
} else {
token = common.HexToAddress(tokenRaw)
}
if contractRaw == "" {
return errors.New("contract is required")
} else {
contract = common.HexToAddress(contractRaw)
}

if keyFile == "" {
return errors.New("keyfile is required")
}

return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Bridging to", recipientRaw)

key, err := TokenMessenger.KeyFromFile(keyFile, password)
if err != nil {
return err
}

client, err := ethclient.Dial(rpc)
if err != nil {
return err
}

return cctpBridge(key, client, recipient, domain, amount, token, contract)
},
}

cctpCmd.Flags().StringVar(&password, "password", "", "Password to encrypt accounts with")
cctpCmd.Flags().StringVar(&keyFile, "keyfile", "", "Keyfile to sign transaction with")
cctpCmd.Flags().StringVar(&rpc, "rpc", "", "RPC URL")
cctpCmd.Flags().StringVar(&recipientRaw, "recipient", "", "Recipient address in the destination domain")
cctpCmd.Flags().Uint32Var(&domain, "domain", 0, "Destination domain")
cctpCmd.Flags().StringVar(&amountRaw, "amount", "", "Amount of tokens to send")
cctpCmd.Flags().StringVar(&tokenRaw, "token", "", "Token to send")
cctpCmd.Flags().StringVar(&contractRaw, "contract", "", "Contract to send tokens from")
return cctpCmd
}
71 changes: 71 additions & 0 deletions cmd/cctp/ethereum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cctp

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)

// Function to send a transaction
func SendTransaction(client *ethclient.Client, key *keystore.Key, calldata []byte, to string, value *big.Int) (*types.Transaction, error) {
chainID, chainIDErr := client.ChainID(context.Background())
if chainIDErr != nil {
return nil, chainIDErr
}

recipientAddress := common.HexToAddress(to)

callMsg := ethereum.CallMsg{
From: key.Address,
To: &recipientAddress,
Value: value,
Data: calldata,
}

gasLimit, gasLimitErr := client.EstimateGas(context.Background(), callMsg)
if gasLimitErr != nil {
return nil, gasLimitErr
}

baseFee, baseFeeErr := client.SuggestGasPrice(context.Background())
if baseFeeErr != nil {
return nil, baseFeeErr
}

gasTipCap, gasTipCapErr := client.SuggestGasTipCap(context.Background())
if gasTipCapErr != nil {
return nil, gasTipCapErr
}

nonce, nonceErr := client.PendingNonceAt(context.Background(), key.Address)
if nonceErr != nil {
return nil, nonceErr
}

rawTransaction := types.NewTx(&types.DynamicFeeTx{
ChainID: chainID,
Nonce: nonce,
GasTipCap: gasTipCap,
GasFeeCap: baseFee,
Gas: gasLimit,
To: &recipientAddress,
Value: value,
Data: calldata,
})

signedTransaction, signedTransactionErr := types.SignTx(rawTransaction, types.NewLondonSigner(chainID), key.PrivateKey)
if signedTransactionErr != nil {
return nil, signedTransactionErr
}

sendTransactionErr := client.SendTransaction(context.Background(), signedTransaction)
if sendTransactionErr != nil {
return nil, sendTransactionErr
}
return signedTransaction, nil
}
4 changes: 3 additions & 1 deletion cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"

arbitrum_bifrost "github.com/G7DAO/bifrost/cmd/arbitrum"
"github.com/G7DAO/bifrost/cmd/cctp"
"github.com/G7DAO/bifrost/cmd/version"
"github.com/spf13/cobra"
)
Expand All @@ -22,8 +23,9 @@ func CreateRootCommand() *cobra.Command {
versionCmd := CreateVersionCommand()

arbitrumCmd := arbitrum_bifrost.CreateArbitrumCommand()
cctpCmd := cctp.CreateCctpCommand()

rootCmd.AddCommand(completionCmd, versionCmd, arbitrumCmd)
rootCmd.AddCommand(completionCmd, versionCmd, arbitrumCmd, cctpCmd)

// By default, cobra Command objects write to stderr. We have to forcibly set them to output to
// stdout.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22.0
toolchain go1.22.2

require (
github.com/G7DAO/seer v0.3.15
github.com/ethereum/go-ethereum v1.14.10
github.com/spf13/cobra v1.8.0
golang.org/x/term v0.20.0
Expand Down Expand Up @@ -41,6 +42,5 @@ require (
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/G7DAO/seer v0.3.15 h1:EO0Q/dD1fSPvQVMpewxFuowI+n95x40j1iJ+sJWrE5M=
github.com/G7DAO/seer v0.3.15/go.mod h1:JVdBWa8ma30x4xCiAVljKczYpl0To7gMWBTFsPSEA/A=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
Expand Down

0 comments on commit 3c03278

Please sign in to comment.