Skip to content

Commit

Permalink
Anvil fork (#1366)
Browse files Browse the repository at this point in the history
fork testing examples
  • Loading branch information
skudasov authored Nov 21, 2024
1 parent 1363f80 commit f1f8047
Show file tree
Hide file tree
Showing 27 changed files with 3,494 additions and 9 deletions.
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [NodeSet with Capabilities](./framework/nodeset_capabilities.md)
- [NodeSet (Local Docker builds)](./framework/nodeset_docker_rebuild.md)
- [NodeSet Compat Environment](./framework/nodeset_compatibility.md)
- [Fork Testing](./framework/fork.md)
- [NodeSet with External Blockchain]()
- [CLI](./framework/cli.md)
- [Configuration](./framework/configuration.md)
Expand Down
42 changes: 42 additions & 0 deletions book/src/framework/fork.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Fork Testing

We verify our on-chain and off-chain changes using forks of various networks.

Go to example project [dir](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/framework/examples/myproject) to try the examples yourself.

## On-chain Only
In this [example](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/framework/examples/myproject/fork_test.go), we:

- Create two `anvil` networks, each targeting the desired network (change [URLs](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/framework/examples/myproject/fork.toml) and `anvil` settings as required, see [full anvil](https://book.getfoundry.sh/reference/anvil/) reference).
- Connect two clients to the respective networks.
- Deploy two test contracts.
- Interact with the deployed contracts.
- Demonstrate interactions using the `anvil` RPC client (more client methods examples are [here](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/framework/rpc/rpc_test.go))

Run it
```
CTF_CONFIGS=fork.toml go test -v -run TestFork
```

## On-chain + Off-chain

The chain setup remains the same as in the previous example, but now we have 5 `Chainlink` nodes [connected with 2 networks](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/framework/examples/myproject/fork_plus_offchain_test.go).

Run it
```
CTF_CONFIGS=fork_plus_offchain.toml go test -v -run TestOffChainAndFork
```

<div class="warning">

Be mindful of RPC rate limits, as your provider may enforce restrictions. Use `docker_cmd_params` field to configure appropriate rate limiting and retries with the following parameters:
```
--compute-units-per-second <CUPS>
--fork-retry-backoff <BACKOFF>
--retries <retries>
--timeout <timeout>
```
If the network imposes limits, the container will panic, triggering messages indicating that the container health check has failed.

</div>

3 changes: 3 additions & 0 deletions framework/.changeset/v0.2.8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Fund nodes method added to `NodeSet`
- Add on-chain fork test example
- Add on-chain + off-chain fork test example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.9'

services:
backend:
image: blockscout/${DOCKER_REPO:-blockscout}:${DOCKER_TAG:-latest}
image: blockscout/blockscout:6.9.0.commit.4100e959
pull_policy: always
restart: always
stop_grace_period: 5m
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.9'

services:
frontend:
image: ghcr.io/blockscout/frontend:${FRONTEND_DOCKER_TAG:-latest}
image: ghcr.io/blockscout/frontend:v1.36.2
pull_policy: always
platform: linux/amd64
restart: always
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.9'

services:
sig-provider:
image: ghcr.io/blockscout/sig-provider:${SIG_PROVIDER_DOCKER_TAG:-latest}
image: ghcr.io/blockscout/sig-provider:v1.1.1
pull_policy: always
platform: linux/amd64
restart: always
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.9'

services:
smart-contract-verifier:
image: ghcr.io/blockscout/smart-contract-verifier:${SMART_CONTRACT_VERIFIER_DOCKER_TAG:-latest}
image: ghcr.io/blockscout/smart-contract-verifier:v1.9.2
pull_policy: always
platform: linux/amd64
restart: always
Expand Down
2 changes: 1 addition & 1 deletion framework/cmd/observability/blockscout/services/stats.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ services:
start_period: 10s

stats:
image: ghcr.io/blockscout/stats:${STATS_DOCKER_TAG:-latest}
image: ghcr.io/blockscout/stats:v2.2.3
pull_policy: always
platform: linux/amd64
restart: always
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.9'

services:
user-ops-indexer:
image: ghcr.io/blockscout/user-ops-indexer:${USER_OPS_INDEXER_DOCKER_TAG:-latest}
image: ghcr.io/blockscout/user-ops-indexer:v1.3.0
pull_policy: always
platform: linux/amd64
restart: always
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.9'

services:
visualizer:
image: ghcr.io/blockscout/visualizer:${VISUALIZER_DOCKER_TAG:-latest}
image: ghcr.io/blockscout/visualizer:v0.2.1
pull_policy: always
platform: linux/amd64
restart: always
Expand Down
6 changes: 5 additions & 1 deletion framework/components/blockchain/anvil.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/smartcontractkit/chainlink-testing-framework/framework"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"strings"
"time"
)

Expand All @@ -18,8 +19,11 @@ const (
// deployAnvil deploy foundry anvil node
func deployAnvil(in *Input) (*Output, error) {
ctx := context.Background()
entryPoint := []string{"anvil", "--host", "0.0.0.0", "--port", in.Port, "--chain-id", in.ChainID, "-b", "1"}
entryPoint := []string{"anvil"}
defaultCmd := []string{"--host", "0.0.0.0", "--port", in.Port, "--chain-id", in.ChainID, "-b", "1"}
entryPoint = append(entryPoint, defaultCmd...)
entryPoint = append(entryPoint, in.DockerCmdParamsOverrides...)
framework.L.Info().Any("Cmd", strings.Join(entryPoint, " ")).Msg("Creating anvil with command")
bindPort := fmt.Sprintf("%s/tcp", in.Port)
containerName := framework.DefaultTCName("anvil")

Expand Down
78 changes: 78 additions & 0 deletions framework/components/simple_node_set/fund.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package simple_node_set

import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/smartcontractkit/chainlink-testing-framework/framework"
"github.com/smartcontractkit/chainlink-testing-framework/framework/clclient"
"math/big"
)

func SendETH(client *ethclient.Client, privateKeyHex string, toAddress string, amount *big.Float) error {
privateKey, err := crypto.HexToECDSA(privateKeyHex)
if err != nil {
return fmt.Errorf("failed to parse private key: %v", err)
}
wei := new(big.Int)
amountWei := new(big.Float).Mul(amount, big.NewFloat(1e18))
amountWei.Int(wei)

publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
return fmt.Errorf("failed to fetch nonce: %v", err)
}

gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
return fmt.Errorf("failed to fetch gas price: %v", err)
}
gasLimit := uint64(21000) // Standard gas limit for ETH transfer

tx := types.NewTransaction(nonce, common.HexToAddress(toAddress), wei, gasLimit, gasPrice, nil)

chainID, err := client.NetworkID(context.Background())
if err != nil {
return fmt.Errorf("failed to fetch chain ID: %v", err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
return fmt.Errorf("failed to sign transaction: %v", err)
}

err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
return fmt.Errorf("failed to send transaction: %v", err)
}
framework.L.Info().Msgf("Transaction sent: %s", signedTx.Hash().Hex())
return nil
}

// FundNodes funds Chainlink nodes with N ETH each
func FundNodes(c *ethclient.Client, nodes []*clclient.ChainlinkClient, pkey string, ethAmount float64) error {
if ethAmount == 0 {
return errors.New("funds_eth is 0, set some value in config, ex.: funds_eth = 30.0")
}
for _, cl := range nodes {
ek, err := cl.ReadPrimaryETHKey()
if err != nil {
return err
}
if err := SendETH(c, pkey, ek.Attributes.Address, big.NewFloat(ethAmount)); err != nil {
return fmt.Errorf("failed to fund CL node %s: %w", ek.Attributes.Address, err)
}
}
return nil
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package onchain

import (
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
testToken "github.com/smartcontractkit/chainlink-testing-framework/framework/examples/example_components/gethwrappers"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"

"github.com/smartcontractkit/chainlink-testing-framework/seth"
)

type Input struct {
URL string `toml:"url"`
Out *Output `toml:"out"`
}

type Output struct {
UseCache bool `toml:"use_cache"`
Addresses []common.Address `toml:"addresses"`
}

func NewProductOnChainDeployment(in *Input) (*Output, error) {
if in.Out != nil && in.Out.UseCache {
return in.Out, nil
}

// deploy your contracts here, example

t := seth.Duration{D: 2 * time.Minute}

c, err := seth.NewClientBuilder().
WithRpcUrl(in.URL).
WithProtections(true, false, &t).
WithGasPriceEstimations(true, 0, seth.Priority_Fast).
WithTracing(seth.TracingLevel_All, []string{seth.TraceOutput_Console}).
WithPrivateKeys([]string{blockchain.DefaultAnvilPrivateKey}).
Build()
if err != nil {
return nil, err
}

contractABI, err := testToken.BurnMintERC677MetaData.GetAbi()
if err != nil {
return nil, err
}

dd, err := c.DeployContract(c.NewTXOpts(),
"TestToken",
*contractABI,
common.FromHex(testToken.BurnMintERC677MetaData.Bin),
"TestToken",
"TestToken",
uint8(18),
big.NewInt(1000),
)
if err != nil {
return nil, err
}

out := &Output{
UseCache: true,
// save all the addresses to output, so it can be cached
Addresses: []common.Address{dd.Address},
}
in.Out = out
return out, nil
}
16 changes: 16 additions & 0 deletions framework/examples/myproject/fork.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[contracts_src]
[contracts_dst]

[blockchain_dst]
chain_id = "2337"
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate"]
image = "f4hrenh9it/foundry:latest"
port = "8545"
type = "anvil"

[blockchain_src]
chain_id = "3337"
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate"]
image = "f4hrenh9it/foundry:latest"
port = "8555"
type = "anvil"
29 changes: 29 additions & 0 deletions framework/examples/myproject/fork_plus_offchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[contracts_src]
[contracts_dst]

[blockchain_dst]
chain_id = "2337"
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate"]
image = "f4hrenh9it/foundry:latest"
port = "8545"
type = "anvil"

[blockchain_src]
chain_id = "3337"
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate"]
image = "f4hrenh9it/foundry:latest"
port = "8555"
type = "anvil"


[nodeset]
nodes = 5
override_mode = "all"

[nodeset.db]
image = "postgres:15.6"

[[nodeset.node_specs]]

[nodeset.node_specs.node]
image = "public.ecr.aws/chainlink/chainlink:v2.17.0"
Loading

0 comments on commit f1f8047

Please sign in to comment.