Skip to content

Commit e2af2a6

Browse files
committed
feat: pseudo.Type.Format() implements fmt.Formatter
1 parent f1a1c67 commit e2af2a6

File tree

5 files changed

+165
-4
lines changed

5 files changed

+165
-4
lines changed

core/state/snapshot/snapshot_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) {
119119
}
120120
// Since the base layer was modified, ensure that data retrievals on the external reference fail
121121
if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale {
122-
t.Errorf("stale reference returned account: %+v (err: %v)", acc, err)
122+
t.Errorf("stale reference returned account: %#x (err: %v)", acc, err)
123123
}
124124
if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale {
125125
t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err)
@@ -169,7 +169,7 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) {
169169
}
170170
// Since the base layer was modified, ensure that data retrievals on the external reference fail
171171
if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale {
172-
t.Errorf("stale reference returned account: %+v (err: %v)", acc, err)
172+
t.Errorf("stale reference returned account: %#x (err: %v)", acc, err)
173173
}
174174
if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale {
175175
t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err)
@@ -231,7 +231,7 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) {
231231
}
232232
// Since the accumulator diff layer was modified, ensure that data retrievals on the external reference fail
233233
if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale {
234-
t.Errorf("stale reference returned account: %+v (err: %v)", acc, err)
234+
t.Errorf("stale reference returned account: %#x (err: %v)", acc, err)
235235
}
236236
if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale {
237237
t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err)
@@ -280,7 +280,7 @@ func TestPostCapBasicDataAccess(t *testing.T) {
280280
// shouldErr checks that an account access errors as expected
281281
shouldErr := func(layer *diffLayer, key string) error {
282282
if data, err := layer.Account(common.HexToHash(key)); err == nil {
283-
return fmt.Errorf("expected error, got data %+v", data)
283+
return fmt.Errorf("expected error, got data %x", data)
284284
}
285285
return nil
286286
}

core/types/rlp_payload.libevm.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package types
1818

1919
import (
20+
"fmt"
2021
"io"
2122

2223
"github.com/ethereum/go-ethereum/libevm/pseudo"
@@ -40,6 +41,10 @@ func RegisterExtras[SA any]() ExtraPayloads[SA] {
4041
}
4142
var extra ExtraPayloads[SA]
4243
registeredExtras = &extraConstructors{
44+
stateAccountType: func() string {
45+
var x SA
46+
return fmt.Sprintf("%T", x)
47+
}(),
4348
newStateAccount: pseudo.NewConstructor[SA]().Zero,
4449
cloneStateAccount: extra.cloneStateAccount,
4550
}
@@ -49,6 +54,7 @@ func RegisterExtras[SA any]() ExtraPayloads[SA] {
4954
var registeredExtras *extraConstructors
5055

5156
type extraConstructors struct {
57+
stateAccountType string
5258
newStateAccount func() *pseudo.Type
5359
cloneStateAccount func(*StateAccountExtra) *StateAccountExtra
5460
}
@@ -123,6 +129,7 @@ func (e *StateAccountExtra) payload() *pseudo.Type {
123129
var _ interface {
124130
rlp.Encoder
125131
rlp.Decoder
132+
fmt.Formatter
126133
} = (*StateAccountExtra)(nil)
127134

128135
// EncodeRLP implements the [rlp.Encoder] interface.
@@ -151,3 +158,18 @@ func (e *StateAccountExtra) DecodeRLP(s *rlp.Stream) error {
151158
return s.Decode(e.t)
152159
}
153160
}
161+
162+
// Format implements the [fmt.Formatter] interface.
163+
func (e *StateAccountExtra) Format(s fmt.State, verb rune) {
164+
var out string
165+
switch r := registeredExtras; {
166+
case r == nil:
167+
out = "<nil>"
168+
case e == nil, e.t == nil:
169+
out = fmt.Sprintf("<nil>[*StateAccountExtra[%s]]", r.stateAccountType)
170+
default:
171+
e.t.Format(s, verb)
172+
return
173+
}
174+
_, _ = s.Write([]byte(out))
175+
}

libevm/pseudo/fmt.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2024 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package pseudo
18+
19+
import (
20+
"fmt"
21+
)
22+
23+
var _ = []fmt.Formatter{
24+
(*Type)(nil),
25+
(*Value[struct{}])(nil),
26+
(*concrete[struct{}])(nil),
27+
}
28+
29+
// Format implements the [fmt.Formatter] interface.
30+
func (t *Type) Format(s fmt.State, verb rune) {
31+
switch {
32+
case t == nil, t.val == nil:
33+
writeToFmtState(s, "<nil>[pseudo.Type[unknown]]")
34+
default:
35+
t.val.Format(s, verb)
36+
}
37+
}
38+
39+
// Format implements the [fmt.Formatter] interface.
40+
func (v *Value[T]) Format(s fmt.State, verb rune) { v.t.Format(s, verb) }
41+
42+
func (c *concrete[T]) Format(s fmt.State, verb rune) {
43+
switch {
44+
case c == nil:
45+
writeToFmtState(s, "<nil>[pseudo.Type[%T]]", concrete[T]{}.val)
46+
default:
47+
// Respects the original formatting directive. fmt all the way down!
48+
format := fmt.Sprintf("pseudo.Type[%%T]{%s}", fmt.FormatString(s, verb))
49+
writeToFmtState(s, format, c.val, c.val)
50+
}
51+
}
52+
53+
func writeToFmtState(s fmt.State, format string, a ...any) {
54+
// There is no way to bubble errors out from a `fmt.Formatter`.
55+
_, _ = s.Write([]byte(fmt.Sprintf(format, a...)))
56+
}

libevm/pseudo/fmt_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2024 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package pseudo
18+
19+
import (
20+
"fmt"
21+
"testing"
22+
23+
"github.com/stretchr/testify/assert"
24+
)
25+
26+
func TestFormat(t *testing.T) {
27+
tests := []struct {
28+
name string
29+
from any
30+
format string
31+
wantContains []string
32+
}{
33+
{
34+
name: "width",
35+
from: 42,
36+
format: "%04d",
37+
wantContains: []string{"int", "0042"},
38+
},
39+
{
40+
name: "precision",
41+
from: float64(2),
42+
format: "%.5f",
43+
wantContains: []string{"float64", "2.00000"},
44+
},
45+
{
46+
name: "flag",
47+
from: 42,
48+
format: "%+d",
49+
wantContains: []string{"int", "+42"},
50+
},
51+
{
52+
name: "verb",
53+
from: 42,
54+
format: "%x",
55+
wantContains: []string{"int", "2a"},
56+
},
57+
}
58+
59+
for _, tt := range tests {
60+
t.Run(tt.name, func(t *testing.T) {
61+
got := fmt.Sprintf(tt.format, fromAny(t, tt.from))
62+
for _, want := range tt.wantContains {
63+
assert.Containsf(t, got, want, "fmt.Sprintf(%q, From(%T[%[2]v]))", tt.format, tt.from)
64+
}
65+
})
66+
}
67+
}
68+
69+
func fromAny(t *testing.T, x any) *Type {
70+
t.Helper()
71+
72+
// Without this, the function will be From[any]().
73+
switch x := x.(type) {
74+
case int:
75+
return From(x).Type
76+
case float64:
77+
return From(x).Type
78+
default:
79+
t.Fatalf("Bad test setup: add type case for %T", x)
80+
return nil
81+
}
82+
}

libevm/pseudo/type.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ type value interface {
193193
json.Unmarshaler
194194
rlp.Encoder
195195
rlp.Decoder
196+
fmt.Formatter
196197
}
197198

198199
type concrete[T any] struct {

0 commit comments

Comments
 (0)