From 702527f5bf080f9b013c090d3eac4b03501ae9f5 Mon Sep 17 00:00:00 2001 From: Sergey Kudasov Date: Mon, 2 Dec 2024 11:45:39 +0100 Subject: [PATCH] Verify with Blockscout (#1413) * example foundry contract * programmatic verification * docs * update regex in reload --- book/src/SUMMARY.md | 1 + .../src/framework/observability/blockscout.md | 7 +- book/src/framework/verify.md | 16 + framework/.changeset/v0.3.1.md | 3 +- .../blockscout/docker-compose.yml | 1 + framework/components/blockchain/verify.go | 22 ++ .../components/simple_node_set/reload.go | 2 +- framework/docker.go | 11 + .../onchain/.github/workflows/test.yml | 34 +++ .../example_components/onchain/.gitignore | 14 + .../example_components/onchain/README.md | 66 +++++ .../onchain/deployment_counter.go | 32 ++ .../example_components/onchain/foundry.toml | 7 + .../onchain/gethwrappers/counter.go | 276 ++++++++++++++++++ .../example_components/onchain/lib/forge-std | 1 + .../onchain/script/Counter.s.sol | 12 + .../onchain/src/Counter.sol | 14 + .../onchain/test/Counter.t.sol | 24 ++ framework/examples/myproject/go.mod | 2 +- framework/examples/myproject/verify.toml | 4 + framework/examples/myproject/verify_test.go | 45 +++ 21 files changed, 585 insertions(+), 9 deletions(-) create mode 100644 book/src/framework/verify.md create mode 100644 framework/components/blockchain/verify.go create mode 100644 framework/examples/myproject/example_components/onchain/.github/workflows/test.yml create mode 100644 framework/examples/myproject/example_components/onchain/.gitignore create mode 100644 framework/examples/myproject/example_components/onchain/README.md create mode 100644 framework/examples/myproject/example_components/onchain/deployment_counter.go create mode 100644 framework/examples/myproject/example_components/onchain/foundry.toml create mode 100644 framework/examples/myproject/example_components/onchain/gethwrappers/counter.go create mode 160000 framework/examples/myproject/example_components/onchain/lib/forge-std create mode 100644 framework/examples/myproject/example_components/onchain/script/Counter.s.sol create mode 100644 framework/examples/myproject/example_components/onchain/src/Counter.sol create mode 100644 framework/examples/myproject/example_components/onchain/test/Counter.t.sol create mode 100644 framework/examples/myproject/verify.toml create mode 100644 framework/examples/myproject/verify_test.go 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) + }) +}