Compare commits
19 Commits
Author | SHA1 | Date |
---|---|---|
|
0df73f3fac | |
|
cd86fca55c | |
|
46e6dd4fcd | |
|
a2479e1aed | |
|
aa651aecab | |
|
008028e5fd | |
|
332c188c9c | |
|
8a65262e98 | |
|
c6f7571c5e | |
|
60535d17a4 | |
|
8f756ec576 | |
|
bb63ef3a21 | |
|
cea12ea4e5 | |
|
dcf1318a24 | |
|
724f77e3ec | |
|
2725ae2bcb | |
|
efc2c36291 | |
|
ac27dad728 | |
|
29d90ae528 |
|
@ -50,6 +50,7 @@ func JSON(reader io.Reader) (ABI, error) {
|
|||
|
||||
var abi ABI
|
||||
if err := dec.Decode(&abi); err != nil {
|
||||
fmt.Println(err)
|
||||
return ABI{}, err
|
||||
}
|
||||
return abi, nil
|
||||
|
|
|
@ -151,15 +151,24 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co
|
|||
return c.address, tx, c, nil
|
||||
}
|
||||
|
||||
func DeployContractRaw(opts *TransactOpts, bytecode []byte, backend ContractBackend, packedParams []byte) (common.Address, *types.Transaction, *BoundContract, error) {
|
||||
// TODO: it's weird to instantiate a bound contract (implies existence of contract) in order to deploy a contract
|
||||
// that doesn't yet exist
|
||||
c := NewBoundContract(common.Address{}, abi.ABI{}, backend, backend, backend)
|
||||
|
||||
tx, err := c.transact(opts, nil, append(bytecode, packedParams...))
|
||||
if err != nil {
|
||||
return common.Address{}, nil, nil, err
|
||||
}
|
||||
c.address = crypto.CreateAddress(opts.From, tx.Nonce())
|
||||
return c.address, tx, c, nil
|
||||
}
|
||||
|
||||
// Call invokes the (constant) contract method with params as input values and
|
||||
// sets the output to result. The result type might be a single field for simple
|
||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||
// returns.
|
||||
func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error {
|
||||
// Don't crash on a lazy user
|
||||
if opts == nil {
|
||||
opts = new(CallOpts)
|
||||
}
|
||||
if results == nil {
|
||||
results = new([]interface{})
|
||||
}
|
||||
|
@ -168,59 +177,10 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input}
|
||||
ctx = ensureContext(opts.Context)
|
||||
code []byte
|
||||
output []byte
|
||||
)
|
||||
if opts.Pending {
|
||||
pb, ok := c.caller.(PendingContractCaller)
|
||||
if !ok {
|
||||
return ErrNoPendingState
|
||||
}
|
||||
output, err = pb.PendingCallContract(ctx, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(output) == 0 {
|
||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||
if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
|
||||
return err
|
||||
} else if len(code) == 0 {
|
||||
return ErrNoCode
|
||||
}
|
||||
}
|
||||
} else if opts.BlockHash != (common.Hash{}) {
|
||||
bh, ok := c.caller.(BlockHashContractCaller)
|
||||
if !ok {
|
||||
return ErrNoBlockHashState
|
||||
}
|
||||
output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(output) == 0 {
|
||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||
if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil {
|
||||
return err
|
||||
} else if len(code) == 0 {
|
||||
return ErrNoCode
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(output) == 0 {
|
||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||
if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
|
||||
return err
|
||||
} else if len(code) == 0 {
|
||||
return ErrNoCode
|
||||
}
|
||||
}
|
||||
|
||||
output, err := c.call(opts, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(*results) == 0 {
|
||||
|
@ -232,6 +192,73 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
|
|||
return c.abi.UnpackIntoInterface(res[0], method, output)
|
||||
}
|
||||
|
||||
func (c *BoundContract) CallRaw(opts *CallOpts, input []byte) ([]byte, error) {
|
||||
return c.call(opts, input)
|
||||
}
|
||||
|
||||
func (c *BoundContract) call(opts *CallOpts, input []byte) ([]byte, error) {
|
||||
// Don't crash on a lazy user
|
||||
if opts == nil {
|
||||
opts = new(CallOpts)
|
||||
}
|
||||
var (
|
||||
msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input}
|
||||
ctx = ensureContext(opts.Context)
|
||||
code []byte
|
||||
output []byte
|
||||
err error
|
||||
)
|
||||
if opts.Pending {
|
||||
pb, ok := c.caller.(PendingContractCaller)
|
||||
if !ok {
|
||||
return nil, ErrNoPendingState
|
||||
}
|
||||
output, err = pb.PendingCallContract(ctx, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(output) == 0 {
|
||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||
if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
|
||||
return nil, err
|
||||
} else if len(code) == 0 {
|
||||
return nil, ErrNoCode
|
||||
}
|
||||
}
|
||||
} else if opts.BlockHash != (common.Hash{}) {
|
||||
bh, ok := c.caller.(BlockHashContractCaller)
|
||||
if !ok {
|
||||
return nil, ErrNoBlockHashState
|
||||
}
|
||||
output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(output) == 0 {
|
||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||
if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil {
|
||||
return nil, err
|
||||
} else if len(code) == 0 {
|
||||
return nil, ErrNoCode
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(output) == 0 {
|
||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||
if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
|
||||
return nil, err
|
||||
} else if len(code) == 0 {
|
||||
return nil, ErrNoCode
|
||||
}
|
||||
}
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// Transact invokes the (paid) contract method with params as input values.
|
||||
func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||
// Otherwise pack up the parameters and invoke the contract
|
||||
|
@ -434,13 +461,16 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
|
|||
// FilterLogs filters contract logs for past blocks, returning the necessary
|
||||
// channels to construct a strongly typed bound iterator on top of them.
|
||||
func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
|
||||
return c.filterLogs(opts, c.abi.Events[name].ID, query...)
|
||||
}
|
||||
|
||||
func (c *BoundContract) filterLogs(opts *FilterOpts, eventID common.Hash, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
|
||||
// Don't crash on a lazy user
|
||||
if opts == nil {
|
||||
opts = new(FilterOpts)
|
||||
}
|
||||
// Append the event selector to the query parameters and construct the topic set
|
||||
query = append([][]interface{}{{c.abi.Events[name].ID}}, query...)
|
||||
|
||||
query = append([][]interface{}{{eventID}}, query...)
|
||||
topics, err := abi.MakeTopics(query...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -480,12 +510,16 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
|
|||
// WatchLogs filters subscribes to contract logs for future blocks, returning a
|
||||
// subscription object that can be used to tear down the watcher.
|
||||
func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
|
||||
return c.watchLogs(opts, c.abi.Events[name].ID, query...)
|
||||
}
|
||||
|
||||
func (c *BoundContract) watchLogs(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{}{{c.abi.Events[name].ID}}, query...)
|
||||
query = append([][]interface{}{{eventID}}, query...)
|
||||
|
||||
topics, err := abi.MakeTopics(query...)
|
||||
if err != nil {
|
||||
|
|
|
@ -33,13 +33,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// Lang is a target programming language selector to generate bindings for.
|
||||
type Lang int
|
||||
|
||||
const (
|
||||
LangGo Lang = iota
|
||||
)
|
||||
|
||||
func isKeyWord(arg string) bool {
|
||||
switch arg {
|
||||
case "break":
|
||||
|
@ -81,7 +74,91 @@ func isKeyWord(arg string) bool {
|
|||
// to be used as is in client code, but rather as an intermediate struct which
|
||||
// enforces compile time type safety and naming convention as opposed to having to
|
||||
// manually maintain hard coded strings that break on runtime.
|
||||
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
|
||||
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, libs map[string]string, aliases map[string]string) (string, error) {
|
||||
data, err := bind(types, abis, bytecodes, fsigs, pkg, libs, aliases)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
funcs := map[string]interface{}{
|
||||
"bindtype": bindType,
|
||||
"bindtopictype": bindTopicType,
|
||||
"capitalise": capitalise,
|
||||
"decapitalise": decapitalise,
|
||||
}
|
||||
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource))
|
||||
if err := tmpl.Execute(buffer, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Pass the code through gofmt to clean it up
|
||||
code, err := format.Source(buffer.Bytes())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%v\n%s", err, buffer)
|
||||
}
|
||||
return string(code), nil
|
||||
}
|
||||
|
||||
func BindV2(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, libs map[string]string, aliases map[string]string) (string, error) {
|
||||
data, err := bind(types, abis, bytecodes, fsigs, pkg, libs, aliases)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, c := range data.Contracts {
|
||||
// We want pack/unpack methods for all existing methods.
|
||||
for name, t := range c.Transacts {
|
||||
c.Calls[name] = t
|
||||
}
|
||||
c.Transacts = nil
|
||||
|
||||
// Make sure we return one argument. If multiple exist
|
||||
// merge them into a struct.
|
||||
for _, call := range c.Calls {
|
||||
if call.Structured {
|
||||
continue
|
||||
}
|
||||
if len(call.Normalized.Outputs) < 2 {
|
||||
continue
|
||||
}
|
||||
// Build up dictionary of existing arg names.
|
||||
keys := make(map[string]struct{})
|
||||
for _, o := range call.Normalized.Outputs {
|
||||
if o.Name != "" {
|
||||
keys[strings.ToLower(o.Name)] = struct{}{}
|
||||
}
|
||||
}
|
||||
// Assign names to anonymous fields.
|
||||
for i, o := range call.Normalized.Outputs {
|
||||
if o.Name != "" {
|
||||
continue
|
||||
}
|
||||
o.Name = capitalise(abi.ResolveNameConflict("arg", func(name string) bool { _, ok := keys[name]; return ok }))
|
||||
call.Normalized.Outputs[i] = o
|
||||
keys[strings.ToLower(o.Name)] = struct{}{}
|
||||
}
|
||||
call.Structured = true
|
||||
}
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
funcs := map[string]interface{}{
|
||||
"bindtype": bindType,
|
||||
"bindtopictype": bindTopicType,
|
||||
"capitalise": capitalise,
|
||||
"decapitalise": decapitalise,
|
||||
}
|
||||
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSourceV2))
|
||||
if err := tmpl.Execute(buffer, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Pass the code through gofmt to clean it up
|
||||
code, err := format.Source(buffer.Bytes())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%v\n%s", err, buffer)
|
||||
}
|
||||
return string(code), nil
|
||||
}
|
||||
|
||||
func bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, libs map[string]string, aliases map[string]string) (*tmplData, error) {
|
||||
var (
|
||||
// contracts is the map of each individual contract requested binding
|
||||
contracts = make(map[string]*tmplContract)
|
||||
|
@ -96,7 +173,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
// Parse the actual ABI to generate the binding for
|
||||
evmABI, err := abi.JSON(strings.NewReader(abis[i]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
// Strip any whitespace from the JSON ABI
|
||||
strippedABI := strings.Map(func(r rune) rune {
|
||||
|
@ -125,14 +202,14 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
|
||||
for _, input := range evmABI.Constructor.Inputs {
|
||||
if hasStruct(input.Type) {
|
||||
bindStructType[lang](input.Type, structs)
|
||||
bindStructType(input.Type, structs)
|
||||
}
|
||||
}
|
||||
|
||||
for _, original := range evmABI.Methods {
|
||||
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
||||
normalized := original
|
||||
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
||||
normalizedName := methodNormalizer(alias(aliases, original.Name))
|
||||
// Ensure there is no duplicated identifier
|
||||
var identifiers = callIdentifiers
|
||||
if !original.IsConstant() {
|
||||
|
@ -147,7 +224,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
})
|
||||
}
|
||||
if identifiers[normalizedName] {
|
||||
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
||||
return nil, fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
||||
}
|
||||
identifiers[normalizedName] = true
|
||||
|
||||
|
@ -159,7 +236,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
||||
}
|
||||
if hasStruct(input.Type) {
|
||||
bindStructType[lang](input.Type, structs)
|
||||
bindStructType(input.Type, structs)
|
||||
}
|
||||
}
|
||||
normalized.Outputs = make([]abi.Argument, len(original.Outputs))
|
||||
|
@ -169,7 +246,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
normalized.Outputs[j].Name = capitalise(output.Name)
|
||||
}
|
||||
if hasStruct(output.Type) {
|
||||
bindStructType[lang](output.Type, structs)
|
||||
bindStructType(output.Type, structs)
|
||||
}
|
||||
}
|
||||
// Append the methods to the call or transact lists
|
||||
|
@ -188,7 +265,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
normalized := original
|
||||
|
||||
// Ensure there is no duplicated identifier
|
||||
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
||||
normalizedName := methodNormalizer(alias(aliases, original.Name))
|
||||
// Name shouldn't start with a digit. It will make the generated code invalid.
|
||||
if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) {
|
||||
normalizedName = fmt.Sprintf("E%s", normalizedName)
|
||||
|
@ -198,7 +275,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
})
|
||||
}
|
||||
if eventIdentifiers[normalizedName] {
|
||||
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
||||
return nil, fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
||||
}
|
||||
eventIdentifiers[normalizedName] = true
|
||||
normalized.Name = normalizedName
|
||||
|
@ -220,7 +297,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index)
|
||||
}
|
||||
if hasStruct(input.Type) {
|
||||
bindStructType[lang](input.Type, structs)
|
||||
bindStructType(input.Type, structs)
|
||||
}
|
||||
}
|
||||
// Append the event to the accumulator list
|
||||
|
@ -233,18 +310,21 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
if evmABI.HasReceive() {
|
||||
receive = &tmplMethod{Original: evmABI.Receive}
|
||||
}
|
||||
|
||||
contracts[types[i]] = &tmplContract{
|
||||
Type: capitalise(types[i]),
|
||||
InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""),
|
||||
InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"),
|
||||
Constructor: evmABI.Constructor,
|
||||
Calls: calls,
|
||||
Transacts: transacts,
|
||||
Fallback: fallback,
|
||||
Receive: receive,
|
||||
Events: events,
|
||||
Libraries: make(map[string]string),
|
||||
Type: capitalise(types[i]),
|
||||
InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""),
|
||||
InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"),
|
||||
Constructor: evmABI.Constructor,
|
||||
Calls: calls,
|
||||
Transacts: transacts,
|
||||
Fallback: fallback,
|
||||
Receive: receive,
|
||||
Events: events,
|
||||
Libraries: make(map[string]string),
|
||||
AllLibraries: make(map[string]string),
|
||||
}
|
||||
|
||||
// Function 4-byte signatures are stored in the same sequence
|
||||
// as types, if available.
|
||||
if len(fsigs) > i {
|
||||
|
@ -265,11 +345,57 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if that type has already been identified as a library
|
||||
for i := 0; i < len(types); i++ {
|
||||
_, ok := isLib[types[i]]
|
||||
contracts[types[i]].Library = ok
|
||||
}
|
||||
|
||||
// compute the full set of libraries that each contract depends on.
|
||||
for _, contract := range contracts {
|
||||
if contract.Library {
|
||||
continue
|
||||
}
|
||||
// recursively traverse the library dependency graph
|
||||
// of the contract, flattening it into a set.
|
||||
//
|
||||
// For abigenv2, we do not generate contract deploy
|
||||
// methods (which in v1 recursively deploy their
|
||||
// library dependencies). So, the entire set of
|
||||
// library dependencies is required, and we will
|
||||
// the order to deploy and link them at runtime.
|
||||
var findDeps func(contract *tmplContract) map[string]struct{}
|
||||
findDeps = func(contract *tmplContract) map[string]struct{} {
|
||||
// 1) match all libraries that this contract depends on
|
||||
re, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
libBin := contracts[contract.Type].InputBin
|
||||
matches := re.FindAllStringSubmatch(libBin, -1)
|
||||
result := make(map[string]struct{})
|
||||
|
||||
// 2) recurse, gathering nested library dependencies
|
||||
for _, match := range matches {
|
||||
pattern := match[1]
|
||||
result[pattern] = struct{}{}
|
||||
depContract := contracts[libs[pattern]]
|
||||
for subPattern, _ := range findDeps(depContract) {
|
||||
result[subPattern] = struct{}{}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
// take the set of library patterns, convert it to a map of type -> pattern
|
||||
deps := findDeps(contract)
|
||||
contract.AllLibraries = make(map[string]string)
|
||||
for contractPattern, _ := range deps {
|
||||
contractType := libs[contractPattern]
|
||||
contract.AllLibraries[contractType] = contractPattern
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the contract template data content and render it
|
||||
data := &tmplData{
|
||||
Package: pkg,
|
||||
|
@ -277,39 +403,11 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
|||
Libraries: libs,
|
||||
Structs: structs,
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
funcs := map[string]interface{}{
|
||||
"bindtype": bindType[lang],
|
||||
"bindtopictype": bindTopicType[lang],
|
||||
"namedtype": namedType[lang],
|
||||
"capitalise": capitalise,
|
||||
"decapitalise": decapitalise,
|
||||
}
|
||||
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
|
||||
if err := tmpl.Execute(buffer, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// For Go bindings pass the code through gofmt to clean it up
|
||||
if lang == LangGo {
|
||||
code, err := format.Source(buffer.Bytes())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%v\n%s", err, buffer)
|
||||
}
|
||||
return string(code), nil
|
||||
}
|
||||
// For all others just return as is for now
|
||||
return buffer.String(), nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// bindType is a set of type binders that convert Solidity types to some supported
|
||||
// programming language types.
|
||||
var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||
LangGo: bindTypeGo,
|
||||
}
|
||||
|
||||
// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones.
|
||||
func bindBasicTypeGo(kind abi.Type) string {
|
||||
// bindBasicType converts basic solidity types(except array, slice and tuple) to Go ones.
|
||||
func bindBasicType(kind abi.Type) string {
|
||||
switch kind.T {
|
||||
case abi.AddressTy:
|
||||
return "common.Address"
|
||||
|
@ -332,32 +430,26 @@ func bindBasicTypeGo(kind abi.Type) string {
|
|||
}
|
||||
}
|
||||
|
||||
// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping
|
||||
// bindType converts solidity types to Go ones. Since there is no clear mapping
|
||||
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
|
||||
// mapped will use an upscaled type (e.g. BigDecimal).
|
||||
func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||
func bindType(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||
switch kind.T {
|
||||
case abi.TupleTy:
|
||||
return structs[kind.TupleRawName+kind.String()].Name
|
||||
case abi.ArrayTy:
|
||||
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs)
|
||||
return fmt.Sprintf("[%d]", kind.Size) + bindType(*kind.Elem, structs)
|
||||
case abi.SliceTy:
|
||||
return "[]" + bindTypeGo(*kind.Elem, structs)
|
||||
return "[]" + bindType(*kind.Elem, structs)
|
||||
default:
|
||||
return bindBasicTypeGo(kind)
|
||||
return bindBasicType(kind)
|
||||
}
|
||||
}
|
||||
|
||||
// bindTopicType is a set of type binders that convert Solidity types to some
|
||||
// supported programming language topic types.
|
||||
var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||
LangGo: bindTopicTypeGo,
|
||||
}
|
||||
|
||||
// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same
|
||||
// bindTopicType converts a Solidity topic type to a Go one. It is almost the same
|
||||
// functionality as for simple types, but dynamic types get converted to hashes.
|
||||
func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||
bound := bindTypeGo(kind, structs)
|
||||
func bindTopicType(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||
bound := bindType(kind, structs)
|
||||
|
||||
// todo(rjl493456442) according solidity documentation, indexed event
|
||||
// parameters that are not value types i.e. arrays and structs are not
|
||||
|
@ -371,16 +463,10 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
|||
return bound
|
||||
}
|
||||
|
||||
// bindStructType is a set of type binders that convert Solidity tuple types to some supported
|
||||
// programming language struct definition.
|
||||
var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||
LangGo: bindStructTypeGo,
|
||||
}
|
||||
|
||||
// bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping
|
||||
// bindStructType converts a Solidity tuple type to a Go one and records the mapping
|
||||
// in the given map.
|
||||
// Notably, this function will resolve and record nested struct recursively.
|
||||
func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||
func bindStructType(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||
switch kind.T {
|
||||
case abi.TupleTy:
|
||||
// We compose a raw struct name and a canonical parameter expression
|
||||
|
@ -401,7 +487,7 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
|||
name := capitalise(kind.TupleRawNames[i])
|
||||
name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] })
|
||||
names[name] = true
|
||||
fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem})
|
||||
fields = append(fields, &tmplField{Type: bindStructType(*elem, structs), Name: name, SolKind: *elem})
|
||||
}
|
||||
name := kind.TupleRawName
|
||||
if name == "" {
|
||||
|
@ -415,20 +501,14 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
|||
}
|
||||
return name
|
||||
case abi.ArrayTy:
|
||||
return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs)
|
||||
return fmt.Sprintf("[%d]", kind.Size) + bindStructType(*kind.Elem, structs)
|
||||
case abi.SliceTy:
|
||||
return "[]" + bindStructTypeGo(*kind.Elem, structs)
|
||||
return "[]" + bindStructType(*kind.Elem, structs)
|
||||
default:
|
||||
return bindBasicTypeGo(kind)
|
||||
return bindBasicType(kind)
|
||||
}
|
||||
}
|
||||
|
||||
// namedType is a set of functions that transform language specific types to
|
||||
// named versions that may be used inside method names.
|
||||
var namedType = map[Lang]func(string, abi.Type) string{
|
||||
LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") },
|
||||
}
|
||||
|
||||
// alias returns an alias of the given string based on the aliasing rules
|
||||
// or returns itself if no rule is matched.
|
||||
func alias(aliases map[string]string, n string) string {
|
||||
|
@ -439,10 +519,8 @@ func alias(aliases map[string]string, n string) string {
|
|||
}
|
||||
|
||||
// methodNormalizer is a name transformer that modifies Solidity method names to
|
||||
// conform to target language naming conventions.
|
||||
var methodNormalizer = map[Lang]func(string) string{
|
||||
LangGo: abi.ToCamelCase,
|
||||
}
|
||||
// conform to Go naming conventions.
|
||||
var methodNormalizer = abi.ToCamelCase
|
||||
|
||||
// capitalise makes a camel-case string which starts with an upper case character.
|
||||
var capitalise = abi.ToCamelCase
|
||||
|
|
|
@ -2072,7 +2072,7 @@ var bindTests = []struct {
|
|||
|
||||
// Tests that packages generated by the binder can be successfully compiled and
|
||||
// the requested tester run against it.
|
||||
func TestGolangBindings(t *testing.T) {
|
||||
func TestBindings(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Skip the test if no Go command can be found
|
||||
gocmd := runtime.GOROOT() + "/bin/go"
|
||||
|
@ -2096,7 +2096,7 @@ func TestGolangBindings(t *testing.T) {
|
|||
types = []string{tt.name}
|
||||
}
|
||||
// Generate the binding and create a Go source file in the workspace
|
||||
bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases)
|
||||
bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", tt.libs, tt.aliases)
|
||||
if err != nil {
|
||||
t.Fatalf("test %d: failed to generate binding: %v", i, err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// ContractInstance provides means to interact with
|
||||
// a deployed contract.
|
||||
type ContractInstance interface {
|
||||
Address() common.Address
|
||||
Backend() ContractBackend
|
||||
}
|
||||
|
||||
type ContractInstanceV2 interface {
|
||||
Address() common.Address
|
||||
Backend() ContractBackend
|
||||
}
|
|
@ -32,18 +32,19 @@ type tmplData struct {
|
|||
|
||||
// tmplContract contains the data needed to generate an individual contract binding.
|
||||
type tmplContract struct {
|
||||
Type string // Type name of the main contract binding
|
||||
InputABI string // JSON ABI used as the input to generate the binding from
|
||||
InputBin string // Optional EVM bytecode used to generate deploy code from
|
||||
FuncSigs map[string]string // Optional map: string signature -> 4-byte signature
|
||||
Constructor abi.Method // Contract constructor for deploy parametrization
|
||||
Calls map[string]*tmplMethod // Contract calls that only read state data
|
||||
Transacts map[string]*tmplMethod // Contract calls that write state data
|
||||
Fallback *tmplMethod // Additional special fallback function
|
||||
Receive *tmplMethod // Additional special receive function
|
||||
Events map[string]*tmplEvent // Contract events accessors
|
||||
Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs
|
||||
Library bool // Indicator whether the contract is a library
|
||||
Type string // Type name of the main contract binding
|
||||
InputABI string // JSON ABI used as the input to generate the binding from
|
||||
InputBin string // Optional EVM bytecode used to generate deploy code from
|
||||
FuncSigs map[string]string // Optional map: string signature -> 4-byte signature
|
||||
Constructor abi.Method // Contract constructor for deploy parametrization
|
||||
Calls map[string]*tmplMethod // Contract calls that only read state data
|
||||
Transacts map[string]*tmplMethod // Contract calls that write state data
|
||||
Fallback *tmplMethod // Additional special fallback function
|
||||
Receive *tmplMethod // Additional special receive function
|
||||
Events map[string]*tmplEvent // Contract events accessors
|
||||
Libraries map[string]string // Same as tmplData, but filtered to only keep direct deps that the contract needs
|
||||
AllLibraries map[string]string // same as Libraries, but all direct/indirect library dependencies
|
||||
Library bool // Indicator whether the contract is a library
|
||||
}
|
||||
|
||||
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
||||
|
@ -76,14 +77,8 @@ type tmplStruct struct {
|
|||
Fields []*tmplField // Struct fields definition depends on the binding language.
|
||||
}
|
||||
|
||||
// tmplSource is language to template mapping containing all the supported
|
||||
// programming languages the package can generate to.
|
||||
var tmplSource = map[Lang]string{
|
||||
LangGo: tmplSourceGo,
|
||||
}
|
||||
|
||||
// tmplSourceGo is the Go source template that the generated Go contract binding
|
||||
// tmplSource is the Go source template that the generated Go contract binding
|
||||
// is based on.
|
||||
//
|
||||
//go:embed source.go.tpl
|
||||
var tmplSourceGo string
|
||||
var tmplSource string
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
package bind
|
||||
|
||||
// tmplSourceV2 is the Go source template that the generated
|
||||
// Go contract binding V2 is based on.
|
||||
const tmplSourceV2 = `
|
||||
// Code generated via abigen V2 - DO NOT EDIT.
|
||||
// This file is a generated binding and any manual changes will be lost.
|
||||
|
||||
package {{.Package}}
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"errors"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var (
|
||||
_ = errors.New
|
||||
_ = big.NewInt
|
||||
_ = bind.Bind
|
||||
_ = common.Big1
|
||||
_ = types.BloomLookup
|
||||
_ = abi.ConvertType
|
||||
)
|
||||
|
||||
{{$structs := .Structs}}
|
||||
{{range $structs}}
|
||||
// {{.Name}} is an auto generated low-level Go binding around an user-defined struct.
|
||||
type {{.Name}} struct {
|
||||
{{range $field := .Fields}}
|
||||
{{$field.Name}} {{$field.Type}}{{end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{range $contract := .Contracts}}
|
||||
var {{$contract.Type}}LibraryDeps = map[string]*bind.MetaData{
|
||||
{{range $name, $pattern := .AllLibraries -}}
|
||||
"{{$pattern}}": {{$name}}MetaData,
|
||||
{{ end}}
|
||||
}
|
||||
|
||||
// {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract.
|
||||
var {{.Type}}MetaData = &bind.MetaData{
|
||||
ABI: "{{.InputABI}}",
|
||||
{{if $contract.FuncSigs -}}
|
||||
Sigs: map[string]string{
|
||||
{{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}",
|
||||
{{end}}
|
||||
},
|
||||
{{end -}}
|
||||
{{if .InputBin -}}
|
||||
Bin: "0x{{.InputBin}}",
|
||||
{{end}}
|
||||
}
|
||||
|
||||
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
||||
type {{.Type}} struct {
|
||||
abi abi.ABI
|
||||
}
|
||||
|
||||
// New{{.Type}} creates a new instance of {{.Type}}.
|
||||
func New{{.Type}}() (*{{.Type}}, error) {
|
||||
parsed, err := {{.Type}}MetaData.GetAbi()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{.Type}}{abi: *parsed}, nil
|
||||
}
|
||||
|
||||
// TODO: create custom exported types where unpack would generate a struct return.
|
||||
|
||||
// TODO: test constructor with inputs
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}) PackConstructor({{range .Constructor.Inputs}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ([]byte, error) {
|
||||
return _{{$contract.Type}}.abi.Pack("" {{range .Constructor.Inputs}}, {{.Name}}{{end}})
|
||||
}
|
||||
|
||||
{{range .Calls}}
|
||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}) Pack{{.Normalized.Name}}({{range .Normalized.Inputs}} {{.Name}} {{bindtype .Type $structs}}, {{end}}) ([]byte, error) {
|
||||
return _{{$contract.Type}}.abi.Pack("{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||
}
|
||||
|
||||
{{/* Unpack method is needed only when there are return args */}}
|
||||
{{if .Normalized.Outputs }}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}(data []byte) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) {
|
||||
out, err := _{{$contract.Type}}.abi.Unpack("{{.Original.Name}}", data)
|
||||
{{if .Structured}}
|
||||
outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} })
|
||||
if err != nil {
|
||||
return *outstruct, err
|
||||
}
|
||||
{{range $i, $t := .Normalized.Outputs}}
|
||||
outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}}
|
||||
|
||||
return *outstruct, err
|
||||
{{else}}
|
||||
if err != nil {
|
||||
return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err
|
||||
}
|
||||
{{range $i, $t := .Normalized.Outputs}}
|
||||
out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}}
|
||||
|
||||
return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err
|
||||
{{end}}
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{range .Events}}
|
||||
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
|
||||
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
|
||||
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}}
|
||||
Raw *types.Log // Blockchain specific contextual infos
|
||||
}
|
||||
|
||||
func {{$contract.Type}}{{.Normalized.Name}}EventID() common.Hash {
|
||||
return common.HexToHash("{{.Original.ID}}")
|
||||
}
|
||||
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}Event(log *types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
|
||||
event := "{{.Normalized.Name}}"
|
||||
if log.Topics[0] != _{{$contract.Type}}.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
}
|
||||
out := new({{$contract.Type}}{{.Normalized.Name}})
|
||||
if len(log.Data) > 0 {
|
||||
if err := _{{$contract.Type}}.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var indexed abi.Arguments
|
||||
for _, arg := range _{{$contract.Type}}.abi.Events[event].Inputs {
|
||||
if arg.Indexed {
|
||||
indexed = append(indexed, arg)
|
||||
}
|
||||
}
|
||||
if err := abi.ParseTopics(out, indexed, log.Topics[1:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Raw = log
|
||||
return out, nil
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`
|
|
@ -0,0 +1,44 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
library RecursiveDep {
|
||||
function AddOne(uint256 val) public pure returns (uint256 ret) {
|
||||
return val + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Array function to delete element at index and re-organize the array
|
||||
// so that there are no gaps between the elements.
|
||||
library Array {
|
||||
using RecursiveDep for uint256;
|
||||
|
||||
function remove(uint256[] storage arr, uint256 index) public {
|
||||
// Move the last element into the place to delete
|
||||
require(arr.length > 0, "Can't remove from empty array");
|
||||
arr[index] = arr[arr.length - 1];
|
||||
arr[index] = arr[index].AddOne();
|
||||
arr.pop();
|
||||
}
|
||||
}
|
||||
|
||||
contract TestArray {
|
||||
using Array for uint256[];
|
||||
|
||||
uint256[] public arr;
|
||||
|
||||
function testArrayRemove(uint256 value) public {
|
||||
for (uint256 i = 0; i < 3; i++) {
|
||||
arr.push(i);
|
||||
}
|
||||
|
||||
arr.remove(1);
|
||||
|
||||
assert(arr.length == 2);
|
||||
assert(arr[0] == 0);
|
||||
assert(arr[1] == 2);
|
||||
}
|
||||
|
||||
constructor(uint256 foobar) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type V2Backend interface {
|
||||
SuggestGasPrice(ctx context.Context) (*big.Int, error)
|
||||
PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error)
|
||||
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
|
||||
SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
|
||||
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
|
||||
SendTransaction(ctx context.Context, tx *types.Transaction) error
|
||||
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
|
||||
EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
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)
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2_generated_testcase"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2_testcase_library"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"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/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient/simulated"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
|
||||
// JSON returns a parsed ABI interface and error if it failed.
|
||||
func JSON(reader io.Reader) (abi.ABI, error) {
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
var instance abi.ABI
|
||||
if err := dec.Decode(&instance); err != nil {
|
||||
return abi.ABI{}, err
|
||||
}
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
func TestV2(t *testing.T) {
|
||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
backend := simulated.NewBackend(
|
||||
types.GenesisAlloc{
|
||||
testAddr: {Balance: big.NewInt(10000000000000000)},
|
||||
},
|
||||
func(nodeConf *node.Config, ethConf *ethconfig.Config) {
|
||||
ethConf.Genesis.Difficulty = big.NewInt(0)
|
||||
},
|
||||
)
|
||||
defer backend.Close()
|
||||
|
||||
contractABI, err := JSON(strings.NewReader(v2_generated_testcase.V2GeneratedTestcaseMetaData.ABI))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
signer := types.LatestSigner(params.AllDevChainProtocolChanges)
|
||||
opts := bind.TransactOpts{
|
||||
From: testAddr,
|
||||
Nonce: nil,
|
||||
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
signedTx, err := tx.WithSignature(signer, signature)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return signedTx, nil
|
||||
},
|
||||
Context: context.Background(),
|
||||
}
|
||||
// we should just be able to use the backend directly, instead of using
|
||||
// this deprecated interface. However, the simulated backend no longer
|
||||
// implements backends.SimulatedBackend...
|
||||
bindBackend := backends.SimulatedBackend{
|
||||
Backend: backend,
|
||||
Client: backend.Client(),
|
||||
}
|
||||
address, tx, _, err := bind.DeployContract(&opts, contractABI, common.Hex2Bytes(v2_generated_testcase.V2GeneratedTestcaseMetaData.Bin), &bindBackend)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = bind.WaitDeployed(context.Background(), &bindBackend, tx)
|
||||
if err != nil {
|
||||
t.Fatalf("error deploying bound contract: %+v", err)
|
||||
}
|
||||
|
||||
contract, err := v2_generated_testcase.NewV2GeneratedTestcase()
|
||||
if err != nil {
|
||||
t.Fatal(err) // can't happen here with the example used. consider removing this block
|
||||
}
|
||||
//contractInstance := v2_generated_testcase.NewV2GeneratedTestcaseInstance(contract, address, bindBackend)
|
||||
contractInstance := ContractInstance{
|
||||
Address: address,
|
||||
Backend: bindBackend,
|
||||
}
|
||||
sinkCh := make(chan *v2_generated_testcase.V2GeneratedTestcaseStruct)
|
||||
// q: what extra functionality is given by specifying this as a custom method, instead of catching emited methods
|
||||
// from the sync channel?
|
||||
unpackStruct := func(log *types.Log) (*v2_generated_testcase.V2GeneratedTestcaseStruct, error) {
|
||||
res, err := contract.UnpackStructEvent(log)
|
||||
return res, err
|
||||
}
|
||||
watchOpts := bind.WatchOpts{
|
||||
Start: nil,
|
||||
Context: context.Background(),
|
||||
}
|
||||
// TODO: test using various topics
|
||||
// q: does nil topics mean to accept any?
|
||||
sub, err := WatchLogs[v2_generated_testcase.V2GeneratedTestcaseStruct](&contractInstance, &watchOpts, v2_generated_testcase.V2GeneratedTestcaseStructEventID(), unpackStruct, sinkCh, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
// send a balance to our contract (contract must accept ether by default)
|
||||
}
|
||||
|
||||
func TestDeployment(t *testing.T) {
|
||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
backend := simulated.NewBackend(
|
||||
types.GenesisAlloc{
|
||||
testAddr: {Balance: big.NewInt(10000000000000000)},
|
||||
},
|
||||
func(nodeConf *node.Config, ethConf *ethconfig.Config) {
|
||||
ethConf.Genesis.Difficulty = big.NewInt(0)
|
||||
},
|
||||
)
|
||||
defer backend.Close()
|
||||
|
||||
_, err := JSON(strings.NewReader(v2_generated_testcase.V2GeneratedTestcaseMetaData.ABI))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
signer := types.LatestSigner(params.AllDevChainProtocolChanges)
|
||||
opts := bind.TransactOpts{
|
||||
From: testAddr,
|
||||
Nonce: nil,
|
||||
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
signedTx, err := tx.WithSignature(signer, signature)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return signedTx, nil
|
||||
},
|
||||
Context: context.Background(),
|
||||
}
|
||||
// we should just be able to use the backend directly, instead of using
|
||||
// this deprecated interface. However, the simulated backend no longer
|
||||
// implements backends.SimulatedBackend...
|
||||
bindBackend := backends.SimulatedBackend{
|
||||
Backend: backend,
|
||||
Client: backend.Client(),
|
||||
}
|
||||
|
||||
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stdout, log.LevelDebug, true)))
|
||||
|
||||
///LinkAndDeployContractsWithOverride(&opts, bindBackend, v2_test)
|
||||
deployTxs, err := DeployContracts(&opts, bindBackend, []byte{}, v2_testcase_library.TestArrayLibraryDeps)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %+v\n", err)
|
||||
}
|
||||
for _, tx := range deployTxs {
|
||||
fmt.Println("waiting for deployment")
|
||||
_, err = bind.WaitDeployed(context.Background(), &bindBackend, tx)
|
||||
if err != nil {
|
||||
t.Fatalf("error deploying bound contract: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* test-cases that should be extracted from v1 tests
|
||||
|
||||
* EventChecker
|
||||
|
||||
*/
|
|
@ -63,15 +63,14 @@ var (
|
|||
Name: "out",
|
||||
Usage: "Output file for the generated binding (default = stdout)",
|
||||
}
|
||||
langFlag = &cli.StringFlag{
|
||||
Name: "lang",
|
||||
Usage: "Destination language for the bindings (go)",
|
||||
Value: "go",
|
||||
}
|
||||
aliasFlag = &cli.StringFlag{
|
||||
Name: "alias",
|
||||
Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2",
|
||||
}
|
||||
v2Flag = &cli.BoolFlag{
|
||||
Name: "v2",
|
||||
Usage: "Generates v2 bindings",
|
||||
}
|
||||
)
|
||||
|
||||
var app = flags.NewApp("Ethereum ABI wrapper code generator")
|
||||
|
@ -86,8 +85,8 @@ func init() {
|
|||
excFlag,
|
||||
pkgFlag,
|
||||
outFlag,
|
||||
langFlag,
|
||||
aliasFlag,
|
||||
v2Flag,
|
||||
}
|
||||
app.Action = abigen
|
||||
}
|
||||
|
@ -98,13 +97,6 @@ func abigen(c *cli.Context) error {
|
|||
if c.String(pkgFlag.Name) == "" {
|
||||
utils.Fatalf("No destination package specified (--pkg)")
|
||||
}
|
||||
var lang bind.Lang
|
||||
switch c.String(langFlag.Name) {
|
||||
case "go":
|
||||
lang = bind.LangGo
|
||||
default:
|
||||
utils.Fatalf("Unsupported destination language \"%s\" (--lang)", c.String(langFlag.Name))
|
||||
}
|
||||
// If the entire solidity code was specified, build and bind based on that
|
||||
var (
|
||||
abis []string
|
||||
|
@ -216,7 +208,15 @@ func abigen(c *cli.Context) error {
|
|||
}
|
||||
}
|
||||
// Generate the contract binding
|
||||
code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases)
|
||||
var (
|
||||
code string
|
||||
err error
|
||||
)
|
||||
if c.IsSet(v2Flag.Name) {
|
||||
code, err = bind.BindV2(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
|
||||
} else {
|
||||
code, err = bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
|
||||
}
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to generate ABI binding: %v", err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue