Skip to content

Commit

Permalink
CLI tool for firedrill (smartcontractkit#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
jarnaud committed Nov 21, 2023
1 parent 924ed86 commit 2cde245
Show file tree
Hide file tree
Showing 3 changed files with 374 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/stretchr/testify v1.8.4
github.com/test-go/testify v1.1.4
github.com/urfave/cli v1.22.14
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
)

Expand Down Expand Up @@ -331,6 +332,7 @@ require (
github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yuin/goldmark v1.4.13 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/zondax/hid v0.9.1 // indirect
Expand Down
56 changes: 56 additions & 0 deletions keres/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Keres

A firedrill tool for generating transactions that will trigger alerts.


## Configuration

Set the RPC and keys in the `.env.prod` configuration file.

## Usage

### Generating reverting transactions

#### Default lane

The default configuration send a transaction from Ethereum Sepolia to Avalanche Fuji.

Simply call:

```bash
$ keres call_reverting
```

It will create a CCIP transaction and use the already deployed reverting contract on Avalanche Fuji as receiver.

#### Custom lane

You can specify the lane to use.
Find the chain name [here](https://github.com/smartcontractkit/ccip-scripts/blob/57f66ba977660e7ad06a87fdff108835d81e6117/rhea/models.go#L50).

First deploy a reverting contract on the destination chain, for example on Polygon Mumbai:

```bash
$ keres --destination polygon-testnet-mumbai deploy
```

Keep the address of the deployed contract (eg. 0x1234) and use it for the next call:

```bash
$ keres --source ethereum-testnet-sepolia --destination polygon-testnet-mumbai call_reverting --receiver 0x1234
```

#### Multiple messages

You can send multiple messages at once:

```bash
$ keres call_reverting --number 3
```

#### Full example

```bash
$ keres --source avalanche-testnet-fuji --destination ethereum-testnet-sepolia call_reverting --receiver 0x035a1767FAaF27D7b24e12ac34875D41bfF0E74F --number 5
```

316 changes: 316 additions & 0 deletions keres/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
package main

import (
"fmt"
"math/big"
"os"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/joho/godotenv"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/maybe_revert_message_receiver"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/burn_mint_erc677"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/recovery"
"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip"
"github.com/smartcontractkit/chainlink/v2/core/utils"
"github.com/urfave/cli/v2"

"github.com/smartcontractkit/ccip-scripts/rhea"
"github.com/smartcontractkit/ccip-scripts/rhea/deployments"
"github.com/smartcontractkit/ccip-scripts/shared"
)

const (
defaultSourceChain = rhea.Sepolia
defaultDestChain = rhea.AvaxFuji
)

var (
defaultRevertingReceiverAddress = map[rhea.Chain]string{
rhea.Sepolia: "0x035a1767FAaF27D7b24e12ac34875D41bfF0E74F",
rhea.AvaxFuji: "0x61B869Ec9198D65F0C5F1A02ED0762a5a8eD22F0",
rhea.PolygonMumbai: "0x2BC755E7223eFa43C9985F48dB45A831740B967a",
}
)

// COMMANDS:
// deploy, d Deploy a reverting contract on the destination chain
// call_reverting, c Call a reverting contract through CCIP
// list_receivers, l List default reverting receivers for each chain
// help, h Shows a list of commands or help for one command
func main() {
recovery.ReportPanics(func() {
log, _ := logger.NewLogger()
filename := ".env.prod"
err := godotenv.Load(filename)
if err != nil {
log.Fatal(err)
}

service := NewService(log)
app := NewKeresApp(service)

if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
})
}

func NewKeresApp(service IService) *cli.App {
var number uint

app := cli.NewApp()
app.Name = "Keres"
app.Version = "0.0.1"
app.Usage = "Keres is a tool to create and send CCIP transactions that trigger errors"

app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "source",
Category: "Chain configuration",
DefaultText: "ethereum-testnet-sepolia",
Usage: "Name of source chain as found in models",
Aliases: []string{"s"},
Action: func(context *cli.Context, s string) error {
service.SetSourceChain(s)
return nil
},
},
&cli.StringFlag{
Name: "destination",
Category: "Chain configuration",
DefaultText: "avalanche-testnet-fuji",
Usage: "Name of destination chain as found in models",
Aliases: []string{"d"},
Action: func(context *cli.Context, s string) error {
service.SetDestinationChain(s)
return nil
},
},
}

app.Commands = []*cli.Command{
{
Name: "deploy",
Aliases: []string{"d"},
Usage: "Deploy a reverting contract on the destination chain",
Action: func(c *cli.Context) error {
service.Deploy()
return nil
},
},
{
Name: "call_reverting",
Aliases: []string{"c"},
Usage: "Call a reverting contract through CCIP",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "receiver",
Usage: "Address of receiving contract",
Aliases: []string{"rec"},
Required: false,
Value: defaultRevertingReceiverAddress[rhea.AvaxFuji],
Action: func(context *cli.Context, s string) error {
service.SetReceiver(common.HexToAddress(s))
return nil
},
},
&cli.UintFlag{
Name: "number",
Usage: "Number of transactions to generate",
Aliases: []string{"n"},
Required: false,
Value: 1,
Destination: &number,
},
},
Action: func(c *cli.Context) error {
service.CallReverting(number)
return nil
},
},
{
Name: "list_receivers",
Aliases: []string{"l"},
Usage: "List default reverting receivers for each chain",
Action: func(context *cli.Context) error {
service.ListRevertingReceivers()
return nil
},
},
}
return app
}

