Skip to content

Commit 07d030b

Browse files
authored
feat: Adds MemoryContractMetadata implementation (#17059)
* feat: Adds MemoryDataStore implementation * chore: 'github.com/stretchr/testify/assert' -> 'github.com/stretchr/testify/require' * feat: Adds MemoryContractMetadata implementation * feat: adds generic Metadata to ContractMetadata * feat: adds ContractMetadataByChainSelector filter * feat: adds generic Metadata to MemoryContractMetadataStore * feat: adds generic Metadata to BaseDataStore * feat: remove Merger from DataStore * feat: improve TestMemoryDataStore_Merge * feat: adds JSON annotation to MemoryContractMetadataStore.Records * feat: ContractMetadata docstrings and tests * feat: ContractMetadataKey docstrings and tests * feat: MemoryContractMetadataStore docstrings * chore: rename chain -> chainSelector for contractMetadataKey * chore: TestContractMetadata_Clone add extra clone check
1 parent 97b9a82 commit 07d030b

10 files changed

+922
-28
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package datastore
2+
3+
import "errors"
4+
5+
var ErrContractMetadataNotFound = errors.New("no contract metadata record can be found for the provided key")
6+
var ErrContractMetadataExists = errors.New("a contract metadata record with the supplied key already exists")
7+
8+
// ContractMetadata implements the Record interface
9+
var _ Record[ContractMetadataKey, ContractMetadata[DefaultMetadata]] = ContractMetadata[DefaultMetadata]{}
10+
11+
// DefaultMetadata is a default implementation of the custom contract metadata that can be used to store the
12+
// contract metadata in the datastore as a simple string.
13+
type DefaultMetadata string
14+
15+
// DefaultMetadata implements the Cloneable interface
16+
func (d DefaultMetadata) Clone() DefaultMetadata { return d }
17+
18+
// ContractMetadata is a generic struct that holds the metadata for a contract on a specific chain.
19+
// It implements the Record interface and is used to store contract metadata in the datastore.
20+
// The metadata is generic and can be of any type that implements the Cloneable interface.
21+
type ContractMetadata[M Cloneable[M]] struct {
22+
// Address is the address of the contract on the chain.
23+
Address string `json:"address"`
24+
// ChainSelector is the chain-selector of the chain where the contract is deployed.
25+
ChainSelector uint64 `json:"chainSelector"`
26+
// Metadata is the metadata associated with the contract.
27+
// It is a generic type that can be of any type that implements the Cloneable interface.
28+
Metadata M `json:"metadata"`
29+
}
30+
31+
// Clone creates a copy of the ContractMetadata.
32+
// The Metadata field is cloned using the Clone method of the Cloneable interface.
33+
func (r ContractMetadata[M]) Clone() ContractMetadata[M] {
34+
return ContractMetadata[M]{
35+
ChainSelector: r.ChainSelector,
36+
Address: r.Address,
37+
Metadata: r.Metadata.Clone(),
38+
}
39+
}
40+
41+
// Key returns the ContractMetadataKey for the ContractMetadata.
42+
// It is used to uniquely identify the contract metadata in the datastore.
43+
func (r ContractMetadata[M]) Key() ContractMetadataKey {
44+
return NewContractMetadataKey(r.ChainSelector, r.Address)
45+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package datastore
2+
3+
// ContractMetadataKey is an interface that represents a key for ContractMetadata records.
4+
// It is used to uniquely identify a record in the ContractMetadataStore.
5+
type ContractMetadataKey interface {
6+
Comparable[ContractMetadataKey]
7+
8+
// Address returns the address of the contract on the chain.
9+
Address() string
10+
// ChainSelector returns the chain-selector of the chain where the contract is deployed.
11+
ChainSelector() uint64
12+
}
13+
14+
// contractMetadataKey implements the ContractMetadataKey interface.
15+
var _ ContractMetadataKey = contractMetadataKey{}
16+
17+
// contractMetadataKey is a struct that implements the ContractMetadataKey interface.
18+
// It is used to uniquely identify a record in the ContractMetadataStore.
19+
type contractMetadataKey struct {
20+
chainSelector uint64
21+
address string
22+
}
23+
24+
// ChainSelector returns the chain-selector of the chain where the contract is deployed.
25+
func (c contractMetadataKey) ChainSelector() uint64 { return c.chainSelector }
26+
27+
// Address returns the address of the contract on the chain.
28+
func (c contractMetadataKey) Address() string { return c.address }
29+
30+
// Equals returns true if the two ContractMetadataKey instances are equal, false otherwise.
31+
func (c contractMetadataKey) Equals(other ContractMetadataKey) bool {
32+
return c.chainSelector == other.ChainSelector() &&
33+
c.address == other.Address()
34+
}
35+
36+
// NewContractMetadataKey creates a new ContractMetadataKey instance.
37+
func NewContractMetadataKey(chainSelector uint64, address string) ContractMetadataKey {
38+
return contractMetadataKey{
39+
chainSelector: chainSelector,
40+
address: address,
41+
}
42+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package datastore
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestContractMetadataKey_Equals(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
key1 ContractMetadataKey
13+
key2 ContractMetadataKey
14+
expected bool
15+
}{
16+
{
17+
name: "Equal keys",
18+
key1: NewContractMetadataKey(1, "0x1234567890abcdef"),
19+
key2: NewContractMetadataKey(1, "0x1234567890abcdef"),
20+
expected: true,
21+
},
22+
{
23+
name: "Different chain selector",
24+
key1: NewContractMetadataKey(1, "0x1234567890abcdef"),
25+
key2: NewContractMetadataKey(2, "0x1234567890abcdef"),
26+
expected: false,
27+
},
28+
{
29+
name: "Different address",
30+
key1: NewContractMetadataKey(1, "0x1234567890abcdef"),
31+
key2: NewContractMetadataKey(1, "0xabcdef1234567890"),
32+
expected: false,
33+
},
34+
{
35+
name: "Completely different keys",
36+
key1: NewContractMetadataKey(1, "0x1234567890abcdef"),
37+
key2: NewContractMetadataKey(2, "0xabcdef1234567890"),
38+
expected: false,
39+
},
40+
}
41+
42+
for _, tt := range tests {
43+
t.Run(tt.name, func(t *testing.T) {
44+
require.Equal(t, tt.expected, tt.key1.Equals(tt.key2))
45+
})
46+
}
47+
}
48+
49+
func TestContractMetadataKey(t *testing.T) {
50+
chainSelector := uint64(1)
51+
address := "0x1234567890abcdef"
52+
53+
key := NewContractMetadataKey(chainSelector, address)
54+
55+
require.Equal(t, chainSelector, key.ChainSelector(), "ChainSelector should return the correct chain selector")
56+
require.Equal(t, address, key.Address(), "Address should return the correct address")
57+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package datastore
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestContractMetadata_Clone(t *testing.T) {
10+
original := ContractMetadata[DefaultMetadata]{
11+
ChainSelector: 1,
12+
Address: "0x123",
13+
Metadata: DefaultMetadata("test data"),
14+
}
15+
16+
cloned := original.Clone()
17+
18+
assert.Equal(t, original.ChainSelector, cloned.ChainSelector)
19+
assert.Equal(t, original.Address, cloned.Address)
20+
assert.Equal(t, original.Metadata, cloned.Metadata)
21+
22+
// Modify the original and ensure the cloned remains unchanged
23+
original.ChainSelector = 2
24+
original.Address = "0x456"
25+
original.Metadata = DefaultMetadata("updated data")
26+
27+
assert.NotEqual(t, original.ChainSelector, cloned.ChainSelector)
28+
assert.NotEqual(t, original.Address, cloned.Address)
29+
assert.NotEqual(t, original.Metadata, cloned.Metadata)
30+
}
31+
32+
func TestContractMetadata_Key(t *testing.T) {
33+
metadata := ContractMetadata[DefaultMetadata]{
34+
ChainSelector: 1,
35+
Address: "0x123",
36+
Metadata: DefaultMetadata("test data"),
37+
}
38+
39+
key := metadata.Key()
40+
expectedKey := NewContractMetadataKey(1, "0x123")
41+
42+
assert.Equal(t, expectedKey, key)
43+
}

deployment/datastore/filters.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,16 @@ func AddressRefByQualifier(qualifier string) FilterFunc[AddressRefKey, AddressRe
6565
return record.Qualifier == qualifier
6666
})
6767
}
68+
69+
// ContractMetadataByChainSelector returns a filter that only includes records with the provided chain.
70+
func ContractMetadataByChainSelector[M Cloneable[M]](chainSelector uint64) FilterFunc[ContractMetadataKey, ContractMetadata[M]] {
71+
return func(records []ContractMetadata[M]) []ContractMetadata[M] {
72+
filtered := make([]ContractMetadata[M], 0, len(records)) // Pre-allocate capacity
73+
for _, record := range records {
74+
if record.ChainSelector == chainSelector {
75+
filtered = append(filtered, record)
76+
}
77+
}
78+
return filtered
79+
}
80+
}

deployment/datastore/filters_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,59 @@ func TestAddressRefByQualifier(t *testing.T) {
253253
})
254254
}
255255
}
256+
257+
func TestContractMetadataByChainSelector(t *testing.T) {
258+
var (
259+
recordOne = ContractMetadata[DefaultMetadata]{
260+
ChainSelector: 1,
261+
Metadata: DefaultMetadata("Record1"),
262+
}
263+
recordTwo = ContractMetadata[DefaultMetadata]{
264+
ChainSelector: 2,
265+
Metadata: DefaultMetadata("Record2"),
266+
}
267+
recordThree = ContractMetadata[DefaultMetadata]{
268+
ChainSelector: 1,
269+
Metadata: DefaultMetadata("Record3"),
270+
}
271+
)
272+
273+
tests := []struct {
274+
name string
275+
givenState []ContractMetadata[DefaultMetadata]
276+
giveChain uint64
277+
expectedResult []ContractMetadata[DefaultMetadata]
278+
}{
279+
{
280+
name: "success: returns records with given chain",
281+
givenState: []ContractMetadata[DefaultMetadata]{
282+
recordOne,
283+
recordTwo,
284+
recordThree,
285+
},
286+
giveChain: 1,
287+
expectedResult: []ContractMetadata[DefaultMetadata]{
288+
recordOne,
289+
recordThree,
290+
},
291+
},
292+
{
293+
name: "success: returns no records with given chain",
294+
givenState: []ContractMetadata[DefaultMetadata]{
295+
recordOne,
296+
recordTwo,
297+
recordThree,
298+
},
299+
giveChain: 3,
300+
expectedResult: []ContractMetadata[DefaultMetadata]{},
301+
},
302+
}
303+
304+
for _, tt := range tests {
305+
t.Run(tt.name, func(t *testing.T) {
306+
filter := ContractMetadataByChainSelector[DefaultMetadata](tt.giveChain)
307+
filteredRecords := filter(tt.givenState)
308+
assert.Equal(t, tt.expectedResult, filteredRecords)
309+
})
310+
}
311+
}

0 commit comments

Comments
 (0)