Skip to content

Commit e1f0879

Browse files
committed
improve error messages
1 parent b5b830a commit e1f0879

File tree

12 files changed

+527
-98
lines changed

12 files changed

+527
-98
lines changed

seth/abi_finder.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package seth
22

33
import (
4+
"fmt"
45
"strings"
56

67
"github.com/ethereum/go-ethereum/accounts/abi"
@@ -127,7 +128,37 @@ func (a *ABIFinder) FindABIByMethod(address string, signature []byte) (ABIFinder
127128
}
128129

129130
if result.Method == nil {
130-
return ABIFinderResult{}, errors.New(ErrNoABIMethod)
131+
abiCount := len(a.ContractStore.ABIs)
132+
abiSample := ""
133+
if abiCount > 0 {
134+
// Show first few ABIs as examples (max 5)
135+
sampleSize := min(abiCount, 5)
136+
samples := make([]string, 0, sampleSize)
137+
count := 0
138+
for abiName := range a.ContractStore.ABIs {
139+
if count >= sampleSize {
140+
break
141+
}
142+
samples = append(samples, strings.TrimSuffix(abiName, ".abi"))
143+
count++
144+
}
145+
abiSample = fmt.Sprintf("\nExample ABIs loaded: %s", strings.Join(samples, ", "))
146+
if abiCount > sampleSize {
147+
abiSample += fmt.Sprintf(" (and %d more)", abiCount-sampleSize)
148+
}
149+
}
150+
151+
return ABIFinderResult{}, fmt.Errorf("no ABI found with method signature %s for contract at address %s.\n"+
152+
"Checked %d ABIs but none matched.%s\n"+
153+
"This usually means:\n"+
154+
" 1. The contract ABI wasn't loaded into Seth's contract store\n"+
155+
" 2. The method signature doesn't match any known ABI\n"+
156+
" 3. You're calling a non-existent contract address\n"+
157+
"Solutions:\n"+
158+
" 1. Add the contract's ABI to the directory specified by 'abi_dir'\n"+
159+
" 2. Use ContractStore.AddABI() to add it programmatically\n"+
160+
" 3. Deploy the contract via Seth so it's automatically registered",
161+
stringSignature, address, abiCount, abiSample)
131162
}
132163

133164
return result, nil

seth/block_stats.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ func (cs *BlockStats) Stats(startBlock *big.Int, endBlock *big.Int) error {
6262
endBlock = latestBlockNumber
6363
}
6464
if endBlock != nil && startBlock.Int64() > endBlock.Int64() {
65-
return fmt.Errorf("start block is less than the end block")
65+
return fmt.Errorf("start block (%d) is greater than end block (%d). "+
66+
"Ensure start block comes before end block in the range",
67+
startBlock.Int64(), endBlock.Int64())
6668
}
6769
L.Info().
6870
Int64("EndBlock", endBlock.Int64()).
@@ -107,7 +109,7 @@ func (cs *BlockStats) Stats(startBlock *big.Int, endBlock *big.Int) error {
107109
// CalculateBlockDurations calculates and logs the duration, TPS, gas used, and gas limit between each consecutive block
108110
func (cs *BlockStats) CalculateBlockDurations(blocks []*types.Block) error {
109111
if len(blocks) == 0 {
110-
return fmt.Errorf("no blocks no analyze")
112+
return fmt.Errorf("no blocks to analyze. Cannot calculate block durations without block data")
111113
}
112114
var (
113115
durations []time.Duration

seth/client.go

Lines changed: 215 additions & 37 deletions
Large diffs are not rendered by default.

seth/config.go

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"github.com/ethereum/go-ethereum/crypto"
1414
"github.com/ethereum/go-ethereum/ethclient/simulated"
1515
"github.com/pelletier/go-toml/v2"
16-
"github.com/pkg/errors"
1716
)
1817

1918
const (
@@ -131,16 +130,30 @@ func DefaultClient(rpcUrl string, privateKeys []string) (*Client, error) {
131130
func ReadConfig() (*Config, error) {
132131
cfgPath := os.Getenv(CONFIG_FILE_ENV_VAR)
133132
if cfgPath == "" {
134-
return nil, errors.New(ErrEmptyConfigPath)
133+
return nil, fmt.Errorf("SETH_CONFIG_PATH environment variable is not set. "+
134+
"Set it to the absolute path of your seth.toml configuration file.\n"+
135+
"Example: export SETH_CONFIG_PATH=/path/to/your/seth.toml")
135136
}
136137
var cfg *Config
137138
d, err := os.ReadFile(cfgPath)
138139
if err != nil {
139-
return nil, errors.Wrap(err, ErrReadSethConfig)
140+
return nil, fmt.Errorf("failed to read Seth config file at '%s': %w\n"+
141+
"Ensure:\n"+
142+
" 1. The file exists at the specified path\n"+
143+
" 2. You have read permissions for the file\n"+
144+
" 3. The path is correct (set via SETH_CONFIG_PATH or parameter)",
145+
cfgPath, err)
140146
}
141147
err = toml.Unmarshal(d, &cfg)
142148
if err != nil {
143-
return nil, errors.Wrap(err, ErrUnmarshalSethConfig)
149+
return nil, fmt.Errorf("failed to parse Seth TOML config from '%s': %w\n"+
150+
"Ensure the file contains valid TOML syntax. "+
151+
"Common issues:\n"+
152+
" 1. Missing quotes around string values\n"+
153+
" 2. Invalid array or table syntax\n"+
154+
" 3. Duplicate keys\n"+
155+
"See example config: https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/seth/seth.toml",
156+
cfgPath, err)
144157
}
145158
absPath, err := filepath.Abs(cfgPath)
146159
if err != nil {
@@ -162,7 +175,15 @@ func ReadConfig() (*Config, error) {
162175
url := os.Getenv(URL_ENV_VAR)
163176

164177
if url == "" {
165-
return nil, fmt.Errorf("network not selected, set %s=... or %s=..., check TOML config for available networks", NETWORK_ENV_VAR, URL_ENV_VAR)
178+
availableNetworks := make([]string, 0, len(cfg.Networks))
179+
for _, n := range cfg.Networks {
180+
availableNetworks = append(availableNetworks, n.Name)
181+
}
182+
return nil, fmt.Errorf("network not selected. Set either:\n"+
183+
" - SETH_NETWORK to one of: %s\n"+
184+
" - SETH_URL to a custom RPC endpoint\n"+
185+
"Check your TOML config at %s for network details",
186+
strings.Join(availableNetworks, ", "), cfgPath)
166187
}
167188

168189
//look for default network
@@ -182,13 +203,21 @@ func ReadConfig() (*Config, error) {
182203
}
183204

184205
if cfg.Network == nil {
185-
return nil, fmt.Errorf("default network not defined in the TOML file")
206+
return nil, fmt.Errorf("default network not defined in the TOML file at %s. "+
207+
"Add a network with name='%s' or specify a network using SETH_NETWORK environment variable",
208+
cfgPath, DefaultNetworkName)
186209
}
187210
}
188211

189212
rootPrivateKey := os.Getenv(ROOT_PRIVATE_KEY_ENV_VAR)
190213
if rootPrivateKey == "" {
191-
return nil, errors.Errorf(ErrEmptyRootPrivateKey, ROOT_PRIVATE_KEY_ENV_VAR)
214+
return nil, fmt.Errorf("no root private key was set. "+
215+
"You can provide the root private key via:\n"+
216+
" 1. %s environment variable (without 0x prefix)\n"+
217+
" 2. 'root_private_key' field in seth.toml\n"+
218+
" 3. WithPrivateKeys() when using ClientBuilder\n"+
219+
"WARNING: Never commit private keys to source control. Use environment variables or secure secret management",
220+
ROOT_PRIVATE_KEY_ENV_VAR)
192221
}
193222
cfg.Network.PrivateKeys = append(cfg.Network.PrivateKeys, rootPrivateKey)
194223
if cfg.Network.DialTimeout == nil {
@@ -205,7 +234,13 @@ func ReadConfig() (*Config, error) {
205234
// If any configuration is invalid, it returns an error.
206235
func (c *Config) Validate() error {
207236
if c.Network == nil {
208-
return errors.New(ErrNetworkIsNil)
237+
return fmt.Errorf("network configuration is nil. "+
238+
"This usually means the network wasn't selected or configured properly.\n"+
239+
"Solutions:\n"+
240+
" 1. Set SETH_NETWORK environment variable to match a network name in seth.toml (e.g., SETH_NETWORK=sepolia)\n"+
241+
" 2. Ensure your seth.toml has a [[networks]] section with 'name' field matching SETH_NETWORK\n"+
242+
" 3. Use ClientBuilder with WithNetwork() to configure the network programmatically\n"+
243+
"See documentation for configuration examples")
209244
}
210245

211246
if c.Network.GasPriceEstimationEnabled {
@@ -225,12 +260,20 @@ func (c *Config) Validate() error {
225260
case Priority_Slow:
226261
case Priority_Auto:
227262
default:
228-
return errors.New("when automating gas estimation is enabled priority must be auto, fast, standard or slow. fix it or disable gas estimation")
263+
return fmt.Errorf("invalid gas estimation priority '%s'. "+
264+
"Must be one of: 'auto', 'fast', 'standard' or 'slow'. "+
265+
"Set 'gas_price_estimation_tx_priority' in your seth.toml config. "+
266+
"To disable gas estimation, set 'gas_price_estimation_enabled = false'",
267+
c.Network.GasPriceEstimationTxPriority)
229268
}
230269

231270
if c.GasBump != nil && c.GasBump.Retries > 0 &&
232271
c.Network.GasPriceEstimationTxPriority == Priority_Auto {
233-
return errors.New("gas bumping is not compatible with auto priority gas estimation")
272+
return fmt.Errorf("configuration conflict: gas bumping (retries=%d) is not compatible with auto priority gas estimation. "+
273+
"Either:\n"+
274+
" 1. Set gas_price_estimation_tx_priority to 'fast', 'standard', or 'slow'\n"+
275+
" 2. Set gas_bump.retries = 0 to disable gas bumping",
276+
c.GasBump.Retries)
234277
}
235278
}
236279

@@ -254,7 +297,10 @@ func (c *Config) Validate() error {
254297
case TracingLevel_Reverted:
255298
case TracingLevel_All:
256299
default:
257-
return errors.New("tracing level must be one of: NONE, REVERTED, ALL")
300+
return fmt.Errorf("invalid tracing level '%s'. Must be one of: 'NONE', 'REVERTED', 'ALL'. "+
301+
"Set 'tracing_level' in your seth.toml config.\n"+
302+
"Recommended: 'REVERTED' for debugging failed transactions, 'NONE' to disable tracing completely",
303+
c.TracingLevel)
258304
}
259305

260306
for _, output := range c.TraceOutputs {
@@ -263,7 +309,10 @@ func (c *Config) Validate() error {
263309
case TraceOutput_JSON:
264310
case TraceOutput_DOT:
265311
default:
266-
return errors.New("trace output must be one of: console, json, dot")
312+
return fmt.Errorf("invalid trace output '%s'. Must be one of: 'console', 'json', 'dot'. "+
313+
"Set 'trace_outputs' in your seth.toml config. "+
314+
"You can specify multiple outputs as an array",
315+
output)
267316
}
268317
}
269318

@@ -276,11 +325,18 @@ func (c *Config) Validate() error {
276325
}
277326

278327
if c.ethclient == nil && len(c.Network.URLs) == 0 {
279-
return errors.New("at least one url should be present in config in 'secret_urls = []'")
328+
return fmt.Errorf("no RPC URLs configured. "+
329+
"You can provide RPC URLs via:\n"+
330+
" 1. 'urls_secret' field in seth.toml: urls_secret = [\"http://your-rpc-url\"]\n"+
331+
" 2. WithRPCURLs() when using ClientBuilder\n"+
332+
" 3. WithEthClient() to provide a pre-configured ethclient instance")
280333
}
281334

282335
if c.ethclient != nil && len(c.Network.URLs) > 0 {
283-
return errors.New(EthClientAndUrlsSet)
336+
return fmt.Errorf("configuration conflict: both ethclient instance and RPC URLs are set. "+
337+
"You cannot set both. Either:\n"+
338+
" 1. Use the provided ethclient (remove 'urls_secret' from config)\n"+
339+
" 2. Use RPC URLs from config (don't provide ethclient)")
284340
}
285341

286342
return nil

seth/contract_store.go

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313

1414
"github.com/ethereum/go-ethereum/accounts/abi"
1515
"github.com/ethereum/go-ethereum/common"
16-
"github.com/pkg/errors"
1716
)
1817

1918
const (
@@ -111,7 +110,12 @@ func NewContractStore(abiPath, binPath string, gethWrappersPaths []string) (*Con
111110

112111
err = cs.loadGethWrappers(gethWrappersPaths)
113112
if err != nil {
114-
return nil, errors.Wrapf(err, "failed to load geth wrappers from %v", gethWrappersPaths)
113+
return nil, fmt.Errorf("failed to load geth wrappers from %v: %w\n"+
114+
"Ensure:\n"+
115+
" 1. The paths point to valid Go files with geth-generated contract wrappers\n"+
116+
" 2. Files contain properly formatted ABI JSON in comments\n"+
117+
" 3. The wrapper files were generated with abigen tool",
118+
gethWrappersPaths, err)
115119
}
116120

117121
return cs, nil
@@ -129,18 +133,29 @@ func (c *ContractStore) loadABIs(abiPath string) error {
129133
L.Debug().Str("File", f.Name()).Msg("ABI file loaded")
130134
ff, err := os.Open(filepath.Join(abiPath, f.Name()))
131135
if err != nil {
132-
return errors.Wrap(err, ErrOpenABIFile)
136+
return fmt.Errorf("failed to open ABI file '%s': %w\n"+
137+
"Ensure the file exists and has proper read permissions",
138+
filepath.Join(abiPath, f.Name()), err)
133139
}
134140
a, err := abi.JSON(ff)
135141
if err != nil {
136-
return errors.Wrap(err, ErrParseABI)
142+
return fmt.Errorf("failed to parse ABI file '%s': %w\n"+
143+
"Ensure the file contains valid JSON ABI format. "+
144+
"ABI files should be generated from contract compilation (e.g., solc, hardhat, foundry)",
145+
f.Name(), err)
137146
}
138147
c.ABIs[f.Name()] = a
139148
foundABI = true
140149
}
141150
}
142151
if !foundABI {
143-
return fmt.Errorf("no ABI files found in '%s'. Fix the path or comment out 'abi_dir' setting", abiPath)
152+
return fmt.Errorf("no ABI files found in '%s'. "+
153+
"Ensure:\n"+
154+
" 1. The directory exists and is readable\n"+
155+
" 2. Files have .abi extension\n"+
156+
" 3. Path is correct (should be relative to config file or absolute)\n"+
157+
"Or comment out 'abi_dir' in config if not using ABI files",
158+
abiPath)
144159
}
145160
}
146161

@@ -159,14 +174,23 @@ func (c *ContractStore) loadBINs(binPath string) error {
159174
L.Debug().Str("File", f.Name()).Msg("BIN file loaded")
160175
bin, err := os.ReadFile(filepath.Join(binPath, f.Name()))
161176
if err != nil {
162-
return errors.Wrap(err, ErrOpenBINFile)
177+
return fmt.Errorf("failed to open BIN file '%s': %w\n"+
178+
"Ensure the file exists and has proper read permissions",
179+
filepath.Join(binPath, f.Name()), err)
163180
}
164181
c.BINs[f.Name()] = common.FromHex(string(bin))
165182
foundBIN = true
166183
}
167184
}
168185
if !foundBIN {
169-
return fmt.Errorf("no BIN files found in '%s'. Fix the path or comment out 'bin_dir' setting", binPath)
186+
return fmt.Errorf("no BIN files (bytecode) found in '%s'. "+
187+
"BIN files are needed for contract deployment. "+
188+
"Ensure:\n"+
189+
" 1. Files have .bin extension\n"+
190+
" 2. They contain compiled contract bytecode (hex-encoded)\n"+
191+
" 3. Path is correct (should be relative to config file or absolute)\n"+
192+
"Or comment out 'bin_dir' in config if deploying contracts via other means",
193+
binPath)
170194
}
171195
}
172196

@@ -253,12 +277,18 @@ TOP_LOOP:
253277
// this cleans up all escape and similar characters that might interfere with the JSON unmarshalling
254278
var rawAbi interface{}
255279
if err := json.Unmarshal([]byte(abiContent), &rawAbi); err != nil {
256-
return "", nil, errors.Wrap(err, "failed to unmarshal ABI content")
280+
return "", nil, fmt.Errorf("failed to unmarshal ABI content from '%s': %w\n"+
281+
"The ABI JSON in the wrapper file is malformed. "+
282+
"Ensure the file was generated correctly with abigen",
283+
filePath, err)
257284
}
258285

259286
parsedAbi, err := abi.JSON(strings.NewReader(fmt.Sprint(rawAbi)))
260287
if err != nil {
261-
return "", nil, errors.Wrap(err, "failed to parse ABI content")
288+
return "", nil, fmt.Errorf("failed to parse ABI content from '%s': %w\n"+
289+
"The ABI structure is invalid. "+
290+
"Regenerate the wrapper file with abigen",
291+
filePath, err)
262292
}
263293

264294
return contractName, &parsedAbi, nil

0 commit comments

Comments
 (0)