|
|
|
@ -1,8 +1,6 @@ |
|
|
|
|
package v2 |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"context" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"github.com/ethereum/go-ethereum" |
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi" |
|
|
|
@ -10,57 +8,74 @@ import ( |
|
|
|
|
"github.com/ethereum/go-ethereum/common" |
|
|
|
|
"github.com/ethereum/go-ethereum/core/types" |
|
|
|
|
"github.com/ethereum/go-ethereum/event" |
|
|
|
|
"math/big" |
|
|
|
|
"regexp" |
|
|
|
|
"strings" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
func FilterLogs[T any](instance bind.ContractInstance, opts *bind.FilterOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), topics ...[]any) (*EventIterator[T], error) { |
|
|
|
|
backend := instance.Backend() |
|
|
|
|
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend) |
|
|
|
|
logs, sub, err := c.FilterLogs(opts, eventID.String(), topics...) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return &EventIterator[T]{unpack: unpack, logs: logs, sub: sub}, nil |
|
|
|
|
type ContractInstance struct { |
|
|
|
|
Address common.Address |
|
|
|
|
Backend bind.ContractBackend |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// WatchOpts is the collection of options to fine tune subscribing for events
|
|
|
|
|
// within a bound contract.
|
|
|
|
|
type WatchOpts struct { |
|
|
|
|
Start *uint64 // Start of the queried range (nil = latest)
|
|
|
|
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func watchLogs(backend V2Backend, address common.Address, opts *WatchOpts, eventID common.Hash, query ...[]interface{}) (chan types.Log, event.Subscription, error) { |
|
|
|
|
// Don't crash on a lazy user
|
|
|
|
|
if opts == nil { |
|
|
|
|
opts = new(WatchOpts) |
|
|
|
|
} |
|
|
|
|
// Append the event selector to the query parameters and construct the topic set
|
|
|
|
|
query = append([][]interface{}{{eventID}}, query...) |
|
|
|
|
|
|
|
|
|
topics, err := abi.MakeTopics(query...) |
|
|
|
|
func DeployContracts(auth *bind.TransactOpts, backend bind.ContractBackend, constructorInput []byte, contracts map[string]*bind.MetaData) { |
|
|
|
|
// match if the contract has dynamic libraries that need to be linked
|
|
|
|
|
hasDepsMatcher, err := regexp.Compile("__\\$.*\\$__") |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, nil, err |
|
|
|
|
panic(err) |
|
|
|
|
} |
|
|
|
|
// Start the background filtering
|
|
|
|
|
logs := make(chan types.Log, 128) |
|
|
|
|
|
|
|
|
|
config := ethereum.FilterQuery{ |
|
|
|
|
Addresses: []common.Address{address}, |
|
|
|
|
Topics: topics, |
|
|
|
|
// deps we are linking
|
|
|
|
|
wipDeps := make(map[string]string) |
|
|
|
|
for id, metadata := range contracts { |
|
|
|
|
wipDeps[id] = metadata.Bin |
|
|
|
|
} |
|
|
|
|
if opts.Start != nil { |
|
|
|
|
config.FromBlock = new(big.Int).SetUint64(*opts.Start) |
|
|
|
|
|
|
|
|
|
// nested iteration: find contracts without library dependencies first,
|
|
|
|
|
// deploy them, link them into any other contracts that depend on them.
|
|
|
|
|
// repeat this until there are no more contracts to link/deploy
|
|
|
|
|
for { |
|
|
|
|
for id, contractBin := range wipDeps { |
|
|
|
|
if !hasDepsMatcher.MatchString(contractBin) { |
|
|
|
|
// this library/contract doesn't depend on any others
|
|
|
|
|
// it can be deployed as-is.
|
|
|
|
|
abi, err := contracts[id].GetAbi() |
|
|
|
|
if err != nil { |
|
|
|
|
panic(err) |
|
|
|
|
} |
|
|
|
|
addr, _, _, err := bind.DeployContractRaw(auth, *abi, []byte(contractBin), backend, constructorInput) |
|
|
|
|
if err != nil { |
|
|
|
|
panic(err) |
|
|
|
|
} |
|
|
|
|
delete(wipDeps, id) |
|
|
|
|
|
|
|
|
|
// embed the address of the deployed contract into any
|
|
|
|
|
// libraries/contracts that depend on it.
|
|
|
|
|
for id, contractBin := range wipDeps { |
|
|
|
|
contractBin = strings.ReplaceAll(contractBin, fmt.Sprintf("__$%s%__", id), fmt.Sprintf("__$%s$__", addr.String())) |
|
|
|
|
wipDeps[id] = contractBin |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if len(wipDeps) == 0 { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
sub, err := backend.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func FilterLogs[T any](instance *ContractInstance, opts *bind.FilterOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), topics ...[]any) (*EventIterator[T], error) { |
|
|
|
|
backend := instance.Backend |
|
|
|
|
c := bind.NewBoundContract(instance.Address, abi.ABI{}, backend, backend, backend) |
|
|
|
|
logs, sub, err := c.FilterLogs(opts, eventID.String(), topics...) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, nil, err |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return logs, sub, nil |
|
|
|
|
return &EventIterator[T]{unpack: unpack, logs: logs, sub: sub}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func WatchLogs[T any](address common.Address, backend V2Backend, opts *WatchOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) { |
|
|
|
|
logs, sub, err := watchLogs(backend, address, opts, eventID, topics...) |
|
|
|
|
func WatchLogs[T any](instance *ContractInstance, opts *bind.WatchOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) { |
|
|
|
|
backend := instance.Backend |
|
|
|
|
c := bind.NewBoundContract(instance.Address, abi.ABI{}, backend, backend, backend) |
|
|
|
|
logs, sub, err := c.WatchLogs(opts, eventID.String(), topics...) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
@ -166,167 +181,14 @@ func Transact(instance bind.ContractInstance, opts *bind.TransactOpts, input []b |
|
|
|
|
return c.RawTransact(opts, input) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ensureContext is a helper method to ensure a context is not nil, even if the
|
|
|
|
|
// user specified it as such.
|
|
|
|
|
func ensureContext(ctx context.Context) context.Context { |
|
|
|
|
if ctx == nil { |
|
|
|
|
return context.Background() |
|
|
|
|
} |
|
|
|
|
return ctx |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// SignerFn is a signer function callback when a contract requires a method to
|
|
|
|
|
// sign the transaction before submission.
|
|
|
|
|
type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) |
|
|
|
|
|
|
|
|
|
// TransactOpts is the collection of authorization data required to create a
|
|
|
|
|
// valid Ethereum transaction.
|
|
|
|
|
type TransactOpts struct { |
|
|
|
|
From common.Address // Ethereum account to send the transaction from
|
|
|
|
|
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
|
|
|
|
|
Signer SignerFn // Method to use for signing the transaction (mandatory)
|
|
|
|
|
|
|
|
|
|
Value *big.Int // Funds to transfer along the transaction (nil = 0 = no funds)
|
|
|
|
|
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
|
|
|
|
|
GasFeeCap *big.Int // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle)
|
|
|
|
|
GasTipCap *big.Int // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle)
|
|
|
|
|
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate)
|
|
|
|
|
AccessList types.AccessList // Access list to set for the transaction execution (nil = no access list)
|
|
|
|
|
|
|
|
|
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
|
|
|
|
|
|
|
|
|
NoSend bool // Do all transact steps but do not send the transaction
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func estimateGasLimit(backend V2Backend, address common.Hash, opts *TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int) (uint64, error) { |
|
|
|
|
if contract != nil { |
|
|
|
|
// Gas estimation cannot succeed without code for method invocations.
|
|
|
|
|
if code, err := backend.PendingCodeAt(ensureContext(opts.Context), address); err != nil { |
|
|
|
|
return 0, err |
|
|
|
|
} else if len(code) == 0 { |
|
|
|
|
return 0, ErrNoCode |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
msg := ethereum.CallMsg{ |
|
|
|
|
From: opts.From, |
|
|
|
|
To: contract, |
|
|
|
|
GasPrice: gasPrice, |
|
|
|
|
GasTipCap: gasTipCap, |
|
|
|
|
GasFeeCap: gasFeeCap, |
|
|
|
|
Value: value, |
|
|
|
|
Data: input, |
|
|
|
|
} |
|
|
|
|
return backend.EstimateGas(ensureContext(opts.Context), msg) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func getNonce(backend V2Backend, opts *TransactOpts) (uint64, error) { |
|
|
|
|
if opts.Nonce == nil { |
|
|
|
|
return backend.PendingNonceAt(ensureContext(opts.Context), opts.From) |
|
|
|
|
} else { |
|
|
|
|
return opts.Nonce.Uint64(), nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func createLegacyTx(backend V2Backend, address common.Hash, opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) { |
|
|
|
|
if opts.GasFeeCap != nil || opts.GasTipCap != nil || opts.AccessList != nil { |
|
|
|
|
return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas or accessList specified but london is not active yet") |
|
|
|
|
} |
|
|
|
|
// Normalize value
|
|
|
|
|
value := opts.Value |
|
|
|
|
if value == nil { |
|
|
|
|
value = new(big.Int) |
|
|
|
|
} |
|
|
|
|
// Estimate GasPrice
|
|
|
|
|
gasPrice := opts.GasPrice |
|
|
|
|
if gasPrice == nil { |
|
|
|
|
price, err := backend.SuggestGasPrice(ensureContext(opts.Context)) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
gasPrice = price |
|
|
|
|
} |
|
|
|
|
// Estimate GasLimit
|
|
|
|
|
gasLimit := opts.GasLimit |
|
|
|
|
if opts.GasLimit == 0 { |
|
|
|
|
var err error |
|
|
|
|
gasLimit, err = estimateGasLimit(backend, address, opts, contract, input, gasPrice, nil, nil, value) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// create the transaction
|
|
|
|
|
nonce, err := getNonce(backend, opts) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
baseTx := &types.LegacyTx{ |
|
|
|
|
To: contract, |
|
|
|
|
Nonce: nonce, |
|
|
|
|
GasPrice: gasPrice, |
|
|
|
|
Gas: gasLimit, |
|
|
|
|
Value: value, |
|
|
|
|
Data: input, |
|
|
|
|
} |
|
|
|
|
return types.NewTx(baseTx), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const basefeeWiggleMultiplier = 2 |
|
|
|
|
|
|
|
|
|
func createDynamicTx(backend V2Backend, opts *TransactOpts, contract *common.Address, input []byte, head *types.Header) (*types.Transaction, error) { |
|
|
|
|
// Normalize value
|
|
|
|
|
value := opts.Value |
|
|
|
|
if value == nil { |
|
|
|
|
value = new(big.Int) |
|
|
|
|
} |
|
|
|
|
// Estimate TipCap
|
|
|
|
|
gasTipCap := opts.GasTipCap |
|
|
|
|
if gasTipCap == nil { |
|
|
|
|
tip, err := backend.SuggestGasTipCap(ensureContext(opts.Context)) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
gasTipCap = tip |
|
|
|
|
} |
|
|
|
|
// Estimate FeeCap
|
|
|
|
|
gasFeeCap := opts.GasFeeCap |
|
|
|
|
if gasFeeCap == nil { |
|
|
|
|
gasFeeCap = new(big.Int).Add( |
|
|
|
|
gasTipCap, |
|
|
|
|
new(big.Int).Mul(head.BaseFee, big.NewInt(basefeeWiggleMultiplier)), |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
if gasFeeCap.Cmp(gasTipCap) < 0 { |
|
|
|
|
return nil, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", gasFeeCap, gasTipCap) |
|
|
|
|
} |
|
|
|
|
// Estimate GasLimit
|
|
|
|
|
gasLimit := opts.GasLimit |
|
|
|
|
if opts.GasLimit == 0 { |
|
|
|
|
var err error |
|
|
|
|
gasLimit, err = c.estimateGasLimit(opts, contract, input, nil, gasTipCap, gasFeeCap, value) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// create the transaction
|
|
|
|
|
nonce, err := c.getNonce(opts) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
baseTx := &types.DynamicFeeTx{ |
|
|
|
|
To: contract, |
|
|
|
|
Nonce: nonce, |
|
|
|
|
GasFeeCap: gasFeeCap, |
|
|
|
|
GasTipCap: gasTipCap, |
|
|
|
|
Gas: gasLimit, |
|
|
|
|
Value: value, |
|
|
|
|
Data: input, |
|
|
|
|
AccessList: opts.AccessList, |
|
|
|
|
} |
|
|
|
|
return types.NewTx(baseTx), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func Transfer(instance bind.ContractInstance, opts *bind.TransactOpts) (*types.Transaction, error) { |
|
|
|
|
backend := instance.Backend() |
|
|
|
|
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend) |
|
|
|
|
return c.Transfer(opts) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func CallRaw(instance bind.ContractInstance, opts *bind.CallOpts, input []byte) ([]byte, error) { |
|
|
|
|
backend := instance.Backend() |
|
|
|
|
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend) |
|
|
|
|
return c.CallRaw(opts, input) |
|
|
|
|
} |
|
|
|
|