Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ func (c *Config) Validate() error {
}
}

if err := c.Contracts.ValidateCanonical(); err != nil {
return err
}

if err := c.Dependencies.ValidateCanonical(); err != nil {
return err
}

for _, em := range c.Emulators {
if _, err := c.Accounts.ByName(em.ServiceAccount); err != nil {
return fmt.Errorf("emulator %s contains nonexisting service account %s", em.Name, em.ServiceAccount)
Expand Down
1 change: 1 addition & 0 deletions config/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ func (c *Contracts) AddDependencyAsContract(dependency Dependency, networkName s
Location: filepath.ToSlash(fmt.Sprintf("%s/%s/%s.cdc", dependencyManagerDirectory, dependency.Source.Address, dependency.Source.ContractName)),
Aliases: aliases,
IsDependency: true,
Canonical: dependency.Canonical,
}

if _, err := c.ByName(contract.Name); err != nil {
Expand Down
21 changes: 17 additions & 4 deletions config/dependency.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ type Source struct {
}

type Dependency struct {
Name string
Source Source
Hash string
Aliases Aliases
Name string
Source Source
Hash string
Aliases Aliases
Canonical string
}

type Dependencies []Dependency
Expand All @@ -77,3 +78,15 @@ func (d *Dependencies) AddOrUpdate(dep Dependency) {

*d = append(*d, dep)
}

// ValidateCanonical validates that all canonical references are valid for dependencies.
func (d *Dependencies) ValidateCanonical() error {
for _, dependency := range *d {
if dependency.Canonical != "" {
if dependency.Canonical == dependency.Name {
return fmt.Errorf("dependency %s cannot have itself as canonical", dependency.Name)
}
}
}
return nil
}
31 changes: 31 additions & 0 deletions config/dependency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,34 @@ func TestDependencies_AddOrUpdate(t *testing.T) {
dep := dependencies.ByName("mydep")
assert.NotNil(t, dep)
}

func TestDependencies_ValidateCanonical(t *testing.T) {
t.Run("valid canonical reference", func(t *testing.T) {
dependencies := Dependencies{
Dependency{Name: "NumberFormatter", Canonical: ""},
Dependency{Name: "NumberFormatterAlias", Canonical: "NumberFormatter"},
}

err := dependencies.ValidateCanonical()
assert.NoError(t, err)
})

t.Run("self-referential canonical", func(t *testing.T) {
dependencies := Dependencies{
Dependency{Name: "NumberFormatter", Canonical: "NumberFormatter"},
}

err := dependencies.ValidateCanonical()
assert.Error(t, err)
assert.Contains(t, err.Error(), "cannot have itself as canonical")
})

t.Run("no canonical reference", func(t *testing.T) {
dependencies := Dependencies{
Dependency{Name: "NumberFormatter", Canonical: ""},
}

err := dependencies.ValidateCanonical()
assert.NoError(t, err)
})
}
101 changes: 101 additions & 0 deletions config/json/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,104 @@ func Test_TransformComplexContractToJSON(t *testing.T) {

assert.JSONEq(t, string(b), string(x))
}

func Test_ConfigContractsWithCanonical(t *testing.T) {
b := []byte(`{
"FUSD": {
"source": "./contracts/FUSD.cdc",
"aliases": {
"testnet": "9a0766d93b6608b7",
"mainnet": "3c5959b568896393"
}
},
"FUSD1": {
"source": "./contracts/FUSD.cdc",
"aliases": {
"testnet": "e223d8a629e49c68",
"mainnet": "8d0e87b65159ae63"
},
"canonical": "FUSD"
},
"FUSD2": {
"source": "./contracts/FUSD.cdc",
"aliases": {
"testnet": "0f9df91c9121c460",
"mainnet": "754a90d51a1c8e1b"
},
"canonical": "FUSD"
}
}`)

var jsonContracts jsonContracts
err := json.Unmarshal(b, &jsonContracts)
assert.NoError(t, err)

contracts, err := jsonContracts.transformToConfig()
assert.NoError(t, err)

assert.Len(t, contracts, 3)

fusd, _ := contracts.ByName("FUSD")
assert.NotNil(t, fusd)
assert.Equal(t, "", fusd.Canonical)
assert.False(t, fusd.IsAlias())

fusd1, _ := contracts.ByName("FUSD1")
assert.NotNil(t, fusd1)
assert.Equal(t, "FUSD", fusd1.Canonical)
assert.True(t, fusd1.IsAlias())
assert.Equal(t, "FUSD", fusd1.CanonicalName())

fusd2, _ := contracts.ByName("FUSD2")
assert.NotNil(t, fusd2)
assert.Equal(t, "FUSD", fusd2.Canonical)
assert.True(t, fusd2.IsAlias())
assert.Equal(t, "FUSD", fusd2.CanonicalName())
}

func Test_TransformContractsWithCanonicalToJSON(t *testing.T) {
b := []byte(`{
"FUSD": {
"source": "./contracts/FUSD.cdc",
"aliases": {
"testnet": "9a0766d93b6608b7",
"mainnet": "3c5959b568896393"
}
},
"FUSD1": {
"source": "./contracts/FUSD.cdc",
"aliases": {
"testnet": "e223d8a629e49c68",
"mainnet": "8d0e87b65159ae63"
},
"canonical": "FUSD"
},
"FUSD2": {
"source": "./contracts/FUSD.cdc",
"aliases": {
"testnet": "0f9df91c9121c460",
"mainnet": "754a90d51a1c8e1b"
},
"canonical": "FUSD"
}
}`)

var jsonContracts jsonContracts
err := json.Unmarshal(b, &jsonContracts)
assert.NoError(t, err)

contracts, err := jsonContracts.transformToConfig()
assert.NoError(t, err)

j := transformContractsToJSON(contracts)

x, _ := json.Marshal(j)

var result map[string]jsonContract
err = json.Unmarshal(x, &result)
assert.NoError(t, err)

assert.Equal(t, "", result["FUSD"].Advanced.Canonical)
assert.Equal(t, "FUSD", result["FUSD1"].Advanced.Canonical)
assert.Equal(t, "FUSD", result["FUSD2"].Advanced.Canonical)
}
19 changes: 11 additions & 8 deletions config/json/dependency.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ func (j jsonDependencies) transformToConfig() (config.Dependencies, error) {
}

dep = config.Dependency{
Name: dependencyName,
Hash: dependency.Extended.Hash,
Name: dependencyName,
Hash: dependency.Extended.Hash,
Canonical: dependency.Extended.Canonical,
Source: config.Source{
NetworkName: depNetwork,
Address: flow.HexToAddress(depAddress),
Expand Down Expand Up @@ -99,9 +100,10 @@ func transformDependenciesToJSON(configDependencies config.Dependencies, configC

jsonDeps[dep.Name] = jsonDependency{
Extended: jsonDependencyExtended{
Source: buildSourceString(dep.Source),
Hash: dep.Hash,
Aliases: aliases,
Source: buildSourceString(dep.Source),
Hash: dep.Hash,
Aliases: aliases,
Canonical: dep.Canonical,
},
}
}
Expand All @@ -123,9 +125,10 @@ func buildSourceString(source config.Source) string {

// jsonDependencyExtended for json parsing advanced config.
type jsonDependencyExtended struct {
Source string `json:"source"`
Hash string `json:"hash"`
Aliases map[string]string `json:"aliases"`
Source string `json:"source"`
Hash string `json:"hash"`
Aliases map[string]string `json:"aliases"`
Canonical string `json:"canonical,omitempty"`
}

// jsonDependency structure for json parsing.
Expand Down
86 changes: 86 additions & 0 deletions config/json/dependency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,89 @@ func Test_TransformDependenciesToJSON(t *testing.T) {

assert.Equal(t, cleanSpecialChars(bOut), cleanSpecialChars(x))
}

func Test_ConfigDependenciesWithCanonical(t *testing.T) {
b := []byte(`{
"NumberFormatter": {
"source": "testnet://8a4dce54554b225d.NumberFormatter",
"hash": "dc7043832da46dbcc8242a53fa95b37f020bc374df42586a62703b2651979fb9",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"testnet": "8a4dce54554b225d"
}
},
"NumberFormatterAlias": {
"source": "testnet://8a4dce54554b225d.NumberFormatter",
"hash": "dc7043832da46dbcc8242a53fa95b37f020bc374df42586a62703b2651979fb9",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"testnet": "8a4dce54554b225d"
},
"canonical": "NumberFormatter"
}
}`)

var jsonDependencies jsonDependencies
err := json.Unmarshal(b, &jsonDependencies)
assert.NoError(t, err)

dependencies, err := jsonDependencies.transformToConfig()
assert.NoError(t, err)

assert.Len(t, dependencies, 2)

canonicalDep := dependencies.ByName("NumberFormatter")
assert.NotNil(t, canonicalDep)
assert.Equal(t, "", canonicalDep.Canonical)

aliasDep := dependencies.ByName("NumberFormatterAlias")
assert.NotNil(t, aliasDep)
assert.Equal(t, "NumberFormatter", aliasDep.Canonical)
}

func Test_TransformDependenciesWithCanonicalToJSON(t *testing.T) {
b := []byte(`{
"NumberFormatter": {
"source": "testnet://8a4dce54554b225d.NumberFormatter",
"hash": "dc7043832da46dbcc8242a53fa95b37f020bc374df42586a62703b2651979fb9",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"testnet": "8a4dce54554b225d"
}
},
"NumberFormatterAlias": {
"source": "testnet://8a4dce54554b225d.NumberFormatter",
"hash": "dc7043832da46dbcc8242a53fa95b37f020bc374df42586a62703b2651979fb9",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"testnet": "8a4dce54554b225d"
},
"canonical": "NumberFormatter"
}
}`)

var jsonContracts jsonContracts
errContracts := json.Unmarshal(b, &jsonContracts)
assert.NoError(t, errContracts)

var jsonDependencies jsonDependencies
err := json.Unmarshal(b, &jsonDependencies)
assert.NoError(t, err)

contracts, err := jsonContracts.transformToConfig()
assert.NoError(t, err)
dependencies, err := jsonDependencies.transformToConfig()
assert.NoError(t, err)

j := transformDependenciesToJSON(dependencies, contracts)

x, _ := json.Marshal(j)

// Parse back and check canonical field
var result map[string]jsonDependency
err = json.Unmarshal(x, &result)
assert.NoError(t, err)

assert.Equal(t, "", result["NumberFormatter"].Extended.Canonical)
assert.Equal(t, "NumberFormatter", result["NumberFormatterAlias"].Extended.Canonical)
}
3 changes: 3 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@
}
},
"type": "object"
},
"canonical": {
"type": "string"
}
},
"additionalProperties": false,
Expand Down
14 changes: 12 additions & 2 deletions state.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,20 +301,30 @@ func (p *State) AliasesForNetwork(network config.Network) project.LocationAliase
// // Get canonical mappings
// mapping := state.CanonicalContractMapping()
// // Returns: {"FUSD_v2": "FUSD"}
//
//
// // Use with import replacer
// contracts, _ := state.DeploymentContractsByNetwork(network)
// aliases := state.AliasesForNetwork(network)
// replacer := project.NewImportReplacer(contracts, aliases, mapping)
//
//
// // Process imports: "FUSD_v2" → "import FUSD as FUSD_v2 from 0x..."
func (p *State) CanonicalContractMapping() map[string]string {
canonicalMapping := make(map[string]string)

// Include canonical mappings from contracts
for _, contract := range p.conf.Contracts {
if contract.IsAlias() {
canonicalMapping[contract.Name] = contract.Canonical
}
}

// Include canonical mappings from dependencies
for _, dependency := range p.conf.Dependencies {
if dependency.Canonical != "" {
canonicalMapping[dependency.Name] = dependency.Canonical
}
}

return canonicalMapping
}

Expand Down
Loading