Skip to content

Commit dc46d3b

Browse files
authored
TT-1716 Review and improve Seth documentation (#1312)
1 parent 4a77370 commit dc46d3b

File tree

7 files changed

+63
-43
lines changed

7 files changed

+63
-43
lines changed

book/src/libs/seth.md

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ Reliable and debug-friendly Ethereum client
3636
15. [Bulk transaction tracing](#bulk-transaction-tracing)
3737
16. [RPC traffic logging](#rpc-traffic-logging)
3838
17. [Read-only mode](#read-only-mode)
39+
18. [ABI Finder](#abi-finder)
40+
19. [Contract Map](#contract-map)
41+
20. [Contract Store](#contract-store)
3942

4043
## Goals
4144

@@ -80,7 +83,7 @@ You can read more about how ABI finding and contract map works [here](../../../s
8083

8184
## Examples
8285

83-
Check [examples](../../../seth/examples) folder
86+
Check [examples](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/seth/examples) folder
8487

8588
Lib provides a small amount of helpers for decoding handling that you can use with vanilla `go-ethereum` generated wrappers
8689

@@ -231,7 +234,7 @@ client, err := NewClientBuilder().
231234
WithEIP1559DynamicFees(true).
232235
WithDynamicGasPrices(120_000_000_000, 44_000_000_000).
233236
WithGasPriceEstimations(true, 10, seth.Priority_Fast).
234-
// gas bumping: retries, max gas price, bumping strategy function
237+
// gas bumping: retries, max gas price, bumping strategy function
235238
WithGasBumping(5, 100_000_000_000, PriorityBasedGasBumpingStrategyFn).
236239
Build()
237240

@@ -839,3 +842,47 @@ Additionally, when the client is build anc `cfg.ReadOnly = true` is set, we will
839842
* RPC health check is disabled
840843
* pending nonce protection is disabled
841844
* gas bumping is disabled
845+
846+
## ABI Finder
847+
848+
In order to be able to decode and trace transactions and calls between smart contracts we need their ABIs. Unfortunately it might happen that two or more contracts have methods with the same signatures, which might result in incorrect tracing. To make that problem less severe we have decided to add a single point of entry for contract deployment in Seth as that way we always know what contract is deployed at which address and thus avoid incorrect tracing due to potentially ambiguous method signatures.
849+
850+
1. We don’t know what contract (ABI) is located at a given address. Should be the case, when the contract either wasn’t uploaded via Seth or we haven’t supplied Seth with a contract map as part of its configuration (more on that later).
851+
852+
a. We sequentially iterate over all known ABIs (map: `name -> ABI_name`) checking whether it has a method with a given signature. Once we get a first match we will upsert that (`address -> ABI_name`) data into the contract map and return the ABI.
853+
854+
The caveat here is that if the method we are searching for belongs is present in more than one ABI we might associate the address with an incorrect address (we will use the first match).
855+
856+
b. If no match is found we will return an error.
857+
858+
2. We know what ABI is located at a given address. It should be the case, when we have either uploaded the contract via Seth, provided Seth with a contract map or already traced a transaction to that address and found an ABI with matching method signature.
859+
860+
a. We fetch the corresponding ABIand check if it indeed contains the method we are looking for (as mentioned earlier in some cases it might not be the case).
861+
862+
b. If it does, we return the ABI.
863+
864+
c. If it doesn’t we iterate over all known ABIs, in the same way as in 1a. If we find a match we update the (`address -> ABI_name`) association in the contract map and return the ABI.
865+
866+
It is possible that this will happen multiple times in case we have multiple contracts with multiple identical methods, but given a sufficiently diverse set of methods that were called we should eventually arrive at a fully correct contract map.
867+
868+
d. If no match is found we will return an error.
869+
870+
### Example
871+
872+
![tracing_example](./images/tracing_example.png)
873+
874+
## Contract Map
875+
876+
We support in-memory contract map and a TOML file contract map that keeps the association of (`address -> ABI_name`). The latter map is only used for non-simulated networks. Every time we deploy a contract we save (`address -> ABI_name`) entry in the in-memory map.If the network is not a simulated one we also save it in a file. That file can later be pointed to in Seth configuration and we will load the contract map from it (**currently without validating whether we have all the ABIs mentioned in the file**).
877+
878+
When saving contract deployment information we will either generate filename for you (if you didn’t configure Seth to use a particular file) using the pattern of `deployed_contracts_${network_name}_${timestamp}.toml` or use the filename provided in Seth TOML configuration file.
879+
880+
It has to be noted that the file contract map is currently updated only, when new contracts are deployed. There’s no mechanism for updating it if we found the mapping invalid (which might be the case if you manually created the entry in the file).
881+
882+
## Contract Store
883+
884+
Seth can be used with the contract store, but it would have very limited usage as transaction decoding and tracing cannot work with ABIs. Thus, when you initialise Seth with contract store it is necessary that it can load at least one ABI.
885+
886+
Another use of Contract Store is simplified contract deployment. For that we also need the contract's bytecode. The contract store can be used to store the bytecode of the contract and then deploy it using the `DeployContractFromContractStore(auth *bind.TransactOpts, name string, backend bind.ContractBackend, params ...interface{})` method. When Seth is intialisied with the contract store and no bytecode files (`*.bin`) are provided, it will log a warning, but initialise successfully nonetheless.
887+
888+
If bytecode file wasn't provided you need to use `DeployContract(auth *bind.TransactOpts, name string, abi abi.ABI, bytecode []byte, backend bind.ContractBackend, params ...interface{})` method, which expects you to provide contract name (best if equal to the name of the ABI file), bytecode and the ABI.

seth/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ func NewClientWithConfig(cfg *Config) (*Client, error) {
155155
)
156156
}
157157

158+
// ValidateConfig checks and validates the provided Config struct.
159+
// It ensures essential fields have valid values or default to appropriate values
160+
// when necessary. This function performs validation on gas price estimation,
161+
// gas limit, tracing level, trace outputs, network dial timeout, and pending nonce protection timeout.
162+
// If any configuration is invalid, it returns an error.
158163
func ValidateConfig(cfg *Config) error {
159164
if cfg.Network.GasPriceEstimationEnabled {
160165
if cfg.Network.GasPriceEstimationBlocks == 0 {
@@ -454,6 +459,10 @@ func (m *Client) checkRPCHealth() error {
454459
return nil
455460
}
456461

462+
// TransferETHFromKey initiates a transfer of Ether from a specified key to a recipient address.
463+
// It validates the private key index, estimates gas limit if not provided, and sends the transaction.
464+
// The function signs and sends an Ethereum transaction using the specified fromKeyNum and recipient address,
465+
// with the specified amount and gas price.
457466
func (m *Client) TransferETHFromKey(ctx context.Context, fromKeyNum int, to string, value *big.Int, gasPrice *big.Int) error {
458467
if err := m.validatePrivateKeysKeyNum(fromKeyNum); err != nil {
459468
return err

seth/decode.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ func (m *Client) handleDisabledTracing(l zerolog.Logger, decoded DecodedTransact
337337
}
338338
}
339339

340+
// MergeEventData merges new event data into the existing EventData map in the DecodedTransactionLog.
340341
func (d *DecodedCommonLog) MergeEventData(newEventData map[string]interface{}) {
341342
if d.EventData == nil {
342343
d.EventData = make(map[string]interface{})
@@ -541,6 +542,8 @@ func (m *Client) CallMsgFromTx(tx *types.Transaction) (ethereum.CallMsg, error)
541542
}, nil
542543
}
543544

545+
// DownloadContractAndGetPragma retrieves the bytecode of a contract at a specified address and block,
546+
// then decodes it to extract the pragma version. Returns the pragma version or an error if retrieval or decoding fails.
544547
func (m *Client) DownloadContractAndGetPragma(address common.Address, block *big.Int) (Pragma, error) {
545548
bytecode, err := m.Client.CodeAt(context.Background(), address, block)
546549
if err != nil {

seth/docs/abi_finder_contract_map.md

Lines changed: 0 additions & 33 deletions
This file was deleted.

seth/docs/contract_store.md

Lines changed: 0 additions & 7 deletions
This file was deleted.

seth/gas.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ func NewGasEstimator(c *Client) *GasEstimator {
1919
return &GasEstimator{Client: c}
2020
}
2121

22-
// Stats prints gas stats
22+
// Stats calculates gas price and tip cap suggestions based on historical fee data over a specified number of blocks.
23+
// It computes quantiles for base fees and tip caps and provides suggested gas price and tip cap values.
2324
func (m *GasEstimator) Stats(fromNumber uint64, priorityPerc float64) (GasSuggestions, error) {
2425
bn, err := m.Client.Client.BlockNumber(context.Background())
2526
if err != nil {

0 commit comments

Comments
 (0)