├── .github
└── workflows
│ └── test.yaml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── README.md
├── cmd
└── ssz
│ ├── generate.go
│ ├── ir.go
│ ├── main.go
│ └── tests.go
├── gen-all-prysm.sh
├── go.mod
├── go.sum
├── specs
├── fork.go
├── harness.go
├── harness_test.go
├── ident.go
├── ident_test.go
├── template.go
├── testdata
│ └── prysm.yaml
├── tests.go
└── tests_test.go
└── sszgen
├── backend
├── bool.go
├── byte.go
├── caster.go
├── container.go
├── container_test.go
├── delegate.go
├── genhtr.go
├── genhtr_test.go
├── genmarshal.go
├── genmarshal_test.go
├── gensize.go
├── gensize_test.go
├── genunmarshal.go
├── genunmarshal_test.go
├── imports.go
├── list.go
├── overlay.go
├── pointer.go
├── render.go
├── render_test.go
├── testdata
│ ├── TestGenerateHashTreeRoot.expected
│ ├── TestGenerateMarshalSSZ.expected
│ ├── TestGenerateSizeSSZ.expected
│ ├── TestGenerateUnmarshalSSZ.expected
│ ├── TestGenerator_GenerateBeaconState.expected
│ └── TestUnmarshalSteps.expected
├── uint.go
├── union.go
├── vector.go
└── visitor.go
├── interfaces
└── ssz.go
├── node.go
├── parser.go
├── parser_test.go
├── representer.go
├── representer_test.go
├── tagparse.go
├── tagparse_test.go
├── testdata
├── simple.go
├── uint256.go.fixture
└── uint256.ssz.go.fixture
├── testutil
├── render.go
├── render_test.go
└── testdata
│ └── examplevars.go
├── types
├── bool.go
├── byte.go
├── container.go
├── container_test.go
├── list.go
├── overlay.go
├── pointer.go
├── uint.go
├── union.go
├── valrep.go
└── vector.go
├── typeutil.go
├── virtualscoper.go
└── virtualscoper_test.go
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: methodical-ssz
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | tags:
8 | - '**'
9 | pull_request:
10 | branches:
11 | - main
12 | workflow_dispatch:
13 |
14 | jobs:
15 | lint:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up Go
20 | uses: actions/setup-go@v2
21 | with:
22 | go-version: 1.19
23 | - name: Download golangci-lint
24 | run: wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s latest
25 | - name: Lint
26 | run: ./bin/golangci-lint run --config .golangci.yml
27 |
28 | test:
29 | runs-on: ubuntu-latest
30 | needs: lint
31 | steps:
32 | - uses: actions/checkout@v2
33 | - name: Set up Go
34 | uses: actions/setup-go@v2
35 | with:
36 | go-version: 1.19
37 | - name: Test
38 | run: go test -v ./...
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ssz-output
2 | generated
3 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | # This file configures github.com/golangci/golangci-lint.
2 |
3 | run:
4 | timeout: 20m
5 | tests: true
6 | # default is true. Enables skipping of directories:
7 | # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
8 | skip-dirs-use-default: true
9 |
10 | linters:
11 | disable-all: true
12 | enable:
13 | - goconst
14 | - goimports
15 | - gosimple
16 | - govet
17 | - ineffassign
18 | - misspell
19 | - unconvert
20 | - typecheck
21 | # - unused
22 | - staticcheck
23 | - bidichk
24 | - durationcheck
25 | - exportloopref
26 | - whitespace
27 |
28 | # - structcheck # lots of false positives
29 | # - errcheck #lot of false positives
30 | # - contextcheck
31 | # - errchkjson # lots of false positives
32 | # - errorlint # this check crashes
33 | # - exhaustive # silly check
34 | # - makezero # false positives
35 | # - nilerr # several intentional
36 |
37 | linters-settings:
38 | gofmt:
39 | simplify: true
40 | goconst:
41 | min-len: 3 # minimum length of string constant
42 | min-occurrences: 6 # minimum number of occurrences
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | methodical-ssz
2 | --------------
3 |
4 | This tool generates code that uses the [prysm fork](https://github.com/prysmaticlabs/fastssz/) of the [fastssz api](https://github.com/ferranbt/fastssz/) (with bindings for [go-hashtree](https://github.com/prysmaticlabs/gohashtree) coming very soon!), for marshaling, unmarshaling and merkleization of go types.
5 |
6 | Generate ssz methodsets
7 | -----------------------
8 |
9 | The source types are parsed using `go/types`, which locates the source code for a given package path through go's local package discovery utilities, so a current limitation is that you need to fetch the package to your go package tree before using the tool. This can be done with go get:
10 | ```
11 | go get github.com/prysmaticlabs/prysm/v3@v3.2.1
12 | ```
13 |
14 | Once this is done, code generation can be run against go types in the desired package:
15 | ```
16 | go run ./cmd/ssz gen --output=beacon-state.ssz.go --type-names BeaconState,BeaconStateAltair,BeaconStateBellatrix github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1
17 | Generating methods for github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/BeaconState
18 | Generating methods for github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/BeaconStateAltair
19 | Generating methods for github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/BeaconStateBellatrix
20 | ```
21 |
22 | The generated file can then be placed in a local directory for the source package so that the generated methods become part of the methodset of the source type.
23 |
24 | Generate spectests for a package
25 | --------------------------------
26 |
27 | This tool has a subcommand to generate go tests from the `ssz_static` tests in the ethereum/consensus-spec-tests repository. The generated tests can be run as normal go tests, which is helpful in identifying specific failure cases and walking through generated code with a debugger.
28 |
29 | spectest tarball
30 | ================
31 |
32 | To get the spec test fixtures for codegen, go to the [consensus-spec-test repository releases page](https://github.com/ethereum/consensus-spec-tests/releases) and download the most recent `pre-release` or `latest` spec tests release.
33 | ```
34 | curl -L https://github.com/ethereum/consensus-spec-tests/releases/download/v1.3.0-rc.2-hotfix/mainnet.tar.gz > mainnet-v1.3.0-rc.2.tar.gz
35 | ```
36 |
37 | yaml config
38 | ===========
39 |
40 | The spectest generation tool needs a mapping between go types and consensus spec container types. This mapping is described as a yaml config file. The mappings for prysm types are committed to the repo in a go `testdata` fixture directory, in `specs/testdata/prysm.yaml`. The following snippet illustrates the format:
41 | ```
42 | package: github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1
43 | preset: mainnet
44 | defs:
45 | - fork: phase0
46 | types:
47 | - name: AggregateAndProof
48 | type_name: AggregateAttestationAndProof
49 | - name: Attestation
50 | # ...
51 | - name: BeaconBlock
52 | # ...
53 | - fork: altair
54 | types:
55 | - name: BeaconBlock
56 | type_name: BeaconBlockAltair
57 | - name: BeaconBlockBody
58 | type_name: BeaconBlockBodyAltair
59 | ```
60 | `package` is the go package containing the types for code generation. Each yaml holds the description for one go package. `preset` is one of mainnet or minimal. At this time only mainnet is supported as preset customization is specific to an implementation's build tooling. `defs` contains a list of yaml objects, with the `fork` key matching the fork directory in the spectest layout, and the `types` key containing a list of objects connecting the `name` of the type from consensus specs / spectest directory names, to the `type_name` found in the `package`. If `type_name` is not present, `name` is the default, so for go types with the same spelling and capitalization as the consensus type, the `name` field alone is all that's needed to specify the type.
61 |
62 | The config reader processes type mappings in canonical fork order, so if a type's schema has not changed since the previous fork, it does not need to be redeclared. For instance the mapping for `Attestation` is only described once in the `phase0` mapping; the same go type will be used to execute the spectest for the Attestation value for all subsequent forks. `BeaconBlock`, on the other hand, has been redefined at every fork, so tests will use `BeaconBlockAltair` for spectests in the `altair` tree and so on. Any types observed in the tarball that don't have an entry defined in the config yaml will be skipped with a warning.
63 |
64 | spectest subcommand
65 | ===================
66 |
67 | The `generated` directory is a good place to stick generated tests as it is already in the .gitignore for the project. Assuming the tarball in the example above has been downloaded to the repo directory, running the following command there will generate spectests for all prysm types described in the test fixture yaml config:
68 | ```
69 | go run ./cmd/ssz spectest --release-uri=file://$PWD/mainnet-v1.3.0-rc.2.tar.gz --config=$PWD/specs/testdata/prysm.yaml --output=$PWD/generated
70 | ```
71 |
72 | Run the spectest like normal go tests:
73 | ```
74 | go test ./generated
75 | ok github.com/OffchainLabs/methodical-ssz/generated 1.003s
76 | ```
77 |
--------------------------------------------------------------------------------
/cmd/ssz/generate.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "os"
8 | "strings"
9 |
10 | "github.com/OffchainLabs/methodical-ssz/sszgen"
11 | "github.com/OffchainLabs/methodical-ssz/sszgen/backend"
12 | "github.com/urfave/cli/v2"
13 | )
14 |
15 | var sourcePackage, output, typeNames string
16 | var disableDelegation bool
17 | var generate = &cli.Command{
18 | Name: "generate",
19 | ArgsUsage: "",
20 | Aliases: []string{"gen"},
21 | Usage: "generate methodsets for a go struct type to support ssz ser/des",
22 | Flags: []cli.Flag{
23 | &cli.StringFlag{
24 | Name: "output",
25 | Value: "",
26 | Usage: "directory to write generated code (same as input by default)",
27 | Destination: &output,
28 | },
29 | &cli.StringFlag{
30 | Name: "type-names",
31 | Value: "",
32 | Usage: "if specified, only generate methods for types specified in this comma-separated list",
33 | Destination: &typeNames,
34 | },
35 | &cli.BoolFlag{
36 | Name: "disable-delegation",
37 | Usage: "if specified, do not check for existing ssz method sets. helpful when the codegen source has them already",
38 | Destination: &disableDelegation,
39 | },
40 | },
41 | Action: func(c *cli.Context) error {
42 | sourcePackage = c.Args().Get(0)
43 | if sourcePackage == "" {
44 | cli.ShowCommandHelp(c, "generate")
45 | return fmt.Errorf("error: mising required argument")
46 | }
47 | var err error
48 | var fields []string
49 | if len(typeNames) > 0 {
50 | fields = strings.Split(strings.TrimSpace(typeNames), ",")
51 | }
52 |
53 | fmt.Printf("Parsing package %v\n", sourcePackage)
54 | ps, err := sszgen.NewGoPathScoper(sourcePackage)
55 | if err != nil {
56 | return err
57 | }
58 |
59 | if output == "" {
60 | output = "methodical.ssz.go"
61 | }
62 | outFh, err := os.Create(output)
63 | if err != nil {
64 | return err
65 | }
66 | defer outFh.Close()
67 |
68 | g := backend.NewGenerator(sourcePackage)
69 | defs, err := sszgen.TypeDefs(ps, fields...)
70 | if err != nil {
71 | return err
72 | }
73 | opts := make([]sszgen.FieldParserOpt, 0)
74 | if disableDelegation {
75 | opts = append(opts, sszgen.WithDisableDelegation())
76 | }
77 | for _, s := range defs {
78 | fmt.Printf("Generating methods for %s/%s\n", s.PackageName, s.Name)
79 | typeRep, err := sszgen.ParseTypeDef(s, opts...)
80 | if err != nil {
81 | return err
82 | }
83 | if err := g.Generate(typeRep); err != nil {
84 | return err
85 | }
86 | }
87 | fmt.Println("Rendering template")
88 | rbytes, err := g.Render()
89 | if err != nil {
90 | return err
91 | }
92 | _, err = io.Copy(outFh, bytes.NewReader(rbytes))
93 | return err
94 | },
95 | }
96 |
--------------------------------------------------------------------------------
/cmd/ssz/ir.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "os"
6 | "strings"
7 |
8 | "github.com/OffchainLabs/methodical-ssz/sszgen"
9 | "github.com/OffchainLabs/methodical-ssz/sszgen/testutil"
10 | "github.com/urfave/cli/v2"
11 | )
12 |
13 | var ir = &cli.Command{
14 | Name: "ir",
15 | ArgsUsage: "",
16 | Aliases: []string{"gen"},
17 | Usage: "generate intermediate representation for a go struct type. This data structure is used by the backend code generator. Outputting it to a source file an be useful for generating test cases and debugging.",
18 | Flags: []cli.Flag{
19 | &cli.StringFlag{
20 | Name: "output",
21 | Value: "",
22 | Usage: "file path to write generated code",
23 | Destination: &output,
24 | Required: true,
25 | },
26 | &cli.StringFlag{
27 | Name: "type-names",
28 | Value: "",
29 | Usage: "if specified, only generate types specified in this comma-separated list",
30 | Destination: &typeNames,
31 | },
32 | },
33 | Action: func(c *cli.Context) error {
34 | if c.NArg() > 0 {
35 | sourcePackage = c.Args().Get(0)
36 | }
37 |
38 | var err error
39 | var fields []string
40 | if len(typeNames) > 0 {
41 | fields = strings.Split(strings.TrimSpace(typeNames), ",")
42 | }
43 | ps, err := sszgen.NewGoPathScoper(sourcePackage)
44 | if err != nil {
45 | return err
46 | }
47 |
48 | outFh, err := os.Create(output)
49 | if err != nil {
50 | return err
51 | }
52 | defer outFh.Close()
53 |
54 | renderedTypes := make([]string, 0)
55 | defs, err := sszgen.TypeDefs(ps, fields...)
56 | if err != nil {
57 | return err
58 | }
59 | for _, s := range defs {
60 | typeRep, err := sszgen.ParseTypeDef(s)
61 | if err != nil {
62 | return err
63 | }
64 | rendered, err := testutil.RenderIntermediate(typeRep)
65 | if err != nil {
66 | return err
67 | }
68 | renderedTypes = append(renderedTypes, rendered)
69 | }
70 | if err != nil {
71 | return err
72 | }
73 |
74 | _, err = io.Copy(outFh, strings.NewReader(strings.Join(renderedTypes, "\n")))
75 | return err
76 | },
77 | }
78 |
--------------------------------------------------------------------------------
/cmd/ssz/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "github.com/urfave/cli/v2"
8 | )
9 |
10 | func main() {
11 | app := &cli.App{
12 | Usage: "ssz support for prysm",
13 | // TODO: implement benchmark
14 | Commands: []*cli.Command{ /*benchmark,*/ generate, ir, tests},
15 | }
16 |
17 | err := app.Run(os.Args)
18 | if err != nil {
19 | log.Fatal(err)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/cmd/ssz/tests.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/url"
7 | "os"
8 |
9 | "github.com/OffchainLabs/methodical-ssz/specs"
10 | "github.com/OffchainLabs/methodical-ssz/sszgen"
11 | "github.com/OffchainLabs/methodical-ssz/sszgen/backend"
12 | "github.com/pkg/errors"
13 | "github.com/spf13/afero"
14 | "github.com/urfave/cli/v2"
15 | )
16 |
17 | var releaseURI, configPath string
18 | var tests = &cli.Command{
19 | Name: "spectest",
20 | Usage: "generate go test methods to execute spectests against generated types",
21 | Flags: []cli.Flag{
22 | &cli.StringFlag{
23 | Name: "release-uri",
24 | Usage: "url or file in file:// format pointing at a github.com/ethereum/consensus-spec-tests release",
25 | Destination: &releaseURI,
26 | },
27 | &cli.StringFlag{
28 | Name: "config",
29 | Usage: "path to yaml file configuring spec test relationships, see readme or prysm example for format",
30 | Required: true,
31 | Destination: &configPath,
32 | },
33 | &cli.StringFlag{
34 | Name: "output",
35 | Usage: "path to output directory where spec test package and copy of consensus types and ssz methods will be written",
36 | Required: true,
37 | Destination: &output,
38 | },
39 | &cli.BoolFlag{
40 | Name: "disable-delegation",
41 | Usage: "if specified, do not check for existing ssz method sets. helpful when the codegen source has them already",
42 | Destination: &disableDelegation,
43 | },
44 | },
45 | Action: func(c *cli.Context) error {
46 | return actionSpectests(c)
47 | },
48 | }
49 |
50 | func actionSpectests(cl *cli.Context) error {
51 | if err := os.MkdirAll(output, os.ModePerm); err != nil {
52 | return errors.Wrapf(err, "failed to create output directory %s", output)
53 | }
54 | fs := afero.NewBasePathFs(afero.NewOsFs(), output)
55 | cfg, err := specs.ParseConfigFile(configPath)
56 | if err != nil {
57 | return err
58 | }
59 | types := cfg.GoTypes()
60 | ps, err := sszgen.NewGoPathScoper(cfg.Package)
61 | if err != nil {
62 | return err
63 | }
64 |
65 | r, err := loadArchive(releaseURI)
66 | if err != nil {
67 | return errors.Wrapf(err, "failed to open spectest archive from uri %s", releaseURI)
68 | }
69 | cases, err := specs.ExtractCases(r, specs.TestIdent{Preset: specs.Mainnet})
70 | if err != nil {
71 | return err
72 | }
73 | for ident := range cases {
74 | fmt.Printf("%s\n", ident)
75 | }
76 |
77 | g := backend.NewGenerator(cfg.Package)
78 | defs, err := sszgen.TypeDefs(ps, types...)
79 | if err != nil {
80 | return err
81 | }
82 | opts := make([]sszgen.FieldParserOpt, 0)
83 | if disableDelegation {
84 | opts = append(opts, sszgen.WithDisableDelegation())
85 | }
86 | for _, s := range defs {
87 | fmt.Printf("Generating methods for %s/%s\n", s.PackageName, s.Name)
88 | typeRep, err := sszgen.ParseTypeDef(s, opts...)
89 | if err != nil {
90 | return err
91 | }
92 | if err := g.Generate(typeRep); err != nil {
93 | return err
94 | }
95 | }
96 | rbytes, err := g.Render()
97 | if err != nil {
98 | return err
99 | }
100 | if err := afero.WriteFile(fs, "methodical.ssz.go", rbytes, 0666); err != nil {
101 | return err
102 | }
103 | source, err := ps.TypeDefSourceCode(defs)
104 | if err != nil {
105 | return err
106 | }
107 | if err := afero.WriteFile(fs, "structs.go", source, 0666); err != nil {
108 | return err
109 | }
110 | return specs.WriteSpecTestFiles(cases, cfg, fs)
111 | }
112 |
113 | func loadArchive(uri string) (io.Reader, error) {
114 | u, err := url.Parse(uri)
115 | if err != nil {
116 | return nil, err
117 | }
118 | if u.Scheme == "file" {
119 | return os.Open(u.Path)
120 | }
121 | return nil, errors.New("unsupported url protocol")
122 | }
123 |
--------------------------------------------------------------------------------
/gen-all-prysm.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | SSZ_OUT=$PWD/ssz-output
4 |
5 | # bad github.com/prysmaticlabs/prysm/v3/proto/engine/v1
6 | #dlv debug --headless --listen=:2345 --api-version=2 --accept-multiclient github.com/OffchainLabs/methodical-ssz/cmd/ssz -- gen --type-names=ExecutionPayload,ExecutionPayloadCapella github.com/prysmaticlabs/prysm/v3/proto/engine/v1
7 | #go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --type-names=ExecutionPayload,ExecutionPayloadCapella github.com/prysmaticlabs/prysm/v3/proto/engine/v1
8 | # good github.com/prysmaticlabs/prysm/v3/proto/engine/v1
9 | ENGINE_OUT=$SSZ_OUT/proto/engine/v1
10 | mkdir -p $ENGINE_OUT
11 | go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --output=$ENGINE_OUT/ssz.go --type-names=ExecutionPayloadHeader,ExecutionPayloadHeaderCapella,Withdrawal github.com/prysmaticlabs/prysm/v3/proto/engine/v1
12 |
13 | ETHV1_OUT=$SSZ_OUT/proto/eth/v1
14 | mkdir -p $ETHV1_OUT
15 | # good github.com/prysmaticlabs/prysm/v3/proto/eth/v1
16 | go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --output=$ETHV1_OUT/ssz.go --type-names=Attestation,AggregateAttestationAndProof,SignedAggregateAttestationAndProof,AttestationData,Checkpoint,BeaconBlock,SignedBeaconBlock,BeaconBlockBody,ProposerSlashing,AttesterSlashing,Deposit,VoluntaryExit,SignedVoluntaryExit,Eth1Data,BeaconBlockHeader,SignedBeaconBlockHeader,IndexedAttestation,SyncAggregate,Deposit_Data,Validator github.com/prysmaticlabs/prysm/v3/proto/eth/v1
17 |
18 | ETHV2_OUT=$SSZ_OUT/proto/eth/v2
19 | mkdir -p $ETHV2_OUT
20 | # bad github.com/prysmaticlabs/prysm/v3/proto/eth/v2
21 | #go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --type-names=SignedBeaconBlockBellatrix,SignedBeaconBlockCapella,BeaconBlockBellatrix,BeaconBlockCapella,BeaconBlockBodyBellatrix,BeaconBlockBodyCapella github.com/prysmaticlabs/prysm/v3/proto/eth/v2
22 | # good github.com/prysmaticlabs/prysm/v3/proto/eth/v2
23 | go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --output=$ETHV2_OUT/ssz.go --type-names=SignedBlindedBeaconBlockBellatrix,SignedBlindedBeaconBlockCapella,SignedBeaconBlockAltair,BlindedBeaconBlockBellatrix,BlindedBeaconBlockCapella,BeaconBlockAltair,BlindedBeaconBlockBodyBellatrix,BlindedBeaconBlockBodyCapella,BeaconBlockBodyAltair,BLSToExecutionChange,SignedBLSToExecutionChange github.com/prysmaticlabs/prysm/v3/proto/eth/v2
24 |
25 | V1ALPHA1_OUT=$SSZ_OUT/proto/prysm/v1alpha1
26 | mkdir -p $V1ALPHA1_OUT
27 | # bad github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1
28 | #go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --type-names=SignedBeaconBlockBellatrix,BeaconBlockBellatrix,BeaconBlockBodyBellatrix,SignedBeaconBlockCapella,BeaconBlockCapella,BeaconBlockBodyCapella,BuilderBidCapella,HistoricalSummary github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1
29 | # good github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1
30 | go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --output=$V1ALPHA1_OUT/ssz.go --type-names=Attestation,AggregateAttestationAndProof,SignedAggregateAttestationAndProof,AttestationData,Checkpoint,BeaconBlock,SignedBeaconBlock,BeaconBlockAltair,SignedBeaconBlockAltair,BeaconBlockBody,BeaconBlockBodyAltair,ProposerSlashing,AttesterSlashing,Deposit,VoluntaryExit,SignedVoluntaryExit,Eth1Data,BeaconBlockHeader,SignedBeaconBlockHeader,IndexedAttestation,SyncAggregate,SignedBlindedBeaconBlockBellatrix,BlindedBeaconBlockBellatrix,BlindedBeaconBlockBodyBellatrix,SignedBlindedBeaconBlockCapella,BlindedBeaconBlockCapella,BlindedBeaconBlockBodyCapella,ValidatorRegistrationV1,SignedValidatorRegistrationV1,BuilderBid,Deposit_Data,BeaconState,BeaconStateAltair,Fork,PendingAttestation,HistoricalBatch,SigningData,ForkData,DepositMessage,SyncCommittee,SyncAggregatorSelectionData,BeaconStateBellatrix,BeaconStateCapella,PowBlock,Status,BeaconBlocksByRangeRequest,ENRForkID,MetaDataV0,MetaDataV1,SyncCommitteeMessage,SyncCommitteeContribution,ContributionAndProof,SignedContributionAndProof,Validator,BLSToExecutionChange,SignedBLSToExecutionChange github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1
31 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/OffchainLabs/methodical-ssz
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/dave/jennifer v1.6.0
7 | github.com/ethereum/go-ethereum v1.11.2
8 | github.com/golang/snappy v0.0.4
9 | github.com/pkg/errors v0.9.1
10 | github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab
11 | github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7
12 | github.com/prysmaticlabs/prysm/v3 v3.2.2-rc.1.0.20230309092345-83a294c1a5a4
13 | github.com/sirupsen/logrus v1.9.0
14 | github.com/spf13/afero v1.2.2
15 | github.com/urfave/cli/v2 v2.24.1
16 | golang.org/x/tools v0.6.0
17 | google.golang.org/protobuf v1.28.1
18 | gopkg.in/yaml.v3 v3.0.1
19 | sigs.k8s.io/yaml v1.2.0
20 | )
21 |
22 | require (
23 | github.com/beorn7/perks v1.0.1 // indirect
24 | github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
25 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
26 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
27 | github.com/d4l3k/messagediff v1.2.1 // indirect
28 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
29 | github.com/golang/protobuf v1.5.2 // indirect
30 | github.com/klauspost/cpuid/v2 v2.2.1 // indirect
31 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
32 | github.com/minio/sha256-simd v1.0.0 // indirect
33 | github.com/mitchellh/mapstructure v1.4.1 // indirect
34 | github.com/prometheus/client_golang v1.14.0 // indirect
35 | github.com/prometheus/client_model v0.3.0 // indirect
36 | github.com/prometheus/common v0.39.0 // indirect
37 | github.com/prometheus/procfs v0.9.0 // indirect
38 | github.com/prysmaticlabs/gohashtree v0.0.2-alpha // indirect
39 | github.com/russross/blackfriday/v2 v2.1.0 // indirect
40 | github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e // indirect
41 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
42 | golang.org/x/crypto v0.5.0 // indirect
43 | golang.org/x/mod v0.8.0 // indirect
44 | golang.org/x/sys v0.5.0 // indirect
45 | golang.org/x/text v0.7.0 // indirect
46 | gopkg.in/yaml.v2 v2.4.0 // indirect
47 | )
48 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
2 | github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE=
3 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
4 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
5 | github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
6 | github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
7 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
8 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
9 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
10 | github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8=
11 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
12 | github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk=
13 | github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ=
14 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
15 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
16 | github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
17 | github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
18 | github.com/dave/jennifer v1.6.0 h1:MQ/6emI2xM7wt0tJzJzyUik2Q3Tcn2eE0vtYgh4GPVI=
19 | github.com/dave/jennifer v1.6.0/go.mod h1:AxTG893FiZKqxy3FP1kL80VMshSMuz2G+EgvszgGRnk=
20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23 | github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
24 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
25 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
26 | github.com/ethereum/go-ethereum v1.11.2 h1:z/luyejbevDCAMUUiu0rc80dxJxOnpoG58k5o0tSawc=
27 | github.com/ethereum/go-ethereum v1.11.2/go.mod h1:DuefStAgaxoaYGLR0FueVcVbehmn5n9QUcVrMCuOvuc=
28 | github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0=
29 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
30 | github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
31 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
32 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
33 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
34 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
35 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
36 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
37 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
38 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
39 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
40 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
41 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1 h1:X2vfSnm1WC8HEo0MBHZg2TcuDUHJj6kd1TmEAQncnSA=
42 | github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gto8n9hAmRxKxoar0iE6BLucjw=
43 | github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o=
44 | github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
45 | github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
46 | github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI=
47 | github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
48 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
49 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
50 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
51 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
52 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
53 | github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
54 | github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
55 | github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
56 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
57 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
58 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
59 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
60 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
61 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
62 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
63 | github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
64 | github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
65 | github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
66 | github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
67 | github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI=
68 | github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y=
69 | github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
70 | github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
71 | github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic=
72 | github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab h1:Y3PcvUrnneMWLuypZpwPz8P70/DQsz6KgV9JveKpyZs=
73 | github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab/go.mod h1:MA5zShstUwCQaE9faGHgCGvEWUbG87p4SAXINhmCkvg=
74 | github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw=
75 | github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4=
76 | github.com/prysmaticlabs/gohashtree v0.0.2-alpha h1:hk5ZsDQuSkyUMhTd55qB396P1+dtyIKiSwMmYE/hyEU=
77 | github.com/prysmaticlabs/gohashtree v0.0.2-alpha/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk=
78 | github.com/prysmaticlabs/protoc-gen-go-cast v0.0.0-20230228205207-28762a7b9294 h1:q9wE0ZZRdTUAAeyFP/w0SwBEnCqlVy2+on6X2/e+eAU=
79 | github.com/prysmaticlabs/prysm/v3 v3.2.2-rc.1.0.20230309092345-83a294c1a5a4 h1:P58mUzwZ4xrbsIiecFLJOaYBeltJTYmQ4bB1iZgArkA=
80 | github.com/prysmaticlabs/prysm/v3 v3.2.2-rc.1.0.20230309092345-83a294c1a5a4/go.mod h1:QyK7KxYY+0PGQaCuRtb55EpvA3Zh74wCNKl8EW+sYS0=
81 | github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
82 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
83 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
84 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
85 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
86 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
87 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
88 | github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
89 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
90 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
91 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
92 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
93 | github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
94 | github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e h1:cR8/SYRgyQCt5cNCMniB/ZScMkhI9nk8U5C7SbISXjo=
95 | github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e/go.mod h1:Tu4lItkATkonrYuvtVjG0/rhy15qrNGNTjPdaphtZ/8=
96 | github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
97 | github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
98 | github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU=
99 | github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
100 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
101 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
102 | github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
103 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
104 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
105 | golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
106 | golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
107 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
108 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
109 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
110 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
111 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
112 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
113 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
114 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
115 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
116 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
117 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
118 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
119 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
120 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
121 | google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84 h1:R1r5J0u6Cx+RNl/6mezTw6oA14cmKC96FeUwL6A9bd4=
122 | google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
123 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
124 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
125 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
126 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
127 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
128 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
129 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
130 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
131 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
132 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
133 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
134 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
135 | sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
136 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
137 |
--------------------------------------------------------------------------------
/specs/fork.go:
--------------------------------------------------------------------------------
1 | package specs
2 |
3 | import "github.com/pkg/errors"
4 |
5 | type Fork string
6 |
7 | var ErrUnknownFork = errors.New("unknown fork name")
8 |
9 | func (f *Fork) UnmarshalText(t []byte) error {
10 | s := string(t)
11 | sf := stringToFork(s)
12 | if sf == ForkUnknown {
13 | return errors.Wrap(ErrUnknownFork, s)
14 | }
15 | *f = sf
16 | return nil
17 | }
18 |
19 | var (
20 | ForkUnknown Fork = ""
21 | Phase0 Fork = "phase0"
22 | Altair Fork = "altair"
23 | Bellatrix Fork = "bellatrix"
24 | Capella Fork = "capella"
25 | //EIP4844 Fork = "eip4844"
26 | )
27 |
28 | // var ForkOrder = []Fork{Phase0, Altair, Bellatrix, Capella, EIP4844}
29 | var ForkOrder = []Fork{Phase0, Altair, Bellatrix, Capella}
30 |
31 | func stringToFork(s string) Fork {
32 | switch s {
33 | case string(Phase0):
34 | return Phase0
35 | case string(Altair):
36 | return Altair
37 | case string(Bellatrix):
38 | return Bellatrix
39 | case string(Capella):
40 | return Capella
41 | /*
42 | case string(EIP4844):
43 | return EIP4844
44 | */
45 | default:
46 | return ForkUnknown
47 | }
48 | }
49 |
50 | func forkIndex(f Fork) (int, error) {
51 | for i := 0; i < len(ForkOrder); i++ {
52 | if ForkOrder[i] == f {
53 | return i, nil
54 | }
55 | }
56 | return 0, ErrUnknownFork
57 | }
58 |
--------------------------------------------------------------------------------
/specs/harness.go:
--------------------------------------------------------------------------------
1 | package specs
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "go/format"
7 | "os"
8 | "path"
9 | "strings"
10 |
11 | "github.com/OffchainLabs/methodical-ssz/sszgen/backend"
12 | "github.com/pkg/errors"
13 | log "github.com/sirupsen/logrus"
14 | "github.com/spf13/afero"
15 | "sigs.k8s.io/yaml"
16 | )
17 |
18 | type SpecRelationships struct {
19 | Package string `json:"package"`
20 | Preset Preset `json:"preset"`
21 | Defs []ForkTypeDefinitions `json:"defs"`
22 | }
23 |
24 | type ForkTypeDefinitions struct {
25 | Fork Fork `json:"fork"`
26 | Types []TypeRelation `json:"types"`
27 | }
28 |
29 | type TypeRelation struct {
30 | SpecName string `json:"name"`
31 | TypeName string `json:"type_name"`
32 | }
33 |
34 | func ParseConfigFile(path string) (*SpecRelationships, error) {
35 | cb, err := os.ReadFile(path)
36 | if err != nil {
37 | return nil, err
38 | }
39 | sr := &SpecRelationships{}
40 | err = yaml.Unmarshal(cb, sr)
41 | return sr, err
42 | }
43 |
44 | func (sr SpecRelationships) GoTypes() []string {
45 | tm := make(map[string]bool)
46 | gt := make([]string, 0)
47 | for _, d := range sr.Defs {
48 | for _, t := range d.Types {
49 | n := t.TypeName
50 | if n == "" {
51 | n = t.SpecName
52 | }
53 | if tm[n] {
54 | continue
55 | }
56 | gt = append(gt, n)
57 | }
58 | }
59 | return gt
60 | }
61 |
62 | func (sr SpecRelationships) RelationsAtFork(f Fork) (map[string]string, error) {
63 | // look up the position of this fork in the ordering of all forks, so we can start walking backwards from there
64 | fidx, err := forkIndex(f)
65 | if err != nil {
66 | return nil, err
67 | }
68 | // convert the list into a map for easier lookup
69 | fm := make(map[Fork][]TypeRelation)
70 | for _, d := range sr.Defs {
71 | fm[d.Fork] = d.Types
72 | }
73 | rf := make(map[string]string)
74 | // walk backwards through the forks to find the highest schema <= the requested fork
75 | for i := fidx; i >= 0; i-- {
76 | // get the fork for the current index
77 | f = ForkOrder[i]
78 | // get the list of type definitions at the given fork
79 | types, ok := fm[f]
80 | if !ok {
81 | // skip this fork if there are no type definitions for it in the given package
82 | continue
83 | }
84 | for _, t := range types {
85 | _, ok := rf[t.SpecName]
86 | // don't replace a newer version of the type with an older version
87 | if !ok {
88 | rf[t.SpecName] = t.TypeName
89 | // a blank Name property means the type name is the same as the spec name
90 | if rf[t.SpecName] == "" {
91 | rf[t.SpecName] = t.SpecName
92 | }
93 | }
94 | }
95 | }
96 | return rf, nil
97 | }
98 |
99 | type TestCaseTpl struct {
100 | ident TestIdent
101 | fixture Fixture
102 | structName string
103 | }
104 |
105 | func (tpl *TestCaseTpl) FixtureDirectory() string {
106 | return path.Join("testdata", tpl.fixture.Directory)
107 | }
108 |
109 | func (tpl *TestCaseTpl) rootPath() string {
110 | return path.Join(tpl.FixtureDirectory(), rootFilename)
111 | }
112 |
113 | func (tpl *TestCaseTpl) yamlPath() string {
114 | return path.Join(tpl.FixtureDirectory(), valueFilename)
115 | }
116 |
117 | func (tpl *TestCaseTpl) serializedPath() string {
118 | return path.Join(tpl.FixtureDirectory(), serializedFilename)
119 | }
120 |
121 | func (tpl *TestCaseTpl) ensureFixtures(fs afero.Fs) error {
122 | f := tpl.fixture
123 | if err := fs.MkdirAll(tpl.FixtureDirectory(), os.ModePerm); err != nil {
124 | return errors.Wrapf(err, "failed to create fixture directory %s", f.Directory)
125 | }
126 | if err := ensure(fs, tpl.rootPath(), f.Root.Contents, f.Root.FileMode); err != nil {
127 | return err
128 | }
129 | if err := ensure(fs, tpl.serializedPath(), f.Serialized.Contents, f.Serialized.FileMode); err != nil {
130 | return err
131 | }
132 | if err := ensure(fs, tpl.yamlPath(), f.Yaml.Contents, f.Yaml.FileMode); err != nil {
133 | return err
134 | }
135 | return nil
136 | }
137 |
138 | func ensure(fs afero.Fs, path string, contents []byte, mode os.FileMode) error {
139 | exists, err := afero.Exists(fs, path)
140 | if err != nil {
141 | return errors.Wrapf(err, "error checking for existence of %s", path)
142 | }
143 | if exists {
144 | return nil
145 | }
146 | if err := afero.WriteFile(fs, path, contents, mode); err != nil {
147 | return errors.Wrapf(err, "error writing fixture contents to %s", path)
148 | }
149 | return nil
150 | }
151 |
152 | func (tpl *TestCaseTpl) TestFuncName() string {
153 | id := tpl.ident
154 | return fmt.Sprintf("Test_%s_%s_%s_%d", id.Preset, id.Fork, tpl.structName, id.Offset)
155 | }
156 |
157 | func (tpl *TestCaseTpl) GoTypeName() string {
158 | return tpl.structName
159 | }
160 |
161 | func (tpl *TestCaseTpl) Render() (string, error) {
162 | b := bytes.NewBuffer(nil)
163 | err := testFuncBodyTpl.Execute(b, tpl)
164 | return b.String(), err
165 | }
166 |
167 | func WriteSpecTestFiles(cases map[TestIdent]Fixture, rels *SpecRelationships, fs afero.Fs) error {
168 | caseFuncs := make([]string, 0)
169 | fg := GroupByFork(cases)
170 | for _, fork := range ForkOrder {
171 | ids := fg[fork]
172 | raf, err := rels.RelationsAtFork(fork)
173 | if err != nil {
174 | return err
175 | }
176 | for _, id := range ids {
177 | structName, ok := raf[id.Name]
178 | if !ok {
179 | log.Infof("No implementation for %s, skipping test", cases[id].Directory)
180 | continue
181 | }
182 | tpl := &TestCaseTpl{
183 | ident: id,
184 | fixture: cases[id],
185 | structName: structName,
186 | }
187 | if err := tpl.ensureFixtures(fs); err != nil {
188 | return err
189 | }
190 | cfunc, err := tpl.Render()
191 | if err != nil {
192 | return err
193 | }
194 | caseFuncs = append(caseFuncs, cfunc)
195 | }
196 | }
197 | packageDecl := "package " + backend.RenderedPackageName(rels.Package) + "\n\n"
198 | contents := packageDecl + "\n\n" + testCaseTemplateImports + "\n\n" + strings.Join(caseFuncs, "\n\n")
199 |
200 | testBytes, err := format.Source([]byte(contents))
201 | if err != nil {
202 | return err
203 | }
204 |
205 | fname := "methodical_test.go"
206 | if err := afero.WriteFile(fs, fname, testBytes, 0666); err != nil {
207 | return errors.Wrapf(err, "error writing spectest functions to %s", fname)
208 | }
209 |
210 | return nil
211 | }
212 |
--------------------------------------------------------------------------------
/specs/harness_test.go:
--------------------------------------------------------------------------------
1 | package specs
2 |
3 | import (
4 | "os"
5 | "path"
6 | "testing"
7 |
8 | "github.com/prysmaticlabs/prysm/v3/testing/require"
9 | "github.com/spf13/afero"
10 | "sigs.k8s.io/yaml"
11 | )
12 |
13 | func TestHarnessYaml(t *testing.T) {
14 | input := `
15 | package: github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1
16 | preset: mainnet
17 | defs:
18 | - fork: phase0
19 | types:
20 | - name: BeaconBlock
21 | - fork: altair
22 | types:
23 | - name: BeaconBlock
24 | type_name: BeaconBlockAltair`
25 | sr := &SpecRelationships{}
26 | err := yaml.Unmarshal([]byte(input), sr)
27 | require.NoError(t, err)
28 | require.Equal(t, "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1", sr.Package)
29 | require.Equal(t, Mainnet, sr.Preset)
30 | require.Equal(t, 2, len(sr.Defs))
31 | require.Equal(t, Phase0, sr.Defs[0].Fork)
32 | require.Equal(t, 1, len(sr.Defs[0].Types))
33 | require.Equal(t, "BeaconBlock", sr.Defs[0].Types[0].SpecName)
34 | require.Equal(t, "", sr.Defs[0].Types[0].TypeName)
35 | require.Equal(t, Altair, sr.Defs[1].Fork)
36 | require.Equal(t, 1, len(sr.Defs[1].Types))
37 | require.Equal(t, "BeaconBlock", sr.Defs[1].Types[0].SpecName)
38 | require.Equal(t, "BeaconBlockAltair", sr.Defs[1].Types[0].TypeName)
39 |
40 | require.Equal(t, 2, len(sr.GoTypes()))
41 | }
42 |
43 | func TestHarnessYamlFull(t *testing.T) {
44 | t.Skip("Skipping this test since no prysm.yaml file is available")
45 | sr := loadPrysmRelations(t)
46 | require.Equal(t, "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1", sr.Package)
47 | }
48 |
49 | func TestRelationsAtFork(t *testing.T) {
50 | t.Skip("Skipping this test since no prysm.yaml file is available")
51 | cases := []struct {
52 | name string
53 | specName string
54 | typeName string
55 | fork Fork
56 | err error
57 | missing bool
58 | }{
59 | {
60 | name: "BeaconBlock at phase0",
61 | specName: "BeaconBlock",
62 | typeName: "BeaconBlock",
63 | fork: Phase0,
64 | },
65 | {
66 | name: "BeaconBlock at altair",
67 | specName: "BeaconBlock",
68 | typeName: "BeaconBlockAltair",
69 | fork: Altair,
70 | },
71 | {
72 | name: "Checkpoint at phase0",
73 | specName: "Checkpoint",
74 | typeName: "Checkpoint",
75 | fork: Phase0,
76 | },
77 | {
78 | name: "Checkpoint at altair",
79 | specName: "Checkpoint",
80 | typeName: "Checkpoint",
81 | fork: Altair,
82 | },
83 | {
84 | name: "Checkpoint at bellatrix",
85 | specName: "Checkpoint",
86 | typeName: "Checkpoint",
87 | fork: Bellatrix,
88 | },
89 | {
90 | name: "ExecutionPayload missing at phase0",
91 | specName: "ExecutionPayload",
92 | typeName: "",
93 | fork: Phase0,
94 | missing: true,
95 | },
96 | {
97 | name: "ExecutionPayload missing at altair",
98 | specName: "ExecutionPayload",
99 | typeName: "",
100 | fork: Altair,
101 | missing: true,
102 | },
103 | {
104 | name: "ExecutionPayload at bellatrix",
105 | specName: "ExecutionPayload",
106 | typeName: "ExecutionPayload",
107 | fork: Bellatrix,
108 | },
109 | {
110 | name: "ExecutionPayload at capella",
111 | specName: "ExecutionPayload",
112 | typeName: "ExecutionPayloadCapella",
113 | fork: Capella,
114 | },
115 | }
116 | sr := loadPrysmRelations(t)
117 | for _, c := range cases {
118 | t.Run(c.name, func(t *testing.T) {
119 | r, err := sr.RelationsAtFork(c.fork)
120 | if err != nil {
121 | require.ErrorIs(t, err, c.err)
122 | return
123 | }
124 | require.NoError(t, err)
125 | tn, ok := r[c.specName]
126 | if c.missing {
127 | require.Equal(t, false, ok)
128 | return
129 | }
130 | require.Equal(t, true, ok)
131 | require.Equal(t, c.typeName, tn)
132 | })
133 | }
134 | }
135 |
136 | func loadPrysmRelations(t *testing.T) *SpecRelationships {
137 | y, err := os.ReadFile("testdata/prysm.yaml")
138 | require.NoError(t, err)
139 | sr := &SpecRelationships{}
140 | err = yaml.Unmarshal(y, sr)
141 | require.NoError(t, err)
142 | return sr
143 | }
144 |
145 | func TestTestCaseTplFuncName(t *testing.T) {
146 | cases := []struct {
147 | name string
148 | ident TestIdent
149 | structname string
150 | }{
151 | {
152 | name: "Test_mainnet_altair_AggregateAndProof_0",
153 | ident: TestIdent{
154 | Preset: Mainnet,
155 | Fork: Altair,
156 | Name: "AggregateAndProof",
157 | Offset: 0,
158 | },
159 | structname: "AggregateAttestationAndProof",
160 | },
161 | }
162 | for _, c := range cases {
163 | t.Run(c.name, func(t *testing.T) {
164 | tpl := TestCaseTpl{
165 | ident: c.ident,
166 | structName: c.ident.Name,
167 | }
168 | require.Equal(t, c.name, tpl.TestFuncName())
169 | })
170 | }
171 | }
172 |
173 | func basicFixture() Fixture {
174 | return Fixture{
175 | Root: FixtureFile{Contents: []byte(`{root: '0x44de62c118d7951f5b6d9a03444e54aff47d02ff57add2a4eb2a198b3e83ae35'}`)},
176 | Directory: "tests/mainnet/altair/ssz_static/AggregateAndProof/ssz_random/case_0",
177 | }
178 | }
179 |
180 | func TestCaseFileLayout(t *testing.T) {
181 | t.Skip("Skipping this test since no prysm.yaml file is available")
182 | fs := afero.NewMemMapFs()
183 | fix := basicFixture()
184 | cases := map[TestIdent]Fixture{
185 | TestIdent{Preset: Mainnet, Fork: Altair, Name: "Checkpoint", Offset: 0}: fix,
186 | }
187 | rels := &SpecRelationships{
188 | Package: "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1",
189 | Preset: Mainnet,
190 | Defs: []ForkTypeDefinitions{
191 | {
192 | Fork: Altair,
193 | Types: []TypeRelation{
194 | {
195 | SpecName: "Checkpoint",
196 | },
197 | },
198 | },
199 | },
200 | }
201 | require.NoError(t, WriteSpecTestFiles(cases, rels, fs))
202 | entries, err := afero.ReadDir(fs, fix.Directory)
203 | require.NoError(t, err)
204 | searching := map[string]bool{
205 | rootFilename: true,
206 | serializedFilename: true,
207 | valueFilename: true,
208 | }
209 | for _, f := range entries {
210 | _, n, err := ParsePath(path.Join(fix.Directory, f.Name()))
211 | require.NoError(t, err)
212 | _, ok := searching[n]
213 | if ok {
214 | delete(searching, n)
215 | }
216 | }
217 | require.Equal(t, 0, len(searching))
218 | }
219 |
220 | func TestRenderTestCaseTpl(t *testing.T) {
221 | tpl := TestCaseTpl{
222 | ident: TestIdent{
223 | Preset: Mainnet,
224 | Fork: Altair,
225 | Offset: 0,
226 | },
227 | fixture: basicFixture(),
228 | structName: "AggregateAttestationAndProof",
229 | }
230 | rendered, err := tpl.Render()
231 | require.NoError(t, err)
232 | expected := `func Test_mainnet_altair_AggregateAttestationAndProof_0(t *testing.T) {
233 | fixtureDir := "testdata/tests/mainnet/altair/ssz_static/AggregateAndProof/ssz_random/case_0"
234 | root, serialized, err := specs.RootAndSerializedFromFixture(fixtureDir)
235 | require.NoError(t, err)
236 | v := &AggregateAttestationAndProof{}
237 | require.NoError(t, v.UnmarshalSSZ(serialized))
238 | sroot, err := v.HashTreeRoot()
239 | require.NoError(t, err)
240 | require.Equal(t, root, sroot)
241 | }`
242 | require.Equal(t, expected, rendered)
243 | }
244 |
--------------------------------------------------------------------------------
/specs/ident.go:
--------------------------------------------------------------------------------
1 | package specs
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 |
8 | "github.com/pkg/errors"
9 | )
10 |
11 | type Preset string
12 |
13 | var (
14 | PresetUnknown Preset = ""
15 | Minimal Preset = "minimal"
16 | Mainnet Preset = "mainnet"
17 | )
18 |
19 | var ErrUnknownPreset = errors.New("unknown preset name")
20 |
21 | func (p *Preset) UnmarshalText(t []byte) error {
22 | s := string(t)
23 | sp := stringToPreset(s)
24 | if sp == PresetUnknown {
25 | return errors.Wrap(ErrUnknownPreset, s)
26 | }
27 | *p = sp
28 | return nil
29 | }
30 |
31 | func stringToPreset(s string) Preset {
32 | switch s {
33 | case string(Minimal):
34 | return Minimal
35 | case string(Mainnet):
36 | return Mainnet
37 | default:
38 | return PresetUnknown
39 | }
40 | }
41 |
42 | type TestIdent struct {
43 | Preset Preset `json:"preset"`
44 | Fork Fork `json:"fork"`
45 | Name string `json:"name"`
46 | Offset int `json:"offset"`
47 | }
48 |
49 | func (ti TestIdent) LessThan(other TestIdent) bool {
50 | if ti.Name == other.Name {
51 | return ti.Offset < other.Offset
52 | }
53 | // strings.Compare will return -1 if ti.Name is < other.Name
54 | return strings.Compare(ti.Name, other.Name) == -1
55 | }
56 |
57 | func (ti TestIdent) String() string {
58 | return fmt.Sprintf("%s:%s:%s:%d", ti.Preset, ti.Fork, ti.Name, ti.Offset)
59 | }
60 |
61 | var layout = struct {
62 | testDir int
63 | preset int
64 | fork int
65 | sszStatic int
66 | typeName int
67 | sszRandom int
68 | caseNum int
69 | fileName int
70 | }{
71 | testDir: 0,
72 | preset: 1,
73 | fork: 2,
74 | sszStatic: 3,
75 | typeName: 4,
76 | sszRandom: 5,
77 | caseNum: 6,
78 | fileName: 7,
79 | }
80 |
81 | func (ti TestIdent) Match(other TestIdent) bool {
82 | if other.Preset == PresetUnknown || other.Fork == ForkUnknown || other.Name == "" {
83 | return false
84 | }
85 | if ti.Preset != PresetUnknown && ti.Preset != other.Preset {
86 | return false
87 | }
88 | if ti.Fork != ForkUnknown && ti.Fork != other.Fork {
89 | return false
90 | }
91 | if ti.Name != "" && ti.Name != other.Name {
92 | return false
93 | }
94 | return true
95 | }
96 |
97 | var ErrPathParse = errors.New("spectest path not in expected format, could not parse identifiers")
98 |
99 | var caseOffset = len("case_")
100 |
101 | func ParsePath(p string) (TestIdent, string, error) {
102 | parts := strings.Split(p, "/")
103 | if len(parts) <= layout.fileName || parts[layout.testDir] != "tests" || parts[layout.sszStatic] != "ssz_static" || parts[layout.sszRandom] != "ssz_random" {
104 | return TestIdent{}, "", nil
105 | }
106 | var offset int
107 | if len(parts[layout.caseNum]) > caseOffset {
108 | a, err := strconv.Atoi(parts[layout.caseNum][caseOffset:])
109 | if err != nil {
110 | return TestIdent{}, "", errors.Wrapf(err, "problem parsing case number from path %s", p)
111 | }
112 | offset = a
113 | }
114 | return TestIdent{
115 | Preset: stringToPreset(parts[layout.preset]),
116 | Fork: stringToFork(parts[layout.fork]),
117 | Name: parts[layout.typeName],
118 | Offset: offset,
119 | }, parts[layout.fileName], nil
120 | }
121 |
--------------------------------------------------------------------------------
/specs/ident_test.go:
--------------------------------------------------------------------------------
1 | package specs
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/prysmaticlabs/prysm/v3/testing/require"
9 | )
10 |
11 | func TestParsePath(t *testing.T) {
12 | cases := []struct {
13 | name string
14 | path string
15 | err error
16 | ident TestIdent
17 | fname string
18 | match bool
19 | }{
20 | {
21 | name: "mainnet capella",
22 | match: true,
23 | path: "tests/mainnet/capella/ssz_static/LightClientOptimisticUpdate/ssz_random/case_0/roots.yaml",
24 | ident: TestIdent{
25 | Preset: Mainnet,
26 | Fork: Capella,
27 | },
28 | fname: "roots.yaml",
29 | },
30 | }
31 | for _, c := range cases {
32 | t.Run(c.name, func(t *testing.T) {
33 | other, fname, err := ParsePath(c.path)
34 | if c.err == nil {
35 | require.NoError(t, err)
36 | }
37 | require.Equal(t, c.match, c.ident.Match(other))
38 | require.Equal(t, c.fname, fname)
39 | })
40 | }
41 | }
42 |
43 | func TestUnmarshalIdentFields(t *testing.T) {
44 | cases := []struct {
45 | name string
46 | marshaled string
47 | err error
48 | preset *Preset
49 | fork *Fork
50 | }{
51 | {
52 | name: "unknown fork",
53 | marshaled: `{"fork": "derp"}`,
54 | err: ErrUnknownFork,
55 | },
56 | {
57 | name: "altair",
58 | marshaled: fmt.Sprintf(`{"fork": "%s"}`, Altair),
59 | fork: &Altair,
60 | },
61 | {
62 | name: "phase0",
63 | marshaled: fmt.Sprintf(`{"fork": "%s"}`, Phase0),
64 | fork: &Phase0,
65 | },
66 | {
67 | name: "unknown preset",
68 | marshaled: `{"preset": "derp"}`,
69 | err: ErrUnknownPreset,
70 | },
71 | {
72 | name: "mainnet preset",
73 | marshaled: `{"preset": "mainnet"}`,
74 | preset: &Mainnet,
75 | },
76 | {
77 | name: "minimal preset",
78 | marshaled: `{"preset": "minimal"}`,
79 | preset: &Minimal,
80 | },
81 | }
82 | for _, c := range cases {
83 | t.Run(c.name, func(t *testing.T) {
84 | ti := &TestIdent{}
85 | err := json.Unmarshal([]byte(c.marshaled), ti)
86 | if c.err == nil {
87 | require.NoError(t, err)
88 | } else {
89 | require.ErrorIs(t, err, c.err)
90 | }
91 | if c.fork != nil {
92 | require.Equal(t, *c.fork, ti.Fork)
93 | }
94 | if c.preset != nil {
95 | require.Equal(t, *c.preset, ti.Preset)
96 | }
97 | })
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/specs/template.go:
--------------------------------------------------------------------------------
1 | package specs
2 |
3 | import "text/template"
4 |
5 | var testCaseTemplateBytes = `func {{.TestFuncName}}(t *testing.T) {
6 | fixtureDir := "{{.FixtureDirectory}}"
7 | root, serialized, err := specs.RootAndSerializedFromFixture(fixtureDir)
8 | require.NoError(t, err)
9 | v := &{{.GoTypeName}}{}
10 | require.NoError(t, v.UnmarshalSSZ(serialized))
11 | sroot, err := v.HashTreeRoot()
12 | require.NoError(t, err)
13 | require.Equal(t, root, sroot)
14 | }`
15 |
16 | var testCaseTemplateImports = `import (
17 | "testing"
18 |
19 | "github.com/OffchainLabs/methodical-ssz/specs"
20 | "github.com/prysmaticlabs/prysm/v3/testing/require"
21 | )`
22 |
23 | var testFuncBodyTpl *template.Template
24 |
25 | func init() {
26 | testFuncBodyTpl = template.Must(template.New("testFuncBodyTpl").Parse(testCaseTemplateBytes))
27 | }
28 |
--------------------------------------------------------------------------------
/specs/testdata/prysm.yaml:
--------------------------------------------------------------------------------
1 | package: github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1
2 | preset: mainnet
3 | defs:
4 | - fork: phase0
5 | types:
6 | - name: AggregateAndProof
7 | type_name: AggregateAttestationAndProof
8 | - name: Attestation
9 | - name: AttestationData
10 | - name: AttesterSlashing
11 | - name: BeaconBlock
12 | - name: BeaconBlockBody
13 | - name: BeaconBlockHeader
14 | - name: BeaconState
15 | - name: Checkpoint
16 | - name: Deposit
17 | - name: DepositData
18 | type_name: Deposit_Data
19 | - name: DepositMessage
20 | - name: Eth1Data
21 | - name: Fork
22 | - name: ForkData
23 | - name: HistoricalBatch
24 | - name: IndexedAttestation
25 | - name: PendingAttestation
26 | - name: ProposerSlashing
27 | - name: SignedAggregateAndProof
28 | type_name: SignedAggregateAttestationAndProof
29 | - name: SignedBeaconBlock
30 | - name: SignedBeaconBlockHeader
31 | - name: SignedVoluntaryExit
32 | - name: SigningData
33 | - name: Validator
34 | - name: VoluntaryExit
35 | - fork: altair
36 | types:
37 | - name: BeaconBlock
38 | type_name: BeaconBlockAltair
39 | - name: BeaconBlockBody
40 | type_name: BeaconBlockBodyAltair
41 | - name: BeaconState
42 | type_name: BeaconStateAltair
43 | - name: ContributionAndProof
44 | - name: SignedBeaconBlock
45 | type_name: SignedBeaconBlockAltair
46 | - name: SignedContributionAndProof
47 | - name: SyncAggregate
48 | - name: SyncAggregatorSelectionData
49 | - name: SyncCommittee
50 | - name: SyncCommitteeContribution
51 | - name: SyncCommitteeMessage
52 | - fork: bellatrix
53 | types:
54 | - name: BeaconBlock
55 | type_name: BeaconBlockBellatrix
56 | - name: BeaconBlockBody
57 | type_name: BeaconBlockBodyBellatrix
58 | - name: BeaconState
59 | type_name: BeaconStateBellatrix
60 | - name: PowBlock
61 | - name: SignedBeaconBlock
62 | type_name: SignedBeaconBlockBellatrix
63 | - fork: capella
64 | types:
65 | - name: BeaconBlock
66 | type_name: BeaconBlockCapella
67 | - name: BeaconBlockBody
68 | type_name: BeaconBlockBodyCapella
69 | - name: BeaconState
70 | type_name: BeaconStateCapella
71 | - name: SignedBeaconBlock
72 | type_name: SignedBeaconBlockCapella
73 | - name: BLSToExecutionChange
74 | - name: SignedBLSToExecutionChange
75 | - name: HistoricalSummary
76 |
--------------------------------------------------------------------------------
/specs/tests.go:
--------------------------------------------------------------------------------
1 | package specs
2 |
3 | import (
4 | "archive/tar"
5 | "compress/gzip"
6 | "io"
7 | "os"
8 | "path"
9 |
10 | "github.com/ethereum/go-ethereum/common/hexutil"
11 | "github.com/golang/snappy"
12 | "github.com/pkg/errors"
13 | "github.com/spf13/afero"
14 | "gopkg.in/yaml.v3"
15 | )
16 |
17 | type Fixture struct {
18 | Directory string
19 | Root FixtureFile
20 | Serialized FixtureFile
21 | Yaml FixtureFile
22 | }
23 |
24 | type FixtureFile struct {
25 | Contents []byte
26 | FileMode os.FileMode
27 | }
28 |
29 | func (f *Fixture) writeRoot(fs afero.Fs) error {
30 | return afero.WriteFile(fs, path.Join(f.Directory, rootFilename), f.Root.Contents, f.Root.FileMode)
31 | }
32 |
33 | var (
34 | rootFilename = "roots.yaml"
35 | serializedFilename = "serialized.ssz_snappy"
36 | valueFilename = "value.yaml"
37 | )
38 |
39 | func IdentFilter(ident TestIdent) func([]TestIdent) []TestIdent {
40 | return func(maybe []TestIdent) []TestIdent {
41 | matches := make([]TestIdent, 0)
42 | for _, m := range maybe {
43 | if ident.Match(m) {
44 | matches = append(matches, m)
45 | }
46 | }
47 | return matches
48 | }
49 | }
50 |
51 | func GroupByFork(cases map[TestIdent]Fixture) map[Fork][]TestIdent {
52 | m := make(map[Fork][]TestIdent)
53 | for id := range cases {
54 | switch len(m[id.Fork]) {
55 | case 0:
56 | m[id.Fork] = []TestIdent{id}
57 | case 1:
58 | m[id.Fork] = append(m[id.Fork], id)
59 | default:
60 | for i, cur := range m[id.Fork] {
61 | if id.LessThan(cur) {
62 | m[id.Fork] = append(m[id.Fork][:i], append([]TestIdent{id}, m[id.Fork][i:]...)...)
63 | break
64 | }
65 | }
66 | }
67 | }
68 | return m
69 | }
70 |
71 | func GroupByType(ti []TestIdent) map[string][]TestIdent {
72 | m := make(map[string][]TestIdent)
73 | for _, t := range ti {
74 | m[t.Name] = append(m[t.Name], t)
75 | }
76 | return m
77 | }
78 |
79 | func ExtractCases(tgz io.Reader, filter TestIdent) (map[TestIdent]Fixture, error) {
80 | cases := make(map[TestIdent]Fixture)
81 | uncompressed, err := gzip.NewReader(tgz)
82 | if err != nil {
83 | return nil, errors.Wrap(err, "unable to read gzip-compressed stream")
84 | }
85 | tr := tar.NewReader(uncompressed)
86 | for {
87 | header, err := tr.Next()
88 | if err == io.EOF {
89 | break
90 | }
91 | if err != nil {
92 | return nil, errors.Wrap(err, "failed to read file header from spectest tarball")
93 | }
94 | ident, fname, err := ParsePath(header.Name)
95 | if err != nil {
96 | return nil, err
97 | }
98 | if !filter.Match(ident) {
99 | continue
100 | }
101 | c, ok := cases[ident]
102 | if !ok {
103 | c = Fixture{Directory: path.Dir(header.Name)}
104 | }
105 | f, err := io.ReadAll(tr)
106 | if err != nil {
107 | return nil, errors.Wrapf(err, "error reading %s from spectest tarball", header.Name)
108 | }
109 | switch fname {
110 | case rootFilename:
111 | c.Root = FixtureFile{Contents: f, FileMode: os.FileMode(header.Mode)}
112 | case serializedFilename:
113 | c.Serialized = FixtureFile{Contents: f, FileMode: os.FileMode(header.Mode)}
114 | case valueFilename:
115 | c.Yaml = FixtureFile{Contents: f, FileMode: os.FileMode(header.Mode)}
116 | }
117 | cases[ident] = c
118 | }
119 | return cases, nil
120 | }
121 |
122 | func DecodeRootFile(f []byte) ([32]byte, error) {
123 | root := [32]byte{}
124 | ry := &struct {
125 | Root string `json:"root"`
126 | }{}
127 | if err := yaml.Unmarshal(f, ry); err != nil {
128 | return root, err
129 | }
130 | br, err := hexutil.Decode(ry.Root)
131 | if err != nil {
132 | return root, err
133 | }
134 | copy(root[:], br)
135 | return root, nil
136 | }
137 |
138 | func RootAndSerializedFromFixture(dir string) ([32]byte, []byte, error) {
139 | rpath := path.Join(dir, rootFilename)
140 | rootBytes, err := os.ReadFile(rpath)
141 | if err != nil {
142 | return [32]byte{}, nil, errors.Wrapf(err, "error reading expected root fixture file %s", rpath)
143 | }
144 | root, err := DecodeRootFile(rootBytes)
145 | if err != nil {
146 | return [32]byte{}, nil, errors.Wrapf(err, "error decoding expected root fixture file %s, hex contents=%#x", rpath, rootBytes)
147 | }
148 |
149 | spath := path.Join(dir, serializedFilename)
150 | snappySer, err := os.ReadFile(spath)
151 | if err != nil {
152 | return [32]byte{}, nil, errors.Wrapf(err, "error reading serialized fixture file %s", spath)
153 | }
154 | serialized, err := snappy.Decode(nil, snappySer)
155 | if err != nil {
156 | return [32]byte{}, nil, errors.Wrapf(err, "error snappy decoding serialized fixture file %s", spath)
157 | }
158 |
159 | return root, serialized, nil
160 | }
161 |
--------------------------------------------------------------------------------
/specs/tests_test.go:
--------------------------------------------------------------------------------
1 | package specs
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/ethereum/go-ethereum/common/hexutil"
7 | "github.com/prysmaticlabs/prysm/v3/testing/require"
8 | )
9 |
10 | func TestDecodeRootFile(t *testing.T) {
11 | e, err := hexutil.Decode("0x44de62c118d7951f5b6d9a03444e54aff47d02ff57add2a4eb2a198b3e83ae35")
12 | expected := [32]byte{}
13 | copy(expected[:], e)
14 | require.NoError(t, err)
15 | f := []byte(`{root: '0x44de62c118d7951f5b6d9a03444e54aff47d02ff57add2a4eb2a198b3e83ae35'}`)
16 | r, err := DecodeRootFile(f)
17 | require.NoError(t, err)
18 | require.Equal(t, expected, r)
19 | }
20 |
--------------------------------------------------------------------------------
/sszgen/backend/bool.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
7 | )
8 |
9 | type generateBool struct {
10 | valRep *types.ValueBool
11 | targetPackage string
12 | casterConfig
13 | importNamer *ImportNamer
14 | }
15 |
16 | func (g *generateBool) generateHTRPutter(fieldName string) string {
17 | return fmt.Sprintf("hh.PutBool(%s)", fieldName)
18 | }
19 |
20 | func (g *generateBool) coerce() func(string) string {
21 | return func(fieldName string) string {
22 | return fmt.Sprintf("%s(%s)", g.valRep.TypeName(), fieldName)
23 | }
24 | }
25 |
26 | func (g *generateBool) generateFixedMarshalValue(fieldName string) string {
27 | return fmt.Sprintf("dst = ssz.MarshalBool(dst, %s)", fieldName)
28 | }
29 |
30 | func (g *generateBool) generateUnmarshalValue(fieldName string, offset string) string {
31 | convert := fmt.Sprintf("ssz.UnmarshalBool(%s)", offset)
32 | return fmt.Sprintf("%s = %s", fieldName, g.casterConfig.toOverlay(convert))
33 | }
34 |
35 | func (g *generateBool) variableSizeSSZ(fieldname string) string {
36 | return ""
37 | }
38 |
39 | var _ valueGenerator = &generateBool{}
40 |
--------------------------------------------------------------------------------
/sszgen/backend/byte.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
7 | )
8 |
9 | type generateByte struct {
10 | *types.ValueByte
11 | targetPackage string
12 | importNamer *ImportNamer
13 | }
14 |
15 | func (g *generateByte) generateHTRPutter(fieldName string) string {
16 | return ""
17 | }
18 |
19 | func (g *generateByte) coerce() func(string) string {
20 | return func(fieldName string) string {
21 | return fmt.Sprintf("%s(%s)", g.TypeName(), fieldName)
22 | }
23 | }
24 |
25 | func (g *generateByte) generateFixedMarshalValue(fieldName string) string {
26 | return ""
27 | }
28 |
29 | func (g *generateByte) generateUnmarshalValue(fieldName string, s string) string {
30 | return ""
31 | }
32 |
33 | func (g *generateByte) variableSizeSSZ(fieldname string) string {
34 | return ""
35 | }
36 |
37 | var _ valueGenerator = &generateByte{}
38 |
--------------------------------------------------------------------------------
/sszgen/backend/caster.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | type caster interface {
4 | setToOverlay(func(string) string)
5 | setFromOverlay(func(string) string)
6 | }
7 |
8 | type casterConfig struct {
9 | toOverlayFunc func(string) string
10 | fromOverlayFunc func(string) string
11 | }
12 |
13 | func (c *casterConfig) setToOverlay(castFunc func(string) string) {
14 | c.toOverlayFunc = castFunc
15 | }
16 |
17 | func (c *casterConfig) toOverlay(value string) string {
18 | if c.toOverlayFunc == nil {
19 | return value
20 | }
21 | return c.toOverlayFunc(value)
22 | }
23 |
24 | func (c *casterConfig) setFromOverlay(castFunc func(string) string) {
25 | c.fromOverlayFunc = castFunc
26 | }
27 |
28 | func (c *casterConfig) fromOverlay(value string) string {
29 | if c.fromOverlayFunc == nil {
30 | return value
31 | }
32 | return c.fromOverlayFunc(value)
33 | }
34 |
--------------------------------------------------------------------------------
/sszgen/backend/container.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
7 | )
8 |
9 | const receiverName = "c"
10 |
11 | type generateContainer struct {
12 | *types.ValueContainer
13 | targetPackage string
14 | importNamer *ImportNamer
15 | }
16 |
17 | func (g *generateContainer) generateHTRPutter(fieldName string) string {
18 | tmpl := `if err := %s.HashTreeRootWith(hh); err != nil {
19 | return err
20 | }`
21 | return fmt.Sprintf(tmpl, fieldName)
22 | }
23 |
24 | func (g *generateContainer) variableSizeSSZ(fieldName string) string {
25 | return fmt.Sprintf("%s.SizeSSZ()", fieldName)
26 | }
27 |
28 | func (g *generateContainer) generateUnmarshalValue(fieldName string, sliceName string) string {
29 | t := `if err = %s.UnmarshalSSZ(%s); err != nil {
30 | return err
31 | }`
32 | return fmt.Sprintf(t, fieldName, sliceName)
33 | }
34 |
35 | func (g *generateContainer) generateFixedMarshalValue(fieldName string) string {
36 | if g.IsVariableSized() {
37 | return fmt.Sprintf(`dst = ssz.WriteOffset(dst, offset)
38 | offset += %s.SizeSSZ()`, fieldName)
39 | }
40 | return g.generateDelegateFieldMarshalSSZ(fieldName)
41 | }
42 |
43 | // method that generates code which calls the MarshalSSZ method of the field
44 | func (g *generateContainer) generateDelegateFieldMarshalSSZ(fieldName string) string {
45 | return fmt.Sprintf(`if dst, err = %s.MarshalSSZTo(dst); err != nil {
46 | return nil, err
47 | }`, fieldName)
48 | }
49 |
50 | func (g *generateContainer) generateVariableMarshalValue(fieldName string) string {
51 | return g.generateDelegateFieldMarshalSSZ(fieldName)
52 | }
53 |
54 | func (g *generateContainer) fixedOffset() int {
55 | offset := 0
56 | for _, c := range g.Contents {
57 | offset += c.Value.FixedSize()
58 | }
59 | return offset
60 | }
61 |
62 | func (g *generateContainer) initializeValue() string {
63 | return fmt.Sprintf("new(%s)", fullyQualifiedTypeName(g.ValueContainer, g.targetPackage, g.importNamer))
64 | }
65 |
66 | var _ valueGenerator = &generateContainer{}
67 | var _ valueInitializer = &generateContainer{}
68 | var _ htrPutter = &generateContainer{}
69 |
--------------------------------------------------------------------------------
/sszgen/backend/delegate.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
8 | )
9 |
10 | // "Delegate" meaning a type that defines it's own ssz methodset
11 | type generateDelegate struct {
12 | types.ValRep
13 | targetPackage string
14 | importNamer *ImportNamer
15 | }
16 |
17 | func (g *generateDelegate) generateHTRPutter(fieldName string) string {
18 | fullTmpl := `if err := %s.HashTreeRootWith(hh); err != nil {
19 | return err
20 | }`
21 | lightTmpl := `if hash, err := %s.HashTreeRoot(); err != nil {
22 | return err
23 | } else {
24 | hh.AppendBytes32(hash[:])
25 | }`
26 | if g.SatisfiesInterface(interfaces.SszFullHasher) {
27 | return fmt.Sprintf(fullTmpl, fieldName)
28 | }
29 | return fmt.Sprintf(lightTmpl, fieldName)
30 | }
31 |
32 | func (g *generateDelegate) variableSizeSSZ(fieldName string) string {
33 | return fmt.Sprintf("%s.SizeSSZ()", fieldName)
34 | }
35 |
36 | func (g *generateDelegate) generateUnmarshalValue(fieldName string, sliceName string) string {
37 | t := `if err = %s.UnmarshalSSZ(%s); err != nil {
38 | return err
39 | }`
40 | return fmt.Sprintf(t, fieldName, sliceName)
41 | }
42 |
43 | func (g *generateDelegate) generateFixedMarshalValue(fieldName string) string {
44 | if g.IsVariableSized() {
45 | return fmt.Sprintf(`dst = ssz.WriteOffset(dst, offset)
46 | offset += %s.SizeSSZ()`, fieldName)
47 | }
48 | return g.generateDelegateFieldMarshalSSZ(fieldName)
49 | }
50 |
51 | // method that generates code which calls the MarshalSSZ method of the field
52 | func (g *generateDelegate) generateDelegateFieldMarshalSSZ(fieldName string) string {
53 | return fmt.Sprintf(`if dst, err = %s.MarshalSSZTo(dst); err != nil {
54 | return nil, err
55 | }`, fieldName)
56 | }
57 |
58 | func (g *generateDelegate) generateVariableMarshalValue(fieldName string) string {
59 | return g.generateDelegateFieldMarshalSSZ(fieldName)
60 | }
61 |
62 | func (g *generateDelegate) initializeValue() string {
63 | switch vt := g.ValRep.(type) {
64 | case *types.ValuePointer:
65 | return fmt.Sprintf("new(%s)", fullyQualifiedTypeName(vt.Referent, g.targetPackage, g.importNamer))
66 | default:
67 | return ""
68 | }
69 | }
70 |
71 | var _ valueGenerator = &generateDelegate{}
72 | var _ htrPutter = &generateDelegate{}
73 | var _ valueInitializer = &generateDelegate{}
74 |
--------------------------------------------------------------------------------
/sszgen/backend/genhtr.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strings"
7 | "text/template"
8 |
9 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
10 | )
11 |
12 | // ChunkSize is used to check if packed bytes align to the chunk sized used by the
13 | // merkleization algorithm. If not, the bytes should be zero-padded to the
14 | // nearest multiple of ChunkSize.
15 | const ChunkSize = 32
16 |
17 | var htrTmpl = `func ({{.Receiver}} {{.Type}}) HashTreeRoot() ([32]byte, error) {
18 | hh := ssz.DefaultHasherPool.Get()
19 | if err := {{.Receiver}}.HashTreeRootWith(hh); err != nil {
20 | ssz.DefaultHasherPool.Put(hh)
21 | return [32]byte{}, err
22 | }
23 | root, err := hh.HashRoot()
24 | ssz.DefaultHasherPool.Put(hh)
25 | return root, err
26 | }
27 |
28 | func ({{.Receiver}} {{.Type}}) HashTreeRootWith(hh *ssz.Hasher) (err error) {
29 | indx := hh.Index()
30 | {{.HTRSteps}}
31 | hh.Merkleize(indx)
32 | return nil
33 | }`
34 |
35 | func GenerateHashTreeRoot(g *generateContainer) (*generatedCode, error) {
36 | htrTmpl, err := template.New("GenerateHashTreeRoot").Parse(htrTmpl)
37 | if err != nil {
38 | return nil, err
39 | }
40 | buf := bytes.NewBuffer(nil)
41 | htrSteps := make([]string, 0)
42 | for i, c := range g.Contents {
43 | fieldName := fmt.Sprintf("%s.%s", receiverName, c.Key)
44 | htrSteps = append(htrSteps, fmt.Sprintf("\t// Field %d: %s", i, c.Key))
45 | vg := newValueGenerator(interfaces.SszLightHasher, c.Value, g.targetPackage, g.importNamer)
46 | htrp, ok := vg.(htrPutter)
47 | if !ok {
48 | continue
49 | }
50 | htrSteps = append(htrSteps, htrp.generateHTRPutter(fieldName))
51 | }
52 | err = htrTmpl.Execute(buf, struct {
53 | Receiver string
54 | Type string
55 | HTRSteps string
56 | }{
57 | Receiver: receiverName,
58 | Type: fmt.Sprintf("*%s", g.TypeName()),
59 | HTRSteps: strings.Join(htrSteps, "\n"),
60 | })
61 | if err != nil {
62 | return nil, err
63 | }
64 | // TODO: allow GenerateHashTreeRoot to return an error since template.Execute
65 | // can technically return an error (get rid of the panics)
66 | return &generatedCode{
67 | blocks: []string{buf.String()},
68 | }, nil
69 | }
70 |
--------------------------------------------------------------------------------
/sszgen/backend/genhtr_test.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
8 | "github.com/prysmaticlabs/prysm/v3/testing/require"
9 | )
10 |
11 | // cases left to satisfy:
12 | // list-vector-byte
13 | func TestGenerateHashTreeRoot(t *testing.T) {
14 | t.Skip("fixtures need to be updated")
15 | b, err := os.ReadFile("testdata/TestGenerateHashTreeRoot.expected")
16 | require.NoError(t, err)
17 | expected := string(b)
18 |
19 | vc, ok := testFixBeaconState.(*types.ValueContainer)
20 | require.Equal(t, true, ok)
21 | gc := &generateContainer{ValueContainer: vc, targetPackage: ""}
22 | code, err := GenerateHashTreeRoot(gc)
23 | require.NoError(t, err)
24 | require.Equal(t, 4, len(code.imports))
25 | actual, err := normalizeFixtureString(code.blocks[0])
26 | require.NoError(t, err)
27 | require.Equal(t, expected, actual)
28 | }
29 |
30 | func TestHTROverlayCoerce(t *testing.T) {
31 | pkg := "derp"
32 | expected := "hh.PutUint64(uint64(b.Slot))"
33 | val := &types.ValueOverlay{
34 | Name: "",
35 | Package: pkg,
36 | Underlying: &types.ValueUint{
37 | Name: "uint64",
38 | Size: 64,
39 | Package: pkg,
40 | },
41 | }
42 | gv := &generateOverlay{ValueOverlay: val, targetPackage: pkg}
43 | actual := gv.generateHTRPutter("b.Slot")
44 | require.Equal(t, expected, actual)
45 | }
46 |
47 | func TestHTRContainer(t *testing.T) {
48 | t.Skip("fixtures need to be updated")
49 | pkg := "derp"
50 | expected := `if err := b.Fork.HashTreeRootWith(hh); err != nil {
51 | return err
52 | }`
53 | val := &types.ValueContainer{}
54 | gv := &generateContainer{ValueContainer: val, targetPackage: pkg}
55 | actual := gv.generateHTRPutter("b.Fork")
56 | require.Equal(t, expected, actual)
57 | }
58 |
59 | func TestHTRByteVector(t *testing.T) {
60 | t.Skip("fixtures need to be updated")
61 | pkg := "derp"
62 | fieldName := "c.GenesisValidatorsRoot"
63 | expected := `{
64 | if len(c.GenesisValidatorsRoot) != 32 {
65 | return ssz.ErrVectorLength
66 | }
67 | hh.PutBytes(c.GenesisValidatorsRoot)
68 | }`
69 | val := &types.ValueVector{
70 | ElementValue: &types.ValueByte{},
71 | Size: 32,
72 | }
73 | gv := &generateVector{
74 | valRep: val,
75 | targetPackage: pkg,
76 | }
77 | actual := gv.generateHTRPutter(fieldName)
78 | require.Equal(t, expected, actual)
79 | }
80 |
--------------------------------------------------------------------------------
/sszgen/backend/genmarshal.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strings"
7 | "text/template"
8 |
9 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
10 | )
11 |
12 | var marshalBodyTmpl = `func ({{.Receiver}} {{.Type}}) MarshalSSZ() ([]byte, error) {
13 | buf := make([]byte, {{.Receiver}}.SizeSSZ())
14 | return {{.Receiver}}.MarshalSSZTo(buf[:0])
15 | }
16 |
17 | func ({{.Receiver}} {{.Type}}) MarshalSSZTo(dst []byte) ([]byte, error) {
18 | var err error
19 | {{- .OffsetDeclaration -}}
20 | {{- .ValueMarshaling }}
21 | {{- .VariableValueMarshaling }}
22 | return dst, err
23 | }`
24 |
25 | func GenerateMarshalSSZ(g *generateContainer) (*generatedCode, error) {
26 | sizeTmpl, err := template.New("GenerateMarshalSSZ").Parse(marshalBodyTmpl)
27 | if err != nil {
28 | return nil, err
29 | }
30 | buf := bytes.NewBuffer(nil)
31 |
32 | marshalValueBlocks := make([]string, 0)
33 | marshalVariableValueBlocks := make([]string, 0)
34 | offset := 0
35 | for i, c := range g.Contents {
36 | // only lists need the offset variable
37 | mg := newValueGenerator(interfaces.SszMarshaler, c.Value, g.targetPackage, g.importNamer)
38 | fieldName := fmt.Sprintf("%s.%s", receiverName, c.Key)
39 | marshalValueBlocks = append(marshalValueBlocks, fmt.Sprintf("\n\t// Field %d: %s", i, c.Key))
40 | vi, ok := mg.(valueInitializer)
41 | if ok {
42 | ini := vi.initializeValue()
43 | if ini != "" {
44 | marshalValueBlocks = append(marshalValueBlocks, fmt.Sprintf("if %s == nil {\n\t%s = %s\n}", fieldName, fieldName, ini))
45 | }
46 | }
47 |
48 | mv := mg.generateFixedMarshalValue(fieldName)
49 | marshalValueBlocks = append(marshalValueBlocks, "\t"+mv)
50 | offset += c.Value.FixedSize()
51 | if !c.Value.IsVariableSized() {
52 | continue
53 | }
54 | vm, ok := mg.(variableMarshaller)
55 | if !ok {
56 | continue
57 | }
58 | vmc := vm.generateVariableMarshalValue(fieldName)
59 | if vmc != "" {
60 | marshalVariableValueBlocks = append(marshalVariableValueBlocks, fmt.Sprintf("\n\t// Field %d: %s", i, c.Key))
61 | marshalVariableValueBlocks = append(marshalVariableValueBlocks, "\t"+vmc)
62 | }
63 | }
64 | // only set the offset declaration if we need it
65 | // otherwise we'll have an unused variable (syntax error)
66 | offsetDeclaration := ""
67 | if g.IsVariableSized() {
68 | // if there are any variable sized values in the container, we'll need to set this offset declaration
69 | // so it gets rendered to the top of the marshal method
70 | offsetDeclaration = fmt.Sprintf("\noffset := %d\n", offset)
71 | }
72 |
73 | err = sizeTmpl.Execute(buf, struct {
74 | Receiver string
75 | Type string
76 | OffsetDeclaration string
77 | ValueMarshaling string
78 | VariableValueMarshaling string
79 | }{
80 | Receiver: receiverName,
81 | Type: fmt.Sprintf("*%s", g.TypeName()),
82 | OffsetDeclaration: offsetDeclaration,
83 | ValueMarshaling: "\n" + strings.Join(marshalValueBlocks, "\n"),
84 | VariableValueMarshaling: "\n" + strings.Join(marshalVariableValueBlocks, "\n"),
85 | })
86 | if err != nil {
87 | return nil, err
88 | }
89 | return &generatedCode{
90 | blocks: []string{buf.String()},
91 | }, nil
92 | }
93 |
--------------------------------------------------------------------------------
/sszgen/backend/genmarshal_test.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
8 | "github.com/prysmaticlabs/prysm/v3/testing/require"
9 | )
10 |
11 | func TestGenerateMarshalSSZ(t *testing.T) {
12 | b, err := os.ReadFile("testdata/TestGenerateMarshalSSZ.expected")
13 | require.NoError(t, err)
14 | expected := string(b)
15 |
16 | vc, ok := testFixBeaconState.(*types.ValueContainer)
17 | require.Equal(t, true, ok)
18 | inm := NewImportNamer("", nil)
19 | gc := &generateContainer{ValueContainer: vc, targetPackage: "", importNamer: inm}
20 | code, err := GenerateMarshalSSZ(gc)
21 | require.NoError(t, err)
22 | require.Equal(t, 2, len(inm.aliases))
23 | actual, err := normalizeFixtureString(code.blocks[0])
24 | require.NoError(t, err)
25 | require.Equal(t, expected, actual)
26 | }
27 |
--------------------------------------------------------------------------------
/sszgen/backend/gensize.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strings"
7 | "text/template"
8 |
9 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
10 | )
11 |
12 | var sizeBodyTmpl = `func ({{.Receiver}} {{.Type}}) SizeSSZ() (int) {
13 | size := {{.FixedSize}}
14 | {{- .VariableSize }}
15 | return size
16 | }`
17 |
18 | func GenerateSizeSSZ(g *generateContainer) (*generatedCode, error) {
19 | sizeTmpl, err := template.New("GenerateSizeSSZ").Parse(sizeBodyTmpl)
20 | if err != nil {
21 | return nil, err
22 | }
23 | buf := bytes.NewBuffer(nil)
24 |
25 | fixedSize := 0
26 | variableComputations := make([]string, 0)
27 | for _, c := range g.Contents {
28 | vg := newValueGenerator(interfaces.SszMarshaler, c.Value, g.targetPackage, g.importNamer)
29 | fixedSize += c.Value.FixedSize()
30 | if !c.Value.IsVariableSized() {
31 | continue
32 | }
33 | fieldName := fmt.Sprintf("%s.%s", receiverName, c.Key)
34 | vi, ok := vg.(valueInitializer)
35 | if ok {
36 | ini := vi.initializeValue()
37 | if ini != "" {
38 | variableComputations = append(variableComputations, fmt.Sprintf("if %s == nil {\n\t%s = %s\n}", fieldName, fieldName, ini))
39 | }
40 | }
41 | cv := vg.variableSizeSSZ(fieldName)
42 | if cv != "" {
43 | variableComputations = append(variableComputations, fmt.Sprintf("\tsize += %s", cv))
44 | }
45 | }
46 |
47 | err = sizeTmpl.Execute(buf, struct {
48 | Receiver string
49 | Type string
50 | FixedSize int
51 | VariableSize string
52 | }{
53 | Receiver: receiverName,
54 | Type: fmt.Sprintf("*%s", g.TypeName()),
55 | FixedSize: fixedSize,
56 | VariableSize: "\n" + strings.Join(variableComputations, "\n"),
57 | })
58 | if err != nil {
59 | return nil, err
60 | }
61 | return &generatedCode{
62 | blocks: []string{buf.String()},
63 | }, nil
64 | }
65 |
--------------------------------------------------------------------------------
/sszgen/backend/gensize_test.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
8 | "github.com/prysmaticlabs/prysm/v3/testing/require"
9 | )
10 |
11 | func TestGenerateSizeSSZ(t *testing.T) {
12 | b, err := os.ReadFile("testdata/TestGenerateSizeSSZ.expected")
13 | require.NoError(t, err)
14 | expected := string(b)
15 |
16 | ty, ok := testFixBeaconState.(*types.ValueContainer)
17 | require.Equal(t, true, ok)
18 | inm := NewImportNamer("", nil)
19 | gc, err := GenerateSizeSSZ(&generateContainer{ValueContainer: ty, targetPackage: "", importNamer: inm})
20 | require.NoError(t, err)
21 | // the size code for BeaconState is all fixed values and calls to values inside loops, so it can safely assume nothing needs
22 | // to be initialized.
23 | // TODO: Add a test case for size code for a type like BeaconBlockBodyBellatrix that needs to init for safety
24 | // (ie actually requires imports)
25 | require.Equal(t, 0, len(gc.imports))
26 | actual, err := normalizeFixtureString(gc.blocks[0])
27 | require.NoError(t, err)
28 | require.Equal(t, expected, actual)
29 | }
30 |
--------------------------------------------------------------------------------
/sszgen/backend/genunmarshal.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strings"
7 | "text/template"
8 |
9 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
10 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
11 | )
12 |
13 | var generateUnmarshalSSZTmpl = `func ({{.Receiver}} {{.Type}}) UnmarshalSSZ(buf []byte) error {
14 | var err error
15 | size := uint64(len(buf))
16 | if size {{ .SizeInequality }} {{ .FixedOffset }} {
17 | return ssz.ErrSize
18 | }
19 |
20 | {{ .SliceDeclaration }}
21 | {{ .ValueUnmarshaling }}
22 | return err
23 | }`
24 |
25 | func GenerateUnmarshalSSZ(g *generateContainer) (*generatedCode, error) {
26 | sizeInequality := "!="
27 | if g.IsVariableSized() {
28 | sizeInequality = "<"
29 | }
30 | ums := g.unmarshalSteps()
31 | unmarshalBlocks := make([]string, 0)
32 | for i, c := range g.Contents {
33 | unmarshalBlocks = append(unmarshalBlocks, fmt.Sprintf("\n\t// Field %d: %s", i, c.Key))
34 | mg := newValueGenerator(interfaces.SszUnmarshaler, c.Value, g.targetPackage, g.importNamer)
35 | fieldName := fmt.Sprintf("%s.%s", receiverName, c.Key)
36 |
37 | vi, ok := mg.(valueInitializer)
38 | if ok {
39 | ini := vi.initializeValue()
40 | if ini != "" {
41 | unmarshalBlocks = append(unmarshalBlocks, fmt.Sprintf("%s = %s", fieldName, ini))
42 | }
43 | }
44 |
45 | sliceName := fmt.Sprintf("s%d", i)
46 | mv := mg.generateUnmarshalValue(fieldName, sliceName)
47 | if mv != "" {
48 | //unmarshalBlocks = append(unmarshalBlocks, fmt.Sprintf("\t%s = %s", fieldName, mv))
49 | unmarshalBlocks = append(unmarshalBlocks, mv)
50 | }
51 |
52 | /*
53 | if !c.Value.IsVariableSized() {
54 | continue
55 | }
56 | _, ok := mg.(variableUnmarshaller)
57 | if !ok {
58 | continue
59 | }
60 | vm := mg.(variableUnmarshaller)
61 | vmc := vm.generateVariableUnmarshalValue(fieldName)
62 | if vmc != "" {
63 | unmarshalVariableBlocks = append(unmarshalVariableBlocks, fmt.Sprintf("\n\t// Field %d: %s", i, c.Key))
64 | unmarshalVariableBlocks = append(unmarshalVariableBlocks, "\t" + vmc)
65 | }
66 | */
67 | }
68 |
69 | sliceDeclarations := strings.Join([]string{ums.fixedSlices(), "", ums.variableSlices(g.fixedOffset())}, "\n")
70 | unmTmpl, err := template.New("GenerateUnmarshalSSZTmpl").Parse(generateUnmarshalSSZTmpl)
71 | if err != nil {
72 | return nil, err
73 | }
74 | buf := bytes.NewBuffer(nil)
75 | err = unmTmpl.Execute(buf, struct {
76 | Receiver string
77 | Type string
78 | SizeInequality string
79 | FixedOffset int
80 | SliceDeclaration string
81 | ValueUnmarshaling string
82 | }{
83 | Receiver: receiverName,
84 | Type: fmt.Sprintf("*%s", g.TypeName()),
85 | SizeInequality: sizeInequality,
86 | FixedOffset: g.fixedOffset(),
87 | SliceDeclaration: sliceDeclarations,
88 | ValueUnmarshaling: strings.Join(unmarshalBlocks, "\n"),
89 | })
90 | if err != nil {
91 | return nil, err
92 | }
93 | return &generatedCode{
94 | blocks: []string{buf.String()},
95 | }, nil
96 | }
97 |
98 | type unmarshalStep struct {
99 | valRep types.ValRep
100 | fieldNumber int
101 | fieldName string
102 | beginByte int
103 | endByte int
104 | previousVariable *unmarshalStep
105 | nextVariable *unmarshalStep
106 | }
107 |
108 | type unmarshalStepSlice []*unmarshalStep
109 |
110 | func (us *unmarshalStep) fixedSize() int {
111 | return us.valRep.FixedSize()
112 | }
113 |
114 | func (us *unmarshalStep) variableOffset(outerFixedSize int) string {
115 | o := fmt.Sprintf("v%d := ssz.ReadOffset(buf[%d:%d]) // %s", us.fieldNumber, us.beginByte, us.endByte, us.fieldName)
116 | if us.previousVariable == nil {
117 | o += fmt.Sprintf("\nif v%d < %d {\n\treturn ssz.ErrInvalidVariableOffset\n}", us.fieldNumber, outerFixedSize)
118 | o += fmt.Sprintf("\nif v%d > size {\n\treturn ssz.ErrOffset\n}", us.fieldNumber)
119 | } else {
120 | o += fmt.Sprintf("\nif v%d > size || v%d < v%d {\n\treturn ssz.ErrOffset\n}", us.fieldNumber, us.fieldNumber, us.previousVariable.fieldNumber)
121 | }
122 | return o
123 | }
124 |
125 | func (us *unmarshalStep) slice() string {
126 | if us.valRep.IsVariableSized() {
127 | if us.nextVariable == nil {
128 | return fmt.Sprintf("s%d := buf[v%d:]\t\t// %s", us.fieldNumber, us.fieldNumber, us.fieldName)
129 | }
130 | return fmt.Sprintf("s%d := buf[v%d:v%d]\t\t// %s", us.fieldNumber, us.fieldNumber, us.nextVariable.fieldNumber, us.fieldName)
131 | }
132 | return fmt.Sprintf("s%d := buf[%d:%d]\t\t// %s", us.fieldNumber, us.beginByte, us.endByte, us.fieldName)
133 | }
134 |
135 | func (steps unmarshalStepSlice) fixedSlices() string {
136 | slices := make([]string, 0)
137 | for _, s := range steps {
138 | if s.valRep.IsVariableSized() {
139 | continue
140 | }
141 | slices = append(slices, s.slice())
142 | }
143 | return strings.Join(slices, "\n")
144 | }
145 |
146 | func (steps unmarshalStepSlice) variableSlices(outerSize int) string {
147 | validate := make([]string, 0)
148 | assign := make([]string, 0)
149 | for _, s := range steps {
150 | if !s.valRep.IsVariableSized() {
151 | continue
152 | }
153 | validate = append(validate, s.variableOffset(outerSize))
154 | assign = append(assign, s.slice())
155 | }
156 | return strings.Join(append(validate, assign...), "\n")
157 | }
158 |
159 | func (g *generateContainer) unmarshalSteps() unmarshalStepSlice {
160 | ums := make([]*unmarshalStep, 0)
161 | var begin, end int
162 | var prevVariable *unmarshalStep
163 | for i, c := range g.Contents {
164 | begin = end
165 | end += c.Value.FixedSize()
166 | um := &unmarshalStep{
167 | valRep: c.Value,
168 | fieldNumber: i,
169 | fieldName: fmt.Sprintf("%s.%s", receiverName, c.Key),
170 | beginByte: begin,
171 | endByte: end,
172 | }
173 | if c.Value.IsVariableSized() {
174 | if prevVariable != nil {
175 | um.previousVariable = prevVariable
176 | prevVariable.nextVariable = um
177 | }
178 | prevVariable = um
179 | }
180 |
181 | ums = append(ums, um)
182 | }
183 | return ums
184 | }
185 |
--------------------------------------------------------------------------------
/sszgen/backend/genunmarshal_test.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "os"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
9 | "github.com/prysmaticlabs/prysm/v3/testing/require"
10 | )
11 |
12 | func TestGenerateUnmarshalSSZ(t *testing.T) {
13 | t.Skip("fixtures need to be updated")
14 | b, err := os.ReadFile("testdata/TestGenerateUnmarshalSSZ.expected")
15 | require.NoError(t, err)
16 | expected := string(b)
17 |
18 | vc, ok := testFixBeaconState.(*types.ValueContainer)
19 | require.Equal(t, true, ok)
20 | gc := &generateContainer{ValueContainer: vc, targetPackage: ""}
21 | code, err := GenerateUnmarshalSSZ(gc)
22 | require.NoError(t, err)
23 | require.Equal(t, 4, len(code.imports))
24 | actual, err := normalizeFixtureString(code.blocks[0])
25 | require.NoError(t, err)
26 | require.Equal(t, expected, actual)
27 | }
28 |
29 | func TestUnmarshalSteps(t *testing.T) {
30 | fixturePath := "testdata/TestUnmarshalSteps.expected"
31 | b, err := os.ReadFile(fixturePath)
32 | require.NoError(t, err)
33 | expected, err := normalizeFixtureBytes(b)
34 | require.NoError(t, err)
35 |
36 | vc, ok := testFixBeaconState.(*types.ValueContainer)
37 | require.Equal(t, true, ok)
38 | gc := &generateContainer{ValueContainer: vc, targetPackage: ""}
39 | ums := gc.unmarshalSteps()
40 | require.Equal(t, 21, len(ums))
41 | require.Equal(t, ums[15].nextVariable.fieldNumber, ums[16].fieldNumber)
42 |
43 | gotRaw := strings.Join([]string{ums.fixedSlices(), "", ums.variableSlices(gc.fixedOffset())}, "\n")
44 | actual, err := normalizeFixtureString(gotRaw)
45 | require.NoError(t, err)
46 | require.Equal(t, expected, actual)
47 | }
48 |
--------------------------------------------------------------------------------
/sszgen/backend/imports.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "fmt"
5 | "go/types"
6 | "strings"
7 | )
8 |
9 | type ImportNamer struct {
10 | source string
11 | aliases map[string]string // map from package path -> alias
12 | reverse map[string]string // map from alias -> package path
13 | }
14 |
15 | var pbRuntime = "google.golang.org/protobuf/runtime/protoimpl"
16 | var protoAliases = map[string]string{
17 | pbRuntime: "protoimpl",
18 | "google.golang.org/protobuf/internal/impl": "protoimpl",
19 | }
20 |
21 | func (n *ImportNamer) NameString(p string) string {
22 | if alias, isProto := protoAliases[p]; isProto {
23 | n.reverse[alias] = pbRuntime
24 | n.aliases[pbRuntime] = alias
25 | return alias
26 | }
27 | // no package name for self
28 | if p == n.source {
29 | return ""
30 | }
31 | name, exists := n.aliases[p]
32 | if exists {
33 | return name
34 | }
35 | // build increasingly long path suffixes until a unique one is found
36 | parts := strings.Split(p, "/")
37 | for i := 0; i < len(parts); i++ {
38 | name := strings.Join(parts[len(parts)-1-i:], "_")
39 | // deal with domain portion of path for extreme case where 2 packages only differ in domain
40 | name = strings.ReplaceAll(name, ".", "_")
41 | // dashes are valid in package names but not go identifiers - like go-bitfield
42 | name = strings.ReplaceAll(name, "-", "_")
43 | _, conflict := n.reverse[name]
44 | if conflict {
45 | continue
46 | }
47 | n.reverse[name] = p
48 | n.aliases[p] = name
49 | return name
50 | }
51 | panic(fmt.Sprintf("unable to find unique name for package %s", p))
52 | }
53 |
54 | func (n *ImportNamer) Name(p *types.Package) string {
55 | return n.NameString(p.Path())
56 | }
57 |
58 | func (n *ImportNamer) ImportSource() string {
59 | imports := make([]string, 0)
60 | for pkg, alias := range n.aliases {
61 | if alias == "google.golang.org/protobuf/internal/impl" {
62 | imports = append(imports, fmt.Sprintf("%s \"google.golang.org/protobuf/runtime/protoimpl\"", alias))
63 | } else {
64 | imports = append(imports, fmt.Sprintf("%s \"%s\"", alias, pkg))
65 | }
66 | }
67 |
68 | return fmt.Sprintf("import (\n%s\n)\n", strings.Join(imports, "\n"))
69 | }
70 |
71 | func (n *ImportNamer) ImportPairs() string {
72 | imports := make([]string, 0)
73 | stdImports := make([]string, 0)
74 | for alias, pkg := range n.reverse {
75 | if pkg == "" {
76 | stdImports = append(stdImports, fmt.Sprintf("\"%s\"", alias))
77 | } else {
78 | imports = append(imports, fmt.Sprintf("%s \"%s\"", alias, pkg))
79 | }
80 | }
81 |
82 | if len(stdImports) > 0 {
83 | return strings.Join(stdImports, "\n") + "\n\n" + strings.Join(imports, "\n")
84 | }
85 | return strings.Join(imports, "\n")
86 | }
87 |
88 | func NewImportNamer(source string, defaults map[string]string) *ImportNamer {
89 | aliases := make(map[string]string)
90 | reverse := make(map[string]string)
91 | for pkg, alias := range defaults {
92 | if pkg != "" {
93 | aliases[pkg] = alias
94 | }
95 | reverse[alias] = pkg
96 | }
97 | return &ImportNamer{
98 | source: source,
99 | aliases: aliases,
100 | reverse: reverse,
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/sszgen/backend/list.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "text/template"
7 |
8 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
9 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
10 | )
11 |
12 | type generateList struct {
13 | valRep *types.ValueList
14 | targetPackage string
15 | casterConfig
16 | importNamer *ImportNamer
17 | }
18 |
19 | var generateListHTRPutterTmpl = `{
20 | if len({{.FieldName}}) > {{.MaxSize}} {
21 | return ssz.ErrListTooBig
22 | }
23 | subIndx := hh.Index()
24 | for _, {{.NestedFieldName}} := range {{.FieldName}} {
25 | {{.AppendCall}}
26 | }
27 | {{- .PadCall}}
28 | {{.Merkleize}}
29 | }`
30 |
31 | type listPutterElements struct {
32 | FieldName string
33 | NestedFieldName string
34 | MaxSize int
35 | AppendCall string
36 | PadCall string
37 | Merkleize string
38 | }
39 |
40 | func renderHtrListPutter(lpe listPutterElements) string {
41 | tmpl, err := template.New("renderHtrListPutter").Parse(generateListHTRPutterTmpl)
42 | if err != nil {
43 | panic(err)
44 | }
45 | buf := bytes.NewBuffer(nil)
46 | err = tmpl.Execute(buf, lpe)
47 | if err != nil {
48 | panic(err)
49 | }
50 | return buf.String()
51 | }
52 |
53 | func (g *generateList) generateHTRPutter(fieldName string) string {
54 | nestedFieldName := "o"
55 | if fieldName[0:1] == "o" && monoCharacter(fieldName) {
56 | nestedFieldName = fieldName + "o"
57 | }
58 |
59 | // resolve pointers and overlays to their underlying types
60 | vr := g.valRep.ElementValue
61 | if vrp, isPointer := vr.(*types.ValuePointer); isPointer {
62 | vr = vrp.Referent
63 | }
64 | if vro, isOverlay := vr.(*types.ValueOverlay); isOverlay {
65 | vr = vro.Underlying
66 | }
67 |
68 | lpe := listPutterElements{
69 | FieldName: fieldName,
70 | NestedFieldName: nestedFieldName,
71 | MaxSize: g.valRep.MaxSize,
72 | }
73 | switch v := vr.(type) {
74 | case *types.ValueByte:
75 | t := `if len(%s) > %d {
76 | return ssz.ErrBytesLength
77 | }
78 | subIndx := hh.Index()
79 | hh.PutBytes(%s)`
80 | putBytes := fmt.Sprintf(t, fieldName, g.valRep.MaxSize, fieldName)
81 | mtmpl := `numItems := uint64(len(%s))
82 | hh.MerkleizeWithMixin(subIndx, numItems, (%d*%d + 31)/32)`
83 | res := "\n{\n" + putBytes + "\n" + fmt.Sprintf(mtmpl, fieldName, g.valRep.MaxSize, v.FixedSize()) + "\n}\n"
84 | return res
85 | case *types.ValueVector:
86 | gv := &generateVector{valRep: v, targetPackage: g.targetPackage}
87 | if gv.isByteVector() {
88 | lpe.AppendCall = gv.renderByteSliceAppend(nestedFieldName)
89 | mtmpl := `numItems := uint64(len(%s))
90 | hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(%d, numItems, %d))`
91 | lpe.Merkleize = fmt.Sprintf(mtmpl, fieldName, g.valRep.MaxSize, v.FixedSize())
92 | return renderHtrListPutter(lpe)
93 | }
94 | case *types.ValueUint:
95 | lpe.AppendCall = fmt.Sprintf("hh.AppendUint%d(%s)", v.Size, nestedFieldName)
96 | if v.FixedSize()%ChunkSize != 0 {
97 | lpe.PadCall = "\nhh.FillUpTo32()"
98 | }
99 | mtmpl := `numItems := uint64(len(%s))
100 | hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(%d, numItems, %d))`
101 | lpe.Merkleize = fmt.Sprintf(mtmpl, fieldName, g.valRep.MaxSize, v.FixedSize())
102 | return renderHtrListPutter(lpe)
103 | case *types.ValueContainer:
104 | gc := newValueGenerator(interfaces.SszLightHasher, v, g.targetPackage, g.importNamer)
105 | lpe.AppendCall = gc.generateHTRPutter(nestedFieldName)
106 | lpe.Merkleize = fmt.Sprintf("hh.MerkleizeWithMixin(subIndx, uint64(len(%s)), %d)", fieldName, g.valRep.MaxSize)
107 | return renderHtrListPutter(lpe)
108 | default:
109 | panic(fmt.Sprintf("unsupported type combination - list of %v", v))
110 | }
111 | return ""
112 | }
113 |
114 | var generateListGenerateUnmarshalValueFixedTmpl = `{
115 | if len({{.SliceName}}) % {{.ElementSize}} != 0 {
116 | return fmt.Errorf("misaligned bytes: {{.FieldName}} length is %d, which is not a multiple of {{.ElementSize}}", len({{.SliceName}}))
117 | }
118 | numElem := len({{.SliceName}}) / {{.ElementSize}}
119 | if numElem > {{ .MaxSize }} {
120 | return fmt.Errorf("ssz-max exceeded: {{.FieldName}} has %d elements, ssz-max is {{.MaxSize}}", numElem)
121 | }
122 | {{.FieldName}} = make([]{{.TypeName}}, numElem)
123 | for {{.LoopVar}} := 0; {{.LoopVar}} < numElem; {{.LoopVar}}++ {
124 | var tmp {{.TypeName}}
125 | {{.Initializer}}
126 | tmpSlice := {{.SliceName}}[{{.LoopVar}}*{{.NestedFixedSize}}:(1+{{.LoopVar}})*{{.NestedFixedSize}}]
127 | {{.NestedUnmarshal}}
128 | {{.FieldName}}[{{.LoopVar}}] = tmp
129 | }
130 | }`
131 |
132 | var generateListGenerateUnmarshalValueVariableTmpl = `{
133 | // empty lists are zero length, so make sure there is room for an offset
134 | // before attempting to unmarshal it
135 | if len({{.SliceName}}) > 3 {
136 | firstOffset := ssz.ReadOffset({{.SliceName}}[0:4])
137 | if firstOffset % 4 != 0 {
138 | return fmt.Errorf("misaligned list bytes: when decoding {{.FieldName}}, end-of-list offset is %d, which is not a multiple of 4 (offset size)", firstOffset)
139 | }
140 | listLen := firstOffset / 4
141 | if listLen > {{.MaxSize}} {
142 | return fmt.Errorf("ssz-max exceeded: {{.FieldName}} has %d elements, ssz-max is {{.MaxSize}}", listLen)
143 | }
144 | listOffsets := make([]uint64, listLen)
145 | for {{.LoopVar}} := 0; uint64({{.LoopVar}}) < listLen; {{.LoopVar}}++ {
146 | listOffsets[{{.LoopVar}}] = ssz.ReadOffset({{.SliceName}}[{{.LoopVar}}*4:({{.LoopVar}}+1)*4])
147 | }
148 | {{.FieldName}} = make([]{{.TypeName}}, len(listOffsets))
149 | for {{.LoopVar}} := 0; {{.LoopVar}} < len(listOffsets); {{.LoopVar}}++ {
150 | var tmp {{.TypeName}}
151 | {{.Initializer}}
152 | var tmpSlice []byte
153 | if {{.LoopVar}}+1 == len(listOffsets) {
154 | tmpSlice = {{.SliceName}}[listOffsets[{{.LoopVar}}]:]
155 | } else {
156 | tmpSlice = {{.SliceName}}[listOffsets[{{.LoopVar}}]:listOffsets[{{.LoopVar}}+1]]
157 | }
158 | {{.NestedUnmarshal}}
159 | {{.FieldName}}[{{.LoopVar}}] = tmp
160 | }
161 | }
162 | }`
163 |
164 | func (g *generateList) generateUnmarshalVariableValue(fieldName string, sliceName string) string {
165 | loopVar := "i"
166 | if fieldName[0:1] == "i" && monoCharacter(fieldName) {
167 | loopVar = fieldName + "i"
168 | }
169 | gg := newValueGenerator(interfaces.SszUnmarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer)
170 | vi, ok := gg.(valueInitializer)
171 | var initializer string
172 | if ok {
173 | initializer = vi.initializeValue()
174 | if initializer != "" {
175 | initializer = "tmp = " + initializer
176 | }
177 | }
178 | tmpl, err := template.New("generateListGenerateUnmarshalValueVariableTmpl").Parse(generateListGenerateUnmarshalValueVariableTmpl)
179 | if err != nil {
180 | panic(err)
181 | }
182 | buf := bytes.NewBuffer(nil)
183 | err = tmpl.Execute(buf, struct {
184 | LoopVar string
185 | SliceName string
186 | ElementSize int
187 | TypeName string
188 | FieldName string
189 | MaxSize int
190 | Initializer string
191 | NestedFixedSize int
192 | NestedUnmarshal string
193 | }{
194 | LoopVar: loopVar,
195 | SliceName: sliceName,
196 | ElementSize: g.valRep.ElementValue.FixedSize(),
197 | TypeName: fullyQualifiedTypeName(g.valRep.ElementValue, g.targetPackage, g.importNamer),
198 | FieldName: fieldName,
199 | MaxSize: g.valRep.MaxSize,
200 | Initializer: initializer,
201 | NestedFixedSize: g.valRep.ElementValue.FixedSize(),
202 | NestedUnmarshal: gg.generateUnmarshalValue("tmp", "tmpSlice"),
203 | })
204 | if err != nil {
205 | panic(err)
206 | }
207 | return buf.String()
208 | }
209 |
210 | func (g *generateList) generateUnmarshalFixedValue(fieldName string, sliceName string) string {
211 | loopVar := "i"
212 | if fieldName[0:1] == "i" && monoCharacter(fieldName) {
213 | loopVar = fieldName + "i"
214 | }
215 | gg := newValueGenerator(interfaces.SszUnmarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer)
216 | nestedUnmarshal := ""
217 | switch g.valRep.ElementValue.(type) {
218 | case *types.ValueByte:
219 | return fmt.Sprintf("%s = append([]byte{}, %s...)", fieldName, g.casterConfig.toOverlay(sliceName))
220 | default:
221 | nestedUnmarshal = gg.generateUnmarshalValue("tmp", "tmpSlice")
222 | }
223 | vi, ok := gg.(valueInitializer)
224 | var initializer string
225 | if ok {
226 | initializer = vi.initializeValue()
227 | if initializer != "" {
228 | initializer = "tmp = " + initializer
229 | }
230 | }
231 | tmpl, err := template.New("generateListGenerateUnmarshalValueFixedTmpl").Parse(generateListGenerateUnmarshalValueFixedTmpl)
232 | if err != nil {
233 | panic(err)
234 | }
235 | buf := bytes.NewBuffer(nil)
236 | err = tmpl.Execute(buf, struct {
237 | LoopVar string
238 | SliceName string
239 | ElementSize int
240 | TypeName string
241 | FieldName string
242 | MaxSize int
243 | Initializer string
244 | NestedFixedSize int
245 | NestedUnmarshal string
246 | }{
247 | LoopVar: loopVar,
248 | SliceName: sliceName,
249 | ElementSize: g.valRep.ElementValue.FixedSize(),
250 | TypeName: fullyQualifiedTypeName(g.valRep.ElementValue, g.targetPackage, g.importNamer),
251 | FieldName: fieldName,
252 | MaxSize: g.valRep.MaxSize,
253 | Initializer: initializer,
254 | NestedFixedSize: g.valRep.ElementValue.FixedSize(),
255 | NestedUnmarshal: nestedUnmarshal,
256 | })
257 | if err != nil {
258 | panic(err)
259 | }
260 | return buf.String()
261 | }
262 |
263 | func (g *generateList) generateUnmarshalValue(fieldName string, sliceName string) string {
264 | if g.valRep.ElementValue.IsVariableSized() {
265 | return g.generateUnmarshalVariableValue(fieldName, sliceName)
266 | } else {
267 | return g.generateUnmarshalFixedValue(fieldName, sliceName)
268 | }
269 | }
270 |
271 | func (g *generateList) generateFixedMarshalValue(fieldName string) string {
272 | tmpl := `dst = ssz.WriteOffset(dst, offset)
273 | offset += %s
274 | `
275 | offset := g.variableSizeSSZ(fieldName)
276 |
277 | return fmt.Sprintf(tmpl, offset)
278 | }
279 |
280 | var variableSizedListTmpl = `func() int {
281 | s := 0
282 | for _, o := range {{ .FieldName }} {
283 | s += 4
284 | s += {{ .SizeComputation }}
285 | }
286 | return s
287 | }()`
288 |
289 | func (g *generateList) variableSizeSSZ(fieldName string) string {
290 | if !g.valRep.ElementValue.IsVariableSized() {
291 | return fmt.Sprintf("len(%s) * %d", fieldName, g.valRep.ElementValue.FixedSize())
292 | }
293 |
294 | gg := newValueGenerator(interfaces.SszMarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer)
295 | vslTmpl, err := template.New("variableSizedListTmpl").Parse(variableSizedListTmpl)
296 | if err != nil {
297 | panic(err)
298 | }
299 | buf := bytes.NewBuffer(nil)
300 | err = vslTmpl.Execute(buf, struct {
301 | FieldName string
302 | SizeComputation string
303 | }{
304 | FieldName: fieldName,
305 | SizeComputation: gg.variableSizeSSZ("o"),
306 | })
307 | if err != nil {
308 | panic(err)
309 | }
310 | return buf.String()
311 | }
312 |
313 | var generateVariableMarshalValueTmpl = `if len({{ .FieldName }}) > {{ .MaxSize }} {
314 | return nil, ssz.ErrListTooBig
315 | }
316 |
317 | for _, o := range {{ .FieldName }} {
318 | if len(o) != {{ .ElementSize }} {
319 | return nil, ssz.ErrBytesLength
320 | }
321 | dst = append(dst, o)
322 | }`
323 |
324 | var tmplVariableOffsetManagement = `{
325 | offset = 4 * len({{.FieldName}})
326 | for _, {{.NestedFieldName}} := range {{.FieldName}} {
327 | dst = ssz.WriteOffset(dst, offset)
328 | offset += {{.SizeComputation}}
329 | }
330 | }
331 | `
332 |
333 | func variableOffsetManagement(vg valueGenerator, fieldName, nestedFieldName string) string {
334 | vomt, err := template.New("tmplVariableOffsetManagement").Parse(tmplVariableOffsetManagement)
335 | if err != nil {
336 | panic(err)
337 | }
338 | buf := bytes.NewBuffer(nil)
339 | err = vomt.Execute(buf, struct {
340 | FieldName string
341 | NestedFieldName string
342 | SizeComputation string
343 | }{
344 | FieldName: fieldName,
345 | NestedFieldName: nestedFieldName,
346 | SizeComputation: vg.variableSizeSSZ(nestedFieldName),
347 | })
348 | if err != nil {
349 | panic(err)
350 | }
351 | return buf.String()
352 | }
353 |
354 | var tmplGenerateMarshalValueList = `if len({{.FieldName}}) > {{.MaxSize}} {
355 | return nil, ssz.ErrListTooBig
356 | }
357 | {{.OffsetManagement}}{{.MarshalValue}}`
358 |
359 | func (g *generateList) generateVariableMarshalValue(fieldName string) string {
360 | mvTmpl, err := template.New("tmplGenerateMarshalValueList").Parse(tmplGenerateMarshalValueList)
361 | if err != nil {
362 | panic(err)
363 | }
364 | var marshalValue string
365 | var offsetMgmt string
366 | switch g.valRep.ElementValue.(type) {
367 | case *types.ValueByte:
368 | marshalValue = fmt.Sprintf("dst = append(dst, %s...)", fieldName)
369 | default:
370 | nestedFieldName := "o"
371 | if fieldName[0:1] == "o" && monoCharacter(fieldName) {
372 | nestedFieldName = fieldName + "o"
373 | }
374 | t := `for _, %s := range %s {
375 | %s
376 | }`
377 | gg := newValueGenerator(interfaces.SszMarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer)
378 | var internal string
379 | if g.valRep.ElementValue.IsVariableSized() {
380 | vm, ok := gg.(variableMarshaller)
381 | if !ok {
382 | panic(fmt.Sprintf("variable size type does not implement variableMarshaller: %v", g.valRep.ElementValue))
383 | }
384 | internal = vm.generateVariableMarshalValue(nestedFieldName)
385 | offsetMgmt = variableOffsetManagement(gg, fieldName, nestedFieldName)
386 | } else {
387 | internal = gg.generateFixedMarshalValue(nestedFieldName)
388 | }
389 | marshalValue = fmt.Sprintf(t, nestedFieldName, fieldName, internal)
390 | }
391 | buf := bytes.NewBuffer(nil)
392 | err = mvTmpl.Execute(buf, struct {
393 | FieldName string
394 | MaxSize int
395 | MarshalValue string
396 | OffsetManagement string
397 | }{
398 | FieldName: fieldName,
399 | MaxSize: g.valRep.MaxSize,
400 | MarshalValue: marshalValue,
401 | OffsetManagement: offsetMgmt,
402 | })
403 | if err != nil {
404 | panic(err)
405 | }
406 | return buf.String()
407 | }
408 |
409 | var _ valueGenerator = &generateList{}
410 |
--------------------------------------------------------------------------------
/sszgen/backend/overlay.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
8 | )
9 |
10 | type generateOverlay struct {
11 | *types.ValueOverlay
12 | targetPackage string
13 | importNamer *ImportNamer
14 | }
15 |
16 | func (g *generateOverlay) toOverlay() func(string) string {
17 | wrapper := g.TypeName()
18 | if g.targetPackage != g.PackagePath() {
19 | wrapper = g.importNamer.NameString(g.PackagePath()) + "." + wrapper
20 | }
21 | return func(value string) string {
22 | return fmt.Sprintf("%s(%s)", wrapper, value)
23 | }
24 | }
25 |
26 | func (g *generateOverlay) generateVariableMarshalValue(fieldName string) string {
27 | gg := newValueGenerator(interfaces.SszMarshaler, g.Underlying, g.targetPackage, g.importNamer)
28 | vm, ok := gg.(variableMarshaller)
29 | if !ok {
30 | return ""
31 | }
32 | return vm.generateVariableMarshalValue(fieldName)
33 | }
34 |
35 | func (g *generateOverlay) generateUnmarshalValue(fieldName string, sliceName string) string {
36 | gg := newValueGenerator(interfaces.SszUnmarshaler, g.Underlying, g.targetPackage, g.importNamer)
37 | c, ok := gg.(caster)
38 | if ok {
39 | c.setToOverlay(g.toOverlay())
40 | }
41 | umv := gg.generateUnmarshalValue(fieldName, sliceName)
42 | if g.IsBitfield() {
43 | switch t := g.Underlying.(type) {
44 | case *types.ValueList:
45 | return fmt.Sprintf(`if err = ssz.ValidateBitlist(%s, %d); err != nil {
46 | return err
47 | }
48 | %s`, sliceName, t.MaxSize, umv)
49 | }
50 | }
51 | return umv
52 | }
53 |
54 | func (g *generateOverlay) generateFixedMarshalValue(fieldName string) string {
55 | gg := newValueGenerator(interfaces.SszMarshaler, g.Underlying, g.targetPackage, g.importNamer)
56 | uc, ok := gg.(coercer)
57 | if ok {
58 | return gg.generateFixedMarshalValue(uc.coerce()(fieldName))
59 | }
60 | return gg.generateFixedMarshalValue(fieldName)
61 | }
62 |
63 | func (g *generateOverlay) variableSizeSSZ(fieldname string) string {
64 | return ""
65 | }
66 |
67 | func (g *generateOverlay) generateHTRPutter(fieldName string) string {
68 | if g.IsBitfield() && g.Name == "Bitlist" {
69 | ul, ok := g.Underlying.(*types.ValueList)
70 | if !ok {
71 | panic(fmt.Sprintf("unexpected underlying type for Bitlist, expected ValueList, got %v", g.Underlying))
72 | }
73 | t := `if len(%s) == 0 {
74 | return ssz.ErrEmptyBitlist
75 | }
76 | hh.PutBitlist(%s, %d)`
77 | return fmt.Sprintf(t, fieldName, fieldName, ul.MaxSize)
78 | }
79 | gg := newValueGenerator(interfaces.SszLightHasher, g.Underlying, g.targetPackage, g.importNamer)
80 | htrp, ok := gg.(htrPutter)
81 | if !ok {
82 | return ""
83 | }
84 | uc, ok := gg.(coercer)
85 | if ok {
86 | c := uc.coerce()
87 | return htrp.generateHTRPutter(c(fieldName))
88 | }
89 | return htrp.generateHTRPutter(fieldName)
90 | }
91 |
92 | var _ valueGenerator = &generateOverlay{}
93 |
--------------------------------------------------------------------------------
/sszgen/backend/pointer.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
8 | )
9 |
10 | type generatePointer struct {
11 | *types.ValuePointer
12 | targetPackage string
13 | importNamer *ImportNamer
14 | }
15 |
16 | func (g *generatePointer) generateHTRPutter(fieldName string) string {
17 | gg := newValueGenerator(interfaces.SszLightHasher, g.Referent, g.targetPackage, g.importNamer)
18 | hp, ok := gg.(htrPutter)
19 | if !ok {
20 | return ""
21 | }
22 | return hp.generateHTRPutter(fieldName)
23 | }
24 |
25 | func (g *generatePointer) generateFixedMarshalValue(fieldName string) string {
26 | gg := newValueGenerator(interfaces.SszMarshaler, g.Referent, g.targetPackage, g.importNamer)
27 | return gg.generateFixedMarshalValue(fieldName)
28 | }
29 |
30 | func (g *generatePointer) generateUnmarshalValue(fieldName string, sliceName string) string {
31 | gg := newValueGenerator(interfaces.SszUnmarshaler, g.Referent, g.targetPackage, g.importNamer)
32 | return gg.generateUnmarshalValue(fieldName, sliceName)
33 | }
34 |
35 | func (g *generatePointer) initializeValue() string {
36 | gg := newValueGenerator(interfaces.SszMarshaler, g.Referent, g.targetPackage, g.importNamer)
37 | iv, ok := gg.(valueInitializer)
38 | if ok {
39 | return iv.initializeValue()
40 | }
41 | return ""
42 | }
43 |
44 | func (g *generatePointer) generateVariableMarshalValue(fieldName string) string {
45 | gg := newValueGenerator(interfaces.SszMarshaler, g.Referent, g.targetPackage, g.importNamer)
46 | vm, ok := gg.(variableMarshaller)
47 | if !ok {
48 | panic(fmt.Sprintf("variable size type does not implement variableMarshaller: %v", g.Referent))
49 | }
50 | return vm.generateVariableMarshalValue(fieldName)
51 | }
52 |
53 | func (g *generatePointer) variableSizeSSZ(fieldName string) string {
54 | gg := newValueGenerator(interfaces.SszMarshaler, g.Referent, g.targetPackage, g.importNamer)
55 | return gg.variableSizeSSZ(fieldName)
56 | }
57 |
58 | var _ valueGenerator = &generatePointer{}
59 | var _ htrPutter = &generatePointer{}
60 |
--------------------------------------------------------------------------------
/sszgen/backend/render.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "go/format"
7 | "go/types"
8 | "strings"
9 | "text/template"
10 |
11 | gentypes "github.com/OffchainLabs/methodical-ssz/sszgen/types"
12 | )
13 |
14 | type generatedCode struct {
15 | blocks []string
16 | // key=package path, value=alias
17 | imports map[string]string
18 | }
19 |
20 | func (gc *generatedCode) renderImportPairs() string {
21 | pairs := make([]string, 0)
22 | for k, v := range gc.imports {
23 | pairs = append(pairs, fmt.Sprintf("%s \"%s\"", v, k))
24 | }
25 | return strings.Join(pairs, "\n")
26 | }
27 |
28 | func (gc *generatedCode) renderBlocks() string {
29 | return strings.Join(gc.blocks, "\n\n")
30 | }
31 |
32 | func (gc *generatedCode) merge(right *generatedCode) {
33 | gc.blocks = append(gc.blocks, right.blocks...)
34 | }
35 |
36 | // Generator needs to be initialized with the package name,
37 | // so use the new NewGenerator func for proper setup.
38 | type Generator struct {
39 | gc []*generatedCode
40 | packagePath string
41 | importNamer *ImportNamer
42 | }
43 |
44 | var defaultSSZImports = map[string]string{
45 | "github.com/prysmaticlabs/fastssz": "ssz",
46 | "fmt": "",
47 | }
48 |
49 | func NewGenerator(packagePath string) *Generator {
50 | importNamer := NewImportNamer(packagePath, defaultSSZImports)
51 | return &Generator{
52 | packagePath: packagePath,
53 | importNamer: importNamer,
54 | }
55 | }
56 |
57 | // TODO Generate should be able to return an error
58 | func (g *Generator) Generate(vr gentypes.ValRep) error {
59 | if vc, ok := vr.(*gentypes.ValueContainer); ok {
60 | return g.genValueContainer(vc)
61 | }
62 | if vo, ok := vr.(*gentypes.ValueOverlay); ok {
63 | return g.genValueOverlay(vo)
64 | }
65 | return fmt.Errorf("can only generate method sets for container & overlay gentypes at this time, type: %v", vr.TypeName())
66 | }
67 |
68 | func (g *Generator) genValueOverlay(vc *gentypes.ValueOverlay) error {
69 | // TODO (MariusVanDerWijden) implement for basic gentypes
70 | return nil
71 | }
72 |
73 | func (g *Generator) genValueContainer(vc *gentypes.ValueContainer) error {
74 | container := &generateContainer{ValueContainer: vc, targetPackage: g.packagePath, importNamer: g.importNamer}
75 | methods := []func(gc *generateContainer) (*generatedCode, error){
76 | GenerateSizeSSZ,
77 | GenerateMarshalSSZ,
78 | GenerateUnmarshalSSZ,
79 | GenerateHashTreeRoot,
80 | }
81 | for _, method := range methods {
82 | code, err := method(container)
83 | if err != nil {
84 | return err
85 | }
86 | g.gc = append(g.gc, code)
87 | }
88 | return nil
89 | }
90 |
91 | var fileTemplate = `package {{.Package}}
92 |
93 | {{ if .Imports -}}
94 | import (
95 | {{.Imports}}
96 | )
97 | {{- end }}
98 |
99 | {{.Blocks}}`
100 |
101 | func (g *Generator) Render() ([]byte, error) {
102 | if g.packagePath == "" {
103 | return nil, fmt.Errorf("missing packagePath: Generator requires a packagePath for code generation.")
104 | }
105 | ft := template.New("generated.ssz.go")
106 | tmpl, err := ft.Parse(fileTemplate)
107 | if err != nil {
108 | return nil, err
109 | }
110 | final := &generatedCode{}
111 | for _, gc := range g.gc {
112 | final.merge(gc)
113 | }
114 | buf := bytes.NewBuffer(nil)
115 | err = tmpl.Execute(buf, struct {
116 | Package string
117 | Imports string
118 | Blocks string
119 | }{
120 | Package: RenderedPackageName(g.packagePath),
121 | Imports: g.importNamer.ImportPairs(),
122 | Blocks: final.renderBlocks(),
123 | })
124 | if err != nil {
125 | return nil, err
126 | }
127 | return format.Source(buf.Bytes())
128 | //return buf.Bytes(), nil
129 | }
130 |
131 | type valueGenerator interface {
132 | variableSizeSSZ(fieldname string) string
133 | generateFixedMarshalValue(string) string
134 | generateUnmarshalValue(string, string) string
135 | generateHTRPutter(string) string
136 | }
137 |
138 | type valueInitializer interface {
139 | initializeValue() string
140 | }
141 |
142 | type variableMarshaller interface {
143 | generateVariableMarshalValue(string) string
144 | }
145 |
146 | type variableUnmarshaller interface {
147 | generateVariableUnmarshalValue(string) string
148 | }
149 |
150 | type coercer interface {
151 | coerce() func(string) string
152 | }
153 |
154 | type htrPutter interface {
155 | generateHTRPutter(string) string
156 | }
157 |
158 | func newValueGenerator(ifaceCtx *types.Interface, vr gentypes.ValRep, packagePath string, inm *ImportNamer) valueGenerator {
159 | if vr.SatisfiesInterface(ifaceCtx) {
160 | return &generateDelegate{ValRep: vr, targetPackage: packagePath, importNamer: inm}
161 | }
162 | switch ty := vr.(type) {
163 | case *gentypes.ValueBool:
164 | return &generateBool{valRep: ty, targetPackage: packagePath, importNamer: inm}
165 | case *gentypes.ValueByte:
166 | return &generateByte{ValueByte: ty, targetPackage: packagePath, importNamer: inm}
167 | case *gentypes.ValueContainer:
168 | return &generateContainer{ValueContainer: ty, targetPackage: packagePath, importNamer: inm}
169 | case *gentypes.ValueList:
170 | return &generateList{valRep: ty, targetPackage: packagePath, importNamer: inm}
171 | case *gentypes.ValueOverlay:
172 | return &generateOverlay{ValueOverlay: ty, targetPackage: packagePath, importNamer: inm}
173 | case *gentypes.ValuePointer:
174 | return &generatePointer{ValuePointer: ty, targetPackage: packagePath, importNamer: inm}
175 | case *gentypes.ValueUint:
176 | return &generateUint{valRep: ty, targetPackage: packagePath, importNamer: inm}
177 | case *gentypes.ValueUnion:
178 | return &generateUnion{ValueUnion: ty, targetPackage: packagePath, importNamer: inm}
179 | case *gentypes.ValueVector:
180 | return &generateVector{valRep: ty, targetPackage: packagePath, importNamer: inm}
181 | }
182 | panic(fmt.Sprintf("Cannot manage generation for unrecognized ValRep implementation %v", vr))
183 | }
184 |
185 | func importAlias(packageName string) string {
186 | parts := strings.Split(packageName, "/")
187 | for i, p := range parts {
188 | if strings.Contains(p, ".") {
189 | continue
190 | }
191 | parts = parts[i:]
192 | break
193 | }
194 | return strings.ReplaceAll(strings.Join(parts, "_"), "-", "_")
195 | }
196 |
197 | func fullyQualifiedTypeName(v gentypes.ValRep, targetPackage string, inamer *ImportNamer) string {
198 | tn := v.TypeName()
199 | if targetPackage == v.PackagePath() || v.PackagePath() == "" {
200 | return tn
201 | }
202 | pkg := inamer.NameString(v.PackagePath())
203 |
204 | if tn[0:1] == "*" {
205 | tn = tn[1:]
206 | pkg = "*" + pkg
207 | }
208 | return pkg + "." + tn
209 | }
210 |
211 | // RenderedPackageName reduces the fully qualified package name to the relative package name, ie
212 | // github.com/prysmaticlabs/prysm/v3/proto/eth/v1 -> v1
213 | func RenderedPackageName(n string) string {
214 | parts := strings.Split(n, "/")
215 | return parts[len(parts)-1]
216 | }
217 |
--------------------------------------------------------------------------------
/sszgen/backend/render_test.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "go/format"
5 | "os"
6 | "testing"
7 |
8 | "github.com/prysmaticlabs/prysm/v3/testing/require"
9 | )
10 |
11 | var generator_generateFixture = `package derp
12 |
13 | import (
14 | "fmt"
15 | derp "github.com/prysmaticlabs/derp/derp"
16 | ssz "github.com/prysmaticlabs/fastssz"
17 | )
18 |
19 | func main() {
20 | fmt.printf("hello world")
21 | }
22 | `
23 |
24 | func TestGenerator_Generate(t *testing.T) {
25 | gc := &generatedCode{
26 | blocks: []string{"func main() {\n\tfmt.printf(\"hello world\")\n}"},
27 | }
28 | defaultImports := map[string]string{
29 | "github.com/prysmaticlabs/derp/derp": "derp",
30 | "github.com/prysmaticlabs/fastssz": "ssz",
31 | "fmt": "",
32 | }
33 | inm := NewImportNamer("github.com/prysmaticlabs/derp", defaultImports)
34 | g := &Generator{packagePath: "github.com/prysmaticlabs/derp", importNamer: inm}
35 | g.gc = append(g.gc, gc)
36 | rendered, err := g.Render()
37 | require.NoError(t, err)
38 | require.Equal(t, generator_generateFixture, string(rendered))
39 | }
40 |
41 | func TestGenerator_GenerateBeaconState(t *testing.T) {
42 | t.Skip("fixtures need to be updated")
43 | b, err := os.ReadFile("testdata/TestGenerator_GenerateBeaconState.expected")
44 | require.NoError(t, err)
45 | formatted, err := format.Source(b)
46 | require.NoError(t, err)
47 | expected := string(formatted)
48 |
49 | g := &Generator{
50 | packagePath: "github.com/prysmaticlabs/prysm/v3/proto/beacon/p2p/v1",
51 | }
52 | g.Generate(testFixBeaconState)
53 | rendered, err := g.Render()
54 | require.NoError(t, err)
55 | actual := string(rendered)
56 | require.Equal(t, expected, actual)
57 | }
58 |
59 | func TestImportAlias(t *testing.T) {
60 | cases := []struct {
61 | packageName string
62 | alias string
63 | }{
64 | {
65 | packageName: "github.com/derp/derp",
66 | alias: "derp_derp",
67 | },
68 | {
69 | packageName: "text/template",
70 | alias: "text_template",
71 | },
72 | {
73 | packageName: "fmt",
74 | alias: "fmt",
75 | },
76 | }
77 | for _, c := range cases {
78 | require.Equal(t, importAlias(c.packageName), c.alias)
79 | }
80 | }
81 |
82 | func TestRenderedPackageName(t *testing.T) {
83 | before := "github.com/prysmaticlabs/prysm/v3/proto/eth/v1"
84 | after := "v1"
85 | require.Equal(t, after, RenderedPackageName(before))
86 | }
87 |
--------------------------------------------------------------------------------
/sszgen/backend/testdata/TestGenerateHashTreeRoot.expected:
--------------------------------------------------------------------------------
1 | // HashTreeRoot ssz hashes the BeaconState object
2 | func (c *BeaconState) XXHashTreeRoot() ([32]byte, error) {
3 | hh := ssz.DefaultHasherPool.Get()
4 | if err := b.HashTreeRootWith(hh); err != nil {
5 | ssz.DefaultHasherPool.Put(hh)
6 | return [32]byte{}, err
7 | }
8 | root, err := hh.HashRoot()
9 | ssz.DefaultHasherPool.Put(hh)
10 | return root, err
11 | }
12 |
13 | func (c *BeaconState) XXHashTreeRootWith(hh *ssz.Hasher) (err error) {
14 | }
15 |
--------------------------------------------------------------------------------
/sszgen/backend/testdata/TestGenerateMarshalSSZ.expected:
--------------------------------------------------------------------------------
1 | func (c *BeaconState) MarshalSSZ() ([]byte, error) {
2 | buf := make([]byte, c.SizeSSZ())
3 | return c.MarshalSSZTo(buf[:0])
4 | }
5 |
6 | func (c *BeaconState) MarshalSSZTo(dst []byte) ([]byte, error) {
7 | var err error
8 | offset := 2687377
9 |
10 | // Field 0: GenesisTime
11 | dst = ssz.MarshalUint64(dst, c.GenesisTime)
12 |
13 | // Field 1: GenesisValidatorsRoot
14 | if len(c.GenesisValidatorsRoot) != 32 {
15 | return nil, ssz.ErrBytesLength
16 | }
17 | dst = append(dst, c.GenesisValidatorsRoot...)
18 |
19 | // Field 2: Slot
20 | dst = ssz.MarshalUint64(dst, uint64(c.Slot))
21 |
22 | // Field 3: Fork
23 | if c.Fork == nil {
24 | c.Fork = new(v1.Fork)
25 | }
26 | if dst, err = c.Fork.MarshalSSZTo(dst); err != nil {
27 | return nil, err
28 | }
29 |
30 | // Field 4: LatestBlockHeader
31 | if c.LatestBlockHeader == nil {
32 | c.LatestBlockHeader = new(v1alpha1.BeaconBlockHeader)
33 | }
34 | if dst, err = c.LatestBlockHeader.MarshalSSZTo(dst); err != nil {
35 | return nil, err
36 | }
37 |
38 | // Field 5: BlockRoots
39 | if len(c.BlockRoots) != 8192 {
40 | return nil, ssz.ErrBytesLength
41 | }
42 | for _, o := range c.BlockRoots {
43 | if len(o) != 32 {
44 | return nil, ssz.ErrBytesLength
45 | }
46 | dst = append(dst, o...)
47 | }
48 |
49 | // Field 6: StateRoots
50 | if len(c.StateRoots) != 8192 {
51 | return nil, ssz.ErrBytesLength
52 | }
53 | for _, o := range c.StateRoots {
54 | if len(o) != 32 {
55 | return nil, ssz.ErrBytesLength
56 | }
57 | dst = append(dst, o...)
58 | }
59 |
60 | // Field 7: HistoricalRoots
61 | dst = ssz.WriteOffset(dst, offset)
62 | offset += len(c.HistoricalRoots) * 32
63 |
64 | // Field 8: Eth1Data
65 | if c.Eth1Data == nil {
66 | c.Eth1Data = new(v1alpha1.Eth1Data)
67 | }
68 | if dst, err = c.Eth1Data.MarshalSSZTo(dst); err != nil {
69 | return nil, err
70 | }
71 |
72 | // Field 9: Eth1DataVotes
73 | dst = ssz.WriteOffset(dst, offset)
74 | offset += len(c.Eth1DataVotes) * 72
75 |
76 | // Field 10: Eth1DepositIndex
77 | dst = ssz.MarshalUint64(dst, c.Eth1DepositIndex)
78 |
79 | // Field 11: Validators
80 | dst = ssz.WriteOffset(dst, offset)
81 | offset += len(c.Validators) * 121
82 |
83 | // Field 12: Balances
84 | dst = ssz.WriteOffset(dst, offset)
85 | offset += len(c.Balances) * 8
86 |
87 | // Field 13: RandaoMixes
88 | if len(c.RandaoMixes) != 65536 {
89 | return nil, ssz.ErrBytesLength
90 | }
91 | for _, o := range c.RandaoMixes {
92 | if len(o) != 32 {
93 | return nil, ssz.ErrBytesLength
94 | }
95 | dst = append(dst, o...)
96 | }
97 |
98 | // Field 14: Slashings
99 | if len(c.Slashings) != 8192 {
100 | return nil, ssz.ErrBytesLength
101 | }
102 | for _, o := range c.Slashings {
103 | dst = ssz.MarshalUint64(dst, o)
104 | }
105 |
106 | // Field 15: PreviousEpochAttestations
107 | dst = ssz.WriteOffset(dst, offset)
108 | offset += func() int {
109 | s := 0
110 | for _, o := range c.PreviousEpochAttestations {
111 | s += 4
112 | s += o.SizeSSZ()
113 | }
114 | return s
115 | }()
116 |
117 | // Field 16: CurrentEpochAttestations
118 | dst = ssz.WriteOffset(dst, offset)
119 | offset += func() int {
120 | s := 0
121 | for _, o := range c.CurrentEpochAttestations {
122 | s += 4
123 | s += o.SizeSSZ()
124 | }
125 | return s
126 | }()
127 |
128 | // Field 17: JustificationBits
129 | if len([]byte(c.JustificationBits)) != 1 {
130 | return nil, ssz.ErrBytesLength
131 | }
132 | dst = append(dst, []byte(c.JustificationBits)...)
133 |
134 | // Field 18: PreviousJustifiedCheckpoint
135 | if c.PreviousJustifiedCheckpoint == nil {
136 | c.PreviousJustifiedCheckpoint = new(v1alpha1.Checkpoint)
137 | }
138 | if dst, err = c.PreviousJustifiedCheckpoint.MarshalSSZTo(dst); err != nil {
139 | return nil, err
140 | }
141 |
142 | // Field 19: CurrentJustifiedCheckpoint
143 | if c.CurrentJustifiedCheckpoint == nil {
144 | c.CurrentJustifiedCheckpoint = new(v1alpha1.Checkpoint)
145 | }
146 | if dst, err = c.CurrentJustifiedCheckpoint.MarshalSSZTo(dst); err != nil {
147 | return nil, err
148 | }
149 |
150 | // Field 20: FinalizedCheckpoint
151 | if c.FinalizedCheckpoint == nil {
152 | c.FinalizedCheckpoint = new(v1alpha1.Checkpoint)
153 | }
154 | if dst, err = c.FinalizedCheckpoint.MarshalSSZTo(dst); err != nil {
155 | return nil, err
156 | }
157 |
158 | // Field 7: HistoricalRoots
159 | if len(c.HistoricalRoots) > 16777216 {
160 | return nil, ssz.ErrListTooBig
161 | }
162 | for _, o := range c.HistoricalRoots {
163 | if len(o) != 32 {
164 | return nil, ssz.ErrBytesLength
165 | }
166 | dst = append(dst, o...)
167 | }
168 |
169 | // Field 9: Eth1DataVotes
170 | if len(c.Eth1DataVotes) > 2048 {
171 | return nil, ssz.ErrListTooBig
172 | }
173 | for _, o := range c.Eth1DataVotes {
174 | if dst, err = o.MarshalSSZTo(dst); err != nil {
175 | return nil, err
176 | }
177 | }
178 |
179 | // Field 11: Validators
180 | if len(c.Validators) > 1099511627776 {
181 | return nil, ssz.ErrListTooBig
182 | }
183 | for _, o := range c.Validators {
184 | if dst, err = o.MarshalSSZTo(dst); err != nil {
185 | return nil, err
186 | }
187 | }
188 |
189 | // Field 12: Balances
190 | if len(c.Balances) > 1099511627776 {
191 | return nil, ssz.ErrListTooBig
192 | }
193 | for _, o := range c.Balances {
194 | dst = ssz.MarshalUint64(dst, o)
195 | }
196 |
197 | // Field 15: PreviousEpochAttestations
198 | if len(c.PreviousEpochAttestations) > 4096 {
199 | return nil, ssz.ErrListTooBig
200 | }
201 | {
202 | offset = 4 * len(c.PreviousEpochAttestations)
203 | for _, o := range c.PreviousEpochAttestations {
204 | dst = ssz.WriteOffset(dst, offset)
205 | offset += o.SizeSSZ()
206 | }
207 | }
208 | for _, o := range c.PreviousEpochAttestations {
209 | if dst, err = o.MarshalSSZTo(dst); err != nil {
210 | return nil, err
211 | }
212 | }
213 |
214 | // Field 16: CurrentEpochAttestations
215 | if len(c.CurrentEpochAttestations) > 4096 {
216 | return nil, ssz.ErrListTooBig
217 | }
218 | {
219 | offset = 4 * len(c.CurrentEpochAttestations)
220 | for _, o := range c.CurrentEpochAttestations {
221 | dst = ssz.WriteOffset(dst, offset)
222 | offset += o.SizeSSZ()
223 | }
224 | }
225 | for _, o := range c.CurrentEpochAttestations {
226 | if dst, err = o.MarshalSSZTo(dst); err != nil {
227 | return nil, err
228 | }
229 | }
230 | return dst, err
231 | }
232 |
--------------------------------------------------------------------------------
/sszgen/backend/testdata/TestGenerateSizeSSZ.expected:
--------------------------------------------------------------------------------
1 | func (c *BeaconState) SizeSSZ() int {
2 | size := 2687377
3 | size += len(c.HistoricalRoots) * 32
4 | size += len(c.Eth1DataVotes) * 72
5 | size += len(c.Validators) * 121
6 | size += len(c.Balances) * 8
7 | size += func() int {
8 | s := 0
9 | for _, o := range c.PreviousEpochAttestations {
10 | s += 4
11 | s += o.SizeSSZ()
12 | }
13 | return s
14 | }()
15 | size += func() int {
16 | s := 0
17 | for _, o := range c.CurrentEpochAttestations {
18 | s += 4
19 | s += o.SizeSSZ()
20 | }
21 | return s
22 | }()
23 | return size
24 | }
25 |
--------------------------------------------------------------------------------
/sszgen/backend/testdata/TestGenerateUnmarshalSSZ.expected:
--------------------------------------------------------------------------------
1 | func (c *BeaconState) XXUnmarshalSSZ(buf []byte) error {
2 | var err error
3 | size := uint64(len(buf))
4 | if size < 2687377 {
5 | return ssz.ErrSize
6 | }
7 |
8 | s0 := buf[0:8] // c.GenesisTime
9 | s1 := buf[8:40] // c.GenesisValidatorsRoot
10 | s2 := buf[40:48] // c.Slot
11 | s3 := buf[48:64] // c.Fork
12 | s4 := buf[64:176] // c.LatestBlockHeader
13 | s5 := buf[176:262320] // c.BlockRoots
14 | s6 := buf[262320:524464] // c.StateRoots
15 | s8 := buf[524468:524540] // c.Eth1Data
16 | s10 := buf[524544:524552] // c.Eth1DepositIndex
17 | s13 := buf[524560:2621712] // c.RandaoMixes
18 | s14 := buf[2621712:2687248] // c.Slashings
19 | s17 := buf[2687256:2687257] // c.JustificationBits
20 | s18 := buf[2687257:2687297] // c.PreviousJustifiedCheckpoint
21 | s19 := buf[2687297:2687337] // c.CurrentJustifiedCheckpoint
22 | s20 := buf[2687337:2687377] // c.FinalizedCheckpoint
23 |
24 | v7 := ssz.ReadOffset(buf[524464:524468]) // c.HistoricalRoots
25 | if v7 < 2687377 {
26 | return ssz.ErrInvalidVariableOffset
27 | }
28 | if v7 > size {
29 | return ssz.ErrOffset
30 | }
31 | v9 := ssz.ReadOffset(buf[524540:524544]) // c.Eth1DataVotes
32 | if v9 > size || v9 < v7 {
33 | return ssz.ErrOffset
34 | }
35 | v11 := ssz.ReadOffset(buf[524552:524556]) // c.Validators
36 | if v11 > size || v11 < v9 {
37 | return ssz.ErrOffset
38 | }
39 | v12 := ssz.ReadOffset(buf[524556:524560]) // c.Balances
40 | if v12 > size || v12 < v11 {
41 | return ssz.ErrOffset
42 | }
43 | v15 := ssz.ReadOffset(buf[2687248:2687252]) // c.PreviousEpochAttestations
44 | if v15 > size || v15 < v12 {
45 | return ssz.ErrOffset
46 | }
47 | v16 := ssz.ReadOffset(buf[2687252:2687256]) // c.CurrentEpochAttestations
48 | if v16 > size || v16 < v15 {
49 | return ssz.ErrOffset
50 | }
51 | s7 := buf[v7:v9] // c.HistoricalRoots
52 | s9 := buf[v9:v11] // c.Eth1DataVotes
53 | s11 := buf[v11:v12] // c.Validators
54 | s12 := buf[v12:v15] // c.Balances
55 | s15 := buf[v15:v16] // c.PreviousEpochAttestations
56 | s16 := buf[v16:] // c.CurrentEpochAttestations
57 |
58 | // Field 0: GenesisTime
59 | c.GenesisTime = ssz.UnmarshallUint64(s0)
60 |
61 | // Field 1: GenesisValidatorsRoot
62 | c.GenesisValidatorsRoot = append([]byte{}, s1...)
63 |
64 | // Field 2: Slot
65 | c.Slot = prysmaticlabs_eth2_types.Slot(ssz.UnmarshallUint64(s2))
66 |
67 | // Field 3: Fork
68 | c.Fork = new(prysmaticlabs_prysm_proto_beacon_p2p_v1.Fork)
69 | if err = c.Fork.UnmarshalSSZ(s3); err != nil {
70 | return err
71 | }
72 |
73 | // Field 4: LatestBlockHeader
74 | c.LatestBlockHeader = new(prysmaticlabs_prysm_proto_eth_v1alpha1.BeaconBlockHeader)
75 | if err = c.LatestBlockHeader.UnmarshalSSZ(s4); err != nil {
76 | return err
77 | }
78 |
79 | // Field 5: BlockRoots
80 | {
81 | var tmp []byte
82 | for i := 0; i < 8192; i++ {
83 | tmpSlice := s5[i*32 : (1+i)*32]
84 | tmp = append([]byte{}, tmpSlice...)
85 | c.BlockRoots = append(c.BlockRoots, tmp)
86 | }
87 | }
88 |
89 | // Field 6: StateRoots
90 | {
91 | var tmp []byte
92 | for i := 0; i < 8192; i++ {
93 | tmpSlice := s6[i*32 : (1+i)*32]
94 | tmp = append([]byte{}, tmpSlice...)
95 | c.StateRoots = append(c.StateRoots, tmp)
96 | }
97 | }
98 |
99 | // Field 7: HistoricalRoots
100 | {
101 | if len(s7)%32 != 0 {
102 | return fmt.Errorf("misaligned bytes: c.HistoricalRoots length is %d, which is not a multiple of 32", len(s7))
103 | }
104 | numElem := len(s7) / 32
105 | if numElem > 16777216 {
106 | return fmt.Errorf("ssz-max exceeded: c.HistoricalRoots has %d elements, ssz-max is 16777216", numElem)
107 | }
108 | for i := 0; i < numElem; i++ {
109 | var tmp []byte
110 |
111 | tmpSlice := s7[i*32 : (1+i)*32]
112 | tmp = append([]byte{}, tmpSlice...)
113 | c.HistoricalRoots = append(c.HistoricalRoots, tmp)
114 | }
115 | }
116 |
117 | // Field 8: Eth1Data
118 | c.Eth1Data = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Eth1Data)
119 | if err = c.Eth1Data.UnmarshalSSZ(s8); err != nil {
120 | return err
121 | }
122 |
123 | // Field 9: Eth1DataVotes
124 | {
125 | if len(s9)%72 != 0 {
126 | return fmt.Errorf("misaligned bytes: c.Eth1DataVotes length is %d, which is not a multiple of 72", len(s9))
127 | }
128 | numElem := len(s9) / 72
129 | if numElem > 2048 {
130 | return fmt.Errorf("ssz-max exceeded: c.Eth1DataVotes has %d elements, ssz-max is 2048", numElem)
131 | }
132 | for i := 0; i < numElem; i++ {
133 | var tmp *prysmaticlabs_prysm_proto_eth_v1alpha1.Eth1Data
134 | tmp = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Eth1Data)
135 | tmpSlice := s9[i*72 : (1+i)*72]
136 | if err = tmp.UnmarshalSSZ(tmpSlice); err != nil {
137 | return err
138 | }
139 | c.Eth1DataVotes = append(c.Eth1DataVotes, tmp)
140 | }
141 | }
142 |
143 | // Field 10: Eth1DepositIndex
144 | c.Eth1DepositIndex = ssz.UnmarshallUint64(s10)
145 |
146 | // Field 11: Validators
147 | {
148 | if len(s11)%121 != 0 {
149 | return fmt.Errorf("misaligned bytes: c.Validators length is %d, which is not a multiple of 121", len(s11))
150 | }
151 | numElem := len(s11) / 121
152 | if numElem > 1099511627776 {
153 | return fmt.Errorf("ssz-max exceeded: c.Validators has %d elements, ssz-max is 1099511627776", numElem)
154 | }
155 | for i := 0; i < numElem; i++ {
156 | var tmp *prysmaticlabs_prysm_proto_eth_v1alpha1.Validator
157 | tmp = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Validator)
158 | tmpSlice := s11[i*121 : (1+i)*121]
159 | if err = tmp.UnmarshalSSZ(tmpSlice); err != nil {
160 | return err
161 | }
162 | c.Validators = append(c.Validators, tmp)
163 | }
164 | }
165 |
166 | // Field 12: Balances
167 | {
168 | if len(s12)%8 != 0 {
169 | return fmt.Errorf("misaligned bytes: c.Balances length is %d, which is not a multiple of 8", len(s12))
170 | }
171 | numElem := len(s12) / 8
172 | if numElem > 1099511627776 {
173 | return fmt.Errorf("ssz-max exceeded: c.Balances has %d elements, ssz-max is 1099511627776", numElem)
174 | }
175 | for i := 0; i < numElem; i++ {
176 | var tmp uint64
177 |
178 | tmpSlice := s12[i*8 : (1+i)*8]
179 | tmp = ssz.UnmarshallUint64(tmpSlice)
180 | c.Balances = append(c.Balances, tmp)
181 | }
182 | }
183 |
184 | // Field 13: RandaoMixes
185 | {
186 | var tmp []byte
187 | for i := 0; i < 65536; i++ {
188 | tmpSlice := s13[i*32 : (1+i)*32]
189 | tmp = append([]byte{}, tmpSlice...)
190 | c.RandaoMixes = append(c.RandaoMixes, tmp)
191 | }
192 | }
193 |
194 | // Field 14: Slashings
195 | {
196 | var tmp uint64
197 | for i := 0; i < 8192; i++ {
198 | tmpSlice := s14[i*8 : (1+i)*8]
199 | tmp = ssz.UnmarshallUint64(tmpSlice)
200 | c.Slashings = append(c.Slashings, tmp)
201 | }
202 | }
203 |
204 | // Field 15: PreviousEpochAttestations
205 | {
206 | // empty lists are zero length, so make sure there is room for an offset
207 | // before attempting to unmarshal it
208 | if len(s15) > 3 {
209 | firstOffset := ssz.ReadOffset(s15[0:4])
210 | if firstOffset%4 != 0 {
211 | return fmt.Errorf("misaligned list bytes: when decoding c.PreviousEpochAttestations, end-of-list offset is %d, which is not a multiple of 4 (offset size)", firstOffset)
212 | }
213 | listLen := firstOffset / 4
214 | if listLen > 4096 {
215 | return fmt.Errorf("ssz-max exceeded: c.PreviousEpochAttestations has %d elements, ssz-max is 4096", listLen)
216 | }
217 | listOffsets := make([]uint64, listLen)
218 | for i := 0; uint64(i) < listLen; i++ {
219 | listOffsets[i] = ssz.ReadOffset(s15[i*4 : (i+1)*4])
220 | }
221 | for i := 0; i < len(listOffsets); i++ {
222 | var tmp *prysmaticlabs_prysm_proto_beacon_p2p_v1.PendingAttestation
223 | tmp = new(prysmaticlabs_prysm_proto_beacon_p2p_v1.PendingAttestation)
224 | var tmpSlice []byte
225 | if i+1 == len(listOffsets) {
226 | tmpSlice = s15[listOffsets[i]:]
227 | } else {
228 | tmpSlice = s15[listOffsets[i]:listOffsets[i+1]]
229 | }
230 | if err = tmp.UnmarshalSSZ(tmpSlice); err != nil {
231 | return err
232 | }
233 | c.PreviousEpochAttestations = append(c.PreviousEpochAttestations, tmp)
234 | }
235 | }
236 | }
237 |
238 | // Field 16: CurrentEpochAttestations
239 | {
240 | // empty lists are zero length, so make sure there is room for an offset
241 | // before attempting to unmarshal it
242 | if len(s16) > 3 {
243 | firstOffset := ssz.ReadOffset(s16[0:4])
244 | if firstOffset%4 != 0 {
245 | return fmt.Errorf("misaligned list bytes: when decoding c.CurrentEpochAttestations, end-of-list offset is %d, which is not a multiple of 4 (offset size)", firstOffset)
246 | }
247 | listLen := firstOffset / 4
248 | if listLen > 4096 {
249 | return fmt.Errorf("ssz-max exceeded: c.CurrentEpochAttestations has %d elements, ssz-max is 4096", listLen)
250 | }
251 | listOffsets := make([]uint64, listLen)
252 | for i := 0; uint64(i) < listLen; i++ {
253 | listOffsets[i] = ssz.ReadOffset(s16[i*4 : (i+1)*4])
254 | }
255 | for i := 0; i < len(listOffsets); i++ {
256 | var tmp *prysmaticlabs_prysm_proto_beacon_p2p_v1.PendingAttestation
257 | tmp = new(prysmaticlabs_prysm_proto_beacon_p2p_v1.PendingAttestation)
258 | var tmpSlice []byte
259 | if i+1 == len(listOffsets) {
260 | tmpSlice = s16[listOffsets[i]:]
261 | } else {
262 | tmpSlice = s16[listOffsets[i]:listOffsets[i+1]]
263 | }
264 | if err = tmp.UnmarshalSSZ(tmpSlice); err != nil {
265 | return err
266 | }
267 | c.CurrentEpochAttestations = append(c.CurrentEpochAttestations, tmp)
268 | }
269 | }
270 | }
271 |
272 | // Field 17: JustificationBits
273 | c.JustificationBits = append([]byte{}, prysmaticlabs_go_bitfield.Bitvector4(s17)...)
274 |
275 | // Field 18: PreviousJustifiedCheckpoint
276 | c.PreviousJustifiedCheckpoint = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Checkpoint)
277 | if err = c.PreviousJustifiedCheckpoint.UnmarshalSSZ(s18); err != nil {
278 | return err
279 | }
280 |
281 | // Field 19: CurrentJustifiedCheckpoint
282 | c.CurrentJustifiedCheckpoint = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Checkpoint)
283 | if err = c.CurrentJustifiedCheckpoint.UnmarshalSSZ(s19); err != nil {
284 | return err
285 | }
286 |
287 | // Field 20: FinalizedCheckpoint
288 | c.FinalizedCheckpoint = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Checkpoint)
289 | if err = c.FinalizedCheckpoint.UnmarshalSSZ(s20); err != nil {
290 | return err
291 | }
292 | return err
293 | }
294 |
--------------------------------------------------------------------------------
/sszgen/backend/testdata/TestUnmarshalSteps.expected:
--------------------------------------------------------------------------------
1 | s0 := buf[0:8] // c.GenesisTime
2 | s1 := buf[8:40] // c.GenesisValidatorsRoot
3 | s2 := buf[40:48] // c.Slot
4 | s3 := buf[48:64] // c.Fork
5 | s4 := buf[64:176] // c.LatestBlockHeader
6 | s5 := buf[176:262320] // c.BlockRoots
7 | s6 := buf[262320:524464] // c.StateRoots
8 | s8 := buf[524468:524540] // c.Eth1Data
9 | s10 := buf[524544:524552] // c.Eth1DepositIndex
10 | s13 := buf[524560:2621712] // c.RandaoMixes
11 | s14 := buf[2621712:2687248] // c.Slashings
12 | s17 := buf[2687256:2687257] // c.JustificationBits
13 | s18 := buf[2687257:2687297] // c.PreviousJustifiedCheckpoint
14 | s19 := buf[2687297:2687337] // c.CurrentJustifiedCheckpoint
15 | s20 := buf[2687337:2687377] // c.FinalizedCheckpoint
16 |
17 | v7 := ssz.ReadOffset(buf[524464:524468]) // c.HistoricalRoots
18 | if v7 < 2687377 {
19 | return ssz.ErrInvalidVariableOffset
20 | }
21 | if v7 > size {
22 | return ssz.ErrOffset
23 | }
24 | v9 := ssz.ReadOffset(buf[524540:524544]) // c.Eth1DataVotes
25 | if v9 > size || v9 < v7 {
26 | return ssz.ErrOffset
27 | }
28 | v11 := ssz.ReadOffset(buf[524552:524556]) // c.Validators
29 | if v11 > size || v11 < v9 {
30 | return ssz.ErrOffset
31 | }
32 | v12 := ssz.ReadOffset(buf[524556:524560]) // c.Balances
33 | if v12 > size || v12 < v11 {
34 | return ssz.ErrOffset
35 | }
36 | v15 := ssz.ReadOffset(buf[2687248:2687252]) // c.PreviousEpochAttestations
37 | if v15 > size || v15 < v12 {
38 | return ssz.ErrOffset
39 | }
40 | v16 := ssz.ReadOffset(buf[2687252:2687256]) // c.CurrentEpochAttestations
41 | if v16 > size || v16 < v15 {
42 | return ssz.ErrOffset
43 | }
44 | s7 := buf[v7:v9] // c.HistoricalRoots
45 | s9 := buf[v9:v11] // c.Eth1DataVotes
46 | s11 := buf[v11:v12] // c.Validators
47 | s12 := buf[v12:v15] // c.Balances
48 | s15 := buf[v15:v16] // c.PreviousEpochAttestations
49 | s16 := buf[v16:] // c.CurrentEpochAttestations
50 |
--------------------------------------------------------------------------------
/sszgen/backend/uint.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
7 | )
8 |
9 | type generateUint struct {
10 | valRep *types.ValueUint
11 | targetPackage string
12 | casterConfig
13 | importNamer *ImportNamer
14 | }
15 |
16 | func (g *generateUint) coerce() func(string) string {
17 | return func(fieldName string) string {
18 | return fmt.Sprintf("%s(%s)", g.valRep.TypeName(), fieldName)
19 | }
20 | }
21 |
22 | func (g *generateUint) generateUnmarshalValue(fieldName string, offset string) string {
23 | // misspelling of Unmarshall due to misspelling of method exported by fastssz
24 | convert := fmt.Sprintf("ssz.UnmarshallUint%d(%s)", g.valRep.Size, offset)
25 | return fmt.Sprintf("%s = %s", fieldName, g.casterConfig.toOverlay(convert))
26 | }
27 |
28 | func (g *generateUint) generateFixedMarshalValue(fieldName string) string {
29 | return fmt.Sprintf("dst = ssz.MarshalUint%d(dst, %s)", g.valRep.Size, fieldName)
30 | }
31 |
32 | func (g *generateUint) generateHTRPutter(fieldName string) string {
33 | return fmt.Sprintf("hh.PutUint%d(%s)", g.valRep.Size, fieldName)
34 | }
35 |
36 | func (g *generateUint) variableSizeSSZ(fieldname string) string {
37 | return ""
38 | }
39 |
40 | var _ valueGenerator = &generateUint{}
41 |
--------------------------------------------------------------------------------
/sszgen/backend/union.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
5 | )
6 |
7 | type generateUnion struct {
8 | *types.ValueUnion
9 | targetPackage string
10 | importNamer *ImportNamer
11 | }
12 |
13 | func (g *generateUnion) generateHTRPutter(fieldName string) string {
14 | return ""
15 | }
16 |
17 | func (g *generateUnion) generateUnmarshalValue(fieldName string, s string) string {
18 | return ""
19 | }
20 |
21 | func (g *generateUnion) generateFixedMarshalValue(fieldName string) string {
22 | return ""
23 | }
24 |
25 | func (g *generateUnion) variableSizeSSZ(fieldname string) string {
26 | return ""
27 | }
28 |
29 | var _ valueGenerator = &generateUnion{}
30 |
--------------------------------------------------------------------------------
/sszgen/backend/vector.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "text/template"
7 |
8 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
9 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
10 | )
11 |
12 | type generateVector struct {
13 | valRep *types.ValueVector
14 | targetPackage string
15 | casterConfig
16 | importNamer *ImportNamer
17 | }
18 |
19 | func (g *generateVector) generateUnmarshalValue(fieldName string, sliceName string) string {
20 | gg := newValueGenerator(interfaces.SszUnmarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer)
21 | switch g.valRep.ElementValue.(type) {
22 | case *types.ValueByte:
23 | t := `%s = make([]byte, 0, %d)
24 | %s = append(%s, %s...)`
25 | return fmt.Sprintf(t, fieldName, g.valRep.Size, fieldName, fieldName, g.casterConfig.toOverlay(sliceName))
26 | default:
27 | loopVar := "i"
28 | if fieldName[0:1] == "i" && monoCharacter(fieldName) {
29 | loopVar = fieldName + "i"
30 | }
31 | t := `{
32 | var tmp {{ .TypeName }}
33 | {{.FieldName}} = make([]{{.TypeName}}, {{.NumElements}})
34 | for {{ .LoopVar }} := 0; {{ .LoopVar }} < {{ .NumElements }}; {{ .LoopVar }} ++ {
35 | tmpSlice := {{ .SliceName }}[{{ .LoopVar }}*{{ .NestedFixedSize }}:(1+{{ .LoopVar }})*{{ .NestedFixedSize }}]
36 | {{ .NestedUnmarshal }}
37 | {{ .FieldName }}[{{.LoopVar}}] = tmp
38 | }
39 | }`
40 | tmpl, err := template.New("tmplgenerateUnmarshalValueDefault").Parse(t)
41 | if err != nil {
42 | panic(err)
43 | }
44 | buf := bytes.NewBuffer(nil)
45 | nvr := g.valRep.ElementValue
46 | err = tmpl.Execute(buf, struct {
47 | TypeName string
48 | SliceName string
49 | NumElements int
50 | NestedFixedSize int
51 | LoopVar string
52 | NestedUnmarshal string
53 | FieldName string
54 | }{
55 | TypeName: fullyQualifiedTypeName(nvr, g.targetPackage, g.importNamer),
56 | SliceName: sliceName,
57 | NumElements: g.valRep.FixedSize() / g.valRep.ElementValue.FixedSize(),
58 | NestedFixedSize: g.valRep.ElementValue.FixedSize(),
59 | LoopVar: loopVar,
60 | NestedUnmarshal: gg.generateUnmarshalValue("tmp", "tmpSlice"),
61 | FieldName: fieldName,
62 | })
63 | if err != nil {
64 | panic(err)
65 | }
66 | return buf.String()
67 | }
68 | }
69 |
70 | var tmplGenerateMarshalValueVector = `if len({{.FieldName}}) != {{.Size}} {
71 | return nil, ssz.ErrBytesLength
72 | }
73 | {{.MarshalValue}}`
74 |
75 | func (g *generateVector) generateFixedMarshalValue(fieldName string) string {
76 | mvTmpl, err := template.New("tmplGenerateMarshalValueVector").Parse(tmplGenerateMarshalValueVector)
77 | if err != nil {
78 | panic(err)
79 | }
80 | var marshalValue string
81 | switch g.valRep.ElementValue.(type) {
82 | case *types.ValueByte:
83 | if g.valRep.IsArray {
84 | marshalValue = fmt.Sprintf("dst = append(dst, %s[:]...)", fieldName)
85 | } else {
86 | marshalValue = fmt.Sprintf("dst = append(dst, %s...)", fieldName)
87 | }
88 | default:
89 | nestedFieldName := "o"
90 | if fieldName[0:1] == "o" && monoCharacter(fieldName) {
91 | nestedFieldName = fieldName + "o"
92 | }
93 | t := `for _, %s := range %s {
94 | %s
95 | }`
96 | gg := newValueGenerator(interfaces.SszMarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer)
97 | internal := gg.generateFixedMarshalValue(nestedFieldName)
98 | marshalValue = fmt.Sprintf(t, nestedFieldName, fieldName, internal)
99 | }
100 | buf := bytes.NewBuffer(nil)
101 | err = mvTmpl.Execute(buf, struct {
102 | FieldName string
103 | Size int
104 | MarshalValue string
105 | }{
106 | FieldName: fieldName,
107 | Size: g.valRep.Size,
108 | MarshalValue: marshalValue,
109 | })
110 | if err != nil {
111 | panic(err)
112 | }
113 | return buf.String()
114 | }
115 |
116 | var generateVectorHTRPutterTmpl = `{
117 | if len({{.FieldName}}) != {{.Size}} {
118 | return ssz.ErrVectorLength
119 | }
120 | subIndx := hh.Index()
121 | for _, {{.NestedFieldName}} := range {{.FieldName}} {
122 | {{.AppendCall}}
123 | }
124 | {{.Merkleize}}
125 | }`
126 |
127 | type vecPutterElements struct {
128 | FieldName string
129 | NestedFieldName string
130 | Size int
131 | AppendCall string
132 | Merkleize string
133 | }
134 |
135 | func renderHtrVecPutter(lpe vecPutterElements) string {
136 | tmpl, err := template.New("renderHtrVecPutter").Parse(generateVectorHTRPutterTmpl)
137 | if err != nil {
138 | panic(err)
139 | }
140 | buf := bytes.NewBuffer(nil)
141 | err = tmpl.Execute(buf, lpe)
142 | if err != nil {
143 | panic(err)
144 | }
145 | return buf.String()
146 | }
147 |
148 | func (g *generateVector) isByteVector() bool {
149 | _, isByte := g.valRep.ElementValue.(*types.ValueByte)
150 | return isByte
151 | }
152 |
153 | const ByteChunkSize = 32
154 |
155 | func (g *generateVector) renderByteSliceAppend(fieldName string) string {
156 | if g.valRep.Size%ByteChunkSize == 0 {
157 | if g.valRep.IsVariableSized() {
158 | return fmt.Sprintf(byteSliceAppendTpl, fieldName, g.valRep.Size, fieldName)
159 | } else {
160 | return fmt.Sprintf(byteSliceAppendTplFixedSize, fieldName, g.valRep.Size, fieldName)
161 | }
162 | } else {
163 | return fmt.Sprintf(byteSlicePutBytesTpl, fieldName, g.valRep.Size, fieldName)
164 | }
165 | }
166 |
167 | var byteSliceAppendTpl = `if len(%s) != %d {
168 | return ssz.ErrBytesLength
169 | }
170 | hh.Append(%s)`
171 |
172 | var byteSliceAppendTplFixedSize = `if len(%s) != %d {
173 | return ssz.ErrBytesLength
174 | }
175 | hh.Append(%s[:])`
176 |
177 | var byteSlicePutBytesTpl = `if len(%s) != %d {
178 | return ssz.ErrBytesLength
179 | }
180 | hh.PutBytes(%s)`
181 |
182 | func (g *generateVector) generateHTRPutter(fieldName string) string {
183 | nestedFieldName := "o"
184 | if fieldName[0:1] == "o" && monoCharacter(fieldName) {
185 | nestedFieldName = fieldName + "o"
186 | }
187 |
188 | // resolve pointers and overlays to their underlying types
189 | vr := g.valRep.ElementValue
190 | if vrp, isPointer := vr.(*types.ValuePointer); isPointer {
191 | vr = vrp.Referent
192 | }
193 | if vro, isOverlay := vr.(*types.ValueOverlay); isOverlay {
194 | vr = vro.Underlying
195 | }
196 |
197 | vpe := vecPutterElements{
198 | FieldName: fieldName,
199 | NestedFieldName: nestedFieldName,
200 | Size: g.valRep.Size,
201 | }
202 |
203 | switch v := vr.(type) {
204 | case *types.ValueByte:
205 | return fmt.Sprintf(byteSlicePutBytesTpl, fieldName, g.valRep.Size, fieldName)
206 | case *types.ValueVector:
207 | gv := &generateVector{valRep: v, targetPackage: g.targetPackage}
208 | if gv.isByteVector() {
209 | vpe.AppendCall = gv.renderByteSliceAppend(nestedFieldName)
210 | vpe.Merkleize = "hh.Merkleize(subIndx)"
211 | return renderHtrVecPutter(vpe)
212 | }
213 | case *types.ValueContainer:
214 | gc := &generateContainer{ValueContainer: v, targetPackage: g.targetPackage}
215 | return gc.generateHTRPutter(fieldName)
216 | case *types.ValueUint:
217 | vpe.AppendCall = fmt.Sprintf("hh.AppendUint%d(%s)", v.Size, nestedFieldName)
218 | vpe.Merkleize = "hh.Merkleize(subIndx)"
219 | return renderHtrVecPutter(vpe)
220 | default:
221 | panic(fmt.Sprintf("unsupported type combination - %v vector of %v", fieldName, v))
222 | }
223 | return ""
224 | }
225 |
226 | func monoCharacter(s string) bool {
227 | ch := s[0]
228 | for i := 1; i < len(s); i++ {
229 | if s[i] == ch {
230 | continue
231 | }
232 | return false
233 | }
234 | return true
235 | }
236 |
237 | var variableSizedVectorTmpl = `func() int {
238 | s := 0
239 | for _, o := range {{ .FieldName }} {
240 | s += 4
241 | s += {{ .SizeComputation }}
242 | }
243 | return s
244 | }()`
245 |
246 | func (g *generateVector) variableSizeSSZ(fieldName string) string {
247 | // TODO(rjl493456442) if the element of vector is fixed size,
248 | // then the vector itself must be fixed as well. This clause
249 | // should be removed.
250 | if !g.valRep.ElementValue.IsVariableSized() {
251 | return fmt.Sprintf("len(%s) * %d", fieldName, g.valRep.ElementValue.FixedSize())
252 | }
253 | gg := newValueGenerator(interfaces.SszMarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer)
254 | vslTmpl, err := template.New("variableSizedVectorTmpl").Parse(variableSizedVectorTmpl)
255 | if err != nil {
256 | panic(err)
257 | }
258 | buf := bytes.NewBuffer(nil)
259 | err = vslTmpl.Execute(buf, struct {
260 | FieldName string
261 | SizeComputation string
262 | }{
263 | FieldName: fieldName,
264 | SizeComputation: gg.variableSizeSSZ("o"),
265 | })
266 | if err != nil {
267 | panic(err)
268 | }
269 | return buf.String()
270 | }
271 |
272 | func (g *generateVector) coerce() func(string) string {
273 | return func(fieldName string) string {
274 | return fmt.Sprintf("%s(%s)", g.valRep.TypeName(), fieldName)
275 | }
276 | }
277 |
278 | var _ valueGenerator = &generateVector{}
279 |
--------------------------------------------------------------------------------
/sszgen/backend/visitor.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import "github.com/OffchainLabs/methodical-ssz/sszgen/types"
4 |
5 | type visitor func(vr types.ValRep)
6 |
7 | func visit(vr types.ValRep, v visitor) {
8 | v(vr)
9 | switch t := vr.(type) {
10 | case *types.ValueContainer:
11 | for _, f := range t.Contents {
12 | visit(f.Value, v)
13 | }
14 | case *types.ValueVector:
15 | visit(t.ElementValue, v)
16 | case *types.ValueList:
17 | visit(t.ElementValue, v)
18 | case *types.ValuePointer:
19 | visit(t.Referent, v)
20 | case *types.ValueOverlay:
21 | visit(t.Underlying, v)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/sszgen/interfaces/ssz.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import (
4 | "go/types"
5 |
6 | "golang.org/x/tools/go/packages"
7 | )
8 |
9 | var (
10 | SszMarshaler *types.Interface
11 | SszUnmarshaler *types.Interface
12 | SszFullHasher *types.Interface
13 | SszLightHasher *types.Interface
14 | )
15 |
16 | func NewSSZSupportMap(t types.Type) map[*types.Interface]bool {
17 | return map[*types.Interface]bool{
18 | SszMarshaler: types.Implements(t, SszMarshaler) || types.Implements(types.NewPointer(t), SszMarshaler),
19 | SszUnmarshaler: types.Implements(t, SszUnmarshaler) || types.Implements(types.NewPointer(t), SszUnmarshaler),
20 | SszLightHasher: types.Implements(t, SszLightHasher) || types.Implements(types.NewPointer(t), SszLightHasher),
21 | SszFullHasher: types.Implements(t, SszFullHasher) || types.Implements(types.NewPointer(t), SszFullHasher),
22 | }
23 | }
24 |
25 | func init() {
26 | pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedTypes}, "github.com/prysmaticlabs/fastssz")
27 | if err != nil {
28 | panic(err)
29 | }
30 | if len(pkgs) == 0 {
31 | panic("missing package, add github.com/prysmaticlabs/fastssz to your go.mod")
32 | }
33 | SszMarshaler = pkgs[0].Types.Scope().Lookup("Marshaler").Type().Underlying().(*types.Interface)
34 | SszUnmarshaler = pkgs[0].Types.Scope().Lookup("Unmarshaler").Type().Underlying().(*types.Interface)
35 | SszFullHasher = pkgs[0].Types.Scope().Lookup("HashRoot").Type().Underlying().(*types.Interface)
36 |
37 | for i := 0; i < SszFullHasher.NumMethods(); i++ {
38 | method := SszFullHasher.Method(i)
39 | if method.Name() == "HashTreeRoot" {
40 | SszLightHasher = types.NewInterfaceType([]*types.Func{method}, nil)
41 | break
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/sszgen/node.go:
--------------------------------------------------------------------------------
1 | package sszgen
2 |
3 | import (
4 | "fmt"
5 | "go/token"
6 | "go/types"
7 | "os"
8 | )
9 |
10 | // TypeDef represents the intermediate struct type used during marshaling.
11 | // This is the input data to all the Go code templates.
12 | type TypeDef struct {
13 | Name string
14 | PackageName string
15 | IsStruct bool
16 | Fields []*FieldDef
17 | fs *token.FileSet
18 | orig *types.Named
19 | scope *fileScope
20 | object types.Object
21 | }
22 |
23 | // FieldDef represents a field of the intermediate marshaling type.
24 | type FieldDef struct {
25 | name string
26 | typ types.Type
27 | tag string
28 | pkg *types.Package
29 | }
30 |
31 | func newStructDef(fs *token.FileSet, imp types.Importer, typ *types.Named, packageName string) *TypeDef {
32 | mtyp := &TypeDef{
33 | Name: typ.Obj().Name(),
34 | PackageName: packageName,
35 | IsStruct: true,
36 | fs: fs,
37 | orig: typ,
38 | }
39 |
40 | styp := typ.Underlying().(*types.Struct)
41 | mtyp.scope = newFileScope(imp, typ.Obj().Pkg())
42 | mtyp.scope.addReferences(styp)
43 |
44 | // Add packages which are always needed.
45 | mtyp.scope.addImport("encoding/json")
46 | mtyp.scope.addImport("errors")
47 |
48 | for i := 0; i < styp.NumFields(); i++ {
49 | f := styp.Field(i)
50 | if !f.Exported() {
51 | continue
52 | }
53 | if f.Anonymous() {
54 | fmt.Fprintf(os.Stderr, "Warning: ignoring embedded field %s\n", f.Name())
55 | continue
56 | }
57 |
58 | switch ftyp := f.Type().(type) {
59 | case *types.Pointer:
60 | fn, ok := ftyp.Elem().(*types.Named)
61 | if ok {
62 | mf := &FieldDef{
63 | name: f.Name(),
64 | typ: f.Type(),
65 | tag: styp.Tag(i),
66 | pkg: fn.Obj().Pkg(),
67 | }
68 | mtyp.Fields = append(mtyp.Fields, mf)
69 | continue
70 | }
71 | }
72 | mf := &FieldDef{
73 | name: f.Name(),
74 | typ: f.Type(),
75 | tag: styp.Tag(i),
76 | pkg: f.Pkg(),
77 | }
78 | mtyp.Fields = append(mtyp.Fields, mf)
79 | }
80 | return mtyp
81 | }
82 |
83 | func newPrimitiveDef(fs *token.FileSet, imp types.Importer, typ *types.Named, packageName string) *TypeDef {
84 | mtyp := &TypeDef{
85 | Name: typ.Obj().Name(),
86 | PackageName: packageName,
87 | IsStruct: false,
88 | fs: fs,
89 | orig: typ,
90 | }
91 | mtyp.scope = newFileScope(imp, typ.Obj().Pkg())
92 | mtyp.scope.addReferences(typ.Underlying())
93 |
94 | // Add packages which are always needed.
95 | mtyp.scope.addImport("encoding/json")
96 | mtyp.scope.addImport("errors")
97 |
98 | fd := &FieldDef{
99 | name: typ.Underlying().String(),
100 | typ: typ.Underlying(),
101 | tag: "",
102 | pkg: typ.Obj().Pkg(),
103 | }
104 | mtyp.Fields = append(mtyp.Fields, fd)
105 | return mtyp
106 | }
107 |
--------------------------------------------------------------------------------
/sszgen/parser.go:
--------------------------------------------------------------------------------
1 | package sszgen
2 |
3 | import (
4 | "fmt"
5 | "go/format"
6 | "go/importer"
7 | "go/token"
8 | "go/types"
9 | "regexp"
10 | "strings"
11 |
12 | "github.com/OffchainLabs/methodical-ssz/sszgen/backend"
13 | "golang.org/x/tools/go/packages"
14 | )
15 |
16 | type GoPathScoper struct {
17 | packagePath string
18 | fieldNames []string
19 | pkg *types.Package
20 | }
21 |
22 | func NewGoPathScoper(packageName string) (*GoPathScoper, error) {
23 | cfg := &packages.Config{
24 | Mode: packages.NeedTypes | packages.NeedDeps | packages.NeedImports,
25 | }
26 | pkgs, err := packages.Load(cfg, []string{packageName}...)
27 | if err != nil {
28 | return nil, err
29 | }
30 | for _, pkg := range pkgs {
31 | if pkg.ID != packageName {
32 | continue
33 | }
34 |
35 | pp := &GoPathScoper{packagePath: pkg.ID, pkg: pkg.Types}
36 | return pp, nil
37 | }
38 | return nil, fmt.Errorf("package named '%s' could not be loaded from the go build system. Please make sure the current folder contains the go.mod for the target package, or that its go.mod is in a parent directory", packageName)
39 | }
40 |
41 | type PathScoper interface {
42 | Path() string
43 | Scope() *types.Scope
44 | }
45 |
46 | func (pp *GoPathScoper) Path() string {
47 | return pp.packagePath
48 | }
49 |
50 | func (pp *GoPathScoper) Scope() *types.Scope {
51 | return pp.pkg.Scope()
52 | }
53 |
54 | func TypeDefs(ps PathScoper, fieldNames ...string) ([]*TypeDef, error) {
55 | fileSet := token.NewFileSet()
56 | imp := importer.Default()
57 |
58 | // If no field names are requested, use all
59 | if fieldNames == nil {
60 | fieldNames = ps.Scope().Names()
61 | }
62 |
63 | results := make([]*TypeDef, len(fieldNames))
64 | for i, typeName := range fieldNames {
65 | typ, obj, err := lookupType(ps.Scope(), typeName)
66 | if err != nil {
67 | return nil, err
68 | }
69 | var mtyp *TypeDef
70 | if _, ok := typ.Underlying().(*types.Struct); ok {
71 | mtyp = newStructDef(fileSet, imp, typ, ps.Path())
72 | } else {
73 | mtyp = newPrimitiveDef(fileSet, imp, typ, ps.Path())
74 | }
75 | mtyp.object = obj
76 | results[i] = mtyp
77 | }
78 | return results, nil
79 | }
80 |
81 | var structTagRe = regexp.MustCompile(`\s+"(.*)"$`)
82 |
83 | func reformatStructTag(line string) string {
84 | line = structTagRe.ReplaceAllString(line, " `$1`")
85 | return strings.ReplaceAll(line, `\"`, `"`)
86 | }
87 |
88 | func (pp *GoPathScoper) TypeDefSourceCode(defs []*TypeDef) ([]byte, error) {
89 | in := backend.NewImportNamer(pp.pkg.Path(), nil)
90 | structs := make([]string, 0)
91 | for _, def := range defs {
92 | obj := def.object
93 | defstring := types.ObjectString(obj, in.Name)
94 | // add a little whitespace for nicer formatting
95 | defstring = strings.ReplaceAll(defstring, ";", "\n")
96 | defstring = strings.ReplaceAll(defstring, "{", "{\n")
97 | defstring = strings.ReplaceAll(defstring, "}", "\n}\n")
98 | lines := strings.Split(defstring, "\n")
99 | for i := 0; i < len(lines); i++ {
100 | lines[i] = strings.TrimSpace(lines[i])
101 | lines[i] = reformatStructTag(lines[i])
102 | }
103 | structs = append(structs, strings.Join(lines, "\n"))
104 | }
105 |
106 | source := "package " + backend.RenderedPackageName(pp.pkg.Path()) + "\n\n" +
107 | in.ImportSource() +
108 | strings.Join(structs, "\n")
109 | return format.Source([]byte(source))
110 | }
111 |
--------------------------------------------------------------------------------
/sszgen/parser_test.go:
--------------------------------------------------------------------------------
1 | package sszgen
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/prysmaticlabs/prysm/v3/testing/require"
7 | )
8 |
9 | func TestReformatStructTags(t *testing.T) {
10 | decl := `PublicKey []byte "protobuf:\"bytes,1,opt,name=public_key,json=publicKey,proto3\" json:\"public_key,omitempty\" spec-name:\"pubkey\" ssz-size:\"48\""`
11 | // unquoted quotation marks should be converted to backticks
12 | expected := "PublicKey []byte `protobuf:\"bytes,1,opt,name=public_key,json=publicKey,proto3\" json:\"public_key,omitempty\" spec-name:\"pubkey\" ssz-size:\"48\"`"
13 | got := reformatStructTag(decl)
14 | require.Equal(t, expected, got)
15 | }
16 |
--------------------------------------------------------------------------------
/sszgen/representer.go:
--------------------------------------------------------------------------------
1 | package sszgen
2 |
3 | import (
4 | "fmt"
5 | "go/types"
6 |
7 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
8 | gentypes "github.com/OffchainLabs/methodical-ssz/sszgen/types"
9 | "github.com/pkg/errors"
10 | )
11 |
12 | type FieldParserOpt func(*FieldParser)
13 |
14 | func WithDisableDelegation() FieldParserOpt {
15 | return func(p *FieldParser) {
16 | p.disableDelegation = true
17 | }
18 | }
19 |
20 | func ParseTypeDef(typ *TypeDef, opts ...FieldParserOpt) (gentypes.ValRep, error) {
21 | p := &FieldParser{}
22 | for _, o := range opts {
23 | o(p)
24 | }
25 | if typ.IsStruct {
26 | vr := &gentypes.ValueContainer{
27 | Name: typ.Name,
28 | Package: typ.orig.Obj().Pkg().Path(),
29 | }
30 | for _, f := range typ.Fields {
31 | rep, err := p.expand(f)
32 | if err != nil {
33 | return nil, err
34 | }
35 | vr.Append(f.name, rep)
36 | }
37 | return vr, nil
38 | }
39 | // PrimitiveType is stored in Fields[0]
40 | rep, err := p.expand(typ.Fields[0])
41 | if err != nil {
42 | return nil, err
43 | }
44 | vr := &gentypes.ValueOverlay{
45 | Name: typ.Name,
46 | Package: rep.PackagePath(),
47 | Underlying: rep,
48 | }
49 | return vr, nil
50 | }
51 |
52 | type FieldParser struct {
53 | disableDelegation bool
54 | }
55 |
56 | func (p *FieldParser) expand(f *FieldDef) (gentypes.ValRep, error) {
57 | switch ty := f.typ.(type) {
58 | case *types.Array:
59 | size := int(ty.Len())
60 | return p.expandArray([]*SSZDimension{{VectorLength: &size}}, f)
61 | case *types.Slice:
62 | return p.expandArrayHead(f)
63 | case *types.Pointer:
64 | vr, err := p.expand(&FieldDef{name: f.name, tag: f.tag, typ: ty.Elem(), pkg: f.pkg})
65 | if err != nil {
66 | return nil, err
67 | }
68 | v := &gentypes.ValuePointer{Referent: vr}
69 | if !p.disableDelegation {
70 | v.Interfaces = interfaces.NewSSZSupportMap(ty)
71 | }
72 | return v, nil
73 | case *types.Struct:
74 | container := gentypes.ValueContainer{
75 | Name: f.name,
76 | Package: f.pkg.Path(),
77 | }
78 | if !p.disableDelegation {
79 | container.Interfaces = interfaces.NewSSZSupportMap(ty)
80 | }
81 | for i := 0; i < ty.NumFields(); i++ {
82 | field := ty.Field(i)
83 | if field.Name() == "" || !field.Exported() {
84 | continue
85 | }
86 | rep, err := p.expand(&FieldDef{name: field.Name(), tag: ty.Tag(i), typ: field.Type(), pkg: field.Pkg()})
87 | if err != nil {
88 | return nil, err
89 | }
90 | container.Append(f.name, rep)
91 | }
92 | return &container, nil
93 | case *types.Named:
94 | exp, err := p.expand(&FieldDef{name: ty.Obj().Name(), tag: f.tag, typ: ty.Underlying(), pkg: f.pkg})
95 | switch ty.Underlying().(type) {
96 | case *types.Struct:
97 | return exp, err
98 | default:
99 | v := &gentypes.ValueOverlay{
100 | Name: ty.Obj().Name(),
101 | Package: ty.Obj().Pkg().Path(),
102 | Underlying: exp,
103 | }
104 | if !p.disableDelegation {
105 | v.Interfaces = interfaces.NewSSZSupportMap(ty)
106 | }
107 | return v, err
108 | }
109 | case *types.Basic:
110 | return p.expandIdent(ty.Kind(), ty.Name())
111 | default:
112 | return nil, fmt.Errorf("unsupported type for %v with name: %v", ty, f.name)
113 | }
114 | }
115 |
116 | func (p *FieldParser) expandArrayHead(f *FieldDef) (gentypes.ValRep, error) {
117 | dims, err := extractSSZDimensions(fmt.Sprintf("`%v`", f.tag))
118 | if err != nil {
119 | return nil, errors.Wrapf(err, "name=%s, package=%s, tag=%s", f.name, f.pkg.Path(), f.tag)
120 | }
121 | return p.expandArray(dims, f)
122 | }
123 |
124 | func (p *FieldParser) expandArray(dims []*SSZDimension, f *FieldDef) (gentypes.ValRep, error) {
125 | if len(dims) == 0 {
126 | return nil, fmt.Errorf("do not have dimension information for type %v", f.name)
127 | }
128 | d := dims[0]
129 | var (
130 | elv gentypes.ValRep
131 | err error
132 | elem types.Type
133 | )
134 | isArray := false
135 | // at this point f.typ is either and array or a slice
136 | if arr, ok := f.typ.(*types.Array); ok {
137 | isArray = true
138 | elem = arr.Elem()
139 | } else if arr, ok := f.typ.(*types.Slice); ok {
140 | elem = arr.Elem()
141 | } else {
142 | return nil, fmt.Errorf("invalid typ in expand array: %v with name: %v ", f.typ, f.name)
143 | }
144 |
145 | // Only expand the inner array if it is not a named type
146 | if _, ok := elem.(*types.Named); !ok && len(dims) > 1 {
147 | elv, err = p.expandArray(dims[1:], &FieldDef{name: f.name, typ: elem.Underlying(), pkg: f.pkg})
148 | if err != nil {
149 | return nil, err
150 | }
151 | } else {
152 | elv, err = p.expand(&FieldDef{name: f.name, tag: f.tag, typ: elem, pkg: f.pkg})
153 | if err != nil {
154 | return nil, err
155 | }
156 | }
157 |
158 | if d.IsVector() {
159 | return &gentypes.ValueVector{
160 | IsArray: isArray,
161 | ElementValue: elv,
162 | Size: d.VectorLen(),
163 | }, nil
164 | }
165 | if d.IsList() {
166 | return &gentypes.ValueList{
167 | ElementValue: elv,
168 | MaxSize: d.ListLen(),
169 | }, nil
170 | }
171 | return nil, nil
172 | }
173 |
174 | func (p *FieldParser) expandIdent(ident types.BasicKind, name string) (gentypes.ValRep, error) {
175 | switch ident {
176 | case types.Bool:
177 | return &gentypes.ValueBool{Name: name}, nil
178 | case types.Byte:
179 | return &gentypes.ValueByte{Name: name}, nil
180 | case types.Uint16:
181 | return &gentypes.ValueUint{Size: 16, Name: name}, nil
182 | case types.Uint32:
183 | return &gentypes.ValueUint{Size: 32, Name: name}, nil
184 | case types.Uint64:
185 | return &gentypes.ValueUint{Size: 64, Name: name}, nil
186 | /*
187 | case "uint128":
188 | return &gentypes.ValueUint{Size: 128, Name: ident.name}, nil
189 | case "uint256":
190 | return &gentypes.ValueUint{Size: 256, Name: ident.name}, nil
191 | */
192 | default:
193 | return nil, fmt.Errorf("unknown ident: %v", name)
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/sszgen/tagparse.go:
--------------------------------------------------------------------------------
1 | package sszgen
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 | "text/scanner"
8 |
9 | "github.com/pkg/errors"
10 | )
11 |
12 | type tokenState int
13 |
14 | const (
15 | tsBegin tokenState = iota
16 | tsLabel
17 | tsValue
18 | tsCloseTick
19 | )
20 |
21 | type TagParser struct {
22 | sc scanner.Scanner
23 | buffer string
24 | }
25 |
26 | func (tp *TagParser) Init(tag string) {
27 | sr := strings.NewReader(tag)
28 | tp.sc = scanner.Scanner{}
29 | tp.sc.Init(sr)
30 | tp.sc.Filename = "tag"
31 | tp.sc.Mode ^= scanner.ScanRawStrings
32 | }
33 |
34 | func (tp TagParser) GetSSZTags() map[string]string {
35 | var labelStr string
36 | var state tokenState
37 | tags := make(map[string]string)
38 | for tok := tp.sc.Scan(); tok != scanner.EOF; tok = tp.sc.Scan() {
39 | if state == tsCloseTick {
40 | panic("undefined beyhavior when scanning beyond the end of the tag")
41 | }
42 | txt := tp.sc.TokenText()
43 | switch txt {
44 | case "`":
45 | if state == tsLabel {
46 | state = tsCloseTick
47 | continue
48 | }
49 | if state == tsBegin {
50 | state = tsLabel
51 | continue
52 | }
53 | case ":":
54 | if state == tsLabel {
55 | state = tsValue
56 | continue
57 | }
58 | case "\"":
59 | continue
60 | default:
61 | if state == tsValue {
62 | tags[labelStr] = trimQuotes(txt)
63 | state = tsLabel
64 | labelStr = ""
65 | continue
66 | }
67 | if state == tsLabel {
68 | labelStr += txt
69 | continue
70 | }
71 | }
72 | }
73 | return tags
74 | }
75 |
76 | // cannot compare untyped nil to typed nil
77 | // this value gives us a nil with type of *int
78 | // to compare to ssz-size = '?' values
79 | var nilInt *int
80 |
81 | func extractSSZDimensions(tag string) ([]*SSZDimension, error) {
82 | tp := &TagParser{}
83 | tp.Init(tag)
84 | tags := tp.GetSSZTags()
85 | szStr, sizeDefined := tags["ssz-size"]
86 | sizes := strings.Split(szStr, ",")
87 | maxStr, maxDefined := tags["ssz-max"]
88 | dims := make([]*SSZDimension, 0)
89 | maxes := strings.Split(maxStr, ",")
90 | if !sizeDefined {
91 | if !maxDefined {
92 | return nil, fmt.Errorf("no ssz-size or ssz-max tags found for element")
93 | }
94 | for _, m := range maxes {
95 | max, err := strconv.Atoi(m)
96 | if err != nil {
97 | return nil, errors.Wrapf(err, "error parsing ssz-size=%s, ssz-max=%s", szStr, maxStr)
98 | }
99 | dims = append(dims, &SSZDimension{ListLength: &max})
100 | }
101 | return dims, nil
102 | }
103 | for i := 0; i < len(sizes); i++ {
104 | if sizes[i] == "?" {
105 | if len(maxes) <= i {
106 | return nil, fmt.Errorf("more than one wildcard in ssz-size, or ssz-max undefined in tag %s", tag)
107 | }
108 | max, err := strconv.Atoi(maxes[i])
109 | if err != nil {
110 | return nil, err
111 | }
112 | dims = append(dims, &SSZDimension{ListLength: &max})
113 | } else {
114 | vsize, err := strconv.Atoi(sizes[i])
115 | if err != nil {
116 | return nil, err
117 | }
118 | dims = append(dims, &SSZDimension{VectorLength: &vsize})
119 | }
120 | }
121 | return dims, nil
122 | }
123 |
124 | type SSZDimension struct {
125 | VectorLength *int
126 | ListLength *int
127 | }
128 |
129 | func (dim *SSZDimension) IsVector() bool {
130 | return dim.VectorLength != nilInt
131 | }
132 |
133 | func (dim *SSZDimension) IsList() bool {
134 | return dim.ListLength != nilInt
135 | }
136 |
137 | func (dim *SSZDimension) ListLen() int {
138 | return *dim.ListLength
139 | }
140 |
141 | func (dim *SSZDimension) VectorLen() int {
142 | return *dim.VectorLength
143 | }
144 |
145 | type SSZListBounds struct {
146 | SSZSize []*int
147 | SSZMax *int
148 | }
149 |
150 | func trimQuotes(s string) string {
151 | if len(s) > 0 && s[0] == '"' {
152 | s = s[1:]
153 | }
154 | if len(s) > 0 && s[len(s)-1] == '"' {
155 | s = s[:len(s)-1]
156 | }
157 | return s
158 | }
159 |
--------------------------------------------------------------------------------
/sszgen/tagparse_test.go:
--------------------------------------------------------------------------------
1 | package sszgen
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/prysmaticlabs/prysm/v3/testing/require"
7 | )
8 |
9 | func TestTokens(t *testing.T) {
10 | testTag := "`protobuf:\"bytes,2004,rep,name=historical_roots,json=historicalRoots,proto3\" json:\"historical_roots,omitempty\" ssz-max:\"16777216\" ssz-size:\"?,32\"`"
11 | tp := &TagParser{}
12 | tp.Init(testTag)
13 | tags := tp.GetSSZTags()
14 | sszSize, ok := tags["ssz-size"]
15 | require.Equal(t, true, ok)
16 | require.Equal(t, "?,32", sszSize)
17 | sszMax, ok := tags["ssz-max"]
18 | require.Equal(t, true, ok)
19 | require.Equal(t, "16777216", sszMax)
20 | }
21 |
22 | func TestFullTag(t *testing.T) {
23 | tag := "`protobuf:\"bytes,1002,opt,name=genesis_validators_root,json=genesisValidatorsRoot,proto3\" json:\"genesis_validators_root,omitempty\" ssz-size:\"32\"`"
24 | _, err := extractSSZDimensions(tag)
25 | require.NoError(t, err)
26 | }
27 |
28 | func TestListOfVector(t *testing.T) {
29 | tag := "`protobuf:\"bytes,2004,rep,name=historical_roots,json=historicalRoots,proto3\" json:\"historical_roots,omitempty\" ssz-max:\"16777216\" ssz-size:\"?,32\"`"
30 | _, err := extractSSZDimensions(tag)
31 | require.NoError(t, err)
32 | }
33 |
34 | func TestWildcardSSZSize(t *testing.T) {
35 | tag := "`ssz-max:\"16777216\" ssz-size:\"?,32\"`"
36 | bounds, err := extractSSZDimensions(tag)
37 | require.NoError(t, err)
38 | require.Equal(t, 2, len(bounds))
39 | require.Equal(t, true, bounds[0].IsList())
40 | require.Equal(t, false, bounds[0].IsVector())
41 | require.Equal(t, 16777216, bounds[0].ListLen())
42 | require.Equal(t, false, bounds[1].IsList())
43 | require.Equal(t, true, bounds[1].IsVector())
44 | require.Equal(t, 32, bounds[1].VectorLen())
45 | }
46 |
47 | func Test2DWildcardSSZSize(t *testing.T) {
48 | tag := "`protobuf:\"bytes,14,rep,name=transactions,proto3\" json:\"transactions,omitempty\" ssz-max:\"1048576,1073741824\" ssz-size:\"?,?\"`"
49 | bounds, err := extractSSZDimensions(tag)
50 | require.NoError(t, err)
51 | require.Equal(t, 2, len(bounds))
52 | require.Equal(t, true, bounds[0].IsList())
53 | require.Equal(t, false, bounds[0].IsVector())
54 | require.Equal(t, 1048576, bounds[0].ListLen())
55 | require.Equal(t, true, bounds[1].IsList())
56 | require.Equal(t, false, bounds[1].IsVector())
57 | require.Equal(t, 1073741824, bounds[1].ListLen())
58 | }
59 |
--------------------------------------------------------------------------------
/sszgen/testdata/simple.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import github_com_prysmaticlabs_go_bitfield "github.com/prysmaticlabs/go-bitfield"
4 |
5 | type NoImports struct {
6 | state int
7 | sizeCache int
8 | unknownFields int
9 |
10 | GenesisTime uint64 `protobuf:"varint,1001,opt,name=genesis_time,json=genesisTime,proto3" json:"genesis_time,omitempty"`
11 | GenesisValidatorsRoot []byte `protobuf:"bytes,1002,opt,name=genesis_validators_root,json=genesisValidatorsRoot,proto3" json:"genesis_validators_root,omitempty" ssz-size:"32"`
12 | BlockRoots [][]byte `protobuf:"bytes,2002,rep,name=block_roots,json=blockRoots,proto3" json:"block_roots,omitempty" ssz-size:"8192,32"`
13 | HistoricalRoots [][]byte `protobuf:"bytes,2004,rep,name=historical_roots,json=historicalRoots,proto3" json:"historical_roots,omitempty" ssz-max:"16777216" ssz-size:"?,32"`
14 | MuhPrim AliasedPrimitive
15 | ContainerField ContainerType
16 | ContainerRefField *AnotherContainerType
17 | ContainerList []ContainerType `ssz-max:"23"`
18 | ContainerVector []ContainerType `ssz-size:"42"`
19 | ContainerVectorRef []*ContainerType `ssz-size:"17"`
20 | ContainerListRef []*ContainerType `ssz-max:"9000"`
21 | OverlayList []AliasedPrimitive `ssz-max:"11"`
22 | OverlayListRef []*AliasedPrimitive `ssz-max:"58"`
23 | OverlayVector []AliasedPrimitive `ssz-size:"23"`
24 | OverlayVectorRef []*AliasedPrimitive `ssz-size:"13"`
25 | }
26 |
27 | type AliasedPrimitive uint64
28 |
29 | type ContainerType struct {
30 | MuhPrim AliasedPrimitive
31 | }
32 |
33 | type AnotherContainerType struct {
34 | MuhPrim AliasedPrimitive
35 | }
36 |
37 | type FixedSizeArray [32]byte
38 |
39 | type FixedContainer struct {
40 | Array FixedSizeArray
41 | }
42 |
43 | type TestBitlist struct {
44 | AggregationBits github_com_prysmaticlabs_go_bitfield.Bitlist `protobuf:"bytes,1,opt,name=aggregation_bits,json=aggregationBits,proto3" json:"aggregation_bits,omitempty" cast-type:"github.com/prysmaticlabs/go-bitfield.Bitlist" ssz-max:"2048"`
45 | JustificationBits github_com_prysmaticlabs_go_bitfield.Bitvector4 `protobuf:"bytes,8001,opt,name=justification_bits,json=justificationBits,proto3" json:"justification_bits,omitempty" cast-type:"github.com/prysmaticlabs/go-bitfield.Bitvector4" ssz-size:"1"`
46 | }
47 |
--------------------------------------------------------------------------------
/sszgen/testdata/uint256.go.fixture:
--------------------------------------------------------------------------------
1 | package faketypes
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "fmt"
7 | )
8 |
9 | var (
10 | ErrBadBufferLength = errors.New("bad ssz buffer length")
11 | ErrBadEncodedLength = errors.New("bad ssz encoded length")
12 | )
13 |
14 | type TestStruct struct {
15 | TwoFixSix *Int
16 | }
17 |
18 | // Int is represented as an array of 4 uint64, in little-endian order,
19 | // so that Int[3] is the most significant, and Int[0] is the least significant
20 | type Int [4]uint64
21 |
22 | // NewInt returns a new initialized Int.
23 | func NewInt(val uint64) *Int {
24 | z := &Int{}
25 | z.SetUint64(val)
26 | return z
27 | }
28 |
29 | // SetUint64 sets z to the value x
30 | func (z *Int) SetUint64(x uint64) *Int {
31 | z[3], z[2], z[1], z[0] = 0, 0, 0, x
32 | return z
33 | }
34 |
35 | // MarshalSSZTo implements the fastssz.Marshaler interface and serializes the
36 | // integer into an already pre-allocated buffer.
37 | func (z *Int) MarshalSSZTo(dst []byte) ([]byte, error) {
38 | if len(dst) < 32 {
39 | return nil, fmt.Errorf("%w: have %d, want %d bytes", ErrBadBufferLength, len(dst), 32)
40 | }
41 | binary.LittleEndian.PutUint64(dst[0:8], z[0])
42 | binary.LittleEndian.PutUint64(dst[8:16], z[1])
43 | binary.LittleEndian.PutUint64(dst[16:24], z[2])
44 | binary.LittleEndian.PutUint64(dst[24:32], z[3])
45 |
46 | return dst[32:], nil
47 | }
48 |
49 | // MarshalSSZ implements the fastssz.Marshaler interface and returns the integer
50 | // marshalled into a newly allocated byte slice.
51 | func (z *Int) MarshalSSZ() ([]byte, error) {
52 | blob := make([]byte, 32)
53 | _, _ = z.MarshalSSZTo(blob) // ignore error, cannot fail, surely have 32 byte space in blob
54 | return blob, nil
55 | }
56 |
57 | // SizeSSZ implements the fastssz.Marshaler interface and returns the byte size
58 | // of the 256 bit int.
59 | func (*Int) SizeSSZ() int {
60 | return 32
61 | }
62 |
63 | // UnmarshalSSZ implements the fastssz.Unmarshaler interface and parses an encoded
64 | // integer into the local struct.
65 | func (z *Int) UnmarshalSSZ(buf []byte) error {
66 | if len(buf) != 32 {
67 | return fmt.Errorf("%w: have %d, want %d bytes", ErrBadEncodedLength, len(buf), 32)
68 | }
69 | z[0] = binary.LittleEndian.Uint64(buf[0:8])
70 | z[1] = binary.LittleEndian.Uint64(buf[8:16])
71 | z[2] = binary.LittleEndian.Uint64(buf[16:24])
72 | z[3] = binary.LittleEndian.Uint64(buf[24:32])
73 |
74 | return nil
75 | }
76 |
77 | // HashTreeRoot implements the fastssz.HashRoot interface's non-dependent part.
78 | func (z *Int) HashTreeRoot() ([32]byte, error) {
79 | var hash [32]byte
80 | _, _ = z.MarshalSSZTo(hash[:]) // ignore error, cannot fail
81 | return hash, nil
82 | }
83 |
--------------------------------------------------------------------------------
/sszgen/testdata/uint256.ssz.go.fixture:
--------------------------------------------------------------------------------
1 | package faketypes
2 |
3 | import (
4 | "fmt"
5 | ssz "github.com/prysmaticlabs/fastssz"
6 | )
7 |
8 | func (c *TestStruct) SizeSSZ() int {
9 | size := 32
10 |
11 | return size
12 | }
13 |
14 | func (c *TestStruct) MarshalSSZ() ([]byte, error) {
15 | buf := make([]byte, c.SizeSSZ())
16 | return c.MarshalSSZTo(buf[:0])
17 | }
18 |
19 | func (c *TestStruct) MarshalSSZTo(dst []byte) ([]byte, error) {
20 | var err error
21 |
22 | // Field 0: TwoFixSix
23 | if c.TwoFixSix == nil {
24 | c.TwoFixSix = new(Int)
25 | }
26 | if dst, err = c.TwoFixSix.MarshalSSZTo(dst); err != nil {
27 | return nil, err
28 | }
29 |
30 | return dst, err
31 | }
32 |
33 | func (c *TestStruct) UnmarshalSSZ(buf []byte) error {
34 | var err error
35 | size := uint64(len(buf))
36 | if size != 32 {
37 | return ssz.ErrSize
38 | }
39 |
40 | s0 := buf[0:32] // c.TwoFixSix
41 |
42 | // Field 0: TwoFixSix
43 | c.TwoFixSix = new(Int)
44 | if err = c.TwoFixSix.UnmarshalSSZ(s0); err != nil {
45 | return err
46 | }
47 | return err
48 | }
49 |
50 | func (c *TestStruct) HashTreeRoot() ([32]byte, error) {
51 | hh := ssz.DefaultHasherPool.Get()
52 | if err := c.HashTreeRootWith(hh); err != nil {
53 | ssz.DefaultHasherPool.Put(hh)
54 | return [32]byte{}, err
55 | }
56 | root, err := hh.HashRoot()
57 | ssz.DefaultHasherPool.Put(hh)
58 | return root, err
59 | }
60 |
61 | func (c *TestStruct) HashTreeRootWith(hh *ssz.Hasher) (err error) {
62 | indx := hh.Index()
63 | // Field 0: TwoFixSix
64 | if hash, err := c.TwoFixSix.HashTreeRoot(); err != nil {
65 | return err
66 | } else {
67 | hh.AppendBytes32(hash[:])
68 | }
69 | hh.Merkleize(indx)
70 | return nil
71 | }
72 |
--------------------------------------------------------------------------------
/sszgen/testutil/render.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "go/format"
5 |
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
7 | jen "github.com/dave/jennifer/jen"
8 | )
9 |
10 | func RenderIntermediate(vr types.ValRep) (string, error) {
11 | file := jen.NewFile("values")
12 | evr, err := encodeValRep(vr)
13 | if err != nil {
14 | return "", err
15 | }
16 | v := jen.Var().Id(vr.TypeName()).Id("types").Dot("ValRep").Op("=").Add(evr)
17 | file.Add(v)
18 |
19 | gs := file.GoString()
20 | b, err := format.Source([]byte(gs))
21 | return string(b), err
22 | }
23 |
24 | func encodeValRep(vr types.ValRep) (jen.Code, error) {
25 | var c jen.Code
26 | switch ty := vr.(type) {
27 | case *types.ValueByte:
28 | values := []jen.Code{jen.Id("Name").Op(":").Lit(ty.Name)}
29 | if ty.Package != "" {
30 | values = append(values, jen.Id("Package").Op(":").Lit(ty.Package))
31 | }
32 | s := jen.Op("&").Id("types").Dot("ValueByte").Values(values...)
33 | return s, nil
34 | case *types.ValueBool:
35 | values := []jen.Code{jen.Id("Name").Op(":").Lit(ty.Name)}
36 | if ty.Package != "" {
37 | values = append(values, jen.Id("Package").Op(":").Lit(ty.Package))
38 | }
39 | s := jen.Op("&").Id("types").Dot("ValueBool").Values(values...)
40 | return s, nil
41 | case *types.ValueUint:
42 | s := jen.Op("&").Id("types").Dot("ValueUint").Values(
43 | jen.Id("Name").Op(":").Lit(ty.Name),
44 | jen.Id("Size").Op(":").Lit(int(ty.Size)),
45 | )
46 | return s, nil
47 | case *types.ValueVector:
48 | ev, err := encodeValRep(ty.ElementValue)
49 | if err != nil {
50 | return nil, err
51 | }
52 | s := jen.Op("&").Id("types").Dot("ValueVector").Values(
53 | jen.Id("Size").Op(":").Lit(ty.Size),
54 | jen.Id("ElementValue").Op(":").Add(ev),
55 | )
56 | return s, nil
57 | case *types.ValueList:
58 | ev, err := encodeValRep(ty.ElementValue)
59 | if err != nil {
60 | return nil, err
61 | }
62 | s := jen.Op("&").Id("types").Dot("ValueList").Values(
63 | jen.Id("MaxSize").Op(":").Lit(ty.MaxSize),
64 | jen.Id("ElementValue").Op(":").Add(ev),
65 | )
66 | return s, nil
67 | case *types.ValueOverlay:
68 | underlying, err := encodeValRep(ty.Underlying)
69 | if err != nil {
70 | return nil, err
71 | }
72 | s := jen.Op("&").Id("types").Dot("ValueOverlay").Values(
73 | jen.Id("Name").Op(":").Lit(ty.Name),
74 | jen.Id("Package").Op(":").Lit(ty.Package),
75 | jen.Id("Underlying").Op(":").Add(underlying),
76 | )
77 | return s, nil
78 | case *types.ValuePointer:
79 | referent, err := encodeValRep(ty.Referent)
80 | if err != nil {
81 | return nil, err
82 | }
83 | s := jen.Op("&").Id("types").Dot("ValuePointer").Values(
84 | jen.Id("Referent").Op(":").Add(referent),
85 | )
86 | return s, nil
87 | case *types.ValueContainer:
88 | contents := make([]jen.Code, 0)
89 | for _, c := range ty.Contents {
90 | cvr, err := encodeValRep(c.Value)
91 | if err != nil {
92 | return nil, err
93 | }
94 | kv := jen.Values(jen.Id("Key").Op(":").Lit(c.Key),
95 | jen.Id("Value").Op(":").Add(cvr))
96 | contents = append(contents, kv)
97 | }
98 | fields := []jen.Code{
99 | jen.Id("Name").Op(":").Lit(ty.Name),
100 | jen.Id("Package").Op(":").Lit(ty.Package),
101 | jen.Id("Contents").Op(":").Index().Id("types").Dot("ContainerField").
102 | Values(contents...),
103 | }
104 | c = jen.Op("&").Id("types").Dot("ValueContainer").Values(fields...)
105 | case *types.ValueUnion:
106 | panic("not implemented")
107 | }
108 | return c, nil
109 | }
110 |
--------------------------------------------------------------------------------
/sszgen/testutil/render_test.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
7 | "github.com/prysmaticlabs/prysm/v3/testing/require"
8 | )
9 |
10 | func TestRenderIntermediate(t *testing.T) {
11 | t.Skip("TODO: investigate this failure")
12 | s := &types.ValueContainer{
13 | Name: "testing",
14 | Package: "github.com/prysmaticlabs/derp",
15 | Contents: []types.ContainerField{
16 | {
17 | Key: "OverlayUint",
18 | Value: &types.ValuePointer{Referent: &types.ValueOverlay{
19 | Name: "FakeContainer",
20 | Package: "github.com/prysmaticlabs/derp/derp",
21 | Underlying: &types.ValueUint{
22 | Name: "uint8",
23 | Size: 8,
24 | },
25 | },
26 | },
27 | },
28 | },
29 | }
30 | expected := ""
31 | actual, err := RenderIntermediate(s)
32 | require.NoError(t, err)
33 | require.Equal(t, expected, actual)
34 | }
35 |
--------------------------------------------------------------------------------
/sszgen/testutil/testdata/examplevars.go:
--------------------------------------------------------------------------------
1 | // This file exists just to give me some bootstraping input to run through github.com/aloder/tojen
2 | // to speed up the tedious process of writing jen code
3 | package testdata
4 |
5 | import (
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types"
7 | )
8 |
9 | var testing types.ValRep = &types.ValueContainer{
10 | Name: "testing",
11 | Package: "github.com/prysmaticlabs/derp",
12 | Contents: map[string]types.ValRep{
13 | "OverlayUint": &types.ValuePointer{
14 | Referent: &types.ValueOverlay{
15 | Name: "FakeContainer",
16 | Package: "github.com/prysmaticlabs/derp/derp",
17 | Underlying: &types.ValueUint{
18 | Name: "uint8",
19 | Size: 8,
20 | },
21 | },
22 | },
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/sszgen/types/bool.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import "go/types"
4 |
5 | type ValueBool struct {
6 | Name string
7 | Package string
8 | }
9 |
10 | func (vb *ValueBool) TypeName() string {
11 | return vb.Name
12 | }
13 |
14 | func (vb *ValueBool) PackagePath() string {
15 | return vb.Package
16 | }
17 |
18 | func (vb *ValueBool) FixedSize() int {
19 | return 1
20 | }
21 |
22 | func (vb *ValueBool) IsVariableSized() bool {
23 | return false
24 | }
25 |
26 | func (vb *ValueBool) SatisfiesInterface(*types.Interface) bool {
27 | return false
28 | }
29 |
30 | var _ ValRep = &ValueBool{}
31 |
--------------------------------------------------------------------------------
/sszgen/types/byte.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import "go/types"
4 |
5 | type ValueByte struct {
6 | Name string
7 | Package string
8 | }
9 |
10 | func (vb *ValueByte) TypeName() string {
11 | return vb.Name
12 | }
13 |
14 | func (vb *ValueByte) PackagePath() string {
15 | return vb.Package
16 | }
17 |
18 | func (vb *ValueByte) FixedSize() int {
19 | return 1
20 | }
21 |
22 | func (vb *ValueByte) IsVariableSized() bool {
23 | return false
24 | }
25 |
26 | func (vb *ValueByte) SatisfiesInterface(*types.Interface) bool {
27 | return false
28 | }
29 |
30 | var _ ValRep = &ValueByte{}
31 |
--------------------------------------------------------------------------------
/sszgen/types/container.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "fmt"
5 | "go/types"
6 | )
7 |
8 | type ContainerField struct {
9 | Key string
10 | Value ValRep
11 | }
12 |
13 | type ValueContainer struct {
14 | Name string
15 | Package string
16 | Contents []ContainerField
17 | HasSSZMethods bool
18 | nameMap map[string]ValRep
19 | Interfaces map[*types.Interface]bool
20 | }
21 |
22 | func (vc *ValueContainer) Fields() []ContainerField {
23 | return vc.Contents
24 | }
25 |
26 | func (vc *ValueContainer) Append(name string, value ValRep) {
27 | vc.Contents = append(vc.Contents, ContainerField{Key: name, Value: value})
28 | if vc.nameMap == nil {
29 | vc.nameMap = make(map[string]ValRep)
30 | }
31 | vc.nameMap[name] = value
32 | }
33 |
34 | func (vc *ValueContainer) GetField(name string) (ValRep, error) {
35 | field, ok := vc.nameMap[name]
36 | if !ok {
37 | return nil, fmt.Errorf("Field named %s not found in container value mapping", name)
38 | }
39 | return field, nil
40 | }
41 |
42 | func (vc *ValueContainer) TypeName() string {
43 | return vc.Name
44 | }
45 |
46 | func (vc *ValueContainer) PackagePath() string {
47 | return vc.Package
48 | }
49 |
50 | func (vc *ValueContainer) FixedSize() int {
51 | if vc.IsVariableSized() {
52 | return 4
53 | }
54 | total := 0
55 | for _, c := range vc.Contents {
56 | o := c.Value
57 | total += o.FixedSize()
58 | }
59 | return total
60 | }
61 |
62 | func (vc *ValueContainer) IsVariableSized() bool {
63 | for _, c := range vc.Contents {
64 | if c.Value.IsVariableSized() {
65 | return true
66 | }
67 | }
68 | return false
69 | }
70 |
71 | func (vc *ValueContainer) SatisfiesInterface(ti *types.Interface) bool {
72 | return vc.Interfaces != nil && vc.Interfaces[ti]
73 | }
74 |
75 | var _ ValRep = &ValueContainer{}
76 |
--------------------------------------------------------------------------------
/sszgen/types/list.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import "go/types"
4 |
5 | type ValueList struct {
6 | ElementValue ValRep
7 | MaxSize int
8 | }
9 |
10 | func (vl *ValueList) TypeName() string {
11 | return "[]" + vl.ElementValue.TypeName()
12 | }
13 |
14 | func (vl *ValueList) PackagePath() string {
15 | return vl.ElementValue.PackagePath()
16 | }
17 |
18 | func (vl *ValueList) FixedSize() int {
19 | return 4
20 | }
21 |
22 | func (vl *ValueList) IsVariableSized() bool {
23 | return true
24 | }
25 |
26 | func (vl *ValueList) SatisfiesInterface(*types.Interface) bool {
27 | return false
28 | }
29 |
30 | var _ ValRep = &ValueList{}
31 |
--------------------------------------------------------------------------------
/sszgen/types/overlay.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "fmt"
5 | "go/types"
6 | )
7 |
8 | type ValueOverlay struct {
9 | Name string
10 | Package string
11 | Underlying ValRep
12 | Interfaces map[*types.Interface]bool
13 | }
14 |
15 | func (vo *ValueOverlay) TypeName() string {
16 | return vo.Name
17 | }
18 |
19 | func (vo *ValueOverlay) PackagePath() string {
20 | return vo.Package
21 | }
22 |
23 | func (vo *ValueOverlay) FixedSize() int {
24 | if vo.IsBitfield() {
25 | return vo.bitfieldFixedSize()
26 | }
27 | return vo.Underlying.FixedSize()
28 | }
29 |
30 | func (vo *ValueOverlay) IsVariableSized() bool {
31 | return vo.Underlying.IsVariableSized()
32 | }
33 |
34 | func (vo *ValueOverlay) SatisfiesInterface(ti *types.Interface) bool {
35 | return vo.Interfaces != nil && vo.Interfaces[ti]
36 | }
37 |
38 | func (vo *ValueOverlay) IsBitfield() bool {
39 | return vo.Package == "github.com/prysmaticlabs/go-bitfield"
40 | }
41 |
42 | func (vo *ValueOverlay) bitfieldFixedSize() int {
43 | switch vo.Name {
44 | case "Bitlist":
45 | return 4
46 | case "Bitlist64":
47 | return 4
48 | case "Bitvector4":
49 | return 1
50 | case "Bitvector8":
51 | return 1
52 | case "Bitvector32":
53 | return 4
54 | case "Bitvector64":
55 | return 8
56 | case "Bitvector128":
57 | return 16
58 | case "Bitvector256":
59 | return 32
60 | case "Bitvector512":
61 | return 64
62 | case "Bitvector1024":
63 | return 128
64 | }
65 | panic(fmt.Sprintf("Can't determine the correct size for bitfield type = %s", vo.Name))
66 | }
67 |
68 | var _ ValRep = &ValueOverlay{}
69 |
--------------------------------------------------------------------------------
/sszgen/types/pointer.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "go/types"
5 |
6 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces"
7 | )
8 |
9 | type ValuePointer struct {
10 | Referent ValRep
11 | Interfaces map[*types.Interface]bool
12 | }
13 |
14 | func (vp *ValuePointer) TypeName() string {
15 | return "*" + vp.Referent.TypeName()
16 | }
17 |
18 | func (vp *ValuePointer) PackagePath() string {
19 | return vp.Referent.PackagePath()
20 | }
21 |
22 | func (vp *ValuePointer) FixedSize() int {
23 | return vp.Referent.FixedSize()
24 | }
25 |
26 | func (vp *ValuePointer) IsVariableSized() bool {
27 | return vp.Referent.IsVariableSized()
28 | }
29 |
30 | func (vp *ValuePointer) SatisfiesInterface(ti *types.Interface) bool {
31 | if vp.Interfaces != nil && vp.Interfaces[ti] {
32 | return true
33 | }
34 | // Unmarshaler needs a pointer receiver, and the above check failed means that there isn't one,
35 | // so we shouldn't allow a value receiver to satisfy the interface.
36 | if ti == interfaces.SszUnmarshaler {
37 | return false
38 | }
39 | // since the other methods are read-only, it's ok to use a method with a value receiver
40 | return vp.Referent.SatisfiesInterface(ti)
41 | }
42 |
43 | var _ ValRep = &ValuePointer{}
44 |
--------------------------------------------------------------------------------
/sszgen/types/uint.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import "go/types"
4 |
5 | type UintSize int
6 |
7 | const (
8 | Uint8 UintSize = 8
9 | Uint16 UintSize = 16
10 | Uint32 UintSize = 32
11 | Uint64 UintSize = 64
12 | Uint128 UintSize = 128
13 | Uint256 UintSize = 256
14 | )
15 |
16 | type ValueUint struct {
17 | Name string
18 | Size UintSize
19 | Package string
20 | }
21 |
22 | func (vu *ValueUint) TypeName() string {
23 | return vu.Name
24 | }
25 |
26 | func (vu *ValueUint) PackagePath() string {
27 | return vu.Package
28 | }
29 |
30 | func (vu *ValueUint) FixedSize() int {
31 | return int(vu.Size) / 8
32 | }
33 |
34 | func (vu *ValueUint) IsVariableSized() bool {
35 | return false
36 | }
37 |
38 | func (vu *ValueUint) SatisfiesInterface(*types.Interface) bool {
39 | return false
40 | }
41 |
42 | var _ ValRep = &ValueUint{}
43 |
--------------------------------------------------------------------------------
/sszgen/types/union.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import "go/types"
4 |
5 | type ValueUnion struct {
6 | Name string
7 | }
8 |
9 | func (vu *ValueUnion) TypeName() string {
10 | return vu.Name
11 | }
12 |
13 | func (vu *ValueUnion) PackagePath() string {
14 | panic("not implemented")
15 | }
16 |
17 | func (vu *ValueUnion) FixedSize() int {
18 | panic("not implemented")
19 | }
20 |
21 | func (vu *ValueUnion) IsVariableSized() bool {
22 | panic("not implemented")
23 | }
24 |
25 | func (vu *ValueUnion) SatisfiesInterface(*types.Interface) bool {
26 | return false
27 | }
28 |
29 | var _ ValRep = &ValueUnion{}
30 |
--------------------------------------------------------------------------------
/sszgen/types/valrep.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import "go/types"
4 |
5 | type ValRep interface {
6 | TypeName() string
7 | FixedSize() int
8 | PackagePath() string
9 | IsVariableSized() bool
10 | SatisfiesInterface(*types.Interface) bool
11 | }
12 |
--------------------------------------------------------------------------------
/sszgen/types/vector.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import "go/types"
4 |
5 | type ValueVector struct {
6 | ElementValue ValRep
7 | Size int
8 | IsArray bool
9 | }
10 |
11 | func (vv *ValueVector) TypeName() string {
12 | return "[]" + vv.ElementValue.TypeName()
13 | }
14 |
15 | func (vv *ValueVector) FixedSize() int {
16 | if vv.IsVariableSized() {
17 | return 4
18 | }
19 | return vv.Size * vv.ElementValue.FixedSize()
20 | }
21 |
22 | func (vv *ValueVector) PackagePath() string {
23 | return vv.ElementValue.PackagePath()
24 | }
25 |
26 | func (vv *ValueVector) IsVariableSized() bool {
27 | return vv.ElementValue.IsVariableSized()
28 | }
29 |
30 | func (vv *ValueVector) SatisfiesInterface(*types.Interface) bool {
31 | return false
32 | }
33 |
34 | var _ ValRep = &ValueVector{}
35 |
--------------------------------------------------------------------------------
/sszgen/typeutil.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Felix Lange .
2 | // Use of this source code is governed by the MIT license,
3 | // which can be found in the LICENSE file.
4 |
5 | package sszgen
6 |
7 | import (
8 | "fmt"
9 | "go/types"
10 | "io"
11 | "sort"
12 | "strconv"
13 |
14 | "github.com/pkg/errors"
15 | )
16 |
17 | // walkNamedTypes runs the callback for all named types contained in the given type.
18 | func walkNamedTypes(typ types.Type, callback func(*types.Named)) {
19 | switch typ := typ.(type) {
20 | case *types.Basic:
21 | case *types.Chan:
22 | walkNamedTypes(typ.Elem(), callback)
23 | case *types.Map:
24 | walkNamedTypes(typ.Key(), callback)
25 | walkNamedTypes(typ.Elem(), callback)
26 | case *types.Named:
27 | callback(typ)
28 | case *types.Pointer:
29 | walkNamedTypes(typ.Elem(), callback)
30 | case *types.Slice:
31 | case *types.Array:
32 | walkNamedTypes(typ.Elem(), callback)
33 | case *types.Struct:
34 | for i := 0; i < typ.NumFields(); i++ {
35 | walkNamedTypes(typ.Field(i).Type(), callback)
36 | }
37 | case *types.Interface:
38 | if typ.NumMethods() > 0 {
39 | panic("BUG: can't walk non-empty interface")
40 | }
41 | default:
42 | panic(fmt.Errorf("BUG: can't walk %T", typ))
43 | }
44 | }
45 |
46 | func lookupType(scope *types.Scope, name string) (*types.Named, types.Object, error) {
47 | if name == "" {
48 | return nil, nil, errors.Wrap(errors.New("no such identifier"), "empty name lookup")
49 | }
50 | obj := scope.Lookup(name)
51 | if obj == nil {
52 | return nil, nil, errors.Wrap(errors.New("no such identifier"), name)
53 | }
54 | typ, ok := obj.(*types.TypeName)
55 | if !ok {
56 | return nil, nil, errors.Wrap(errors.New("not a type"), name)
57 | }
58 | return typ.Type().(*types.Named), obj, nil
59 | }
60 |
61 | func isPointer(typ types.Type) bool {
62 | _, ok := typ.(*types.Pointer)
63 | return ok
64 | }
65 |
66 | func underlyingSlice(typ types.Type) *types.Slice {
67 | for {
68 | switch t := typ.(type) {
69 | case *types.Named:
70 | typ = typ.Underlying()
71 | case *types.Slice:
72 | return t
73 | default:
74 | return nil
75 | }
76 | }
77 | }
78 |
79 | func underlyingMap(typ types.Type) *types.Map {
80 | for {
81 | switch t := typ.(type) {
82 | case *types.Named:
83 | typ = typ.Underlying()
84 | case *types.Map:
85 | return t
86 | default:
87 | return nil
88 | }
89 | }
90 | }
91 |
92 | func ensureNilCheckable(typ types.Type) types.Type {
93 | orig := typ
94 | named := false
95 | for {
96 | switch typ.(type) {
97 | case *types.Named:
98 | typ = typ.Underlying()
99 | named = true
100 | case *types.Slice, *types.Map:
101 | if named {
102 | // Named slices, maps, etc. are special because they can have a custom
103 | // decoder function that prevents the JSON null value. Wrap them with a
104 | // pointer to allow null always so required/optional works as expected.
105 | return types.NewPointer(orig)
106 | }
107 | return orig
108 | case *types.Pointer, *types.Interface:
109 | return orig
110 | default:
111 | return types.NewPointer(orig)
112 | }
113 | }
114 | }
115 |
116 | // checkConvertible determines whether values of type from can be converted to type to. It
117 | // returns nil if convertible and a descriptive error otherwise.
118 | // See package documentation for this definition of 'convertible'.
119 | func checkConvertible(from, to types.Type) error {
120 | if types.ConvertibleTo(from, to) {
121 | return nil
122 | }
123 | // Slices.
124 | sfrom := underlyingSlice(from)
125 | sto := underlyingSlice(to)
126 | if sfrom != nil && sto != nil {
127 | if !types.ConvertibleTo(sfrom.Elem(), sto.Elem()) {
128 | return fmt.Errorf("slice element type %s is not convertible to %s", sfrom.Elem(), sto.Elem())
129 | }
130 | return nil
131 | }
132 | // Maps.
133 | mfrom := underlyingMap(from)
134 | mto := underlyingMap(to)
135 | if mfrom != nil && mto != nil {
136 | if !types.ConvertibleTo(mfrom.Key(), mto.Key()) {
137 | return fmt.Errorf("map key type %s is not convertible to %s", mfrom.Key(), mto.Key())
138 | }
139 | if !types.ConvertibleTo(mfrom.Elem(), mto.Elem()) {
140 | return fmt.Errorf("map element type %s is not convertible to %s", mfrom.Elem(), mto.Elem())
141 | }
142 | return nil
143 | }
144 | return fmt.Errorf("type %s is not convertible to %s", from, to)
145 | }
146 |
147 | // fileScope tracks imports and other names at file scope.
148 | type fileScope struct {
149 | imports []*types.Package
150 | importsByName map[string]*types.Package
151 | importNames map[string]string
152 | otherNames map[string]bool // non-package identifiers
153 | pkg *types.Package
154 | imp types.Importer
155 | }
156 |
157 | func newFileScope(imp types.Importer, pkg *types.Package) *fileScope {
158 | return &fileScope{otherNames: make(map[string]bool), pkg: pkg, imp: imp}
159 | }
160 |
161 | func (s *fileScope) writeImportDecl(w io.Writer) {
162 | fmt.Fprintln(w, "import (")
163 | for _, pkg := range s.imports {
164 | if s.importNames[pkg.Path()] != pkg.Name() {
165 | fmt.Fprintf(w, "\t%s %q\n", s.importNames[pkg.Path()], pkg.Path())
166 | } else {
167 | fmt.Fprintf(w, "\t%q\n", pkg.Path())
168 | }
169 | }
170 | fmt.Fprintln(w, ")")
171 | }
172 |
173 | // addImport loads a package and adds it to the import set.
174 | func (s *fileScope) addImport(path string) {
175 | pkg, err := s.imp.Import(path)
176 | if err != nil {
177 | panic(fmt.Errorf("can't import %q: %v", path, err))
178 | }
179 | s.insertImport(pkg)
180 | s.rebuildImports()
181 | }
182 |
183 | // addReferences marks all names referenced by typ as used.
184 | func (s *fileScope) addReferences(typ types.Type) {
185 | walkNamedTypes(typ, func(nt *types.Named) {
186 | pkg := nt.Obj().Pkg()
187 | if pkg == s.pkg {
188 | s.otherNames[nt.Obj().Name()] = true
189 | } else if pkg != nil {
190 | s.insertImport(nt.Obj().Pkg())
191 | }
192 | })
193 | s.rebuildImports()
194 | }
195 |
196 | // insertImport adds pkg to the list of known imports.
197 | // This method should not be used directly because it doesn't
198 | // rebuild the import name cache.
199 | func (s *fileScope) insertImport(pkg *types.Package) {
200 | i := sort.Search(len(s.imports), func(i int) bool {
201 | return s.imports[i].Path() >= pkg.Path()
202 | })
203 | if i < len(s.imports) && s.imports[i] == pkg {
204 | return
205 | }
206 | s.imports = append(s.imports[:i], append([]*types.Package{pkg}, s.imports[i:]...)...)
207 | }
208 |
209 | // rebuildImports caches the names of imported packages.
210 | func (s *fileScope) rebuildImports() {
211 | s.importNames = make(map[string]string)
212 | s.importsByName = make(map[string]*types.Package)
213 | for _, pkg := range s.imports {
214 | s.maybeRenameImport(pkg)
215 | }
216 | }
217 |
218 | func (s *fileScope) maybeRenameImport(pkg *types.Package) {
219 | name := pkg.Name()
220 | for i := 0; s.isNameTaken(name); i++ {
221 | name = pkg.Name()
222 | if i > 0 {
223 | name += strconv.Itoa(i - 1)
224 | }
225 | }
226 | s.importNames[pkg.Path()] = name
227 | s.importsByName[name] = pkg
228 | }
229 |
230 | // isNameTaken reports whether the given name is used by an import or other identifier.
231 | func (s *fileScope) isNameTaken(name string) bool {
232 | return s.importsByName[name] != nil || s.otherNames[name] || types.Universe.Lookup(name) != nil
233 | }
234 |
235 | // qualify is a types.Qualifier that prepends the (possibly renamed) package name of
236 | // imported types to a type name.
237 | func (s *fileScope) qualify(pkg *types.Package) string {
238 | if pkg == s.pkg {
239 | return ""
240 | }
241 | return s.packageName(pkg.Path())
242 | }
243 |
244 | func (s *fileScope) packageName(path string) string {
245 | name, ok := s.importNames[path]
246 | if !ok {
247 | panic("BUG: missing package " + path)
248 | }
249 | return name
250 | }
251 |
252 | // funcScope tracks used identifiers in a function. It can create new identifiers that do
253 | // not clash with the parent scope.
254 | type funcScope struct {
255 | used map[string]bool
256 | parent *fileScope
257 | }
258 |
259 | func newFuncScope(parent *fileScope) *funcScope {
260 | return &funcScope{make(map[string]bool), parent}
261 | }
262 |
263 | // newIdent creates a new identifier that doesn't clash with any name
264 | // in the scope or its parent file scope.
265 | func (s *funcScope) newIdent(base string) string {
266 | for i := 0; ; i++ {
267 | name := base
268 | if i > 0 {
269 | name += strconv.Itoa(i - 1)
270 | }
271 | if !s.parent.isNameTaken(name) && !s.used[name] {
272 | s.used[name] = true
273 | return name
274 | }
275 | }
276 | }
277 |
--------------------------------------------------------------------------------
/sszgen/virtualscoper.go:
--------------------------------------------------------------------------------
1 | package sszgen
2 |
3 | import (
4 | "go/ast"
5 | "go/importer"
6 | "go/parser"
7 | "go/token"
8 | "go/types"
9 |
10 | "github.com/pkg/errors"
11 | )
12 |
13 | type VirtualFile struct {
14 | name string
15 | contents string
16 | }
17 |
18 | type VirtualPathScoper struct {
19 | vfiles []VirtualFile
20 | files []*ast.File
21 | scope *types.Scope
22 | path string
23 | }
24 |
25 | func NewVirtualPathScoper(pkgName string, vfs ...VirtualFile) (*VirtualPathScoper, error) {
26 | vps := &VirtualPathScoper{
27 | vfiles: vfs,
28 | files: make([]*ast.File, len(vfs)),
29 | path: pkgName,
30 | }
31 | fset := token.NewFileSet()
32 | for i, vf := range vfs {
33 | f, err := parser.ParseFile(fset, vf.name, vf.contents, 0)
34 | if err != nil {
35 | return nil, err
36 | }
37 | vps.files[i] = f
38 | }
39 | conf := types.Config{Importer: importer.Default()}
40 | pkg, err := conf.Check(pkgName, fset, vps.files, nil)
41 | if err != nil {
42 | return nil, errors.Wrap(err, "error from conf.Check")
43 | }
44 | vps.scope = pkg.Scope()
45 | return vps, nil
46 | }
47 |
48 | func (vps *VirtualPathScoper) Path() string {
49 | return vps.path
50 | }
51 |
52 | func (vps *VirtualPathScoper) Scope() *types.Scope {
53 | return vps.scope
54 | }
55 |
--------------------------------------------------------------------------------
/sszgen/virtualscoper_test.go:
--------------------------------------------------------------------------------
1 | package sszgen
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/OffchainLabs/methodical-ssz/sszgen/backend"
8 | "github.com/prysmaticlabs/prysm/v3/testing/require"
9 | )
10 |
11 | func TestNewVirtualPathScoper(t *testing.T) {
12 | cbytes, err := os.ReadFile("testdata/uint256.go.fixture")
13 | require.NoError(t, err)
14 | vf := VirtualFile{
15 | name: "uint256.go",
16 | contents: string(cbytes),
17 | }
18 | expected, err := os.ReadFile("testdata/uint256.ssz.go.fixture")
19 | require.NoError(t, err)
20 | pkgName := "github.com/ethereum/go-ethereum/faketypes"
21 | vps, err := NewVirtualPathScoper(pkgName, vf)
22 | require.NoError(t, err)
23 | require.Equal(t, pkgName, vps.Path())
24 | defs, err := TypeDefs(vps, "TestStruct")
25 | require.NoError(t, err)
26 | require.Equal(t, 1, len(defs))
27 | typeRep, err := ParseTypeDef(defs[0])
28 | require.NoError(t, err)
29 | g := backend.NewGenerator(pkgName)
30 | require.NoError(t, g.Generate(typeRep))
31 | rb, err := g.Render()
32 | require.NoError(t, err)
33 | // compare string representations so test failures will be legible
34 | require.Equal(t, string(expected), string(rb))
35 | }
36 |
--------------------------------------------------------------------------------