type IService interface {
SetSourceChain(s string)
SetDestinationChain(s string)
SetReceiver(address common.Address)

Deploy()
CallReverting(number uint)
ListRevertingReceivers()
}

type Service struct {
log logger.SugaredLogger
sourceChain rhea.Chain
destinationChain rhea.Chain
source rhea.EvmDeploymentConfig
destination rhea.EvmDeploymentConfig
receiver common.Address
}

func NewService(log logger.Logger) IService {
return &Service{
log: logger.Sugared(log),
sourceChain: defaultSourceChain,
destinationChain: defaultDestChain,
}
}

func (s *Service) ListRevertingReceivers() {
s.log.Infof("Default reverting receivers:")
i := 0
for chain, addr := range defaultRevertingReceiverAddress {
first := '\u251c'
if i == len(defaultRevertingReceiverAddress)-1 {
first = '\u2514'
}
s.log.Infof("%c %s: %s", first, chain, addr)
i++
}
}

func (s *Service) SetSourceChain(chain string) {
s.sourceChain = rhea.Chain(chain)
}

func (s *Service) SetDestinationChain(chain string) {
s.destinationChain = rhea.Chain(chain)
}

func (s *Service) SetReceiver(address common.Address) {
s.receiver = address
}

// Deploy programmatically deploy on the destination chain a receiving contract which always revert.
func (s *Service) Deploy() {
s.log.Infof("Deploying receiving contract on %s ...", s.destinationChain)
s.setupChains()

auth := s.getAuthDest()
receiver, _, _, err := maybe_revert_message_receiver.DeployMaybeRevertMessageReceiver(auth, s.destination.Client, true)
if err != nil {
s.log.Error(fmt.Errorf("error while deploying contract: %s", err))
}
s.log.Info("Deployed reverting receiver contract at: ", receiver.Hex())
}

