From c86ba7e54f4a0d5a0e8a10874a6c682f831011fe Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 20 Feb 2024 22:04:50 +0530 Subject: [PATCH] Add FT/NFT type change rules for contract update validator --- cmd/util/ledger/migrations/cadence.go | 23 ++- .../migrations/staged_contracts_migration.go | 49 +++++- .../staged_contracts_migration_test.go | 160 ++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- insecure/go.mod | 2 +- insecure/go.sum | 4 +- integration/go.mod | 2 +- integration/go.sum | 4 +- 9 files changed, 224 insertions(+), 26 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index 8eb3c2e6182..f9de7c85ac8 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -15,30 +15,35 @@ import ( "github.com/onflow/flow-go/model/flow" ) -func NewCadence1InterfaceStaticTypeConverter(chainID flow.ChainID) statictypes.InterfaceTypeConverterFunc { +func NewInterfaceTypeConversionRules(chainID flow.ChainID) StaticTypeMigrationRules { systemContracts := systemcontracts.SystemContractsForChain(chainID) oldFungibleTokenResolverType, newFungibleTokenResolverType := fungibleTokenResolverRule(systemContracts) - rules := StaticTypeMigrationRules{ + return StaticTypeMigrationRules{ oldFungibleTokenResolverType.ID(): newFungibleTokenResolverType, } - - return NewStaticTypeMigrator[*interpreter.InterfaceStaticType](rules) } -func NewCadence1CompositeStaticTypeConverter(chainID flow.ChainID) statictypes.CompositeTypeConverterFunc { - +func NewCompositeTypeConversionRules(chainID flow.ChainID) StaticTypeMigrationRules { systemContracts := systemcontracts.SystemContractsForChain(chainID) oldFungibleTokenVaultCompositeType, newFungibleTokenVaultType := fungibleTokenVaultRule(systemContracts) oldNonFungibleTokenNFTCompositeType, newNonFungibleTokenNFTType := nonFungibleTokenNFTRule(systemContracts) - rules := StaticTypeMigrationRules{ + return StaticTypeMigrationRules{ oldFungibleTokenVaultCompositeType.ID(): newFungibleTokenVaultType, oldNonFungibleTokenNFTCompositeType.ID(): newNonFungibleTokenNFTType, } +} +func NewCadence1InterfaceStaticTypeConverter(chainID flow.ChainID) statictypes.InterfaceTypeConverterFunc { + rules := NewInterfaceTypeConversionRules(chainID) + return NewStaticTypeMigrator[*interpreter.InterfaceStaticType](rules) +} + +func NewCadence1CompositeStaticTypeConverter(chainID flow.ChainID) statictypes.CompositeTypeConverterFunc { + rules := NewCompositeTypeConversionRules(chainID) return NewStaticTypeMigrator[*interpreter.CompositeStaticType](rules) } @@ -193,7 +198,9 @@ func NewCadence1ContractsMigrations( stagedContracts []StagedContract, ) []ledger.Migration { - stagedContractsMigration := NewStagedContractsMigration(chainID) + stagedContractsMigration := NewStagedContractsMigration(chainID). + WithContractUpdateValidation() + stagedContractsMigration.RegisterContractUpdates(stagedContracts) return []ledger.Migration{ diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index d287aa681bd..5a156c6a737 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -24,13 +24,14 @@ import ( ) type StagedContractsMigration struct { - name string - chainID flow.ChainID - log zerolog.Logger - mutex sync.RWMutex - stagedContracts map[common.Address]map[flow.RegisterID]Contract - contractsByLocation map[common.Location][]byte - enableUpdateValidation bool + name string + chainID flow.ChainID + log zerolog.Logger + mutex sync.RWMutex + stagedContracts map[common.Address]map[flow.RegisterID]Contract + contractsByLocation map[common.Location][]byte + enableUpdateValidation bool + userDefinedTypeChangeCheckFunc func(oldTypeID common.TypeID, newTypeID common.TypeID) (checked bool, valid bool) } type StagedContract struct { @@ -56,6 +57,7 @@ func NewStagedContractsMigration(chainID flow.ChainID) *StagedContractsMigration func (m *StagedContractsMigration) WithContractUpdateValidation() *StagedContractsMigration { m.enableUpdateValidation = true + m.userDefinedTypeChangeCheckFunc = newUserDefinedTypeChangeCheckerFunc(m.chainID) return m } @@ -212,7 +214,7 @@ func (m *StagedContractsMigration) MigrateAccount( oldCode := payload.Value() if m.enableUpdateValidation { - err = CheckContractUpdateValidity( + err = m.checkContractUpdateValidity( mr, address, name, @@ -254,7 +256,7 @@ func (m *StagedContractsMigration) MigrateAccount( return payloads, nil } -func CheckContractUpdateValidity( +func (m *StagedContractsMigration) checkContractUpdateValidity( mr *migratorRuntime, address common.Address, contractName string, @@ -292,5 +294,34 @@ func CheckContractUpdateValidity( elaborations, ) + validator.WithUserDefinedTypeChangeChecker( + m.userDefinedTypeChangeCheckFunc, + ) + return validator.Validate() } + +func newUserDefinedTypeChangeCheckerFunc( + chainID flow.ChainID, +) func(oldTypeID common.TypeID, newTypeID common.TypeID) (checked, valid bool) { + + typeChangeRules := map[common.TypeID]common.TypeID{} + + compositeTypeRules := NewCompositeTypeConversionRules(chainID) + for typeID, newStaticType := range compositeTypeRules { + typeChangeRules[typeID] = newStaticType.ID() + } + + interfaceTypeRules := NewInterfaceTypeConversionRules(chainID) + for typeID, newStaticType := range interfaceTypeRules { + typeChangeRules[typeID] = newStaticType.ID() + } + + return func(oldTypeID common.TypeID, newTypeID common.TypeID) (checked, valid bool) { + expectedNewTypeID, found := typeChangeRules[oldTypeID] + if found { + return true, expectedNewTypeID == newTypeID + } + return false, false + } +} diff --git a/cmd/util/ledger/migrations/staged_contracts_migration_test.go b/cmd/util/ledger/migrations/staged_contracts_migration_test.go index 4213bb3a904..429ba9f0d98 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration_test.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration_test.go @@ -6,10 +6,12 @@ import ( "io" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/rs/zerolog" + "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/convert" "github.com/onflow/flow-go/model/flow" @@ -573,3 +575,161 @@ func TestStagedContractsWithImports(t *testing.T) { require.Equal(t, newCodeC, string(payloads[2].Value())) }) } + +func TestStagedContractsWithUpdateValidator(t *testing.T) { + t.Parallel() + + chainID := flow.Emulator + systemContracts := systemcontracts.SystemContractsForChain(chainID) + + address, err := common.HexToAddress("0x1") + require.NoError(t, err) + + ctx := context.Background() + + t.Run("FungibleToken.Vault", func(t *testing.T) { + t.Parallel() + + ftAddress := common.Address(systemContracts.FungibleToken.Address) + + oldCodeA := fmt.Sprintf(` + import FungibleToken from %s + + pub contract A { + pub var vault: @FungibleToken.Vault? + init() { + self.vault <- nil + } + } + `, + ftAddress.HexWithPrefix(), + ) + + newCodeA := fmt.Sprintf(` + import FungibleToken from %s + access(all) contract A { + access(all) var vault: @{FungibleToken.Vault}? + init() { + self.vault <- nil + } + } + `, + ftAddress.HexWithPrefix(), + ) + + ftContract := ` + access(all) contract FungibleToken { + access(all) resource interface Vault {} + } + ` + + stagedContracts := []StagedContract{ + { + Contract: Contract{ + Name: "A", + Code: []byte(newCodeA), + }, + Address: address, + }, + } + + migration := NewStagedContractsMigration(chainID) + migration.RegisterContractUpdates(stagedContracts) + migration.WithContractUpdateValidation() + + logWriter := &logWriter{} + log := zerolog.New(logWriter) + err = migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + payloads := []*ledger.Payload{ + newContractPayload(address, "A", []byte(oldCodeA)), + newContractPayload(ftAddress, "FungibleToken", []byte(ftContract)), + } + + payloads, err = migration.MigrateAccount(ctx, address, payloads) + require.NoError(t, err) + + err = migration.Close() + require.NoError(t, err) + + require.Empty(t, logWriter.logs) + + require.Len(t, payloads, 2) + require.Equal(t, newCodeA, string(payloads[0].Value())) + }) + + t.Run("other type", func(t *testing.T) { + t.Parallel() + + otherAddress, err := common.HexToAddress("0x2") + require.NoError(t, err) + + oldCodeA := fmt.Sprintf(` + import FungibleToken from %s + + pub contract A { + pub var vault: @FungibleToken.Vault? + init() { + self.vault <- nil + } + } + `, + otherAddress.HexWithPrefix(), // Importing from some other address + ) + + newCodeA := fmt.Sprintf(` + import FungibleToken from %s + access(all) contract A { + access(all) var vault: @{FungibleToken.Vault}? + init() { + self.vault <- nil + } + } + `, + otherAddress.HexWithPrefix(), // Importing from some other address + ) + + ftContract := ` + access(all) contract FungibleToken { + access(all) resource interface Vault {} + } + ` + + stagedContracts := []StagedContract{ + { + Contract: Contract{ + Name: "A", + Code: []byte(newCodeA), + }, + Address: address, + }, + } + + migration := NewStagedContractsMigration(chainID) + migration.RegisterContractUpdates(stagedContracts) + migration.WithContractUpdateValidation() + + logWriter := &logWriter{} + log := zerolog.New(logWriter) + err = migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + payloads := []*ledger.Payload{ + newContractPayload(address, "A", []byte(oldCodeA)), + newContractPayload(otherAddress, "FungibleToken", []byte(ftContract)), + } + + payloads, err = migration.MigrateAccount(ctx, address, payloads) + require.NoError(t, err) + + err = migration.Close() + require.NoError(t, err) + + require.Len(t, logWriter.logs, 1) + assert.Contains(t, logWriter.logs[0], "cannot update contract `A`") + + require.Len(t, payloads, 2) + require.Equal(t, oldCodeA, string(payloads[0].Value())) + }) +} diff --git a/go.mod b/go.mod index 318d8ef9804..aeb709ecaaa 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.3 github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f - github.com/onflow/cadence v1.0.0-preview.2.0.20240216110422-a67dbbf21171 + github.com/onflow/cadence v1.0.0-preview.2.0.20240220162541-acde2e928710 github.com/onflow/crypto v0.25.0 github.com/onflow/flow v0.3.4 github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.2-0.20240206003101-928bf99024d7 diff --git a/go.sum b/go.sum index fb22a4f8c98..10862e5fe0d 100644 --- a/go.sum +++ b/go.sum @@ -2476,8 +2476,8 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6 github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f h1:Z8/PgTqOgOg02MTRpTBYO2k16FE6z4wEOtaC2WBR9Xo= github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f/go.mod h1:xvP61FoOs95K7IYdIYRnNcYQGf4nbF/uuJ0tHf4DRuM= github.com/onflow/cadence v1.0.0-M3/go.mod h1:odXGZZ/wGNA5mwT8bC9v8u8EXACHllB2ABSZK65TGL8= -github.com/onflow/cadence v1.0.0-preview.2.0.20240216110422-a67dbbf21171 h1:l9AQBie6xFp3+vv+pWfUctSR6Uax7j5RZpAhP2z06So= -github.com/onflow/cadence v1.0.0-preview.2.0.20240216110422-a67dbbf21171/go.mod h1:a4mccDU90hmuxCLUFzs9J/ANG/rYbFa36h4Z0bBAqNU= +github.com/onflow/cadence v1.0.0-preview.2.0.20240220162541-acde2e928710 h1:NQ/8SnXikjdKNmLLITGo9RVhV71m+RwjhCmOAkvPKio= +github.com/onflow/cadence v1.0.0-preview.2.0.20240220162541-acde2e928710/go.mod h1:a4mccDU90hmuxCLUFzs9J/ANG/rYbFa36h4Z0bBAqNU= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/flow v0.3.4 h1:FXUWVdYB90f/rjNcY0Owo30gL790tiYff9Pb/sycXYE= diff --git a/insecure/go.mod b/insecure/go.mod index c3296cc1822..aad463cf6ff 100644 --- a/insecure/go.mod +++ b/insecure/go.mod @@ -205,7 +205,7 @@ require ( github.com/multiformats/go-varint v0.0.7 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f // indirect - github.com/onflow/cadence v1.0.0-preview.2.0.20240216110422-a67dbbf21171 // indirect + github.com/onflow/cadence v1.0.0-preview.2.0.20240220162541-acde2e928710 // indirect github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.2-0.20240206003101-928bf99024d7 // indirect github.com/onflow/flow-core-contracts/lib/go/templates v0.15.2-0.20240206003101-928bf99024d7 // indirect github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20240205224107-320aa3cf09e0 // indirect diff --git a/insecure/go.sum b/insecure/go.sum index d92d2486600..3949e0a158e 100644 --- a/insecure/go.sum +++ b/insecure/go.sum @@ -2463,8 +2463,8 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6 github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f h1:Z8/PgTqOgOg02MTRpTBYO2k16FE6z4wEOtaC2WBR9Xo= github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f/go.mod h1:xvP61FoOs95K7IYdIYRnNcYQGf4nbF/uuJ0tHf4DRuM= github.com/onflow/cadence v1.0.0-M3/go.mod h1:odXGZZ/wGNA5mwT8bC9v8u8EXACHllB2ABSZK65TGL8= -github.com/onflow/cadence v1.0.0-preview.2.0.20240216110422-a67dbbf21171 h1:l9AQBie6xFp3+vv+pWfUctSR6Uax7j5RZpAhP2z06So= -github.com/onflow/cadence v1.0.0-preview.2.0.20240216110422-a67dbbf21171/go.mod h1:a4mccDU90hmuxCLUFzs9J/ANG/rYbFa36h4Z0bBAqNU= +github.com/onflow/cadence v1.0.0-preview.2.0.20240220162541-acde2e928710 h1:NQ/8SnXikjdKNmLLITGo9RVhV71m+RwjhCmOAkvPKio= +github.com/onflow/cadence v1.0.0-preview.2.0.20240220162541-acde2e928710/go.mod h1:a4mccDU90hmuxCLUFzs9J/ANG/rYbFa36h4Z0bBAqNU= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.2-0.20240206003101-928bf99024d7 h1:OI/4F2NK/X/4x3dTUFFDGtuOsSa9pX+jjBeSEcBrY/M= diff --git a/integration/go.mod b/integration/go.mod index 0a0c645f350..20b6cac99c1 100644 --- a/integration/go.mod +++ b/integration/go.mod @@ -19,7 +19,7 @@ require ( github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.3.0 - github.com/onflow/cadence v1.0.0-preview.2.0.20240216110422-a67dbbf21171 + github.com/onflow/cadence v1.0.0-preview.2.0.20240220162541-acde2e928710 github.com/onflow/crypto v0.25.0 github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.2-0.20240206003101-928bf99024d7 github.com/onflow/flow-core-contracts/lib/go/templates v0.15.2-0.20240206003101-928bf99024d7 diff --git a/integration/go.sum b/integration/go.sum index e3779ccd865..ea800b9aefa 100644 --- a/integration/go.sum +++ b/integration/go.sum @@ -2538,8 +2538,8 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6 github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f h1:Z8/PgTqOgOg02MTRpTBYO2k16FE6z4wEOtaC2WBR9Xo= github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f/go.mod h1:xvP61FoOs95K7IYdIYRnNcYQGf4nbF/uuJ0tHf4DRuM= github.com/onflow/cadence v1.0.0-M3/go.mod h1:odXGZZ/wGNA5mwT8bC9v8u8EXACHllB2ABSZK65TGL8= -github.com/onflow/cadence v1.0.0-preview.2.0.20240216110422-a67dbbf21171 h1:l9AQBie6xFp3+vv+pWfUctSR6Uax7j5RZpAhP2z06So= -github.com/onflow/cadence v1.0.0-preview.2.0.20240216110422-a67dbbf21171/go.mod h1:a4mccDU90hmuxCLUFzs9J/ANG/rYbFa36h4Z0bBAqNU= +github.com/onflow/cadence v1.0.0-preview.2.0.20240220162541-acde2e928710 h1:NQ/8SnXikjdKNmLLITGo9RVhV71m+RwjhCmOAkvPKio= +github.com/onflow/cadence v1.0.0-preview.2.0.20240220162541-acde2e928710/go.mod h1:a4mccDU90hmuxCLUFzs9J/ANG/rYbFa36h4Z0bBAqNU= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.2-0.20240206003101-928bf99024d7 h1:OI/4F2NK/X/4x3dTUFFDGtuOsSa9pX+jjBeSEcBrY/M=