From 8c360ed83eec9aa78f448eb9cf4a4835eb07ad32 Mon Sep 17 00:00:00 2001 From: Janez Podhostnik Date: Tue, 14 May 2024 14:49:05 +0200 Subject: [PATCH 1/4] Add a dependency check for cadecne imports --- fvm/context.go | 9 + fvm/environment/env.go | 6 + fvm/environment/mock/environment.go | 52 +++++ fvm/environment/programs.go | 8 + fvm/environment/system_contracts.go | 45 ++++ fvm/fvm_test.go | 277 ++++++++++++++++++++++++ fvm/storage/derived/dependencies.go | 6 + fvm/systemcontracts/system_contracts.go | 1 + fvm/transactionInvoker.go | 59 +++++ module/mock/machine_account_metrics.go | 40 ++++ 10 files changed, 503 insertions(+) create mode 100644 module/mock/machine_account_metrics.go diff --git a/fvm/context.go b/fvm/context.go index c1b0dd09d32..0a2295196e3 100644 --- a/fvm/context.go +++ b/fvm/context.go @@ -29,6 +29,7 @@ type Context struct { // limits and set them to MaxUint64, effectively disabling these limits. DisableMemoryAndInteractionLimits bool EVMEnabled bool + DependencyCheckEnabled bool ComputationLimit uint64 MemoryLimit uint64 MaxStateKeySize uint64 @@ -193,6 +194,14 @@ func WithServiceEventCollectionEnabled() Option { } } +// WithDependencyCheckEnabled enables or disables the dependency check. +func WithDependencyCheckEnabled(enabled bool) Option { + return func(ctx Context) Context { + ctx.DependencyCheckEnabled = enabled + return ctx + } +} + // WithBlocks sets the block storage provider for a virtual machine context. // // The VM uses the block storage provider to provide historical block information to diff --git a/fvm/environment/env.go b/fvm/environment/env.go index 59dc4f83416..0b7cd677d37 100644 --- a/fvm/environment/env.go +++ b/fvm/environment/env.go @@ -3,10 +3,12 @@ package environment import ( "github.com/onflow/cadence" "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" "github.com/rs/zerolog" otelTrace "go.opentelemetry.io/otel/trace" reusableRuntime "github.com/onflow/flow-go/fvm/runtime" + "github.com/onflow/flow-go/fvm/storage/derived" "github.com/onflow/flow-go/fvm/tracing" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/trace" @@ -85,6 +87,10 @@ type Environment interface { // Reset resets all stateful environment modules (e.g., ContractUpdater, // EventEmitter) to initial state. Reset() + + GetProgramDependencies() (derived.ProgramDependencies, error) + + CheckDependencies(dependencies []common.AddressLocation, auths []flow.Address) (cadence.Value, error) } type EnvironmentParams struct { diff --git a/fvm/environment/mock/environment.go b/fvm/environment/mock/environment.go index 63b52c751a4..da65b863989 100644 --- a/fvm/environment/mock/environment.go +++ b/fvm/environment/mock/environment.go @@ -12,6 +12,8 @@ import ( common "github.com/onflow/cadence/runtime/common" + derived "github.com/onflow/flow-go/fvm/storage/derived" + environment "github.com/onflow/flow-go/fvm/environment" flow "github.com/onflow/flow-go/model/flow" @@ -252,6 +254,32 @@ func (_m *Environment) BorrowCadenceRuntime() *runtime.ReusableCadenceRuntime { return r0 } +// CheckDependencies provides a mock function with given fields: dependencies, auths +func (_m *Environment) CheckDependencies(dependencies []common.AddressLocation, auths []flow.Address) (cadence.Value, error) { + ret := _m.Called(dependencies, auths) + + var r0 cadence.Value + var r1 error + if rf, ok := ret.Get(0).(func([]common.AddressLocation, []flow.Address) (cadence.Value, error)); ok { + return rf(dependencies, auths) + } + if rf, ok := ret.Get(0).(func([]common.AddressLocation, []flow.Address) cadence.Value); ok { + r0 = rf(dependencies, auths) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(cadence.Value) + } + } + + if rf, ok := ret.Get(1).(func([]common.AddressLocation, []flow.Address) error); ok { + r1 = rf(dependencies, auths) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // CheckPayerBalanceAndGetMaxTxFees provides a mock function with given fields: payer, inclusionEffort, executionEffort func (_m *Environment) CheckPayerBalanceAndGetMaxTxFees(payer flow.Address, inclusionEffort uint64, executionEffort uint64) (cadence.Value, error) { ret := _m.Called(payer, inclusionEffort, executionEffort) @@ -803,6 +831,30 @@ func (_m *Environment) GetOrLoadProgram(location common.Location, load func() (* return r0, r1 } +// GetProgramDependencies provides a mock function with given fields: +func (_m *Environment) GetProgramDependencies() (derived.ProgramDependencies, error) { + ret := _m.Called() + + var r0 derived.ProgramDependencies + var r1 error + if rf, ok := ret.Get(0).(func() (derived.ProgramDependencies, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() derived.ProgramDependencies); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(derived.ProgramDependencies) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetSigningAccounts provides a mock function with given fields: func (_m *Environment) GetSigningAccounts() ([]common.Address, error) { ret := _m.Called() diff --git a/fvm/environment/programs.go b/fvm/environment/programs.go index 85c52a843d9..3645a1c98c8 100644 --- a/fvm/environment/programs.go +++ b/fvm/environment/programs.go @@ -186,6 +186,14 @@ func (programs *Programs) DecodeArgument( return v, err } +func (programs *Programs) GetProgramDependencies() (derived.ProgramDependencies, error) { + top, err := programs.dependencyStack.top() + if err != nil { + return derived.ProgramDependencies{}, err + } + return top, nil +} + func (programs *Programs) cacheHit() { programs.metrics.RuntimeTransactionProgramsCacheHit() } diff --git a/fvm/environment/system_contracts.go b/fvm/environment/system_contracts.go index 52a4ce7312d..2ea17c739c6 100644 --- a/fvm/environment/system_contracts.go +++ b/fvm/environment/system_contracts.go @@ -279,3 +279,48 @@ func (sys *SystemContracts) AccountsStorageCapacity( }, ) } + +func (sys *SystemContracts) CheckDependencies( + dependencies []common.AddressLocation, + authorizers []flow.Address, +) (cadence.Value, error) { + + dependenciesAddresses := make([]cadence.Value, len(dependencies)) + dependenciesNames := make([]cadence.Value, len(dependencies)) + for i, dep := range dependencies { + dependenciesAddresses[i] = cadence.BytesToAddress(dep.Address.Bytes()) + dependenciesNames[i] = cadence.String(dep.Name) + } + + authorizersAddresses := make([]cadence.Value, len(authorizers)) + for i, auth := range authorizers { + authorizersAddresses[i] = cadence.BytesToAddress(auth.Bytes()) + } + + return sys.Invoke( + ContractFunctionSpec{ + AddressFromChain: ServiceAddress, + LocationName: systemcontracts.ContractNameServiceAccount, + FunctionName: systemcontracts.ContractServiceAccountFunction_checkDependencies, + ArgumentTypes: []sema.Type{ + sema.NewVariableSizedType( + nil, + &sema.AddressType{}, + ), + sema.NewVariableSizedType( + nil, + sema.StringType, + ), + sema.NewVariableSizedType( + nil, + &sema.AddressType{}, + ), + }, + }, + []cadence.Value{ + cadence.NewArray(dependenciesAddresses), + cadence.NewArray(dependenciesNames), + cadence.NewArray(authorizersAddresses), + }, + ) +} diff --git a/fvm/fvm_test.go b/fvm/fvm_test.go index a723b42928a..76862bfe44c 100644 --- a/fvm/fvm_test.go +++ b/fvm/fvm_test.go @@ -8,6 +8,8 @@ import ( "strings" "testing" + "github.com/rs/zerolog" + "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/ccf" jsoncdc "github.com/onflow/cadence/encoding/json" @@ -15,6 +17,7 @@ import ( "github.com/onflow/cadence/runtime/common" cadenceErrors "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/tests/utils" + coreContracts "github.com/onflow/flow-core-contracts/lib/go/contracts" "github.com/stretchr/testify/assert" mockery "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -1394,6 +1397,8 @@ func TestSettingExecutionWeights(t *testing.T) { }, )) + log := zerolog.New(zerolog.NewTestWriter(t)) + t.Run("transaction should not use up more computation that the transaction body itself", newVMTest().withBootstrapProcedureOptions( fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), @@ -1410,6 +1415,7 @@ func TestSettingExecutionWeights(t *testing.T) { fvm.WithAccountStorageLimit(true), fvm.WithTransactionFeesEnabled(true), fvm.WithMemoryLimit(math.MaxUint64), + fvm.WithLogger(log), ).run( func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { // Use the maximum amount of computation so that the transaction still passes. @@ -3161,3 +3167,274 @@ func TestEVM(t *testing.T) { }), ) } + +func TestDependencyCheck(t *testing.T) { + + t.Run("DependencyCheck", newVMTest(). + withBootstrapProcedureOptions(). + withContextOptions( + fvm.WithContractDeploymentRestricted(false), + ). + run(func( + t *testing.T, + vm fvm.VM, + chain flow.Chain, + ctx fvm.Context, + snapshotTree snapshot.SnapshotTree, + ) { + ctx.DependencyCheckEnabled = true + + // Create a private key + privateKeys, err := testutil.GenerateAccountPrivateKeys(1) + require.NoError(t, err) + + // Bootstrap a ledger, creating an account with the provided private key and the root account. + snapshotTree, accounts, err := testutil.CreateAccounts( + vm, + snapshotTree, + privateKeys, + chain, + ) + require.NoError(t, err) + + contractA := ` + pub contract A { + } + ` + + contractB := fmt.Sprintf(` + import A from %s + + pub contract B { + }`, + accounts[0].HexWithPrefix(), + ) + + contractC := fmt.Sprintf(` + import B from %s + import A from %s + + pub contract C { + }`, + accounts[0].HexWithPrefix(), + accounts[0].HexWithPrefix(), + ) + + contractD := fmt.Sprintf(` + import C from %s + import B from %s + import A from %s + + pub contract D { + }`, + accounts[0].HexWithPrefix(), + accounts[0].HexWithPrefix(), + accounts[0].HexWithPrefix(), + ) + + var sequenceNumber uint64 = 0 + + runTransactionWithOutputHandler := func( + code []byte, + handle func(t *testing.T, err error, output fvm.ProcedureOutput), + ) { + txBody := flow.NewTransactionBody(). + SetScript(code). + SetPayer(chain.ServiceAddress()). + SetProposalKey(chain.ServiceAddress(), 0, sequenceNumber). + AddAuthorizer(accounts[0]) + + _ = testutil.SignPayload(txBody, accounts[0], privateKeys[0]) + _ = testutil.SignEnvelope(txBody, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) + + executionSnapshot, output, err := vm.Run( + ctx, + fvm.Transaction(txBody, 0), + snapshotTree, + ) + + handle(t, err, output) + + snapshotTree = snapshotTree.Append(executionSnapshot) + + // increment sequence number + sequenceNumber++ + } + + runTransaction := func( + code []byte, + ) { + runTransactionWithOutputHandler(code, + func(t *testing.T, err error, output fvm.ProcedureOutput) { + require.NoError(t, err) + require.NoError(t, output.Err) + }) + + } + + deployServiceContractDependencyCheck := func(dependencyCheck string) { + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) + + code := string(coreContracts.FlowServiceAccount( + sc.FungibleToken.Address.HexWithPrefix(), + sc.FlowToken.Address.HexWithPrefix(), + sc.FlowFees.Address.HexWithPrefix(), + sc.FlowStorageFees.Address.HexWithPrefix(), + )) + + code = strings.Replace( + code, + "init() {", + fmt.Sprintf("%s\ninit() {", dependencyCheck), + 1) + + update := utils.UpdateTransaction( + "FlowServiceAccount", + []byte(code), + ) + + txBody := flow.NewTransactionBody(). + SetScript(update). + AddAuthorizer(chain.ServiceAddress()) + + err := testutil.SignTransactionAsServiceAccount(txBody, sequenceNumber, chain) + require.NoError(t, err) + + executionSnapshot, output, err := vm.Run( + ctx, + fvm.Transaction(txBody, 0), + snapshotTree, + ) + + require.NoError(t, err) + require.NoError(t, output.Err) + + snapshotTree = snapshotTree.Append(executionSnapshot) + + sequenceNumber++ + } + + // Deploy `A` + runTransaction(utils.DeploymentTransaction( + "A", + []byte(contractA), + )) + + // Deploy `B` + runTransaction(utils.DeploymentTransaction( + "B", + []byte(contractB), + )) + + // Deploy `C` + runTransaction(utils.DeploymentTransaction( + "C", + []byte(contractC), + )) + + // Deploy `D` + runTransaction(utils.DeploymentTransaction( + "D", + []byte(contractD), + )) + + // This succeeds because dependency check does not exist yet + runTransaction([]byte(fmt.Sprintf( + ` + import D from %s + + transaction { + prepare(signer: AuthAccount) { + } + }`, + accounts[0].HexWithPrefix(), + ))) + + deployServiceContractDependencyCheck(` + access(all) fun checkDependencies(_ dependenciesAddresses: [Address], _ dependenciesNames: [String], _ authorizers: [Address]) { } + `) + + // This succeeds because dependency check is empty + runTransaction([]byte(fmt.Sprintf( + ` + import D from %s + + transaction { + prepare(signer: AuthAccount) { + } + }`, + accounts[0].HexWithPrefix(), + ))) + + deployServiceContractDependencyCheck(fmt.Sprintf(` + access(all) fun checkDependencies(_ dependenciesAddresses: [Address], _ dependenciesNames: [String], _ authorizers: [Address]) { + if authorizers[0] == %s { + return + } + + panic("dependencies check failed") + } + `, chain.ServiceAddress().HexWithPrefix())) + + // This fails because dependency check panics + runTransactionWithOutputHandler( + []byte(fmt.Sprintf( + ` + import D from %s + + transaction { + prepare(signer: AuthAccount) { + } + }`, + accounts[0].HexWithPrefix(), + )), + func(t *testing.T, err error, output fvm.ProcedureOutput) { + require.NoError(t, err) + require.Error(t, output.Err) + require.ErrorContains(t, output.Err, "dependencies check failed") + }, + ) + + deployServiceContractDependencyCheck(fmt.Sprintf(` + pub event TestEvent(_ dependenciesNames: [String]) + + access(all) fun checkDependencies(_ dependenciesAddresses: [Address], _ dependenciesNames: [String], _ authorizers: [Address]) { + if authorizers[0] == %s { + return + } + + emit TestEvent(dependenciesNames) + } + `, chain.ServiceAddress().HexWithPrefix())) + + // This fails because dependency check panics + runTransactionWithOutputHandler( + []byte(fmt.Sprintf( + ` + import D from %s + + transaction { + prepare(signer: AuthAccount) { + } + }`, + accounts[0].HexWithPrefix(), + )), + func(t *testing.T, err error, output fvm.ProcedureOutput) { + require.NoError(t, err) + require.NoError(t, output.Err) + + require.Len(t, output.Events, 1) + + event := output.Events[0] + require.Equal(t, flow.EventType("A.8c5303eaa26202d6.FlowServiceAccount.TestEvent"), event.Type) + + payload, err := ccf.Decode(nil, event.Payload) + require.NoError(t, err) + + dependencies := payload.(cadence.Event).Fields[0].(cadence.Array).Values + + require.Equal(t, []cadence.Value{cadence.String("D"), cadence.String("C"), cadence.String("B"), cadence.String("A")}, dependencies) + }, + ) + })) +} diff --git a/fvm/storage/derived/dependencies.go b/fvm/storage/derived/dependencies.go index c5ba3b85e29..974af686d96 100644 --- a/fvm/storage/derived/dependencies.go +++ b/fvm/storage/derived/dependencies.go @@ -2,6 +2,7 @@ package derived import ( "github.com/onflow/cadence/runtime/common" + "golang.org/x/exp/maps" ) // ProgramDependencies are the locations of programs that a program depends on. @@ -54,3 +55,8 @@ func (d ProgramDependencies) ContainsLocation(location common.Location) bool { _, ok := d.locations[location] return ok } + +// Locations returns all the locations. +func (d ProgramDependencies) Locations() []common.Location { + return maps.Keys(d.locations) +} diff --git a/fvm/systemcontracts/system_contracts.go b/fvm/systemcontracts/system_contracts.go index a0c3a39848d..915c2a5b200 100644 --- a/fvm/systemcontracts/system_contracts.go +++ b/fvm/systemcontracts/system_contracts.go @@ -55,6 +55,7 @@ const ( ContractServiceAccountFunction_defaultTokenBalance = "defaultTokenBalance" ContractServiceAccountFunction_deductTransactionFee = "deductTransactionFee" ContractServiceAccountFunction_verifyPayersBalanceForTransactionExecution = "verifyPayersBalanceForTransactionExecution" + ContractServiceAccountFunction_checkDependencies = "checkDependencies" ContractStorageFeesFunction_calculateAccountCapacity = "calculateAccountCapacity" ContractStorageFeesFunction_getAccountsCapacityForTransactionStorageCheck = "getAccountsCapacityForTransactionStorageCheck" ContractStorageFeesFunction_defaultTokenAvailableBalance = "defaultTokenAvailableBalance" diff --git a/fvm/transactionInvoker.go b/fvm/transactionInvoker.go index 57c0c449cbf..bc802baa26b 100644 --- a/fvm/transactionInvoker.go +++ b/fvm/transactionInvoker.go @@ -6,6 +6,7 @@ import ( "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" "github.com/rs/zerolog" "go.opentelemetry.io/otel/attribute" otelTrace "go.opentelemetry.io/otel/trace" @@ -19,6 +20,7 @@ import ( "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/fvm/storage/state" "github.com/onflow/flow-go/fvm/systemcontracts" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/trace" ) @@ -418,7 +420,17 @@ func (executor *transactionExecutor) normalExecution() ( } executor.txnState.RunWithAllLimitsDisabled(func() { + err = executor.deductTransactionFees() + + }) + + if err != nil { + return + } + + executor.txnState.RunWithAllLimitsDisabled(func() { + err = executor.checkDependencies() }) return @@ -490,3 +502,50 @@ func (executor *transactionExecutor) commit( return nil } + +func (executor *transactionExecutor) checkDependencies() error { + + if !executor.ctx.DependencyCheckEnabled { + return nil + } + + deps, err := executor.env.GetProgramDependencies() + + if err != nil { + return fmt.Errorf("failed to get program dependencies: %w", err) + } + + dependencies := make([]common.AddressLocation, 0, len(deps.Locations())) + for _, loc := range deps.Locations() { + if addressLoc, ok := loc.(common.AddressLocation); ok { + dependencies = append(dependencies, addressLoc) + } + } + + authorizerSet := make(map[flow.Address]struct{}, len(executor.proc.Transaction.Authorizers)) + for _, authorizer := range executor.proc.Transaction.Authorizers { + authorizerSet[authorizer] = struct{}{} + } + + authorizerSet[executor.proc.Transaction.Payer] = struct{}{} + + auths := make([]flow.Address, 0, len(authorizerSet)) + for auth := range authorizerSet { + auths = append(auths, auth) + } + + _, err = executor.env.CheckDependencies( + dependencies, + auths) + + // If the service contract does not have the required method, ignore the error, but log it + if errors.Is(err, interpreter.NotInvokableError{}) { + log := executor.env.Logger() + log.Info(). + Str("method", systemcontracts.ContractServiceAccountFunction_checkDependencies). + Msg("Service contract does not have the required method") + return nil + } + + return err +} diff --git a/module/mock/machine_account_metrics.go b/module/mock/machine_account_metrics.go new file mode 100644 index 00000000000..14420e8a173 --- /dev/null +++ b/module/mock/machine_account_metrics.go @@ -0,0 +1,40 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mock + +import mock "github.com/stretchr/testify/mock" + +// MachineAccountMetrics is an autogenerated mock type for the MachineAccountMetrics type +type MachineAccountMetrics struct { + mock.Mock +} + +// AccountBalance provides a mock function with given fields: bal +func (_m *MachineAccountMetrics) AccountBalance(bal float64) { + _m.Called(bal) +} + +// IsMisconfigured provides a mock function with given fields: misconfigured +func (_m *MachineAccountMetrics) IsMisconfigured(misconfigured bool) { + _m.Called(misconfigured) +} + +// RecommendedMinBalance provides a mock function with given fields: bal +func (_m *MachineAccountMetrics) RecommendedMinBalance(bal float64) { + _m.Called(bal) +} + +type mockConstructorTestingTNewMachineAccountMetrics interface { + mock.TestingT + Cleanup(func()) +} + +// NewMachineAccountMetrics creates a new instance of MachineAccountMetrics. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMachineAccountMetrics(t mockConstructorTestingTNewMachineAccountMetrics) *MachineAccountMetrics { + mock := &MachineAccountMetrics{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} From 69667cd122113bc02ad83482b72c93310fcf0531 Mon Sep 17 00:00:00 2001 From: Janez Podhostnik Date: Tue, 14 May 2024 14:52:39 +0200 Subject: [PATCH 2/4] enable depencdency check for testnet --- cmd/scaffold.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/scaffold.go b/cmd/scaffold.go index 4e0e498f3cb..018605b811e 100644 --- a/cmd/scaffold.go +++ b/cmd/scaffold.go @@ -1547,6 +1547,13 @@ func (fnb *FlowNodeBuilder) initFvmOptions() { fvm.WithContractDeploymentRestricted(false), ) } + // temporarily enable dependency check for testnet + if fnb.RootChainID == flow.Testnet { + vmOpts = append(vmOpts, + fvm.WithDependencyCheckEnabled(true), + ) + } + fnb.FvmOptions = vmOpts } From e26f0bc027c17e2fe61a2302aed1ac30068fcfd6 Mon Sep 17 00:00:00 2001 From: Janez Podhostnik Date: Tue, 14 May 2024 17:47:41 +0200 Subject: [PATCH 3/4] fix tests --- fvm/fvm_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fvm/fvm_test.go b/fvm/fvm_test.go index 76862bfe44c..86778c9a688 100644 --- a/fvm/fvm_test.go +++ b/fvm/fvm_test.go @@ -3368,7 +3368,7 @@ func TestDependencyCheck(t *testing.T) { deployServiceContractDependencyCheck(fmt.Sprintf(` access(all) fun checkDependencies(_ dependenciesAddresses: [Address], _ dependenciesNames: [String], _ authorizers: [Address]) { - if authorizers[0] == %s { + if authorizers.length == 1 && authorizers[0] == %s { return } @@ -3399,7 +3399,7 @@ func TestDependencyCheck(t *testing.T) { pub event TestEvent(_ dependenciesNames: [String]) access(all) fun checkDependencies(_ dependenciesAddresses: [Address], _ dependenciesNames: [String], _ authorizers: [Address]) { - if authorizers[0] == %s { + if authorizers.length == 1 && authorizers[0] == %s { return } @@ -3433,7 +3433,7 @@ func TestDependencyCheck(t *testing.T) { dependencies := payload.(cadence.Event).Fields[0].(cadence.Array).Values - require.Equal(t, []cadence.Value{cadence.String("D"), cadence.String("C"), cadence.String("B"), cadence.String("A")}, dependencies) + require.ElementsMatch(t, []cadence.Value{cadence.String("D"), cadence.String("C"), cadence.String("B"), cadence.String("A")}, dependencies) }, ) })) From 395d26a8fc3feb77e11fe5ad124ab2d3cc012ba9 Mon Sep 17 00:00:00 2001 From: Janez Podhostnik Date: Wed, 15 May 2024 14:10:25 +0200 Subject: [PATCH 4/4] extract contract function spec --- fvm/environment/system_contracts.go | 40 +++++++++++++++-------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/fvm/environment/system_contracts.go b/fvm/environment/system_contracts.go index 2ea17c739c6..0e214cab16b 100644 --- a/fvm/environment/system_contracts.go +++ b/fvm/environment/system_contracts.go @@ -280,6 +280,26 @@ func (sys *SystemContracts) AccountsStorageCapacity( ) } +var checkDependenciesSpec = ContractFunctionSpec{ + AddressFromChain: ServiceAddress, + LocationName: systemcontracts.ContractNameServiceAccount, + FunctionName: systemcontracts.ContractServiceAccountFunction_checkDependencies, + ArgumentTypes: []sema.Type{ + sema.NewVariableSizedType( + nil, + &sema.AddressType{}, + ), + sema.NewVariableSizedType( + nil, + sema.StringType, + ), + sema.NewVariableSizedType( + nil, + &sema.AddressType{}, + ), + }, +} + func (sys *SystemContracts) CheckDependencies( dependencies []common.AddressLocation, authorizers []flow.Address, @@ -298,25 +318,7 @@ func (sys *SystemContracts) CheckDependencies( } return sys.Invoke( - ContractFunctionSpec{ - AddressFromChain: ServiceAddress, - LocationName: systemcontracts.ContractNameServiceAccount, - FunctionName: systemcontracts.ContractServiceAccountFunction_checkDependencies, - ArgumentTypes: []sema.Type{ - sema.NewVariableSizedType( - nil, - &sema.AddressType{}, - ), - sema.NewVariableSizedType( - nil, - sema.StringType, - ), - sema.NewVariableSizedType( - nil, - &sema.AddressType{}, - ), - }, - }, + checkDependenciesSpec, []cadence.Value{ cadence.NewArray(dependenciesAddresses), cadence.NewArray(dependenciesNames),