good morning!!!!

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • github/maticnetwork/bor
  • open/bor
2 results
Show changes
Showing
with 3826 additions and 1598 deletions
...@@ -10,45 +10,64 @@ ...@@ -10,45 +10,64 @@
GOBIN = ./build/bin GOBIN = ./build/bin
GO ?= latest GO ?= latest
GORUN = env GO111MODULE=on go run
GOPATH = $(shell go env GOPATH)
bor:
$(GORUN) build/ci.go install ./cmd/geth
mkdir -p $(GOPATH)/bin/
cp $(GOBIN)/geth $(GOBIN)/bor
cp $(GOBIN)/* $(GOPATH)/bin/
bor-all:
$(GORUN) build/ci.go install
mkdir -p $(GOPATH)/bin/
cp $(GOBIN)/geth $(GOBIN)/bor
cp $(GOBIN)/* $(GOPATH)/bin/
protoc:
protoc --go_out=. --go-grpc_out=. ./command/server/proto/*.proto
geth: geth:
build/env.sh go run build/ci.go install ./cmd/geth $(GORUN) build/ci.go install ./cmd/geth
@echo "Done building." @echo "Done building."
@echo "Run \"$(GOBIN)/geth\" to launch geth." @echo "Run \"$(GOBIN)/geth\" to launch geth."
all: all:
build/env.sh go run build/ci.go install $(GORUN) build/ci.go install
android: android:
build/env.sh go run build/ci.go aar --local $(GORUN) build/ci.go aar --local
@echo "Done building." @echo "Done building."
@echo "Import \"$(GOBIN)/geth.aar\" to use the library." @echo "Import \"$(GOBIN)/geth.aar\" to use the library."
@echo "Import \"$(GOBIN)/geth-sources.jar\" to add javadocs"
@echo "For more info see https://stackoverflow.com/questions/20994336/android-studio-how-to-attach-javadoc"
ios: ios:
build/env.sh go run build/ci.go xcode --local $(GORUN) build/ci.go xcode --local
@echo "Done building." @echo "Done building."
@echo "Import \"$(GOBIN)/Geth.framework\" to use the library." @echo "Import \"$(GOBIN)/Geth.framework\" to use the library."
test: all test:
build/env.sh go run build/ci.go test # Skip mobile and cmd tests since they are being deprecated
go test -v $$(go list ./... | grep -v go-ethereum/cmd/) -cover -coverprofile=cover.out
lint: ## Run linters. lint: ## Run linters.
build/env.sh go run build/ci.go lint $(GORUN) build/ci.go lint
clean: clean:
./build/clean_go_build_cache.sh env GO111MODULE=on go clean -cache
rm -fr build/_workspace/pkg/ $(GOBIN)/* rm -fr build/_workspace/pkg/ $(GOBIN)/*
# The devtools target installs tools required for 'go generate'. # The devtools target installs tools required for 'go generate'.
# You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'. # You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'.
devtools: devtools:
env GOBIN= go get -u golang.org/x/tools/cmd/stringer env GOBIN= go install golang.org/x/tools/cmd/stringer@latest
env GOBIN= go get -u github.com/kevinburke/go-bindata/go-bindata env GOBIN= go install github.com/kevinburke/go-bindata/go-bindata@latest
env GOBIN= go get -u github.com/fjl/gencodec env GOBIN= go install github.com/fjl/gencodec@latest
env GOBIN= go get -u github.com/golang/protobuf/protoc-gen-go env GOBIN= go install github.com/golang/protobuf/protoc-gen-go@latest
env GOBIN= go install ./cmd/abigen env GOBIN= go install ./cmd/abigen
@type "npm" 2> /dev/null || echo 'Please install node.js and npm'
@type "solc" 2> /dev/null || echo 'Please install solc' @type "solc" 2> /dev/null || echo 'Please install solc'
@type "protoc" 2> /dev/null || echo 'Please install protoc' @type "protoc" 2> /dev/null || echo 'Please install protoc'
...@@ -63,12 +82,12 @@ geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm geth-linux-mips64 get ...@@ -63,12 +82,12 @@ geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm geth-linux-mips64 get
@ls -ld $(GOBIN)/geth-linux-* @ls -ld $(GOBIN)/geth-linux-*
geth-linux-386: geth-linux-386:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/386 -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=linux/386 -v ./cmd/geth
@echo "Linux 386 cross compilation done:" @echo "Linux 386 cross compilation done:"
@ls -ld $(GOBIN)/geth-linux-* | grep 386 @ls -ld $(GOBIN)/geth-linux-* | grep 386
geth-linux-amd64: geth-linux-amd64:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/amd64 -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=linux/amd64 -v ./cmd/geth
@echo "Linux amd64 cross compilation done:" @echo "Linux amd64 cross compilation done:"
@ls -ld $(GOBIN)/geth-linux-* | grep amd64 @ls -ld $(GOBIN)/geth-linux-* | grep amd64
...@@ -77,42 +96,42 @@ geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-ar ...@@ -77,42 +96,42 @@ geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-ar
@ls -ld $(GOBIN)/geth-linux-* | grep arm @ls -ld $(GOBIN)/geth-linux-* | grep arm
geth-linux-arm-5: geth-linux-arm-5:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm-5 -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=linux/arm-5 -v ./cmd/geth
@echo "Linux ARMv5 cross compilation done:" @echo "Linux ARMv5 cross compilation done:"
@ls -ld $(GOBIN)/geth-linux-* | grep arm-5 @ls -ld $(GOBIN)/geth-linux-* | grep arm-5
geth-linux-arm-6: geth-linux-arm-6:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm-6 -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=linux/arm-6 -v ./cmd/geth
@echo "Linux ARMv6 cross compilation done:" @echo "Linux ARMv6 cross compilation done:"
@ls -ld $(GOBIN)/geth-linux-* | grep arm-6 @ls -ld $(GOBIN)/geth-linux-* | grep arm-6
geth-linux-arm-7: geth-linux-arm-7:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm-7 -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=linux/arm-7 -v ./cmd/geth
@echo "Linux ARMv7 cross compilation done:" @echo "Linux ARMv7 cross compilation done:"
@ls -ld $(GOBIN)/geth-linux-* | grep arm-7 @ls -ld $(GOBIN)/geth-linux-* | grep arm-7
geth-linux-arm64: geth-linux-arm64:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm64 -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=linux/arm64 -v ./cmd/geth
@echo "Linux ARM64 cross compilation done:" @echo "Linux ARM64 cross compilation done:"
@ls -ld $(GOBIN)/geth-linux-* | grep arm64 @ls -ld $(GOBIN)/geth-linux-* | grep arm64
geth-linux-mips: geth-linux-mips:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips --ldflags '-extldflags "-static"' -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=linux/mips --ldflags '-extldflags "-static"' -v ./cmd/geth
@echo "Linux MIPS cross compilation done:" @echo "Linux MIPS cross compilation done:"
@ls -ld $(GOBIN)/geth-linux-* | grep mips @ls -ld $(GOBIN)/geth-linux-* | grep mips
geth-linux-mipsle: geth-linux-mipsle:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mipsle --ldflags '-extldflags "-static"' -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=linux/mipsle --ldflags '-extldflags "-static"' -v ./cmd/geth
@echo "Linux MIPSle cross compilation done:" @echo "Linux MIPSle cross compilation done:"
@ls -ld $(GOBIN)/geth-linux-* | grep mipsle @ls -ld $(GOBIN)/geth-linux-* | grep mipsle
geth-linux-mips64: geth-linux-mips64:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips64 --ldflags '-extldflags "-static"' -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=linux/mips64 --ldflags '-extldflags "-static"' -v ./cmd/geth
@echo "Linux MIPS64 cross compilation done:" @echo "Linux MIPS64 cross compilation done:"
@ls -ld $(GOBIN)/geth-linux-* | grep mips64 @ls -ld $(GOBIN)/geth-linux-* | grep mips64
geth-linux-mips64le: geth-linux-mips64le:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips64le --ldflags '-extldflags "-static"' -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=linux/mips64le --ldflags '-extldflags "-static"' -v ./cmd/geth
@echo "Linux MIPS64le cross compilation done:" @echo "Linux MIPS64le cross compilation done:"
@ls -ld $(GOBIN)/geth-linux-* | grep mips64le @ls -ld $(GOBIN)/geth-linux-* | grep mips64le
...@@ -121,12 +140,12 @@ geth-darwin: geth-darwin-386 geth-darwin-amd64 ...@@ -121,12 +140,12 @@ geth-darwin: geth-darwin-386 geth-darwin-amd64
@ls -ld $(GOBIN)/geth-darwin-* @ls -ld $(GOBIN)/geth-darwin-*
geth-darwin-386: geth-darwin-386:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=darwin/386 -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=darwin/386 -v ./cmd/geth
@echo "Darwin 386 cross compilation done:" @echo "Darwin 386 cross compilation done:"
@ls -ld $(GOBIN)/geth-darwin-* | grep 386 @ls -ld $(GOBIN)/geth-darwin-* | grep 386
geth-darwin-amd64: geth-darwin-amd64:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=darwin/amd64 -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=darwin/amd64 -v ./cmd/geth
@echo "Darwin amd64 cross compilation done:" @echo "Darwin amd64 cross compilation done:"
@ls -ld $(GOBIN)/geth-darwin-* | grep amd64 @ls -ld $(GOBIN)/geth-darwin-* | grep amd64
...@@ -135,11 +154,45 @@ geth-windows: geth-windows-386 geth-windows-amd64 ...@@ -135,11 +154,45 @@ geth-windows: geth-windows-386 geth-windows-amd64
@ls -ld $(GOBIN)/geth-windows-* @ls -ld $(GOBIN)/geth-windows-*
geth-windows-386: geth-windows-386:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=windows/386 -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=windows/386 -v ./cmd/geth
@echo "Windows 386 cross compilation done:" @echo "Windows 386 cross compilation done:"
@ls -ld $(GOBIN)/geth-windows-* | grep 386 @ls -ld $(GOBIN)/geth-windows-* | grep 386
geth-windows-amd64: geth-windows-amd64:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=windows/amd64 -v ./cmd/geth $(GORUN) build/ci.go xgo -- --go=$(GO) --targets=windows/amd64 -v ./cmd/geth
@echo "Windows amd64 cross compilation done:" @echo "Windows amd64 cross compilation done:"
@ls -ld $(GOBIN)/geth-windows-* | grep amd64 @ls -ld $(GOBIN)/geth-windows-* | grep amd64
PACKAGE_NAME := github.com/maticnetwork/bor
GOLANG_CROSS_VERSION ?= v1.17.2
.PHONY: release-dry-run
release-dry-run:
@docker run \
--rm \
--privileged \
-e CGO_ENABLED=1 \
-e GITHUB_TOKEN \
-e DOCKER_USERNAME \
-e DOCKER_PASSWORD \
-v /var/run/docker.sock:/var/run/docker.sock \
-v `pwd`:/go/src/$(PACKAGE_NAME) \
-w /go/src/$(PACKAGE_NAME) \
ghcr.io/troian/golang-cross:${GOLANG_CROSS_VERSION} \
--rm-dist --skip-validate --skip-publish
.PHONY: release
release:
@docker run \
--rm \
--privileged \
-e CGO_ENABLED=1 \
-e GITHUB_TOKEN \
-e DOCKER_USERNAME \
-e DOCKER_PASSWORD \
-e SLACK_WEBHOOK \
-v /var/run/docker.sock:/var/run/docker.sock \
-v `pwd`:/go/src/$(PACKAGE_NAME) \
-w /go/src/$(PACKAGE_NAME) \
ghcr.io/troian/golang-cross:${GOLANG_CROSS_VERSION} \
--rm-dist --skip-validate
This diff is collapsed.
...@@ -2,31 +2,31 @@ ...@@ -2,31 +2,31 @@
## Supported Versions ## Supported Versions
Please see Releases. We recommend to use the most recent released version. Please see [Releases](https://github.com/ethereum/go-ethereum/releases). We recommend using the [most recently released version](https://github.com/ethereum/go-ethereum/releases/latest).
## Audit reports ## Audit reports
Audit reports are published in the `docs` folder: https://github.com/ethereum/go-ethereum/tree/master/docs/audits Audit reports are published in the `docs` folder: https://github.com/ethereum/go-ethereum/tree/master/docs/audits
| Scope | Date | Report Link | | Scope | Date | Report Link |
| ------- | ------- | ----------- | | ------- | ------- | ----------- |
| `geth` | 20170425 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2017-04-25_Geth-audit_Truesec.pdf) | | `geth` | 20170425 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2017-04-25_Geth-audit_Truesec.pdf) |
| `clef` | 20180914 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2018-09-14_Clef-audit_NCC.pdf) | | `clef` | 20180914 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2018-09-14_Clef-audit_NCC.pdf) |
| `Discv5` | 20191015 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2019-10-15_Discv5_audit_LeastAuthority.pdf) |
| `Discv5` | 20200124 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2020-01-24_DiscV5_audit_Cure53.pdf) |
## Reporting a Vulnerability ## Reporting a Vulnerability
**Please do not file a public ticket** mentioning the vulnerability. **Please do not file a public ticket** mentioning the vulnerability.
To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. Please read the [disclosure page](https://github.com/ethereum/go-ethereum/security/advisories?state=published) for more information about publically disclosed security vulnerabilities.
Use the built-in `geth version-check` feature to check whether the software is affected by any known vulnerability. This command will fetch the latest [`vulnerabilities.json`](https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json) file which contains known security vulnerabilities concerning `geth`, and cross-check the data against its own version number.
The following key may be used to communicate sensitive information to developers. The following key may be used to communicate sensitive information to developers.
Fingerprint: `AE96 ED96 9E47 9B00 84F3 E17F E88D 3334 FA5F 6A0A` Fingerprint: `AE96 ED96 9E47 9B00 84F3 E17F E88D 3334 FA5F 6A0A`
``` ```
-----BEGIN PGP PUBLIC KEY BLOCK----- -----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1 Version: GnuPG v1
......
...@@ -19,10 +19,12 @@ package abi ...@@ -19,10 +19,12 @@ package abi
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
) )
// The ABI holds information about a contract's context and available // The ABI holds information about a contract's context and available
...@@ -32,6 +34,13 @@ type ABI struct { ...@@ -32,6 +34,13 @@ type ABI struct {
Constructor Method Constructor Method
Methods map[string]Method Methods map[string]Method
Events map[string]Event Events map[string]Event
Errors map[string]Error
// Additional "special" functions introduced in solidity v0.6.0.
// It's separated from the original default fallback. Each contract
// can only define one fallback and receive function.
Fallback Method // Note it's also used to represent legacy fallback before v0.6.0
Receive Method
} }
// JSON returns a parsed ABI interface and error if it failed. // JSON returns a parsed ABI interface and error if it failed.
...@@ -42,7 +51,6 @@ func JSON(reader io.Reader) (ABI, error) { ...@@ -42,7 +51,6 @@ func JSON(reader io.Reader) (ABI, error) {
if err := dec.Decode(&abi); err != nil { if err := dec.Decode(&abi); err != nil {
return ABI{}, err return ABI{}, err
} }
return abi, nil return abi, nil
} }
...@@ -70,110 +78,131 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { ...@@ -70,110 +78,131 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
return nil, err return nil, err
} }
// Pack up the method ID too if not a constructor and return // Pack up the method ID too if not a constructor and return
return append(method.ID(), arguments...), nil return append(method.ID, arguments...), nil
} }
// Unpack output in v according to the abi specification func (abi ABI) getArguments(name string, data []byte) (Arguments, error) {
func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) {
if len(data) == 0 {
return fmt.Errorf("abi: unmarshalling empty output")
}
// since there can't be naming collisions with contracts and events, // since there can't be naming collisions with contracts and events,
// we need to decide whether we're calling a method or an event // we need to decide whether we're calling a method or an event
var args Arguments
if method, ok := abi.Methods[name]; ok { if method, ok := abi.Methods[name]; ok {
if len(data)%32 != 0 { if len(data)%32 != 0 {
return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) return nil, fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data)
} }
return method.Outputs.Unpack(v, data) args = method.Outputs
} }
if event, ok := abi.Events[name]; ok { if event, ok := abi.Events[name]; ok {
return event.Inputs.Unpack(v, data) args = event.Inputs
} }
return fmt.Errorf("abi: could not locate named method or event") if args == nil {
return nil, errors.New("abi: could not locate named method or event")
}
return args, nil
} }
// UnpackIntoMap unpacks a log into the provided map[string]interface{} // Unpack unpacks the output according to the abi specification.
func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) {
if len(data) == 0 { args, err := abi.getArguments(name, data)
return fmt.Errorf("abi: unmarshalling empty output") if err != nil {
return nil, err
} }
// since there can't be naming collisions with contracts and events, return args.Unpack(data)
// we need to decide whether we're calling a method or an event }
if method, ok := abi.Methods[name]; ok {
if len(data)%32 != 0 { // UnpackIntoInterface unpacks the output in v according to the abi specification.
return fmt.Errorf("abi: improperly formatted output") // It performs an additional copy. Please only use, if you want to unpack into a
} // structure that does not strictly conform to the abi structure (e.g. has additional arguments)
return method.Outputs.UnpackIntoMap(v, data) func (abi ABI) UnpackIntoInterface(v interface{}, name string, data []byte) error {
args, err := abi.getArguments(name, data)
if err != nil {
return err
} }
if event, ok := abi.Events[name]; ok { unpacked, err := args.Unpack(data)
return event.Inputs.UnpackIntoMap(v, data) if err != nil {
return err
} }
return fmt.Errorf("abi: could not locate named method or event") return args.Copy(v, unpacked)
} }
// UnmarshalJSON implements json.Unmarshaler interface // UnpackIntoMap unpacks a log into the provided map[string]interface{}.
func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) {
args, err := abi.getArguments(name, data)
if err != nil {
return err
}
return args.UnpackIntoMap(v, data)
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (abi *ABI) UnmarshalJSON(data []byte) error { func (abi *ABI) UnmarshalJSON(data []byte) error {
var fields []struct { var fields []struct {
Type string Type string
Name string Name string
Constant bool Inputs []Argument
Outputs []Argument
// Status indicator which can be: "pure", "view",
// "nonpayable" or "payable".
StateMutability string
// Deprecated Status indicators, but removed in v0.6.0.
Constant bool // True if function is either pure or view
Payable bool // True if function is payable
// Event relevant indicator represents the event is
// declared as anonymous.
Anonymous bool Anonymous bool
Inputs []Argument
Outputs []Argument
} }
if err := json.Unmarshal(data, &fields); err != nil { if err := json.Unmarshal(data, &fields); err != nil {
return err return err
} }
abi.Methods = make(map[string]Method) abi.Methods = make(map[string]Method)
abi.Events = make(map[string]Event) abi.Events = make(map[string]Event)
abi.Errors = make(map[string]Error)
for _, field := range fields { for _, field := range fields {
switch field.Type { switch field.Type {
case "constructor": case "constructor":
abi.Constructor = Method{ abi.Constructor = NewMethod("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil)
Inputs: field.Inputs, case "function":
name := overloadedName(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok })
abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, field.Inputs, field.Outputs)
case "fallback":
// New introduced function type in v0.6.0, check more detail
// here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function
if abi.HasFallback() {
return errors.New("only single fallback is allowed")
} }
// empty defaults to function according to the abi spec abi.Fallback = NewMethod("", "", Fallback, field.StateMutability, field.Constant, field.Payable, nil, nil)
case "function", "": case "receive":
name := field.Name // New introduced function type in v0.6.0, check more detail
_, ok := abi.Methods[name] // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function
for idx := 0; ok; idx++ { if abi.HasReceive() {
name = fmt.Sprintf("%s%d", field.Name, idx) return errors.New("only single receive is allowed")
_, ok = abi.Methods[name]
} }
abi.Methods[name] = Method{ if field.StateMutability != "payable" {
Name: name, return errors.New("the statemutability of receive can only be payable")
RawName: field.Name,
Const: field.Constant,
Inputs: field.Inputs,
Outputs: field.Outputs,
} }
abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil)
case "event": case "event":
name := field.Name name := overloadedName(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok })
_, ok := abi.Events[name] abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs)
for idx := 0; ok; idx++ { case "error":
name = fmt.Sprintf("%s%d", field.Name, idx) abi.Errors[field.Name] = NewError(field.Name, field.Inputs)
_, ok = abi.Events[name] default:
} return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name)
abi.Events[name] = Event{
Name: name,
RawName: field.Name,
Anonymous: field.Anonymous,
Inputs: field.Inputs,
}
} }
} }
return nil return nil
} }
// MethodById looks up a method by the 4-byte id // MethodById looks up a method by the 4-byte id,
// returns nil if none found // returns nil if none found.
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
if len(sigdata) < 4 { if len(sigdata) < 4 {
return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata)) return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata))
} }
for _, method := range abi.Methods { for _, method := range abi.Methods {
if bytes.Equal(method.ID(), sigdata[:4]) { if bytes.Equal(method.ID, sigdata[:4]) {
return &method, nil return &method, nil
} }
} }
...@@ -184,9 +213,58 @@ func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { ...@@ -184,9 +213,58 @@ func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
// ABI and returns nil if none found. // ABI and returns nil if none found.
func (abi *ABI) EventByID(topic common.Hash) (*Event, error) { func (abi *ABI) EventByID(topic common.Hash) (*Event, error) {
for _, event := range abi.Events { for _, event := range abi.Events {
if bytes.Equal(event.ID().Bytes(), topic.Bytes()) { if bytes.Equal(event.ID.Bytes(), topic.Bytes()) {
return &event, nil return &event, nil
} }
} }
return nil, fmt.Errorf("no event with id: %#x", topic.Hex()) return nil, fmt.Errorf("no event with id: %#x", topic.Hex())
} }
// HasFallback returns an indicator whether a fallback function is included.
func (abi *ABI) HasFallback() bool {
return abi.Fallback.Type == Fallback
}
// HasReceive returns an indicator whether a receive function is included.
func (abi *ABI) HasReceive() bool {
return abi.Receive.Type == Receive
}
// revertSelector is a special function selector for revert reason unpacking.
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
// the provided revert reason is abi-encoded as if it were a call to a function
// `Error(string)`. So it's a special tool for it.
func UnpackRevert(data []byte) (string, error) {
if len(data) < 4 {
return "", errors.New("invalid data for unpacking")
}
if !bytes.Equal(data[:4], revertSelector) {
return "", errors.New("invalid data for unpacking")
}
typ, _ := NewType("string", "", nil)
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
if err != nil {
return "", err
}
return unpacked[0].(string), nil
}
// overloadedName returns the next available name for a given thing.
// Needed since solidity allows for overloading.
//
// e.g. if the abi contains Methods send, send1
// overloadedName would return send2 for input send.
//
// overloadedName works for methods, events and errors.
func overloadedName(rawName string, isAvail func(string) bool) string {
name := rawName
ok := isAvail(name)
for idx := 0; ok; idx++ {
name = fmt.Sprintf("%s%d", rawName, idx)
ok = isAvail(name)
}
return name
}
This diff is collapsed.
...@@ -34,13 +34,14 @@ type Argument struct { ...@@ -34,13 +34,14 @@ type Argument struct {
type Arguments []Argument type Arguments []Argument
type ArgumentMarshaling struct { type ArgumentMarshaling struct {
Name string Name string
Type string Type string
Components []ArgumentMarshaling InternalType string
Indexed bool Components []ArgumentMarshaling
Indexed bool
} }
// UnmarshalJSON implements json.Unmarshaler interface // UnmarshalJSON implements json.Unmarshaler interface.
func (argument *Argument) UnmarshalJSON(data []byte) error { func (argument *Argument) UnmarshalJSON(data []byte) error {
var arg ArgumentMarshaling var arg ArgumentMarshaling
err := json.Unmarshal(data, &arg) err := json.Unmarshal(data, &arg)
...@@ -48,7 +49,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error { ...@@ -48,7 +49,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error {
return fmt.Errorf("argument json err: %v", err) return fmt.Errorf("argument json err: %v", err)
} }
argument.Type, err = NewType(arg.Type, arg.Components) argument.Type, err = NewType(arg.Type, arg.InternalType, arg.Components)
if err != nil { if err != nil {
return err return err
} }
...@@ -58,19 +59,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error { ...@@ -58,19 +59,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events // NonIndexed returns the arguments with indexed arguments filtered out.
// can ever have 'indexed' arguments, it should always be false on arguments for method input/output
func (arguments Arguments) LengthNonIndexed() int {
out := 0
for _, arg := range arguments {
if !arg.Indexed {
out++
}
}
return out
}
// NonIndexed returns the arguments with indexed arguments filtered out
func (arguments Arguments) NonIndexed() Arguments { func (arguments Arguments) NonIndexed() Arguments {
var ret []Argument var ret []Argument
for _, arg := range arguments { for _, arg := range arguments {
...@@ -81,203 +70,127 @@ func (arguments Arguments) NonIndexed() Arguments { ...@@ -81,203 +70,127 @@ func (arguments Arguments) NonIndexed() Arguments {
return ret return ret
} }
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[] // isTuple returns true for non-atomic constructs, like (uint,uint) or uint[].
func (arguments Arguments) isTuple() bool { func (arguments Arguments) isTuple() bool {
return len(arguments) > 1 return len(arguments) > 1
} }
// Unpack performs the operation hexdata -> Go format // Unpack performs the operation hexdata -> Go format.
func (arguments Arguments) Unpack(v interface{}, data []byte) error { func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) {
// make sure the passed value is arguments pointer if len(data) == 0 {
if reflect.Ptr != reflect.ValueOf(v).Kind() { if len(arguments) != 0 {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v) return nil, fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
}
marshalledValues, err := arguments.UnpackValues(data)
if err != nil {
return err
}
if arguments.isTuple() {
return arguments.unpackTuple(v, marshalledValues)
}
return arguments.unpackAtomic(v, marshalledValues[0])
}
// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value
func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error {
marshalledValues, err := arguments.UnpackValues(data)
if err != nil {
return err
}
return arguments.unpackIntoMap(v, marshalledValues)
}
// unpack sets the unmarshalled value to go format.
// Note the dst here must be settable.
func unpack(t *Type, dst interface{}, src interface{}) error {
var (
dstVal = reflect.ValueOf(dst).Elem()
srcVal = reflect.ValueOf(src)
)
tuple, typ := false, t
for {
if typ.T == SliceTy || typ.T == ArrayTy {
typ = typ.Elem
continue
}
tuple = typ.T == TupleTy
break
}
if !tuple {
return set(dstVal, srcVal)
}
// Dereferences interface or pointer wrapper
dstVal = indirectInterfaceOrPtr(dstVal)
switch t.T {
case TupleTy:
if dstVal.Kind() != reflect.Struct {
return fmt.Errorf("abi: invalid dst value for unpack, want struct, got %s", dstVal.Kind())
}
fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, dstVal)
if err != nil {
return err
}
for i, elem := range t.TupleElems {
fname := fieldmap[t.TupleRawNames[i]]
field := dstVal.FieldByName(fname)
if !field.IsValid() {
return fmt.Errorf("abi: field %s can't found in the given value", t.TupleRawNames[i])
}
if err := unpack(elem, field.Addr().Interface(), srcVal.Field(i).Interface()); err != nil {
return err
}
} }
return nil // Nothing to unmarshal, return default variables
case SliceTy: nonIndexedArgs := arguments.NonIndexed()
if dstVal.Kind() != reflect.Slice { defaultVars := make([]interface{}, len(nonIndexedArgs))
return fmt.Errorf("abi: invalid dst value for unpack, want slice, got %s", dstVal.Kind()) for index, arg := range nonIndexedArgs {
defaultVars[index] = reflect.New(arg.Type.GetType())
} }
slice := reflect.MakeSlice(dstVal.Type(), srcVal.Len(), srcVal.Len()) return defaultVars, nil
for i := 0; i < slice.Len(); i++ {
if err := unpack(t.Elem, slice.Index(i).Addr().Interface(), srcVal.Index(i).Interface()); err != nil {
return err
}
}
dstVal.Set(slice)
case ArrayTy:
if dstVal.Kind() != reflect.Array {
return fmt.Errorf("abi: invalid dst value for unpack, want array, got %s", dstVal.Kind())
}
array := reflect.New(dstVal.Type()).Elem()
for i := 0; i < array.Len(); i++ {
if err := unpack(t.Elem, array.Index(i).Addr().Interface(), srcVal.Index(i).Interface()); err != nil {
return err
}
}
dstVal.Set(array)
} }
return nil return arguments.UnpackValues(data)
} }
// unpackIntoMap unpacks marshalledValues into the provided map[string]interface{} // UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value.
func (arguments Arguments) unpackIntoMap(v map[string]interface{}, marshalledValues []interface{}) error { func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error {
// Make sure map is not nil // Make sure map is not nil
if v == nil { if v == nil {
return fmt.Errorf("abi: cannot unpack into a nil map") return fmt.Errorf("abi: cannot unpack into a nil map")
} }
if len(data) == 0 {
if len(arguments) != 0 {
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
}
return nil // Nothing to unmarshal, return
}
marshalledValues, err := arguments.UnpackValues(data)
if err != nil {
return err
}
for i, arg := range arguments.NonIndexed() { for i, arg := range arguments.NonIndexed() {
v[arg.Name] = marshalledValues[i] v[arg.Name] = marshalledValues[i]
} }
return nil return nil
} }
// unpackAtomic unpacks ( hexdata -> go ) a single value // Copy performs the operation go format -> provided struct.
func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error { func (arguments Arguments) Copy(v interface{}, values []interface{}) error {
if arguments.LengthNonIndexed() == 0 { // make sure the passed value is arguments pointer
return nil if reflect.Ptr != reflect.ValueOf(v).Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
} }
argument := arguments.NonIndexed()[0] if len(values) == 0 {
elem := reflect.ValueOf(v).Elem() if len(arguments) != 0 {
return fmt.Errorf("abi: attempting to copy no values while %d arguments are expected", len(arguments))
if elem.Kind() == reflect.Struct && argument.Type.T != TupleTy {
fieldmap, err := mapArgNamesToStructFields([]string{argument.Name}, elem)
if err != nil {
return err
} }
field := elem.FieldByName(fieldmap[argument.Name]) return nil // Nothing to copy, return
if !field.IsValid() { }
return fmt.Errorf("abi: field %s can't be found in the given value", argument.Name) if arguments.isTuple() {
} return arguments.copyTuple(v, values)
return unpack(&argument.Type, field.Addr().Interface(), marshalledValues)
} }
return unpack(&argument.Type, elem.Addr().Interface(), marshalledValues) return arguments.copyAtomic(v, values[0])
} }
// unpackTuple unpacks ( hexdata -> go ) a batch of values. // unpackAtomic unpacks ( hexdata -> go ) a single value
func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error { func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error {
var ( dst := reflect.ValueOf(v).Elem()
value = reflect.ValueOf(v).Elem() src := reflect.ValueOf(marshalledValues)
typ = value.Type()
kind = value.Kind() if dst.Kind() == reflect.Struct {
) return set(dst.Field(0), src)
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
return err
} }
return set(dst, src)
}
// If the interface is a struct, get of abi->struct_field mapping // copyTuple copies a batch of values from marshalledValues to v.
var abi2struct map[string]string func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface{}) error {
if kind == reflect.Struct { value := reflect.ValueOf(v).Elem()
var ( nonIndexedArgs := arguments.NonIndexed()
argNames []string
err error switch value.Kind() {
) case reflect.Struct:
for _, arg := range arguments.NonIndexed() { argNames := make([]string, len(nonIndexedArgs))
argNames = append(argNames, arg.Name) for i, arg := range nonIndexedArgs {
argNames[i] = arg.Name
} }
abi2struct, err = mapArgNamesToStructFields(argNames, value) var err error
abi2struct, err := mapArgNamesToStructFields(argNames, value)
if err != nil { if err != nil {
return err return err
} }
} for i, arg := range nonIndexedArgs {
for i, arg := range arguments.NonIndexed() {
switch kind {
case reflect.Struct:
field := value.FieldByName(abi2struct[arg.Name]) field := value.FieldByName(abi2struct[arg.Name])
if !field.IsValid() { if !field.IsValid() {
return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name) return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name)
} }
if err := unpack(&arg.Type, field.Addr().Interface(), marshalledValues[i]); err != nil { if err := set(field, reflect.ValueOf(marshalledValues[i])); err != nil {
return err return err
} }
case reflect.Slice, reflect.Array: }
if value.Len() < i { case reflect.Slice, reflect.Array:
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len()) if value.Len() < len(marshalledValues) {
} return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
v := value.Index(i) }
if err := requireAssignable(v, reflect.ValueOf(marshalledValues[i])); err != nil { for i := range nonIndexedArgs {
return err if err := set(value.Index(i), reflect.ValueOf(marshalledValues[i])); err != nil {
}
if err := unpack(&arg.Type, v.Addr().Interface(), marshalledValues[i]); err != nil {
return err return err
} }
default:
return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", typ)
} }
default:
return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", value.Type())
} }
return nil return nil
} }
// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, // UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification,
// without supplying a struct to unpack into. Instead, this method returns a list containing the // without supplying a struct to unpack into. Instead, this method returns a list containing the
// values. An atomic argument will be a list with one element. // values. An atomic argument will be a list with one element.
func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
retval := make([]interface{}, 0, arguments.LengthNonIndexed()) nonIndexedArgs := arguments.NonIndexed()
retval := make([]interface{}, 0, len(nonIndexedArgs))
virtualArgs := 0 virtualArgs := 0
for index, arg := range arguments.NonIndexed() { for index, arg := range nonIndexedArgs {
marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data)
if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) {
// If we have a static array, like [3]uint256, these are coded as // If we have a static array, like [3]uint256, these are coded as
...@@ -304,18 +217,18 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { ...@@ -304,18 +217,18 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
return retval, nil return retval, nil
} }
// PackValues performs the operation Go format -> Hexdata // PackValues performs the operation Go format -> Hexdata.
// It is the semantic opposite of UnpackValues // It is the semantic opposite of UnpackValues.
func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) { func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) {
return arguments.Pack(args...) return arguments.Pack(args...)
} }
// Pack performs the operation Go format -> Hexdata // Pack performs the operation Go format -> Hexdata.
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
// Make sure arguments match up and pack them // Make sure arguments match up and pack them
abiArgs := arguments abiArgs := arguments
if len(args) != len(abiArgs) { if len(args) != len(abiArgs) {
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs)) return nil, fmt.Errorf("argument count mismatch: got %d for %d", len(args), len(abiArgs))
} }
// variable input is the output appended at the end of packed // variable input is the output appended at the end of packed
// output. This is used for strings and bytes types input. // output. This is used for strings and bytes types input.
......
...@@ -17,10 +17,12 @@ ...@@ -17,10 +17,12 @@
package bind package bind
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"errors" "errors"
"io" "io"
"io/ioutil" "io/ioutil"
"math/big"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/external" "github.com/ethereum/go-ethereum/accounts/external"
...@@ -28,11 +30,21 @@ import ( ...@@ -28,11 +30,21 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
) )
// ErrNoChainID is returned whenever the user failed to specify a chain id.
var ErrNoChainID = errors.New("no chain id specified")
// ErrNotAuthorized is returned when an account is not properly unlocked.
var ErrNotAuthorized = errors.New("not authorized to sign this account")
// NewTransactor is a utility method to easily create a transaction signer from // NewTransactor is a utility method to easily create a transaction signer from
// an encrypted json key stream and the associated passphrase. // an encrypted json key stream and the associated passphrase.
//
// Deprecated: Use NewTransactorWithChainID instead.
func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
log.Warn("WARNING: NewTransactor has been deprecated in favour of NewTransactorWithChainID")
json, err := ioutil.ReadAll(keyin) json, err := ioutil.ReadAll(keyin)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -45,13 +57,17 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { ...@@ -45,13 +57,17 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
} }
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from // NewKeyStoreTransactor is a utility method to easily create a transaction signer from
// an decrypted key from a keystore // an decrypted key from a keystore.
//
// Deprecated: Use NewKeyStoreTransactorWithChainID instead.
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
log.Warn("WARNING: NewKeyStoreTransactor has been deprecated in favour of NewTransactorWithChainID")
signer := types.HomesteadSigner{}
return &TransactOpts{ return &TransactOpts{
From: account.Address, From: account.Address,
Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address != account.Address { if address != account.Address {
return nil, errors.New("not authorized to sign this account") return nil, ErrNotAuthorized
} }
signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes())
if err != nil { if err != nil {
...@@ -59,18 +75,23 @@ func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account ...@@ -59,18 +75,23 @@ func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account
} }
return tx.WithSignature(signer, signature) return tx.WithSignature(signer, signature)
}, },
Context: context.Background(),
}, nil }, nil
} }
// NewKeyedTransactor is a utility method to easily create a transaction signer // NewKeyedTransactor is a utility method to easily create a transaction signer
// from a single private key. // from a single private key.
//
// Deprecated: Use NewKeyedTransactorWithChainID instead.
func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
log.Warn("WARNING: NewKeyedTransactor has been deprecated in favour of NewKeyedTransactorWithChainID")
keyAddr := crypto.PubkeyToAddress(key.PublicKey) keyAddr := crypto.PubkeyToAddress(key.PublicKey)
signer := types.HomesteadSigner{}
return &TransactOpts{ return &TransactOpts{
From: keyAddr, From: keyAddr,
Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address != keyAddr { if address != keyAddr {
return nil, errors.New("not authorized to sign this account") return nil, ErrNotAuthorized
} }
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key)
if err != nil { if err != nil {
...@@ -78,7 +99,69 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { ...@@ -78,7 +99,69 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
} }
return tx.WithSignature(signer, signature) return tx.WithSignature(signer, signature)
}, },
Context: context.Background(),
}
}
// NewTransactorWithChainID is a utility method to easily create a transaction signer from
// an encrypted json key stream and the associated passphrase.
func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.Int) (*TransactOpts, error) {
json, err := ioutil.ReadAll(keyin)
if err != nil {
return nil, err
}
key, err := keystore.DecryptKey(json, passphrase)
if err != nil {
return nil, err
}
return NewKeyedTransactorWithChainID(key.PrivateKey, chainID)
}
// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from
// an decrypted key from a keystore.
func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) {
if chainID == nil {
return nil, ErrNoChainID
}
signer := types.LatestSignerForChainID(chainID)
return &TransactOpts{
From: account.Address,
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address != account.Address {
return nil, ErrNotAuthorized
}
signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes())
if err != nil {
return nil, err
}
return tx.WithSignature(signer, signature)
},
Context: context.Background(),
}, nil
}
// NewKeyedTransactorWithChainID is a utility method to easily create a transaction signer
// from a single private key.
func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) {
keyAddr := crypto.PubkeyToAddress(key.PublicKey)
if chainID == nil {
return nil, ErrNoChainID
} }
signer := types.LatestSignerForChainID(chainID)
return &TransactOpts{
From: keyAddr,
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address != keyAddr {
return nil, ErrNotAuthorized
}
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key)
if err != nil {
return nil, err
}
return tx.WithSignature(signer, signature)
},
Context: context.Background(),
}, nil
} }
// NewClefTransactor is a utility method to easily create a transaction signer // NewClefTransactor is a utility method to easily create a transaction signer
...@@ -86,11 +169,12 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { ...@@ -86,11 +169,12 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts { func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts {
return &TransactOpts{ return &TransactOpts{
From: account.Address, From: account.Address,
Signer: func(signer types.Signer, address common.Address, transaction *types.Transaction) (*types.Transaction, error) { Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) {
if address != account.Address { if address != account.Address {
return nil, errors.New("not authorized to sign this account") return nil, ErrNotAuthorized
} }
return clef.SignTx(account, transaction, nil) // Clef enforces its own chain id return clef.SignTx(account, transaction, nil) // Clef enforces its own chain id
}, },
Context: context.Background(),
} }
} }
...@@ -32,22 +32,23 @@ var ( ...@@ -32,22 +32,23 @@ var (
// have any code associated with it (i.e. suicided). // have any code associated with it (i.e. suicided).
ErrNoCode = errors.New("no contract code at given address") ErrNoCode = errors.New("no contract code at given address")
// This error is raised when attempting to perform a pending state action // ErrNoPendingState is raised when attempting to perform a pending state action
// on a backend that doesn't implement PendingContractCaller. // on a backend that doesn't implement PendingContractCaller.
ErrNoPendingState = errors.New("backend does not support pending state") ErrNoPendingState = errors.New("backend does not support pending state")
// This error is returned by WaitDeployed if contract creation leaves an // ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
// empty contract behind. // an empty contract behind.
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment") ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
) )
// ContractCaller defines the methods needed to allow operating with contract on a read // ContractCaller defines the methods needed to allow operating with a contract on a read
// only basis. // only basis.
type ContractCaller interface { type ContractCaller interface {
// CodeAt returns the code of the given account. This is needed to differentiate // CodeAt returns the code of the given account. This is needed to differentiate
// between contract internal errors and the local chain being out of sync. // between contract internal errors and the local chain being out of sync.
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
// ContractCall executes an Ethereum contract call with the specified data as the
// CallContract executes an Ethereum contract call with the specified data as the
// input. // input.
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
} }
...@@ -58,28 +59,41 @@ type ContractCaller interface { ...@@ -58,28 +59,41 @@ type ContractCaller interface {
type PendingContractCaller interface { type PendingContractCaller interface {
// PendingCodeAt returns the code of the given account in the pending state. // PendingCodeAt returns the code of the given account in the pending state.
PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error)
// PendingCallContract executes an Ethereum contract call against the pending state. // PendingCallContract executes an Ethereum contract call against the pending state.
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
} }
// ContractTransactor defines the methods needed to allow operating with contract // ContractTransactor defines the methods needed to allow operating with a contract
// on a write only basis. Beside the transacting method, the remainder are helpers // on a write only basis. Besides the transacting method, the remainder are helpers
// used when the user does not provide some needed values, but rather leaves it up // used when the user does not provide some needed values, but rather leaves it up
// to the transactor to decide. // to the transactor to decide.
type ContractTransactor interface { type ContractTransactor interface {
// HeaderByNumber returns a block header from the current canonical chain. If
// number is nil, the latest known header is returned.
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
// PendingCodeAt returns the code of the given account in the pending state. // PendingCodeAt returns the code of the given account in the pending state.
PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error)
// PendingNonceAt retrieves the current pending nonce associated with an account. // PendingNonceAt retrieves the current pending nonce associated with an account.
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely // SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction. // execution of a transaction.
SuggestGasPrice(ctx context.Context) (*big.Int, error) SuggestGasPrice(ctx context.Context) (*big.Int, error)
// SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow
// a timely execution of a transaction.
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
// EstimateGas tries to estimate the gas needed to execute a specific // EstimateGas tries to estimate the gas needed to execute a specific
// transaction based on the current pending state of the backend blockchain. // transaction based on the current pending state of the backend blockchain.
// There is no guarantee that this is the true gas limit requirement as other // There is no guarantee that this is the true gas limit requirement as other
// transactions may be added or removed by miners, but it should provide a basis // transactions may be added or removed by miners, but it should provide a basis
// for setting a reasonable default. // for setting a reasonable default.
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
// SendTransaction injects the transaction into the pending pool for execution. // SendTransaction injects the transaction into the pending pool for execution.
SendTransaction(ctx context.Context, tx *types.Transaction) error SendTransaction(ctx context.Context, tx *types.Transaction) error
} }
......
package backends
import (
"context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
)
func (fb *filterBackend) GetBorBlockReceipt(ctx context.Context, hash common.Hash) (*types.Receipt, error) {
number := rawdb.ReadHeaderNumber(fb.db, hash)
if number == nil {
return nil, nil
}
receipt := rawdb.ReadRawBorReceipt(fb.db, hash, *number)
if receipt == nil {
return nil, nil
}
return receipt, nil
}
func (fb *filterBackend) GetBorBlockLogs(ctx context.Context, hash common.Hash) ([]*types.Log, error) {
receipt, err := fb.GetBorBlockReceipt(ctx, hash)
if err != nil || receipt == nil {
return nil, err
}
return receipt.Logs, nil
}
// SubscribeStateSyncEvent subscribes to state sync events
func (fb *filterBackend) SubscribeStateSyncEvent(ch chan<- core.StateSyncEvent) event.Subscription {
return fb.bc.SubscribeStateSyncEvent(ch)
}
This diff is collapsed.
...@@ -21,6 +21,8 @@ import ( ...@@ -21,6 +21,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"strings"
"sync"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
...@@ -32,7 +34,7 @@ import ( ...@@ -32,7 +34,7 @@ import (
// SignerFn is a signer function callback when a contract requires a method to // SignerFn is a signer function callback when a contract requires a method to
// sign the transaction before submission. // sign the transaction before submission.
type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Transaction, error) type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error)
// CallOpts is the collection of options to fine tune a contract call request. // CallOpts is the collection of options to fine tune a contract call request.
type CallOpts struct { type CallOpts struct {
...@@ -49,11 +51,15 @@ type TransactOpts struct { ...@@ -49,11 +51,15 @@ type TransactOpts struct {
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state) Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
Signer SignerFn // Method to use for signing the transaction (mandatory) Signer SignerFn // Method to use for signing the transaction (mandatory)
Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds) Value *big.Int // Funds to transfer along the transaction (nil = 0 = no funds)
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle) GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate) GasFeeCap *big.Int // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle)
GasTipCap *big.Int // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle)
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate)
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
NoSend bool // Do all transact steps but do not send the transaction
} }
// FilterOpts is the collection of options to fine tune filtering for events // FilterOpts is the collection of options to fine tune filtering for events
...@@ -72,6 +78,29 @@ type WatchOpts struct { ...@@ -72,6 +78,29 @@ type WatchOpts struct {
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
} }
// MetaData collects all metadata for a bound contract.
type MetaData struct {
mu sync.Mutex
Sigs map[string]string
Bin string
ABI string
ab *abi.ABI
}
func (m *MetaData) GetAbi() (*abi.ABI, error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.ab != nil {
return m.ab, nil
}
if parsed, err := abi.JSON(strings.NewReader(m.ABI)); err != nil {
return nil, err
} else {
m.ab = &parsed
}
return m.ab, nil
}
// BoundContract is the base wrapper object that reflects a contract on the // BoundContract is the base wrapper object that reflects a contract on the
// Ethereum network. It contains a collection of methods that are used by the // Ethereum network. It contains a collection of methods that are used by the
// higher level contract bindings to operate. // higher level contract bindings to operate.
...@@ -117,11 +146,14 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co ...@@ -117,11 +146,14 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co
// sets the output to result. The result type might be a single field for simple // 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, a slice of interfaces for anonymous returns and a struct for named
// returns. // returns.
func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error { func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error {
// Don't crash on a lazy user // Don't crash on a lazy user
if opts == nil { if opts == nil {
opts = new(CallOpts) opts = new(CallOpts)
} }
if results == nil {
results = new([]interface{})
}
// Pack the input, call and unpack the results // Pack the input, call and unpack the results
input, err := c.abi.Pack(method, params...) input, err := c.abi.Pack(method, params...)
if err != nil { if err != nil {
...@@ -149,7 +181,10 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, ...@@ -149,7 +181,10 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
} }
} else { } else {
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
if err == nil && len(output) == 0 { if err != nil {
return err
}
if len(output) == 0 {
// Make sure we have a contract to operate on, and bail out otherwise. // 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 { if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
return err return err
...@@ -158,10 +193,14 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, ...@@ -158,10 +193,14 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
} }
} }
} }
if err != nil {
if len(*results) == 0 {
res, err := c.abi.Unpack(method, output)
*results = res
return err return err
} }
return c.abi.Unpack(result, method, output) res := *results
return c.abi.UnpackIntoInterface(res[0], method, output)
} }
// Transact invokes the (paid) contract method with params as input values. // Transact invokes the (paid) contract method with params as input values.
...@@ -171,73 +210,189 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in ...@@ -171,73 +210,189 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
if err != nil { if err != nil {
return nil, err return nil, err
} }
// todo(rjl493456442) check the method is payable or not,
// reject invalid transaction at the first place
return c.transact(opts, &c.address, input) return c.transact(opts, &c.address, input)
} }
// RawTransact initiates a transaction with the given raw calldata as the input.
// It's usually used to initiate transactions for invoking **Fallback** function.
func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) {
// todo(rjl493456442) check the method is payable or not,
// reject invalid transaction at the first place
return c.transact(opts, &c.address, calldata)
}
// Transfer initiates a plain transaction to move funds to the contract, calling // Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available. // its default method if one is available.
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
// todo(rjl493456442) check the payable fallback or receive is defined
// or not, reject invalid transaction at the first place
return c.transact(opts, &c.address, nil) return c.transact(opts, &c.address, nil)
} }
// transact executes an actual transaction invocation, first deriving any missing func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Address, input []byte, head *types.Header) (*types.Transaction, error) {
// authorization fields, and then scheduling the transaction for execution. // Normalize value
func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
var err error
// Ensure a valid value field and resolve the account nonce
value := opts.Value value := opts.Value
if value == nil { if value == nil {
value = new(big.Int) value = new(big.Int)
} }
var nonce uint64 // Estimate TipCap
if opts.Nonce == nil { gasTipCap := opts.GasTipCap
nonce, err = c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From) if gasTipCap == nil {
tip, err := c.transactor.SuggestGasTipCap(ensureContext(opts.Context))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to retrieve account nonce: %v", err) return nil, err
} }
} else { gasTipCap = tip
nonce = opts.Nonce.Uint64() }
// Estimate FeeCap
gasFeeCap := opts.GasFeeCap
if gasFeeCap == nil {
gasFeeCap = new(big.Int).Add(
gasTipCap,
new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
)
}
if gasFeeCap.Cmp(gasTipCap) < 0 {
return nil, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", gasFeeCap, gasTipCap)
}
// Estimate GasLimit
gasLimit := opts.GasLimit
if opts.GasLimit == 0 {
var err error
gasLimit, err = c.estimateGasLimit(opts, contract, input, nil, gasTipCap, gasFeeCap, value)
if err != nil {
return nil, err
}
}
// create the transaction
nonce, err := c.getNonce(opts)
if err != nil {
return nil, err
}
baseTx := &types.DynamicFeeTx{
To: contract,
Nonce: nonce,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Gas: gasLimit,
Value: value,
Data: input,
}
return types.NewTx(baseTx), nil
}
func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
if opts.GasFeeCap != nil || opts.GasTipCap != nil {
return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet")
} }
// Figure out the gas allowance and gas price values // Normalize value
value := opts.Value
if value == nil {
value = new(big.Int)
}
// Estimate GasPrice
gasPrice := opts.GasPrice gasPrice := opts.GasPrice
if gasPrice == nil { if gasPrice == nil {
gasPrice, err = c.transactor.SuggestGasPrice(ensureContext(opts.Context)) price, err := c.transactor.SuggestGasPrice(ensureContext(opts.Context))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to suggest gas price: %v", err) return nil, err
} }
gasPrice = price
} }
// Estimate GasLimit
gasLimit := opts.GasLimit gasLimit := opts.GasLimit
if gasLimit == 0 { if opts.GasLimit == 0 {
// Gas estimation cannot succeed without code for method invocations var err error
if contract != nil { gasLimit, err = c.estimateGasLimit(opts, contract, input, gasPrice, nil, nil, value)
if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil {
return nil, err
} else if len(code) == 0 {
return nil, ErrNoCode
}
}
// If the contract surely has code (or code is not needed), estimate the transaction
msg := ethereum.CallMsg{From: opts.From, To: contract, Value: value, Data: input}
gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to estimate gas needed: %v", err) return nil, err
}
}
// create the transaction
nonce, err := c.getNonce(opts)
if err != nil {
return nil, err
}
baseTx := &types.LegacyTx{
To: contract,
Nonce: nonce,
GasPrice: gasPrice,
Gas: gasLimit,
Value: value,
Data: input,
}
return types.NewTx(baseTx), nil
}
func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int) (uint64, error) {
if contract != nil {
// Gas estimation cannot succeed without code for method invocations.
if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil {
return 0, err
} else if len(code) == 0 {
return 0, ErrNoCode
} }
} }
// Create the transaction, sign it and schedule it for execution msg := ethereum.CallMsg{
var rawTx *types.Transaction From: opts.From,
if contract == nil { To: contract,
rawTx = types.NewContractCreation(nonce, value, gasLimit, gasPrice, input) GasPrice: gasPrice,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Value: value,
Data: input,
}
return c.transactor.EstimateGas(ensureContext(opts.Context), msg)
}
func (c *BoundContract) getNonce(opts *TransactOpts) (uint64, error) {
if opts.Nonce == nil {
return c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From)
} else {
return opts.Nonce.Uint64(), nil
}
}
// transact executes an actual transaction invocation, first deriving any missing
// authorization fields, and then scheduling the transaction for execution.
func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
if opts.GasPrice != nil && (opts.GasFeeCap != nil || opts.GasTipCap != nil) {
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
}
// Create the transaction
var (
rawTx *types.Transaction
err error
)
if opts.GasPrice != nil {
rawTx, err = c.createLegacyTx(opts, contract, input)
} else { } else {
rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input) // Only query for basefee if gasPrice not specified
if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil); err != nil {
return nil, errHead
} else if head.BaseFee != nil {
rawTx, err = c.createDynamicTx(opts, contract, input, head)
} else {
// Chain is not London ready -> use legacy transaction
rawTx, err = c.createLegacyTx(opts, contract, input)
}
} }
if err != nil {
return nil, err
}
// Sign the transaction and schedule it for execution
if opts.Signer == nil { if opts.Signer == nil {
return nil, errors.New("no signer to authorize the transaction with") return nil, errors.New("no signer to authorize the transaction with")
} }
signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx) signedTx, err := opts.Signer(opts.From, rawTx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if opts.NoSend {
return signedTx, nil
}
if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil {
return nil, err return nil, err
} }
...@@ -252,9 +407,9 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int ...@@ -252,9 +407,9 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
opts = new(FilterOpts) opts = new(FilterOpts)
} }
// Append the event selector to the query parameters and construct the topic set // 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{}{{c.abi.Events[name].ID}}, query...)
topics, err := makeTopics(query...) topics, err := abi.MakeTopics(query...)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
...@@ -301,9 +456,9 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter ...@@ -301,9 +456,9 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter
opts = new(WatchOpts) opts = new(WatchOpts)
} }
// Append the event selector to the query parameters and construct the topic set // 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{}{{c.abi.Events[name].ID}}, query...)
topics, err := makeTopics(query...) topics, err := abi.MakeTopics(query...)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
...@@ -326,8 +481,11 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter ...@@ -326,8 +481,11 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter
// UnpackLog unpacks a retrieved log into the provided output structure. // UnpackLog unpacks a retrieved log into the provided output structure.
func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error { func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error {
if log.Topics[0] != c.abi.Events[event].ID {
return fmt.Errorf("event signature mismatch")
}
if len(log.Data) > 0 { if len(log.Data) > 0 {
if err := c.abi.Unpack(out, event, log.Data); err != nil { if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
return err return err
} }
} }
...@@ -337,11 +495,14 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) ...@@ -337,11 +495,14 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log)
indexed = append(indexed, arg) indexed = append(indexed, arg)
} }
} }
return parseTopics(out, indexed, log.Topics[1:]) return abi.ParseTopics(out, indexed, log.Topics[1:])
} }
// UnpackLogIntoMap unpacks a retrieved log into the provided map. // UnpackLogIntoMap unpacks a retrieved log into the provided map.
func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error { func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error {
if log.Topics[0] != c.abi.Events[event].ID {
return fmt.Errorf("event signature mismatch")
}
if len(log.Data) > 0 { if len(log.Data) > 0 {
if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil {
return err return err
...@@ -353,14 +514,14 @@ func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event strin ...@@ -353,14 +514,14 @@ func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event strin
indexed = append(indexed, arg) indexed = append(indexed, arg)
} }
} }
return parseTopicsIntoMap(out, indexed, log.Topics[1:]) return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:])
} }
// ensureContext is a helper method to ensure a context is not nil, even if the // ensureContext is a helper method to ensure a context is not nil, even if the
// user specified it as such. // user specified it as such.
func ensureContext(ctx context.Context) context.Context { func ensureContext(ctx context.Context) context.Context {
if ctx == nil { if ctx == nil {
return context.TODO() return context.Background()
} }
return ctx return ctx
} }
...@@ -17,9 +17,9 @@ ...@@ -17,9 +17,9 @@
package bind_test package bind_test
import ( import (
"bytes"
"context" "context"
"math/big" "math/big"
"reflect"
"strings" "strings"
"testing" "testing"
...@@ -31,11 +31,54 @@ import ( ...@@ -31,11 +31,54 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/assert"
) )
func mockSign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { return tx, nil }
type mockTransactor struct {
baseFee *big.Int
gasTipCap *big.Int
gasPrice *big.Int
suggestGasTipCapCalled bool
suggestGasPriceCalled bool
}
func (mt *mockTransactor) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
return &types.Header{BaseFee: mt.baseFee}, nil
}
func (mt *mockTransactor) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
return []byte{1}, nil
}
func (mt *mockTransactor) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
return 0, nil
}
func (mt *mockTransactor) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
mt.suggestGasPriceCalled = true
return mt.gasPrice, nil
}
func (mt *mockTransactor) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
mt.suggestGasTipCapCalled = true
return mt.gasTipCap, nil
}
func (mt *mockTransactor) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
return 0, nil
}
func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transaction) error {
return nil
}
type mockCaller struct { type mockCaller struct {
codeAtBlockNumber *big.Int codeAtBlockNumber *big.Int
callContractBlockNumber *big.Int callContractBlockNumber *big.Int
pendingCodeAtCalled bool
pendingCallContractCalled bool
} }
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
...@@ -47,6 +90,16 @@ func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, b ...@@ -47,6 +90,16 @@ func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, b
mc.callContractBlockNumber = blockNumber mc.callContractBlockNumber = blockNumber
return nil, nil return nil, nil
} }
func (mc *mockCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
mc.pendingCodeAtCalled = true
return nil, nil
}
func (mc *mockCaller) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
mc.pendingCallContractCalled = true
return nil, nil
}
func TestPassingBlockNumber(t *testing.T) { func TestPassingBlockNumber(t *testing.T) {
mc := &mockCaller{} mc := &mockCaller{}
...@@ -59,11 +112,10 @@ func TestPassingBlockNumber(t *testing.T) { ...@@ -59,11 +112,10 @@ func TestPassingBlockNumber(t *testing.T) {
}, },
}, },
}, mc, nil, nil) }, mc, nil, nil)
var ret string
blockNumber := big.NewInt(42) blockNumber := big.NewInt(42)
bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, &ret, "something") bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, nil, "something")
if mc.callContractBlockNumber != blockNumber { if mc.callContractBlockNumber != blockNumber {
t.Fatalf("CallContract() was not passed the block number") t.Fatalf("CallContract() was not passed the block number")
...@@ -73,7 +125,7 @@ func TestPassingBlockNumber(t *testing.T) { ...@@ -73,7 +125,7 @@ func TestPassingBlockNumber(t *testing.T) {
t.Fatalf("CodeAt() was not passed the block number") t.Fatalf("CodeAt() was not passed the block number")
} }
bc.Call(&bind.CallOpts{}, &ret, "something") bc.Call(&bind.CallOpts{}, nil, "something")
if mc.callContractBlockNumber != nil { if mc.callContractBlockNumber != nil {
t.Fatalf("CallContract() was passed a block number when it should not have been") t.Fatalf("CallContract() was passed a block number when it should not have been")
...@@ -82,57 +134,39 @@ func TestPassingBlockNumber(t *testing.T) { ...@@ -82,57 +134,39 @@ func TestPassingBlockNumber(t *testing.T) {
if mc.codeAtBlockNumber != nil { if mc.codeAtBlockNumber != nil {
t.Fatalf("CodeAt() was passed a block number when it should not have been") t.Fatalf("CodeAt() was passed a block number when it should not have been")
} }
bc.Call(&bind.CallOpts{BlockNumber: blockNumber, Pending: true}, nil, "something")
if !mc.pendingCallContractCalled {
t.Fatalf("CallContract() was not passed the block number")
}
if !mc.pendingCodeAtCalled {
t.Fatalf("CodeAt() was not passed the block number")
}
} }
const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158" const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) { func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
hash := crypto.Keccak256Hash([]byte("testName")) hash := crypto.Keccak256Hash([]byte("testName"))
mockLog := types.Log{ topics := []common.Hash{
Address: common.HexToAddress("0x0"), crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")),
Topics: []common.Hash{ hash,
common.HexToHash("0x0"),
hash,
},
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: common.HexToHash("0x0"),
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
mockLog := newMockLog(topics, common.HexToHash("0x0"))
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
receivedMap := make(map[string]interface{})
expectedReceivedMap := map[string]interface{}{ expectedReceivedMap := map[string]interface{}{
"name": hash, "name": hash,
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
"amount": big.NewInt(1), "amount": big.NewInt(1),
"memo": []byte{88}, "memo": []byte{88},
} }
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
t.Error(err)
}
if len(receivedMap) != 4 {
t.Fatal("unpacked map expected to have length 4")
}
if receivedMap["name"] != expectedReceivedMap["name"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["sender"] != expectedReceivedMap["sender"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
t.Error("unpacked map does not match expected map")
}
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
t.Error("unpacked map does not match expected map")
}
} }
func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
...@@ -141,51 +175,23 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { ...@@ -141,51 +175,23 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
hash := crypto.Keccak256Hash(sliceBytes) hash := crypto.Keccak256Hash(sliceBytes)
mockLog := types.Log{ topics := []common.Hash{
Address: common.HexToAddress("0x0"), crypto.Keccak256Hash([]byte("received(string[],address,uint256,bytes)")),
Topics: []common.Hash{ hash,
common.HexToHash("0x0"),
hash,
},
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: common.HexToHash("0x0"),
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
mockLog := newMockLog(topics, common.HexToHash("0x0"))
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
receivedMap := make(map[string]interface{})
expectedReceivedMap := map[string]interface{}{ expectedReceivedMap := map[string]interface{}{
"names": hash, "names": hash,
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
"amount": big.NewInt(1), "amount": big.NewInt(1),
"memo": []byte{88}, "memo": []byte{88},
} }
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
t.Error(err)
}
if len(receivedMap) != 4 {
t.Fatal("unpacked map expected to have length 4")
}
if receivedMap["names"] != expectedReceivedMap["names"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["sender"] != expectedReceivedMap["sender"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
t.Error("unpacked map does not match expected map")
}
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
t.Error("unpacked map does not match expected map")
}
} }
func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
...@@ -194,51 +200,23 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { ...@@ -194,51 +200,23 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
hash := crypto.Keccak256Hash(arrBytes) hash := crypto.Keccak256Hash(arrBytes)
mockLog := types.Log{ topics := []common.Hash{
Address: common.HexToAddress("0x0"), crypto.Keccak256Hash([]byte("received(address[2],address,uint256,bytes)")),
Topics: []common.Hash{ hash,
common.HexToHash("0x0"),
hash,
},
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: common.HexToHash("0x0"),
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
mockLog := newMockLog(topics, common.HexToHash("0x0"))
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
receivedMap := make(map[string]interface{})
expectedReceivedMap := map[string]interface{}{ expectedReceivedMap := map[string]interface{}{
"addresses": hash, "addresses": hash,
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
"amount": big.NewInt(1), "amount": big.NewInt(1),
"memo": []byte{88}, "memo": []byte{88},
} }
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
t.Error(err)
}
if len(receivedMap) != 4 {
t.Fatal("unpacked map expected to have length 4")
}
if receivedMap["addresses"] != expectedReceivedMap["addresses"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["sender"] != expectedReceivedMap["sender"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
t.Error("unpacked map does not match expected map")
}
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
t.Error("unpacked map does not match expected map")
}
} }
func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
...@@ -249,99 +227,117 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { ...@@ -249,99 +227,117 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
functionTyBytes := append(addrBytes, functionSelector...) functionTyBytes := append(addrBytes, functionSelector...)
var functionTy [24]byte var functionTy [24]byte
copy(functionTy[:], functionTyBytes[0:24]) copy(functionTy[:], functionTyBytes[0:24])
mockLog := types.Log{ topics := []common.Hash{
Address: common.HexToAddress("0x0"), crypto.Keccak256Hash([]byte("received(function,address,uint256,bytes)")),
Topics: []common.Hash{ common.BytesToHash(functionTyBytes),
common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"),
common.BytesToHash(functionTyBytes),
},
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"),
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"))
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
receivedMap := make(map[string]interface{})
expectedReceivedMap := map[string]interface{}{ expectedReceivedMap := map[string]interface{}{
"function": functionTy, "function": functionTy,
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
"amount": big.NewInt(1), "amount": big.NewInt(1),
"memo": []byte{88}, "memo": []byte{88},
} }
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
t.Error(err)
}
if len(receivedMap) != 4 {
t.Fatal("unpacked map expected to have length 4")
}
if receivedMap["function"] != expectedReceivedMap["function"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["sender"] != expectedReceivedMap["sender"] {
t.Error("unpacked map does not match expected map")
}
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
t.Error("unpacked map does not match expected map")
}
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
t.Error("unpacked map does not match expected map")
}
} }
func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
byts := []byte{1, 2, 3, 4, 5} bytes := []byte{1, 2, 3, 4, 5}
hash := crypto.Keccak256Hash(byts) hash := crypto.Keccak256Hash(bytes)
mockLog := types.Log{ topics := []common.Hash{
Address: common.HexToAddress("0x0"), crypto.Keccak256Hash([]byte("received(bytes,address,uint256,bytes)")),
Topics: []common.Hash{ hash,
common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"),
hash,
},
Data: hexutil.MustDecode(hexData),
BlockNumber: uint64(26),
TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"),
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"))
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
receivedMap := make(map[string]interface{})
expectedReceivedMap := map[string]interface{}{ expectedReceivedMap := map[string]interface{}{
"content": hash, "content": hash,
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
"amount": big.NewInt(1), "amount": big.NewInt(1),
"memo": []byte{88}, "memo": []byte{88},
} }
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
}
func TestTransactGasFee(t *testing.T) {
assert := assert.New(t)
// GasTipCap and GasFeeCap
// When opts.GasTipCap and opts.GasFeeCap are nil
mt := &mockTransactor{baseFee: big.NewInt(100), gasTipCap: big.NewInt(5)}
bc := bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil)
opts := &bind.TransactOpts{Signer: mockSign}
tx, err := bc.Transact(opts, "")
assert.Nil(err)
assert.Equal(big.NewInt(5), tx.GasTipCap())
assert.Equal(big.NewInt(205), tx.GasFeeCap())
assert.Nil(opts.GasTipCap)
assert.Nil(opts.GasFeeCap)
assert.True(mt.suggestGasTipCapCalled)
// Second call to Transact should use latest suggested GasTipCap
mt.gasTipCap = big.NewInt(6)
mt.suggestGasTipCapCalled = false
tx, err = bc.Transact(opts, "")
assert.Nil(err)
assert.Equal(big.NewInt(6), tx.GasTipCap())
assert.Equal(big.NewInt(206), tx.GasFeeCap())
assert.True(mt.suggestGasTipCapCalled)
// GasPrice
// When opts.GasPrice is nil
mt = &mockTransactor{gasPrice: big.NewInt(5)}
bc = bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil)
opts = &bind.TransactOpts{Signer: mockSign}
tx, err = bc.Transact(opts, "")
assert.Nil(err)
assert.Equal(big.NewInt(5), tx.GasPrice())
assert.Nil(opts.GasPrice)
assert.True(mt.suggestGasPriceCalled)
// Second call to Transact should use latest suggested GasPrice
mt.gasPrice = big.NewInt(6)
mt.suggestGasPriceCalled = false
tx, err = bc.Transact(opts, "")
assert.Nil(err)
assert.Equal(big.NewInt(6), tx.GasPrice())
assert.True(mt.suggestGasPriceCalled)
}
func unpackAndCheck(t *testing.T, bc *bind.BoundContract, expected map[string]interface{}, mockLog types.Log) {
received := make(map[string]interface{})
if err := bc.UnpackLogIntoMap(received, "received", mockLog); err != nil {
t.Error(err) t.Error(err)
} }
if len(receivedMap) != 4 { if len(received) != len(expected) {
t.Fatal("unpacked map expected to have length 4") t.Fatalf("unpacked map length %v not equal expected length of %v", len(received), len(expected))
} }
if receivedMap["content"] != expectedReceivedMap["content"] { for name, elem := range expected {
t.Error("unpacked map does not match expected map") if !reflect.DeepEqual(elem, received[name]) {
t.Errorf("field %v does not match expected, want %v, got %v", name, elem, received[name])
}
} }
if receivedMap["sender"] != expectedReceivedMap["sender"] { }
t.Error("unpacked map does not match expected map")
} func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { return types.Log{
t.Error("unpacked map does not match expected map") Address: common.HexToAddress("0x0"),
} Topics: topics,
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { Data: hexutil.MustDecode(hexData),
t.Error("unpacked map does not match expected map") BlockNumber: uint64(26),
TxHash: txHash,
TxIndex: 111,
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
Index: 7,
Removed: false,
} }
} }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// Copyright 2018 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 (
"encoding/binary"
"errors"
"fmt"
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// makeTopics converts a filter query argument list into a filter topic set.
func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
topics := make([][]common.Hash, len(query))
for i, filter := range query {
for _, rule := range filter {
var topic common.Hash
// Try to generate the topic based on simple types
switch rule := rule.(type) {
case common.Hash:
copy(topic[:], rule[:])
case common.Address:
copy(topic[common.HashLength-common.AddressLength:], rule[:])
case *big.Int:
blob := rule.Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case bool:
if rule {
topic[common.HashLength-1] = 1
}
case int8:
blob := big.NewInt(int64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case int16:
blob := big.NewInt(int64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case int32:
blob := big.NewInt(int64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case int64:
blob := big.NewInt(rule).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint8:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint16:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint32:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint64:
blob := new(big.Int).SetUint64(rule).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case string:
hash := crypto.Keccak256Hash([]byte(rule))
copy(topic[:], hash[:])
case []byte:
hash := crypto.Keccak256Hash(rule)
copy(topic[:], hash[:])
default:
// Attempt to generate the topic from funky types
val := reflect.ValueOf(rule)
switch {
// static byte array
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
default:
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
}
}
topics[i] = append(topics[i], topic)
}
}
return topics, nil
}
// Big batch of reflect types for topic reconstruction.
var (
reflectHash = reflect.TypeOf(common.Hash{})
reflectAddress = reflect.TypeOf(common.Address{})
reflectBigInt = reflect.TypeOf(new(big.Int))
)
// parseTopics converts the indexed topic fields into actual log field values.
//
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
// hashes as the topic value!
func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error {
// Sanity check that the fields and topics match up
if len(fields) != len(topics) {
return errors.New("topic/field count mismatch")
}
// Iterate over all the fields and reconstruct them from topics
for _, arg := range fields {
if !arg.Indexed {
return errors.New("non-indexed field in topic reconstruction")
}
field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name))
// Try to parse the topic back into the fields based on primitive types
switch field.Kind() {
case reflect.Bool:
if topics[0][common.HashLength-1] == 1 {
field.Set(reflect.ValueOf(true))
}
case reflect.Int8:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int8(num.Int64())))
case reflect.Int16:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int16(num.Int64())))
case reflect.Int32:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int32(num.Int64())))
case reflect.Int64:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(num.Int64()))
case reflect.Uint8:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint8(num.Uint64())))
case reflect.Uint16:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint16(num.Uint64())))
case reflect.Uint32:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint32(num.Uint64())))
case reflect.Uint64:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(num.Uint64()))
default:
// Ran out of plain primitive types, try custom types
switch field.Type() {
case reflectHash: // Also covers all dynamic types
field.Set(reflect.ValueOf(topics[0]))
case reflectAddress:
var addr common.Address
copy(addr[:], topics[0][common.HashLength-common.AddressLength:])
field.Set(reflect.ValueOf(addr))
case reflectBigInt:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(num))
default:
// Ran out of custom types, try the crazies
switch {
// static byte array
case arg.Type.T == abi.FixedBytesTy:
reflect.Copy(field, reflect.ValueOf(topics[0][:arg.Type.Size]))
default:
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
}
}
}
topics = topics[1:]
}
return nil
}
// parseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs
func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics []common.Hash) error {
// Sanity check that the fields and topics match up
if len(fields) != len(topics) {
return errors.New("topic/field count mismatch")
}
// Iterate over all the fields and reconstruct them from topics
for _, arg := range fields {
if !arg.Indexed {
return errors.New("non-indexed field in topic reconstruction")
}
switch arg.Type.T {
case abi.BoolTy:
out[arg.Name] = topics[0][common.HashLength-1] == 1
case abi.IntTy, abi.UintTy:
num := new(big.Int).SetBytes(topics[0][:])
out[arg.Name] = num
case abi.AddressTy:
var addr common.Address
copy(addr[:], topics[0][common.HashLength-common.AddressLength:])
out[arg.Name] = addr
case abi.HashTy:
out[arg.Name] = topics[0]
case abi.FixedBytesTy:
out[arg.Name] = topics[0][:]
case abi.StringTy, abi.BytesTy, abi.SliceTy, abi.ArrayTy:
// Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash
// whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash
out[arg.Name] = topics[0]
case abi.FunctionTy:
if garbage := binary.BigEndian.Uint64(topics[0][0:8]); garbage != 0 {
return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[0].Bytes())
}
var tmp [24]byte
copy(tmp[:], topics[0][8:32])
out[arg.Name] = tmp
default: // Not handling tuples
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
}
topics = topics[1:]
}
return nil
}
This diff is collapsed.
...@@ -18,7 +18,7 @@ package bind ...@@ -18,7 +18,7 @@ package bind
import ( import (
"context" "context"
"fmt" "errors"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -56,14 +56,14 @@ func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*ty ...@@ -56,14 +56,14 @@ func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*ty
// contract address when it is mined. It stops waiting when ctx is canceled. // contract address when it is mined. It stops waiting when ctx is canceled.
func WaitDeployed(ctx context.Context, b DeployBackend, tx *types.Transaction) (common.Address, error) { func WaitDeployed(ctx context.Context, b DeployBackend, tx *types.Transaction) (common.Address, error) {
if tx.To() != nil { if tx.To() != nil {
return common.Address{}, fmt.Errorf("tx is not contract creation") return common.Address{}, errors.New("tx is not contract creation")
} }
receipt, err := WaitMined(ctx, b, tx) receipt, err := WaitMined(ctx, b, tx)
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
if receipt.ContractAddress == (common.Address{}) { if receipt.ContractAddress == (common.Address{}) {
return common.Address{}, fmt.Errorf("zero address") return common.Address{}, errors.New("zero address")
} }
// Check that code has indeed been deployed at the address. // Check that code has indeed been deployed at the address.
// This matters on pre-Homestead chains: OOG in the constructor // This matters on pre-Homestead chains: OOG in the constructor
......
This diff is collapsed.