Skip to content

Commit 137a17e

Browse files
authored
Move relayer FeeQuote and binary utils to go-sequence (#289)
* Move relayer FeeQuote to go-sequence * Move write*() binary utility functions into its own lib/binaryutil pkg * Write unit tests for binaryutil pkg
1 parent 289a6a0 commit 137a17e

File tree

5 files changed

+319
-0
lines changed

5 files changed

+319
-0
lines changed

lib/binaryutil/bigint.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package binaryutil
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
"math/big"
7+
)
8+
9+
func WriteOptionalBigInt(w io.Writer, n *big.Int) error {
10+
err := binary.Write(w, binary.LittleEndian, n != nil)
11+
if err != nil {
12+
return err
13+
}
14+
15+
if n != nil {
16+
err = WriteBigInt(w, n)
17+
if err != nil {
18+
return err
19+
}
20+
}
21+
22+
return nil
23+
}
24+
25+
func WriteBigInt(w io.Writer, n *big.Int) error {
26+
err := binary.Write(w, binary.LittleEndian, int8(n.Sign()))
27+
if err != nil {
28+
return err
29+
}
30+
31+
_, err = w.Write(n.Bytes())
32+
if err != nil {
33+
return err
34+
}
35+
36+
return nil
37+
}

lib/binaryutil/bigint_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package binaryutil
2+
3+
import (
4+
"bytes"
5+
"math/big"
6+
"testing"
7+
)
8+
9+
func TestWriteBigInt(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
input *big.Int
13+
expected []byte
14+
}{
15+
{
16+
name: "positive",
17+
input: big.NewInt(12345),
18+
expected: append([]byte{0x01}, big.NewInt(12345).Bytes()...),
19+
},
20+
{
21+
name: "negative",
22+
input: big.NewInt(-12345),
23+
expected: append([]byte{0xFF}, big.NewInt(12345).Bytes()...),
24+
},
25+
{
26+
name: "zero",
27+
input: big.NewInt(0),
28+
expected: []byte{0x00},
29+
},
30+
}
31+
32+
for _, tt := range tests {
33+
t.Run(tt.name, func(t *testing.T) {
34+
var buf bytes.Buffer
35+
err := WriteBigInt(&buf, tt.input)
36+
if err != nil {
37+
t.Fatalf("WriteBigInt() error = %v", err)
38+
}
39+
if !bytes.Equal(buf.Bytes(), tt.expected) {
40+
t.Errorf("WriteBigInt() = %v, want %v", buf.Bytes(), tt.expected)
41+
}
42+
})
43+
}
44+
}
45+
46+
func TestWriteOptionalBigInt(t *testing.T) {
47+
tests := []struct {
48+
name string
49+
input *big.Int
50+
expected []byte
51+
}{
52+
{
53+
name: "nil",
54+
input: nil,
55+
expected: []byte{0x00},
56+
},
57+
{
58+
name: "positive",
59+
input: big.NewInt(12345),
60+
expected: append([]byte{0x01}, append([]byte{0x01}, big.NewInt(12345).Bytes()...)...),
61+
},
62+
{
63+
name: "negative",
64+
input: big.NewInt(-12345),
65+
expected: append([]byte{0x01}, append([]byte{0xFF}, big.NewInt(12345).Bytes()...)...),
66+
},
67+
{
68+
name: "zero",
69+
input: big.NewInt(0),
70+
expected: append([]byte{0x01}, []byte{0x00}...),
71+
},
72+
}
73+
74+
for _, tt := range tests {
75+
t.Run(tt.name, func(t *testing.T) {
76+
var buf bytes.Buffer
77+
err := WriteOptionalBigInt(&buf, tt.input)
78+
if err != nil {
79+
t.Fatalf("WriteOptionalBigInt() error = %v", err)
80+
}
81+
if !bytes.Equal(buf.Bytes(), tt.expected) {
82+
t.Errorf("WriteOptionalBigInt() = %v, want %v", buf.Bytes(), tt.expected)
83+
}
84+
})
85+
}
86+
}

lib/binaryutil/uint.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package binaryutil
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
)
7+
8+
func WriteOptionalUint64(w io.Writer, n *uint64) error {
9+
err := binary.Write(w, binary.LittleEndian, n != nil)
10+
if err != nil {
11+
return err
12+
}
13+
14+
if n != nil {
15+
err = binary.Write(w, binary.LittleEndian, *n)
16+
if err != nil {
17+
return err
18+
}
19+
}
20+
21+
return nil
22+
}

