forked from smartcontractkit/ccip
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CLI tool for firedrill (smartcontractkit#48)
- Loading branch information
Showing
3 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |