Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions backend/groth16/bn254/solidity.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ const solidityTemplate = `
{{- $numWitness := sub $numPublic $numCommitments }}
{{- $PublicAndCommitmentCommitted := .Vk.PublicAndCommitmentCommitted }}
// SPDX-License-Identifier: MIT

{{- if .Cfg.SortedImports }}
{{ range $imp := .Cfg.SortedImports }}
{{ $imp }}
{{- end }}
{{ end }}
pragma solidity {{ .Cfg.PragmaVersion }};

/// @title Groth16 verifier template.
Expand All @@ -23,7 +27,7 @@ pragma solidity {{ .Cfg.PragmaVersion }};
/// (256 bytes) and compressed (128 bytes) format. A view function is provided
/// to compress proofs.
/// @notice See <https://2π.com/23/bn254-compression> for further explanation.
contract Verifier {
contract Verifier{{ .Cfg.InterfaceDeclaration }} {

/// Some of the provided public input values are larger than the field modulus.
/// @dev Public input elements are not automatically reduced, as this is can be
Expand Down Expand Up @@ -121,6 +125,14 @@ contract Verifier {
uint256 constant PUB_{{sub $i 1}}_Y = {{ (fpstr $ki.Y) }};
{{- end }}
{{- end }}
{{- if .Cfg.Constants }}

{{ .Cfg.Constants }}
{{- end }}
{{- if .Cfg.Constructor }}

{{ .Cfg.Constructor }}
{{- end }}

/// Negation in Fp.
/// @notice Returns a number x such that a + x = 0 in Fp.
Expand Down Expand Up @@ -799,6 +811,10 @@ contract Verifier {
revert ProofInvalid();
}
}
{{- if .Cfg.Functions }}

{{ .Cfg.Functions }}
{{- end }}
}
`

Expand Down
20 changes: 18 additions & 2 deletions backend/plonk/bn254/solidity.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 136 additions & 0 deletions backend/solidity/options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package solidity_test

import (
"strings"
"testing"

"github.com/consensys/gnark/backend/solidity"
)

func TestSortedImports(t *testing.T) {
cfg, err := solidity.NewExportConfig(
solidity.WithImport(strings.NewReader(`import { B } from "b.sol";`)),
solidity.WithImport(strings.NewReader(`import { A } from "a.sol";`)),
solidity.WithImport(strings.NewReader(`import { C } from "c.sol";`)),
)
if err != nil {
t.Fatal(err)
}
imports := cfg.SortedImports()
if len(imports) != 3 {
t.Fatalf("expected 3 imports, got %d", len(imports))
}
if imports[0] != `import { A } from "a.sol";` {
t.Errorf("expected first import to be A, got %s", imports[0])
}
if imports[1] != `import { B } from "b.sol";` {
t.Errorf("expected second import to be B, got %s", imports[1])
}
if imports[2] != `import { C } from "c.sol";` {
t.Errorf("expected third import to be C, got %s", imports[2])
}
}

func TestInterfaceDeclaration(t *testing.T) {
tests := []struct {
name string
interfaces []string
expected string
}{
{
name: "no interfaces",
interfaces: nil,
expected: "",
},
{
name: "single interface",
interfaces: []string{"IVerifier"},
expected: " is IVerifier",
},
{
name: "multiple interfaces",
interfaces: []string{"IVerifier", "IPlonk"},
expected: " is IVerifier, IPlonk",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
opts := make([]solidity.ExportOption, 0, len(tt.interfaces))
for _, iface := range tt.interfaces {
opts = append(opts, solidity.WithInterface(strings.NewReader(iface)))
}
cfg, err := solidity.NewExportConfig(opts...)
if err != nil {
t.Fatal(err)
}
if got := cfg.InterfaceDeclaration(); got != tt.expected {
t.Errorf("InterfaceDeclaration() = %q, want %q", got, tt.expected)
}
})
}
}

func TestWithConstants(t *testing.T) {
cfg, err := solidity.NewExportConfig(
solidity.WithConstants(strings.NewReader(" bytes32 private immutable CHAIN_CONFIG;")),
)
if err != nil {
t.Fatal(err)
}
if cfg.Constants != " bytes32 private immutable CHAIN_CONFIG;" {
t.Errorf("unexpected constants: %s", cfg.Constants)
}
}

func TestWithConstructor(t *testing.T) {
constructor := ` constructor(bytes32 config) {
CHAIN_CONFIG = config;
}`
cfg, err := solidity.NewExportConfig(
solidity.WithConstructor(strings.NewReader(constructor)),
)
if err != nil {
t.Fatal(err)
}
if cfg.Constructor != constructor {
t.Errorf("unexpected constructor: %s", cfg.Constructor)
}
}

func TestWithFunctions(t *testing.T) {
functions := ` function getConfig() external view returns (bytes32) {
return CHAIN_CONFIG;
}`
cfg, err := solidity.NewExportConfig(
solidity.WithFunctions(strings.NewReader(functions)),
)
if err != nil {
t.Fatal(err)
}
if cfg.Functions != functions {
t.Errorf("unexpected functions: %s", cfg.Functions)
}
}

func TestEmptyConfig(t *testing.T) {
cfg, err := solidity.NewExportConfig()
if err != nil {
t.Fatal(err)
}
if len(cfg.SortedImports()) != 0 {
t.Error("expected no imports")
}
if cfg.InterfaceDeclaration() != "" {
t.Error("expected empty interface declaration")
}
if cfg.Constants != "" {
t.Error("expected empty constants")
}
if cfg.Constructor != "" {
t.Error("expected empty constructor")
}
if cfg.Functions != "" {
t.Error("expected empty functions")
}
}
131 changes: 131 additions & 0 deletions backend/solidity/solidity.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package solidity
import (
"fmt"
"hash"
"io"
"sort"

"github.com/consensys/gnark/backend"
"golang.org/x/crypto/sha3"
Expand All @@ -17,6 +19,17 @@ type ExportOption func(*ExportConfig) error
type ExportConfig struct {
PragmaVersion string
HashToFieldFn hash.Hash
// Imports contains additional import statements to include in the generated contract.
// Each key is an import statement (without semicolon), sorted by key for deterministic output.
Imports map[string]struct{}
// Interfaces contains the interface names that the contract implements.
Interfaces []string
// Constants contains additional constant declarations to include in the contract.
Constants string
// Constructor contains the constructor code to include in the contract.
Constructor string
// Functions contains additional functions to include in the contract.
Functions string
}

// NewExportConfig returns a default ExportConfig with given export options opts
Expand Down Expand Up @@ -53,6 +66,124 @@ func WithHashToFieldFunction(hFunc hash.Hash) ExportOption {
}
}

// WithImport adds an import statement to the generated Solidity contract. Can
// be called multiple times to add multiple imports. The imports are sorted
// alphabetically for deterministic output.
//
// Example:
//
// solidity.WithImport(strings.NewReader(`import { Mimc } from "../../../libraries/Mimc.sol";`))
func WithImport(r io.Reader) ExportOption {
return func(cfg *ExportConfig) error {
b, err := io.ReadAll(r)
if err != nil {
return fmt.Errorf("read import: %w", err)
}
if cfg.Imports == nil {
cfg.Imports = make(map[string]struct{})
}
cfg.Imports[string(b)] = struct{}{}
return nil
}
}

// WithInterface adds an interface name that the contract implements. Can be
// called multiple times to add multiple interfaces.
//
// Example:
//
// solidity.WithInterface(strings.NewReader("IPlonkVerifier"))
func WithInterface(r io.Reader) ExportOption {
return func(cfg *ExportConfig) error {
b, err := io.ReadAll(r)
if err != nil {
return fmt.Errorf("read interface: %w", err)
}
cfg.Interfaces = append(cfg.Interfaces, string(b))
return nil
}
}

// WithConstants adds additional constant declarations to the generated Solidity
// contract. The constants are inserted after the existing constants in the
// template.
//
// Example:
//
// solidity.WithConstants(strings.NewReader("bytes32 private immutable CHAIN_CONFIGURATION;"))
func WithConstants(r io.Reader) ExportOption {
return func(cfg *ExportConfig) error {
b, err := io.ReadAll(r)
if err != nil {
return fmt.Errorf("read constants: %w", err)
}
cfg.Constants = string(b)
return nil
}
}

// WithConstructor adds a constructor to the generated Solidity contract.
//
// Example:
//
// solidity.WithConstructor(strings.NewReader("constructor() { }"))
func WithConstructor(r io.Reader) ExportOption {
return func(cfg *ExportConfig) error {
b, err := io.ReadAll(r)
if err != nil {
return fmt.Errorf("read constructor: %w", err)
}
cfg.Constructor = string(b)
return nil
}
}

// WithFunctions adds additional functions to the generated Solidity contract.
// The functions are inserted before the closing brace of the contract.
//
// Example:
//
// solidity.WithFunctions(strings.NewReader("function foo() public { }"))
func WithFunctions(r io.Reader) ExportOption {
return func(cfg *ExportConfig) error {
b, err := io.ReadAll(r)
if err != nil {
return fmt.Errorf("read functions: %w", err)
}
cfg.Functions = string(b)
return nil
}
}

// SortedImports returns the imports sorted alphabetically for deterministic output.
func (cfg *ExportConfig) SortedImports() []string {
if len(cfg.Imports) == 0 {
return nil
}
imports := make([]string, 0, len(cfg.Imports))
for imp := range cfg.Imports {
imports = append(imports, imp)
}
sort.Strings(imports)
return imports
}

// InterfaceDeclaration returns the interface declaration string for the contract.
// Returns empty string if no interfaces are defined.
func (cfg *ExportConfig) InterfaceDeclaration() string {
if len(cfg.Interfaces) == 0 {
return ""
}
result := " is "
for i, iface := range cfg.Interfaces {
if i > 0 {
result += ", "
}
result += iface
}
return result
}

// WithProverTargetSolidityVerifier returns a prover option that sets all the
// necessary prover options which are suitable for verifying the proofs in the
// Solidity verifier.
Expand Down
Loading