diff --git a/runtime/cmd/cmd.go b/runtime/cmd/cmd.go index d674d9f6f0..80f808594b 100644 --- a/runtime/cmd/cmd.go +++ b/runtime/cmd/cmd.go @@ -366,6 +366,11 @@ func (*StandardLibraryHandler) RecordContractUpdate(_ common.AddressLocation, _ // NO-OP } +func (h *StandardLibraryHandler) ContractUpdateRecorded(_ common.AddressLocation) bool { + // NO-OP + return false +} + func (*StandardLibraryHandler) InterpretContract( _ common.AddressLocation, _ *interpreter.Program, diff --git a/runtime/contract_test.go b/runtime/contract_test.go index 58adf2f463..02a9bb7ece 100644 --- a/runtime/contract_test.go +++ b/runtime/contract_test.go @@ -309,6 +309,8 @@ func TestRuntimeContract(t *testing.T) { ) RequireError(t, err) + require.ErrorContains(t, err, "cannot overwrite existing contract") + // the deployed code should not have been updated, // and no events should have been emitted, // as the deployment should fail @@ -450,6 +452,8 @@ func TestRuntimeContract(t *testing.T) { } else { RequireError(t, err) + require.ErrorContains(t, err, "cannot overwrite existing contract") + require.Empty(t, deployedCode) require.Empty(t, events) require.Empty(t, loggedMessages) @@ -475,9 +479,11 @@ func TestRuntimeContract(t *testing.T) { Location: nextTransactionLocation(), }, ) - require.NoError(t, err) + RequireError(t, err) - require.Equal(t, []byte(tc.code2), deployedCode) + require.ErrorContains(t, err, "cannot overwrite existing contract") + + require.Empty(t, deployedCode) require.Equal(t, []string{ @@ -486,20 +492,18 @@ func TestRuntimeContract(t *testing.T) { `"Test"`, codeArrayString, `nil`, - `"Test"`, - code2ArrayString, - `"Test"`, - code2ArrayString, }, loggedMessages, ) - require.Len(t, events, 2) - assert.EqualValues(t, stdlib.AccountContractRemovedEventType.ID(), events[0].Type().ID()) - assert.EqualValues(t, stdlib.AccountContractAddedEventType.ID(), events[1].Type().ID()) + require.Len(t, events, 1) + assert.EqualValues(t, + stdlib.AccountContractRemovedEventType.ID(), + events[0].Type().ID(), + ) contractValueExists := getContractValueExists() - + // contract still exists (from previous transaction), if not interface if tc.isInterface { require.False(t, contractValueExists) } else { diff --git a/runtime/contract_update_test.go b/runtime/contract_update_test.go index 57f0130760..dbb52f8bb8 100644 --- a/runtime/contract_update_test.go +++ b/runtime/contract_update_test.go @@ -29,7 +29,7 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" . "github.com/onflow/cadence/runtime/tests/runtime_utils" - "github.com/onflow/cadence/runtime/tests/utils" + . "github.com/onflow/cadence/runtime/tests/utils" ) func TestRuntimeContractUpdateWithDependencies(t *testing.T) { @@ -142,7 +142,7 @@ func TestRuntimeContractUpdateWithDependencies(t *testing.T) { err := runtime.ExecuteTransaction( Script{ - Source: utils.DeploymentTransaction( + Source: DeploymentTransaction( "Foo", []byte(fooContractV1), ), @@ -163,7 +163,7 @@ func TestRuntimeContractUpdateWithDependencies(t *testing.T) { err = runtime.ExecuteTransaction( Script{ - Source: utils.DeploymentTransaction( + Source: DeploymentTransaction( "Bar", []byte(barContractV1), ), @@ -183,7 +183,7 @@ func TestRuntimeContractUpdateWithDependencies(t *testing.T) { signerAccount = common.MustBytesToAddress([]byte{0x1}) err = runtime.ExecuteTransaction( Script{ - Source: utils.UpdateTransaction("Foo", []byte(fooContractV2)), + Source: UpdateTransaction("Foo", []byte(fooContractV2)), }, Context{ Interface: runtimeInterface, @@ -204,7 +204,7 @@ func TestRuntimeContractUpdateWithDependencies(t *testing.T) { err = runtime.ExecuteTransaction( Script{ - Source: utils.UpdateTransaction("Bar", []byte(barContractV2)), + Source: UpdateTransaction("Bar", []byte(barContractV2)), }, Context{ Interface: runtimeInterface, @@ -283,7 +283,7 @@ func TestRuntimeContractUpdateWithPrecedingIdentifiers(t *testing.T) { err := runtime.ExecuteTransaction( Script{ - Source: utils.UpdateTransaction("Foo", []byte(fooContractV2)), + Source: UpdateTransaction("Foo", []byte(fooContractV2)), }, Context{ Interface: runtimeInterface, @@ -293,3 +293,107 @@ func TestRuntimeContractUpdateWithPrecedingIdentifiers(t *testing.T) { require.NoError(t, err) } + +func TestRuntimeInvalidContractRedeploy(t *testing.T) { + + t.Parallel() + + foo1 := []byte(` + access(all) + contract Foo { + + access(all) + resource R { + + access(all) + var x: Int + + init() { + self.x = 0 + } + } + + access(all) + fun createR(): @R { + return <-create R() + } + } + `) + + foo2 := []byte(` + access(all) + contract Foo { + + access(all) + struct R { + access(all) + var x: Int + + init() { + self.x = 0 + } + } + } + `) + + tx := []byte(` + transaction(foo1: String, foo2: String) { + prepare(signer: auth(Contracts) &Account) { + signer.contracts.add(name: "Foo", code: foo1.utf8) + signer.contracts.add(name: "Foo", code: foo2.utf8) + } + } + `) + + runtime := NewTestInterpreterRuntimeWithConfig(Config{ + AtreeValidationEnabled: false, + }) + + address := common.MustBytesToAddress([]byte{0x1}) + + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { + return []Address{address}, nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) ([]byte, error) { + return nil, nil + }, + OnResolveLocation: NewSingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + // "delay" + return nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (value cadence.Value, err error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + + // Deploy + + err := runtime.ExecuteTransaction( + Script{ + Source: tx, + Arguments: encodeArgs([]cadence.Value{ + cadence.String(foo1), + cadence.String(foo2), + }), + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + + RequireError(t, err) + + require.ErrorContains(t, err, "cannot overwrite existing contract") +} diff --git a/runtime/environment.go b/runtime/environment.go index 605d7f5442..c768073f4f 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -318,6 +318,10 @@ func (e *interpreterEnvironment) RecordContractUpdate( e.storage.recordContractUpdate(location, contractValue) } +func (e *interpreterEnvironment) ContractUpdateRecorded(location common.AddressLocation) bool { + return e.storage.contractUpdateRecorded(location) +} + func (e *interpreterEnvironment) TemporarilyRecordCode(location common.AddressLocation, code []byte) { e.codesAndPrograms.setCode(location, code) } diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index b75a1cba59..171695b532 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -136,26 +136,53 @@ func (interpreter *Interpreter) valueIndexExpressionGetterSetter(indexExpression }, ) + // Normally, moves of nested resources (e.g `let r <- rs[0]`) are statically rejected. + // + // However, there are cases in which we do allow moves of nested resources: + // + // - In a swap statement (e.g. `rs[0] <-> rs[1]`) + // - In a variable declaration with two values/assignments (e.g. `let r <- rs["foo"] <- nil`) + // + // In both cases we know that a move of the nested resource is immediately followed by a replacement. + // This notion of an expression that moves a nested resource is tracked in the elaboration. + // + // When indexing is a move of a nested resource, we need to remove the key/value from the container. + // However, for some containers, like arrays, the removal influences other values in the container. + // In case of an array, the removal of an element shifts all following elements. + // + // A removal alone would thus result in subsequent code being executed incorrectly. + // For example, in the case where a swap operation through indexing is performed on the same array, + // e.g. `rs[0] <-> rs[1]`, once the first removal was performed, the second operates on a modified container. + // + // Prevent this problem by temporarily writing a placeholder value after the removal. + // Only perform the replacement with a placeholder in the case of a nested resource move. + // We know that in that case the get operation will be followed by a set operation, + // which will replace the temporary placeholder. + isNestedResourceMove := elaboration.IsNestedResourceMoveExpression(indexExpression) + var get func(allowMissing bool) Value + + if isNestedResourceMove { + get = func(_ bool) Value { + value := target.RemoveKey(interpreter, locationRange, transferredIndexingValue) + target.InsertKey(interpreter, locationRange, transferredIndexingValue, placeholder) + return value + } + } else { + get = func(_ bool) Value { + value := target.GetKey(interpreter, locationRange, transferredIndexingValue) + + // If the indexing value is a reference, then return a reference for the resulting value. + return interpreter.maybeGetReference(indexExpression, value) + } + } + return getterSetter{ target: target, - get: func(_ bool) Value { - if isNestedResourceMove { - return target.RemoveKey(interpreter, locationRange, transferredIndexingValue) - } else { - value := target.GetKey(interpreter, locationRange, transferredIndexingValue) - - // If the indexing value is a reference, then return a reference for the resulting value. - return interpreter.maybeGetReference(indexExpression, value) - } - }, + get: get, set: func(value Value) { - if isNestedResourceMove { - target.InsertKey(interpreter, locationRange, transferredIndexingValue, value) - } else { - target.SetKey(interpreter, locationRange, transferredIndexingValue, value) - } + target.SetKey(interpreter, locationRange, transferredIndexingValue, value) }, } } diff --git a/runtime/interpreter/interpreter_statement.go b/runtime/interpreter/interpreter_statement.go index 5d7f4a6862..ca1ea8cedd 100644 --- a/runtime/interpreter/interpreter_statement.go +++ b/runtime/interpreter/interpreter_statement.go @@ -163,8 +163,10 @@ func (interpreter *Interpreter) visitIfStatementWithVariableDeclaration( // If the resource was not moved ou of the container, // its contents get deleted. + getterSetter := interpreter.assignmentGetterSetter(declaration.Value) + const allowMissing = false - value := interpreter.assignmentGetterSetter(declaration.Value).get(allowMissing) + value := getterSetter.get(allowMissing) if value == nil { panic(errors.NewUnreachableError()) } @@ -527,8 +529,10 @@ func (interpreter *Interpreter) visitVariableDeclaration( // If the resource was not moved ou of the container, // its contents get deleted. + getterSetter := interpreter.assignmentGetterSetter(declaration.Value) + const allowMissing = false - result := interpreter.assignmentGetterSetter(declaration.Value).get(allowMissing) + result := getterSetter.get(allowMissing) if result == nil { panic(errors.NewUnreachableError()) } @@ -581,23 +585,32 @@ func (interpreter *Interpreter) VisitAssignmentStatement(assignment *ast.Assignm } func (interpreter *Interpreter) VisitSwapStatement(swap *ast.SwapStatement) StatementResult { + + // Get type information + swapStatementTypes := interpreter.Program.Elaboration.SwapStatementTypes(swap) leftType := swapStatementTypes.LeftType rightType := swapStatementTypes.RightType - const allowMissing = false + // Evaluate the left side (target and key) - // Evaluate the left expression leftGetterSetter := interpreter.assignmentGetterSetter(swap.Left) + + // Evaluate the right side (target and key) + + rightGetterSetter := interpreter.assignmentGetterSetter(swap.Right) + + // Get left and right values + + const allowMissing = false + leftValue := leftGetterSetter.get(allowMissing) interpreter.checkSwapValue(leftValue, swap.Left) - // Evaluate the right expression - rightGetterSetter := interpreter.assignmentGetterSetter(swap.Right) rightValue := rightGetterSetter.get(allowMissing) interpreter.checkSwapValue(rightValue, swap.Right) - // Set right value to left target + // Set right value to left target, // and left value to right target locationRange := LocationRange{ diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 3ddbc5ca04..891e827839 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -18408,6 +18408,9 @@ func (v *DictionaryValue) SetKey( case NilValue: _ = v.Remove(interpreter, locationRange, keyValue) + case placeholderValue: + // NO-OP + default: panic(errors.NewUnreachableError()) } diff --git a/runtime/interpreter/value_placeholder.go b/runtime/interpreter/value_placeholder.go new file mode 100644 index 0000000000..6cc6184852 --- /dev/null +++ b/runtime/interpreter/value_placeholder.go @@ -0,0 +1,105 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package interpreter + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/common" +) + +// placeholderValue +type placeholderValue struct{} + +var placeholder Value = placeholderValue{} + +var _ Value = placeholderValue{} + +func (placeholderValue) isValue() {} + +func (f placeholderValue) String() string { + return f.RecursiveString(SeenReferences{}) +} + +func (f placeholderValue) RecursiveString(_ SeenReferences) string { + return "" +} + +func (f placeholderValue) MeteredString(_ common.MemoryGauge, _ SeenReferences) string { + return "" +} + +func (f placeholderValue) Accept(_ *Interpreter, _ Visitor) { + // NO-OP +} + +func (f placeholderValue) Walk(_ *Interpreter, _ func(Value)) { + // NO-OP +} + +func (f placeholderValue) StaticType(_ *Interpreter) StaticType { + return PrimitiveStaticTypeNever +} + +func (placeholderValue) IsImportable(_ *Interpreter) bool { + return false +} + +func (f placeholderValue) ConformsToStaticType( + _ *Interpreter, + _ LocationRange, + _ TypeConformanceResults, +) bool { + return true +} + +func (f placeholderValue) Storable(_ atree.SlabStorage, _ atree.Address, _ uint64) (atree.Storable, error) { + return NonStorable{Value: f}, nil +} + +func (placeholderValue) NeedsStoreTo(_ atree.Address) bool { + return false +} + +func (placeholderValue) IsResourceKinded(_ *Interpreter) bool { + return false +} + +func (f placeholderValue) Transfer( + interpreter *Interpreter, + _ LocationRange, + _ atree.Address, + remove bool, + storable atree.Storable, + _ map[atree.StorageID]struct{}, +) Value { + // TODO: actually not needed, value is not storable + if remove { + interpreter.RemoveReferencedSlab(storable) + } + return f +} + +func (f placeholderValue) Clone(_ *Interpreter) Value { + return f +} + +func (placeholderValue) DeepRemove(_ *Interpreter) { + // NO-OP +} diff --git a/runtime/sema/check_function.go b/runtime/sema/check_function.go index c84275e3af..db4204b7fb 100644 --- a/runtime/sema/check_function.go +++ b/runtime/sema/check_function.go @@ -79,6 +79,21 @@ func (checker *Checker) visitFunctionDeclaration( declaration.Identifier, ) + functionBlock := declaration.FunctionBlock + + if declaration.IsNative() { + if !functionBlock.IsEmpty() { + checker.report(&NativeFunctionWithImplementationError{ + Range: ast.NewRangeFromPositioned( + checker.memoryGauge, + functionBlock, + ), + }) + } + + functionBlock = nil + } + // global functions were previously declared, see `declareFunctionDeclaration` functionType := checker.Elaboration.FunctionDeclarationFunctionType(declaration) @@ -108,7 +123,7 @@ func (checker *Checker) visitFunctionDeclaration( declaration.ReturnTypeAnnotation, access, functionType, - declaration.FunctionBlock, + functionBlock, options.mustExit, nil, options.checkResourceLoss, diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index d15dce1be6..5d31b1d552 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -862,6 +862,23 @@ func (e *InvalidNativeModifierError) Error() string { return "invalid native modifier for declaration" } +// NativeFunctionWithImplementationError + +type NativeFunctionWithImplementationError struct { + ast.Range +} + +var _ SemanticError = &NativeFunctionWithImplementationError{} +var _ errors.UserError = &NativeFunctionWithImplementationError{} + +func (*NativeFunctionWithImplementationError) isSemanticError() {} + +func (*NativeFunctionWithImplementationError) IsUserError() {} + +func (e *NativeFunctionWithImplementationError) Error() string { + return "native function must not have an implementation" +} + // InvalidNameError type InvalidNameError struct { diff --git a/runtime/sema/type.go b/runtime/sema/type.go index d8834360f1..cf7be4bf8e 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4735,7 +4735,11 @@ func (t *CompositeType) GetMembers() map[string]MemberResolver { } func (t *CompositeType) initializeMemberResolvers() { - t.memberResolversOnce.Do(func() { + t.memberResolversOnce.Do(t.initializerMemberResolversFunc()) +} + +func (t *CompositeType) initializerMemberResolversFunc() func() { + return func() { memberResolvers := MembersMapAsResolvers(t.Members) // Check conformances. @@ -4770,7 +4774,13 @@ func (t *CompositeType) initializeMemberResolvers() { } t.memberResolvers = withBuiltinMembers(t, memberResolvers) - }) + } +} + +func (t *CompositeType) ResolveMembers() { + if t.Members.Len() != len(t.GetMembers()) { + t.initializerMemberResolversFunc()() + } } func (t *CompositeType) FieldPosition(name string, declaration ast.CompositeLikeDeclaration) ast.Position { diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index f252312860..8c26d7448d 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -1349,7 +1349,11 @@ type AccountContractAdditionHandler interface { ) (*interpreter.Program, error) // UpdateAccountContractCode updates the code associated with an account contract. UpdateAccountContractCode(location common.AddressLocation, code []byte) error - RecordContractUpdate(location common.AddressLocation, value *interpreter.CompositeValue) + RecordContractUpdate( + location common.AddressLocation, + value *interpreter.CompositeValue, + ) + ContractUpdateRecorded(location common.AddressLocation) bool InterpretContract( location common.AddressLocation, program *interpreter.Program, @@ -1443,9 +1447,10 @@ func changeAccountContracts( } else { // We are adding a new contract. - // Ensure that no contract/contract interface with the given name exists already + // Ensure that no contract/contract interface with the given name exists already, + // and no contract deploy or update was recorded before - if len(existingCode) > 0 { + if len(existingCode) > 0 || handler.ContractUpdateRecorded(location) { panic(errors.NewDefaultUserError( "cannot overwrite existing contract with name %q in account %s", contractName, diff --git a/runtime/stdlib/contracts/test.cdc b/runtime/stdlib/contracts/test.cdc index 8a6634e082..70a484d54f 100644 --- a/runtime/stdlib/contracts/test.cdc +++ b/runtime/stdlib/contracts/test.cdc @@ -3,177 +3,170 @@ access(all) contract Test { - /// Blockchain emulates a real network. + /// backend emulates a real network. /// - access(all) - struct Blockchain { + access(self) + let backend: {BlockchainBackend} - access(all) - let backend: {BlockchainBackend} + init(backend: {BlockchainBackend}) { + self.backend = backend + } - init(backend: {BlockchainBackend}) { - self.backend = backend - } + /// Executes a script and returns the script return value and the status. + /// `returnValue` field of the result will be `nil` if the script failed. + /// + access(all) + fun executeScript(_ script: String, _ arguments: [AnyStruct]): ScriptResult { + return self.backend.executeScript(script, arguments) + } - /// Executes a script and returns the script return value and the status. - /// `returnValue` field of the result will be `nil` if the script failed. - /// - access(all) - fun executeScript(_ script: String, _ arguments: [AnyStruct]): ScriptResult { - return self.backend.executeScript(script, arguments) - } + /// Creates a signer account by submitting an account creation transaction. + /// The transaction is paid by the service account. + /// The returned account can be used to sign and authorize transactions. + /// + access(all) + fun createAccount(): TestAccount { + return self.backend.createAccount() + } - /// Creates a signer account by submitting an account creation transaction. - /// The transaction is paid by the service account. - /// The returned account can be used to sign and authorize transactions. - /// - access(all) - fun createAccount(): TestAccount { - return self.backend.createAccount() - } + /// Returns the account for the given address. + /// + access(all) + fun getAccount(_ address: Address): TestAccount { + return self.backend.getAccount(address) + } - /// Add a transaction to the current block. - /// - access(all) - fun addTransaction(_ tx: Transaction) { - self.backend.addTransaction(tx) - } + /// Add a transaction to the current block. + /// + access(all) + fun addTransaction(_ tx: Transaction) { + self.backend.addTransaction(tx) + } - /// Executes the next transaction in the block, if any. - /// Returns the result of the transaction, or nil if no transaction was scheduled. - /// - access(all) - fun executeNextTransaction(): TransactionResult? { - return self.backend.executeNextTransaction() - } + /// Executes the next transaction in the block, if any. + /// Returns the result of the transaction, or nil if no transaction was scheduled. + /// + access(all) + fun executeNextTransaction(): TransactionResult? { + return self.backend.executeNextTransaction() + } - /// Commit the current block. - /// Committing will fail if there are un-executed transactions in the block. - /// - access(all) - fun commitBlock() { - self.backend.commitBlock() - } + /// Commit the current block. + /// Committing will fail if there are un-executed transactions in the block. + /// + access(all) + fun commitBlock() { + self.backend.commitBlock() + } - /// Executes a given transaction and commit the current block. - /// - access(all) - fun executeTransaction(_ tx: Transaction): TransactionResult { + /// Executes a given transaction and commit the current block. + /// + access(all) + fun executeTransaction(_ tx: Transaction): TransactionResult { + self.addTransaction(tx) + let txResult = self.executeNextTransaction()! + self.commitBlock() + return txResult + } + + /// Executes a given set of transactions and commit the current block. + /// + access(all) + fun executeTransactions(_ transactions: [Transaction]): [TransactionResult] { + for tx in transactions { self.addTransaction(tx) - let txResult = self.executeNextTransaction()! - self.commitBlock() - return txResult } - /// Executes a given set of transactions and commit the current block. - /// - access(all) - fun executeTransactions(_ transactions: [Transaction]): [TransactionResult] { - for tx in transactions { - self.addTransaction(tx) - } - - var results: [TransactionResult] = [] - for tx in transactions { - let txResult = self.executeNextTransaction()! - results.append(txResult) - } - - self.commitBlock() - return results + var results: [TransactionResult] = [] + for tx in transactions { + let txResult = self.executeNextTransaction()! + results.append(txResult) } - /// Deploys a given contract, and initilizes it with the arguments. - /// - access(all) - fun deployContract( - name: String, - code: String, - account: TestAccount, - arguments: [AnyStruct] - ): Error? { - return self.backend.deployContract( - name: name, - code: code, - account: account, - arguments: arguments - ) - } + self.commitBlock() + return results + } - /// Set the configuration to be used by the blockchain. - /// Overrides any existing configuration. - /// - access(all) - fun useConfiguration(_ configuration: Configuration) { - self.backend.useConfiguration(configuration) - } + /// Deploys a given contract, and initilizes it with the arguments. + /// + access(all) + fun deployContract( + name: String, + path: String, + arguments: [AnyStruct] + ): Error? { + return self.backend.deployContract( + name: name, + path: path, + arguments: arguments + ) + } - /// Returns all the logs from the blockchain, up to the calling point. - /// - access(all) - fun logs(): [String] { - return self.backend.logs() - } + /// Returns all the logs from the blockchain, up to the calling point. + /// + access(all) + fun logs(): [String] { + return self.backend.logs() + } - /// Returns the service account of the blockchain. Can be used to sign - /// transactions with this account. - /// - access(all) - fun serviceAccount(): TestAccount { - return self.backend.serviceAccount() - } + /// Returns the service account of the blockchain. Can be used to sign + /// transactions with this account. + /// + access(all) + fun serviceAccount(): TestAccount { + return self.backend.serviceAccount() + } - /// Returns all events emitted from the blockchain. - /// - access(all) - fun events(): [AnyStruct] { - return self.backend.events(nil) - } + /// Returns all events emitted from the blockchain. + /// + access(all) + fun events(): [AnyStruct] { + return self.backend.events(nil) + } - /// Returns all events emitted from the blockchain, - /// filtered by type. - /// - access(all) - fun eventsOfType(_ type: Type): [AnyStruct] { - return self.backend.events(type) - } + /// Returns all events emitted from the blockchain, + /// filtered by type. + /// + access(all) + fun eventsOfType(_ type: Type): [AnyStruct] { + return self.backend.events(type) + } - /// Resets the state of the blockchain to the given height. - /// - access(all) - fun reset(to height: UInt64) { - self.backend.reset(to: height) - } + /// Resets the state of the blockchain to the given height. + /// + access(all) + fun reset(to height: UInt64) { + self.backend.reset(to: height) + } - /// Moves the time of the blockchain by the given delta, - /// which should be passed in the form of seconds. - /// - access(all) - fun moveTime(by delta: Fix64) { - self.backend.moveTime(by: delta) - } + /// Moves the time of the blockchain by the given delta, + /// which should be passed in the form of seconds. + /// + access(all) + fun moveTime(by delta: Fix64) { + self.backend.moveTime(by: delta) + } - /// Creates a snapshot of the blockchain, at the - /// current ledger state, with the given name. - /// - access(all) - fun createSnapshot(name: String) { - let err = self.backend.createSnapshot(name: name) - if err != nil { - panic(err!.message) - } + /// Creates a snapshot of the blockchain, at the + /// current ledger state, with the given name. + /// + access(all) + fun createSnapshot(name: String) { + let err = self.backend.createSnapshot(name: name) + if err != nil { + panic(err!.message) } + } - /// Loads a snapshot of the blockchain, with the - /// given name, and updates the current ledger - /// state. - /// - access(all) - fun loadSnapshot(name: String) { - let err = self.backend.loadSnapshot(name: name) - if err != nil { - panic(err!.message) - } + /// Loads a snapshot of the blockchain, with the + /// given name, and updates the current ledger + /// state. + /// + access(all) + fun loadSnapshot(name: String) { + let err = self.backend.loadSnapshot(name: name) + if err != nil { + panic(err!.message) } } @@ -183,7 +176,6 @@ contract Test { access(all) let test: fun(AnyStruct): Bool - access(all) init(test: fun(AnyStruct): Bool) { self.test = test } @@ -275,6 +267,7 @@ contract Test { // access(all) struct Error { + access(all) let message: String @@ -300,20 +293,6 @@ contract Test { } } - /// Configuration to be used by the blockchain. - /// Can be used to set the address mappings. - /// - access(all) - struct Configuration { - - access(all) - let addresses: {String: Address} - - init(addresses: {String: Address}) { - self.addresses = addresses - } - } - /// Transaction that can be submitted and executed on the blockchain. /// access(all) @@ -357,6 +336,11 @@ contract Test { access(all) fun createAccount(): TestAccount + /// Returns the account for the given address. + /// + access(all) + fun getAccount(_ address: Address): TestAccount + /// Add a transaction to the current block. /// access(all) @@ -379,17 +363,10 @@ contract Test { access(all) fun deployContract( name: String, - code: String, - account: TestAccount, + path: String, arguments: [AnyStruct] ): Error? - /// Set the configuration to be used by the blockchain. - /// Overrides any existing configuration. - /// - access(all) - fun useConfiguration(_ configuration: Configuration) - /// Returns all the logs from the blockchain, up to the calling point. /// access(all) diff --git a/runtime/stdlib/test-framework.go b/runtime/stdlib/test-framework.go index 3e8d0523c2..6496ee98fb 100644 --- a/runtime/stdlib/test-framework.go +++ b/runtime/stdlib/test-framework.go @@ -30,7 +30,7 @@ import ( // This is used as a way to inject test provider dependencies dynamically. type TestFramework interface { - NewEmulatorBackend() Blockchain + EmulatorBackend() Blockchain ReadFile(string) (string, error) } @@ -43,6 +43,8 @@ type Blockchain interface { CreateAccount() (*Account, error) + GetAccount(interpreter.AddressValue) (*Account, error) + AddTransaction( inter *interpreter.Interpreter, code string, @@ -58,13 +60,10 @@ type Blockchain interface { DeployContract( inter *interpreter.Interpreter, name string, - code string, - account *Account, + path string, arguments []interpreter.Value, ) error - UseConfiguration(configuration *Configuration) - StandardLibraryHandler() StandardLibraryHandler Logs() []string @@ -98,7 +97,3 @@ type Account struct { PublicKey *PublicKey Address common.Address } - -type Configuration struct { - Addresses map[string]common.Address -} diff --git a/runtime/stdlib/test.go b/runtime/stdlib/test.go index 47a565cb8f..de914c21a9 100644 --- a/runtime/stdlib/test.go +++ b/runtime/stdlib/test.go @@ -48,8 +48,6 @@ const accountAddressFieldName = "address" const matcherTestFieldName = "test" -const addressesFieldName = "addresses" - const TestContractLocation = common.IdentifierLocation(testContractTypeName) var testOnce sync.Once diff --git a/runtime/stdlib/test_contract.go b/runtime/stdlib/test_contract.go index 488f1f24f4..2467f48331 100644 --- a/runtime/stdlib/test_contract.go +++ b/runtime/stdlib/test_contract.go @@ -32,20 +32,19 @@ import ( ) type TestContractType struct { - Checker *sema.Checker - CompositeType *sema.CompositeType - InitializerTypes []sema.Type - emulatorBackendType *testEmulatorBackendType - newEmulatorBlockchainFunctionType *sema.FunctionType - expectFunction interpreter.FunctionValue - newMatcherFunction interpreter.FunctionValue - haveElementCountFunction interpreter.FunctionValue - beEmptyFunction interpreter.FunctionValue - equalFunction interpreter.FunctionValue - beGreaterThanFunction interpreter.FunctionValue - containFunction interpreter.FunctionValue - beLessThanFunction interpreter.FunctionValue - expectFailureFunction interpreter.FunctionValue + Checker *sema.Checker + CompositeType *sema.CompositeType + InitializerTypes []sema.Type + emulatorBackendType *testEmulatorBackendType + expectFunction interpreter.FunctionValue + newMatcherFunction interpreter.FunctionValue + haveElementCountFunction interpreter.FunctionValue + beEmptyFunction interpreter.FunctionValue + equalFunction interpreter.FunctionValue + beGreaterThanFunction interpreter.FunctionValue + containFunction interpreter.FunctionValue + beLessThanFunction interpreter.FunctionValue + expectFailureFunction interpreter.FunctionValue } // 'Test.assert' function @@ -366,65 +365,6 @@ func newTestTypeReadFileFunction(testFramework TestFramework) *interpreter.HostF ) } -// 'Test.newEmulatorBlockchain' function - -const testTypeNewEmulatorBlockchainFunctionDocString = ` -Creates a blockchain which is backed by a new emulator instance. -` - -const testTypeNewEmulatorBlockchainFunctionName = "newEmulatorBlockchain" - -const testBlockchainTypeName = "Blockchain" - -func newTestTypeNewEmulatorBlockchainFunctionType(blockchainType *sema.CompositeType) *sema.FunctionType { - return &sema.FunctionType{ - ReturnTypeAnnotation: sema.NewTypeAnnotation( - blockchainType, - ), - } -} - -func (t *TestContractType) newNewEmulatorBlockchainFunction( - testFramework TestFramework, -) *interpreter.HostFunctionValue { - return interpreter.NewUnmeteredHostFunctionValue( - t.newEmulatorBlockchainFunctionType, - func(invocation interpreter.Invocation) interpreter.Value { - inter := invocation.Interpreter - locationRange := invocation.LocationRange - - // Create an `EmulatorBackend` - emulatorBackend := t.emulatorBackendType.newEmulatorBackend( - inter, - testFramework.NewEmulatorBackend(), - locationRange, - ) - - // Create a 'Blockchain' struct value, that wraps the emulator backend, - // by calling the constructor of 'Blockchain'. - - blockchainConstructor := getNestedTypeConstructorValue( - *invocation.Self, - testBlockchainTypeName, - ) - - blockchain, err := inter.InvokeExternally( - blockchainConstructor, - blockchainConstructor.Type, - []interpreter.Value{ - emulatorBackend, - }, - ) - - if err != nil { - panic(err) - } - - return blockchain - }, - ) -} - // 'Test.NewMatcher' function. // Constructs a matcher that test only 'AnyStruct'. // Accepts test function that accepts subtype of 'AnyStruct'. @@ -1035,8 +975,6 @@ func newTestContractType() *TestContractType { matcherType := ty.matcherType() matcherTestFunctionType := compositeFunctionType(matcherType, matcherTestFieldName) - blockchainType := ty.blockchainType() - // Test.assert() compositeType.Members.Set( testTypeAssertFunctionName, @@ -1070,19 +1008,6 @@ func newTestContractType() *TestContractType { ), ) - // Test.newEmulatorBlockchain() - newEmulatorBlockchainFunctionType := newTestTypeNewEmulatorBlockchainFunctionType(blockchainType) - compositeType.Members.Set( - testTypeNewEmulatorBlockchainFunctionName, - sema.NewUnmeteredPublicFunctionMember( - compositeType, - testTypeNewEmulatorBlockchainFunctionName, - newEmulatorBlockchainFunctionType, - testTypeNewEmulatorBlockchainFunctionDocString, - ), - ) - ty.newEmulatorBlockchainFunctionType = newEmulatorBlockchainFunctionType - // Test.readFile() compositeType.Members.Set( testTypeReadFileFunctionName, @@ -1233,6 +1158,7 @@ func newTestContractType() *TestContractType { ty.expectFailureFunction = newTestTypeExpectFailureFunction( expectFailureFunctionType, ) + compositeType.ResolveMembers() return ty } @@ -1273,23 +1199,6 @@ func (t *TestContractType) matcherType() *sema.CompositeType { return matcherType } -func (t *TestContractType) blockchainType() *sema.CompositeType { - typ, ok := t.CompositeType.NestedTypes.Get(testBlockchainTypeName) - if !ok { - panic(typeNotFoundError(testContractTypeName, testBlockchainTypeName)) - } - - matcherType, ok := typ.(*sema.CompositeType) - if !ok || matcherType.Kind != common.CompositeKindStructure { - panic(errors.NewUnexpectedError( - "invalid type for '%s'. expected struct type", - testMatcherTypeName, - )) - } - - return matcherType -} - func (t *TestContractType) NewTestContract( inter *interpreter.Interpreter, testFramework TestFramework, @@ -1300,9 +1209,14 @@ func (t *TestContractType) NewTestContract( error, ) { initializerTypes := t.InitializerTypes + emulatorBackend := t.emulatorBackendType.newEmulatorBackend( + inter, + testFramework.EmulatorBackend(), + interpreter.EmptyLocationRange, + ) value, err := inter.InvokeFunctionValue( constructor, - nil, + []interpreter.Value{emulatorBackend}, initializerTypes, initializerTypes, invocationRange, @@ -1318,8 +1232,6 @@ func (t *TestContractType) NewTestContract( compositeValue.Functions[testTypeAssertEqualFunctionName] = testTypeAssertEqualFunction compositeValue.Functions[testTypeFailFunctionName] = testTypeFailFunction compositeValue.Functions[testTypeExpectFunctionName] = t.expectFunction - compositeValue.Functions[testTypeNewEmulatorBlockchainFunctionName] = - t.newNewEmulatorBlockchainFunction(testFramework) compositeValue.Functions[testTypeReadFileFunctionName] = newTestTypeReadFileFunction(testFramework) diff --git a/runtime/stdlib/test_emulatorbackend.go b/runtime/stdlib/test_emulatorbackend.go index aca8706e56..1e087139ea 100644 --- a/runtime/stdlib/test_emulatorbackend.go +++ b/runtime/stdlib/test_emulatorbackend.go @@ -20,6 +20,8 @@ package stdlib import ( + "fmt" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" @@ -41,7 +43,6 @@ type testEmulatorBackendType struct { executeNextTransactionFunctionType *sema.FunctionType commitBlockFunctionType *sema.FunctionType deployContractFunctionType *sema.FunctionType - useConfigFunctionType *sema.FunctionType logsFunctionType *sema.FunctionType serviceAccountFunctionType *sema.FunctionType eventsFunctionType *sema.FunctionType @@ -49,6 +50,7 @@ type testEmulatorBackendType struct { moveTimeFunctionType *sema.FunctionType createSnapshotFunctionType *sema.FunctionType loadSnapshotFunctionType *sema.FunctionType + getAccountFunctionType *sema.FunctionType } func newTestEmulatorBackendType( @@ -84,11 +86,6 @@ func newTestEmulatorBackendType( testEmulatorBackendTypeDeployContractFunctionName, ) - useConfigFunctionType := interfaceFunctionType( - blockchainBackendInterfaceType, - testEmulatorBackendTypeUseConfigFunctionName, - ) - logsFunctionType := interfaceFunctionType( blockchainBackendInterfaceType, testEmulatorBackendTypeLogsFunctionName, @@ -124,6 +121,11 @@ func newTestEmulatorBackendType( testEmulatorBackendTypeLoadSnapshotFunctionName, ) + getAccountFunctionType := interfaceFunctionType( + blockchainBackendInterfaceType, + testEmulatorBackendTypeGetAccountFunctionName, + ) + compositeType := &sema.CompositeType{ Identifier: testEmulatorBackendTypeName, Kind: common.CompositeKindStructure, @@ -170,12 +172,6 @@ func newTestEmulatorBackendType( deployContractFunctionType, testEmulatorBackendTypeDeployContractFunctionDocString, ), - sema.NewUnmeteredPublicFunctionMember( - compositeType, - testEmulatorBackendTypeUseConfigFunctionName, - useConfigFunctionType, - testEmulatorBackendTypeUseConfigFunctionDocString, - ), sema.NewUnmeteredPublicFunctionMember( compositeType, testEmulatorBackendTypeLogsFunctionName, @@ -218,6 +214,12 @@ func newTestEmulatorBackendType( loadSnapshotFunctionType, testEmulatorBackendTypeLoadSnapshotFunctionDocString, ), + sema.NewUnmeteredPublicFunctionMember( + compositeType, + testEmulatorBackendTypeGetAccountFunctionName, + getAccountFunctionType, + testEmulatorBackendTypeGetAccountFunctionDocString, + ), } compositeType.Members = sema.MembersAsMap(members) @@ -231,7 +233,6 @@ func newTestEmulatorBackendType( executeNextTransactionFunctionType: executeNextTransactionFunctionType, commitBlockFunctionType: commitBlockFunctionType, deployContractFunctionType: deployContractFunctionType, - useConfigFunctionType: useConfigFunctionType, logsFunctionType: logsFunctionType, serviceAccountFunctionType: serviceAccountFunctionType, eventsFunctionType: eventsFunctionType, @@ -239,6 +240,7 @@ func newTestEmulatorBackendType( moveTimeFunctionType: moveTimeFunctionType, createSnapshotFunctionType: createSnapshotFunctionType, loadSnapshotFunctionType: loadSnapshotFunctionType, + getAccountFunctionType: getAccountFunctionType, } } @@ -348,6 +350,47 @@ func newTestAccountValue( return accountValue } +// 'EmulatorBackend.getAccount' function + +const testEmulatorBackendTypeGetAccountFunctionName = "getAccount" + +const testEmulatorBackendTypeGetAccountFunctionDocString = ` +Returns the account for the given address. +` + +func (t *testEmulatorBackendType) newGetAccountFunction( + blockchain Blockchain, +) *interpreter.HostFunctionValue { + return interpreter.NewUnmeteredHostFunctionValue( + t.getAccountFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + address, ok := invocation.Arguments[0].(interpreter.AddressValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + account, err := blockchain.GetAccount(address) + if err != nil { + msg := fmt.Sprintf("account with address: %s was not found", address) + panic(PanicError{ + Message: msg, + LocationRange: invocation.LocationRange, + }) + } + + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + return newTestAccountValue( + blockchain, + inter, + locationRange, + account, + ) + }, + ) +} + // 'EmulatorBackend.addTransaction' function const testEmulatorBackendTypeAddTransactionFunctionName = "addTransaction" @@ -509,22 +552,14 @@ func (t *testEmulatorBackendType) newDeployContractFunction( panic(errors.NewUnreachableError()) } - // Contract code - code, ok := invocation.Arguments[1].(*interpreter.StringValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - // authorizer - accountValue, ok := invocation.Arguments[2].(interpreter.MemberAccessibleValue) + // Contract file path + path, ok := invocation.Arguments[1].(*interpreter.StringValue) if !ok { panic(errors.NewUnreachableError()) } - account := accountFromValue(inter, accountValue, invocation.LocationRange) - // Contract init arguments - args, err := arrayValueToSlice(inter, invocation.Arguments[3]) + args, err := arrayValueToSlice(inter, invocation.Arguments[2]) if err != nil { panic(err) } @@ -532,8 +567,7 @@ func (t *testEmulatorBackendType) newDeployContractFunction( err = blockchain.DeployContract( inter, name.Str, - code.Str, - account, + path.Str, args, ) @@ -542,65 +576,6 @@ func (t *testEmulatorBackendType) newDeployContractFunction( ) } -// 'EmulatorBackend.useConfiguration' function - -const testEmulatorBackendTypeUseConfigFunctionName = "useConfiguration" - -const testEmulatorBackendTypeUseConfigFunctionDocString = ` -Set the configuration to be used by the blockchain. -Overrides any existing configuration. -` - -func (t *testEmulatorBackendType) newUseConfigFunction( - blockchain Blockchain, -) *interpreter.HostFunctionValue { - return interpreter.NewUnmeteredHostFunctionValue( - t.useConfigFunctionType, - func(invocation interpreter.Invocation) interpreter.Value { - inter := invocation.Interpreter - - // configurations - configsValue, ok := invocation.Arguments[0].(*interpreter.CompositeValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - addresses, ok := configsValue.GetMember( - inter, - invocation.LocationRange, - addressesFieldName, - ).(*interpreter.DictionaryValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - mapping := make(map[string]common.Address, addresses.Count()) - - addresses.Iterate(inter, func(locationValue, addressValue interpreter.Value) bool { - location, ok := locationValue.(*interpreter.StringValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - address, ok := addressValue.(interpreter.AddressValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - mapping[location.Str] = common.Address(address) - - return true - }) - - blockchain.UseConfiguration(&Configuration{ - Addresses: mapping, - }) - - return interpreter.Void - }, - ) -} - // 'EmulatorBackend.logs' function const testEmulatorBackendTypeLogsFunctionName = "logs" @@ -846,10 +821,6 @@ func (t *testEmulatorBackendType) newEmulatorBackend( Name: testEmulatorBackendTypeDeployContractFunctionName, Value: t.newDeployContractFunction(blockchain), }, - { - Name: testEmulatorBackendTypeUseConfigFunctionName, - Value: t.newUseConfigFunction(blockchain), - }, { Name: testEmulatorBackendTypeLogsFunctionName, Value: t.newLogsFunction(blockchain), @@ -878,6 +849,10 @@ func (t *testEmulatorBackendType) newEmulatorBackend( Name: testEmulatorBackendTypeLoadSnapshotFunctionName, Value: t.newLoadSnapshotFunction(blockchain), }, + { + Name: testEmulatorBackendTypeGetAccountFunctionName, + Value: t.newGetAccountFunction(blockchain), + }, } // TODO: Use SimpleCompositeValue diff --git a/runtime/stdlib/test_test.go b/runtime/stdlib/test_test.go index f16b66075d..6a3a2e4af4 100644 --- a/runtime/stdlib/test_test.go +++ b/runtime/stdlib/test_test.go @@ -16,7 +16,8 @@ * limitations under the License. */ -package stdlib +// This is in order to avoid cyclic import errors with runtime package +package stdlib_test import ( "errors" @@ -26,6 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/activations" "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" @@ -33,18 +35,24 @@ import ( "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/parser" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" "github.com/onflow/cadence/runtime/tests/checker" "github.com/onflow/cadence/runtime/tests/utils" ) func newTestContractInterpreter(t *testing.T, code string) (*interpreter.Interpreter, error) { - return newTestContractInterpreterWithTestFramework(t, code, nil) + testFramework := &mockedTestFramework{ + emulatorBackend: func() stdlib.Blockchain { + return &mockedBlockchain{} + }, + } + return newTestContractInterpreterWithTestFramework(t, code, testFramework) } func newTestContractInterpreterWithTestFramework( t *testing.T, code string, - testFramework TestFramework, + testFramework stdlib.TestFramework, ) (*interpreter.Interpreter, error) { program, err := parser.ParseProgram( nil, @@ -54,8 +62,8 @@ func newTestContractInterpreterWithTestFramework( require.NoError(t, err) activation := sema.NewVariableActivation(sema.BaseValueActivation) - activation.DeclareValue(AssertFunction) - activation.DeclareValue(PanicFunction) + activation.DeclareValue(stdlib.AssertFunction) + activation.DeclareValue(stdlib.PanicFunction) checker, err := sema.NewChecker( program, @@ -72,15 +80,15 @@ func newTestContractInterpreterWithTestFramework( sema.Import, error, ) { - if importedLocation == TestContractLocation { + if importedLocation == stdlib.TestContractLocation { return sema.ElaborationImport{ - Elaboration: GetTestContractType().Checker.Elaboration, + Elaboration: stdlib.GetTestContractType().Checker.Elaboration, }, nil } return nil, errors.New("invalid import") }, - ContractValueHandler: TestCheckerContractValueHandler, + ContractValueHandler: stdlib.TestCheckerContractValueHandler, }, ) require.NoError(t, err) @@ -90,13 +98,13 @@ func newTestContractInterpreterWithTestFramework( return nil, err } - storage := newUnmeteredInMemoryStorage() + storage := interpreter.NewInMemoryStorage(nil) var uuid uint64 = 0 baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) - interpreter.Declare(baseActivation, AssertFunction) - interpreter.Declare(baseActivation, PanicFunction) + interpreter.Declare(baseActivation, stdlib.AssertFunction) + interpreter.Declare(baseActivation, stdlib.PanicFunction) inter, err := interpreter.NewInterpreter( interpreter.ProgramFromChecker(checker), @@ -105,8 +113,8 @@ func newTestContractInterpreterWithTestFramework( Storage: storage, BaseActivation: baseActivation, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { - if location == TestContractLocation { - program := interpreter.ProgramFromChecker(GetTestContractType().Checker) + if location == stdlib.TestContractLocation { + program := interpreter.ProgramFromChecker(stdlib.GetTestContractType().Checker) subInterpreter, err := inter.NewSubInterpreter(program, location) if err != nil { panic(err) @@ -118,7 +126,7 @@ func newTestContractInterpreterWithTestFramework( return nil }, - ContractValueHandler: NewTestInterpreterContractValueHandler(testFramework), + ContractValueHandler: stdlib.NewTestInterpreterContractValueHandler(testFramework), UUIDHandler: func() (uint64, error) { uuid++ return uuid, nil @@ -142,7 +150,8 @@ func TestTestNewMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let matcher = Test.newMatcher(fun (_ value: AnyStruct): Bool { if !value.getType().isSubtype(of: Type()) { return false @@ -169,7 +178,8 @@ func TestTestNewMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let matcher = Test.newMatcher(fun (_ value: Int): Bool { return value == 7 @@ -193,7 +203,8 @@ func TestTestNewMatcher(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { let matcher = Test.newMatcher(fun (_ value: Int): Bool { return (value + 7) == 4 @@ -218,7 +229,8 @@ func TestTestNewMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let matcher = Test.newMatcher(fun (_ value: &Foo): Bool { return value.a == 4 @@ -233,8 +245,11 @@ func TestTestNewMatcher(t *testing.T) { return res } - access(all) resource Foo { - access(all) let a: Int + access(all) + resource Foo { + + access(all) + let a: Int init(_ a: Int) { self.a = a @@ -254,18 +269,20 @@ func TestTestNewMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { + access(all) + fun test() { - let matcher = Test.newMatcher(fun (_ value: @Foo): Bool { - destroy value - return true - }) - } + let matcher = Test.newMatcher(fun (_ value: @Foo): Bool { + destroy value + return true + }) + } - access(all) resource Foo {} - ` + access(all) + resource Foo {} + ` _, err := newTestContractInterpreter(t, script) @@ -277,17 +294,18 @@ func TestTestNewMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { - let matcher = Test.newMatcher(fun (_ value: Int): Bool { - return value == 7 - }) + let matcher = Test.newMatcher(fun (_ value: Int): Bool { + return value == 7 + }) - return matcher.test(7) - } - ` + return matcher.test(7) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -301,15 +319,16 @@ func TestTestNewMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { + access(all) + fun test() { - let matcher = Test.newMatcher(fun (_ value: Int): Bool { - return value == 7 - }) - } - ` + let matcher = Test.newMatcher(fun (_ value: Int): Bool { + return value == 7 + }) + } + ` _, err := newTestContractInterpreter(t, script) @@ -322,24 +341,25 @@ func TestTestNewMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { + access(all) + fun test() { - let matcher1 = Test.newMatcher(fun (_ value: Int): Bool { - return (value + 5) == 10 - }) + let matcher1 = Test.newMatcher(fun (_ value: Int): Bool { + return (value + 5) == 10 + }) - let matcher2 = Test.newMatcher(fun (_ value: String): Bool { - return value.length == 10 - }) + let matcher2 = Test.newMatcher(fun (_ value: String): Bool { + return value.length == 10 + }) - let matcher3 = matcher1.and(matcher2) + let matcher3 = matcher1.and(matcher2) - // Invoke with a type that matches to only one matcher - matcher3.test(5) - } - ` + // Invoke with a type that matches to only one matcher + matcher3.test(5) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -360,7 +380,8 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { Test.equal(1) } ` @@ -378,7 +399,8 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let matcher = Test.equal(1) return matcher.test(1) } @@ -398,13 +420,15 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let f = Foo() let matcher = Test.equal(f) return matcher.test(f) } - access(all) struct Foo {} + access(all) + struct Foo {} ` inter, err := newTestContractInterpreter(t, script) @@ -421,13 +445,15 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let f <- create Foo() let matcher = Test.equal(<-f) return matcher.test(<- create Foo()) } - access(all) resource Foo {} + access(all) + resource Foo {} ` _, err := newTestContractInterpreter(t, script) @@ -444,7 +470,8 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { let matcher = Test.equal("hello") } ` @@ -462,7 +489,8 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { let matcher = Test.equal(1) } ` @@ -480,7 +508,8 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let one = Test.equal(1) let two = Test.equal(2) @@ -505,7 +534,8 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let one = Test.equal(1) let two = Test.equal(2) @@ -529,7 +559,8 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let one = Test.equal(1) let two = Test.equal(2) @@ -551,24 +582,26 @@ func TestTestEqualMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let one = Test.equal(1) + access(all) + fun testMatch(): Bool { + let one = Test.equal(1) - let notOne = Test.not(one) + let notOne = Test.not(one) - return notOne.test(2) - } + return notOne.test(2) + } - access(all) fun testNoMatch(): Bool { - let one = Test.equal(1) + access(all) + fun testNoMatch(): Bool { + let one = Test.equal(1) - let notOne = Test.not(one) + let notOne = Test.not(one) - return notOne.test(1) - } - ` + return notOne.test(1) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -588,7 +621,8 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let one = Test.equal(1) let two = Test.equal(2) let three = Test.equal(3) @@ -613,7 +647,8 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let foo <- create Foo() let bar <- create Bar() @@ -626,8 +661,10 @@ func TestTestEqualMatcher(t *testing.T) { && matcher.test(<-create Bar()) } - access(all) resource Foo {} - access(all) resource Bar {} + access(all) + resource Foo {} + access(all) + resource Bar {} ` _, err := newTestContractInterpreter(t, script) @@ -645,7 +682,8 @@ func TestTestEqualMatcher(t *testing.T) { script := ` import Test - access(all) fun test(): Bool { + access(all) + fun test(): Bool { let foo <- create Foo() let bar <- create Bar() @@ -657,8 +695,10 @@ func TestTestEqualMatcher(t *testing.T) { return matcher.test(<-create Foo()) } - access(all) resource Foo {} - access(all) resource Bar {} + access(all) + resource Foo {} + access(all) + resource Bar {} ` _, err := newTestContractInterpreter(t, script) @@ -678,12 +718,13 @@ func TestAssertEqual(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - Test.assertEqual("this string", "this string") - } - ` + access(all) + fun test() { + Test.assertEqual("this string", "this string") + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -696,19 +737,20 @@ func TestAssertEqual(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - Test.assertEqual(15, 21) - } - ` + access(all) + fun test() { + Test.assertEqual(15, 21) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) _, err = inter.Invoke("test") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, @@ -720,19 +762,20 @@ func TestAssertEqual(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - Test.assertEqual(true, 1) - } - ` + access(all) + fun test() { + Test.assertEqual(true, 1) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) _, err = inter.Invoke("test") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, @@ -744,20 +787,22 @@ func TestAssertEqual(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testEqual() { - let expected = Address(0xf8d6e0586b0a20c7) - let actual = Address(0xf8d6e0586b0a20c7) - Test.assertEqual(expected, actual) - } + access(all) + fun testEqual() { + let expected = Address(0xf8d6e0586b0a20c7) + let actual = Address(0xf8d6e0586b0a20c7) + Test.assertEqual(expected, actual) + } - access(all) fun testNotEqual() { - let expected = Address(0xf8d6e0586b0a20c7) - let actual = Address(0xee82856bf20e2aa6) - Test.assertEqual(expected, actual) - } - ` + access(all) + fun testNotEqual() { + let expected = Address(0xf8d6e0586b0a20c7) + let actual = Address(0xee82856bf20e2aa6) + Test.assertEqual(expected, actual) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -767,7 +812,7 @@ func TestAssertEqual(t *testing.T) { _, err = inter.Invoke("testNotEqual") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, @@ -779,28 +824,33 @@ func TestAssertEqual(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) struct Foo { - access(all) let answer: Int + access(all) + struct Foo { + + access(all) + let answer: Int - init(answer: Int) { - self.answer = answer - } - } + init(answer: Int) { + self.answer = answer + } + } - access(all) fun testEqual() { - let expected = Foo(answer: 42) - let actual = Foo(answer: 42) - Test.assertEqual(expected, actual) - } + access(all) + fun testEqual() { + let expected = Foo(answer: 42) + let actual = Foo(answer: 42) + Test.assertEqual(expected, actual) + } - access(all) fun testNotEqual() { - let expected = Foo(answer: 42) - let actual = Foo(answer: 420) - Test.assertEqual(expected, actual) - } - ` + access(all) + fun testNotEqual() { + let expected = Foo(answer: 42) + let actual = Foo(answer: 420) + Test.assertEqual(expected, actual) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -810,7 +860,7 @@ func TestAssertEqual(t *testing.T) { _, err = inter.Invoke("testNotEqual") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, @@ -822,20 +872,22 @@ func TestAssertEqual(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testEqual() { - let expected = [1, 2, 3] - let actual = [1, 2, 3] - Test.assertEqual(expected, actual) - } + access(all) + fun testEqual() { + let expected = [1, 2, 3] + let actual = [1, 2, 3] + Test.assertEqual(expected, actual) + } - access(all) fun testNotEqual() { - let expected = [1, 2, 3] - let actual = [1, 2] - Test.assertEqual(expected, actual) - } - ` + access(all) + fun testNotEqual() { + let expected = [1, 2, 3] + let actual = [1, 2] + Test.assertEqual(expected, actual) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -845,7 +897,7 @@ func TestAssertEqual(t *testing.T) { _, err = inter.Invoke("testNotEqual") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, @@ -857,20 +909,22 @@ func TestAssertEqual(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testEqual() { - let expected = {1: true, 2: false, 3: true} - let actual = {1: true, 2: false, 3: true} - Test.assertEqual(expected, actual) - } + access(all) + fun testEqual() { + let expected = {1: true, 2: false, 3: true} + let actual = {1: true, 2: false, 3: true} + Test.assertEqual(expected, actual) + } - access(all) fun testNotEqual() { - let expected = {1: true, 2: false} - let actual = {1: true, 2: true} - Test.assertEqual(expected, actual) - } - ` + access(all) + fun testNotEqual() { + let expected = {1: true, 2: false} + let actual = {1: true, 2: true} + Test.assertEqual(expected, actual) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -880,11 +934,11 @@ func TestAssertEqual(t *testing.T) { _, err = inter.Invoke("testNotEqual") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, - "not equal: expected: {2: false, 1: true}, actual: {2: true, 1: true}", + "not equal: expected: {1: true, 2: false}, actual: {2: true, 1: true}", ) }) @@ -892,16 +946,18 @@ func TestAssertEqual(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - let f1 <- create Foo() - let f2 <- create Foo() - Test.assertEqual(<-f1, <-f2) - } + access(all) + fun test() { + let f1 <- create Foo() + let f2 <- create Foo() + Test.assertEqual(<-f1, <-f2) + } - access(all) resource Foo {} - ` + access(all) + resource Foo {} + ` _, err := newTestContractInterpreter(t, script) @@ -914,17 +970,20 @@ func TestAssertEqual(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - let foo <- create Foo() - let bar = Bar() - Test.assertEqual(<-foo, bar) - } + access(all) + fun test() { + let foo <- create Foo() + let bar = Bar() + Test.assertEqual(<-foo, bar) + } - access(all) resource Foo {} - access(all) struct Bar {} - ` + access(all) + resource Foo {} + access(all) + struct Bar {} + ` _, err := newTestContractInterpreter(t, script) @@ -936,17 +995,20 @@ func TestAssertEqual(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - let foo = Foo() - let bar <- create Bar() - Test.expect(foo, Test.equal(<-bar)) - } + access(all) + fun test() { + let foo = Foo() + let bar <- create Bar() + Test.expect(foo, Test.equal(<-bar)) + } - access(all) struct Foo {} - access(all) resource Bar {} - ` + access(all) + struct Foo {} + access(all) + resource Bar {} + ` _, err := newTestContractInterpreter(t, script) @@ -963,32 +1025,34 @@ func TestTestBeSucceededMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let successful = Test.beSucceeded() + access(all) + fun testMatch(): Bool { + let successful = Test.beSucceeded() - let scriptResult = Test.ScriptResult( - status: Test.ResultStatus.succeeded, - returnValue: 42, - error: nil - ) + let scriptResult = Test.ScriptResult( + status: Test.ResultStatus.succeeded, + returnValue: 42, + error: nil + ) - return successful.test(scriptResult) - } + return successful.test(scriptResult) + } - access(all) fun testNoMatch(): Bool { - let successful = Test.beSucceeded() + access(all) + fun testNoMatch(): Bool { + let successful = Test.beSucceeded() - let scriptResult = Test.ScriptResult( - status: Test.ResultStatus.failed, - returnValue: nil, - error: Test.Error("Exceeding limit") - ) + let scriptResult = Test.ScriptResult( + status: Test.ResultStatus.failed, + returnValue: nil, + error: Test.Error("Exceeding limit") + ) - return successful.test(scriptResult) - } - ` + return successful.test(scriptResult) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1006,30 +1070,32 @@ func TestTestBeSucceededMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let successful = Test.beSucceeded() + access(all) + fun testMatch(): Bool { + let successful = Test.beSucceeded() - let transactionResult = Test.TransactionResult( - status: Test.ResultStatus.succeeded, - error: nil - ) + let transactionResult = Test.TransactionResult( + status: Test.ResultStatus.succeeded, + error: nil + ) - return successful.test(transactionResult) - } + return successful.test(transactionResult) + } - access(all) fun testNoMatch(): Bool { - let successful = Test.beSucceeded() + access(all) + fun testNoMatch(): Bool { + let successful = Test.beSucceeded() - let transactionResult = Test.TransactionResult( - status: Test.ResultStatus.failed, - error: Test.Error("Exceeded Limit") - ) + let transactionResult = Test.TransactionResult( + status: Test.ResultStatus.failed, + error: Test.Error("Exceeded Limit") + ) - return successful.test(transactionResult) - } - ` + return successful.test(transactionResult) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1047,14 +1113,15 @@ func TestTestBeSucceededMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test(): Bool { - let successful = Test.beSucceeded() + access(all) + fun test(): Bool { + let successful = Test.beSucceeded() - return successful.test("hello") - } - ` + return successful.test("hello") + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1072,32 +1139,34 @@ func TestTestBeFailedMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let failed = Test.beFailed() + access(all) + fun testMatch(): Bool { + let failed = Test.beFailed() - let scriptResult = Test.ScriptResult( - status: Test.ResultStatus.failed, - returnValue: nil, - error: Test.Error("Exceeding limit") - ) + let scriptResult = Test.ScriptResult( + status: Test.ResultStatus.failed, + returnValue: nil, + error: Test.Error("Exceeding limit") + ) - return failed.test(scriptResult) - } + return failed.test(scriptResult) + } - access(all) fun testNoMatch(): Bool { - let failed = Test.beFailed() + access(all) + fun testNoMatch(): Bool { + let failed = Test.beFailed() - let scriptResult = Test.ScriptResult( - status: Test.ResultStatus.succeeded, - returnValue: 42, - error: nil - ) + let scriptResult = Test.ScriptResult( + status: Test.ResultStatus.succeeded, + returnValue: 42, + error: nil + ) - return failed.test(scriptResult) - } - ` + return failed.test(scriptResult) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1115,30 +1184,32 @@ func TestTestBeFailedMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let failed = Test.beFailed() + access(all) + fun testMatch(): Bool { + let failed = Test.beFailed() - let transactionResult = Test.TransactionResult( - status: Test.ResultStatus.failed, - error: Test.Error("Exceeding limit") - ) + let transactionResult = Test.TransactionResult( + status: Test.ResultStatus.failed, + error: Test.Error("Exceeding limit") + ) - return failed.test(transactionResult) - } + return failed.test(transactionResult) + } - access(all) fun testNoMatch(): Bool { - let failed = Test.beFailed() + access(all) + fun testNoMatch(): Bool { + let failed = Test.beFailed() - let transactionResult = Test.TransactionResult( - status: Test.ResultStatus.succeeded, - error: nil - ) + let transactionResult = Test.TransactionResult( + status: Test.ResultStatus.succeeded, + error: nil + ) - return failed.test(transactionResult) - } - ` + return failed.test(transactionResult) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1156,14 +1227,15 @@ func TestTestBeFailedMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test(): Bool { - let failed = Test.beFailed() + access(all) + fun test(): Bool { + let failed = Test.beFailed() - return failed.test([]) - } - ` + return failed.test([]) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1215,7 +1287,7 @@ func TestTestAssertErrorMatcher(t *testing.T) { Test.assertError(result, errorMessage: "exceeding limit") } - ` + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1267,7 +1339,7 @@ func TestTestAssertErrorMatcher(t *testing.T) { Test.assertError(result, errorMessage: "exceeding limit") } - ` + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1293,20 +1365,22 @@ func TestTestBeNilMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let isNil = Test.beNil() + access(all) + fun testMatch(): Bool { + let isNil = Test.beNil() - return isNil.test(nil) - } + return isNil.test(nil) + } - access(all) fun testNoMatch(): Bool { - let isNil = Test.beNil() + access(all) + fun testNoMatch(): Bool { + let isNil = Test.beNil() - return isNil.test([1, 2]) - } - ` + return isNil.test([1, 2]) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1329,20 +1403,22 @@ func TestTestBeEmptyMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let emptyArray = Test.beEmpty() + access(all) + fun testMatch(): Bool { + let emptyArray = Test.beEmpty() - return emptyArray.test([]) - } + return emptyArray.test([]) + } - access(all) fun testNoMatch(): Bool { - let emptyArray = Test.beEmpty() + access(all) + fun testNoMatch(): Bool { + let emptyArray = Test.beEmpty() - return emptyArray.test([42, 23, 31]) - } - ` + return emptyArray.test([42, 23, 31]) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1360,22 +1436,24 @@ func TestTestBeEmptyMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let emptyDict = Test.beEmpty() - let dict: {Bool: Int} = {} + access(all) + fun testMatch(): Bool { + let emptyDict = Test.beEmpty() + let dict: {Bool: Int} = {} - return emptyDict.test(dict) - } + return emptyDict.test(dict) + } - access(all) fun testNoMatch(): Bool { - let emptyDict = Test.beEmpty() - let dict: {Bool: Int} = {true: 1, false: 0} + access(all) + fun testNoMatch(): Bool { + let emptyDict = Test.beEmpty() + let dict: {Bool: Int} = {true: 1, false: 0} - return emptyDict.test(dict) - } - ` + return emptyDict.test(dict) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1393,14 +1471,15 @@ func TestTestBeEmptyMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test(): Bool { - let emptyDict = Test.beEmpty() + access(all) + fun test(): Bool { + let emptyDict = Test.beEmpty() - return emptyDict.test("empty") - } - ` + return emptyDict.test("empty") + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1420,20 +1499,22 @@ func TestTestHaveElementCountMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let hasThreeElements = Test.haveElementCount(3) + access(all) + fun testMatch(): Bool { + let hasThreeElements = Test.haveElementCount(3) - return hasThreeElements.test([42, 23, 31]) - } + return hasThreeElements.test([42, 23, 31]) + } - access(all) fun testNoMatch(): Bool { - let hasThreeElements = Test.haveElementCount(3) + access(all) + fun testNoMatch(): Bool { + let hasThreeElements = Test.haveElementCount(3) - return hasThreeElements.test([42]) - } - ` + return hasThreeElements.test([42]) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1451,22 +1532,24 @@ func TestTestHaveElementCountMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let hasTwoElements = Test.haveElementCount(2) - let dict: {Bool: Int} = {true: 1, false: 0} + access(all) + fun testMatch(): Bool { + let hasTwoElements = Test.haveElementCount(2) + let dict: {Bool: Int} = {true: 1, false: 0} - return hasTwoElements.test(dict) - } + return hasTwoElements.test(dict) + } - access(all) fun testNoMatch(): Bool { - let hasTwoElements = Test.haveElementCount(2) - let dict: {Bool: Int} = {} + access(all) + fun testNoMatch(): Bool { + let hasTwoElements = Test.haveElementCount(2) + let dict: {Bool: Int} = {} - return hasTwoElements.test(dict) - } - ` + return hasTwoElements.test(dict) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1484,14 +1567,15 @@ func TestTestHaveElementCountMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test(): Bool { - let hasTwoElements = Test.haveElementCount(2) + access(all) + fun test(): Bool { + let hasTwoElements = Test.haveElementCount(2) - return hasTwoElements.test("two") - } - ` + return hasTwoElements.test("two") + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1511,20 +1595,22 @@ func TestTestContainMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let containsTwenty = Test.contain(20) + access(all) + fun testMatch(): Bool { + let containsTwenty = Test.contain(20) - return containsTwenty.test([42, 20, 31]) - } + return containsTwenty.test([42, 20, 31]) + } - access(all) fun testNoMatch(): Bool { - let containsTwenty = Test.contain(20) + access(all) + fun testNoMatch(): Bool { + let containsTwenty = Test.contain(20) - return containsTwenty.test([42]) - } - ` + return containsTwenty.test([42]) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1542,22 +1628,24 @@ func TestTestContainMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let containsFalse = Test.contain(false) - let dict: {Bool: Int} = {true: 1, false: 0} + access(all) + fun testMatch(): Bool { + let containsFalse = Test.contain(false) + let dict: {Bool: Int} = {true: 1, false: 0} - return containsFalse.test(dict) - } + return containsFalse.test(dict) + } - access(all) fun testNoMatch(): Bool { - let containsFive = Test.contain(5) - let dict: {Int: Bool} = {1: true, 0: false} + access(all) + fun testNoMatch(): Bool { + let containsFive = Test.contain(5) + let dict: {Int: Bool} = {1: true, 0: false} - return containsFive.test(dict) - } - ` + return containsFive.test(dict) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1575,14 +1663,15 @@ func TestTestContainMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test(): Bool { - let containsFalse = Test.contain(false) + access(all) + fun test(): Bool { + let containsFalse = Test.contain(false) - return containsFalse.test("false") - } - ` + return containsFalse.test("false") + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1602,20 +1691,22 @@ func TestTestBeGreaterThanMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let greaterThanFive = Test.beGreaterThan(5) + access(all) + fun testMatch(): Bool { + let greaterThanFive = Test.beGreaterThan(5) - return greaterThanFive.test(7) - } + return greaterThanFive.test(7) + } - access(all) fun testNoMatch(): Bool { - let greaterThanFive = Test.beGreaterThan(5) + access(all) + fun testNoMatch(): Bool { + let greaterThanFive = Test.beGreaterThan(5) - return greaterThanFive.test(2) - } - ` + return greaterThanFive.test(2) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1633,14 +1724,15 @@ func TestTestBeGreaterThanMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test(): Bool { - let greaterThanFive = Test.beGreaterThan(5) + access(all) + fun test(): Bool { + let greaterThanFive = Test.beGreaterThan(5) - return greaterThanFive.test("7") - } - ` + return greaterThanFive.test("7") + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1658,20 +1750,22 @@ func TestTestBeLessThanMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun testMatch(): Bool { - let lessThanSeven = Test.beLessThan(7) + access(all) + fun testMatch(): Bool { + let lessThanSeven = Test.beLessThan(7) - return lessThanSeven.test(5) - } + return lessThanSeven.test(5) + } - access(all) fun testNoMatch(): Bool { - let lessThanSeven = Test.beLessThan(7) + access(all) + fun testNoMatch(): Bool { + let lessThanSeven = Test.beLessThan(7) - return lessThanSeven.test(9) - } - ` + return lessThanSeven.test(9) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1689,14 +1783,15 @@ func TestTestBeLessThanMatcher(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test(): Bool { - let lessThanSeven = Test.beLessThan(7) + access(all) + fun test(): Bool { + let lessThanSeven = Test.beLessThan(7) - return lessThanSeven.test(true) - } - ` + return lessThanSeven.test(true) + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1716,7 +1811,8 @@ func TestTestExpect(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { Test.expect("this string", Test.equal("this string")) } ` @@ -1734,7 +1830,8 @@ func TestTestExpect(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { Test.expect("this string", Test.equal("other string")) } ` @@ -1745,11 +1842,11 @@ func TestTestExpect(t *testing.T) { _, err = inter.Invoke("test") require.Error(t, err) - assertionErr := &AssertionError{} + assertionErr := &stdlib.AssertionError{} assert.ErrorAs(t, err, assertionErr) assert.Equal(t, "given value is: \"this string\"", assertionErr.Message) assert.Equal(t, "test", assertionErr.LocationRange.Location.String()) - assert.Equal(t, 5, assertionErr.LocationRange.StartPosition().Line) + assert.Equal(t, 6, assertionErr.LocationRange.StartPosition().Line) }) t.Run("different types", func(t *testing.T) { @@ -1758,7 +1855,8 @@ func TestTestExpect(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { Test.expect("string", Test.equal(1)) } ` @@ -1777,7 +1875,8 @@ func TestTestExpect(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { Test.expect("hello", Test.equal("hello")) } ` @@ -1795,7 +1894,8 @@ func TestTestExpect(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { Test.expect("string", Test.equal(1)) } ` @@ -1813,13 +1913,15 @@ func TestTestExpect(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { let f1 <- create Foo() let f2 <- create Foo() Test.expect(<-f1, Test.equal(<-f2)) } - access(all) resource Foo {} + access(all) + resource Foo {} ` _, err := newTestContractInterpreter(t, script) @@ -1835,14 +1937,17 @@ func TestTestExpect(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { let foo <- create Foo() let bar = Bar() Test.expect(<-foo, Test.equal(bar)) } - access(all) resource Foo {} - access(all) struct Bar {} + access(all) + resource Foo {} + access(all) + struct Bar {} ` _, err := newTestContractInterpreter(t, script) @@ -1857,14 +1962,17 @@ func TestTestExpect(t *testing.T) { script := ` import Test - access(all) fun test() { + access(all) + fun test() { let foo = Foo() let bar <- create Bar() Test.expect(foo, Test.equal(<-bar)) } - access(all) struct Foo {} - access(all) resource Bar {} + access(all) + struct Foo {} + access(all) + resource Bar {} ` _, err := newTestContractInterpreter(t, script) @@ -1882,30 +1990,33 @@ func TestTestExpectFailure(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - let foo = Foo(answer: 42) - Test.expectFailure(fun(): Void { - foo.correctAnswer(42) - }, errorMessageSubstring: "wrong answer!") - } + access(all) + fun test() { + let foo = Foo(answer: 42) + Test.expectFailure(fun(): Void { + foo.correctAnswer(42) + }, errorMessageSubstring: "wrong answer!") + } - access(all) struct Foo { - access(self) let answer: UInt8 + access(all) + struct Foo { + access(self) let answer: UInt8 - init(answer: UInt8) { - self.answer = answer - } + init(answer: UInt8) { + self.answer = answer + } - access(all) fun correctAnswer(_ input: UInt8): Bool { - if self.answer != input { - panic("wrong answer!") - } - return true - } - } - ` + access(all) + fun correctAnswer(_ input: UInt8): Bool { + if self.answer != input { + panic("wrong answer!") + } + return true + } + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1923,30 +2034,33 @@ func TestTestExpectFailure(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - let foo = Foo(answer: 42) - Test.expectFailure(fun(): Void { - foo.correctAnswer(43) - }, errorMessageSubstring: "wrong answer!") - } + access(all) + fun test() { + let foo = Foo(answer: 42) + Test.expectFailure(fun(): Void { + foo.correctAnswer(43) + }, errorMessageSubstring: "wrong answer!") + } - access(all) struct Foo { - access(self) let answer: UInt8 + access(all) + struct Foo { + access(self) let answer: UInt8 - init(answer: UInt8) { - self.answer = answer - } + init(answer: UInt8) { + self.answer = answer + } - access(all) fun correctAnswer(_ input: UInt8): Bool { - if self.answer != input { - panic("wrong answer!") - } - return true - } - } - ` + access(all) + fun correctAnswer(_ input: UInt8): Bool { + if self.answer != input { + panic("wrong answer!") + } + return true + } + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -1959,30 +2073,33 @@ func TestTestExpectFailure(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - let foo = Foo(answer: 42) - Test.expectFailure(fun(): Void { - foo.correctAnswer(43) - }, errorMessageSubstring: "what is wrong?") - } + access(all) + fun test() { + let foo = Foo(answer: 42) + Test.expectFailure(fun(): Void { + foo.correctAnswer(43) + }, errorMessageSubstring: "what is wrong?") + } - access(all) struct Foo { - access(self) let answer: UInt8 + access(all) + struct Foo { + access(self) let answer: UInt8 - init(answer: UInt8) { - self.answer = answer - } + init(answer: UInt8) { + self.answer = answer + } - access(all) fun correctAnswer(_ input: UInt8): Bool { - if self.answer != input { - panic("wrong answer!") - } - return true - } - } - ` + access(all) + fun correctAnswer(_ input: UInt8): Bool { + if self.answer != input { + panic("wrong answer!") + } + return true + } + } + ` inter, err := newTestContractInterpreter(t, script) require.NoError(t, err) @@ -2000,31 +2117,34 @@ func TestTestExpectFailure(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - let foo = Foo(answer: 42) - Test.expectFailure(fun(answer: UInt64): Foo { - foo.correctAnswer(42) - return foo - }, errorMessageSubstring: "wrong answer") - } + access(all) + fun test() { + let foo = Foo(answer: 42) + Test.expectFailure(fun(answer: UInt64): Foo { + foo.correctAnswer(42) + return foo + }, errorMessageSubstring: "wrong answer") + } - access(all) struct Foo { - access(self) let answer: UInt8 + access(all) + struct Foo { + access(self) let answer: UInt8 - init(answer: UInt8) { - self.answer = answer - } + init(answer: UInt8) { + self.answer = answer + } - access(all) fun correctAnswer(_ input: UInt8): Bool { - if self.answer != input { - panic("wrong answer!") - } - return true - } - } - ` + access(all) + fun correctAnswer(_ input: UInt8): Bool { + if self.answer != input { + panic("wrong answer!") + } + return true + } + } + ` _, err := newTestContractInterpreter(t, script) errs := checker.RequireCheckerErrors(t, err, 1) @@ -2035,30 +2155,33 @@ func TestTestExpectFailure(t *testing.T) { t.Parallel() script := ` - import Test + import Test - access(all) fun test() { - let foo = Foo(answer: 42) - Test.expectFailure(fun(): Void { - foo.correctAnswer(42) - }, errorMessageSubstring: ["wrong answer"]) - } + access(all) + fun test() { + let foo = Foo(answer: 42) + Test.expectFailure(fun(): Void { + foo.correctAnswer(42) + }, errorMessageSubstring: ["wrong answer"]) + } - access(all) struct Foo { - access(self) let answer: UInt8 + access(all) + struct Foo { + access(self) let answer: UInt8 - init(answer: UInt8) { - self.answer = answer - } + init(answer: UInt8) { + self.answer = answer + } - access(all) fun correctAnswer(_ input: UInt8): Bool { - if self.answer != input { - panic("wrong answer!") - } - return true - } - } - ` + access(all) + fun correctAnswer(_ input: UInt8): Bool { + if self.answer != input { + panic("wrong answer!") + } + return true + } + } + ` _, err := newTestContractInterpreter(t, script) errs := checker.RequireCheckerErrors(t, err, 1) @@ -2076,18 +2199,18 @@ func TestBlockchain(t *testing.T) { const script = ` import Test - access(all) fun test() { - let blockchain = Test.newEmulatorBlockchain() - let events = blockchain.events() + access(all) + fun test() { + let events = Test.events() Test.expect(events, Test.beEmpty()) } - ` + ` eventsInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ events: func(inter *interpreter.Interpreter, eventType interpreter.StaticType) interpreter.Value { eventsInvoked = true @@ -2121,25 +2244,22 @@ func TestBlockchain(t *testing.T) { access(all) struct Foo {} - access(all) + access(all) fun test() { - let blockchain = Test.newEmulatorBlockchain() - // 'Foo' is not an event-type. // But we just need to test the API, so it doesn't really matter. let typ = Type() - - let events = blockchain.eventsOfType(typ) + let events = Test.eventsOfType(typ) Test.expect(events, Test.beEmpty()) } - ` + ` eventsInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ events: func(inter *interpreter.Interpreter, eventType interpreter.StaticType) interpreter.Value { eventsInvoked = true @@ -2177,15 +2297,14 @@ func TestBlockchain(t *testing.T) { access(all) fun test() { - let blockchain = Test.newEmulatorBlockchain() - blockchain.reset(to: 5) + Test.reset(to: 5) } - ` + ` resetInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ reset: func(height uint64) { resetInvoked = true @@ -2212,15 +2331,14 @@ func TestBlockchain(t *testing.T) { access(all) fun test() { - let blockchain = Test.newEmulatorBlockchain() - blockchain.reset(to: 5.5) + Test.reset(to: 5.5) } - ` + ` resetInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ reset: func(height uint64) { resetInvoked = true @@ -2243,18 +2361,17 @@ func TestBlockchain(t *testing.T) { access(all) fun testMoveForward() { - let blockchain = Test.newEmulatorBlockchain() // timeDelta is the representation of 35 days, // in the form of seconds. let timeDelta = Fix64(35 * 24 * 60 * 60) - blockchain.moveTime(by: timeDelta) + Test.moveTime(by: timeDelta) } - ` + ` moveTimeInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ moveTime: func(timeDelta int64) { moveTimeInvoked = true @@ -2281,18 +2398,17 @@ func TestBlockchain(t *testing.T) { access(all) fun testMoveBackward() { - let blockchain = Test.newEmulatorBlockchain() // timeDelta is the representation of 35 days, // in the form of seconds. let timeDelta = Fix64(35 * 24 * 60 * 60) * -1.0 - blockchain.moveTime(by: timeDelta) + Test.moveTime(by: timeDelta) } - ` + ` moveTimeInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ moveTime: func(timeDelta int64) { moveTimeInvoked = true @@ -2319,15 +2435,14 @@ func TestBlockchain(t *testing.T) { access(all) fun testMoveTime() { - let blockchain = Test.newEmulatorBlockchain() - blockchain.moveTime(by: 3000) + Test.moveTime(by: 3000) } - ` + ` moveTimeInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ moveTime: func(timeDelta int64) { moveTimeInvoked = true @@ -2342,7 +2457,7 @@ func TestBlockchain(t *testing.T) { assert.False(t, moveTimeInvoked) }) - t.Run("newEmulatorBackend", func(t *testing.T) { + t.Run("createSnapshot", func(t *testing.T) { t.Parallel() const script = ` @@ -2350,45 +2465,14 @@ func TestBlockchain(t *testing.T) { access(all) fun test() { - let blockchain = Test.newEmulatorBlockchain() - Test.assertEqual(Type(), blockchain.getType()) - } - ` - - newEmulatorBackendInvoked := false - - testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { - newEmulatorBackendInvoked = true - return &mockedBlockchain{} - }, - } - - inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) - require.NoError(t, err) - - _, err = inter.Invoke("test") - require.NoError(t, err) - - assert.True(t, newEmulatorBackendInvoked) - }) - - t.Run("createSnapshot", func(t *testing.T) { - t.Parallel() - - const script = ` - import Test - - access(all) fun test() { - let blockchain = Test.newEmulatorBlockchain() - blockchain.createSnapshot(name: "adminCreated") + Test.createSnapshot(name: "adminCreated") } - ` + ` createSnapshotInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ createSnapshot: func(name string) error { createSnapshotInvoked = true @@ -2415,16 +2499,16 @@ func TestBlockchain(t *testing.T) { const script = ` import Test - access(all) fun test() { - let blockchain = Test.newEmulatorBlockchain() - blockchain.createSnapshot(name: "adminCreated") + access(all) + fun test() { + Test.createSnapshot(name: "adminCreated") } - ` + ` createSnapshotInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ createSnapshot: func(name string) error { createSnapshotInvoked = true @@ -2451,17 +2535,17 @@ func TestBlockchain(t *testing.T) { const script = ` import Test - access(all) fun test() { - let blockchain = Test.newEmulatorBlockchain() - blockchain.createSnapshot(name: "adminCreated") - blockchain.loadSnapshot(name: "adminCreated") + access(all) + fun test() { + Test.createSnapshot(name: "adminCreated") + Test.loadSnapshot(name: "adminCreated") } - ` + ` loadSnapshotInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ createSnapshot: func(name string) error { assert.Equal(t, "adminCreated", name) @@ -2493,17 +2577,17 @@ func TestBlockchain(t *testing.T) { const script = ` import Test - access(all) fun test() { - let blockchain = Test.newEmulatorBlockchain() - blockchain.createSnapshot(name: "adminCreated") - blockchain.loadSnapshot(name: "contractDeployed") + access(all) + fun test() { + Test.createSnapshot(name: "adminCreated") + Test.loadSnapshot(name: "contractDeployed") } - ` + ` loadSnapshotInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ createSnapshot: func(name string) error { assert.Equal(t, "adminCreated", name) @@ -2529,6 +2613,193 @@ func TestBlockchain(t *testing.T) { assert.True(t, loadSnapshotInvoked) }) + t.Run("deployContract", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + access(all) + fun test() { + let err = Test.deployContract( + name: "FooContract", + path: "./contracts/FooContract.cdc", + arguments: ["Hey, there!"] + ) + + Test.expect(err, Test.beNil()) + } + ` + + deployContractInvoked := false + + testFramework := &mockedTestFramework{ + emulatorBackend: func() stdlib.Blockchain { + return &mockedBlockchain{ + deployContract: func( + inter *interpreter.Interpreter, + name string, + path string, + arguments []interpreter.Value, + ) error { + deployContractInvoked = true + assert.Equal(t, "FooContract", name) + assert.Equal(t, "./contracts/FooContract.cdc", path) + assert.Equal(t, 1, len(arguments)) + argument := arguments[0].(*interpreter.StringValue) + assert.Equal(t, "Hey, there!", argument.Str) + + return nil + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.True(t, deployContractInvoked) + }) + + t.Run("deployContract with failure", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + access(all) + fun test() { + let err = Test.deployContract( + name: "FooContract", + path: "./contracts/FooContract.cdc", + arguments: ["Hey, there!"] + ) + + Test.assertEqual( + "failed to deploy contract: FooContract", + err!.message + ) + } + ` + + deployContractInvoked := false + + testFramework := &mockedTestFramework{ + emulatorBackend: func() stdlib.Blockchain { + return &mockedBlockchain{ + deployContract: func( + inter *interpreter.Interpreter, + name string, + path string, + arguments []interpreter.Value, + ) error { + deployContractInvoked = true + + return fmt.Errorf("failed to deploy contract: %s", name) + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.True(t, deployContractInvoked) + }) + + t.Run("getAccount", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + access(all) + fun test() { + let account = Test.getAccount(0x0000000000000009) + Test.assertEqual(0x0000000000000009 as Address, account.address) + } + ` + + getAccountInvoked := false + + testFramework := &mockedTestFramework{ + emulatorBackend: func() stdlib.Blockchain { + return &mockedBlockchain{ + getAccount: func(address interpreter.AddressValue) (*stdlib.Account, error) { + getAccountInvoked = true + assert.Equal(t, "0000000000000009", address.Hex()) + addr := common.Address(address) + + return &stdlib.Account{ + Address: addr, + PublicKey: &stdlib.PublicKey{ + PublicKey: []byte{1, 2, 3}, + SignAlgo: sema.SignatureAlgorithmECDSA_P256, + }, + }, nil + }, + stdlibHandler: func() stdlib.StandardLibraryHandler { + return runtime.NewBaseInterpreterEnvironment(runtime.Config{}) + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.True(t, getAccountInvoked) + }) + + t.Run("getAccount with failure", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + access(all) + fun test() { + let account = Test.getAccount(0x0000000000000009) + } + ` + + getAccountInvoked := false + + testFramework := &mockedTestFramework{ + emulatorBackend: func() stdlib.Blockchain { + return &mockedBlockchain{ + getAccount: func(address interpreter.AddressValue) (*stdlib.Account, error) { + getAccountInvoked = true + assert.Equal(t, "0000000000000009", address.Hex()) + + return nil, fmt.Errorf("failed to retrieve account with address: %s", address) + }, + stdlibHandler: func() stdlib.StandardLibraryHandler { + return runtime.NewBaseInterpreterEnvironment(runtime.Config{}) + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.Error(t, err) + assert.ErrorContains(t, err, "account with address: 0x0000000000000009 was not found") + + assert.True(t, getAccountInvoked) + }) + // TODO: Add more tests for the remaining functions. } @@ -2542,27 +2813,26 @@ func TestBlockchainAccount(t *testing.T) { const script = ` import Test - access(all) fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + access(all) + fun test() { + let account = Test.createAccount() assert(account.address == 0x0100000000000000) } - ` + ` testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + emulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ - createAccount: func() (*Account, error) { - return &Account{ - PublicKey: &PublicKey{ + createAccount: func() (*stdlib.Account, error) { + return &stdlib.Account{ + PublicKey: &stdlib.PublicKey{ PublicKey: []byte{1, 2, 3}, SignAlgo: sema.SignatureAlgorithmECDSA_P256, }, Address: common.Address{1}, }, nil }, - - stdlibHandler: func() StandardLibraryHandler { + stdlibHandler: func() stdlib.StandardLibraryHandler { return nil }, } @@ -2578,18 +2848,18 @@ func TestBlockchainAccount(t *testing.T) { } type mockedTestFramework struct { - newEmulatorBackend func() Blockchain - readFile func(s string) (string, error) + emulatorBackend func() stdlib.Blockchain + readFile func(s string) (string, error) } -var _ TestFramework = &mockedTestFramework{} +var _ stdlib.TestFramework = &mockedTestFramework{} -func (m mockedTestFramework) NewEmulatorBackend() Blockchain { - if m.newEmulatorBackend == nil { +func (m mockedTestFramework) EmulatorBackend() stdlib.Blockchain { + if m.emulatorBackend == nil { panic("'NewEmulatorBackend' is not implemented") } - return m.newEmulatorBackend() + return m.emulatorBackend() } func (m mockedTestFramework) ReadFile(fileName string) (string, error) { @@ -2603,15 +2873,15 @@ func (m mockedTestFramework) ReadFile(fileName string) (string, error) { // mockedBlockchain is the implementation of `Blockchain` for testing purposes. type mockedBlockchain struct { runScript func(inter *interpreter.Interpreter, code string, arguments []interpreter.Value) - createAccount func() (*Account, error) - addTransaction func(inter *interpreter.Interpreter, code string, authorizers []common.Address, signers []*Account, arguments []interpreter.Value) error - executeTransaction func() *TransactionResult + createAccount func() (*stdlib.Account, error) + getAccount func(interpreter.AddressValue) (*stdlib.Account, error) + addTransaction func(inter *interpreter.Interpreter, code string, authorizers []common.Address, signers []*stdlib.Account, arguments []interpreter.Value) error + executeTransaction func() *stdlib.TransactionResult commitBlock func() error - deployContract func(inter *interpreter.Interpreter, name string, code string, account *Account, arguments []interpreter.Value) error - useConfiguration func(configuration *Configuration) - stdlibHandler func() StandardLibraryHandler + deployContract func(inter *interpreter.Interpreter, name string, path string, arguments []interpreter.Value) error + stdlibHandler func() stdlib.StandardLibraryHandler logs func() []string - serviceAccount func() (*Account, error) + serviceAccount func() (*stdlib.Account, error) events func(inter *interpreter.Interpreter, eventType interpreter.StaticType) interpreter.Value reset func(uint64) moveTime func(int64) @@ -2619,13 +2889,13 @@ type mockedBlockchain struct { loadSnapshot func(string) error } -var _ Blockchain = &mockedBlockchain{} +var _ stdlib.Blockchain = &mockedBlockchain{} func (m mockedBlockchain) RunScript( inter *interpreter.Interpreter, code string, arguments []interpreter.Value, -) *ScriptResult { +) *stdlib.ScriptResult { if m.runScript == nil { panic("'RunScript' is not implemented") } @@ -2633,7 +2903,7 @@ func (m mockedBlockchain) RunScript( return m.RunScript(inter, code, arguments) } -func (m mockedBlockchain) CreateAccount() (*Account, error) { +func (m mockedBlockchain) CreateAccount() (*stdlib.Account, error) { if m.createAccount == nil { panic("'CreateAccount' is not implemented") } @@ -2641,11 +2911,19 @@ func (m mockedBlockchain) CreateAccount() (*Account, error) { return m.createAccount() } +func (m mockedBlockchain) GetAccount(address interpreter.AddressValue) (*stdlib.Account, error) { + if m.getAccount == nil { + panic("'getAccount' is not implemented") + } + + return m.getAccount(address) +} + func (m mockedBlockchain) AddTransaction( inter *interpreter.Interpreter, code string, authorizers []common.Address, - signers []*Account, + signers []*stdlib.Account, arguments []interpreter.Value, ) error { if m.addTransaction == nil { @@ -2655,7 +2933,7 @@ func (m mockedBlockchain) AddTransaction( return m.addTransaction(inter, code, authorizers, signers, arguments) } -func (m mockedBlockchain) ExecuteNextTransaction() *TransactionResult { +func (m mockedBlockchain) ExecuteNextTransaction() *stdlib.TransactionResult { if m.executeTransaction == nil { panic("'ExecuteNextTransaction' is not implemented") } @@ -2674,26 +2952,17 @@ func (m mockedBlockchain) CommitBlock() error { func (m mockedBlockchain) DeployContract( inter *interpreter.Interpreter, name string, - code string, - account *Account, + path string, arguments []interpreter.Value, ) error { if m.deployContract == nil { panic("'DeployContract' is not implemented") } - return m.deployContract(inter, name, code, account, arguments) -} - -func (m mockedBlockchain) UseConfiguration(configuration *Configuration) { - if m.useConfiguration == nil { - panic("'UseConfiguration' is not implemented") - } - - m.useConfiguration(configuration) + return m.deployContract(inter, name, path, arguments) } -func (m mockedBlockchain) StandardLibraryHandler() StandardLibraryHandler { +func (m mockedBlockchain) StandardLibraryHandler() stdlib.StandardLibraryHandler { if m.stdlibHandler == nil { panic("'StandardLibraryHandler' is not implemented") } @@ -2709,7 +2978,7 @@ func (m mockedBlockchain) Logs() []string { return m.logs() } -func (m mockedBlockchain) ServiceAccount() (*Account, error) { +func (m mockedBlockchain) ServiceAccount() (*stdlib.Account, error) { if m.serviceAccount == nil { panic("'ServiceAccount' is not implemented") } diff --git a/runtime/storage.go b/runtime/storage.go index 6a66a9fea5..a679657406 100644 --- a/runtime/storage.go +++ b/runtime/storage.go @@ -175,6 +175,17 @@ func (s *Storage) recordContractUpdate( s.contractUpdates.Set(key, contractValue) } +func (s *Storage) contractUpdateRecorded( + location common.AddressLocation, +) bool { + if s.contractUpdates == nil { + return false + } + + key := interpreter.NewStorageKey(s.memoryGauge, location.Address, location.Name) + return s.contractUpdates.Contains(key) +} + type ContractUpdate struct { ContractValue *interpreter.CompositeValue Key interpreter.StorageKey diff --git a/runtime/tests/checker/function_test.go b/runtime/tests/checker/function_test.go index 1a2428748b..5e1facb8cb 100644 --- a/runtime/tests/checker/function_test.go +++ b/runtime/tests/checker/function_test.go @@ -24,6 +24,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/parser" "github.com/onflow/cadence/runtime/sema" ) @@ -429,20 +431,123 @@ func TestCheckNativeFunctionDeclaration(t *testing.T) { t.Parallel() - _, err := ParseAndCheckWithOptions(t, - ` - native fun test() {} - `, - ParseAndCheckOptions{ - ParseOptions: parser.Config{ - NativeModifierEnabled: true, + t.Run("disabled", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, + ` + native fun test(): Int {} + `, + ParseAndCheckOptions{ + ParseOptions: parser.Config{ + NativeModifierEnabled: true, + }, + Config: &sema.Config{ + AllowNativeDeclarations: false, + }, }, - }, - ) + ) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvalidNativeModifierError{}, errs[0]) + }) + + t.Run("enabled, valid", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, + ` + native fun test(): Int {} + `, + ParseAndCheckOptions{ + ParseOptions: parser.Config{ + NativeModifierEnabled: true, + }, + Config: &sema.Config{ + AllowNativeDeclarations: true, + }, + }, + ) + + require.NoError(t, err) + }) + + t.Run("enabled, invalid", func(t *testing.T) { - assert.IsType(t, &sema.InvalidNativeModifierError{}, errs[0]) + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, + ` + native fun test(): Int { + return 1 + } + `, + ParseAndCheckOptions{ + ParseOptions: parser.Config{ + NativeModifierEnabled: true, + }, + Config: &sema.Config{ + AllowNativeDeclarations: true, + }, + }, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.NativeFunctionWithImplementationError{}, errs[0]) + }) + + t.Run("enabled, composite", func(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheckWithOptions(t, + ` + struct S { + native fun test(foo: String): Int {} + } + `, + ParseAndCheckOptions{ + ParseOptions: parser.Config{ + NativeModifierEnabled: true, + }, + Config: &sema.Config{ + AllowNativeDeclarations: true, + }, + }, + ) + require.NoError(t, err) + + sType := RequireGlobalType(t, checker.Elaboration, "S") + require.NotNil(t, sType) + + const testFunctionIdentifier = "test" + testMemberResolver, ok := sType.GetMembers()[testFunctionIdentifier] + require.True(t, ok) + + assert.Equal(t, + common.DeclarationKindFunction, + testMemberResolver.Kind, + ) + + member := testMemberResolver.Resolve(nil, testFunctionIdentifier, ast.EmptyRange, nil) + + assert.Equal(t, + sema.NewTypeAnnotation(&sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Identifier: "foo", + TypeAnnotation: sema.NewTypeAnnotation(sema.StringType), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.IntType), + }), + member.TypeAnnotation, + ) + }) } func TestCheckResultVariable(t *testing.T) { diff --git a/runtime/tests/interpreter/account_test.go b/runtime/tests/interpreter/account_test.go index e0f83103be..142eab408d 100644 --- a/runtime/tests/interpreter/account_test.go +++ b/runtime/tests/interpreter/account_test.go @@ -116,6 +116,7 @@ type testAccountHandler struct { ) updateAccountContractCode func(location common.AddressLocation, code []byte) error recordContractUpdate func(location common.AddressLocation, value *interpreter.CompositeValue) + contractUpdateRecorded func(location common.AddressLocation) bool interpretContract func( location common.AddressLocation, program *interpreter.Program, @@ -317,6 +318,13 @@ func (t *testAccountHandler) RecordContractUpdate(location common.AddressLocatio t.recordContractUpdate(location, value) } +func (t *testAccountHandler) ContractUpdateRecorded(location common.AddressLocation) bool { + if t.contractUpdateRecorded == nil { + panic(errors.NewUnexpectedError("unexpected call to ContractUpdateRecorded")) + } + return t.contractUpdateRecorded(location) +} + func (t *testAccountHandler) InterpretContract( location common.AddressLocation, program *interpreter.Program, diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 2f5d274dfd..4c83de0a4c 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -71,6 +71,65 @@ func parseCheckAndInterpretWithOptions( return parseCheckAndInterpretWithOptionsAndMemoryMetering(t, code, options, nil) } +func parseCheckAndInterpretWithLogs( + tb testing.TB, + code string, +) ( + inter *interpreter.Interpreter, + getLogs func() []string, + err error, +) { + var logs []string + + logFunction := stdlib.NewStandardLibraryFunction( + "log", + &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "value", + TypeAnnotation: sema.NewTypeAnnotation(sema.AnyStructType), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + sema.VoidType, + ), + }, + ``, + func(invocation interpreter.Invocation) interpreter.Value { + message := invocation.Arguments[0].String() + logs = append(logs, message) + return interpreter.Void + }, + ) + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(logFunction) + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, logFunction) + + result, err := parseCheckAndInterpretWithOptions( + tb, + code, + ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + BaseActivation: baseActivation, + }, + CheckerConfig: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + HandleCheckerError: nil, + }, + ) + + getLogs = func() []string { + return logs + } + + return result, getLogs, err +} + func parseCheckAndInterpretWithMemoryMetering( t testing.TB, code string, @@ -9520,88 +9579,49 @@ func TestInterpretNestedDestroy(t *testing.T) { t.Parallel() - var logs []string - - logFunction := stdlib.NewStandardLibraryFunction( - "log", - &sema.FunctionType{ - Parameters: []sema.Parameter{ - { - Label: sema.ArgumentLabelNotRequired, - Identifier: "value", - TypeAnnotation: sema.AnyStructTypeAnnotation, - }, - }, - ReturnTypeAnnotation: sema.VoidTypeAnnotation, - }, - ``, - func(invocation interpreter.Invocation) interpreter.Value { - message := invocation.Arguments[0].String() - logs = append(logs, message) - return interpreter.Void - }, - ) - - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(logFunction) - - baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) - interpreter.Declare(baseActivation, logFunction) - - inter, err := parseCheckAndInterpretWithOptions(t, - ` - resource B { - let id: Int + inter, getLogs, err := parseCheckAndInterpretWithLogs(t, ` + resource B { + let id: Int - init(_ id: Int){ - self.id = id - } + init(_ id: Int){ + self.id = id + } - destroy(){ - log("destroying B with id:") - log(self.id) - } + destroy(){ + log("destroying B with id:") + log(self.id) } + } - resource A { - let id: Int - let bs: @[B] + resource A { + let id: Int + let bs: @[B] - init(_ id: Int){ - self.id = id - self.bs <- [] - } + init(_ id: Int){ + self.id = id + self.bs <- [] + } - fun add(_ b: @B){ - self.bs.append(<-b) - } + fun add(_ b: @B){ + self.bs.append(<-b) + } - destroy() { - log("destroying A with id:") - log(self.id) - destroy self.bs - } + destroy() { + log("destroying A with id:") + log(self.id) + destroy self.bs } + } - fun test() { - let a <- create A(1) - a.add(<- create B(2)) - a.add(<- create B(3)) - a.add(<- create B(4)) + fun test() { + let a <- create A(1) + a.add(<- create B(2)) + a.add(<- create B(3)) + a.add(<- create B(4)) - destroy a - } - `, - ParseCheckAndInterpretOptions{ - Config: &interpreter.Config{ - BaseActivation: baseActivation, - }, - CheckerConfig: &sema.Config{ - BaseValueActivation: baseValueActivation, - }, - HandleCheckerError: nil, - }, - ) + destroy a + } + `) require.NoError(t, err) value, err := inter.Invoke("test") @@ -9625,7 +9645,7 @@ func TestInterpretNestedDestroy(t *testing.T) { `"destroying B with id:"`, "4", }, - logs, + getLogs(), ) } @@ -11521,3 +11541,314 @@ func TestInterpretConditionsWrapperFunctionType(t *testing.T) { require.NoError(t, err) }) } + +func TestInterpretSwapInSameArray(t *testing.T) { + + t.Parallel() + + t.Run("resources, different indices", func(t *testing.T) { + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + resource R { + let value: Int + + init(value: Int) { + self.value = value + } + } + + fun test(): [Int] { + let rs <- [ + <- create R(value: 0), + <- create R(value: 1), + <- create R(value: 2) + ] + + // We swap only '0' and '1' + rs[0] <-> rs[1] + + let values = [ + rs[0].value, + rs[1].value, + rs[2].value + ] + + destroy rs + + return values + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(1), + interpreter.NewUnmeteredIntValueFromInt64(0), + interpreter.NewUnmeteredIntValueFromInt64(2), + ), + value, + ) + }) + + t.Run("resources, same indices", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + resource R { + let value: Int + + init(value: Int) { + self.value = value + } + } + + fun test(): [Int] { + let rs <- [ + <- create R(value: 0), + <- create R(value: 1), + <- create R(value: 2) + ] + + // We swap only '1' + rs[1] <-> rs[1] + + let values = [ + rs[0].value, + rs[1].value, + rs[2].value + ] + + destroy rs + + return values + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(0), + interpreter.NewUnmeteredIntValueFromInt64(1), + interpreter.NewUnmeteredIntValueFromInt64(2), + ), + value, + ) + }) + + t.Run("structs", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + struct S { + let value: Int + + init(value: Int) { + self.value = value + } + } + + fun test(): [Int] { + let structs = [ + S(value: 0), + S(value: 1), + S(value: 2) + ] + + // We swap only '0' and '1' + structs[0] <-> structs[1] + + return [ + structs[0].value, + structs[1].value, + structs[2].value + ] + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(1), + interpreter.NewUnmeteredIntValueFromInt64(0), + interpreter.NewUnmeteredIntValueFromInt64(2), + ), + value, + ) + }) +} + +func TestInterpretSwapDictionaryKeysWithSideEffects(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + t.Parallel() + + inter, getLogs, err := parseCheckAndInterpretWithLogs(t, ` + let xs: [{Int: String}] = [{2: "x"}, {3: "y"}] + + fun a(): Int { + log("a") + return 0 + } + + fun b(): Int { + log("b") + return 2 + } + + fun c(): Int { + log("c") + return 1 + } + + fun d(): Int { + log("d") + return 3 + } + + fun test() { + log(xs) + xs[a()][b()] <-> xs[c()][d()] + log(xs) + } + `) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.Equal(t, + []string{ + `[{2: "x"}, {3: "y"}]`, + `"a"`, + `"b"`, + `"c"`, + `"d"`, + `[{2: "y"}, {3: "x"}]`, + }, + getLogs(), + ) + + }) + + t.Run("resources", func(t *testing.T) { + t.Parallel() + + inter, getLogs, err := parseCheckAndInterpretWithLogs(t, ` + resource Resource { + var value: Int + + init(_ value: Int) { + log( + "Creating resource with UUID " + .concat(self.uuid.toString()) + .concat(" and value ") + .concat(value.toString()) + ) + self.value = value + } + + destroy() { + log( + "Destroying resource with UUID " + .concat(self.uuid.toString()) + .concat(" and value ") + .concat(self.value.toString()) + ) + } + } + + resource ResourceLoser { + var dict: @{Int: Resource} + var toBeLost: @Resource + + init(_ victim: @Resource) { + self.dict <- {1: <- create Resource(2)} + + self.toBeLost <- victim + + // Magic happens during the swap below. + self.dict[1] <-> self.dict[self.shenanigans()] + } + + fun shenanigans(): Int { + var d <- create Resource(3) + + self.toBeLost <-> d + + // This takes advantage of the fact that self.dict[1] has been + // temporarily removed at the point of the swap when this gets called + // We take advantage of this window of opportunity to + // insert the "to-be-lost" resource in its place. The swap implementation + // will blindly overwrite it. + var old <- self.dict.insert(key: 1, <- d) + + // "old" will be nil here thanks to the removal done by the swap + // implementation. We have to destroy it to please sema checker. + destroy old + + return 1 + } + + destroy() { + destroy self.dict + destroy self.toBeLost + } + } + + fun test() { + destroy <- create ResourceLoser(<- create Resource(1)) + } + `) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.Equal(t, + []string{ + `"Creating resource with UUID 1 and value 1"`, + `"Creating resource with UUID 3 and value 2"`, + `"Creating resource with UUID 4 and value 3"`, + `"Destroying resource with UUID 3 and value 2"`, + `"Destroying resource with UUID 1 and value 1"`, + `"Destroying resource with UUID 4 and value 3"`, + }, + getLogs(), + ) + }) +}