-
Notifications
You must be signed in to change notification settings - Fork 20.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ABIGEN v2 #26782
base: master
Are you sure you want to change the base?
ABIGEN v2 #26782
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty cool! I don't like the *2
naming, but I guess Felix likes it, so I won't argue to hard against it. I'm going to do some more tests and then approve
Something is broken here:
With this contract it produces invalid code:
Produced code:
(out is never used) I think the issue is that you generate Unpacking functions for solidity functions that have no return value. |
You should probably do with fmt the same thing as with the other imports: |
Aha, we don't need an unpack method when there are no return params. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add some tests, and more documentation.
@s1na are you still actively working on this PR? It is of interest to our project so we would be willing to devote some time to helping getting it over the line. Our use case is: we are building a state channel client in Go. Currently, it manages a private key, and acts as a signer. It uses the One suggestion I would have is to maintain backward compatibility with the current version of |
Hi @geoknee, great to see your interest in this PR. I'm not actively working on this, although I plan to finish it at some point. Your help is appreciated. I want to first address
We really want to get v2 out instead of packing more features into v1. I saw you have already generated v2 bindings for your contract. It'd be already great to hear if things are working and you've had any friction points. The biggest outstanding point is to add a test suite for v2. |
Fair enough, we can use both side by side in our project without too much pain.
I did spot a couple of problems:
statechannels/go-nitro@a1e9dd7 I pushed a fix here s1na#10.
|
c044348
to
008028e
Compare
accounts/abi/bind/v2/v2_test.go
Outdated
"context" | ||
"encoding/json" | ||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" | ||
"github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2_generated_testcase" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Build fails on this import atm
@MariusVanDerWijden I have a few things I am trying to wrap with this. It's in a bit of a messy state, and I'd ask to hold off on review until I can get this finished (couple of hours). |
return rootMetadatas | ||
} | ||
|
||
func __linkDeps(metadata MetaData, depMap map[string]*MetaData, roots *map[string]struct{}) MetaData { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's up with this name? Why such a strange prefix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's the recursing part of linkDeps
. I didn't know what to call it. That code needs a bit of love (documentation/renaming where appropriate) but I'm trying to port the v1 bind tests over to v2 atm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, that's now how we name things in go (the underscores). I'm sure you can figure out a more descriptive name
accounts/abi/bind/dep_tree.go
Outdated
// if this contract/library depends on other libraries deploy them (and their dependencies) first | ||
for _, dep := range metadata.Deps { | ||
if err := d.linkAndDeploy(dep); err != nil { | ||
return err | ||
} | ||
} | ||
// if we just deployed any prerequisite contracts, link their deployed addresses into the bytecode to produce | ||
// a deployer bytecode for this contract. | ||
deployerCode := metadata.Bin | ||
for _, dep := range metadata.Deps { | ||
linkAddr, ok := d.deployedAddrs[dep.Pattern] | ||
if !ok { | ||
linkAddr = d.overrideAddrs[dep.Pattern] | ||
} | ||
deployerCode = strings.ReplaceAll(deployerCode, "__$"+dep.Pattern+"$__", strings.ToLower(linkAddr.String()[2:])) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can combine these two steps, and also make it look nicer if you change the signature to return common.Address, error
:
// if this contract/library depends on other libraries deploy them (and their dependencies) first
deployerCode := metadata.Bin
for _, dep := range metadata.Deps {
addr, err := d.linkAndDeploy(dep)
if err != nil {
return common.Address{}, err
}
// link their deployed addresses into the bytecode to produce
deployerCode = strings.ReplaceAll(deployerCode, "__$"+dep.Pattern+"$__", strings.ToLower(addr.String()[2:]))
}
(still store the addr/tx as previous, though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See second commit in 62a7806
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. I'm going to pull these in to the branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, I wouldn't mind if you squash things a bit, perhaps squash sequences of commits from one author into one commit.
accounts/abi/bind/dep_tree.go
Outdated
if deployParams.inputs != nil { | ||
deployer.input = map[string][]byte{contract.Pattern: deployParams.inputs[contract.Pattern]} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This clause is not covered by testing (commenting it out doesn't cause failures). And it's not surprising, because the clause doesn't make sense.
On each iteration of a contract
, it overwrites the input
-map with the current contract
input.
Why not just copy/clone the deployParams.inputs
map to the deployer
, already when creating the deployer
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Afaict you can turn it into
func LinkAndDeploy(deployParams *DeploymentParams, deploy DeployFn) (res *DeploymentResult, err error) {
deployer := newDepTreeDeployer(deployParams.overrides, deploy, deployParams.inputs)
for _, contract := range deployParams.contracts {
if _ err := deployer.linkAndDeploy(contract); err != nil {
return deployer.result(), err
}
}
return deployer.result(), nil
accounts/abi/bind/bindv2.go
Outdated
func (b *contractBinder) RegisterCallIdentifier(id string) (string, error) { | ||
return b.registerIdentifier(b.callIdentifiers, id) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WHy is this public accessor needed? Are users expected to use this? In what scenario?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a public method on a private type. Will move it to lowercase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a public method on a private type. Will move it to lowercase.
i.e. I didn't mean it for public consumption. a mistake on my part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, if it's not for public, is it even needed? doesn't save a whole lot of typing really
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not really needed no.
accounts/abi/bind/bind.go
Outdated
LangGo: abi.ToCamelCase, | ||
} | ||
// conform to Go naming conventions. | ||
var methodNormalizer = abi.ToCamelCase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed at all (now that you removed the lang-dependent map). It's never actually changed, is it? So why not just use abi.ToCamelCase
and keep it simple?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same with capitalise
below, really
normalized.Inputs = make([]abi.Argument, len(original.Inputs)) | ||
copy(normalized.Inputs, original.Inputs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
normalized.Inputs = make([]abi.Argument, len(original.Inputs)) | |
copy(normalized.Inputs, original.Inputs) | |
normalized.Inputs = slices.Clone(original.Inputs) |
And same for outputs.
…e subtest that failed.
accounts/abi/bind/bindv2.go
Outdated
|
||
// normalizeErrorOrEventFields normalizes errors/events for emitting through bindings: | ||
// Any anonymous fields are given generated names. | ||
func (cb *contractBinder) normalizeErrorOrEventFields(originalInputs abi.Arguments) abi.Arguments { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method does multiple things. Howver, it only uses cb
in order to do this:
if hasStruct(input.Type) {
cb.binder.BindStructType(input.Type)
}
Hence, would it be possible to change into
// normalizeErrorOrEventFields normalizes errors/events for emitting through bindings:
// Any anonymous fields are given generated names.
func (cb *contractBinder) normalizeErrorOrEventFields(originalInputs abi.Arguments) abi.Arguments {
args := normalizeArgs(originalInputs)
// Soup up the struct types
for _, input := range args {
if hasStruct(input.Type) {
cb.binder.BindStructType(input.Type)
}
}
return args
}
func normalizeArgs(originalInputs abi.Arguments) abi.Arguments {
...
?
Would make it more testable
accounts/abi/bind/bindv2.go
Outdated
if !used[capitalise(normalizedArguments[i].Name)] { | ||
used[capitalise(normalizedArguments[i].Name)] = true | ||
break | ||
} | ||
normalizedArguments[i].Name = fmt.Sprintf("%s%d", normalizedArguments[i].Name, index) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks slightly wrong to me. We check for capitalized version of the name, and then iterate on changing the name. But we don't actually capitalize the name. So the used-check uses the capitalized version, but non-capitalized is set as name.
It's a big confusing, and looks like a bug
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be something like this?
func normalizeArguments(args abi.Arguments) abi.Arguments {
args := slices.Clone(args)
used := make(map[string]bool)
for i, input := range args {
if input.Name == "" || isKeyWord(input.Name) {
args[i].Name = fmt.Sprintf("arg%d", i)
}
name := capitalise(args[i].Name)
for index := 0; ; index++ {
if !used[name] {
used[name] = true
break
}
// try next
name = capitalise(fmt.Sprintf("%s%d", args[i].Name, index))
}
args[i].Name = name
}
return args
}
} | ||
|
||
// bindEvent normalizes an event and registers it to be emitted in the bindings. | ||
func (cb *contractBinder) bindEvent(original abi.Event) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move bindEvent
and bindError
so they are right below bindMethod
type tmplDataV2 struct { | ||
Package string // Name of the package to place the generated file in | ||
Contracts map[string]*tmplContractV2 // List of contracts to generate into this file | ||
Libraries map[string]string // Map of the contract's name to link pattern |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only thing that's different which warranted addition of templDataV2
.
…t fails before the fix, and also also a few more test cases for args normalization.
…t the backing-array provided by the API user from being mutated). re-enable deployment-with-overrides test
…the results of feeding v1 binding test ABIs through the v2 generator.
Looks like the tests fail at the moment @jwasinger because the variable |
I'd lean towards not generating them on the fly. We've discussed this previously and concluded that generating them at runtime makes the testing logic more complicated than it has to be. We test the properties of the generated bindings in various testcases in |
…ndings changed s.t. constructor unpack does not return error)
…re mistakenly generated with a broken iteration of the code, and empty/broken bindings were included)
This PR adds a new version of abigen which will co-exist parallel to the existing version for a while. To generate v2 bindings provide the
--v2
flag toabigen
cmd.Summary
The main point of abigen v2 is having a lightweight generated binding which allows for more composability. This is possible now thanks to Go generics. "only" methods to pack and unpack call and return data are generated for a contract. As well as unpacking of logs.
To interact with the contract a library is available with functions such as
Call
,Transact
,FilterLogs
, etc. These take in the packed calldata, or a function pointer to unpack return data.Features
The new version is at feature-parity with v1 at a much lower generated binding size. The only missing feature as of now is sessions.
Example
V1 and v2 bindings for a sample contract are available here: https://gist.github.com/s1na/05f2d241b07372b41ba1747ce6e098b7. A sample script using v2 is available in
main.go
.