248 lines
7.7 KiB
Go
248 lines
7.7 KiB
Go
package v2
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"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"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
type ContractInstance struct {
|
|
Address common.Address
|
|
Backend bind.ContractBackend
|
|
}
|
|
|
|
func deployDeps(backend bind.ContractBackend, auth *bind.TransactOpts, constructorInputs map[string][]byte, contracts map[string]string) (deploymentTxs map[common.Address]*types.Transaction, deployAddrs map[common.Address]struct{}, err error) {
|
|
for pattern, contractBin := range contracts {
|
|
contractBinBytes, err := hex.DecodeString(contractBin[2:])
|
|
if err != nil {
|
|
return deploymentTxs, deployAddrs, fmt.Errorf("contract bytecode is not a hex string: %s", contractBin[2:])
|
|
}
|
|
var constructorInput []byte
|
|
if inp, ok := constructorInputs[pattern]; ok {
|
|
constructorInput = inp
|
|
} else {
|
|
constructorInput = make([]byte, 0) // TODO check if we can pass a nil byte slice here.
|
|
}
|
|
addr, tx, _, err := bind.DeployContractRaw(auth, contractBinBytes, backend, constructorInput)
|
|
if err != nil {
|
|
return deploymentTxs, deployAddrs, fmt.Errorf("failed to deploy contract: %v", err)
|
|
}
|
|
deploymentTxs[addr] = tx
|
|
deployAddrs[addr] = struct{}{}
|
|
}
|
|
|
|
return deploymentTxs, deployAddrs, nil
|
|
}
|
|
|
|
func linkDeps(deps *map[string]string, linked *map[string]common.Address) (deployableDeps map[string]string) {
|
|
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
reMatchAnyPattern, err := regexp.Compile("__\\$.*\\$__")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
deployableDeps = make(map[string]string)
|
|
|
|
for pattern, dep := range *deps {
|
|
// attempt to replace references to every single linked dep
|
|
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(dep, -1) {
|
|
matchingPattern := match[1]
|
|
addr, ok := (*linked)[matchingPattern]
|
|
if !ok {
|
|
continue
|
|
}
|
|
(*deps)[pattern] = strings.ReplaceAll(dep, matchingPattern, addr.String())
|
|
}
|
|
// if we linked something into this dep, see if it can be deployed
|
|
if !reMatchAnyPattern.MatchString((*deps)[pattern]) {
|
|
deployableDeps[pattern] = (*deps)[pattern]
|
|
delete(*deps, pattern)
|
|
}
|
|
}
|
|
|
|
return deployableDeps
|
|
}
|
|
|
|
func LinkAndDeployContractsWithOverride(auth *bind.TransactOpts, backend bind.ContractBackend, constructorInputs map[string][]byte, contracts, libs map[string]string, overrides map[string]common.Address) (allDeployTxs map[common.Address]*types.Transaction, allDeployAddrs map[common.Address]struct{}, err error) {
|
|
var depsToDeploy map[string]string // map of pattern -> unlinked binary for deps we will deploy
|
|
|
|
// initialize the set of already-deployed contracts with given override addresses
|
|
linked := make(map[string]common.Address)
|
|
for pattern, deployAddr := range overrides {
|
|
linked[pattern] = deployAddr
|
|
if _, ok := contracts[pattern]; ok {
|
|
delete(contracts, pattern)
|
|
}
|
|
}
|
|
|
|
// link and deploy dynamic libraries
|
|
for {
|
|
deployableDeps := linkDeps(&depsToDeploy, &linked)
|
|
if len(deployableDeps) == 0 {
|
|
break
|
|
}
|
|
deployTxs, deployAddrs, err := deployDeps(backend, auth, constructorInputs, deployableDeps)
|
|
for addr, _ := range deployAddrs {
|
|
allDeployAddrs[addr] = struct{}{}
|
|
}
|
|
for addr, tx := range deployTxs {
|
|
allDeployTxs[addr] = tx
|
|
}
|
|
if err != nil {
|
|
return deployTxs, allDeployAddrs, err
|
|
}
|
|
}
|
|
|
|
// link and deploy the contracts
|
|
contractBins := make(map[string]string)
|
|
linkedContracts := linkDeps(&contractBins, &linked)
|
|
deployTxs, deployAddrs, err := deployDeps(backend, auth, constructorInputs, linkedContracts)
|
|
for addr, _ := range deployAddrs {
|
|
allDeployAddrs[addr] = struct{}{}
|
|
}
|
|
for addr, tx := range deployTxs {
|
|
allDeployTxs[addr] = tx
|
|
}
|
|
return allDeployTxs, allDeployAddrs, err
|
|
}
|
|
|
|
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, err
|
|
}
|
|
return &EventIterator[T]{unpack: unpack, logs: logs, sub: sub}, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
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
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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)
|
|
}
|