// CallReverting programmatically send a CCIP transaction that calls a reverting contract.
func (s *Service) CallReverting(number uint) {
s.log.Infof("Will call a reverting contract on %s with %d transactions...", s.destinationChain, number)
s.setupChains()

// If receiver is not set by user, try to use the default one.
if s.receiver == (common.Address{}) {
if defaultReceiver, ok := defaultRevertingReceiverAddress[s.destinationChain]; ok {
s.log.Infof("Using default receiver for %s: %s", s.destinationChain, defaultReceiver)
s.receiver = common.HexToAddress(defaultReceiver)
} else {
s.log.Fatalf("no default receiver for %s, please provide a receiver address", s.destinationChain)
}
}

routerContract, err := router.NewRouter(s.source.ChainConfig.Router, s.source.Client)
if err != nil {
s.log.Fatal(fmt.Errorf("cannot load router contract: %s", err))
}
s.log.Info("Using router contract at: ", routerContract.Address().Hex()) // OK router.

destChainID := s.destination.ChainConfig.EvmChainId
s.log.Info("Using destination chain ID: ", destChainID)
destChainSelector := rhea.GetCCIPChainSelector(destChainID)
s.log.Info("Using destination chain selector: ", destChainSelector)
s.log.Info("Using receiver at: ", s.receiver.Hex())

feeTokenSource := s.source.ChainConfig.SupportedTokens[rhea.LINK]
feeTokenSourceAddress := feeTokenSource.Token
s.log.Info("Using fee token at source: ", feeTokenSourceAddress.Hex()) // LINK (fee token) on Fuji.

// Approve router to spend fee token.
auth := s.getAuthSource()
s.approveRouter(auth, routerContract, feeTokenSourceAddress, number)

// Send CCIP messages to the router.
receiverAddressBytes := s.getAddressBytes(s.receiver)
for i := uint(0); i < number; i++ {
s.log.Infof("Sending CCIP transaction %d/%d...", i+1, number)
msg := router.ClientEVM2AnyMessage{
Receiver: receiverAddressBytes,
TokenAmounts: []router.ClientEVMTokenAmount{},
FeeToken: feeTokenSourceAddress,
ExtraArgs: []byte{},
}

tx, err := routerContract.CcipSend(auth, destChainSelector, msg)
if err != nil {
s.log.Fatalf("unexpected error while sending CCIP transaction: %s", err)
} else {
s.log.Infof("sent transaction: %s", tx.Hash().Hex())
}
}
}

func (s *Service) getAddressBytes(address common.Address) []byte {
addressBytes, err := utils.ABIEncode(`[{"type":"address"}]`, address)
if err != nil {
s.log.Fatalf("cannot encode address: %s", err)
}
return addressBytes
}

// approveRouter approves the router to spend the fee token.
func (s *Service) approveRouter(auth *bind.TransactOpts, routerContract *router.Router, feeTokenSourceAddress common.Address, nbTransactions uint) {
s.log.Info("Approving router to spend fee token...")
backend := s.source.Client
token, err := burn_mint_erc677.NewBurnMintERC677(feeTokenSourceAddress, backend)
if err != nil {
s.log.Fatalf("cannot load token contract: %s", err)
}
tx, err := token.Approve(auth, routerContract.Address(), big.NewInt(int64(nbTransactions*1e19)))
if err != nil {
s.log.Fatalf("cannot approve router: %s", err)
}
err = shared.WaitForMined(s.log, backend, tx.Hash(), true)
if err != nil {
s.log.Fatalf("tx not mined: %s", err)
}
}

func (s *Service) setupChains() {
s.source = deployments.ProdChainMapping[s.sourceChain][s.destinationChain]
s.destination = deployments.ProdChainMapping[s.destinationChain][s.sourceChain]
if s.source.ChainConfig.EvmChainId == 0 || s.destination.ChainConfig.EvmChainId == 0 {
s.log.Fatalf("cannot find the lane for %s -> %s", s.sourceChain, s.destinationChain)
}

err := s.source.SetupReadOnlyChain(s.log.Named(ccip.ChainName(int64(s.source.ChainConfig.EvmChainId))))
if err != nil {
s.log.Fatalf("error while setting up source chain: %s", err)
}
err = s.destination.SetupReadOnlyChain(s.log.Named(ccip.ChainName(int64(s.destination.ChainConfig.EvmChainId))))
if err != nil {
s.log.Fatalf("error while setting up destination chain: %s", err)
}
}

func (s *Service) getAuthSource() *bind.TransactOpts {
return rhea.GetOwner(nil, os.Getenv("OWNER_KEY"), s.source.ChainConfig.EvmChainId, s.source.ChainConfig.GasSettings)
}

func (s *Service) getAuthDest() *bind.TransactOpts {
return rhea.GetOwner(nil, os.Getenv("OWNER_KEY"), s.destination.ChainConfig.EvmChainId, s.destination.ChainConfig.GasSettings)
}

0 comments on commit 2cde245

Please sign in to comment.