diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
index 8482e4489..128be2421 100644
--- a/book/src/SUMMARY.md
+++ b/book/src/SUMMARY.md
@@ -13,6 +13,7 @@
- [Creating your own components](./developing/developing_components.md)
- [Fork Testing](./framework/fork.md)
- [Quick Contracts Deployment](./framework/quick_deployment.md)
+ - [Verifying Contracts](./framework/verify.md)
- [NodeSet with External Blockchain]()
- [CLI](./framework/cli.md)
- [Configuration](./framework/configuration.md)
diff --git a/book/src/framework/observability/blockscout.md b/book/src/framework/observability/blockscout.md
index 1246f9b58..28112bf0c 100644
--- a/book/src/framework/observability/blockscout.md
+++ b/book/src/framework/observability/blockscout.md
@@ -25,9 +25,4 @@ ctf bs -r http://host.docker.internal:8555 d
Blockscout isn’t ideal for local, ephemeral environments, as it won’t re-index blocks and transactions on test reruns. The easiest approach is to set up Blockscout first, initialize the test environment, switch to the [cache](../components/caching.md) config, and run tests without restarting RPC nodes.
Otherwise, use `ctf bs r` each time you restart your test with a fresh docker environment.
-
-
-
-
-Blockscout integration is still WIP, for now Blockscout reads only one node that is on `:8545`, all our blockchain implementation expose this port by default.
-
+
\ No newline at end of file
diff --git a/book/src/framework/verify.md b/book/src/framework/verify.md
new file mode 100644
index 000000000..8c73c3531
--- /dev/null
+++ b/book/src/framework/verify.md
@@ -0,0 +1,16 @@
+# Verifying Contracts
+
+Check out our [example](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/framework/examples/myproject/verify_test.go) of programmatically verifying contracts using `Blockscout` and `Foundry`. You'll need to provide:
+
+- The path to your Foundry directory
+- The path to the contract
+- The contract name
+
+```golang
+ err := blockchain.VerifyContract(blockchainComponentOutput, c.Addresses[0].String(),
+ "example_components/onchain",
+ "src/Counter.sol",
+ "Counter",
+ )
+ require.NoError(t, err)
+```
\ No newline at end of file
diff --git a/framework/.changeset/v0.3.1.md b/framework/.changeset/v0.3.1.md
index 2d83b2c4e..cdce0f412 100644
--- a/framework/.changeset/v0.3.1.md
+++ b/framework/.changeset/v0.3.1.md
@@ -1,2 +1,3 @@
-- Use docker cmd instead of testcontainers
+- Use docker cmd for building instead of testcontainers-go
- Add "CHAINLINK_IMAGE" flag to override NodeSet image
+- Add Go wrapper for Blockscout verification (foundry)
\ No newline at end of file
diff --git a/framework/cmd/observability/blockscout/docker-compose.yml b/framework/cmd/observability/blockscout/docker-compose.yml
index 900ec4e9b..975a66aa6 100644
--- a/framework/cmd/observability/blockscout/docker-compose.yml
+++ b/framework/cmd/observability/blockscout/docker-compose.yml
@@ -30,6 +30,7 @@ services:
ETHEREUM_JSONRPC_VARIANT: 'geth'
ETHEREUM_JSONRPC_HTTP_URL: ${BLOCKSCOUT_RPC_URL}
ETHEREUM_JSONRPC_TRACE_URL: ${BLOCKSCOUT_RPC_URL}
+ CHAIN_ID: ${BLOCKSCOUT_CHAIN_ID:-1337}
visualizer:
extends:
diff --git a/framework/components/blockchain/verify.go b/framework/components/blockchain/verify.go
new file mode 100644
index 000000000..85d2f4fce
--- /dev/null
+++ b/framework/components/blockchain/verify.go
@@ -0,0 +1,22 @@
+package blockchain
+
+import (
+ "fmt"
+ "github.com/smartcontractkit/chainlink-testing-framework/framework"
+)
+
+// VerifyContract wraps the forge verify-contract command.
+func VerifyContract(out *Output, address, foundryDir, contractFile, contractName string) error {
+ args := []string{
+ "verify-contract",
+ "--rpc-url", out.Nodes[0].HostHTTPUrl,
+ "--chain-id",
+ out.ChainID,
+ "--compiler-version=0.8.24",
+ address,
+ fmt.Sprintf("%s:%s", contractFile, contractName),
+ "--verifier", "blockscout",
+ "--verifier-url", "http://localhost/api/",
+ }
+ return framework.RunCommandDir(foundryDir, "forge", args...)
+}
diff --git a/framework/components/simple_node_set/reload.go b/framework/components/simple_node_set/reload.go
index 7ec5048ec..446a28074 100644
--- a/framework/components/simple_node_set/reload.go
+++ b/framework/components/simple_node_set/reload.go
@@ -9,7 +9,7 @@ import (
// UpgradeNodeSet updates nodes configuration TOML files
// this API is discouraged, however, you can use it if nodes require restart or configuration updates, temporarily!
func UpgradeNodeSet(in *Input, bc *blockchain.Output, wait time.Duration) (*Output, error) {
- _, err := chaos.ExecPumba("rm --volumes=false re2:node.*|ns-postgresql.*", wait)
+ _, err := chaos.ExecPumba("rm --volumes=false re2:^node.*|ns-postgresql.*", wait)
if err != nil {
return nil, err
}
diff --git a/framework/docker.go b/framework/docker.go
index 9e690a55f..fd6b69318 100644
--- a/framework/docker.go
+++ b/framework/docker.go
@@ -77,6 +77,17 @@ func runCommand(name string, args ...string) error {
return cmd.Run()
}
+// RunCommandDir executes a command in some directory and prints the output
+func RunCommandDir(dir, name string, args ...string) error {
+ cmd := exec.Command(name, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if dir != "" {
+ cmd.Dir = dir
+ }
+ return cmd.Run()
+}
+
// DockerClient wraps a Docker API client and provides convenience methods
type DockerClient struct {
cli *client.Client
diff --git a/framework/examples/myproject/example_components/onchain/.github/workflows/test.yml b/framework/examples/myproject/example_components/onchain/.github/workflows/test.yml
new file mode 100644
index 000000000..9282e8294
--- /dev/null
+++ b/framework/examples/myproject/example_components/onchain/.github/workflows/test.yml
@@ -0,0 +1,34 @@
+name: test
+
+on: workflow_dispatch
+
+env:
+ FOUNDRY_PROFILE: ci
+
+jobs:
+ check:
+ strategy:
+ fail-fast: true
+
+ name: Foundry project
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
+ with:
+ version: nightly
+
+ - name: Run Forge build
+ run: |
+ forge --version
+ forge build --sizes
+ id: build
+
+ - name: Run Forge tests
+ run: |
+ forge test -vvv
+ id: test
diff --git a/framework/examples/myproject/example_components/onchain/.gitignore b/framework/examples/myproject/example_components/onchain/.gitignore
new file mode 100644
index 000000000..85198aaa5
--- /dev/null
+++ b/framework/examples/myproject/example_components/onchain/.gitignore
@@ -0,0 +1,14 @@
+# Compiler files
+cache/
+out/
+
+# Ignores development broadcast logs
+!/broadcast
+/broadcast/*/31337/
+/broadcast/**/dry-run/
+
+# Docs
+docs/
+
+# Dotenv file
+.env
diff --git a/framework/examples/myproject/example_components/onchain/README.md b/framework/examples/myproject/example_components/onchain/README.md
new file mode 100644
index 000000000..8817d6ab7
--- /dev/null
+++ b/framework/examples/myproject/example_components/onchain/README.md
@@ -0,0 +1,66 @@
+## Foundry
+
+**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
+
+Foundry consists of:
+
+- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
+- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
+- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
+- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
+
+## Documentation
+
+https://book.getfoundry.sh/
+
+## Usage
+
+### Build
+
+```shell
+$ forge build
+```
+
+### Test
+
+```shell
+$ forge test
+```
+
+### Format
+
+```shell
+$ forge fmt
+```
+
+### Gas Snapshots
+
+```shell
+$ forge snapshot
+```
+
+### Anvil
+
+```shell
+$ anvil
+```
+
+### Deploy
+
+```shell
+$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key
+```
+
+### Cast
+
+```shell
+$ cast
+```
+
+### Help
+
+```shell
+$ forge --help
+$ anvil --help
+$ cast --help
+```
diff --git a/framework/examples/myproject/example_components/onchain/deployment_counter.go b/framework/examples/myproject/example_components/onchain/deployment_counter.go
new file mode 100644
index 000000000..a7061a3cb
--- /dev/null
+++ b/framework/examples/myproject/example_components/onchain/deployment_counter.go
@@ -0,0 +1,32 @@
+package onchain
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/smartcontractkit/chainlink-testing-framework/framework/examples/example_components/onchain/gethwrappers"
+ "github.com/smartcontractkit/chainlink-testing-framework/seth"
+)
+
+func NewCounterDeployment(c *seth.Client, in *Input) (*Output, error) {
+ if in.Out != nil && in.Out.UseCache {
+ return in.Out, nil
+ }
+ counterABI, err := gethwrappers.GethwrappersMetaData.GetAbi()
+ if err != nil {
+ return nil, err
+ }
+ dd, err := c.DeployContract(c.NewTXOpts(),
+ "TestCounter",
+ *counterABI,
+ common.FromHex(gethwrappers.GethwrappersMetaData.Bin),
+ )
+ 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
+}
diff --git a/framework/examples/myproject/example_components/onchain/foundry.toml b/framework/examples/myproject/example_components/onchain/foundry.toml
new file mode 100644
index 000000000..0abefc497
--- /dev/null
+++ b/framework/examples/myproject/example_components/onchain/foundry.toml
@@ -0,0 +1,7 @@
+[profile.default]
+chain_id = "1337"
+src = "src"
+out = "out"
+libs = ["lib"]
+
+# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
diff --git a/framework/examples/myproject/example_components/onchain/gethwrappers/counter.go b/framework/examples/myproject/example_components/onchain/gethwrappers/counter.go
new file mode 100644
index 000000000..3ad4ab2ca
--- /dev/null
+++ b/framework/examples/myproject/example_components/onchain/gethwrappers/counter.go
@@ -0,0 +1,276 @@
+// Code generated - DO NOT EDIT.
+// This file is a generated binding and any manual changes will be lost.
+
+package gethwrappers
+
+import (
+ "errors"
+ "math/big"
+ "strings"
+
+ ethereum "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/event"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var (
+ _ = errors.New
+ _ = big.NewInt
+ _ = strings.NewReader
+ _ = ethereum.NotFound
+ _ = bind.Bind
+ _ = common.Big1
+ _ = types.BloomLookup
+ _ = event.NewSubscription
+ _ = abi.ConvertType
+)
+
+// GethwrappersMetaData contains all meta data concerning the Gethwrappers contract.
+var GethwrappersMetaData = &bind.MetaData{
+ ABI: "[{\"type\":\"function\",\"name\":\"increment\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"number\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setNumber\",\"inputs\":[{\"name\":\"newNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"}]",
+ Bin: "0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220b4b78a03f7df1ffcbf8115e03ede66c3aae910f19ea5b824b889e4e6056ab55c64736f6c63430008180033",
+}
+
+// GethwrappersABI is the input ABI used to generate the binding from.
+// Deprecated: Use GethwrappersMetaData.ABI instead.
+var GethwrappersABI = GethwrappersMetaData.ABI
+
+// GethwrappersBin is the compiled bytecode used for deploying new contracts.
+// Deprecated: Use GethwrappersMetaData.Bin instead.
+var GethwrappersBin = GethwrappersMetaData.Bin
+
+// DeployGethwrappers deploys a new Ethereum contract, binding an instance of Gethwrappers to it.
+func DeployGethwrappers(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Gethwrappers, error) {
+ parsed, err := GethwrappersMetaData.GetAbi()
+ if err != nil {
+ return common.Address{}, nil, nil, err
+ }
+ if parsed == nil {
+ return common.Address{}, nil, nil, errors.New("GetABI returned nil")
+ }
+
+ address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(GethwrappersBin), backend)
+ if err != nil {
+ return common.Address{}, nil, nil, err
+ }
+ return address, tx, &Gethwrappers{GethwrappersCaller: GethwrappersCaller{contract: contract}, GethwrappersTransactor: GethwrappersTransactor{contract: contract}, GethwrappersFilterer: GethwrappersFilterer{contract: contract}}, nil
+}
+
+// Gethwrappers is an auto generated Go binding around an Ethereum contract.
+type Gethwrappers struct {
+ GethwrappersCaller // Read-only binding to the contract
+ GethwrappersTransactor // Write-only binding to the contract
+ GethwrappersFilterer // Log filterer for contract events
+}
+
+// GethwrappersCaller is an auto generated read-only Go binding around an Ethereum contract.
+type GethwrappersCaller struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// GethwrappersTransactor is an auto generated write-only Go binding around an Ethereum contract.
+type GethwrappersTransactor struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// GethwrappersFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
+type GethwrappersFilterer struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// GethwrappersSession is an auto generated Go binding around an Ethereum contract,
+// with pre-set call and transact options.
+type GethwrappersSession struct {
+ Contract *Gethwrappers // Generic contract binding to set the session for
+ CallOpts bind.CallOpts // Call options to use throughout this session
+ TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
+}
+
+// GethwrappersCallerSession is an auto generated read-only Go binding around an Ethereum contract,
+// with pre-set call options.
+type GethwrappersCallerSession struct {
+ Contract *GethwrappersCaller // Generic contract caller binding to set the session for
+ CallOpts bind.CallOpts // Call options to use throughout this session
+}
+
+// GethwrappersTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
+// with pre-set transact options.
+type GethwrappersTransactorSession struct {
+ Contract *GethwrappersTransactor // Generic contract transactor binding to set the session for
+ TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
+}
+
+// GethwrappersRaw is an auto generated low-level Go binding around an Ethereum contract.
+type GethwrappersRaw struct {
+ Contract *Gethwrappers // Generic contract binding to access the raw methods on
+}
+
+// GethwrappersCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
+type GethwrappersCallerRaw struct {
+ Contract *GethwrappersCaller // Generic read-only contract binding to access the raw methods on
+}
+
+// GethwrappersTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
+type GethwrappersTransactorRaw struct {
+ Contract *GethwrappersTransactor // Generic write-only contract binding to access the raw methods on
+}
+
+// NewGethwrappers creates a new instance of Gethwrappers, bound to a specific deployed contract.
+func NewGethwrappers(address common.Address, backend bind.ContractBackend) (*Gethwrappers, error) {
+ contract, err := bindGethwrappers(address, backend, backend, backend)
+ if err != nil {
+ return nil, err
+ }
+ return &Gethwrappers{GethwrappersCaller: GethwrappersCaller{contract: contract}, GethwrappersTransactor: GethwrappersTransactor{contract: contract}, GethwrappersFilterer: GethwrappersFilterer{contract: contract}}, nil
+}
+
+// NewGethwrappersCaller creates a new read-only instance of Gethwrappers, bound to a specific deployed contract.
+func NewGethwrappersCaller(address common.Address, caller bind.ContractCaller) (*GethwrappersCaller, error) {
+ contract, err := bindGethwrappers(address, caller, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &GethwrappersCaller{contract: contract}, nil
+}
+
+// NewGethwrappersTransactor creates a new write-only instance of Gethwrappers, bound to a specific deployed contract.
+func NewGethwrappersTransactor(address common.Address, transactor bind.ContractTransactor) (*GethwrappersTransactor, error) {
+ contract, err := bindGethwrappers(address, nil, transactor, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &GethwrappersTransactor{contract: contract}, nil
+}
+
+// NewGethwrappersFilterer creates a new log filterer instance of Gethwrappers, bound to a specific deployed contract.
+func NewGethwrappersFilterer(address common.Address, filterer bind.ContractFilterer) (*GethwrappersFilterer, error) {
+ contract, err := bindGethwrappers(address, nil, nil, filterer)
+ if err != nil {
+ return nil, err
+ }
+ return &GethwrappersFilterer{contract: contract}, nil
+}
+
+// bindGethwrappers binds a generic wrapper to an already deployed contract.
+func bindGethwrappers(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
+ parsed, err := GethwrappersMetaData.GetAbi()
+ if err != nil {
+ return nil, err
+ }
+ return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil
+}
+
+// Call invokes the (constant) contract method with params as input values and
+// sets the output to result. The result type might be a single field for simple
+// returns, a slice of interfaces for anonymous returns and a struct for named
+// returns.
+func (_Gethwrappers *GethwrappersRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+ return _Gethwrappers.Contract.GethwrappersCaller.contract.Call(opts, result, method, params...)
+}
+
+// Transfer initiates a plain transaction to move funds to the contract, calling
+// its default method if one is available.
+func (_Gethwrappers *GethwrappersRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _Gethwrappers.Contract.GethwrappersTransactor.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_Gethwrappers *GethwrappersRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+ return _Gethwrappers.Contract.GethwrappersTransactor.contract.Transact(opts, method, params...)
+}
+
+// Call invokes the (constant) contract method with params as input values and
+// sets the output to result. The result type might be a single field for simple
+// returns, a slice of interfaces for anonymous returns and a struct for named
+// returns.
+func (_Gethwrappers *GethwrappersCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+ return _Gethwrappers.Contract.contract.Call(opts, result, method, params...)
+}
+
+// Transfer initiates a plain transaction to move funds to the contract, calling
+// its default method if one is available.
+func (_Gethwrappers *GethwrappersTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _Gethwrappers.Contract.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_Gethwrappers *GethwrappersTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+ return _Gethwrappers.Contract.contract.Transact(opts, method, params...)
+}
+
+// Number is a free data retrieval call binding the contract method 0x8381f58a.
+//
+// Solidity: function number() view returns(uint256)
+func (_Gethwrappers *GethwrappersCaller) Number(opts *bind.CallOpts) (*big.Int, error) {
+ var out []interface{}
+ err := _Gethwrappers.contract.Call(opts, &out, "number")
+
+ if err != nil {
+ return *new(*big.Int), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+ return out0, err
+
+}
+
+// Number is a free data retrieval call binding the contract method 0x8381f58a.
+//
+// Solidity: function number() view returns(uint256)
+func (_Gethwrappers *GethwrappersSession) Number() (*big.Int, error) {
+ return _Gethwrappers.Contract.Number(&_Gethwrappers.CallOpts)
+}
+
+// Number is a free data retrieval call binding the contract method 0x8381f58a.
+//
+// Solidity: function number() view returns(uint256)
+func (_Gethwrappers *GethwrappersCallerSession) Number() (*big.Int, error) {
+ return _Gethwrappers.Contract.Number(&_Gethwrappers.CallOpts)
+}
+
+// Increment is a paid mutator transaction binding the contract method 0xd09de08a.
+//
+// Solidity: function increment() returns()
+func (_Gethwrappers *GethwrappersTransactor) Increment(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _Gethwrappers.contract.Transact(opts, "increment")
+}
+
+// Increment is a paid mutator transaction binding the contract method 0xd09de08a.
+//
+// Solidity: function increment() returns()
+func (_Gethwrappers *GethwrappersSession) Increment() (*types.Transaction, error) {
+ return _Gethwrappers.Contract.Increment(&_Gethwrappers.TransactOpts)
+}
+
+// Increment is a paid mutator transaction binding the contract method 0xd09de08a.
+//
+// Solidity: function increment() returns()
+func (_Gethwrappers *GethwrappersTransactorSession) Increment() (*types.Transaction, error) {
+ return _Gethwrappers.Contract.Increment(&_Gethwrappers.TransactOpts)
+}
+
+// SetNumber is a paid mutator transaction binding the contract method 0x3fb5c1cb.
+//
+// Solidity: function setNumber(uint256 newNumber) returns()
+func (_Gethwrappers *GethwrappersTransactor) SetNumber(opts *bind.TransactOpts, newNumber *big.Int) (*types.Transaction, error) {
+ return _Gethwrappers.contract.Transact(opts, "setNumber", newNumber)
+}
+
+// SetNumber is a paid mutator transaction binding the contract method 0x3fb5c1cb.
+//
+// Solidity: function setNumber(uint256 newNumber) returns()
+func (_Gethwrappers *GethwrappersSession) SetNumber(newNumber *big.Int) (*types.Transaction, error) {
+ return _Gethwrappers.Contract.SetNumber(&_Gethwrappers.TransactOpts, newNumber)
+}
+
+// SetNumber is a paid mutator transaction binding the contract method 0x3fb5c1cb.
+//
+// Solidity: function setNumber(uint256 newNumber) returns()
+func (_Gethwrappers *GethwrappersTransactorSession) SetNumber(newNumber *big.Int) (*types.Transaction, error) {
+ return _Gethwrappers.Contract.SetNumber(&_Gethwrappers.TransactOpts, newNumber)
+}
diff --git a/framework/examples/myproject/example_components/onchain/lib/forge-std b/framework/examples/myproject/example_components/onchain/lib/forge-std
new file mode 160000
index 000000000..1eea5bae1
--- /dev/null
+++ b/framework/examples/myproject/example_components/onchain/lib/forge-std
@@ -0,0 +1 @@
+Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262
diff --git a/framework/examples/myproject/example_components/onchain/script/Counter.s.sol b/framework/examples/myproject/example_components/onchain/script/Counter.s.sol
new file mode 100644
index 000000000..df9ee8b02
--- /dev/null
+++ b/framework/examples/myproject/example_components/onchain/script/Counter.s.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.13;
+
+import {Script, console} from "forge-std/Script.sol";
+
+contract CounterScript is Script {
+ function setUp() public {}
+
+ function run() public {
+ vm.broadcast();
+ }
+}
diff --git a/framework/examples/myproject/example_components/onchain/src/Counter.sol b/framework/examples/myproject/example_components/onchain/src/Counter.sol
new file mode 100644
index 000000000..aded7997b
--- /dev/null
+++ b/framework/examples/myproject/example_components/onchain/src/Counter.sol
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.13;
+
+contract Counter {
+ uint256 public number;
+
+ function setNumber(uint256 newNumber) public {
+ number = newNumber;
+ }
+
+ function increment() public {
+ number++;
+ }
+}
diff --git a/framework/examples/myproject/example_components/onchain/test/Counter.t.sol b/framework/examples/myproject/example_components/onchain/test/Counter.t.sol
new file mode 100644
index 000000000..54b724f7a
--- /dev/null
+++ b/framework/examples/myproject/example_components/onchain/test/Counter.t.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.13;
+
+import {Test, console} from "forge-std/Test.sol";
+import {Counter} from "../src/Counter.sol";
+
+contract CounterTest is Test {
+ Counter public counter;
+
+ function setUp() public {
+ counter = new Counter();
+ counter.setNumber(0);
+ }
+
+ function test_Increment() public {
+ counter.increment();
+ assertEq(counter.number(), 1);
+ }
+
+ function testFuzz_SetNumber(uint256 x) public {
+ counter.setNumber(x);
+ assertEq(counter.number(), x);
+ }
+}
diff --git a/framework/examples/myproject/go.mod b/framework/examples/myproject/go.mod
index 9ff73fdf7..55a200278 100644
--- a/framework/examples/myproject/go.mod
+++ b/framework/examples/myproject/go.mod
@@ -10,7 +10,7 @@ replace (
require (
github.com/ethereum/go-ethereum v1.14.11
github.com/go-resty/resty/v2 v2.15.3
- github.com/smartcontractkit/chainlink-testing-framework/framework v0.2.13
+ github.com/smartcontractkit/chainlink-testing-framework/framework v0.3.0
github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.9
github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.2
github.com/stretchr/testify v1.9.0
diff --git a/framework/examples/myproject/verify.toml b/framework/examples/myproject/verify.toml
new file mode 100644
index 000000000..7d015ef10
--- /dev/null
+++ b/framework/examples/myproject/verify.toml
@@ -0,0 +1,4 @@
+[contracts_src]
+[blockchain_a]
+ type = "anvil"
+ docker_cmd_params = ["-b", "1"]
\ No newline at end of file
diff --git a/framework/examples/myproject/verify_test.go b/framework/examples/myproject/verify_test.go
new file mode 100644
index 000000000..a148d6851
--- /dev/null
+++ b/framework/examples/myproject/verify_test.go
@@ -0,0 +1,45 @@
+package examples
+
+import (
+ "github.com/smartcontractkit/chainlink-testing-framework/framework"
+ "github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
+ "github.com/smartcontractkit/chainlink-testing-framework/framework/examples/example_components/onchain"
+ "github.com/smartcontractkit/chainlink-testing-framework/seth"
+ "github.com/stretchr/testify/require"
+ "testing"
+ "time"
+)
+
+type VerifyCfg struct {
+ ContractsSrc *onchain.Input `toml:"contracts_src" validate:"required"`
+ BlockchainA *blockchain.Input `toml:"blockchain_a" validate:"required"`
+}
+
+func TestVerify(t *testing.T) {
+ in, err := framework.Load[VerifyCfg](t)
+ require.NoError(t, err)
+
+ bc, err := blockchain.NewBlockchainNetwork(in.BlockchainA)
+ require.NoError(t, err)
+
+ scSrc, err := seth.NewClientBuilder().
+ WithRpcUrl(bc.Nodes[0].HostWSUrl).
+ WithPrivateKeys([]string{blockchain.DefaultAnvilPrivateKey}).
+ Build()
+ require.NoError(t, err)
+ in.ContractsSrc.URL = bc.Nodes[0].HostWSUrl
+ c, err := onchain.NewCounterDeployment(scSrc, in.ContractsSrc)
+ require.NoError(t, err)
+
+ t.Run("verify contract and test with debug", func(t *testing.T) {
+ // give Blockscout some time to index your transactions
+ // there is no API for that
+ time.Sleep(10 * time.Second)
+ err := blockchain.VerifyContract(bc, c.Addresses[0].String(),
+ "example_components/onchain",
+ "src/Counter.sol",
+ "Counter",
+ )
+ require.NoError(t, err)
+ })
+}