diff --git a/runtime/environment.go b/runtime/environment.go index 561dd448f0..e02f26024d 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -168,23 +168,22 @@ func newInterpreterEnvironment(config Config) *interpreterEnvironment { func (e *interpreterEnvironment) newInterpreterConfig() *interpreter.Config { return &interpreter.Config{ - InvalidatedResourceValidationEnabled: true, - MemoryGauge: e, - BaseActivationHandler: e.getBaseActivation, - OnEventEmitted: e.newOnEventEmittedHandler(), - OnAccountLinked: e.newOnAccountLinkedHandler(), - InjectedCompositeFieldsHandler: e.newInjectedCompositeFieldsHandler(), - UUIDHandler: e.newUUIDHandler(), - ContractValueHandler: e.newContractValueHandler(), - ImportLocationHandler: e.newImportLocationHandler(), - PublicAccountHandler: e.newPublicAccountHandler(), - AuthAccountHandler: e.newAuthAccountHandler(), - OnRecordTrace: e.newOnRecordTraceHandler(), - OnResourceOwnerChange: e.newResourceOwnerChangedHandler(), - CompositeTypeHandler: e.newCompositeTypeHandler(), - CompositeValueFunctionsHandler: e.newCompositeValueFunctionsHandler(), - TracingEnabled: e.config.TracingEnabled, - AtreeValueValidationEnabled: e.config.AtreeValidationEnabled, + MemoryGauge: e, + BaseActivationHandler: e.getBaseActivation, + OnEventEmitted: e.newOnEventEmittedHandler(), + OnAccountLinked: e.newOnAccountLinkedHandler(), + InjectedCompositeFieldsHandler: e.newInjectedCompositeFieldsHandler(), + UUIDHandler: e.newUUIDHandler(), + ContractValueHandler: e.newContractValueHandler(), + ImportLocationHandler: e.newImportLocationHandler(), + PublicAccountHandler: e.newPublicAccountHandler(), + AuthAccountHandler: e.newAuthAccountHandler(), + OnRecordTrace: e.newOnRecordTraceHandler(), + OnResourceOwnerChange: e.newResourceOwnerChangedHandler(), + CompositeTypeHandler: e.newCompositeTypeHandler(), + CompositeValueFunctionsHandler: e.newCompositeValueFunctionsHandler(), + TracingEnabled: e.config.TracingEnabled, + AtreeValueValidationEnabled: e.config.AtreeValidationEnabled, // NOTE: ignore e.config.AtreeValidationEnabled here, // and disable storage validation after each value modification. // Instead, storage is validated after commits (if validation is enabled), diff --git a/runtime/interpreter/config.go b/runtime/interpreter/config.go index 90bd3c8981..f2535dbe18 100644 --- a/runtime/interpreter/config.go +++ b/runtime/interpreter/config.go @@ -59,8 +59,6 @@ type Config struct { OnStatement OnStatementFunc // OnLoopIteration is triggered when a loop iteration is about to be executed OnLoopIteration OnLoopIterationFunc - // InvalidatedResourceValidationEnabled determines if the validation of invalidated resources is enabled - InvalidatedResourceValidationEnabled bool // TracingEnabled determines if tracing is enabled. // Tracing reports certain operations, e.g. composite value transfers TracingEnabled bool diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 0d2b05b65c..dfbbfbc6ce 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -246,7 +246,7 @@ type Storage interface { CheckHealth() error } -type ReferencedResourceKindedValues map[atree.StorageID]map[ReferenceTrackedResourceKindedValue]struct{} +type ReferencedResourceKindedValues map[atree.StorageID]map[*EphemeralReferenceValue]struct{} type Interpreter struct { Location common.Location @@ -838,14 +838,12 @@ func (interpreter *Interpreter) resultValue(returnValue Value, returnType sema.T optionalType.Type, ) - interpreter.maybeTrackReferencedResourceKindedValue(returnValue.value) return NewSomeValueNonCopying(interpreter, innerValue) case NilValue: return NilValue{} } } - interpreter.maybeTrackReferencedResourceKindedValue(returnValue) return NewEphemeralReferenceValue(interpreter, false, returnValue, returnType) } @@ -5260,18 +5258,20 @@ func (interpreter *Interpreter) ValidateAtreeValue(value atree.Value) { } func (interpreter *Interpreter) maybeTrackReferencedResourceKindedValue(value Value) { - if value, ok := value.(ReferenceTrackedResourceKindedValue); ok { - interpreter.trackReferencedResourceKindedValue(value.StorageID(), value) + if referenceValue, ok := value.(*EphemeralReferenceValue); ok { + if value, ok := referenceValue.Value.(ReferenceTrackedResourceKindedValue); ok { + interpreter.trackReferencedResourceKindedValue(value.StorageID(), referenceValue) + } } } func (interpreter *Interpreter) trackReferencedResourceKindedValue( id atree.StorageID, - value ReferenceTrackedResourceKindedValue, + value *EphemeralReferenceValue, ) { values := interpreter.SharedState.referencedResourceKindedValues[id] if values == nil { - values = map[ReferenceTrackedResourceKindedValue]struct{}{} + values = map[*EphemeralReferenceValue]struct{}{} interpreter.SharedState.referencedResourceKindedValues[id] = values } values[value] = struct{}{} @@ -5280,7 +5280,7 @@ func (interpreter *Interpreter) trackReferencedResourceKindedValue( func (interpreter *Interpreter) updateReferencedResource( currentStorageID atree.StorageID, newStorageID atree.StorageID, - updateFunc func(value ReferenceTrackedResourceKindedValue), + updateFunc func(value *EphemeralReferenceValue), ) { values := interpreter.SharedState.referencedResourceKindedValues[currentStorageID] if values == nil { @@ -5304,10 +5304,7 @@ func (interpreter *Interpreter) startResourceTracking( hasPosition ast.HasPosition, ) { - config := interpreter.SharedState.Config - - if !config.InvalidatedResourceValidationEnabled || - identifier == sema.SelfIdentifier { + if identifier == sema.SelfIdentifier { return } @@ -5339,10 +5336,8 @@ func (interpreter *Interpreter) checkInvalidatedResourceUse( identifier string, hasPosition ast.HasPosition, ) { - config := interpreter.SharedState.Config - if !config.InvalidatedResourceValidationEnabled || - identifier == sema.SelfIdentifier { + if identifier == sema.SelfIdentifier { return } @@ -5385,12 +5380,6 @@ func (interpreter *Interpreter) resourceForValidation(value Value) ResourceKinde } func (interpreter *Interpreter) invalidateResource(value Value) { - config := interpreter.SharedState.Config - - if !config.InvalidatedResourceValidationEnabled { - return - } - if value == nil || !value.IsResourceKinded(interpreter) { return } diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index e5b5b0cbb3..387e54b63b 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -303,7 +303,19 @@ func (interpreter *Interpreter) VisitIdentifierExpression(expression *ast.Identi } func (interpreter *Interpreter) evalExpression(expression ast.Expression) Value { - return ast.AcceptExpression[Value](expression, interpreter) + result := ast.AcceptExpression[Value](expression, interpreter) + + resourceKindedValue, ok := result.(ResourceKindedValue) + if ok && resourceKindedValue.isInvalidatedResource(interpreter) { + panic(InvalidatedResourceError{ + LocationRange: LocationRange{ + Location: interpreter.Location, + HasPosition: expression, + }, + }) + } + + return result } func (interpreter *Interpreter) VisitBinaryExpression(expression *ast.BinaryExpression) Value { @@ -960,15 +972,6 @@ func (interpreter *Interpreter) visitInvocationExpressionWithImplicitArgument(in panic(errors.NewUnreachableError()) } - // Bound functions - if boundFunction, ok := function.(BoundFunctionValue); ok && boundFunction.Self != nil { - self := *boundFunction.Self - if resource, ok := self.(ReferenceTrackedResourceKindedValue); ok { - storageID := resource.StorageID() - interpreter.trackReferencedResourceKindedValue(storageID, resource) - } - } - // NOTE: evaluate all argument expressions in call-site scope, not in function body var argumentExpressions []ast.Expression @@ -1183,8 +1186,6 @@ func (interpreter *Interpreter) VisitReferenceExpression(referenceExpression *as result := interpreter.evalExpression(referenceExpression.Expression) - interpreter.maybeTrackReferencedResourceKindedValue(result) - // There are four potential cases: // 1) Target type is optional, actual value is also optional (nil/SomeValue) // 2) Target type is optional, actual value is non-optional @@ -1211,7 +1212,6 @@ func (interpreter *Interpreter) VisitReferenceExpression(referenceExpression *as } innerValue := result.InnerValue(interpreter, locationRange) - interpreter.maybeTrackReferencedResourceKindedValue(innerValue) return NewSomeValueNonCopying( interpreter, @@ -1251,14 +1251,15 @@ func (interpreter *Interpreter) VisitReferenceExpression(referenceExpression *as case *sema.ReferenceType: // Case (3): target type is non-optional, actual value is optional. - // Unwrap the optional and add it to reference tracking. + // This path shouldn't be reachable. This is only a defensive step + // to ensure references are properly created/tracked. if someValue, ok := result.(*SomeValue); ok { locationRange := LocationRange{ Location: interpreter.Location, HasPosition: referenceExpression.Expression, } innerValue := someValue.InnerValue(interpreter, locationRange) - interpreter.maybeTrackReferencedResourceKindedValue(innerValue) + return NewEphemeralReferenceValue(interpreter, typ.Authorized, innerValue, typ.Type) } // Case (4): target type is non-optional, actual value is also non-optional @@ -1341,7 +1342,6 @@ func (interpreter *Interpreter) VisitAttachExpression(attachExpression *ast.Atta base, interpreter.MustSemaTypeOfValue(base).(*sema.CompositeType), ) - interpreter.trackReferencedResourceKindedValue(base.StorageID(), base) attachment, ok := interpreter.visitInvocationExpressionWithImplicitArgument( attachExpression.Attachment, @@ -1352,9 +1352,6 @@ func (interpreter *Interpreter) VisitAttachExpression(attachExpression *ast.Atta panic(errors.NewUnreachableError()) } - // Because `self` in attachments is a reference, we need to track the attachment if it's a resource - interpreter.trackReferencedResourceKindedValue(attachment.StorageID(), attachment) - base = base.Transfer( interpreter, locationRange, diff --git a/runtime/interpreter/sharedstate.go b/runtime/interpreter/sharedstate.go index f250ce3f58..71fc3b6674 100644 --- a/runtime/interpreter/sharedstate.go +++ b/runtime/interpreter/sharedstate.go @@ -59,7 +59,7 @@ func NewSharedState(config *Config) *SharedState { }, inStorageIteration: false, storageMutatedDuringIteration: false, - referencedResourceKindedValues: map[atree.StorageID]map[ReferenceTrackedResourceKindedValue]struct{}{}, + referencedResourceKindedValues: map[atree.StorageID]map[*EphemeralReferenceValue]struct{}{}, resourceVariables: map[ResourceKindedValue]*Variable{}, CapabilityControllerIterations: map[AddressPath]int{}, containerValueIteration: map[atree.StorageID]struct{}{}, diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 31f0e104bf..f542c620f2 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -202,6 +202,7 @@ type ResourceKindedValue interface { Value Destroy(interpreter *Interpreter, locationRange LocationRange) IsDestroyed() bool + isInvalidatedResource(*Interpreter) bool } func maybeDestroy(interpreter *Interpreter, locationRange LocationRange, value Value) { @@ -1771,12 +1772,8 @@ func (v *ArrayValue) IsImportable(inter *Interpreter) bool { return importable } -func (v *ArrayValue) checkInvalidatedResourceUse(interpreter *Interpreter, locationRange LocationRange) { - if v.isDestroyed || (v.array == nil && v.IsResourceKinded(interpreter)) { - panic(InvalidatedResourceError{ - LocationRange: locationRange, - }) - } +func (v *ArrayValue) isInvalidatedResource(interpreter *Interpreter) bool { + return v.isDestroyed || (v.array == nil && v.IsResourceKinded(interpreter)) } func (v *ArrayValue) Destroy(interpreter *Interpreter, locationRange LocationRange) { @@ -1785,10 +1782,6 @@ func (v *ArrayValue) Destroy(interpreter *Interpreter, locationRange LocationRan config := interpreter.SharedState.Config - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - if config.TracingEnabled { startTime := time.Now() @@ -1817,25 +1810,19 @@ func (v *ArrayValue) Destroy(interpreter *Interpreter, locationRange LocationRan ) v.isDestroyed = true - - if config.InvalidatedResourceValidationEnabled { - v.array = nil - } + v.array = nil interpreter.updateReferencedResource( storageID, storageID, - func(value ReferenceTrackedResourceKindedValue) { - arrayValue, ok := value.(*ArrayValue) + func(value *EphemeralReferenceValue) { + arrayValue, ok := value.Value.(*ArrayValue) if !ok { panic(errors.NewUnreachableError()) } arrayValue.isDestroyed = true - - if config.InvalidatedResourceValidationEnabled { - arrayValue.array = nil - } + arrayValue.array = nil }, ) } @@ -1912,12 +1899,6 @@ func (v *ArrayValue) Concat(interpreter *Interpreter, locationRange LocationRang } func (v *ArrayValue) GetKey(interpreter *Interpreter, locationRange LocationRange, key Value) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - index := key.(NumberValue).ToInt(locationRange) return v.Get(interpreter, locationRange, index) } @@ -1957,12 +1938,6 @@ func (v *ArrayValue) Get(interpreter *Interpreter, locationRange LocationRange, } func (v *ArrayValue) SetKey(interpreter *Interpreter, locationRange LocationRange, key Value, value Value) { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - index := key.(NumberValue).ToInt(locationRange) v.Set(interpreter, locationRange, index, value) } @@ -2085,12 +2060,6 @@ func (v *ArrayValue) AppendAll(interpreter *Interpreter, locationRange LocationR } func (v *ArrayValue) InsertKey(interpreter *Interpreter, locationRange LocationRange, key Value, value Value) { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - index := key.(NumberValue).ToInt(locationRange) v.Insert(interpreter, locationRange, index, value) } @@ -2143,12 +2112,6 @@ func (v *ArrayValue) Insert(interpreter *Interpreter, locationRange LocationRang } func (v *ArrayValue) RemoveKey(interpreter *Interpreter, locationRange LocationRange, key Value) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - index := key.(NumberValue).ToInt(locationRange) return v.Remove(interpreter, locationRange, index) } @@ -2249,11 +2212,6 @@ func (v *ArrayValue) Contains( } func (v *ArrayValue) GetMember(interpreter *Interpreter, locationRange LocationRange, name string) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } switch name { case "length": return NewIntValueFromInt64(interpreter, int64(v.Count())) @@ -2514,23 +2472,11 @@ func (v *ArrayValue) GetMember(interpreter *Interpreter, locationRange LocationR } func (v *ArrayValue) RemoveMember(interpreter *Interpreter, locationRange LocationRange, _ string) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - // Arrays have no removable members (fields / functions) panic(errors.NewUnreachableError()) } func (v *ArrayValue) SetMember(interpreter *Interpreter, locationRange LocationRange, _ string, _ Value) bool { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - // Arrays have no settable members (fields / functions) panic(errors.NewUnreachableError()) } @@ -2663,10 +2609,6 @@ func (v *ArrayValue) Transfer( config := interpreter.SharedState.Config - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - interpreter.ReportComputation(common.ComputationKindTransferArrayValue, uint(v.Count())) if config.TracingEnabled { @@ -2744,7 +2686,11 @@ func (v *ArrayValue) Transfer( } } - var res *ArrayValue + res := newArrayValueFromAtreeValue(array, v.Type) + res.elementSize = v.elementSize + res.semaType = v.semaType + res.isResourceKinded = v.isResourceKinded + res.isDestroyed = v.isDestroyed if isResourceKinded { // Update the resource in-place, @@ -2756,36 +2702,19 @@ func (v *ArrayValue) Transfer( // This allows raising an error when the resource array is attempted // to be transferred/moved again (see beginning of this function) - if config.InvalidatedResourceValidationEnabled { - v.array = nil - } else { - v.array = array - res = v - } + v.array = nil newStorageID := array.StorageID() interpreter.updateReferencedResource( currentStorageID, newStorageID, - func(value ReferenceTrackedResourceKindedValue) { - arrayValue, ok := value.(*ArrayValue) - if !ok { - panic(errors.NewUnreachableError()) - } - arrayValue.array = array + func(value *EphemeralReferenceValue) { + value.Value = res }, ) } - if res == nil { - res = newArrayValueFromAtreeValue(array, v.Type) - res.elementSize = v.elementSize - res.semaType = v.semaType - res.isResourceKinded = v.isResourceKinded - res.isDestroyed = v.isDestroyed - } - return res } @@ -16471,10 +16400,6 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio config := interpreter.SharedState.Config - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - if config.TracingEnabled { startTime := time.Now() @@ -16542,25 +16467,19 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio ) v.isDestroyed = true - - if config.InvalidatedResourceValidationEnabled { - v.dictionary = nil - } + v.dictionary = nil interpreter.updateReferencedResource( storageID, storageID, - func(value ReferenceTrackedResourceKindedValue) { - compositeValue, ok := value.(*CompositeValue) + func(value *EphemeralReferenceValue) { + compositeValue, ok := value.Value.(*CompositeValue) if !ok { panic(errors.NewUnreachableError()) } compositeValue.isDestroyed = true - - if config.InvalidatedResourceValidationEnabled { - compositeValue.dictionary = nil - } + compositeValue.dictionary = nil }, ) @@ -16581,10 +16500,6 @@ func (v *CompositeValue) getBuiltinMember(interpreter *Interpreter, locationRang func (v *CompositeValue) GetMember(interpreter *Interpreter, locationRange LocationRange, name string) Value { config := interpreter.SharedState.Config - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - if config.TracingEnabled { startTime := time.Now() @@ -16644,12 +16559,8 @@ func (v *CompositeValue) GetMember(interpreter *Interpreter, locationRange Locat return nil } -func (v *CompositeValue) checkInvalidatedResourceUse(locationRange LocationRange) { - if v.isDestroyed || (v.dictionary == nil && v.Kind == common.CompositeKindResource) { - panic(InvalidatedResourceError{ - LocationRange: locationRange, - }) - } +func (v *CompositeValue) isInvalidatedResource(_ *Interpreter) bool { + return v.isDestroyed || (v.dictionary == nil && v.Kind == common.CompositeKindResource) } func (v *CompositeValue) getInterpreter(interpreter *Interpreter) *Interpreter { @@ -16740,10 +16651,6 @@ func (v *CompositeValue) RemoveMember( config := interpreter.SharedState.Config - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - if config.TracingEnabled { startTime := time.Now() @@ -16807,10 +16714,6 @@ func (v *CompositeValue) SetMember( ) bool { config := interpreter.SharedState.Config - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - interpreter.enforceNotResourceDestruction(v.StorageID(), locationRange) if config.TracingEnabled { @@ -16943,12 +16846,6 @@ func formatComposite(memoryGauge common.MemoryGauge, typeId string, fields []Com } func (v *CompositeValue) GetField(interpreter *Interpreter, locationRange LocationRange, name string) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - storable, err := v.dictionary.Get( StringAtreeValueComparator, StringAtreeValueHashInput, @@ -17196,11 +17093,6 @@ func (v *CompositeValue) Transfer( config := interpreter.SharedState.Config - // Should be checked before accessing `v.dictionary`. - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - baseUse, elementOverhead, dataUse, metaDataUse := common.NewCompositeMemoryUsages(v.dictionary.Count(), 0) common.UseMemory(interpreter, baseUse) common.UseMemory(interpreter, elementOverhead) @@ -17319,7 +17211,24 @@ func (v *CompositeValue) Transfer( } } - var res *CompositeValue + info := NewCompositeTypeInfo( + interpreter, + v.Location, + v.QualifiedIdentifier, + v.Kind, + ) + + res := newCompositeValueFromOrderedMap(dictionary, info) + res.injectedFields = v.injectedFields + res.computedFields = v.computedFields + res.NestedVariables = v.NestedVariables + res.Functions = v.Functions + res.Destructor = v.Destructor + res.Stringer = v.Stringer + res.isDestroyed = v.isDestroyed + res.typeID = v.typeID + res.staticType = v.staticType + res.base = v.base if isResourceKinded { // Update the resource in-place, @@ -17331,48 +17240,19 @@ func (v *CompositeValue) Transfer( // This allows raising an error when the resource is attempted // to be transferred/moved again (see beginning of this function) - if config.InvalidatedResourceValidationEnabled { - v.dictionary = nil - } else { - v.dictionary = dictionary - res = v - } + v.dictionary = nil newStorageID := dictionary.StorageID() interpreter.updateReferencedResource( currentStorageID, newStorageID, - func(value ReferenceTrackedResourceKindedValue) { - compositeValue, ok := value.(*CompositeValue) - if !ok { - panic(errors.NewUnreachableError()) - } - compositeValue.dictionary = dictionary + func(value *EphemeralReferenceValue) { + value.Value = res }, ) } - if res == nil { - info := NewCompositeTypeInfo( - interpreter, - v.Location, - v.QualifiedIdentifier, - v.Kind, - ) - res = newCompositeValueFromOrderedMap(dictionary, info) - res.injectedFields = v.injectedFields - res.computedFields = v.computedFields - res.NestedVariables = v.NestedVariables - res.Functions = v.Functions - res.Destructor = v.Destructor - res.Stringer = v.Stringer - res.isDestroyed = v.isDestroyed - res.typeID = v.typeID - res.staticType = v.staticType - res.base = v.base - } - onResourceOwnerChange := config.OnResourceOwnerChange if needsStoreTo && @@ -17616,8 +17496,6 @@ func (v *CompositeValue) setBaseValue(interpreter *Interpreter, base *CompositeV // the base reference can only be borrowed with the declared type of the attachment's base v.base = NewEphemeralReferenceValue(interpreter, false, base, baseType) - - interpreter.trackReferencedResourceKindedValue(base.StorageID(), base) } func attachmentMemberName(ty sema.Type) string { @@ -17646,7 +17524,6 @@ func attachmentBaseAndSelfValues( base = v.getBaseValue() // in attachment functions, self is a reference value self = NewEphemeralReferenceValue(interpreter, false, v, interpreter.MustSemaTypeOfValue(v)) - interpreter.trackReferencedResourceKindedValue(v.StorageID(), v) return } @@ -17697,7 +17574,6 @@ func (v *CompositeValue) GetTypeKey( attachment.setBaseValue(interpreter, v) attachmentRef := NewEphemeralReferenceValue(interpreter, false, attachment, ty) - interpreter.trackReferencedResourceKindedValue(attachment.StorageID(), attachment) return NewSomeValueNonCopying(interpreter, attachmentRef) } @@ -17971,12 +17847,8 @@ func (v *DictionaryValue) IsDestroyed() bool { return v.isDestroyed } -func (v *DictionaryValue) checkInvalidatedResourceUse(interpreter *Interpreter, locationRange LocationRange) { - if v.isDestroyed || (v.dictionary == nil && v.IsResourceKinded(interpreter)) { - panic(InvalidatedResourceError{ - LocationRange: locationRange, - }) - } +func (v *DictionaryValue) isInvalidatedResource(interpreter *Interpreter) bool { + return v.isDestroyed || (v.dictionary == nil && v.IsResourceKinded(interpreter)) } func (v *DictionaryValue) Destroy(interpreter *Interpreter, locationRange LocationRange) { @@ -17985,10 +17857,6 @@ func (v *DictionaryValue) Destroy(interpreter *Interpreter, locationRange Locati config := interpreter.SharedState.Config - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - if config.TracingEnabled { startTime := time.Now() @@ -18022,24 +17890,17 @@ func (v *DictionaryValue) Destroy(interpreter *Interpreter, locationRange Locati v.isDestroyed = true - if config.InvalidatedResourceValidationEnabled { - v.dictionary = nil - } - interpreter.updateReferencedResource( storageID, storageID, - func(value ReferenceTrackedResourceKindedValue) { - dictionaryValue, ok := value.(*DictionaryValue) + func(value *EphemeralReferenceValue) { + dictionaryValue, ok := value.Value.(*DictionaryValue) if !ok { panic(errors.NewUnreachableError()) } dictionaryValue.isDestroyed = true - - if config.InvalidatedResourceValidationEnabled { - dictionaryValue.dictionary = nil - } + dictionaryValue.dictionary = nil }, ) } @@ -18137,12 +17998,6 @@ func (v *DictionaryValue) Get( } func (v *DictionaryValue) GetKey(interpreter *Interpreter, locationRange LocationRange, keyValue Value) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - value, ok := v.Get(interpreter, locationRange, keyValue) if ok { return NewSomeValueNonCopying(interpreter, value) @@ -18159,12 +18014,6 @@ func (v *DictionaryValue) SetKey( ) { interpreter.validateMutation(v.StorageID(), locationRange) - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - interpreter.checkContainerMutation(v.Type.KeyType, keyValue, locationRange) interpreter.checkContainerMutation( OptionalStaticType{ // intentionally unmetered @@ -18243,10 +18092,6 @@ func (v *DictionaryValue) GetMember( ) Value { config := interpreter.SharedState.Config - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - if config.TracingEnabled { startTime := time.Now() @@ -18412,24 +18257,12 @@ func (v *DictionaryValue) GetMember( return nil } -func (v *DictionaryValue) RemoveMember(interpreter *Interpreter, locationRange LocationRange, _ string) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - +func (v *DictionaryValue) RemoveMember(_ *Interpreter, _ LocationRange, _ string) Value { // Dictionaries have no removable members (fields / functions) panic(errors.NewUnreachableError()) } -func (v *DictionaryValue) SetMember(interpreter *Interpreter, locationRange LocationRange, _ string, _ Value) bool { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - +func (v *DictionaryValue) SetMember(_ *Interpreter, _ LocationRange, _ string, _ Value) bool { // Dictionaries have no settable members (fields / functions) panic(errors.NewUnreachableError()) } @@ -18443,12 +18276,6 @@ func (v *DictionaryValue) RemoveKey( locationRange LocationRange, key Value, ) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - return v.Remove(interpreter, locationRange, key) } @@ -18758,10 +18585,6 @@ func (v *DictionaryValue) Transfer( config := interpreter.SharedState.Config - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(interpreter, locationRange) - } - if config.TracingEnabled { startTime := time.Now() @@ -18852,7 +18675,11 @@ func (v *DictionaryValue) Transfer( } } - var res *DictionaryValue + res := newDictionaryValueFromOrderedMap(dictionary, v.Type) + res.elementSize = v.elementSize + res.semaType = v.semaType + res.isResourceKinded = v.isResourceKinded + res.isDestroyed = v.isDestroyed if isResourceKinded { // Update the resource in-place, @@ -18864,36 +18691,19 @@ func (v *DictionaryValue) Transfer( // This allows raising an error when the resource array is attempted // to be transferred/moved again (see beginning of this function) - if config.InvalidatedResourceValidationEnabled { - v.dictionary = nil - } else { - v.dictionary = dictionary - res = v - } + v.dictionary = nil newStorageID := dictionary.StorageID() interpreter.updateReferencedResource( currentStorageID, newStorageID, - func(value ReferenceTrackedResourceKindedValue) { - dictionaryValue, ok := value.(*DictionaryValue) - if !ok { - panic(errors.NewUnreachableError()) - } - dictionaryValue.dictionary = dictionary + func(value *EphemeralReferenceValue) { + value.Value = res }, ) } - if res == nil { - res = newDictionaryValueFromOrderedMap(dictionary, v.Type) - res.elementSize = v.elementSize - res.semaType = v.semaType - res.isResourceKinded = v.isResourceKinded - res.isDestroyed = v.isDestroyed - } - return res } @@ -19190,6 +19000,10 @@ func (NilValue) ChildStorables() []atree.Storable { return nil } +func (NilValue) isInvalidatedResource(_ *Interpreter) bool { + return false +} + // SomeValue type SomeValue struct { @@ -19265,20 +19079,10 @@ func (v *SomeValue) IsDestroyed() bool { } func (v *SomeValue) Destroy(interpreter *Interpreter, locationRange LocationRange) { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - innerValue := v.InnerValue(interpreter, locationRange) maybeDestroy(interpreter, locationRange, innerValue) v.isDestroyed = true - - if config.InvalidatedResourceValidationEnabled { - v.value = nil - } } func (v *SomeValue) String() string { @@ -19294,11 +19098,6 @@ func (v *SomeValue) MeteredString(memoryGauge common.MemoryGauge, seenReferences } func (v *SomeValue) GetMember(interpreter *Interpreter, locationRange LocationRange, name string) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } switch name { case sema.OptionalTypeMapFunctionName: return NewHostFunctionValue( @@ -19344,22 +19143,10 @@ func (v *SomeValue) GetMember(interpreter *Interpreter, locationRange LocationRa } func (v *SomeValue) RemoveMember(interpreter *Interpreter, locationRange LocationRange, _ string) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - panic(errors.NewUnreachableError()) } func (v *SomeValue) SetMember(interpreter *Interpreter, locationRange LocationRange, _ string, _ Value) bool { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - panic(errors.NewUnreachableError()) } @@ -19439,14 +19226,6 @@ func (v *SomeValue) IsResourceKinded(interpreter *Interpreter) bool { return v.value.IsResourceKinded(interpreter) } -func (v *SomeValue) checkInvalidatedResourceUse(locationRange LocationRange) { - if v.isDestroyed || v.value == nil { - panic(InvalidatedResourceError{ - LocationRange: locationRange, - }) - } -} - func (v *SomeValue) Transfer( interpreter *Interpreter, locationRange LocationRange, @@ -19455,12 +19234,6 @@ func (v *SomeValue) Transfer( storable atree.Storable, preventTransfer map[atree.StorageID]struct{}, ) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - innerValue := v.value needsStoreTo := v.NeedsStoreTo(address) @@ -19494,15 +19267,7 @@ func (v *SomeValue) Transfer( // then mark the resource array as invalidated, by unsetting the backing array. // This allows raising an error when the resource array is attempted // to be transferred/moved again (see beginning of this function) - - if config.InvalidatedResourceValidationEnabled { - v.value = nil - } else { - v.value = innerValue - v.valueStorable = nil - res = v - } - + v.value = nil } if res == nil { @@ -19526,16 +19291,14 @@ func (v *SomeValue) DeepRemove(interpreter *Interpreter) { } } -func (v *SomeValue) InnerValue(interpreter *Interpreter, locationRange LocationRange) Value { - config := interpreter.SharedState.Config - - if config.InvalidatedResourceValidationEnabled { - v.checkInvalidatedResourceUse(locationRange) - } - +func (v *SomeValue) InnerValue(_ *Interpreter, _ LocationRange) Value { return v.value } +func (v *SomeValue) isInvalidatedResource(_ *Interpreter) bool { + return v.value == nil || v.IsDestroyed() +} + type SomeStorable struct { gauge common.MemoryGauge Storable atree.Storable @@ -19940,25 +19703,30 @@ var _ MemberAccessibleValue = &EphemeralReferenceValue{} var _ ReferenceValue = &EphemeralReferenceValue{} func NewUnmeteredEphemeralReferenceValue( + interpreter *Interpreter, authorized bool, value Value, borrowedType sema.Type, ) *EphemeralReferenceValue { - return &EphemeralReferenceValue{ + ref := &EphemeralReferenceValue{ Authorized: authorized, Value: value, BorrowedType: borrowedType, } + + interpreter.maybeTrackReferencedResourceKindedValue(ref) + + return ref } func NewEphemeralReferenceValue( - gauge common.MemoryGauge, + interpreter *Interpreter, authorized bool, value Value, borrowedType sema.Type, ) *EphemeralReferenceValue { - common.UseMemory(gauge, common.EphemeralReferenceValueMemoryUsage) - return NewUnmeteredEphemeralReferenceValue(authorized, value, borrowedType) + common.UseMemory(interpreter, common.EphemeralReferenceValueMemoryUsage) + return NewUnmeteredEphemeralReferenceValue(interpreter, authorized, value, borrowedType) } func (*EphemeralReferenceValue) isValue() {} @@ -20243,8 +20011,8 @@ func (v *EphemeralReferenceValue) Transfer( return v } -func (v *EphemeralReferenceValue) Clone(_ *Interpreter) Value { - return NewUnmeteredEphemeralReferenceValue(v.Authorized, v.Value, v.BorrowedType) +func (v *EphemeralReferenceValue) Clone(inter *Interpreter) Value { + return NewUnmeteredEphemeralReferenceValue(inter, v.Authorized, v.Value, v.BorrowedType) } func (*EphemeralReferenceValue) DeepRemove(_ *Interpreter) { diff --git a/runtime/interpreter/value_function.go b/runtime/interpreter/value_function.go index 1734d966dd..c7028f49e3 100644 --- a/runtime/interpreter/value_function.go +++ b/runtime/interpreter/value_function.go @@ -327,6 +327,7 @@ type BoundFunctionValue struct { Function FunctionValue Base *EphemeralReferenceValue Self *MemberAccessibleValue + selfRef *EphemeralReferenceValue } var _ Value = BoundFunctionValue{} @@ -341,9 +342,22 @@ func NewBoundFunctionValue( common.UseMemory(interpreter, common.BoundFunctionValueMemoryUsage) + // Since 'self' work as an implicit reference, create an explicit one and hold it. + // This reference is later used to check the validity of the referenced value/resource. + var selfRef *EphemeralReferenceValue + if reference, isReference := (*self).(*EphemeralReferenceValue); isReference { + // For attachments, 'self' is already a reference. + // So no need to create a reference again. + selfRef = reference + } else { + semaType := interpreter.MustSemaTypeOfValue(*self) + selfRef = NewEphemeralReferenceValue(interpreter, true, *self, semaType) + } + return BoundFunctionValue{ Function: function, Self: self, + selfRef: selfRef, Base: base, } } @@ -385,16 +399,12 @@ func (f BoundFunctionValue) FunctionType() *sema.FunctionType { } func (f BoundFunctionValue) invoke(invocation Invocation) Value { - self := f.Self - invocation.Self = self - if self != nil { - if resource, ok := (*self).(ResourceKindedValue); ok && resource.IsDestroyed() { - panic(DestroyedResourceError{ - LocationRange: invocation.LocationRange, - }) - } - } + invocation.Self = f.Self invocation.Base = f.Base + + // Check if the 'self' is not invalidated. + _ = f.selfRef.MustReferencedValue(invocation.Interpreter, invocation.LocationRange) + return f.Function.invoke(invocation) } diff --git a/runtime/interpreter/value_function_test.go b/runtime/interpreter/value_function_test.go index 724229ad77..e7a2f39a99 100644 --- a/runtime/interpreter/value_function_test.go +++ b/runtime/interpreter/value_function_test.go @@ -63,6 +63,14 @@ func TestFunctionStaticType(t *testing.T) { inter := newTestInterpreter(t) + inter.SharedState.Config.CompositeTypeHandler = func(location common.Location, typeID TypeID) *sema.CompositeType { + return &sema.CompositeType{ + Location: utils.TestLocation, + Identifier: "foo", + Kind: common.CompositeKindStructure, + } + } + hostFunction := func(_ Invocation) Value { return TrueValue } diff --git a/runtime/interpreter/value_test.go b/runtime/interpreter/value_test.go index db7d030122..b4413b0048 100644 --- a/runtime/interpreter/value_test.go +++ b/runtime/interpreter/value_test.go @@ -4164,8 +4164,9 @@ func TestValue_ConformsToStaticType(t *testing.T) { t.Parallel() test( - func(_ *Interpreter) Value { + func(inter *Interpreter) Value { return NewUnmeteredEphemeralReferenceValue( + inter, false, TrueValue, sema.BoolType, @@ -4175,8 +4176,9 @@ func TestValue_ConformsToStaticType(t *testing.T) { ) test( - func(_ *Interpreter) Value { + func(inter *Interpreter) Value { return NewUnmeteredEphemeralReferenceValue( + inter, false, TrueValue, sema.StringType, diff --git a/runtime/storage_test.go b/runtime/storage_test.go index b90f76d35e..2da5838a41 100644 --- a/runtime/storage_test.go +++ b/runtime/storage_test.go @@ -4192,3 +4192,111 @@ func TestRuntimeStorageIteration(t *testing.T) { }) }) } + +func TestRuntimeStorageReferenceBoundFunction(t *testing.T) { + + t.Parallel() + + runtime := newTestInterpreterRuntime() + + signerAddress := common.MustBytesToAddress([]byte{0x42}) + + deployTx := DeploymentTransaction("Test", []byte(` + access(all) contract Test { + + access(all) resource R { + access(all) fun foo() {} + } + + access(all) fun createR(): @R { + return <-create R() + } + } + `)) + + accountCodes := map[Location][]byte{} + var events []cadence.Event + var loggedMessages []string + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{signerAddress}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + updateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + emitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + log: func(message string) { + loggedMessages = append(loggedMessages, message) + }, + } + + nextTransactionLocation := newTransactionLocationGenerator() + + // Deploy contract + + err := runtime.ExecuteTransaction( + Script{ + Source: deployTx, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + // Run test transaction + + const testTx = ` + import Test from 0x42 + + transaction { + prepare(signer: AuthAccount) { + signer.save(<-Test.createR(), to: /storage/r) + + signer.link<&Test.R>( + /public/r, + target: /storage/r + ) + + let ref = signer.getCapability<&Test.R>(/public/r).borrow()! + + var func = ref.foo + + let r <- signer.load<@Test.R>(from: /storage/r)! + + // Should be OK + func() + + destroy r + + // Should fail! + func() + } + } + ` + + err = runtime.ExecuteTransaction( + Script{ + Source: []byte(testTx), + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + + RequireError(t, err) + require.ErrorAs(t, err, &interpreter.DestroyedResourceError{}) +} diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 80f8e022e4..74b1b75caa 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -199,7 +199,6 @@ func parseCheckAndInterpretWithOptionsAndMemoryMetering( if options.Config != nil { config = *options.Config } - config.InvalidatedResourceValidationEnabled = true config.AtreeValueValidationEnabled = true config.AtreeStorageValidationEnabled = true if config.UUIDHandler == nil { diff --git a/runtime/tests/interpreter/member_test.go b/runtime/tests/interpreter/member_test.go index 7a3c03e269..0ade907bfa 100644 --- a/runtime/tests/interpreter/member_test.go +++ b/runtime/tests/interpreter/member_test.go @@ -408,7 +408,7 @@ func TestInterpretMemberAccessType(t *testing.T) { sType := checker.RequireGlobalType(t, inter.Program.Elaboration, "S") - ref := interpreter.NewUnmeteredEphemeralReferenceValue(false, value, sType) + ref := interpreter.NewUnmeteredEphemeralReferenceValue(inter, false, value, sType) _, err = inter.Invoke("get", ref) require.NoError(t, err) @@ -455,7 +455,7 @@ func TestInterpretMemberAccessType(t *testing.T) { sType := checker.RequireGlobalType(t, inter.Program.Elaboration, "S") - ref := interpreter.NewUnmeteredEphemeralReferenceValue(false, value, sType) + ref := interpreter.NewUnmeteredEphemeralReferenceValue(inter, false, value, sType) _, err = inter.Invoke("get", ref) RequireError(t, err) @@ -497,7 +497,7 @@ func TestInterpretMemberAccessType(t *testing.T) { sType := checker.RequireGlobalType(t, inter.Program.Elaboration, "S") - ref := interpreter.NewUnmeteredEphemeralReferenceValue(false, value, sType) + ref := interpreter.NewUnmeteredEphemeralReferenceValue(inter, false, value, sType) _, err = inter.Invoke( "get", @@ -542,7 +542,7 @@ func TestInterpretMemberAccessType(t *testing.T) { sType := checker.RequireGlobalType(t, inter.Program.Elaboration, "S") - ref := interpreter.NewUnmeteredEphemeralReferenceValue(false, value, sType) + ref := interpreter.NewUnmeteredEphemeralReferenceValue(inter, false, value, sType) _, err = inter.Invoke( "get", diff --git a/runtime/tests/interpreter/resources_test.go b/runtime/tests/interpreter/resources_test.go index ba6069c2d7..e62f97d8f4 100644 --- a/runtime/tests/interpreter/resources_test.go +++ b/runtime/tests/interpreter/resources_test.go @@ -2161,7 +2161,7 @@ func TestInterpreterResourcePreCondition(t *testing.T) { struct interface Receiver { pub fun deposit(from: @S) { - post { + pre { from != nil: "" } } @@ -2192,7 +2192,7 @@ func TestInterpreterResourcePostCondition(t *testing.T) { struct interface Receiver { pub fun deposit(from: @S) { post { - from != nil: "" + from != nil: "" // This is an error. Resource is destroyed at this point } } } @@ -2209,7 +2209,8 @@ func TestInterpreterResourcePostCondition(t *testing.T) { `) _, err := inter.Invoke("test") - require.NoError(t, err) + RequireError(t, err) + require.ErrorAs(t, err, &interpreter.InvalidatedResourceError{}) } func TestInterpreterResourcePreAndPostCondition(t *testing.T) { @@ -2222,10 +2223,10 @@ func TestInterpreterResourcePreAndPostCondition(t *testing.T) { struct interface Receiver { pub fun deposit(from: @S) { pre { - from != nil: "" + from != nil: "" // This is OK } post { - from != nil: "" + from != nil: "" // This is an error: Resource is destroyed at this point } } } @@ -2248,7 +2249,8 @@ func TestInterpreterResourcePreAndPostCondition(t *testing.T) { `) _, err := inter.Invoke("test") - require.NoError(t, err) + RequireError(t, err) + require.ErrorAs(t, err, &interpreter.InvalidatedResourceError{}) } func TestInterpreterResourceConditionAdditionalParam(t *testing.T) { @@ -2290,7 +2292,7 @@ func TestInterpreterResourceConditionAdditionalParam(t *testing.T) { require.NoError(t, err) } -func TestInterpreterResourceDoubleWrappedCondition(t *testing.T) { +func TestInterpreterResourceDoubleWrappedPreCondition(t *testing.T) { t.Parallel() @@ -2302,28 +2304,60 @@ func TestInterpreterResourceDoubleWrappedCondition(t *testing.T) { pre { from != nil: "" } - post { + } + } + + struct interface B { + pub fun deposit(from: @S) { + pre { from != nil: "" } } } - struct interface B { + struct Vault: A, B { pub fun deposit(from: @S) { pre { from != nil: "" } + destroy from + } + } + + fun test() { + Vault().deposit(from: <-create S()) + } + `) + + _, err := inter.Invoke("test") + require.NoError(t, err) +} + +func TestInterpreterResourceDoubleWrappedPostCondition(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + resource S {} + + struct interface A { + pub fun deposit(from: @S) { post { from != nil: "" } } } - struct Vault: A, B { + struct interface B { pub fun deposit(from: @S) { - pre { + post { from != nil: "" } + } + } + + struct Vault: A, B { + pub fun deposit(from: @S) { post { 1 > 0: "" } @@ -2337,7 +2371,8 @@ func TestInterpreterResourceDoubleWrappedCondition(t *testing.T) { `) _, err := inter.Invoke("test") - require.NoError(t, err) + RequireError(t, err) + require.ErrorAs(t, err, &interpreter.InvalidatedResourceError{}) } func TestInterpretOptionalResourceReference(t *testing.T) { @@ -3369,3 +3404,47 @@ func TestInterpretOptionalBindingElseBranch(t *testing.T) { // Error must be thrown at `<-r2` in the array literal assert.Equal(t, 18, invalidatedResourceErr.StartPosition().Line) } + +func TestInterpretPreConditionResourceMove(t *testing.T) { + + t.Parallel() + + inter, err := parseCheckAndInterpretWithOptions(t, ` + pub resource Vault { } + pub resource interface Interface { + pub fun foo(_ r: @AnyResource) { + pre { + consume(&r as &AnyResource, <- r) + } + } + } + pub resource Implementation: Interface { + pub fun foo(_ r: @AnyResource) { + pre { + consume(&r as &AnyResource, <- r) + } + } + } + pub fun consume(_ unusedRef: &AnyResource?, _ r: @AnyResource): Bool { + destroy r + return true + } + pub fun main() { + let a <- create Implementation() + let b <- create Vault() + a.foo(<-b) + destroy a + }`, + ParseCheckAndInterpretOptions{ + HandleCheckerError: func(err error) { + checkerErrors := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidInterfaceConditionResourceInvalidationError{}, checkerErrors[0]) + }, + }, + ) + require.NoError(t, err) + + _, err = inter.Invoke("main") + RequireError(t, err) + require.ErrorAs(t, err, &interpreter.InvalidatedResourceError{}) +}