lib/binaryutil/uint_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package binaryutil
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestWriteOptionalUint64(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
input *uint64
12+
expected []byte
13+
}{
14+
{
15+
name: "nil",
16+
input: nil,
17+
expected: []byte{0x00},
18+
},
19+
{
20+
name: "present",
21+
input: func() *uint64 { v := uint64(12345); return &v }(),
22+
expected: []byte{0x01, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
23+
},
24+
}
25+
26+
for _, tt := range tests {
27+
t.Run(tt.name, func(t *testing.T) {
28+
var buf bytes.Buffer
29+
err := WriteOptionalUint64(&buf, tt.input)
30+
if err != nil {
31+
t.Fatalf("WriteOptionalUint64() error = %v", err)
32+
}
33+
if !bytes.Equal(buf.Bytes(), tt.expected) {
34+
t.Errorf("WriteOptionalUint64() = %v, want %v", buf.Bytes(), tt.expected)
35+
}
36+
})
37+
}
38+
}

relayer/quote.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package relayer
2+
3+
import (
4+
"bytes"
5+
"encoding/binary"
6+
"fmt"
7+
"math/big"
8+
"sort"
9+
"time"
10+
11+
"github.com/0xsequence/ethkit/ethwallet"
12+
"github.com/0xsequence/ethkit/go-ethereum/common"
13+
"github.com/0xsequence/go-sequence/lib/binaryutil"
14+
)
15+
16+
type FeeQuote struct {
17+
TransactionDigest common.Hash
18+
IsWhitelisted bool
19+
GasSponsor *uint64
20+
GasTank *uint64
21+
GasUsage *uint64
22+
GasPrice *big.Int
23+
NativePrice *big.Int
24+
TokenPrices map[string]*big.Int
25+
ExpiresAt *time.Time
26+
Signature []byte
27+
}
28+
29+
func (q *FeeQuote) Sign(wallet *ethwallet.Wallet, validFor time.Duration) error {
30+
expiresAt := time.Now().Add(validFor)
31+
q.ExpiresAt = &expiresAt
32+
33+
message, err := q.message()
34+
if err != nil {
35+
return err
36+
}
37+
38+
q.Signature, err = wallet.SignMessage(message)
39+
return err
40+
}
41+
42+
func (q *FeeQuote) Verify(wallet *ethwallet.Wallet) error {
43+
return q.VerifySignedBy(wallet.Address())
44+
}
45+
46+
// VerifySignedBy verifies the quote against one or more expected signer addresses
47+
func (q *FeeQuote) VerifySignedBy(expectedAddresses ...common.Address) error {
48+
if q.ExpiresAt.Before(time.Now()) {
49+
return fmt.Errorf("quote expired")
50+
}
51+
52+
message, err := q.message()
53+
if err != nil {
54+
return err
55+
}
56+
57+
address, err := ethwallet.RecoverAddress(message, q.Signature)
58+
if err != nil {
59+
return err
60+
}
61+
62+
for _, expected := range expectedAddresses {
63+
if address == expected {
64+
return nil
65+
}
66+
}
67+
68+
return fmt.Errorf("quote signed by unexpected address %s", address.Hex())
69+
}
70+
71+
func (q *FeeQuote) message() ([]byte, error) {
72+
if q.ExpiresAt == nil {
73+
return nil, fmt.Errorf("missing expiration date")
74+
}
75+
76+
var message bytes.Buffer
77+
78+
_, err := message.Write(q.TransactionDigest.Bytes())
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
err = binary.Write(&message, binary.LittleEndian, q.IsWhitelisted)
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
err = binaryutil.WriteOptionalUint64(&message, q.GasSponsor)
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
err = binaryutil.WriteOptionalUint64(&message, q.GasTank)
94+
if err != nil {
95+
return nil, err
96+
}
97+
98+
err = binaryutil.WriteOptionalUint64(&message, q.GasUsage)
99+
if err != nil {
100+
return nil, err
101+
}
102+
103+
err = binaryutil.WriteOptionalBigInt(&message, q.GasPrice)
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
err = binaryutil.WriteOptionalBigInt(&message, q.NativePrice)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
tokens := make([]string, 0, len(q.TokenPrices))
114+
for token := range q.TokenPrices {
115+
tokens = append(tokens, token)
116+
}
117+
sort.Strings(tokens)
118+
for _, token := range tokens {
119+
_, err = message.WriteString(token)
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
err = binaryutil.WriteBigInt(&message, q.TokenPrices[token])
125+
if err != nil {
126+
return nil, err
127+
}
128+
}
129+
130+
err = binary.Write(&message, binary.LittleEndian, q.ExpiresAt.Unix())
131+
if err != nil {
132+
return nil, err
133+
}
134+
135+
return message.Bytes(), nil
136+
}

0 commit comments

Comments
 (0)