2024-11-04 04:32:26 -06:00
|
|
|
package v2
|
|
|
|
|
|
|
|
import (
|
2024-11-24 06:35:16 -06:00
|
|
|
"encoding/hex"
|
2024-11-06 07:28:54 -06:00
|
|
|
"fmt"
|
2024-11-04 04:32:26 -06:00
|
|
|
"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"
|
2024-11-20 05:57:03 -06:00
|
|
|
"regexp"
|
|
|
|
"strings"
|
2024-11-04 04:32:26 -06:00
|
|
|
)
|
|
|
|
|
2024-11-20 05:57:03 -06:00
|
|
|
type ContractInstance struct {
|
|
|
|
Address common.Address
|
|
|
|
Backend bind.ContractBackend
|
2024-11-04 04:32:26 -06:00
|
|
|
}
|
|
|
|
|
2024-11-26 04:18:20 -06:00
|
|
|
// deployContract deploys a hex-encoded contract with the given constructor
|
|
|
|
// input. It returns the deployment transaction, address on success.
|
2024-11-24 08:00:26 -06:00
|
|
|
func deployContract(backend bind.ContractBackend, auth *bind.TransactOpts, constructor []byte, contract string) (deploymentTx *types.Transaction, deploymentAddr common.Address, err error) {
|
|
|
|
contractBinBytes, err := hex.DecodeString(contract[2:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, common.Address{}, fmt.Errorf("contract bytecode is not a hex string: %s", contractBinBytes[2:])
|
|
|
|
}
|
|
|
|
addr, tx, _, err := bind.DeployContractRaw(auth, contractBinBytes, backend, constructor)
|
|
|
|
if err != nil {
|
|
|
|
return nil, common.Address{}, fmt.Errorf("failed to deploy contract: %v", err)
|
|
|
|
}
|
|
|
|
return tx, addr, nil
|
|
|
|
}
|
|
|
|
|
2024-11-26 04:18:20 -06:00
|
|
|
// deployLibs iterates the set contracts (map of pattern to hex-encoded
|
|
|
|
// contract deploy code). each value in contracts is deployed, and the
|
|
|
|
// resulting addresses/deployment-txs are returned on success.
|
2024-11-24 08:53:31 -06:00
|
|
|
func deployLibs(backend bind.ContractBackend, auth *bind.TransactOpts, contracts map[string]string) (deploymentTxs map[common.Address]*types.Transaction, deployAddrs map[string]common.Address, err error) {
|
|
|
|
deploymentTxs = make(map[common.Address]*types.Transaction)
|
|
|
|
deployAddrs = make(map[string]common.Address)
|
|
|
|
|
|
|
|
for pattern, contractBin := range contracts {
|
2024-11-24 06:35:16 -06:00
|
|
|
contractBinBytes, err := hex.DecodeString(contractBin[2:])
|
|
|
|
if err != nil {
|
|
|
|
return deploymentTxs, deployAddrs, fmt.Errorf("contract bytecode is not a hex string: %s", contractBin[2:])
|
|
|
|
}
|
2024-11-24 08:00:26 -06:00
|
|
|
// TODO: can pass nil for constructor?
|
|
|
|
addr, tx, _, err := bind.DeployContractRaw(auth, contractBinBytes, backend, []byte{})
|
2024-11-24 06:35:16 -06:00
|
|
|
if err != nil {
|
|
|
|
return deploymentTxs, deployAddrs, fmt.Errorf("failed to deploy contract: %v", err)
|
|
|
|
}
|
|
|
|
deploymentTxs[addr] = tx
|
2024-11-24 08:53:31 -06:00
|
|
|
deployAddrs[pattern] = addr
|
2024-11-24 06:35:16 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return deploymentTxs, deployAddrs, nil
|
|
|
|
}
|
|
|
|
|
2024-11-26 04:18:20 -06:00
|
|
|
// linkContract takes an unlinked contract deploy code (contract) a map of
|
|
|
|
// linked-and-deployed library dependencies, replaces references to library
|
|
|
|
// deps in the contract code, and returns the contract deployment bytecode on
|
|
|
|
// success.
|
2024-11-24 08:00:26 -06:00
|
|
|
func linkContract(contract string, linkedLibs map[string]common.Address) (deployableContract string, err error) {
|
|
|
|
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
// link in any library the contract depends on
|
|
|
|
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contract, -1) {
|
|
|
|
matchingPattern := match[1]
|
|
|
|
addr := linkedLibs[matchingPattern]
|
2024-11-24 08:53:31 -06:00
|
|
|
contract = strings.ReplaceAll(contract, "__$"+matchingPattern+"$__", addr.String()[2:])
|
2024-11-24 08:00:26 -06:00
|
|
|
}
|
|
|
|
return contract, nil
|
|
|
|
}
|
|
|
|
|
2024-11-26 04:18:20 -06:00
|
|
|
// linkLibs iterates the set of dependencies that have yet to be
|
|
|
|
// linked/deployed (pending), replacing references to library dependencies
|
|
|
|
// if those dependencies are fully linked/deployed (in 'linked').
|
|
|
|
//
|
|
|
|
// contracts that have become fully linked in the current invocation are
|
|
|
|
// returned in the resulting map.
|
|
|
|
func linkLibs(pending *map[string]string, linked map[string]common.Address) (deployableDeps map[string]string) {
|
2024-11-24 06:35:16 -06:00
|
|
|
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
|
2024-11-06 07:28:54 -06:00
|
|
|
if err != nil {
|
2024-11-20 05:57:03 -06:00
|
|
|
panic(err)
|
2024-11-06 07:28:54 -06:00
|
|
|
}
|
2024-11-24 06:35:16 -06:00
|
|
|
reMatchAnyPattern, err := regexp.Compile("__\\$.*\\$__")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
deployableDeps = make(map[string]string)
|
2024-11-06 07:28:54 -06:00
|
|
|
|
2024-11-26 04:18:20 -06:00
|
|
|
for pattern, dep := range *pending {
|
2024-11-26 02:54:18 -06:00
|
|
|
// link references to dependent libraries that have been deployed
|
2024-11-24 06:35:16 -06:00
|
|
|
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(dep, -1) {
|
|
|
|
matchingPattern := match[1]
|
2024-11-26 04:18:20 -06:00
|
|
|
addr, ok := linked[matchingPattern]
|
2024-11-24 06:35:16 -06:00
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
2024-11-26 04:18:20 -06:00
|
|
|
(*pending)[pattern] = strings.ReplaceAll(dep, "__$"+matchingPattern+"$__", addr.String()[2:])
|
2024-11-24 06:35:16 -06:00
|
|
|
}
|
2024-11-26 04:18:20 -06:00
|
|
|
// if the library code became fully linked, move it from pending->linked.
|
|
|
|
if !reMatchAnyPattern.MatchString((*pending)[pattern]) {
|
|
|
|
deployableDeps[pattern] = (*pending)[pattern]
|
|
|
|
delete(*pending, pattern)
|
2024-11-24 06:35:16 -06:00
|
|
|
}
|
2024-11-06 07:28:54 -06:00
|
|
|
}
|
2024-11-24 06:35:16 -06:00
|
|
|
return deployableDeps
|
|
|
|
}
|
2024-11-20 05:57:03 -06:00
|
|
|
|
2024-11-26 04:18:20 -06:00
|
|
|
// ContractDeployParams represents state needed to deploy a contract:
|
|
|
|
// the metdata and constructor input (which can be nil if no input is specified).
|
2024-11-26 02:54:18 -06:00
|
|
|
type ContractDeployParams struct {
|
2024-11-26 04:18:20 -06:00
|
|
|
Meta *bind.MetaData
|
|
|
|
// Input is the ABI-encoded constructor input for the contract deployment.
|
|
|
|
Input []byte
|
2024-11-26 02:54:18 -06:00
|
|
|
}
|
|
|
|
|
2024-11-26 04:18:20 -06:00
|
|
|
// DeploymentParams represents parameters needed to deploy a
|
|
|
|
// set of contracts, their dependency libraries. It takes an optional override
|
|
|
|
// list to specify libraries that have already been deployed on-chain.
|
2024-11-26 02:54:18 -06:00
|
|
|
type DeploymentParams struct {
|
2024-11-26 04:18:20 -06:00
|
|
|
// Contracts is the set of contract deployment parameters for contracts
|
|
|
|
// that are about to be deployed.
|
2024-11-26 02:54:18 -06:00
|
|
|
Contracts []ContractDeployParams
|
2024-11-26 04:18:20 -06:00
|
|
|
// Libraries is a map of pattern to metadata for library contracts that
|
|
|
|
// are to be deployed.
|
2024-11-26 04:41:18 -06:00
|
|
|
Libraries []*bind.MetaData
|
2024-11-26 04:18:20 -06:00
|
|
|
// Overrides is an optional map of pattern to deployment address.
|
|
|
|
// Contracts/libraries that refer to dependencies in the override
|
|
|
|
// set are linked to the provided address (an already-deployed contract).
|
2024-11-26 02:54:18 -06:00
|
|
|
Overrides map[string]common.Address
|
|
|
|
}
|
|
|
|
|
2024-11-26 04:18:20 -06:00
|
|
|
// DeploymentResult contains the relevant information from the deployment of
|
|
|
|
// multiple contracts: their deployment txs and addresses.
|
2024-11-26 02:54:18 -06:00
|
|
|
type DeploymentResult struct {
|
2024-11-26 04:18:20 -06:00
|
|
|
// map of contract library pattern -> deploy transaction
|
2024-11-26 02:54:18 -06:00
|
|
|
Txs map[string]*types.Transaction
|
2024-11-26 04:18:20 -06:00
|
|
|
// map of contract library pattern -> deployed address
|
2024-11-26 02:54:18 -06:00
|
|
|
Addrs map[string]common.Address
|
|
|
|
}
|
|
|
|
|
2024-11-26 04:18:20 -06:00
|
|
|
// LinkAndDeploy deploys a specified set of contracts and their dependent
|
|
|
|
// libraries.
|
|
|
|
func LinkAndDeploy(auth *bind.TransactOpts, backend bind.ContractBackend, deployParams DeploymentParams) (res *DeploymentResult, err error) {
|
2024-11-26 02:54:18 -06:00
|
|
|
libMetas := deployParams.Libraries
|
|
|
|
overrides := deployParams.Overrides
|
|
|
|
|
|
|
|
res = &DeploymentResult{
|
|
|
|
Txs: make(map[string]*types.Transaction),
|
|
|
|
Addrs: make(map[string]common.Address),
|
|
|
|
}
|
2024-11-24 08:53:31 -06:00
|
|
|
|
2024-11-25 01:30:29 -06:00
|
|
|
// re-express libraries as a map of pattern -> pre-link binary
|
2024-11-26 04:18:20 -06:00
|
|
|
pending := make(map[string]string)
|
2024-11-26 04:41:18 -06:00
|
|
|
for _, meta := range libMetas {
|
|
|
|
pending[meta.Pattern] = meta.Bin
|
2024-11-24 08:53:31 -06:00
|
|
|
}
|
|
|
|
|
2024-11-25 01:30:29 -06:00
|
|
|
// initialize the set of already-deployed contracts with given override addresses
|
2024-11-26 04:18:20 -06:00
|
|
|
deployed := make(map[string]common.Address)
|
2024-11-24 06:35:16 -06:00
|
|
|
for pattern, deployAddr := range overrides {
|
2024-11-26 04:18:20 -06:00
|
|
|
deployed[pattern] = deployAddr
|
|
|
|
if _, ok := pending[pattern]; ok {
|
|
|
|
delete(pending, pattern)
|
2024-11-20 05:57:03 -06:00
|
|
|
}
|
2024-11-24 06:35:16 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// link and deploy dynamic libraries
|
|
|
|
for {
|
2024-11-26 04:18:20 -06:00
|
|
|
deployableDeps := linkLibs(&pending, deployed)
|
2024-11-24 06:35:16 -06:00
|
|
|
if len(deployableDeps) == 0 {
|
2024-11-20 05:57:03 -06:00
|
|
|
break
|
|
|
|
}
|
2024-11-24 08:00:26 -06:00
|
|
|
deployTxs, deployAddrs, err := deployLibs(backend, auth, deployableDeps)
|
2024-11-24 08:53:31 -06:00
|
|
|
for pattern, addr := range deployAddrs {
|
2024-11-26 04:18:20 -06:00
|
|
|
deployed[pattern] = addr
|
2024-11-26 02:54:18 -06:00
|
|
|
res.Addrs[pattern] = addr
|
|
|
|
res.Txs[pattern] = deployTxs[addr]
|
2024-11-24 06:35:16 -06:00
|
|
|
}
|
|
|
|
if err != nil {
|
2024-11-26 02:54:18 -06:00
|
|
|
return res, err
|
2024-11-24 06:35:16 -06:00
|
|
|
}
|
|
|
|
}
|
2024-11-26 02:54:18 -06:00
|
|
|
|
|
|
|
for _, contractParams := range deployParams.Contracts {
|
2024-11-26 04:18:20 -06:00
|
|
|
linkedContract, err := linkContract(contractParams.Meta.Bin, deployed)
|
2024-11-26 02:54:18 -06:00
|
|
|
if err != nil {
|
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
// link and deploy the contracts
|
2024-11-26 04:18:20 -06:00
|
|
|
contractTx, contractAddr, err := deployContract(backend, auth, contractParams.Input, linkedContract)
|
2024-11-26 02:54:18 -06:00
|
|
|
if err != nil {
|
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
res.Txs[contractParams.Meta.Pattern] = contractTx
|
|
|
|
res.Addrs[contractParams.Meta.Pattern] = contractAddr
|
2024-11-06 07:28:54 -06:00
|
|
|
}
|
2024-11-26 02:54:18 -06:00
|
|
|
|
|
|
|
return res, nil
|
2024-11-20 05:57:03 -06:00
|
|
|
}
|
|
|
|
|
2024-11-26 04:32:46 -06:00
|
|
|
// TODO: adding docs soon (jwasinger)
|
2024-11-20 05:57:03 -06:00
|
|
|
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...)
|
2024-11-06 07:28:54 -06:00
|
|
|
if err != nil {
|
2024-11-20 05:57:03 -06:00
|
|
|
return nil, err
|
2024-11-06 07:28:54 -06:00
|
|
|
}
|
2024-11-20 05:57:03 -06:00
|
|
|
return &EventIterator[T]{unpack: unpack, logs: logs, sub: sub}, nil
|
2024-11-06 07:28:54 -06:00
|
|
|
}
|
|
|
|
|
2024-11-26 04:32:46 -06:00
|
|
|
// TODO: adding docs soon (jwasinger)
|
2024-11-20 05:57:03 -06:00
|
|
|
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...)
|
2024-11-04 04:32:26 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
|
|
defer sub.Unsubscribe()
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case log := <-logs:
|
|
|
|
// New log arrived, parse the event and forward to the user
|
|
|
|
ev, err := unpack(&log)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case sink <- ev:
|
|
|
|
case err := <-sub.Err():
|
|
|
|
return err
|
|
|
|
case <-quit:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
case err := <-sub.Err():
|
|
|
|
return err
|
|
|
|
case <-quit:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// EventIterator is returned from FilterLogs and is used to iterate over the raw logs and unpacked data for events.
|
|
|
|
type EventIterator[T any] struct {
|
|
|
|
Event *T // Event containing the contract specifics and raw log
|
|
|
|
|
|
|
|
unpack func(*types.Log) (*T, error) // Unpack function for the event
|
|
|
|
|
|
|
|
logs chan types.Log // Log channel receiving the found contract events
|
|
|
|
sub ethereum.Subscription // Subscription for errors, completion and termination
|
|
|
|
done bool // Whether the subscription completed delivering logs
|
|
|
|
fail error // Occurred error to stop iteration
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next advances the iterator to the subsequent event, returning whether there
|
|
|
|
// are any more events found. In case of a retrieval or parsing error, false is
|
|
|
|
// returned and Error() can be queried for the exact failure.
|
|
|
|
func (it *EventIterator[T]) Next() bool {
|
|
|
|
// If the iterator failed, stop iterating
|
|
|
|
if it.fail != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// If the iterator completed, deliver directly whatever's available
|
|
|
|
if it.done {
|
|
|
|
select {
|
|
|
|
case log := <-it.logs:
|
|
|
|
res, err := it.unpack(&log)
|
|
|
|
if err != nil {
|
|
|
|
it.fail = err
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
it.Event = res
|
|
|
|
return true
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Iterator still in progress, wait for either a data or an error event
|
|
|
|
select {
|
|
|
|
case log := <-it.logs:
|
|
|
|
res, err := it.unpack(&log)
|
|
|
|
if err != nil {
|
|
|
|
it.fail = err
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
it.Event = res
|
|
|
|
return true
|
|
|
|
|
|
|
|
case err := <-it.sub.Err():
|
|
|
|
it.done = true
|
|
|
|
it.fail = err
|
|
|
|
return it.Next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error returns any retrieval or parsing error occurred during filtering.
|
|
|
|
func (it *EventIterator[T]) Error() error {
|
|
|
|
return it.fail
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close terminates the iteration process, releasing any pending underlying
|
|
|
|
// resources.
|
|
|
|
func (it *EventIterator[T]) Close() error {
|
|
|
|
it.sub.Unsubscribe()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-11-26 04:32:46 -06:00
|
|
|
// Transact creates and submits a transaction to the bound contract instance
|
|
|
|
// using the provided abi-encoded input (or nil).
|
2024-11-04 04:32:26 -06:00
|
|
|
func Transact(instance bind.ContractInstance, opts *bind.TransactOpts, input []byte) (*types.Transaction, error) {
|
|
|
|
var (
|
|
|
|
addr = instance.Address()
|
|
|
|
backend = instance.Backend()
|
|
|
|
)
|
|
|
|
c := bind.NewBoundContract(addr, abi.ABI{}, backend, backend, backend)
|
|
|
|
return c.RawTransact(opts, input)
|
|
|
|
}
|
|
|
|
|
2024-11-26 04:32:46 -06:00
|
|
|
// Call performs an eth_call on the given bound contract instance, using the
|
|
|
|
// provided abi-encoded input (or nil).
|
|
|
|
func Call(instance bind.ContractInstance, opts *bind.CallOpts, input []byte) ([]byte, error) {
|
2024-11-20 05:57:03 -06:00
|
|
|
backend := instance.Backend()
|
|
|
|
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend)
|
|
|
|
return c.CallRaw(opts, input)
|
|
|
|
}
|