Skip to content

Commit

Permalink
Merge pull request #195 from ian0371/bind
Browse files Browse the repository at this point in the history
abi/bind: Fix bugs
  • Loading branch information
ian0371 authored Jan 6, 2025
2 parents 0a06768 + 4e7f8d7 commit 7b4a6f3
Show file tree
Hide file tree
Showing 23 changed files with 31,944 additions and 20,370 deletions.
2 changes: 1 addition & 1 deletion accounts/abi/argument.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{
dst := reflect.ValueOf(v).Elem()
src := reflect.ValueOf(marshalledValues)

if dst.Kind() == reflect.Struct && src.Kind() != reflect.Struct {
if dst.Kind() == reflect.Struct {
return set(dst.Field(0), src)
}
return set(dst, src)
Expand Down
70 changes: 57 additions & 13 deletions accounts/abi/bind/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,53 @@ const (
LangObjC
)

func isKeyWord(arg string) bool {
switch arg {
case "break":
case "case":
case "chan":
case "const":
case "continue":
case "default":
case "defer":
case "else":
case "fallthrough":
case "for":
case "func":
case "go":
case "goto":
case "if":
case "import":
case "interface":
case "iota":
case "map":
case "make":
case "new":
case "package":
case "range":
case "return":
case "select":
case "struct":
case "switch":
case "type":
case "var":
default:
return false
}

return true
}

// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
// to be used as is in client code, but rather as an intermediate struct which
// enforces compile time type safety and naming convention opposed to having to
// 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, runtimebytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
var (
// contracts is the map of each individual contract requested binding
contracts = make(map[string]*tmplContract)

// structs is the map of all reclared structs shared by passed contracts.
// structs is the map of all redeclared structs shared by passed contracts.
structs = make(map[string]*tmplStruct)

// isLib is the map used to flag each encountered library as such
Expand Down Expand Up @@ -81,14 +118,21 @@ func Bind(types []string, abis []string, bytecodes []string, runtimebytecodes []
fallback *tmplMethod
receive *tmplMethod

// identifiers are used to detect duplicated identifier of function
// and event. For all calls, transacts and events, abigen will generate
// identifiers are used to detect duplicated identifiers of functions
// and events. For all calls, transacts and events, abigen will generate
// corresponding bindings. However we have to ensure there is no
// identifier coliision in the bindings of these categories.
// identifier collisions in the bindings of these categories.
callIdentifiers = make(map[string]bool)
transactIdentifiers = make(map[string]bool)
eventIdentifiers = make(map[string]bool)
)

for _, input := range evmABI.Constructor.Inputs {
if hasStruct(input.Type) {
bindStructType[lang](input.Type, structs)
}
}

for _, original := range evmABI.Methods {
// Normalize the method for capital cases and non-anonymous inputs/outputs
normalized := original
Expand All @@ -106,7 +150,7 @@ func Bind(types []string, abis []string, bytecodes []string, runtimebytecodes []
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
copy(normalized.Inputs, original.Inputs)
for j, input := range normalized.Inputs {
if input.Name == "" {
if input.Name == "" || isKeyWord(input.Name) {
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
}
if hasStruct(input.Type) {
Expand Down Expand Up @@ -149,7 +193,7 @@ func Bind(types []string, abis []string, bytecodes []string, runtimebytecodes []
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
copy(normalized.Inputs, original.Inputs)
for j, input := range normalized.Inputs {
if input.Name == "" {
if input.Name == "" || isKeyWord(input.Name) {
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
}
if hasStruct(input.Type) {
Expand Down Expand Up @@ -248,7 +292,7 @@ var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) stri
LangJava: bindTypeJava,
}

// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go one.
// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones.
func bindBasicTypeGo(kind abi.Type) string {
switch kind.T {
case abi.AddressTy:
Expand Down Expand Up @@ -371,15 +415,15 @@ var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct)
}

// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same
// funcionality as for simple types, but dynamic types get converted to hashes.
// 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)

// todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored.
//
// We only convert stringS and bytes to hash, still need to deal with
// We only convert strings and bytes to hash, still need to deal with
// array(both fixed-size and dynamic-size) and struct.
if bound == "string" || bound == "[]byte" {
bound = "common.Hash"
Expand Down Expand Up @@ -417,7 +461,7 @@ var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct
func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
switch kind.T {
case abi.TupleTy:
// We compose raw struct name and canonical parameter expression
// We compose a raw struct name and a canonical parameter expression
// together here. The reason is before solidity v0.5.11, kind.TupleRawName
// is empty, so we use canonical parameter expression to distinguish
// different struct definition. From the consideration of backward
Expand Down Expand Up @@ -488,7 +532,7 @@ func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
}

// namedType is a set of functions that transform language specific types to
// named versions that my be used inside method names.
// 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") },
LangJava: namedTypeJava,
Expand Down Expand Up @@ -530,7 +574,7 @@ 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 concentions.
// conform to target language naming conventions.
var methodNormalizer = map[Lang]func(string) string{
LangGo: abi.ToCamelCase,
LangJava: decapitalise,
Expand Down
183 changes: 183 additions & 0 deletions accounts/abi/bind/bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,43 @@ var bindTests = []struct {
nil,
nil,
},
{
`NonExistentStruct`,
`
contract NonExistentStruct {
function Struct() public view returns(uint256 a, uint256 b) {
return (10, 10);
}
}
`,
[]string{`6080604052348015600f57600080fd5b5060888061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063d5f6622514602d575b600080fd5b6033604c565b6040805192835260208301919091528051918290030190f35b600a809156fea264697066735822beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef64736f6c6343decafe0033`},
[]string{`[{"inputs":[],"name":"Struct","outputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"stateMutability":"pure","type":"function"}]`},
`
"github.com/kaiachain/kaia/accounts/abi/bind"
"github.com/kaiachain/kaia/accounts/abi/bind/backends"
"github.com/kaiachain/kaia/blockchain"
"github.com/kaiachain/kaia/common"
`,
`
// Create a simulator and wrap a non-deployed contract
sim := backends.NewSimulatedBackend(blockchain.GenesisAlloc{})
defer sim.Close()
nonexistent, err := NewNonExistentStruct(common.Address{}, sim)
if err != nil {
t.Fatalf("Failed to access non-existent contract: %v", err)
}
// Ensure that contract calls fail with the appropriate error
if res, err := nonexistent.Struct(nil); err == nil {
t.Fatalf("Call succeeded on non-existent contract: %v", res)
} else if (err != bind.ErrNoCode) {
t.Fatalf("Error mismatch: have %v, want %v", err, bind.ErrNoCode)
}
`,
nil,
nil,
nil,
nil,
},
// Tests that gas estimation works for contracts with weird gas mechanics too.
{
`FunkyGasPattern`,
Expand Down Expand Up @@ -1763,6 +1800,76 @@ var bindTests = []struct {
nil,
nil,
},
// Test resolving single struct argument
{
`NewSingleStructArgument`,
`
pragma solidity ^0.8.0;
contract NewSingleStructArgument {
struct MyStruct{
uint256 a;
uint256 b;
}
event StructEvent(MyStruct s);
function TestEvent() public {
emit StructEvent(MyStruct({a: 1, b: 2}));
}
}
`,
[]string{"608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806324ec1d3f14602d575b600080fd5b60336035565b005b7fb4b2ff75e30cb4317eaae16dd8a187dd89978df17565104caa6c2797caae27d460405180604001604052806001815260200160028152506040516078919060ba565b60405180910390a1565b6040820160008201516096600085018260ad565b50602082015160a7602085018260ad565b50505050565b60b48160d3565b82525050565b600060408201905060cd60008301846082565b92915050565b600081905091905056fea26469706673582212208823628796125bf9941ce4eda18da1be3cf2931b231708ab848e1bd7151c0c9a64736f6c63430008070033"},
[]string{`[{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"indexed":false,"internalType":"struct Test.MyStruct","name":"s","type":"tuple"}],"name":"StructEvent","type":"event"},{"inputs":[],"name":"TestEvent","outputs":[],"stateMutability":"nonpayable","type":"function"}]`},
`
"math/big"
"github.com/kaiachain/kaia/accounts/abi/bind"
"github.com/kaiachain/kaia/accounts/abi/bind/backends"
"github.com/kaiachain/kaia/blockchain"
"github.com/kaiachain/kaia/crypto"
`,
`
var (
key, _ = crypto.GenerateKey()
auth = bind.NewKeyedTransactor(key)
sim = backends.NewSimulatedBackend(blockchain.GenesisAlloc{auth.From: {Balance: big.NewInt(1000000000000000000)}})
)
defer sim.Close()
_, _, d, err := DeployNewSingleStructArgument(auth, sim)
if err != nil {
t.Fatalf("Failed to deploy contract %v", err)
}
sim.Commit()
_, err = d.TestEvent(auth)
if err != nil {
t.Fatalf("Failed to call contract %v", err)
}
sim.Commit()
it, err := d.FilterStructEvent(nil)
if err != nil {
t.Fatalf("Failed to filter contract event %v", err)
}
var count int
for it.Next() {
if it.Event.S.A.Cmp(big.NewInt(1)) != 0 {
t.Fatal("Unexpected contract event")
}
if it.Event.S.B.Cmp(big.NewInt(2)) != 0 {
t.Fatal("Unexpected contract event")
}
count += 1
}
if count != 1 {
t.Fatal("Unexpected contract event number")
}
`,
nil,
nil,
nil,
nil,
},
// Test errors introduced in v0.8.4
{
`NewErrors`,
Expand Down Expand Up @@ -1817,6 +1924,82 @@ var bindTests = []struct {
nil,
nil,
},
{
name: `ConstructorWithStructParam`,
contract: `
pragma solidity >=0.8.0 <0.9.0;
contract ConstructorWithStructParam {
struct StructType {
uint256 field;
}
constructor(StructType memory st) {}
}
`,
bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`},
abi: []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`},
imports: `
"math/big"
"github.com/kaiachain/kaia/accounts/abi/bind"
"github.com/kaiachain/kaia/accounts/abi/bind/backends"
"github.com/kaiachain/kaia/blockchain"
"github.com/kaiachain/kaia/crypto"
`,
tester: `
var (
key, _ = crypto.GenerateKey()
auth = bind.NewKeyedTransactor(key)
sim = backends.NewSimulatedBackend(blockchain.GenesisAlloc{auth.From: {Balance: big.NewInt(1000000000000000000)}})
)
defer sim.Close()
_, tx, _, err := DeployConstructorWithStructParam(auth, sim, ConstructorWithStructParamStructType{Field: big.NewInt(42)})
if err != nil {
t.Fatalf("DeployConstructorWithStructParam() got err %v; want nil err", err)
}
sim.Commit()
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
t.Logf("Deployment tx: %+v", tx)
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
}
`,
},
{
name: "RangeKeyword",
contract: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract keywordcontract {
function functionWithKeywordParameter(range uint256) public pure {}
}
`,
bytecode: []string{"0x608060405234801561001057600080fd5b5060dc8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063527a119f14602d575b600080fd5b60436004803603810190603f9190605b565b6045565b005b50565b6000813590506055816092565b92915050565b600060208284031215606e57606d608d565b5b6000607a848285016048565b91505092915050565b6000819050919050565b600080fd5b6099816083565b811460a357600080fd5b5056fea2646970667358221220d4f4525e2615516394055d369fb17df41c359e5e962734f27fd683ea81fd9db164736f6c63430008070033"},
abi: []string{`[{"inputs":[{"internalType":"uint256","name":"range","type":"uint256"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`},
imports: `
"math/big"
"github.com/kaiachain/kaia/accounts/abi/bind"
"github.com/kaiachain/kaia/accounts/abi/bind/backends"
"github.com/kaiachain/kaia/blockchain"
"github.com/kaiachain/kaia/crypto"
`,
tester: `
var (
key, _ = crypto.GenerateKey()
auth = bind.NewKeyedTransactor(key)
sim = backends.NewSimulatedBackend(blockchain.GenesisAlloc{auth.From: {Balance: big.NewInt(1000000000000000000)}})
)
_, tx, _, err := DeployRangeKeyword(auth, sim)
if err != nil {
t.Fatalf("error deploying contract: %v", err)
}
sim.Commit()
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
t.Errorf("error deploying the contract: %v", err)
}
`,
},
}

// Tests that packages generated by the binder can be successfully compiled and
Expand Down
Loading

0 comments on commit 7b4a6f3

Please sign in to comment.