From 292081d0562edea07ea7d35c6f9d7d86ee963cbe Mon Sep 17 00:00:00 2001 From: turbolent Date: Thu, 21 Nov 2024 17:41:42 +0000 Subject: [PATCH 01/80] v1.0.3 --- npm-packages/cadence-parser/package.json | 2 +- version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/npm-packages/cadence-parser/package.json b/npm-packages/cadence-parser/package.json index cc96f03cd..677ef0225 100644 --- a/npm-packages/cadence-parser/package.json +++ b/npm-packages/cadence-parser/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/cadence-parser", - "version": "1.0.2-preview.52", + "version": "1.0.3-preview.52", "description": "The Cadence parser", "homepage": "https://github.com/onflow/cadence", "repository": { diff --git a/version.go b/version.go index 513d494f1..b5cb28b0e 100644 --- a/version.go +++ b/version.go @@ -21,4 +21,4 @@ package cadence -const Version = "v1.0.2" +const Version = "v1.0.3" From 1d021b12b36b0f040b2588582972ca49c5532233 Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:24:49 -0600 Subject: [PATCH 02/80] Implement atree.WrapperValue and atree.WrapperStorable Currently, Cadence interpreter.SomeValue is opaque to packages like Atree, so interpreter.SomeValue is treated as atomic value. This causes container wrapped by interpreter.SomeValue to be treated as atomic value since it becomes opaque. Given this, the integration between Cadence and Atree does not establish parent-child relationship of a wrapped container since the inner values of interpreter.SomeValue are not visible to Atree. Resolving this issue requires unwrapping containers wrapped by interpreter.SomeValue. This commit implements these new interfaces provided by atree to unwrap inner Value and inner Storable in Cadence: - atree.WrapperValue - atree.WrapperStorable When Cadence passes objects that implement these interfaces, Atree will call the interface functions (that implement unwrapping) when setting child-parent callbacks. --- go.mod | 6 +- go.sum | 14 +- runtime/interpreter/value.go | 62 ++ runtime/interpreter/value_some_test.go | 804 +++++++++++++++++++++++++ 4 files changed, 876 insertions(+), 10 deletions(-) create mode 100644 runtime/interpreter/value_some_test.go diff --git a/go.mod b/go.mod index e3b766d7e..12ac349e2 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/onflow/atree v0.8.0-rc.6 github.com/rivo/uniseg v0.4.4 github.com/schollz/progressbar/v3 v3.13.1 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c github.com/tidwall/pretty v1.2.1 github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d @@ -54,7 +54,7 @@ require ( github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/assert v1.3.0 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect @@ -62,3 +62,5 @@ require ( gonum.org/v1/gonum v0.6.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20241224003741-68d02a879b11 diff --git a/go.sum b/go.sum index 40840eb0b..a72d7acd6 100644 --- a/go.sum +++ b/go.sum @@ -37,7 +37,6 @@ github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.0 h1:4ZexSFt8agMNzNisrsilL6RClWDC5YJnLHNIfTy4iuc= github.com/klauspost/cpuid/v2 v2.2.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kodova/html-to-markdown v1.0.1 h1:MJxQAnqxtss3DaPnm72DRV65HZiMQZF3DUAfEaTg+14= @@ -75,8 +74,8 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onflow/atree v0.8.0-rc.6 h1:GWgaylK24b5ta2Hq+TvyOF7X5tZLiLzMMn7lEt59fsA= -github.com/onflow/atree v0.8.0-rc.6/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/Ct1AA5zBo= +github.com/onflow/atree-internal v0.8.2-0.20241224003741-68d02a879b11 h1:gMibU5zvwWUdhZEfI99J9MRj3Rc32LC2jTWI4bg9XZ8= +github.com/onflow/atree-internal v0.8.2-0.20241224003741-68d02a879b11/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -98,8 +97,8 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= @@ -108,11 +107,10 @@ github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d h1:5JInRQbk5UBX github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d/go.mod h1:Nlx5Y115XQvNcIdIy7dZXaNSUpzwBSge4/Ivk93/Yog= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg= diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 258dda932..812a5090f 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -2283,6 +2283,7 @@ func newArrayValueFromAtreeArray( var _ Value = &ArrayValue{} var _ atree.Value = &ArrayValue{} +var _ atree.WrapperValue = &ArrayValue{} var _ EquatableValue = &ArrayValue{} var _ ValueIndexableValue = &ArrayValue{} var _ MemberAccessibleValue = &ArrayValue{} @@ -3362,9 +3363,17 @@ func (v *ArrayValue) Storable( address atree.Address, maxInlineSize uint64, ) (atree.Storable, error) { + // NOTE: Need to change ArrayValue.UnwrapAtreeValue() + // if ArrayValue is stored with wrapping. return v.array.Storable(storage, address, maxInlineSize) } +func (v *ArrayValue) UnwrapAtreeValue() (atree.Value, uint64) { + // Wrapper size is 0 because ArrayValue is stored as + // atree.Array without any physical wrapping. + return v.array, 0 +} + func (v *ArrayValue) IsReferenceTrackedResourceKindedValue() {} func (v *ArrayValue) Transfer( @@ -17380,6 +17389,8 @@ var _ HashableValue = &CompositeValue{} var _ MemberAccessibleValue = &CompositeValue{} var _ ReferenceTrackedResourceKindedValue = &CompositeValue{} var _ ContractValue = &CompositeValue{} +var _ atree.Value = &CompositeValue{} +var _ atree.WrapperValue = &CompositeValue{} func (*CompositeValue) isValue() {} @@ -18282,9 +18293,18 @@ func (v *CompositeValue) Storable( return NonStorable{Value: v}, nil } + // NOTE: Need to change CompositeValue.UnwrapAtreeValue() + // if CompositeValue is stored with wrapping. + return v.dictionary.Storable(storage, address, maxInlineSize) } +func (v *CompositeValue) UnwrapAtreeValue() (atree.Value, uint64) { + // Wrapper size is 0 because CompositeValue is stored as + // atree.OrderedMap without any physical wrapping. + return v.dictionary, 0 +} + func (v *CompositeValue) NeedsStoreTo(address atree.Address) bool { return address != v.StorageAddress() } @@ -19351,6 +19371,7 @@ func newDictionaryValueFromAtreeMap( var _ Value = &DictionaryValue{} var _ atree.Value = &DictionaryValue{} +var _ atree.WrapperValue = &DictionaryValue{} var _ EquatableValue = &DictionaryValue{} var _ ValueIndexableValue = &DictionaryValue{} var _ MemberAccessibleValue = &DictionaryValue{} @@ -20376,9 +20397,17 @@ func (v *DictionaryValue) Storable( address atree.Address, maxInlineSize uint64, ) (atree.Storable, error) { + // NOTE: Need to change DictionaryValue.UnwrapAtreeValue() + // if DictionaryValue is stored with wrapping. return v.dictionary.Storable(storage, address, maxInlineSize) } +func (v *DictionaryValue) UnwrapAtreeValue() (atree.Value, uint64) { + // Wrapper size is 0 because DictionaryValue is stored as + // atree.OrderedMap without any physical wrapping. + return v.dictionary, 0 +} + func (v *DictionaryValue) IsReferenceTrackedResourceKindedValue() {} func (v *DictionaryValue) Transfer( @@ -20887,6 +20916,26 @@ var _ Value = &SomeValue{} var _ EquatableValue = &SomeValue{} var _ MemberAccessibleValue = &SomeValue{} var _ OptionalValue = &SomeValue{} +var _ atree.Value = &SomeValue{} +var _ atree.WrapperValue = &SomeValue{} + +func (v *SomeValue) UnwrapAtreeValue() (atree.Value, uint64) { + // Unwrap SomeValue(s) + nonSomeValue, nestedLevels := v.nonSomeValue() + + // Get SomeValue(s) wrapper size + someStorableEncodedPrefixSize := getSomeStorableEncodedPrefixSize(nestedLevels) + + // Unwrap nonSomeValue if needed + switch nonSomeValue := nonSomeValue.(type) { + case atree.WrapperValue: + unwrappedValue, wrapperSize := nonSomeValue.UnwrapAtreeValue() + return unwrappedValue, wrapperSize + uint64(someStorableEncodedPrefixSize) + + default: + return nonSomeValue, uint64(someStorableEncodedPrefixSize) + } +} func (*SomeValue) isValue() {} @@ -21215,6 +21264,19 @@ type SomeStorable struct { } var _ atree.ContainerStorable = SomeStorable{} +var _ atree.WrapperStorable = SomeStorable{} + +func (s SomeStorable) UnwrapAtreeStorable() atree.Storable { + storable := s.Storable + + switch storable := storable.(type) { + case atree.WrapperStorable: + return storable.UnwrapAtreeStorable() + + default: + return storable + } +} func (s SomeStorable) HasPointer() bool { switch cs := s.Storable.(type) { diff --git a/runtime/interpreter/value_some_test.go b/runtime/interpreter/value_some_test.go new file mode 100644 index 000000000..0d1a0b650 --- /dev/null +++ b/runtime/interpreter/value_some_test.go @@ -0,0 +1,804 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * 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_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/tests/utils" +) + +func TestSomeValueUnwrapAtreeValue(t *testing.T) { + + const ( + cborTagSize = 2 + someStorableWithMultipleNestedlevelsArraySize = 1 + ) + + t.Parallel() + + t.Run("SomeValue(bool)", func(t *testing.T) { + bv := interpreter.BoolValue(true) + + v := interpreter.NewUnmeteredSomeValueNonCopying(bv) + + unwrappedValue, wrapperSize := v.UnwrapAtreeValue() + require.Equal(t, bv, unwrappedValue) + require.Equal(t, uint64(cborTagSize), wrapperSize) + }) + + t.Run("SomeValue(SomeValue(bool))", func(t *testing.T) { + bv := interpreter.BoolValue(true) + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + bv)) + + unwrappedValue, wrapperSize := v.UnwrapAtreeValue() + require.Equal(t, bv, unwrappedValue) + require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedlevelsArraySize+1), wrapperSize) + }) + + t.Run("SomeValue(SomeValue(ArrayValue(...)))", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + + inter, err := interpreter.NewInterpreter( + &interpreter.Program{ + Program: ast.NewProgram(nil, []ast.Declaration{}), + Elaboration: sema.NewElaboration(nil), + }, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + }, + ) + require.NoError(t, err) + + address := common.Address{'A'} + + values := []interpreter.Value{ + interpreter.NewUnmeteredUInt64Value(0), + interpreter.NewUnmeteredUInt64Value(1), + } + + array := interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + values..., + ) + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + array)) + + unwrappedValue, wrapperSize := v.UnwrapAtreeValue() + require.IsType(t, &atree.Array{}, unwrappedValue) + require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedlevelsArraySize+1), wrapperSize) + + atreeArray := unwrappedValue.(*atree.Array) + require.Equal(t, atree.Address(address), atreeArray.Address()) + require.Equal(t, uint64(len(values)), atreeArray.Count()) + + for i, expectedValue := range values { + v, err := atreeArray.Get(uint64(i)) + require.NoError(t, err) + require.Equal(t, expectedValue, v) + } + }) + + t.Run("SomeValue(SomeValue(DictionaryValue(...)))", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + + inter, err := interpreter.NewInterpreter( + &interpreter.Program{ + Program: ast.NewProgram(nil, []ast.Declaration{}), + Elaboration: sema.NewElaboration(nil), + }, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + }, + ) + require.NoError(t, err) + + address := common.Address{'A'} + + values := []interpreter.Value{ + interpreter.NewUnmeteredUInt64Value(0), + interpreter.NewUnmeteredStringValue("a"), + interpreter.NewUnmeteredUInt64Value(1), + interpreter.NewUnmeteredStringValue("b"), + } + + dict := interpreter.NewDictionaryValueWithAddress( + inter, + interpreter.EmptyLocationRange, + &interpreter.DictionaryStaticType{ + KeyType: interpreter.PrimitiveStaticTypeAnyStruct, + ValueType: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + values..., + ) + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + dict)) + + unwrappedValue, wrapperSize := v.UnwrapAtreeValue() + require.IsType(t, &atree.OrderedMap{}, unwrappedValue) + require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedlevelsArraySize+1), wrapperSize) + + // Verify unwrapped value + atreeMap := unwrappedValue.(*atree.OrderedMap) + require.Equal(t, atree.Address(address), atreeMap.Address()) + require.Equal(t, uint64(len(values)/2), atreeMap.Count()) + + valueComparator := func( + storage atree.SlabStorage, + atreeValue atree.Value, + otherStorable atree.Storable, + ) (bool, error) { + value := interpreter.MustConvertStoredValue(inter, atreeValue) + otherValue := interpreter.StoredValue(inter, otherStorable, storage) + return value.(interpreter.EquatableValue).Equal(inter, interpreter.EmptyLocationRange, otherValue), nil + } + + hashInputProvider := func( + value atree.Value, + scratch []byte, + ) ([]byte, error) { + hashInput := interpreter.MustConvertStoredValue(inter, value).(interpreter.HashableValue). + HashInput(inter, interpreter.EmptyLocationRange, scratch) + return hashInput, nil + } + + for i := 0; i < len(values); i += 2 { + key := values[i] + expectedValue := values[i+1] + + v, err := atreeMap.Get( + valueComparator, + hashInputProvider, + key, + ) + require.NoError(t, err) + require.Equal(t, expectedValue, v) + } + }) + + t.Run("SomeValue(SomeValue(CompositeValue(...)))", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + + inter, err := interpreter.NewInterpreter( + &interpreter.Program{ + Program: ast.NewProgram(nil, []ast.Declaration{}), + Elaboration: sema.NewElaboration(nil), + }, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + }, + ) + require.NoError(t, err) + + address := common.Address{'A'} + + identifier := "test" + + location := common.AddressLocation{ + Address: address, + Name: identifier, + } + + kind := common.CompositeKindStructure + + fields := []interpreter.CompositeField{ + interpreter.NewUnmeteredCompositeField( + "field1", + interpreter.NewUnmeteredStringValue("a"), + ), + interpreter.NewUnmeteredCompositeField( + "field2", + interpreter.NewUnmeteredStringValue("b"), + ), + } + + composite := interpreter.NewCompositeValue( + inter, + interpreter.EmptyLocationRange, + location, + identifier, + kind, + fields, + address, + ) + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + composite)) + + unwrappedValue, wrapperSize := v.UnwrapAtreeValue() + require.IsType(t, &atree.OrderedMap{}, unwrappedValue) + require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedlevelsArraySize+1), wrapperSize) + + // Verify unwrapped value + atreeMap := unwrappedValue.(*atree.OrderedMap) + require.Equal(t, atree.Address(address), atreeMap.Address()) + require.Equal(t, uint64(len(fields)), atreeMap.Count()) + + for _, f := range fields { + v, err := atreeMap.Get( + interpreter.StringAtreeValueComparator, + interpreter.StringAtreeValueHashInput, + interpreter.StringAtreeValue(f.Name), + ) + require.NoError(t, err) + require.Equal(t, f.Value, v) + } + }) +} + +func TestSomeStorableUnwrapAtreeStorable(t *testing.T) { + + t.Parallel() + + address := common.Address{'A'} + + t.Run("SomeValue(bool)", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.BoolValue(true)) + + const maxInlineSize = 1024 / 4 + storable, err := v.Storable(storage, atree.Address(address), maxInlineSize) + require.NoError(t, err) + require.IsType(t, interpreter.SomeStorable{}, storable) + + unwrappedStorable := storable.(interpreter.SomeStorable).UnwrapAtreeStorable() + require.Equal(t, interpreter.BoolValue(true), unwrappedStorable) + }) + + t.Run("SomeValue(SomeValue(bool))", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.BoolValue(true))) + + const maxInlineSize = 1024 / 4 + storable, err := v.Storable(storage, atree.Address(address), maxInlineSize) + require.NoError(t, err) + require.IsType(t, interpreter.SomeStorable{}, storable) + + unwrappedStorable := storable.(interpreter.SomeStorable).UnwrapAtreeStorable() + require.Equal(t, interpreter.BoolValue(true), unwrappedStorable) + }) + + t.Run("SomeValue(SomeValue(ArrayValue(...))), small ArrayValue", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + + inter, err := interpreter.NewInterpreter( + &interpreter.Program{ + Program: ast.NewProgram(nil, []ast.Declaration{}), + Elaboration: sema.NewElaboration(nil), + }, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + }, + ) + require.NoError(t, err) + + values := []interpreter.Value{ + interpreter.NewUnmeteredUInt64Value(0), + interpreter.NewUnmeteredUInt64Value(1), + } + + array := interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + values..., + ) + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + array)) + + const maxInlineSize = 1024 / 4 + storable, err := v.Storable(storage, atree.Address(address), maxInlineSize) + require.NoError(t, err) + require.IsType(t, interpreter.SomeStorable{}, storable) + + unwrappedStorable := storable.(interpreter.SomeStorable).UnwrapAtreeStorable() + require.IsType(t, &atree.ArrayDataSlab{}, unwrappedStorable) + + unwrappedValue, err := unwrappedStorable.(*atree.ArrayDataSlab).StoredValue(storage) + require.NoError(t, err) + require.IsType(t, &atree.Array{}, unwrappedValue) + + atreeArray := unwrappedValue.(*atree.Array) + require.Equal(t, atree.Address(address), atreeArray.Address()) + require.Equal(t, uint64(len(values)), atreeArray.Count()) + + for i, expectedValue := range values { + v, err := atreeArray.Get(uint64(i)) + require.NoError(t, err) + require.Equal(t, expectedValue, v) + } + }) + + t.Run("SomeValue(SomeValue(ArrayValue(...))), large ArrayValue", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + + inter, err := interpreter.NewInterpreter( + &interpreter.Program{ + Program: ast.NewProgram(nil, []ast.Declaration{}), + Elaboration: sema.NewElaboration(nil), + }, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + }, + ) + require.NoError(t, err) + + const valuesCount = 40 + values := make([]interpreter.Value, valuesCount) + for i := range valuesCount { + values[i] = interpreter.NewUnmeteredUInt64Value(uint64(i)) + } + + array := interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + values..., + ) + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + array)) + + const maxInlineSize = 1024 / 8 + storable, err := v.Storable(storage, atree.Address(address), maxInlineSize) + require.NoError(t, err) + require.IsType(t, interpreter.SomeStorable{}, storable) + + unwrappedStorable := storable.(interpreter.SomeStorable).UnwrapAtreeStorable() + require.IsType(t, atree.SlabIDStorable{}, unwrappedStorable) + + unwrappedValue, err := unwrappedStorable.(atree.SlabIDStorable).StoredValue(storage) + require.NoError(t, err) + require.IsType(t, &atree.Array{}, unwrappedValue) + + atreeArray := unwrappedValue.(*atree.Array) + require.Equal(t, atree.Address(address), atreeArray.Address()) + require.Equal(t, uint64(len(values)), atreeArray.Count()) + + for i, expectedValue := range values { + v, err := atreeArray.Get(uint64(i)) + require.NoError(t, err) + require.Equal(t, expectedValue, v) + } + }) + + t.Run("SomeValue(SomeValue(DictionaryValue(...))), small DictionaryValue", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + inter, err := interpreter.NewInterpreter( + &interpreter.Program{ + Program: ast.NewProgram(nil, []ast.Declaration{}), + Elaboration: sema.NewElaboration(nil), + }, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + }, + ) + require.NoError(t, err) + + address := common.Address{'A'} + + values := []interpreter.Value{ + interpreter.NewUnmeteredUInt64Value(0), + interpreter.NewUnmeteredStringValue("a"), + interpreter.NewUnmeteredUInt64Value(1), + interpreter.NewUnmeteredStringValue("b"), + } + + dict := interpreter.NewDictionaryValueWithAddress( + inter, + interpreter.EmptyLocationRange, + &interpreter.DictionaryStaticType{ + KeyType: interpreter.PrimitiveStaticTypeAnyStruct, + ValueType: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + values..., + ) + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + dict)) + + const maxInlineSize = 1024 / 4 + storable, err := v.Storable(storage, atree.Address(address), maxInlineSize) + require.NoError(t, err) + require.IsType(t, interpreter.SomeStorable{}, storable) + + unwrappedStorable := storable.(interpreter.SomeStorable).UnwrapAtreeStorable() + require.IsType(t, &atree.MapDataSlab{}, unwrappedStorable) + + unwrappedValue, err := unwrappedStorable.(*atree.MapDataSlab).StoredValue(storage) + require.NoError(t, err) + require.IsType(t, &atree.OrderedMap{}, unwrappedValue) + + // Verify unwrapped value + atreeMap := unwrappedValue.(*atree.OrderedMap) + require.Equal(t, atree.Address(address), atreeMap.Address()) + require.Equal(t, uint64(len(values)/2), atreeMap.Count()) + + valueComparator := func( + storage atree.SlabStorage, + atreeValue atree.Value, + otherStorable atree.Storable, + ) (bool, error) { + value := interpreter.MustConvertStoredValue(inter, atreeValue) + otherValue := interpreter.StoredValue(inter, otherStorable, storage) + return value.(interpreter.EquatableValue).Equal(inter, interpreter.EmptyLocationRange, otherValue), nil + } + + hashInputProvider := func( + value atree.Value, + scratch []byte, + ) ([]byte, error) { + hashInput := interpreter.MustConvertStoredValue(inter, value).(interpreter.HashableValue). + HashInput(inter, interpreter.EmptyLocationRange, scratch) + return hashInput, nil + } + + for i := 0; i < len(values); i += 2 { + key := values[i] + expectedValue := values[i+1] + + v, err := atreeMap.Get( + valueComparator, + hashInputProvider, + key, + ) + require.NoError(t, err) + require.Equal(t, expectedValue, v) + } + }) + + t.Run("SomeValue(SomeValue(DictionaryValue(...))), large DictionaryValue", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + + inter, err := interpreter.NewInterpreter( + &interpreter.Program{ + Program: ast.NewProgram(nil, []ast.Declaration{}), + Elaboration: sema.NewElaboration(nil), + }, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + }, + ) + require.NoError(t, err) + + address := common.Address{'A'} + + const valuesCount = 20 + values := make([]interpreter.Value, valuesCount*2) + + char := 'a' + for i := 0; i < len(values); i += 2 { + values[i] = interpreter.NewUnmeteredUInt64Value(uint64(i)) + values[i+1] = interpreter.NewUnmeteredStringValue(string(char)) + char += 1 + } + + dict := interpreter.NewDictionaryValueWithAddress( + inter, + interpreter.EmptyLocationRange, + &interpreter.DictionaryStaticType{ + KeyType: interpreter.PrimitiveStaticTypeAnyStruct, + ValueType: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + values..., + ) + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + dict)) + + const maxInlineSize = 1024 / 8 + storable, err := v.Storable(storage, atree.Address(address), maxInlineSize) + require.NoError(t, err) + require.IsType(t, interpreter.SomeStorable{}, storable) + + unwrappedStorable := storable.(interpreter.SomeStorable).UnwrapAtreeStorable() + require.IsType(t, atree.SlabIDStorable{}, unwrappedStorable) + + unwrappedValue, err := unwrappedStorable.(atree.SlabIDStorable).StoredValue(storage) + require.NoError(t, err) + require.IsType(t, &atree.OrderedMap{}, unwrappedValue) + + // Verify unwrapped value + atreeMap := unwrappedValue.(*atree.OrderedMap) + require.Equal(t, atree.Address(address), atreeMap.Address()) + require.Equal(t, uint64(len(values)/2), atreeMap.Count()) + + valueComparator := func( + storage atree.SlabStorage, + atreeValue atree.Value, + otherStorable atree.Storable, + ) (bool, error) { + value := interpreter.MustConvertStoredValue(inter, atreeValue) + otherValue := interpreter.StoredValue(inter, otherStorable, storage) + return value.(interpreter.EquatableValue).Equal(inter, interpreter.EmptyLocationRange, otherValue), nil + } + + hashInputProvider := func( + value atree.Value, + scratch []byte, + ) ([]byte, error) { + hashInput := interpreter.MustConvertStoredValue(inter, value).(interpreter.HashableValue). + HashInput(inter, interpreter.EmptyLocationRange, scratch) + return hashInput, nil + } + + for i := 0; i < len(values); i += 2 { + key := values[i] + expectedValue := values[i+1] + + v, err := atreeMap.Get( + valueComparator, + hashInputProvider, + key, + ) + require.NoError(t, err) + require.Equal(t, expectedValue, v) + } + }) + + t.Run("SomeValue(SomeValue(CompositeValue(...))), small CompositeValue", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + + inter, err := interpreter.NewInterpreter( + &interpreter.Program{ + Program: ast.NewProgram(nil, []ast.Declaration{}), + Elaboration: sema.NewElaboration(nil), + }, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + }, + ) + require.NoError(t, err) + + address := common.Address{'A'} + + identifier := "test" + + location := common.AddressLocation{ + Address: address, + Name: identifier, + } + + kind := common.CompositeKindStructure + + fields := []interpreter.CompositeField{ + interpreter.NewUnmeteredCompositeField( + "field1", + interpreter.NewUnmeteredStringValue("a"), + ), + interpreter.NewUnmeteredCompositeField( + "field2", + interpreter.NewUnmeteredStringValue("b"), + ), + } + + composite := interpreter.NewCompositeValue( + inter, + interpreter.EmptyLocationRange, + location, + identifier, + kind, + fields, + address, + ) + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + composite)) + + const maxInlineSize = 1024 / 4 + storable, err := v.Storable(storage, atree.Address(address), maxInlineSize) + require.NoError(t, err) + require.IsType(t, interpreter.SomeStorable{}, storable) + + unwrappedStorable := storable.(interpreter.SomeStorable).UnwrapAtreeStorable() + require.IsType(t, &atree.MapDataSlab{}, unwrappedStorable) + + unwrappedValue, err := unwrappedStorable.(*atree.MapDataSlab).StoredValue(storage) + require.NoError(t, err) + require.IsType(t, &atree.OrderedMap{}, unwrappedValue) + + // Verify unwrapped value + atreeMap := unwrappedValue.(*atree.OrderedMap) + require.Equal(t, atree.Address(address), atreeMap.Address()) + require.Equal(t, uint64(len(fields)), atreeMap.Count()) + + for _, f := range fields { + v, err := atreeMap.Get( + interpreter.StringAtreeValueComparator, + interpreter.StringAtreeValueHashInput, + interpreter.StringAtreeValue(f.Name), + ) + require.NoError(t, err) + require.Equal(t, f.Value, v) + } + }) + + t.Run("SomeValue(SomeValue(CompositeValue(...))), large CompositeValue", func(t *testing.T) { + storage := newUnmeteredInMemoryStorage() + + inter, err := interpreter.NewInterpreter( + &interpreter.Program{ + Program: ast.NewProgram(nil, []ast.Declaration{}), + Elaboration: sema.NewElaboration(nil), + }, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + }, + ) + require.NoError(t, err) + + address := common.Address{'A'} + + identifier := "test" + + location := common.AddressLocation{ + Address: address, + Name: identifier, + } + + kind := common.CompositeKindStructure + + const fieldsCount = 20 + fields := make([]interpreter.CompositeField, fieldsCount) + char := 'a' + for i := range len(fields) { + fields[i] = interpreter.NewUnmeteredCompositeField( + fmt.Sprintf("field%d", i), + interpreter.NewUnmeteredStringValue(string(char)), + ) + char += 1 + } + + composite := interpreter.NewCompositeValue( + inter, + interpreter.EmptyLocationRange, + location, + identifier, + kind, + fields, + address, + ) + + v := interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + composite)) + + const maxInlineSize = 1024 / 8 + storable, err := v.Storable(storage, atree.Address(address), maxInlineSize) + require.NoError(t, err) + require.IsType(t, interpreter.SomeStorable{}, storable) + + unwrappedStorable := storable.(interpreter.SomeStorable).UnwrapAtreeStorable() + require.IsType(t, atree.SlabIDStorable{}, unwrappedStorable) + + unwrappedValue, err := unwrappedStorable.(atree.SlabIDStorable).StoredValue(storage) + require.NoError(t, err) + require.IsType(t, &atree.OrderedMap{}, unwrappedValue) + + // Verify unwrapped value + atreeMap := unwrappedValue.(*atree.OrderedMap) + require.Equal(t, atree.Address(address), atreeMap.Address()) + require.Equal(t, uint64(len(fields)), atreeMap.Count()) + + for _, f := range fields { + v, err := atreeMap.Get( + interpreter.StringAtreeValueComparator, + interpreter.StringAtreeValueHashInput, + interpreter.StringAtreeValue(f.Name), + ) + require.NoError(t, err) + require.Equal(t, f.Value, v) + } + }) +} From 9d82cd4b8999e31302f417e7a06d92c97fd3f730 Mon Sep 17 00:00:00 2001 From: Kay-Zee Date: Fri, 27 Dec 2024 11:06:17 -0800 Subject: [PATCH 03/80] Update CI --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 634088911..ff806ba8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,7 @@ on: env: GO_VERSION: '1.22' + GOPRIVATE: github.com/onflow/atree-internal concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} @@ -42,6 +43,17 @@ jobs: - name: Install Flow CLI run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" + - name: Setup SSH for Onflow + run: git config --global url.git@github.com:onflow/.insteadOf https://github.com/onflow/ + + - name: Add GOPRIVATE + run: go env -w GOPRIVATE=${{ env.GOPRIVATE }} + + - name: Setup SSH Agent + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ATREE_DEPLOY_KEY }} + - name: Build run: make -j8 build From 590c66ec61c46b5d3cf49e6990fc97f4a69b99a6 Mon Sep 17 00:00:00 2001 From: Kay-Zee Date: Fri, 27 Dec 2024 11:17:14 -0800 Subject: [PATCH 04/80] Fix the lint job as well --- .github/workflows/ci.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff806ba8f..9d9817d65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,17 @@ jobs: go-version: ${{ env.GO_VERSION }} cache: true + - name: Setup SSH for Onflow + run: git config --global url.git@github.com:onflow/.insteadOf https://github.com/onflow/ + + - name: Add GOPRIVATE + run: go env -w GOPRIVATE=${{ env.GOPRIVATE }} + + - name: Setup SSH Agent + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ATREE_DEPLOY_KEY }} + - name: Lint run: make lint-github-actions From edca885b63b3ed6b082e0f85a7e9792a83d1b2cf Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Fri, 27 Dec 2024 13:37:01 -0600 Subject: [PATCH 05/80] Lint --- runtime/interpreter/value_some_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/interpreter/value_some_test.go b/runtime/interpreter/value_some_test.go index 0d1a0b650..340f83676 100644 --- a/runtime/interpreter/value_some_test.go +++ b/runtime/interpreter/value_some_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" From 179083c7e76477d4db51c0271ddb649214d19c5b Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Thu, 2 Jan 2025 09:06:24 -0600 Subject: [PATCH 06/80] Add integration test using a hardcoded seed This commit adds an integration test that uses a hardcoded seed which produced a test failure. --- runtime/tests/interpreter/values_test.go | 380 +++++++++++++++++++++++ 1 file changed, 380 insertions(+) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 0144838fc..9a26f2839 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -1925,3 +1925,383 @@ func TestCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { require.NoError(t, storage.CheckHealth()) } + +// TestInterpretArrayOperations tests ArrayValue operations with +// the a hardcoded seed that produced a past test failure. +// See TestInterpretRandomArrayOperations for same test using +// non-hardcoded seed. +func TestInterpretArrayOperations(t *testing.T) { + + // Use this random seed because it reproduces prior test failure. + seed := int64(1735328920693279431) + + r := randomValueGenerator{ + seed: 1735328920693279431, + rand: rand.New(rand.NewSource(seed)), + } + + t.Logf("seed: %d", r.seed) + + storage := newUnmeteredInMemoryStorage() + inter, err := interpreter.NewInterpreter( + &interpreter.Program{ + Program: ast.NewProgram(nil, []ast.Declaration{}), + Elaboration: sema.NewElaboration(nil), + }, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + }, + ) + require.NoError(t, err) + + numberOfValues := r.randomInt(containerMaxSize) + + var testArray, copyOfTestArray *interpreter.ArrayValue + var storageSize, slabCounts int + + elements := make([]interpreter.Value, numberOfValues) + orgOwner := common.Address{'A'} + + t.Run("construction", func(t *testing.T) { + values := make([]interpreter.Value, numberOfValues) + for i := 0; i < numberOfValues; i++ { + value := r.randomStorableValue(inter, 0) + elements[i] = value + values[i] = value.Clone(inter) + } + + testArray = interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + orgOwner, + values..., + ) + + storageSize, slabCounts = getSlabStorageSize(t, storage) + + require.Equal(t, len(elements), testArray.Count()) + + for index, orgElement := range elements { + element := testArray.Get(inter, interpreter.EmptyLocationRange, index) + utils.AssertValuesEqual(t, inter, orgElement, element) + } + + owner := testArray.GetOwner() + assert.Equal(t, orgOwner, owner) + }) + + t.Run("iterate", func(t *testing.T) { + require.Equal(t, testArray.Count(), len(elements)) + + index := 0 + testArray.Iterate( + inter, + func(element interpreter.Value) (resume bool) { + orgElement := elements[index] + utils.AssertValuesEqual(t, inter, orgElement, element) + + elementByIndex := testArray.Get(inter, interpreter.EmptyLocationRange, index) + utils.AssertValuesEqual(t, inter, element, elementByIndex) + + index++ + return true + }, + false, + interpreter.EmptyLocationRange, + ) + }) + + t.Run("deep copy", func(t *testing.T) { + newOwner := atree.Address{'B'} + copyOfTestArray = testArray.Transfer( + inter, + interpreter.EmptyLocationRange, + newOwner, + false, + nil, + nil, + true, // testArray is standalone. + ).(*interpreter.ArrayValue) + + require.Equal(t, len(elements), copyOfTestArray.Count()) + + for index, orgElement := range elements { + element := copyOfTestArray.Get(inter, interpreter.EmptyLocationRange, index) + utils.AssertValuesEqual(t, inter, orgElement, element) + } + + owner := copyOfTestArray.GetOwner() + assert.Equal(t, newOwner[:], owner[:]) + }) + + t.Run("deep removal", func(t *testing.T) { + copyOfTestArray.DeepRemove(inter, true) + err = storage.Remove(copyOfTestArray.SlabID()) + require.NoError(t, err) + + // deep removal should clean up everything + newStorageSize, newSlabCounts := getSlabStorageSize(t, storage) + assert.Equal(t, slabCounts, newSlabCounts) + assert.Equal(t, storageSize, newStorageSize) + + assert.Equal(t, len(elements), testArray.Count()) + + // go over original elements again and check no missing data (no side effect should be found) + for index, orgElement := range elements { + element := testArray.Get(inter, interpreter.EmptyLocationRange, index) + utils.AssertValuesEqual(t, inter, orgElement, element) + } + + owner := testArray.GetOwner() + assert.Equal(t, orgOwner, owner) + }) + + t.Run("insert", func(t *testing.T) { + newElements := make([]interpreter.Value, numberOfValues) + + testArray = interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + orgOwner, + ) + + require.Equal(t, 0, testArray.Count()) + + for i := 0; i < numberOfValues; i++ { + element := r.randomStorableValue(inter, 0) + newElements[i] = element + + testArray.Insert( + inter, + interpreter.EmptyLocationRange, + i, + element.Clone(inter), + ) + } + + require.Equal(t, len(newElements), testArray.Count()) + + // Go over original values again and check no missing data (no side effect should be found) + for index, element := range newElements { + value := testArray.Get(inter, interpreter.EmptyLocationRange, index) + utils.AssertValuesEqual(t, inter, element, value) + } + }) + + t.Run("append", func(t *testing.T) { + newElements := make([]interpreter.Value, numberOfValues) + + testArray = interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + orgOwner, + ) + + require.Equal(t, 0, testArray.Count()) + + for i := 0; i < numberOfValues; i++ { + element := r.randomStorableValue(inter, 0) + newElements[i] = element + + testArray.Append( + inter, + interpreter.EmptyLocationRange, + element.Clone(inter), + ) + } + + require.Equal(t, len(newElements), testArray.Count()) + + // Go over original values again and check no missing data (no side effect should be found) + for index, element := range newElements { + value := testArray.Get(inter, interpreter.EmptyLocationRange, index) + utils.AssertValuesEqual(t, inter, element, value) + } + }) + + t.Run("remove", func(t *testing.T) { + newElements := make([]interpreter.Value, numberOfValues) + + for i := 0; i < numberOfValues; i++ { + newElements[i] = r.randomStorableValue(inter, 0) + } + + testArray = interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + orgOwner, + ) + + require.Equal(t, 0, testArray.Count()) + + // Get the initial storage size before inserting values + startingStorageSize, startingSlabCounts := getSlabStorageSize(t, storage) + + // Insert + for index, element := range newElements { + testArray.Insert( + inter, + interpreter.EmptyLocationRange, + index, + element.Clone(inter), + ) + } + + require.Equal(t, len(newElements), testArray.Count()) + + // Remove + for _, element := range newElements { + removedValue := testArray.Remove(inter, interpreter.EmptyLocationRange, 0) + + // Removed value must be same as the original value + utils.AssertValuesEqual(t, inter, element, removedValue) + } + + // Array must be empty + require.Equal(t, 0, testArray.Count()) + + storageSize, slabCounts := getSlabStorageSize(t, storage) + + // Storage size after removals should be same as the size before insertion. + assert.Equal(t, startingStorageSize, storageSize) + assert.Equal(t, startingSlabCounts, slabCounts) + }) + + t.Run("random insert & remove", func(t *testing.T) { + elements := make([]interpreter.Value, numberOfValues) + + for i := 0; i < numberOfValues; i++ { + elements[i] = r.randomStorableValue(inter, 0) + } + + testArray = interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + orgOwner, + ) + + require.Equal(t, 0, testArray.Count()) + + // Get the initial storage size before inserting values + startingStorageSize, startingSlabCounts := getSlabStorageSize(t, storage) + + insertCount := 0 + deleteCount := 0 + + isInsert := func() bool { + if testArray.Count() == 0 { + return true + } + + if insertCount >= numberOfValues { + return false + } + + return r.randomInt(1) == 1 + } + + for insertCount < numberOfValues || testArray.Count() > 0 { + // Perform a random operation out of insert/remove + if isInsert() { + value := elements[insertCount].Clone(inter) + + testArray.Append( + inter, + interpreter.EmptyLocationRange, + value, + ) + insertCount++ + } else { + orgValue := elements[deleteCount] + removedValue := testArray.RemoveFirst(inter, interpreter.EmptyLocationRange) + + // Removed value must be same as the original value + utils.AssertValuesEqual(t, inter, orgValue, removedValue) + + deleteCount++ + } + } + + // Dictionary must be empty + require.Equal(t, 0, testArray.Count()) + + storageSize, slabCounts := getSlabStorageSize(t, storage) + + // Storage size after removals should be same as the size before insertion. + assert.Equal(t, startingStorageSize, storageSize) + assert.Equal(t, startingSlabCounts, slabCounts) + }) + + t.Run("move", func(t *testing.T) { + values := make([]interpreter.Value, numberOfValues) + elements := make([]interpreter.Value, numberOfValues) + + for i := 0; i < numberOfValues; i++ { + value := r.randomStorableValue(inter, 0) + elements[i] = value + values[i] = value.Clone(inter) + } + + array := interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + orgOwner, + values..., + ) + + require.Equal(t, len(elements), array.Count()) + + owner := array.GetOwner() + assert.Equal(t, orgOwner, owner) + + newOwner := atree.Address{'B'} + movedArray := array.Transfer( + inter, + interpreter.EmptyLocationRange, + newOwner, + true, + nil, + nil, + true, // array is standalone. + ).(*interpreter.ArrayValue) + + require.Equal(t, len(elements), movedArray.Count()) + + // Cleanup the slab of original array. + err := storage.Remove(array.SlabID()) + require.NoError(t, err) + + // Check the elements + for index, orgElement := range elements { + element := movedArray.Get(inter, interpreter.EmptyLocationRange, index) + utils.AssertValuesEqual(t, inter, orgElement, element) + } + + owner = movedArray.GetOwner() + assert.Equal(t, newOwner[:], owner[:]) + }) +} From c3f567dbc7e75a27e419d29510e0124e5f9dee78 Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Fri, 3 Jan 2025 17:46:48 -0600 Subject: [PATCH 07/80] Implement updated atree.WrapperStorable interface This commit implements a new interface function available in atree.WrapperStorable: - WrapAtreeStorable(Storable) Storable --- go.mod | 2 +- go.sum | 4 ++-- runtime/interpreter/value.go | 12 ++++++++++++ runtime/tests/interpreter/resources_test.go | 4 ++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 12ac349e2..74aa9783a 100644 --- a/go.mod +++ b/go.mod @@ -63,4 +63,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20241224003741-68d02a879b11 +replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20250103230356-3b72130d6113 diff --git a/go.sum b/go.sum index a72d7acd6..e38ad0273 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onflow/atree-internal v0.8.2-0.20241224003741-68d02a879b11 h1:gMibU5zvwWUdhZEfI99J9MRj3Rc32LC2jTWI4bg9XZ8= -github.com/onflow/atree-internal v0.8.2-0.20241224003741-68d02a879b11/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/atree-internal v0.8.2-0.20250103230356-3b72130d6113 h1:Wjs4qj9BohnwkX3ilJDfAAcj7jmkjxEkCF7osjEEMjI= +github.com/onflow/atree-internal v0.8.2-0.20250103230356-3b72130d6113/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 812a5090f..c26e56e27 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -21278,6 +21278,18 @@ func (s SomeStorable) UnwrapAtreeStorable() atree.Storable { } } +// WrapAtreeStorable() wraps storable as innermost wrapped value and +// returns new wrapped storable. +func (s SomeStorable) WrapAtreeStorable(storable atree.Storable) atree.Storable { + _, nestedLevels := s.nonSomeStorable() + + newStorable := SomeStorable{Storable: storable} + for i := 1; i < int(nestedLevels); i++ { + newStorable = SomeStorable{Storable: newStorable} + } + return newStorable +} + func (s SomeStorable) HasPointer() bool { switch cs := s.Storable.(type) { case atree.ContainerStorable: diff --git a/runtime/tests/interpreter/resources_test.go b/runtime/tests/interpreter/resources_test.go index 964855685..4d93c187f 100644 --- a/runtime/tests/interpreter/resources_test.go +++ b/runtime/tests/interpreter/resources_test.go @@ -3575,6 +3575,10 @@ func TestInterpretInvalidNilCoalescingResourceDuplication(t *testing.T) { errs := checker.RequireCheckerErrors(t, err, 1) assert.IsType(t, &sema.InvalidNilCoalescingRightResourceOperandError{}, errs[0]) }, + Config: &interpreter.Config{ + AtreeStorageValidationEnabled: false, + AtreeValueValidationEnabled: false, + }, }, ) require.NoError(t, err) From f4c9f5b6c6cb32c7a9de6eec3c635357826742d5 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 6 Jan 2025 10:15:54 -0800 Subject: [PATCH 08/80] Update TestInterpretInvalidNilCoalescingResourceDuplication test to match the new behavior --- runtime/tests/interpreter/interpreter_test.go | 43 +++++++++- runtime/tests/interpreter/resources_test.go | 78 ++++++++++++++----- 2 files changed, 102 insertions(+), 19 deletions(-) diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index f7fa752b8..556134684 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -71,6 +71,23 @@ func parseCheckAndInterpretWithOptions( return parseCheckAndInterpretWithOptionsAndMemoryMetering(t, code, options, nil) } +func parseCheckAndInterpretWithAtreeValidationsDisabled( + t testing.TB, + code string, + options ParseCheckAndInterpretOptions, +) ( + inter *interpreter.Interpreter, + err error, +) { + return parseCheckAndInterpretWithOptionsAndMemoryMeteringAndAtreeValidations( + t, + code, + options, + nil, + false, + ) +} + func parseCheckAndInterpretWithLogs( tb testing.TB, code string, @@ -172,6 +189,25 @@ func parseCheckAndInterpretWithOptionsAndMemoryMetering( inter *interpreter.Interpreter, err error, ) { + return parseCheckAndInterpretWithOptionsAndMemoryMeteringAndAtreeValidations( + t, + code, + options, + memoryGauge, + true, + ) +} + +func parseCheckAndInterpretWithOptionsAndMemoryMeteringAndAtreeValidations( + t testing.TB, + code string, + options ParseCheckAndInterpretOptions, + memoryGauge common.MemoryGauge, + enableAtreeValidations bool, +) ( + inter *interpreter.Interpreter, + err error, +) { checker, err := checker.ParseAndCheckWithOptionsAndMemoryMetering(t, code, @@ -201,10 +237,15 @@ func parseCheckAndInterpretWithOptionsAndMemoryMetering( if options.Config != nil { config = *options.Config } - if memoryGauge == nil { + + if enableAtreeValidations { config.AtreeValueValidationEnabled = true config.AtreeStorageValidationEnabled = true + } else { + config.AtreeValueValidationEnabled = false + config.AtreeStorageValidationEnabled = false } + if config.UUIDHandler == nil { config.UUIDHandler = func() (uint64, error) { uuid++ diff --git a/runtime/tests/interpreter/resources_test.go b/runtime/tests/interpreter/resources_test.go index 4d93c187f..6ebf3f10d 100644 --- a/runtime/tests/interpreter/resources_test.go +++ b/runtime/tests/interpreter/resources_test.go @@ -24,11 +24,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/tests/checker" . "github.com/onflow/cadence/runtime/tests/utils" - - "github.com/onflow/cadence/runtime/interpreter" ) func TestInterpretOptionalResourceBindingWithSecondValue(t *testing.T) { @@ -3548,8 +3549,12 @@ func TestInterpretInvalidNilCoalescingResourceDuplication(t *testing.T) { t.Parallel() - inter, err := parseCheckAndInterpretWithOptions(t, - ` + t.Run("remove", func(t *testing.T) { + + t.Parallel() + + inter, err := parseCheckAndInterpretWithAtreeValidationsDisabled(t, + ` access(all) resource R { access(all) let answer: Int init() { @@ -3570,22 +3575,59 @@ func TestInterpretInvalidNilCoalescingResourceDuplication(t *testing.T) { return answer1 + answer2 } `, - ParseCheckAndInterpretOptions{ - HandleCheckerError: func(err error) { - errs := checker.RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidNilCoalescingRightResourceOperandError{}, errs[0]) + ParseCheckAndInterpretOptions{ + HandleCheckerError: func(err error) { + errs := checker.RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidNilCoalescingRightResourceOperandError{}, errs[0]) + }, }, - Config: &interpreter.Config{ - AtreeStorageValidationEnabled: false, - AtreeValueValidationEnabled: false, + ) + require.NoError(t, err) + + _, err = inter.Invoke("main") + require.Error(t, err) + + var inliningError *atree.FatalError + require.ErrorAs(t, err, &inliningError) + require.Contains(t, inliningError.Error(), "failed to uninline") + }) + + t.Run("destroy", func(t *testing.T) { + + t.Parallel() + + inter, err := parseCheckAndInterpretWithAtreeValidationsDisabled(t, + ` + access(all) resource R { + access(all) let answer: Int + init() { + self.answer = 42 + } + } + + access(all) fun main(): Int { + let rs <- [<- create R(), nil] + rs[1] <-! (nil ?? rs[0]) + let answer1 = rs[0]?.answer! + let answer2 = rs[1]?.answer! + destroy rs + return answer1 + answer2 + } + `, + ParseCheckAndInterpretOptions{ + HandleCheckerError: func(err error) { + errs := checker.RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidNilCoalescingRightResourceOperandError{}, errs[0]) + }, }, - }, - ) - require.NoError(t, err) + ) + require.NoError(t, err) - _, err = inter.Invoke("main") - require.Error(t, err) + _, err = inter.Invoke("main") + require.Error(t, err) + + var destroyedResourceErr interpreter.DestroyedResourceError + require.ErrorAs(t, err, &destroyedResourceErr) + }) - var destroyedResourceErr interpreter.DestroyedResourceError - require.ErrorAs(t, err, &destroyedResourceErr) } From 350a160f660f21c69167bbfbd6cb40fc2c3f29ea Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:30:53 -0600 Subject: [PATCH 09/80] Update comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bastian Müller --- runtime/interpreter/value.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index c26e56e27..86d8fc83e 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -3370,7 +3370,7 @@ func (v *ArrayValue) Storable( func (v *ArrayValue) UnwrapAtreeValue() (atree.Value, uint64) { // Wrapper size is 0 because ArrayValue is stored as - // atree.Array without any physical wrapping. + // atree.Array without any physical wrapping (see ArrayValue.Storable()). return v.array, 0 } @@ -18301,7 +18301,7 @@ func (v *CompositeValue) Storable( func (v *CompositeValue) UnwrapAtreeValue() (atree.Value, uint64) { // Wrapper size is 0 because CompositeValue is stored as - // atree.OrderedMap without any physical wrapping. + // atree.OrderedMap without any physical wrapping (see CompositeValue.Storable()). return v.dictionary, 0 } @@ -20404,7 +20404,7 @@ func (v *DictionaryValue) Storable( func (v *DictionaryValue) UnwrapAtreeValue() (atree.Value, uint64) { // Wrapper size is 0 because DictionaryValue is stored as - // atree.OrderedMap without any physical wrapping. + // atree.OrderedMap without any physical wrapping (see DictionaryValue.Storable()). return v.dictionary, 0 } From 8d9cf5b7fba016f17400abcadeccc006391610e7 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 6 Jan 2025 11:13:04 -0800 Subject: [PATCH 10/80] Fix memory metering tests --- runtime/tests/interpreter/interpreter_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 556134684..375143b5f 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -189,12 +189,17 @@ func parseCheckAndInterpretWithOptionsAndMemoryMetering( inter *interpreter.Interpreter, err error, ) { + + // Atree validation should be disabled for memory metering tests. + // Otherwise, validation may also affect the memory consumption. + enableAtreeValidations := memoryGauge == nil + return parseCheckAndInterpretWithOptionsAndMemoryMeteringAndAtreeValidations( t, code, options, memoryGauge, - true, + enableAtreeValidations, ) } From 65e6c530b5e77d9b47969db103350bb2ce8cf2eb Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:05:34 -0600 Subject: [PATCH 11/80] Update comment --- runtime/interpreter/value.go | 5 +++++ runtime/tests/interpreter/values_test.go | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 86d8fc83e..3cb02f466 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -20919,7 +20919,12 @@ var _ OptionalValue = &SomeValue{} var _ atree.Value = &SomeValue{} var _ atree.WrapperValue = &SomeValue{} +// UnwrapAtreeValue returns non-SomeValue and wrapper size. func (v *SomeValue) UnwrapAtreeValue() (atree.Value, uint64) { + // NOTE: + // - non-SomeValue is the same as non-SomeValue in SomeValue.Storable() + // - non-SomeValue wrapper size is the same as encoded wrapper size in SomeStorable.ByteSize(). + // Unwrap SomeValue(s) nonSomeValue, nestedLevels := v.nonSomeValue() diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 9a26f2839..c528a4fb0 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -1927,9 +1927,20 @@ func TestCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { } // TestInterpretArrayOperations tests ArrayValue operations with -// the a hardcoded seed that produced a past test failure. -// See TestInterpretRandomArrayOperations for same test using -// non-hardcoded seed. +// the a hardcoded seed that produced a past test failure when using +// the atree.WrapperValue interface (added to atree in January 2025). +// +// When WrapperValue (e.g. SomeValue) is inserted into atree array, +// atree array unwraps WrapperValue and tracks its inserted index in array. +// When such value is removed, atree array also needs to unwrap the removed +// value and untracks its index in the array. +// For more info about Wrappervalue and WrapperStorable, see: +// https://github.com/onflow/cadence-internal/issues/286 +// https://github.com/onflow/atree-internal/pull/2 +// https://github.com/onflow/cadence-internal/pull/287 +// +// NOTE: TestInterpretRandomArrayOperations is the same test as this, but +// it uses a non-hardcoded seed. func TestInterpretArrayOperations(t *testing.T) { // Use this random seed because it reproduces prior test failure. From e4950e0295b82713a5c24968c9b24e2eff13f553 Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:06:55 -0600 Subject: [PATCH 12/80] Refactor tests --- runtime/tests/interpreter/values_test.go | 374 +---------------------- 1 file changed, 7 insertions(+), 367 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index c528a4fb0..e79a24e67 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -599,6 +599,12 @@ func TestInterpretRandomArrayOperations(t *testing.T) { } r := newRandomValueGenerator() + + testInterpretArrayOperations(t, r) +} + +func testInterpretArrayOperations(t *testing.T, r randomValueGenerator) { + t.Logf("seed: %d", r.seed) storage := newUnmeteredInMemoryStorage() @@ -1938,9 +1944,6 @@ func TestCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { // https://github.com/onflow/cadence-internal/issues/286 // https://github.com/onflow/atree-internal/pull/2 // https://github.com/onflow/cadence-internal/pull/287 -// -// NOTE: TestInterpretRandomArrayOperations is the same test as this, but -// it uses a non-hardcoded seed. func TestInterpretArrayOperations(t *testing.T) { // Use this random seed because it reproduces prior test failure. @@ -1951,368 +1954,5 @@ func TestInterpretArrayOperations(t *testing.T) { rand: rand.New(rand.NewSource(seed)), } - t.Logf("seed: %d", r.seed) - - storage := newUnmeteredInMemoryStorage() - inter, err := interpreter.NewInterpreter( - &interpreter.Program{ - Program: ast.NewProgram(nil, []ast.Declaration{}), - Elaboration: sema.NewElaboration(nil), - }, - utils.TestLocation, - &interpreter.Config{ - Storage: storage, - ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { - return interpreter.VirtualImport{ - Elaboration: inter.Program.Elaboration, - } - }, - }, - ) - require.NoError(t, err) - - numberOfValues := r.randomInt(containerMaxSize) - - var testArray, copyOfTestArray *interpreter.ArrayValue - var storageSize, slabCounts int - - elements := make([]interpreter.Value, numberOfValues) - orgOwner := common.Address{'A'} - - t.Run("construction", func(t *testing.T) { - values := make([]interpreter.Value, numberOfValues) - for i := 0; i < numberOfValues; i++ { - value := r.randomStorableValue(inter, 0) - elements[i] = value - values[i] = value.Clone(inter) - } - - testArray = interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - values..., - ) - - storageSize, slabCounts = getSlabStorageSize(t, storage) - - require.Equal(t, len(elements), testArray.Count()) - - for index, orgElement := range elements { - element := testArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, orgElement, element) - } - - owner := testArray.GetOwner() - assert.Equal(t, orgOwner, owner) - }) - - t.Run("iterate", func(t *testing.T) { - require.Equal(t, testArray.Count(), len(elements)) - - index := 0 - testArray.Iterate( - inter, - func(element interpreter.Value) (resume bool) { - orgElement := elements[index] - utils.AssertValuesEqual(t, inter, orgElement, element) - - elementByIndex := testArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, element, elementByIndex) - - index++ - return true - }, - false, - interpreter.EmptyLocationRange, - ) - }) - - t.Run("deep copy", func(t *testing.T) { - newOwner := atree.Address{'B'} - copyOfTestArray = testArray.Transfer( - inter, - interpreter.EmptyLocationRange, - newOwner, - false, - nil, - nil, - true, // testArray is standalone. - ).(*interpreter.ArrayValue) - - require.Equal(t, len(elements), copyOfTestArray.Count()) - - for index, orgElement := range elements { - element := copyOfTestArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, orgElement, element) - } - - owner := copyOfTestArray.GetOwner() - assert.Equal(t, newOwner[:], owner[:]) - }) - - t.Run("deep removal", func(t *testing.T) { - copyOfTestArray.DeepRemove(inter, true) - err = storage.Remove(copyOfTestArray.SlabID()) - require.NoError(t, err) - - // deep removal should clean up everything - newStorageSize, newSlabCounts := getSlabStorageSize(t, storage) - assert.Equal(t, slabCounts, newSlabCounts) - assert.Equal(t, storageSize, newStorageSize) - - assert.Equal(t, len(elements), testArray.Count()) - - // go over original elements again and check no missing data (no side effect should be found) - for index, orgElement := range elements { - element := testArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, orgElement, element) - } - - owner := testArray.GetOwner() - assert.Equal(t, orgOwner, owner) - }) - - t.Run("insert", func(t *testing.T) { - newElements := make([]interpreter.Value, numberOfValues) - - testArray = interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - ) - - require.Equal(t, 0, testArray.Count()) - - for i := 0; i < numberOfValues; i++ { - element := r.randomStorableValue(inter, 0) - newElements[i] = element - - testArray.Insert( - inter, - interpreter.EmptyLocationRange, - i, - element.Clone(inter), - ) - } - - require.Equal(t, len(newElements), testArray.Count()) - - // Go over original values again and check no missing data (no side effect should be found) - for index, element := range newElements { - value := testArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, element, value) - } - }) - - t.Run("append", func(t *testing.T) { - newElements := make([]interpreter.Value, numberOfValues) - - testArray = interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - ) - - require.Equal(t, 0, testArray.Count()) - - for i := 0; i < numberOfValues; i++ { - element := r.randomStorableValue(inter, 0) - newElements[i] = element - - testArray.Append( - inter, - interpreter.EmptyLocationRange, - element.Clone(inter), - ) - } - - require.Equal(t, len(newElements), testArray.Count()) - - // Go over original values again and check no missing data (no side effect should be found) - for index, element := range newElements { - value := testArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, element, value) - } - }) - - t.Run("remove", func(t *testing.T) { - newElements := make([]interpreter.Value, numberOfValues) - - for i := 0; i < numberOfValues; i++ { - newElements[i] = r.randomStorableValue(inter, 0) - } - - testArray = interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - ) - - require.Equal(t, 0, testArray.Count()) - - // Get the initial storage size before inserting values - startingStorageSize, startingSlabCounts := getSlabStorageSize(t, storage) - - // Insert - for index, element := range newElements { - testArray.Insert( - inter, - interpreter.EmptyLocationRange, - index, - element.Clone(inter), - ) - } - - require.Equal(t, len(newElements), testArray.Count()) - - // Remove - for _, element := range newElements { - removedValue := testArray.Remove(inter, interpreter.EmptyLocationRange, 0) - - // Removed value must be same as the original value - utils.AssertValuesEqual(t, inter, element, removedValue) - } - - // Array must be empty - require.Equal(t, 0, testArray.Count()) - - storageSize, slabCounts := getSlabStorageSize(t, storage) - - // Storage size after removals should be same as the size before insertion. - assert.Equal(t, startingStorageSize, storageSize) - assert.Equal(t, startingSlabCounts, slabCounts) - }) - - t.Run("random insert & remove", func(t *testing.T) { - elements := make([]interpreter.Value, numberOfValues) - - for i := 0; i < numberOfValues; i++ { - elements[i] = r.randomStorableValue(inter, 0) - } - - testArray = interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - ) - - require.Equal(t, 0, testArray.Count()) - - // Get the initial storage size before inserting values - startingStorageSize, startingSlabCounts := getSlabStorageSize(t, storage) - - insertCount := 0 - deleteCount := 0 - - isInsert := func() bool { - if testArray.Count() == 0 { - return true - } - - if insertCount >= numberOfValues { - return false - } - - return r.randomInt(1) == 1 - } - - for insertCount < numberOfValues || testArray.Count() > 0 { - // Perform a random operation out of insert/remove - if isInsert() { - value := elements[insertCount].Clone(inter) - - testArray.Append( - inter, - interpreter.EmptyLocationRange, - value, - ) - insertCount++ - } else { - orgValue := elements[deleteCount] - removedValue := testArray.RemoveFirst(inter, interpreter.EmptyLocationRange) - - // Removed value must be same as the original value - utils.AssertValuesEqual(t, inter, orgValue, removedValue) - - deleteCount++ - } - } - - // Dictionary must be empty - require.Equal(t, 0, testArray.Count()) - - storageSize, slabCounts := getSlabStorageSize(t, storage) - - // Storage size after removals should be same as the size before insertion. - assert.Equal(t, startingStorageSize, storageSize) - assert.Equal(t, startingSlabCounts, slabCounts) - }) - - t.Run("move", func(t *testing.T) { - values := make([]interpreter.Value, numberOfValues) - elements := make([]interpreter.Value, numberOfValues) - - for i := 0; i < numberOfValues; i++ { - value := r.randomStorableValue(inter, 0) - elements[i] = value - values[i] = value.Clone(inter) - } - - array := interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - values..., - ) - - require.Equal(t, len(elements), array.Count()) - - owner := array.GetOwner() - assert.Equal(t, orgOwner, owner) - - newOwner := atree.Address{'B'} - movedArray := array.Transfer( - inter, - interpreter.EmptyLocationRange, - newOwner, - true, - nil, - nil, - true, // array is standalone. - ).(*interpreter.ArrayValue) - - require.Equal(t, len(elements), movedArray.Count()) - - // Cleanup the slab of original array. - err := storage.Remove(array.SlabID()) - require.NoError(t, err) - - // Check the elements - for index, orgElement := range elements { - element := movedArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, orgElement, element) - } - - owner = movedArray.GetOwner() - assert.Equal(t, newOwner[:], owner[:]) - }) + testInterpretArrayOperations(t, r) } From 48adefaf48cb3fda37950b7e0da7906e3aa003af Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:20:24 -0600 Subject: [PATCH 13/80] Update atree version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 74aa9783a..97ad78fbf 100644 --- a/go.mod +++ b/go.mod @@ -63,4 +63,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20250103230356-3b72130d6113 +replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20250106221556-b9ddce7b8760 diff --git a/go.sum b/go.sum index e38ad0273..787394a50 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onflow/atree-internal v0.8.2-0.20250103230356-3b72130d6113 h1:Wjs4qj9BohnwkX3ilJDfAAcj7jmkjxEkCF7osjEEMjI= -github.com/onflow/atree-internal v0.8.2-0.20250103230356-3b72130d6113/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/atree-internal v0.8.2-0.20250106221556-b9ddce7b8760 h1:YVlF0ZIDLoxcUYvIbQe6T3XrrxTPzcC8sZcy3KHhjuU= +github.com/onflow/atree-internal v0.8.2-0.20250106221556-b9ddce7b8760/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= From 74ddc8e09ed0ec7f49c0225919cfc3cd8c46c55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 7 Jan 2025 10:11:21 -0800 Subject: [PATCH 14/80] port Supun's reproducers from flow-go/FVM https://github.com/onflow/flow-go-internal/compare/ff48a3c92161218ac8b9a6dda787817d551b849f...3df9e5a6192614aeba85783ec25b6a39d66d46e7 --- runtime/runtime_test.go | 324 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 5efbfe7da..bf4fb7805 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -11591,3 +11591,327 @@ func TestRuntimeInvocationReturnTypeInferenceFailure(t *testing.T) { var typeErr *sema.InvocationTypeInferenceError require.ErrorAs(t, err, &typeErr) } + +func TestRuntimeSomeValueChildContainerMutation(t *testing.T) { + + t.Parallel() + + buyTicketTx := []byte(` + import Foo from 0x1 + + transaction() { + prepare(acct: auth(Storage, Capabilities) &Account) { + Foo.logVaultBalance() + var pool = Foo.borrowLotteryPool()! + pool.buyTickets() + Foo.logVaultBalance() + } + execute {} + } + `) + + nextTransactionLocation := NewTransactionLocationGenerator() + + setupTest := func(t *testing.T) ( + runTransaction func(tx []byte) (logs []string), + ) { + + rt := NewTestInterpreterRuntime() + + accountCodes := map[Location][]byte{} + + address := common.MustBytesToAddress([]byte{0x1}) + + var logs []string + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { + return []Address{address}, nil + }, + OnResolveLocation: NewSingleIdentifierLocationResolver(t), + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnProgramLog: func(message string) { + logs = append(logs, message) + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnEmitEvent: func(event cadence.Event) error { + return nil + }, + } + + runTransaction = func(tx []byte) []string { + + logs = logs[:0] + + err := rt.ExecuteTransaction( + Script{ + Source: tx, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + return logs + } + + return runTransaction + } + + t.Run("non optional vault", func(t *testing.T) { + + t.Parallel() + + contractFoo := ` + access(all) contract Foo { + access(all) resource Vault { + access(all) + var balance: UFix64 + init(balance: UFix64) { + self.balance = balance + } + access(all) fun withdraw(amount: UFix64): @Vault { + self.balance = self.balance - amount + return <-create Vault(balance: amount) + } + access(all) fun deposit(from: @Vault) { + self.balance = self.balance + from.balance + destroy from + } + } + access(all) fun createEmptyVault(): @Vault { + return <- create Vault(balance: 0.0) + } + access(all) resource LotteryPool { + access(contract) + let ftVault: @Vault + init() { + self.ftVault <- Foo.createEmptyVault() + } + access(all) + fun buyTickets() { + self.borrowVault().deposit(from: <- create Vault(balance: 5.0)) + } + access(all) fun buyNewTicket() { + self.borrowVault().deposit(from: <- create Vault(balance: 5.0)) + } + access(self) + view fun borrowVault(): &Vault { + return &self.ftVault as &Vault + } + } + init() { + self.account.storage.save(<- create LotteryPool(), to: /storage/lottery_pool) + } + access(all) fun borrowLotteryPool(): &LotteryPool? { + return self.account.storage.borrow<&LotteryPool>(from: /storage/lottery_pool) + } + access(all) fun logVaultBalance() { + var pool = self.borrowLotteryPool()! + log(pool.ftVault.balance) + } + } + ` + + runTransaction := setupTest(t) + + runTransaction(DeploymentTransaction( + "Foo", + []byte(contractFoo), + )) + + logs := runTransaction(buyTicketTx) + assert.Equal(t, []string{"0.00000000", "5.00000000"}, logs) + + logs = runTransaction(buyTicketTx) + assert.Equal(t, []string{"5.00000000", "10.00000000"}, logs) + }) + + t.Run("optional vault", func(t *testing.T) { + + t.Parallel() + + contractFoo := ` + access(all) contract Foo { + access(all) resource Vault { + access(all) + var balance: UFix64 + init(balance: UFix64) { + self.balance = balance + } + access(all) fun withdraw(amount: UFix64): @Vault { + self.balance = self.balance - amount + return <-create Vault(balance: amount) + } + access(all) fun deposit(from: @Vault) { + self.balance = self.balance + from.balance + destroy from + } + } + access(all) fun createEmptyVault(): @Vault { + return <- create Vault(balance: 0.0) + } + access(all) resource LotteryPool { + access(contract) + let ftVault: @Vault? + init() { + self.ftVault <- Foo.createEmptyVault() + } + access(all) + fun buyTickets() { + self.borrowVault().deposit(from: <- create Vault(balance: 5.0)) + } + access(all) fun buyNewTicket() { + self.borrowVault().deposit(from: <- create Vault(balance: 5.0)) + } + access(self) + view fun borrowVault(): &Vault { + return &self.ftVault as &Vault? ?? panic("Cannot borrow vault") + } + } + init() { + self.account.storage.save(<- create LotteryPool(), to: /storage/lottery_pool) + } + access(all) fun borrowLotteryPool(): &LotteryPool? { + return self.account.storage.borrow<&LotteryPool>(from: /storage/lottery_pool) + } + access(all) fun logVaultBalance() { + var pool = self.borrowLotteryPool()! + log(pool.ftVault!.balance) + } + } + ` + + runTransaction := setupTest(t) + + runTransaction(DeploymentTransaction( + "Foo", + []byte(contractFoo), + )) + + logs := runTransaction(buyTicketTx) + assert.Equal(t, []string{"0.00000000", "5.00000000"}, logs) + + logs = runTransaction(buyTicketTx) + assert.Equal(t, []string{"5.00000000", "10.00000000"}, logs) + }) + + t.Run("deeply nested optional vault", func(t *testing.T) { + contractFoo := ` + access(all) + contract Foo { + access(all) + resource Vault { + access(all) + var balance: UFix64 + init(balance: UFix64) { + self.balance = balance + } + access(all) + fun withdraw(amount: UFix64): @Vault { + self.balance = self.balance - amount + return <-create Vault(balance: amount) + } + access(all) + fun deposit(from: @Vault) { + self.balance = self.balance + from.balance + destroy from + } + } + access(all) + fun createEmptyVault(): @Vault { + return <- create Vault(balance: 0.0) + } + access(all) + resource LotteryPool { + access(contract) + let jackpotPool: @Change + access(contract) + let lotteries: @{UInt64: Lottery} + init() { + self.jackpotPool <- create Change() + self.lotteries <- {0: <- create Lottery()} + } + access(all) + fun buyTickets() { + var lotteryRef = self.borrowLotteryRef()! + lotteryRef.buyNewTicket() + } + access(self) + fun borrowLotteryRef(): &Lottery? { + return &self.lotteries[0] + } + } + access(all) + resource Lottery { + access(contract) + let current: @Change + init() { + self.current <- create Change() + } + access(all) + fun buyNewTicket() { + var change = self.borrowCurrentLotteryChange() + change.forceMerge() + } + access(contract) + view fun borrowCurrentLotteryChange(): &Change { + return &self.current + } + } + access(all) + resource Change { + access(contract) + var ftVault: @Vault? + init() { + self.ftVault <- Foo.createEmptyVault() + } + access(all) + fun forceMerge() { + self.borrowVault().deposit(from: <- create Vault(balance: 5.0)) + } + access(self) + view fun borrowVault(): &Vault { + return &self.ftVault as &Vault? ?? panic("Cannot borrow vault") + } + } + init() { + self.account.storage.save(<- create LotteryPool(), to: /storage/lottery_pool) + } + access(all) + fun borrowLotteryPool(): &LotteryPool? { + return self.account.storage.borrow<&LotteryPool>(from: /storage/lottery_pool) + } + access(all) + fun logVaultBalance() { + var pool = self.borrowLotteryPool()! + log(pool.lotteries[0]!.current.ftVault!.balance) + } + } + ` + + runTransaction := setupTest(t) + + runTransaction(DeploymentTransaction( + "Foo", + []byte(contractFoo), + )) + + logs := runTransaction(buyTicketTx) + assert.Equal(t, []string{"0.00000000", "5.00000000"}, logs) + + logs = runTransaction(buyTicketTx) + assert.Equal(t, []string{"5.00000000", "10.00000000"}, logs) + }) +} From 6120350c4585739aa58075260d3bf13e2f108747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 7 Jan 2025 13:34:14 -0800 Subject: [PATCH 15/80] try to add reproducer tests for some value issues --- runtime/tests/interpreter/interpreter_test.go | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 375143b5f..80ef2e360 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -40,6 +40,7 @@ import ( "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/runtime_utils" . "github.com/onflow/cadence/runtime/tests/utils" ) @@ -12506,3 +12507,226 @@ func TestInterpretOptionalAddressInConditional(t *testing.T) { value, ) } + +func TestRuntimeSomeValueChildContainerMutation2(t *testing.T) { + + t.Parallel() + + ledger := NewTestLedger(nil, nil) + + newInter := func() *interpreter.Interpreter { + + inter, err := parseCheckAndInterpretWithOptions(t, + ` + + struct Foo { + let values: {String: Int}? + + init() { + self.values = {} + } + + fun set(key: String, value: Int) { + if let ref: auth(Mutate) &{String: Int} = &self.values { + ref[key] = value + } + } + + fun get(key: String): Int? { + if let ref: &{String: Int} = &self.values { + return ref[key] + } + return nil + } + } + + fun setup(): Foo { + let foo = Foo() + foo.set(key: "a", value: 1) + return foo + } + + fun update(foo: &Foo): Bool { + if foo.get(key: "a") != 1 { + return false + } + foo.set(key: "a", value: 2) + return true + } + `, + ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + Storage: runtime.NewStorage(ledger, nil), + }, + }, + ) + require.NoError(t, err) + + return inter + } + + inter := newInter() + + foo, err := inter.Invoke("setup") + require.NoError(t, err) + + address := common.MustBytesToAddress([]byte{0x1}) + path := interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo") + + storage := inter.Storage().(*runtime.Storage) + storageMap := storage.GetStorageMap( + address, + path.Domain.Identifier(), + true, + ) + + foo = foo.Transfer( + inter, + interpreter.EmptyLocationRange, + atree.Address(address), + false, + nil, + nil, + true, + ) + storageMap.WriteValue(inter, interpreter.StringStorageMapKey(path.Identifier), foo) + + err = storage.Commit(inter, false) + require.NoError(t, err) + + inter = newInter() + + storage = inter.Storage().(*runtime.Storage) + storageMap = storage.GetStorageMap( + address, + path.Domain.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + ref := interpreter.NewStorageReferenceValue( + nil, + interpreter.UnauthorizedAccess, + address, + path, + nil, + ) + + result, err := inter.Invoke("update", ref) + require.NoError(t, err) + assert.Equal(t, interpreter.TrueValue, result) +} + +func TestRuntimeSomeValueChildContainerMutation3(t *testing.T) { + + t.Parallel() + + ledger := NewTestLedger(nil, nil) + + newInter := func() *interpreter.Interpreter { + + inter, err := parseCheckAndInterpretWithOptions(t, + ` + + resource Bar { + var value: Int + + init() { + self.value = 0 + } + } + + resource Foo { + let bar: @Bar? + + init() { + self.bar <- create Bar() + } + + fun set(value: Int) { + if let ref: &Bar = &self.bar { + ref.value = value + } + } + + fun getValue(): Int? { + return self.bar?.value + } + } + + fun setup(): @Foo { + let foo <- create Foo() + foo.set(value: 1) + return <-foo + } + + fun update(foo: &Foo): Bool { + if foo.getValue() != 1 { + return false + } + foo.set(value: 2) + return true + } + `, + ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + Storage: runtime.NewStorage(ledger, nil), + }, + }, + ) + require.NoError(t, err) + + return inter + } + + inter := newInter() + + foo, err := inter.Invoke("setup") + require.NoError(t, err) + + address := common.MustBytesToAddress([]byte{0x1}) + path := interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo") + + storage := inter.Storage().(*runtime.Storage) + storageMap := storage.GetStorageMap( + address, + path.Domain.Identifier(), + true, + ) + + foo = foo.Transfer( + inter, + interpreter.EmptyLocationRange, + atree.Address(address), + false, + nil, + nil, + true, + ) + storageMap.WriteValue(inter, interpreter.StringStorageMapKey(path.Identifier), foo) + + err = storage.Commit(inter, false) + require.NoError(t, err) + + inter = newInter() + + storage = inter.Storage().(*runtime.Storage) + storageMap = storage.GetStorageMap( + address, + path.Domain.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + ref := interpreter.NewStorageReferenceValue( + nil, + interpreter.UnauthorizedAccess, + address, + path, + nil, + ) + + result, err := inter.Invoke("update", ref) + require.NoError(t, err) + assert.Equal(t, interpreter.TrueValue, result) +} From 684ca4d0b4fc740ccf8cf40765dda46216a95a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 7 Jan 2025 14:11:03 -0800 Subject: [PATCH 16/80] update values again, improve test names, add test for arrays --- runtime/tests/interpreter/interpreter_test.go | 228 +++++++++++++++++- 1 file changed, 226 insertions(+), 2 deletions(-) diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 80ef2e360..58392e3ba 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -12508,7 +12508,7 @@ func TestInterpretOptionalAddressInConditional(t *testing.T) { ) } -func TestRuntimeSomeValueChildContainerMutation2(t *testing.T) { +func TestInterpretSomeValueChildContainerMutation_Dictionary(t *testing.T) { t.Parallel() @@ -12553,6 +12553,14 @@ func TestRuntimeSomeValueChildContainerMutation2(t *testing.T) { foo.set(key: "a", value: 2) return true } + + fun updateAgain(foo: &Foo): Bool { + if foo.get(key: "a") != 2 { + return false + } + foo.set(key: "a", value: 3) + return true + } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ @@ -12565,6 +12573,8 @@ func TestRuntimeSomeValueChildContainerMutation2(t *testing.T) { return inter } + // Setup + inter := newInter() foo, err := inter.Invoke("setup") @@ -12594,6 +12604,8 @@ func TestRuntimeSomeValueChildContainerMutation2(t *testing.T) { err = storage.Commit(inter, false) require.NoError(t, err) + // Update + inter = newInter() storage = inter.Storage().(*runtime.Storage) @@ -12615,9 +12627,33 @@ func TestRuntimeSomeValueChildContainerMutation2(t *testing.T) { result, err := inter.Invoke("update", ref) require.NoError(t, err) assert.Equal(t, interpreter.TrueValue, result) + + // Update again + + inter = newInter() + + storage = inter.Storage().(*runtime.Storage) + storageMap = storage.GetStorageMap( + address, + path.Domain.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + ref = interpreter.NewStorageReferenceValue( + nil, + interpreter.UnauthorizedAccess, + address, + path, + nil, + ) + + result, err = inter.Invoke("updateAgain", ref) + require.NoError(t, err) + assert.Equal(t, interpreter.TrueValue, result) } -func TestRuntimeSomeValueChildContainerMutation3(t *testing.T) { +func TestInterpretSomeValueChildContainerMutation_Composite(t *testing.T) { t.Parallel() @@ -12667,6 +12703,166 @@ func TestRuntimeSomeValueChildContainerMutation3(t *testing.T) { foo.set(value: 2) return true } + + fun updateAgain(foo: &Foo): Bool { + if foo.getValue() != 2 { + return false + } + foo.set(value: 3) + return true + } + `, + ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + Storage: runtime.NewStorage(ledger, nil), + }, + }, + ) + require.NoError(t, err) + + return inter + } + + // Setup + + inter := newInter() + + foo, err := inter.Invoke("setup") + require.NoError(t, err) + + address := common.MustBytesToAddress([]byte{0x1}) + path := interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo") + + storage := inter.Storage().(*runtime.Storage) + storageMap := storage.GetStorageMap( + address, + path.Domain.Identifier(), + true, + ) + + foo = foo.Transfer( + inter, + interpreter.EmptyLocationRange, + atree.Address(address), + false, + nil, + nil, + true, + ) + storageMap.WriteValue(inter, interpreter.StringStorageMapKey(path.Identifier), foo) + + err = storage.Commit(inter, false) + require.NoError(t, err) + + // Update + + inter = newInter() + + storage = inter.Storage().(*runtime.Storage) + storageMap = storage.GetStorageMap( + address, + path.Domain.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + ref := interpreter.NewStorageReferenceValue( + nil, + interpreter.UnauthorizedAccess, + address, + path, + nil, + ) + + result, err := inter.Invoke("update", ref) + require.NoError(t, err) + assert.Equal(t, interpreter.TrueValue, result) + + err = storage.Commit(inter, false) + require.NoError(t, err) + + // Update again + + inter = newInter() + + storage = inter.Storage().(*runtime.Storage) + storageMap = storage.GetStorageMap( + address, + path.Domain.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + ref = interpreter.NewStorageReferenceValue( + nil, + interpreter.UnauthorizedAccess, + address, + path, + nil, + ) + + result, err = inter.Invoke("updateAgain", ref) + require.NoError(t, err) + assert.Equal(t, interpreter.TrueValue, result) +} + +func TestInterpretSomeValueChildContainerMutation_Array(t *testing.T) { + + t.Parallel() + + ledger := NewTestLedger(nil, nil) + + newInter := func() *interpreter.Interpreter { + + inter, err := parseCheckAndInterpretWithOptions(t, + ` + + struct Foo { + let values: [Int]? + + init() { + self.values = [] + } + + fun set(value: Int) { + if let ref: auth(Mutate) &[Int] = &self.values { + if ref.length == 0 { + ref.append(value) + } else { + ref[0] = value + } + } + } + + fun getValue(): Int? { + if let ref: &[Int] = &self.values { + return ref[0] + } + return nil + } + } + + fun setup(): Foo { + let foo = Foo() + foo.set(value: 1) + return foo + } + + fun update(foo: &Foo): Bool { + if foo.getValue() != 1 { + return false + } + foo.set(value: 2) + return true + } + + fun updateAgain(foo: &Foo): Bool { + if foo.getValue() != 2 { + return false + } + foo.set(value: 3) + return true + } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ @@ -12679,6 +12875,8 @@ func TestRuntimeSomeValueChildContainerMutation3(t *testing.T) { return inter } + // Setup + inter := newInter() foo, err := inter.Invoke("setup") @@ -12708,6 +12906,8 @@ func TestRuntimeSomeValueChildContainerMutation3(t *testing.T) { err = storage.Commit(inter, false) require.NoError(t, err) + // Update + inter = newInter() storage = inter.Storage().(*runtime.Storage) @@ -12729,4 +12929,28 @@ func TestRuntimeSomeValueChildContainerMutation3(t *testing.T) { result, err := inter.Invoke("update", ref) require.NoError(t, err) assert.Equal(t, interpreter.TrueValue, result) + + // Update again + + inter = newInter() + + storage = inter.Storage().(*runtime.Storage) + storageMap = storage.GetStorageMap( + address, + path.Domain.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + ref = interpreter.NewStorageReferenceValue( + nil, + interpreter.UnauthorizedAccess, + address, + path, + nil, + ) + + result, err = inter.Invoke("updateAgain", ref) + require.NoError(t, err) + assert.Equal(t, interpreter.TrueValue, result) } From 13f79cd778364f90f6dca95a94f7a23eaacf188c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 7 Jan 2025 14:27:45 -0800 Subject: [PATCH 17/80] add missing commits --- runtime/tests/interpreter/interpreter_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 58392e3ba..641f24c2e 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -12628,6 +12628,9 @@ func TestInterpretSomeValueChildContainerMutation_Dictionary(t *testing.T) { require.NoError(t, err) assert.Equal(t, interpreter.TrueValue, result) + err = storage.Commit(inter, false) + require.NoError(t, err) + // Update again inter = newInter() @@ -12930,6 +12933,9 @@ func TestInterpretSomeValueChildContainerMutation_Array(t *testing.T) { require.NoError(t, err) assert.Equal(t, interpreter.TrueValue, result) + err = storage.Commit(inter, false) + require.NoError(t, err) + // Update again inter = newInter() From 5e0df33594a6907a38a2e056854e6b9eb58eca1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 7 Jan 2025 16:19:24 -0800 Subject: [PATCH 18/80] refactor tests, add variants with doubly-nested optionals --- runtime/tests/interpreter/interpreter_test.go | 592 +++++++++--------- 1 file changed, 286 insertions(+), 306 deletions(-) diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 641f24c2e..62d98026e 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -12508,33 +12508,184 @@ func TestInterpretOptionalAddressInConditional(t *testing.T) { ) } -func TestInterpretSomeValueChildContainerMutation_Dictionary(t *testing.T) { +func TestInterpretSomeValueChildContainerMutation(t *testing.T) { t.Parallel() - ledger := NewTestLedger(nil, nil) + test := func(t *testing.T, code string) { - newInter := func() *interpreter.Interpreter { + t.Parallel() - inter, err := parseCheckAndInterpretWithOptions(t, - ` + ledger := NewTestLedger(nil, nil) + + newInter := func() *interpreter.Interpreter { + + inter, err := parseCheckAndInterpretWithOptions(t, + code, + ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + Storage: runtime.NewStorage(ledger, nil), + }, + }, + ) + require.NoError(t, err) + + return inter + } + + // Setup + + inter := newInter() + + foo, err := inter.Invoke("setup") + require.NoError(t, err) + + address := common.MustBytesToAddress([]byte{0x1}) + path := interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo") + + storage := inter.Storage().(*runtime.Storage) + storageMap := storage.GetStorageMap( + address, + path.Domain.Identifier(), + true, + ) + + foo = foo.Transfer( + inter, + interpreter.EmptyLocationRange, + atree.Address(address), + false, + nil, + nil, + true, + ) + storageMap.WriteValue(inter, interpreter.StringStorageMapKey(path.Identifier), foo) + + err = storage.Commit(inter, false) + require.NoError(t, err) + + // Update + + inter = newInter() + + storage = inter.Storage().(*runtime.Storage) + storageMap = storage.GetStorageMap( + address, + path.Domain.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + ref := interpreter.NewStorageReferenceValue( + nil, + interpreter.UnauthorizedAccess, + address, + path, + nil, + ) + + result, err := inter.Invoke("update", ref) + require.NoError(t, err) + assert.Equal(t, interpreter.TrueValue, result) + + err = storage.Commit(inter, false) + require.NoError(t, err) + + // Update again + + inter = newInter() + + storage = inter.Storage().(*runtime.Storage) + storageMap = storage.GetStorageMap( + address, + path.Domain.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + ref = interpreter.NewStorageReferenceValue( + nil, + interpreter.UnauthorizedAccess, + address, + path, + nil, + ) + + result, err = inter.Invoke("updateAgain", ref) + require.NoError(t, err) + assert.Equal(t, interpreter.TrueValue, result) + } + t.Run("dictionary, one level", func(t *testing.T) { + + test(t, ` + struct Foo { + let values: {String: Int}? + + init() { + self.values = {} + } + + fun set(key: String, value: Int) { + if let ref: auth(Mutate) &{String: Int} = &self.values { + ref[key] = value + } + } + + fun get(key: String): Int? { + if let ref: &{String: Int} = &self.values { + return ref[key] + } + return nil + } + } + + fun setup(): Foo { + let foo = Foo() + foo.set(key: "a", value: 1) + return foo + } + + fun update(foo: &Foo): Bool { + if foo.get(key: "a") != 1 { + return false + } + foo.set(key: "a", value: 2) + return true + } + + fun updateAgain(foo: &Foo): Bool { + if foo.get(key: "a") != 2 { + return false + } + foo.set(key: "a", value: 3) + return true + } + `) + }) + + t.Run("dictionary, two levels", func(t *testing.T) { + test(t, ` struct Foo { - let values: {String: Int}? + let values: {String: Int}?? init() { self.values = {} } fun set(key: String, value: Int) { - if let ref: auth(Mutate) &{String: Int} = &self.values { - ref[key] = value + if let optRef: auth(Mutate) &{String: Int}? = &self.values { + if let ref: auth(Mutate) &{String: Int} = optRef { + ref[key] = value + } } } fun get(key: String): Int? { - if let ref: &{String: Int} = &self.values { - return ref[key] + if let optRef: &{String: Int}? = &self.values { + if let ref: &{String: Int} = optRef { + return ref[key] + } } return nil } @@ -12550,7 +12701,7 @@ func TestInterpretSomeValueChildContainerMutation_Dictionary(t *testing.T) { if foo.get(key: "a") != 1 { return false } - foo.set(key: "a", value: 2) + foo.set(key: "a", value: 2) return true } @@ -12558,114 +12709,15 @@ func TestInterpretSomeValueChildContainerMutation_Dictionary(t *testing.T) { if foo.get(key: "a") != 2 { return false } - foo.set(key: "a", value: 3) + foo.set(key: "a", value: 3) return true } - `, - ParseCheckAndInterpretOptions{ - Config: &interpreter.Config{ - Storage: runtime.NewStorage(ledger, nil), - }, - }, - ) - require.NoError(t, err) - - return inter - } - - // Setup - - inter := newInter() - - foo, err := inter.Invoke("setup") - require.NoError(t, err) - - address := common.MustBytesToAddress([]byte{0x1}) - path := interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo") - - storage := inter.Storage().(*runtime.Storage) - storageMap := storage.GetStorageMap( - address, - path.Domain.Identifier(), - true, - ) - - foo = foo.Transfer( - inter, - interpreter.EmptyLocationRange, - atree.Address(address), - false, - nil, - nil, - true, - ) - storageMap.WriteValue(inter, interpreter.StringStorageMapKey(path.Identifier), foo) - - err = storage.Commit(inter, false) - require.NoError(t, err) - - // Update - - inter = newInter() - - storage = inter.Storage().(*runtime.Storage) - storageMap = storage.GetStorageMap( - address, - path.Domain.Identifier(), - false, - ) - require.NotNil(t, storageMap) - - ref := interpreter.NewStorageReferenceValue( - nil, - interpreter.UnauthorizedAccess, - address, - path, - nil, - ) - - result, err := inter.Invoke("update", ref) - require.NoError(t, err) - assert.Equal(t, interpreter.TrueValue, result) - - err = storage.Commit(inter, false) - require.NoError(t, err) - - // Update again - - inter = newInter() - - storage = inter.Storage().(*runtime.Storage) - storageMap = storage.GetStorageMap( - address, - path.Domain.Identifier(), - false, - ) - require.NotNil(t, storageMap) - - ref = interpreter.NewStorageReferenceValue( - nil, - interpreter.UnauthorizedAccess, - address, - path, - nil, - ) - - result, err = inter.Invoke("updateAgain", ref) - require.NoError(t, err) - assert.Equal(t, interpreter.TrueValue, result) -} - -func TestInterpretSomeValueChildContainerMutation_Composite(t *testing.T) { - - t.Parallel() - - ledger := NewTestLedger(nil, nil) + `) + }) - newInter := func() *interpreter.Interpreter { + t.Run("resource, one level", func(t *testing.T) { - inter, err := parseCheckAndInterpretWithOptions(t, - ` + test(t, ` resource Bar { var value: Int @@ -12703,7 +12755,7 @@ func TestInterpretSomeValueChildContainerMutation_Composite(t *testing.T) { if foo.getValue() != 1 { return false } - foo.set(value: 2) + foo.set(value: 2) return true } @@ -12711,151 +12763,59 @@ func TestInterpretSomeValueChildContainerMutation_Composite(t *testing.T) { if foo.getValue() != 2 { return false } - foo.set(value: 3) + foo.set(value: 3) return true } - `, - ParseCheckAndInterpretOptions{ - Config: &interpreter.Config{ - Storage: runtime.NewStorage(ledger, nil), - }, - }, - ) - require.NoError(t, err) - - return inter - } - - // Setup - - inter := newInter() - - foo, err := inter.Invoke("setup") - require.NoError(t, err) - - address := common.MustBytesToAddress([]byte{0x1}) - path := interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo") - - storage := inter.Storage().(*runtime.Storage) - storageMap := storage.GetStorageMap( - address, - path.Domain.Identifier(), - true, - ) - - foo = foo.Transfer( - inter, - interpreter.EmptyLocationRange, - atree.Address(address), - false, - nil, - nil, - true, - ) - storageMap.WriteValue(inter, interpreter.StringStorageMapKey(path.Identifier), foo) - - err = storage.Commit(inter, false) - require.NoError(t, err) - - // Update - - inter = newInter() - - storage = inter.Storage().(*runtime.Storage) - storageMap = storage.GetStorageMap( - address, - path.Domain.Identifier(), - false, - ) - require.NotNil(t, storageMap) - - ref := interpreter.NewStorageReferenceValue( - nil, - interpreter.UnauthorizedAccess, - address, - path, - nil, - ) - - result, err := inter.Invoke("update", ref) - require.NoError(t, err) - assert.Equal(t, interpreter.TrueValue, result) - - err = storage.Commit(inter, false) - require.NoError(t, err) - - // Update again - - inter = newInter() - - storage = inter.Storage().(*runtime.Storage) - storageMap = storage.GetStorageMap( - address, - path.Domain.Identifier(), - false, - ) - require.NotNil(t, storageMap) - - ref = interpreter.NewStorageReferenceValue( - nil, - interpreter.UnauthorizedAccess, - address, - path, - nil, - ) - - result, err = inter.Invoke("updateAgain", ref) - require.NoError(t, err) - assert.Equal(t, interpreter.TrueValue, result) -} + `) -func TestInterpretSomeValueChildContainerMutation_Array(t *testing.T) { + }) - t.Parallel() + t.Run("resource, two levels", func(t *testing.T) { - ledger := NewTestLedger(nil, nil) + test(t, ` - newInter := func() *interpreter.Interpreter { + resource Bar { + var value: Int - inter, err := parseCheckAndInterpretWithOptions(t, - ` + init() { + self.value = 0 + } + } - struct Foo { - let values: [Int]? + resource Foo { + let bar: @Bar?? init() { - self.values = [] + self.bar <- create Bar() } fun set(value: Int) { - if let ref: auth(Mutate) &[Int] = &self.values { - if ref.length == 0 { - ref.append(value) - } else { - ref[0] = value + if let optRef: &Bar? = &self.bar { + if let ref = optRef { + ref.value = value } } } fun getValue(): Int? { - if let ref: &[Int] = &self.values { - return ref[0] + if let optRef: &Bar? = &self.bar { + return optRef?.value } return nil } } - fun setup(): Foo { - let foo = Foo() + fun setup(): @Foo { + let foo <- create Foo() foo.set(value: 1) - return foo + return <-foo } fun update(foo: &Foo): Bool { if foo.getValue() != 1 { return false } - foo.set(value: 2) + foo.set(value: 2) return true } @@ -12863,100 +12823,120 @@ func TestInterpretSomeValueChildContainerMutation_Array(t *testing.T) { if foo.getValue() != 2 { return false } - foo.set(value: 3) + foo.set(value: 3) return true } - `, - ParseCheckAndInterpretOptions{ - Config: &interpreter.Config{ - Storage: runtime.NewStorage(ledger, nil), - }, - }, - ) - require.NoError(t, err) + `) + }) - return inter - } + t.Run("array, one level", func(t *testing.T) { - // Setup + test(t, ` - inter := newInter() + struct Foo { + let values: [Int]? - foo, err := inter.Invoke("setup") - require.NoError(t, err) + init() { + self.values = [] + } - address := common.MustBytesToAddress([]byte{0x1}) - path := interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo") + fun set(value: Int) { + if let ref: auth(Mutate) &[Int] = &self.values { + if ref.length == 0 { + ref.append(value) + } else { + ref[0] = value + } + } + } - storage := inter.Storage().(*runtime.Storage) - storageMap := storage.GetStorageMap( - address, - path.Domain.Identifier(), - true, - ) + fun getValue(): Int? { + if let ref: &[Int] = &self.values { + return ref[0] + } + return nil + } + } - foo = foo.Transfer( - inter, - interpreter.EmptyLocationRange, - atree.Address(address), - false, - nil, - nil, - true, - ) - storageMap.WriteValue(inter, interpreter.StringStorageMapKey(path.Identifier), foo) + fun setup(): Foo { + let foo = Foo() + foo.set(value: 1) + return foo + } - err = storage.Commit(inter, false) - require.NoError(t, err) + fun update(foo: &Foo): Bool { + if foo.getValue() != 1 { + return false + } + foo.set(value: 2) + return true + } - // Update + fun updateAgain(foo: &Foo): Bool { + if foo.getValue() != 2 { + return false + } + foo.set(value: 3) + return true + } + `) - inter = newInter() + }) - storage = inter.Storage().(*runtime.Storage) - storageMap = storage.GetStorageMap( - address, - path.Domain.Identifier(), - false, - ) - require.NotNil(t, storageMap) + t.Run("array, two levels", func(t *testing.T) { - ref := interpreter.NewStorageReferenceValue( - nil, - interpreter.UnauthorizedAccess, - address, - path, - nil, - ) + test(t, ` - result, err := inter.Invoke("update", ref) - require.NoError(t, err) - assert.Equal(t, interpreter.TrueValue, result) + struct Foo { + let values: [Int]?? - err = storage.Commit(inter, false) - require.NoError(t, err) + init() { + self.values = [] + } - // Update again + fun set(value: Int) { + if let optRef: auth(Mutate) &[Int]? = &self.values { + if let ref = optRef { + if ref.length == 0 { + ref.append(value) + } else { + ref[0] = value + } + } + } + } - inter = newInter() + fun getValue(): Int? { + if let optRef: &[Int]? = &self.values { + if let ref = optRef { + return ref[0] + } + } + return nil + } + } - storage = inter.Storage().(*runtime.Storage) - storageMap = storage.GetStorageMap( - address, - path.Domain.Identifier(), - false, - ) - require.NotNil(t, storageMap) + fun setup(): Foo { + let foo = Foo() + foo.set(value: 1) + return foo + } - ref = interpreter.NewStorageReferenceValue( - nil, - interpreter.UnauthorizedAccess, - address, - path, - nil, - ) + fun update(foo: &Foo): Bool { + if foo.getValue() != 1 { + return false + } + foo.set(value: 2) + return true + } - result, err = inter.Invoke("updateAgain", ref) - require.NoError(t, err) - assert.Equal(t, interpreter.TrueValue, result) + fun updateAgain(foo: &Foo): Bool { + if foo.getValue() != 2 { + return false + } + foo.set(value: 3) + return true + } + `) + }) } From e44871d50591922542ba1238bc4de628c983dfa6 Mon Sep 17 00:00:00 2001 From: Joe Sprowes Date: Wed, 8 Jan 2025 10:22:16 -0600 Subject: [PATCH 19/80] limit memory allocations during bit shift operations for Int128, UInt128, Int256, UInt256, Word128, and Word256. --- runtime/interpreter/errors.go | 16 +- runtime/interpreter/value.go | 224 ++++---- runtime/tests/interpreter/bitwise_test.go | 522 ++++++++++++++++++ .../tests/interpreter/memory_metering_test.go | 16 +- 4 files changed, 659 insertions(+), 119 deletions(-) diff --git a/runtime/interpreter/errors.go b/runtime/interpreter/errors.go index 331e0db9b..6fa3ea9f1 100644 --- a/runtime/interpreter/errors.go +++ b/runtime/interpreter/errors.go @@ -299,7 +299,21 @@ func (e UnderflowError) Error() string { return "underflow" } -// UnderflowError +// NegativeShiftError + +type NegativeShiftError struct { + LocationRange +} + +var _ errors.UserError = NegativeShiftError{} + +func (NegativeShiftError) IsUserError() {} + +func (e NegativeShiftError) Error() string { + return "negative shift" +} + +// DivisionByZeroError type DivisionByZeroError struct { LocationRange diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 3cb02f466..754078395 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -8029,16 +8029,17 @@ func (v Int128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVal } if o.BigInt.Sign() < 0 { - panic(UnderflowError{ + panic(NegativeShiftError{ LocationRange: locationRange, }) } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) + if o.BigInt.Cmp(big.NewInt(128)) == 1 { + return NewInt128ValueFromUint64(interpreter, 0) } + // Add usage for possible intermediate value. + common.UseMemory(interpreter, Int128MemoryUsage) + valueGetter := func() *big.Int { res := new(big.Int) res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) @@ -8060,14 +8061,12 @@ func (v Int128Value) BitwiseRightShift(interpreter *Interpreter, other IntegerVa } if o.BigInt.Sign() < 0 { - panic(UnderflowError{ + panic(NegativeShiftError{ LocationRange: locationRange, }) } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) + if o.BigInt.Cmp(big.NewInt(128)) == 1 { + return NewInt128ValueFromUint64(interpreter, 0) } valueGetter := func() *big.Int { @@ -8771,18 +8770,20 @@ func (v Int256Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVal }) } + if o.BigInt.Sign() < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + if o.BigInt.Cmp(big.NewInt(256)) == 1 { + return NewInt256ValueFromUint64(interpreter, 0) + } + + // Add usage for possible intermediate value. + common.UseMemory(interpreter, Int256MemoryUsage) + valueGetter := func() *big.Int { res := new(big.Int) - if o.BigInt.Sign() < 0 { - panic(UnderflowError{ - LocationRange: locationRange, - }) - } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) - } res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) return res @@ -8802,18 +8803,17 @@ func (v Int256Value) BitwiseRightShift(interpreter *Interpreter, other IntegerVa }) } + if o.BigInt.Sign() < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + if o.BigInt.Cmp(big.NewInt(256)) == 1 { + return NewInt256ValueFromUint64(interpreter, 0) + } + valueGetter := func() *big.Int { res := new(big.Int) - if o.BigInt.Sign() < 0 { - panic(UnderflowError{ - LocationRange: locationRange, - }) - } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) - } res.Rsh(v.BigInt, uint(o.BigInt.Uint64())) return res } @@ -12318,20 +12318,22 @@ func (v UInt128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa }) } + if o.BigInt.Sign() < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + if o.BigInt.Cmp(big.NewInt(128)) == 1 { + return NewUInt128ValueFromUint64(interpreter, 0) + } + + // Add usage for possible intermediate value. + common.UseMemory(interpreter, Uint128MemoryUsage) + return NewUInt128ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - if o.BigInt.Sign() < 0 { - panic(UnderflowError{ - LocationRange: locationRange, - }) - } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) - } return res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) }, ) @@ -12348,20 +12350,19 @@ func (v UInt128Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV }) } + if o.BigInt.Sign() < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + if o.BigInt.Cmp(big.NewInt(128)) == 1 { + return NewUInt128ValueFromUint64(interpreter, 0) + } + return NewUInt128ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - if o.BigInt.Sign() < 0 { - panic(UnderflowError{ - LocationRange: locationRange, - }) - } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) - } return res.Rsh(v.BigInt, uint(o.BigInt.Uint64())) }, ) @@ -12994,20 +12995,22 @@ func (v UInt256Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa }) } + if o.BigInt.Sign() < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + if o.BigInt.Cmp(big.NewInt(256)) == 1 { + return NewUInt256ValueFromUint64(interpreter, 0) + } + + // Add usage for possible intermediate value. + common.UseMemory(interpreter, Uint256MemoryUsage) + return NewUInt256ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - if o.BigInt.Sign() < 0 { - panic(UnderflowError{ - LocationRange: locationRange, - }) - } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) - } return res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) }, ) @@ -13024,20 +13027,19 @@ func (v UInt256Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV }) } + if o.BigInt.Sign() < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + if o.BigInt.Cmp(big.NewInt(256)) == 1 { + return NewUInt256ValueFromUint64(interpreter, 0) + } + return NewUInt256ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - if o.BigInt.Sign() < 0 { - panic(UnderflowError{ - LocationRange: locationRange, - }) - } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) - } return res.Rsh(v.BigInt, uint(o.BigInt.Uint64())) }, ) @@ -15358,20 +15360,22 @@ func (v Word128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa }) } + if o.BigInt.Sign() < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + if o.BigInt.Cmp(big.NewInt(128)) == 1 { + return NewWord128ValueFromUint64(interpreter, 0) + } + + // Add usage for possible intermediate value. + common.UseMemory(interpreter, Uint128MemoryUsage) + return NewWord128ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - if o.BigInt.Sign() < 0 { - panic(UnderflowError{ - LocationRange: locationRange, - }) - } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) - } return res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) }, ) @@ -15387,20 +15391,19 @@ func (v Word128Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV }) } + if o.BigInt.Sign() < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + if o.BigInt.Cmp(big.NewInt(128)) == 1 { + return NewWord128ValueFromUint64(interpreter, 0) + } + return NewWord128ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - if o.BigInt.Sign() < 0 { - panic(UnderflowError{ - LocationRange: locationRange, - }) - } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) - } return res.Rsh(v.BigInt, uint(o.BigInt.Uint64())) }, ) @@ -15939,20 +15942,22 @@ func (v Word256Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa }) } + if o.BigInt.Sign() < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + if o.BigInt.Cmp(big.NewInt(256)) == 1 { + return NewWord256ValueFromUint64(interpreter, 0) + } + + // Add usage for possible intermediate value. + common.UseMemory(interpreter, Uint256MemoryUsage) + return NewWord256ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - if o.BigInt.Sign() < 0 { - panic(UnderflowError{ - LocationRange: locationRange, - }) - } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) - } return res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) }, ) @@ -15969,20 +15974,19 @@ func (v Word256Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV }) } + if o.BigInt.Sign() < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + if o.BigInt.Cmp(big.NewInt(256)) == 1 { + return NewWord256ValueFromUint64(interpreter, 0) + } + return NewWord256ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - if o.BigInt.Sign() < 0 { - panic(UnderflowError{ - LocationRange: locationRange, - }) - } - if !o.BigInt.IsUint64() { - panic(OverflowError{ - LocationRange: locationRange, - }) - } return res.Rsh(v.BigInt, uint(o.BigInt.Uint64())) }, ) diff --git a/runtime/tests/interpreter/bitwise_test.go b/runtime/tests/interpreter/bitwise_test.go index 8c71f7fda..b630639ee 100644 --- a/runtime/tests/interpreter/bitwise_test.go +++ b/runtime/tests/interpreter/bitwise_test.go @@ -22,6 +22,8 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" . "github.com/onflow/cadence/runtime/tests/utils" @@ -251,3 +253,523 @@ func TestInterpretBitwiseRightShift(t *testing.T) { }) } } + +func TestInterpretBitwiseLeftShift128(t *testing.T) { + + t.Parallel() + + t.Run("Int128 << 130", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int128 = 0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + let b: Int128 = 130 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt128ValueFromInt64(int64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int128 << 32", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int128 = 0x7fff_ffff + let b: Int128 = 32 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt128ValueFromInt64(int64(0x7fff_ffff_0000_0000)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int128 << -3", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + fun test() { + let a: Int128 = 0x7fff_ffff + let b: Int128 = -3 + let c = a << b + } + `) + _, err := inter.Invoke("test") + RequireError(t, err) + + var shiftErr interpreter.NegativeShiftError + require.ErrorAs(t, err, &shiftErr) + }) + + t.Run("UInt128 << 130", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: UInt128 = 0x7fff_ffff + let b: UInt128 = 130 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredUInt128ValueFromUint64(uint64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("UInt128 << 32", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: UInt128 = 0xffff_ffff + let b: UInt128 = 32 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredUInt128ValueFromUint64(uint64(0xffff_ffff_0000_0000)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Word128 << 130", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Word128 = 0xffff_ffff_ffff_ffff + let b: Word128 = 130 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredWord128ValueFromUint64(uint64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Word128 << 32", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Word128 = 0xffff_ffff + let b: Word128 = 32 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredWord128ValueFromUint64(uint64(0xffff_ffff_0000_0000)), + inter.Globals.Get("c").GetValue(inter), + ) + }) +} + +func TestInterpretBitwiseLeftShift256(t *testing.T) { + + t.Parallel() + + t.Run("Int256 << 260", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int256 = 0x7fff_ffff + let b: Int256 = 260 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt256ValueFromInt64(int64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int256 << 192", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int256 = 0x7fff_ffff + let b: Int256 = 32 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt256ValueFromInt64(int64(0x7fff_ffff_0000_0000)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int256 << -3", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + fun test() { + let a: Int256 = 0x7fff_ffff + let b: Int256 = -3 + let c = a << b + } + `) + _, err := inter.Invoke("test") + RequireError(t, err) + + var shiftErr interpreter.NegativeShiftError + require.ErrorAs(t, err, &shiftErr) + }) + + t.Run("UInt256 << 260", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: UInt256 = 0x7fff_ffff + let b: UInt256 = 260 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredUInt256ValueFromUint64(uint64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("UInt256 << 192", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: UInt256 = 0x7fff_ffff + let b: UInt256 = 32 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredUInt256ValueFromUint64(uint64(0x7fff_ffff_0000_0000)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Word256 << 260", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Word256 = 0x7fff_ffff + let b: Word256 = 260 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredWord256ValueFromUint64(uint64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Word256 << 192", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Word256 = 0x7fff_ffff + let b: Word256 = 32 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredWord256ValueFromUint64(uint64(0x7fff_ffff_0000_0000)), + inter.Globals.Get("c").GetValue(inter), + ) + }) +} + +func TestInterpretBitwiseRightShift128(t *testing.T) { + + t.Parallel() + + t.Run("Int128 >> 130", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int128 = 0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + let b: Int128 = 130 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt128ValueFromInt64(int64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int128 >> 32", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int128 = 0x7fff_ffff_0000_0000 + let b: Int128 = 32 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt128ValueFromInt64(int64(0x7fff_ffff)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int128 >> -3", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + fun test() { + let a: Int128 = 0x7fff_ffff + let b: Int128 = -3 + let c = a >> b + } + `) + _, err := inter.Invoke("test") + RequireError(t, err) + + var shiftErr interpreter.NegativeShiftError + require.ErrorAs(t, err, &shiftErr) + }) + + t.Run("UInt128 >> 130", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: UInt128 = 0x7fff_ffff + let b: UInt128 = 130 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredUInt128ValueFromUint64(uint64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("UInt128 >> 32", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: UInt128 = 0xffff_ffff_0000_0000 + let b: UInt128 = 32 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredUInt128ValueFromUint64(uint64(0xffff_ffff)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Word128 >> 130", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Word128 = 0xffff_ffff_ffff_ffff + let b: Word128 = 130 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredWord128ValueFromUint64(uint64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Word128 >> 32", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Word128 = 0xffff_ffff_0000_0000 + let b: Word128 = 32 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredWord128ValueFromUint64(uint64(0xffff_ffff)), + inter.Globals.Get("c").GetValue(inter), + ) + }) +} + +func TestInterpretBitwiseRightShift256(t *testing.T) { + + t.Parallel() + + t.Run("Int256 >> 260", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int256 = 0x7fff_ffff + let b: Int256 = 260 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt256ValueFromInt64(int64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int256 >> 32", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int256 = 0x7fff_ffff_0000_0000 + let b: Int256 = 32 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt256ValueFromInt64(int64(0x7fff_ffff)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int256 >> -3", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + fun test() { + let a: Int256 = 0x7fff_ffff + let b: Int256 = -3 + let c = a >> b + } + `) + _, err := inter.Invoke("test") + RequireError(t, err) + + var shiftErr interpreter.NegativeShiftError + require.ErrorAs(t, err, &shiftErr) + }) + + t.Run("UInt256 >> 260", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: UInt256 = 0x7fff_ffff + let b: UInt256 = 260 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredUInt256ValueFromUint64(uint64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("UInt256 >> 32", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: UInt256 = 0x7fff_ffff_0000_0000 + let b: UInt256 = 32 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredUInt256ValueFromUint64(uint64(0x7fff_ffff)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Word256 >> 260", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Word256 = 0x7fff_ffff + let b: Word256 = 260 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredWord256ValueFromUint64(uint64(0)), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Word256 >> 32", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Word256 = 0x7fff_ffff_0000_0000 + let b: Word256 = 32 + let c = a >> b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredWord256ValueFromUint64(uint64(0x7fff_ffff)), + inter.Globals.Get("c").GetValue(inter), + ) + }) +} diff --git a/runtime/tests/interpreter/memory_metering_test.go b/runtime/tests/interpreter/memory_metering_test.go index 43a2f7788..0c60f3382 100644 --- a/runtime/tests/interpreter/memory_metering_test.go +++ b/runtime/tests/interpreter/memory_metering_test.go @@ -3289,9 +3289,9 @@ func TestInterpretUInt128Metering(t *testing.T) { _, err := inter.Invoke("main") require.NoError(t, err) - // creation: 8 + 8 + // creation: 8 + 8 + 16 // result: 16 - assert.Equal(t, uint64(32), meter.getMemory(common.MemoryKindBigInt)) + assert.Equal(t, uint64(48), meter.getMemory(common.MemoryKindBigInt)) }) t.Run("bitwise right-shift", func(t *testing.T) { @@ -3587,9 +3587,9 @@ func TestInterpretUInt256Metering(t *testing.T) { _, err := inter.Invoke("main") require.NoError(t, err) - // creation: 8 + 8 + // creation: 8 + 8 + 32 // result: 32 - assert.Equal(t, uint64(48), meter.getMemory(common.MemoryKindBigInt)) + assert.Equal(t, uint64(80), meter.getMemory(common.MemoryKindBigInt)) }) t.Run("bitwise right-shift", func(t *testing.T) { @@ -5308,9 +5308,9 @@ func TestInterpretInt128Metering(t *testing.T) { _, err := inter.Invoke("main") require.NoError(t, err) - // two literals: 8 + 8 + // two literals: 8 + 8 + 16 // result: 16 - assert.Equal(t, uint64(32), meter.getMemory(common.MemoryKindBigInt)) + assert.Equal(t, uint64(48), meter.getMemory(common.MemoryKindBigInt)) }) t.Run("bitwise right shift", func(t *testing.T) { @@ -5677,9 +5677,9 @@ func TestInterpretInt256Metering(t *testing.T) { _, err := inter.Invoke("main") require.NoError(t, err) - // two literals: 8 + 8 + // two literals: 8 + 8 + 32 // result: 32 - assert.Equal(t, uint64(48), meter.getMemory(common.MemoryKindBigInt)) + assert.Equal(t, uint64(80), meter.getMemory(common.MemoryKindBigInt)) }) t.Run("bitwise right shift", func(t *testing.T) { From 0bd0f438927d7e7fbf02dbcc83e81a60718d653a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 8 Jan 2025 08:50:08 -0800 Subject: [PATCH 20/80] add tests that nest optional containers on multiple levels --- runtime/tests/interpreter/interpreter_test.go | 302 +++++++++++++++--- 1 file changed, 262 insertions(+), 40 deletions(-) diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 62d98026e..e3023fb14 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -12666,55 +12666,127 @@ func TestInterpretSomeValueChildContainerMutation(t *testing.T) { t.Run("dictionary, two levels", func(t *testing.T) { test(t, ` - struct Foo { - let values: {String: Int}?? + struct Foo { + let values: {String: Int}?? - init() { - self.values = {} - } + init() { + self.values = {} + } - fun set(key: String, value: Int) { - if let optRef: auth(Mutate) &{String: Int}? = &self.values { - if let ref: auth(Mutate) &{String: Int} = optRef { - ref[key] = value - } - } - } + fun set(key: String, value: Int) { + if let optRef: auth(Mutate) &{String: Int}? = &self.values { + if let ref: auth(Mutate) &{String: Int} = optRef { + ref[key] = value + } + } + } - fun get(key: String): Int? { - if let optRef: &{String: Int}? = &self.values { - if let ref: &{String: Int} = optRef { - return ref[key] - } - } - return nil - } - } + fun get(key: String): Int? { + if let optRef: &{String: Int}? = &self.values { + if let ref: &{String: Int} = optRef { + return ref[key] + } + } + return nil + } + } - fun setup(): Foo { - let foo = Foo() - foo.set(key: "a", value: 1) - return foo - } + fun setup(): Foo { + let foo = Foo() + foo.set(key: "a", value: 1) + return foo + } - fun update(foo: &Foo): Bool { - if foo.get(key: "a") != 1 { - return false - } - foo.set(key: "a", value: 2) - return true - } + fun update(foo: &Foo): Bool { + if foo.get(key: "a") != 1 { + return false + } + foo.set(key: "a", value: 2) + return true + } - fun updateAgain(foo: &Foo): Bool { - if foo.get(key: "a") != 2 { - return false - } - foo.set(key: "a", value: 3) - return true - } + fun updateAgain(foo: &Foo): Bool { + if foo.get(key: "a") != 2 { + return false + } + foo.set(key: "a", value: 3) + return true + } `) }) + t.Run("dictionary, nested", func(t *testing.T) { + + test(t, ` + struct Bar { + let values: {String: Int}? + + init() { + self.values = {} + } + + fun set(key: String, value: Int) { + if let ref: auth(Mutate) &{String: Int} = &self.values { + ref[key] = value + } + } + + fun get(key: String): Int? { + if let ref: &{String: Int} = &self.values { + return ref[key] + } + return nil + } + } + + struct Foo { + let values: {String: Bar}? + + init() { + self.values = {} + } + + fun set(key: String, value: Int) { + if let ref: auth(Mutate) &{String: Bar} = &self.values { + if ref[key] == nil { + ref[key] = Bar() + } + ref[key]?.set(key: key, value: value) + } + } + + fun get(key: String): Int? { + if let ref: &{String: Bar} = &self.values { + return ref[key]?.get(key: key) ?? nil + } + return nil + } + } + + fun setup(): Foo { + let foo = Foo() + foo.set(key: "a", value: 1) + return foo + } + + fun update(foo: &Foo): Bool { + if foo.get(key: "a") != 1 { + return false + } + foo.set(key: "a", value: 2) + return true + } + + fun updateAgain(foo: &Foo): Bool { + if foo.get(key: "a") != 2 { + return false + } + foo.set(key: "a", value: 3) + return true + } + `) + }) + t.Run("resource, one level", func(t *testing.T) { test(t, ` @@ -12829,6 +12901,77 @@ func TestInterpretSomeValueChildContainerMutation(t *testing.T) { `) }) + t.Run("resource, nested", func(t *testing.T) { + + test(t, ` + resource Baz { + var value: Int + + init() { + self.value = 0 + } + } + + resource Bar { + let baz: @Baz? + + init() { + self.baz <- create Baz() + } + + fun set(value: Int) { + if let ref: &Baz = &self.baz { + ref.value = value + } + } + + fun getValue(): Int? { + return self.baz?.value + } + } + + resource Foo { + let bar: @Bar? + + init() { + self.bar <- create Bar() + } + + fun set(value: Int) { + if let ref: &Bar = &self.bar { + ref.set(value: value) + } + } + + fun getValue(): Int? { + return self.bar?.getValue() ?? nil + } + } + + fun setup(): @Foo { + let foo <- create Foo() + foo.set(value: 1) + return <-foo + } + + fun update(foo: &Foo): Bool { + if foo.getValue() != 1 { + return false + } + foo.set(value: 2) + return true + } + + fun updateAgain(foo: &Foo): Bool { + if foo.getValue() != 2 { + return false + } + foo.set(value: 3) + return true + } + `) + }) + t.Run("array, one level", func(t *testing.T) { test(t, ` @@ -12939,4 +13082,83 @@ func TestInterpretSomeValueChildContainerMutation(t *testing.T) { } `) }) + + t.Run("array, nested", func(t *testing.T) { + + test(t, ` + + struct Bar { + let values: [Int]? + + init() { + self.values = [] + } + + fun set(value: Int) { + if let ref: auth(Mutate) &[Int] = &self.values { + if ref.length == 0 { + ref.append(value) + } else { + ref[0] = value + } + } + } + + fun getValue(): Int? { + if let ref: &[Int] = &self.values { + return ref[0] + } + return nil + } + } + + struct Foo { + let values: [Bar]? + + init() { + self.values = [] + } + + fun set(value: Int) { + if let ref: auth(Mutate) &[Bar] = &self.values { + if ref.length == 0 { + ref.append(Bar()) + } + ref[0].set(value: value) + } + } + + fun getValue(): Int? { + if let ref: &[Bar] = &self.values { + return ref[0].getValue() + } + return nil + } + } + + fun setup(): Foo { + let foo = Foo() + foo.set(value: 1) + return foo + } + + fun update(foo: &Foo): Bool { + if foo.getValue() != 1 { + return false + } + foo.set(value: 2) + return true + } + + fun updateAgain(foo: &Foo): Bool { + if foo.getValue() != 2 { + return false + } + foo.set(value: 3) + return true + } + `) + + }) + } From 507fa6b380b3a768015add3a35c4585cbcf03a34 Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:01:53 -0600 Subject: [PATCH 21/80] Update atree version to latest --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 97ad78fbf..032aa3e2e 100644 --- a/go.mod +++ b/go.mod @@ -63,4 +63,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20250106221556-b9ddce7b8760 +replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20250109185652-759a26eda3c3 diff --git a/go.sum b/go.sum index 787394a50..c590d67cc 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onflow/atree-internal v0.8.2-0.20250106221556-b9ddce7b8760 h1:YVlF0ZIDLoxcUYvIbQe6T3XrrxTPzcC8sZcy3KHhjuU= -github.com/onflow/atree-internal v0.8.2-0.20250106221556-b9ddce7b8760/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/atree-internal v0.8.2-0.20250109185652-759a26eda3c3 h1:kwuIssnbosjsGnaxNXSIEzV8u1+Lhd9bDJTrkRB8x0w= +github.com/onflow/atree-internal v0.8.2-0.20250109185652-759a26eda3c3/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= From 86034640042bacf25e8beb760e3039b29a03b1d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 9 Jan 2025 16:33:58 -0800 Subject: [PATCH 22/80] fix constructors for unsigned integer values --- values.go | 10 +++++----- values_test.go | 21 +++++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/values.go b/values.go index 085507b32..1ebd2ccc0 100644 --- a/values.go +++ b/values.go @@ -653,7 +653,7 @@ var _ Value = UInt{} func NewUInt(i uint) UInt { return UInt{ - Value: big.NewInt(int64(i)), + Value: (&big.Int{}).SetUint64(uint64(i)), } } @@ -860,7 +860,7 @@ var UInt128MemoryUsage = common.NewCadenceBigIntMemoryUsage(16) func NewUInt128(i uint) UInt128 { return UInt128{ - Value: big.NewInt(int64(i)), + Value: (&big.Int{}).SetUint64(uint64(i)), } } @@ -924,7 +924,7 @@ var UInt256MemoryUsage = common.NewCadenceBigIntMemoryUsage(32) func NewUInt256(i uint) UInt256 { return UInt256{ - Value: big.NewInt(int64(i)), + Value: (&big.Int{}).SetUint64(uint64(i)), } } @@ -1134,7 +1134,7 @@ var Word128MemoryUsage = common.NewCadenceBigIntMemoryUsage(16) func NewWord128(i uint) Word128 { return Word128{ - Value: big.NewInt(int64(i)), + Value: (&big.Int{}).SetUint64(uint64(i)), } } @@ -1198,7 +1198,7 @@ var Word256MemoryUsage = common.NewCadenceBigIntMemoryUsage(32) func NewWord256(i uint) Word256 { return Word256{ - Value: big.NewInt(int64(i)), + Value: (&big.Int{}).SetUint64(uint64(i)), } } diff --git a/values_test.go b/values_test.go index 35437dd51..aac6eaa8b 100644 --- a/values_test.go +++ b/values_test.go @@ -20,6 +20,7 @@ package cadence import ( "fmt" + "math" "math/big" "testing" "unicode/utf8" @@ -58,8 +59,8 @@ func newValueTestCases() map[string]valueTestCase { return map[string]valueTestCase{ "UInt": { - value: NewUInt(10), - string: "10", + value: NewUInt(math.MaxUint64), + string: "18446744073709551615", expectedType: UIntType, }, "UInt8": { @@ -83,13 +84,13 @@ func newValueTestCases() map[string]valueTestCase { expectedType: UInt64Type, }, "UInt128": { - value: NewUInt128(128), - string: "128", + value: NewUInt128(math.MaxUint64), + string: "18446744073709551615", expectedType: UInt128Type, }, "UInt256": { - value: NewUInt256(256), - string: "256", + value: NewUInt256(math.MaxUint64), + string: "18446744073709551615", expectedType: UInt256Type, }, "Int": { @@ -148,13 +149,13 @@ func newValueTestCases() map[string]valueTestCase { expectedType: Word64Type, }, "Word128": { - value: NewWord128(128), - string: "128", + value: NewWord128(math.MaxUint64), + string: "18446744073709551615", expectedType: Word128Type, }, "Word256": { - value: NewWord256(256), - string: "256", + value: NewWord256(math.MaxUint64), + string: "18446744073709551615", expectedType: Word256Type, }, "UFix64": { From c7ce25e7f556225420d5fb7c086a815fdae1b5e3 Mon Sep 17 00:00:00 2001 From: Joe Sprowes Date: Mon, 13 Jan 2025 10:06:12 -0600 Subject: [PATCH 23/80] remove unnecessary allocations --- runtime/interpreter/value.go | 85 ++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 27 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 754078395..a48e158a6 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -7407,6 +7407,22 @@ func (Int64Value) ChildStorables() []atree.Storable { return nil } +// truncate trims a big.Int to maxWords by directly modifying its underlying representation. +func truncate(x *big.Int, maxWords int) *big.Int { + // Get the absolute value of x as a nat slice. + abs := x.Bits() + + // Limit the nat slice to maxWords. + if len(abs) > maxWords { + abs = abs[:maxWords] + } + + // Update the big.Int's internal representation. + x.SetBits(abs) + + return x +} + // Int128Value type Int128Value struct { @@ -8033,17 +8049,19 @@ func (v Int128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVal LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(128)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 128 { return NewInt128ValueFromUint64(interpreter, 0) } - // Add usage for possible intermediate value. + // The maximum shift value at this point is 127, which may lead to an + // additional allocation of up to 128 bits. Add usage for possible + // intermediate value. common.UseMemory(interpreter, Int128MemoryUsage) valueGetter := func() *big.Int { res := new(big.Int) - res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) - return res + res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + return truncate(res, 2) } return NewInt128ValueFromBigInt(interpreter, valueGetter) @@ -8065,7 +8083,7 @@ func (v Int128Value) BitwiseRightShift(interpreter *Interpreter, other IntegerVa LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(128)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 128 { return NewInt128ValueFromUint64(interpreter, 0) } @@ -8775,18 +8793,19 @@ func (v Int256Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVal LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(256)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 256 { return NewInt256ValueFromUint64(interpreter, 0) } - // Add usage for possible intermediate value. + // The maximum shift value at this point is 255, which may lead to an + // additional allocation of up to 256 bits. Add usage for possible + // intermediate value. common.UseMemory(interpreter, Int256MemoryUsage) valueGetter := func() *big.Int { res := new(big.Int) - res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) - - return res + res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + return truncate(res, 4) } return NewInt256ValueFromBigInt(interpreter, valueGetter) @@ -8808,7 +8827,7 @@ func (v Int256Value) BitwiseRightShift(interpreter *Interpreter, other IntegerVa LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(256)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 256 { return NewInt256ValueFromUint64(interpreter, 0) } @@ -12323,18 +12342,21 @@ func (v UInt128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(128)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 128 { return NewUInt128ValueFromUint64(interpreter, 0) } - // Add usage for possible intermediate value. + // The maximum shift value at this point is 127, which may lead to an + // additional allocation of up to 128 bits. Add usage for possible + // intermediate value. common.UseMemory(interpreter, Uint128MemoryUsage) return NewUInt128ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - return res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + return truncate(res, 2) }, ) } @@ -12355,7 +12377,7 @@ func (v UInt128Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(128)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 128 { return NewUInt128ValueFromUint64(interpreter, 0) } @@ -13000,18 +13022,21 @@ func (v UInt256Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(256)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 256 { return NewUInt256ValueFromUint64(interpreter, 0) } - // Add usage for possible intermediate value. + // The maximum shift value at this point is 255, which may lead to an + // additional allocation of up to 256 bits. Add usage for possible + // intermediate value. common.UseMemory(interpreter, Uint256MemoryUsage) return NewUInt256ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - return res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + return truncate(res, 4) }, ) } @@ -13032,7 +13057,7 @@ func (v UInt256Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(256)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 256 { return NewUInt256ValueFromUint64(interpreter, 0) } @@ -15365,18 +15390,21 @@ func (v Word128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(128)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 128 { return NewWord128ValueFromUint64(interpreter, 0) } - // Add usage for possible intermediate value. + // The maximum shift value at this point is 127, which may lead to an + // additional allocation of up to 128 bits. Add usage for possible + // intermediate value. common.UseMemory(interpreter, Uint128MemoryUsage) return NewWord128ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - return res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + return truncate(res, 2) }, ) } @@ -15396,7 +15424,7 @@ func (v Word128Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(128)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 128 { return NewWord128ValueFromUint64(interpreter, 0) } @@ -15947,18 +15975,21 @@ func (v Word256Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(256)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 256 { return NewWord256ValueFromUint64(interpreter, 0) } - // Add usage for possible intermediate value. + // The maximum shift value at this point is 255, which may lead to an + // additional allocation of up to 256 bits. Add usage for possible + // intermediate value. common.UseMemory(interpreter, Uint256MemoryUsage) return NewWord256ValueFromBigInt( interpreter, func() *big.Int { res := new(big.Int) - return res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) + return truncate(res, 4) }, ) } @@ -15979,7 +16010,7 @@ func (v Word256Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV LocationRange: locationRange, }) } - if o.BigInt.Cmp(big.NewInt(256)) == 1 { + if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 256 { return NewWord256ValueFromUint64(interpreter, 0) } From 9dd001f54e247f5df4dc7e71b1444540321ed4f5 Mon Sep 17 00:00:00 2001 From: Joe Sprowes Date: Mon, 13 Jan 2025 15:50:41 -0600 Subject: [PATCH 24/80] Updated version. --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index b5cb28b0e..698fda3b9 100644 --- a/version.go +++ b/version.go @@ -21,4 +21,4 @@ package cadence -const Version = "v1.0.3" +const Version = "v1.0.4-rc.1" From 0f9df0970e13f2508e7106cb0780f7f739a77b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 13 Jan 2025 16:42:21 -0800 Subject: [PATCH 25/80] refactor value smoke tests to generate cadence.Values and commit between operations --- runtime/interpreter/value.go | 8 + runtime/tests/interpreter/values_test.go | 2224 +++++++++++----------- 2 files changed, 1172 insertions(+), 1060 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index a48e158a6..39030a2ce 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -4074,6 +4074,10 @@ func (v *ArrayValue) SetType(staticType ArrayStaticType) { } } +func (v *ArrayValue) Inlined() bool { + return v.array.Inlined() +} + // NumberValue type NumberValue interface { ComparableValue @@ -20759,6 +20763,10 @@ func (v *DictionaryValue) SetType(staticType *DictionaryStaticType) { } } +func (v *DictionaryValue) Inlined() bool { + return v.dictionary.Inlined() +} + // OptionalValue type OptionalValue interface { diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index e79a24e67..8330beddd 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -32,564 +32,710 @@ import ( "github.com/onflow/atree" + "github.com/onflow/cadence" + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" + . "github.com/onflow/cadence/runtime/tests/runtime_utils" "github.com/onflow/cadence/runtime/tests/utils" ) // TODO: make these program args? const containerMaxDepth = 3 -const containerMaxSize = 100 +const containerMaxSize = 50 const compositeMaxFields = 10 -var runSmokeTests = flag.Bool("runSmokeTests", false, "Run smoke tests on values") +var runSmokeTests = flag.Bool("runSmokeTests", true, "Run smoke tests on values") var validateAtree = flag.Bool("validateAtree", true, "Enable atree validation") var smokeTestSeed = flag.Int64("smokeTestSeed", -1, "Seed for prng (-1 specifies current Unix time)") -func TestInterpretRandomMapOperations(t *testing.T) { - if !*runSmokeTests { - t.Skip("smoke tests are disabled") - } +func newRandomValueTestInterpreter(t *testing.T) (inter *interpreter.Interpreter, resetStorage func()) { - t.Parallel() - - r := newRandomValueGenerator() - t.Logf("seed: %d", r.seed) + config := &interpreter.Config{ + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + return interpreter.VirtualImport{ + Elaboration: inter.Program.Elaboration, + } + }, + AtreeStorageValidationEnabled: *validateAtree, + AtreeValueValidationEnabled: *validateAtree, + } - storage := newUnmeteredInMemoryStorage() inter, err := interpreter.NewInterpreter( &interpreter.Program{ - Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, utils.TestLocation, - &interpreter.Config{ - Storage: storage, - ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { - return interpreter.VirtualImport{ - Elaboration: inter.Program.Elaboration, - } - }, - AtreeStorageValidationEnabled: *validateAtree, - AtreeValueValidationEnabled: *validateAtree, - }, + config, ) require.NoError(t, err) - numberOfValues := r.randomInt(containerMaxSize) + ledger := NewTestLedger(nil, nil) - var testMap, copyOfTestMap *interpreter.DictionaryValue - var storageSize, slabCounts int + resetStorage = func() { + if config.Storage != nil { + storage := config.Storage.(*runtime.Storage) + err := storage.Commit(inter, false) + require.NoError(t, err) + } + config.Storage = runtime.NewStorage(ledger, nil) + } - entries := newValueMap(numberOfValues) - orgOwner := common.Address{'A'} + resetStorage() - t.Run("construction", func(t *testing.T) { - keyValues := make([]interpreter.Value, numberOfValues*2) - for i := 0; i < numberOfValues; i++ { - key := r.randomHashableValue(inter) - value := r.randomStorableValue(inter, 0) + return inter, resetStorage +} - entries.put(inter, key, value) +func importValue(t *testing.T, inter *interpreter.Interpreter, value cadence.Value) interpreter.Value { - keyValues[i*2] = key - keyValues[i*2+1] = value - } + switch value := value.(type) { + case cadence.Array: + // Work around for "cannot import array: elements do not belong to the same type", + // caused by import of array without expected type, which leads to inference of the element type: + // Create an empty array with an expected type, then append imported elements to it. - testMap = interpreter.NewDictionaryValueWithAddress( + arrayResult, err := runtime.ImportValue( inter, interpreter.EmptyLocationRange, - &interpreter.DictionaryStaticType{ - KeyType: interpreter.PrimitiveStaticTypeAnyStruct, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - keyValues..., + nil, + cadence.Array{}, + sema.NewVariableSizedType(nil, sema.AnyStructType), ) + require.NoError(t, err) + require.IsType(t, &interpreter.ArrayValue{}, arrayResult) + array := arrayResult.(*interpreter.ArrayValue) - storageSize, slabCounts = getSlabStorageSize(t, storage) + for _, element := range value.Values { + array.Append( + inter, + interpreter.EmptyLocationRange, + importValue(t, inter, element), + ) + } - require.Equal(t, testMap.Count(), entries.size()) + return array - entries.foreach(func(orgKey, orgValue interpreter.Value) (exit bool) { - exists := testMap.ContainsKey(inter, interpreter.EmptyLocationRange, orgKey) - require.True(t, bool(exists)) + case cadence.Dictionary: + // Work around for "cannot import dictionary: keys does not belong to the same type", + // caused by import of dictionary without expected type, which leads to inference of the key type: + // Create an empty dictionary with an expected type, then append imported key-value pairs to it. - value, found := testMap.Get(inter, interpreter.EmptyLocationRange, orgKey) - require.True(t, found) - utils.AssertValuesEqual(t, inter, orgValue, value) + dictionaryResult, err := runtime.ImportValue( + inter, + interpreter.EmptyLocationRange, + nil, + cadence.Dictionary{}, + sema.NewDictionaryType( + nil, + sema.HashableStructType, + sema.AnyStructType, + ), + ) + require.NoError(t, err) + require.IsType(t, &interpreter.DictionaryValue{}, dictionaryResult) + dictionary := dictionaryResult.(*interpreter.DictionaryValue) - return false - }) + for _, pair := range value.Pairs { + dictionary.Insert( + inter, + interpreter.EmptyLocationRange, + importValue(t, inter, pair.Key), + importValue(t, inter, pair.Value), + ) + } - owner := testMap.GetOwner() - assert.Equal(t, orgOwner, owner) - }) + return dictionary - t.Run("iterate", func(t *testing.T) { - require.Equal(t, testMap.Count(), entries.size()) + case cadence.Struct: - testMap.Iterate( + structResult, err := runtime.ImportValue( inter, interpreter.EmptyLocationRange, - func(key, value interpreter.Value) (resume bool) { - orgValue, ok := entries.get(inter, key) - require.True(t, ok, "cannot find key: %v", key) - - utils.AssertValuesEqual(t, inter, orgValue, value) - return true + nil, + cadence.Struct{ + StructType: value.StructType, }, + nil, ) - }) + require.NoError(t, err) + require.IsType(t, &interpreter.CompositeValue{}, structResult) + composite := structResult.(*interpreter.CompositeValue) + + for fieldName, fieldValue := range value.FieldsMappedByName() { + composite.SetMember( + inter, + interpreter.EmptyLocationRange, + fieldName, + importValue(t, inter, fieldValue), + ) + } - t.Run("deep copy", func(t *testing.T) { - newOwner := atree.Address{'B'} - copyOfTestMap = testMap.Transfer( + return composite + + case cadence.Optional: + + if value.Value == nil { + return interpreter.NilValue{} + } + + return interpreter.NewUnmeteredSomeValueNonCopying( + importValue(t, inter, value.Value), + ) + + default: + result, err := runtime.ImportValue( inter, interpreter.EmptyLocationRange, - newOwner, - false, nil, + value, nil, - true, // testMap is standalone. - ).(*interpreter.DictionaryValue) - - require.Equal(t, entries.size(), copyOfTestMap.Count()) - - entries.foreach(func(orgKey, orgValue interpreter.Value) (exit bool) { - exists := copyOfTestMap.ContainsKey(inter, interpreter.EmptyLocationRange, orgKey) - require.True(t, bool(exists)) - - value, found := copyOfTestMap.Get(inter, interpreter.EmptyLocationRange, orgKey) - require.True(t, found) - utils.AssertValuesEqual(t, inter, orgValue, value) + ) + require.NoError(t, err) + return result + } +} - return false - }) +func withoutAtreeStorageValidationEnabled[T any](inter *interpreter.Interpreter, f func() T) T { + config := inter.SharedState.Config + original := config.AtreeStorageValidationEnabled + config.AtreeStorageValidationEnabled = false + result := f() + config.AtreeStorageValidationEnabled = original + return result +} - owner := copyOfTestMap.GetOwner() - assert.Equal(t, newOwner[:], owner[:]) - }) +func TestInterpretRandomMapOperations(t *testing.T) { + if !*runSmokeTests { + t.Skip("smoke tests are disabled") + } - t.Run("deep remove", func(t *testing.T) { - copyOfTestMap.DeepRemove(inter, true) - err = storage.Remove(copyOfTestMap.SlabID()) - require.NoError(t, err) + t.Parallel() - // deep removal should clean up everything - newStorageSize, newSlabCounts := getSlabStorageSize(t, storage) - assert.Equal(t, slabCounts, newSlabCounts) - assert.Equal(t, storageSize, newStorageSize) + orgOwner := common.Address{'A'} - require.Equal(t, entries.size(), testMap.Count()) + const dictionaryStorageMapKey = interpreter.StringStorageMapKey("dictionary") - // go over original values again and check no missing data (no side effect should be found) - entries.foreach(func(orgKey, orgValue interpreter.Value) (exit bool) { - exists := testMap.ContainsKey(inter, interpreter.EmptyLocationRange, orgKey) - require.True(t, bool(exists)) + createDictionary := func( + r *randomValueGenerator, + inter *interpreter.Interpreter, + ) ( + *interpreter.DictionaryValue, + cadence.Dictionary, + func() *interpreter.DictionaryValue, + ) { + expectedValue := r.randomDictionaryValue(inter, 0) - value, found := testMap.Get(inter, interpreter.EmptyLocationRange, orgKey) - require.True(t, found) - utils.AssertValuesEqual(t, inter, orgValue, value) + keyValues := make([]interpreter.Value, 2*len(expectedValue.Pairs)) + for i, pair := range expectedValue.Pairs { - return false - }) + key := importValue(t, inter, pair.Key) + value := importValue(t, inter, pair.Value) - owner := testMap.GetOwner() - assert.Equal(t, orgOwner, owner) - }) + keyValues[i*2] = key + keyValues[i*2+1] = value + } - t.Run("insert", func(t *testing.T) { - newEntries := newValueMap(numberOfValues) + // Construct a dictionary directly in the owner's account. + // However, the dictionary is not referenced by the root of the storage yet + // (a storage map), so atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. - dictionary := interpreter.NewDictionaryValueWithAddress( + dictionary := withoutAtreeStorageValidationEnabled( inter, - interpreter.EmptyLocationRange, - &interpreter.DictionaryStaticType{ - KeyType: interpreter.PrimitiveStaticTypeAnyStruct, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, + func() *interpreter.DictionaryValue { + return interpreter.NewDictionaryValueWithAddress( + inter, + interpreter.EmptyLocationRange, + &interpreter.DictionaryStaticType{ + KeyType: interpreter.PrimitiveStaticTypeHashableStruct, + ValueType: interpreter.PrimitiveStaticTypeAnyStruct, + }, + orgOwner, + keyValues..., + ) }, - orgOwner, ) - // Insert - for i := 0; i < numberOfValues; i++ { - key := r.randomHashableValue(inter) - value := r.randomStorableValue(inter, 0) + // Store the dictionary in a storage map, so that the dictionary's slab + // is referenced by the root of the storage. + + inter.Storage(). + GetStorageMap( + orgOwner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + dictionaryStorageMapKey, + dictionary, + ) + + reloadDictionary := func() *interpreter.DictionaryValue { + storageMap := inter.Storage().GetStorageMap( + orgOwner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) - newEntries.put(inter, key, value) + readValue := storageMap.ReadValue(inter, dictionaryStorageMapKey) + require.NotNil(t, readValue) - _ = dictionary.Insert(inter, interpreter.EmptyLocationRange, key, value) + require.IsType(t, &interpreter.DictionaryValue{}, readValue) + return readValue.(*interpreter.DictionaryValue) } - require.Equal(t, newEntries.size(), dictionary.Count()) + return dictionary, expectedValue, reloadDictionary + } + + checkDictionary := func( + inter *interpreter.Interpreter, + dictionary *interpreter.DictionaryValue, + expectedValue cadence.Dictionary, + expectedOwner common.Address, + ) { + require.Equal(t, len(expectedValue.Pairs), dictionary.Count()) - // Go over original values again and check no missing data (no side effect should be found) - newEntries.foreach(func(orgKey, orgValue interpreter.Value) (exit bool) { - exists := dictionary.ContainsKey(inter, interpreter.EmptyLocationRange, orgKey) + for _, pair := range expectedValue.Pairs { + pairKey := importValue(t, inter, pair.Key) + + exists := dictionary.ContainsKey(inter, interpreter.EmptyLocationRange, pairKey) require.True(t, bool(exists)) - value, found := dictionary.Get(inter, interpreter.EmptyLocationRange, orgKey) + value, found := dictionary.Get(inter, interpreter.EmptyLocationRange, pairKey) require.True(t, found) - utils.AssertValuesEqual(t, inter, orgValue, value) - return false - }) - }) + pairValue := importValue(t, inter, pair.Value) + utils.AssertValuesEqual(t, inter, pairValue, value) + } - t.Run("remove", func(t *testing.T) { - newEntries := newValueMap(numberOfValues) + owner := dictionary.GetOwner() + assert.Equal(t, expectedOwner, owner) + } - keyValues := make([][2]interpreter.Value, numberOfValues) - for i := 0; i < numberOfValues; i++ { - key := r.randomHashableValue(inter) - value := r.randomStorableValue(inter, 0) + doubleCheckDictionary := func( + inter *interpreter.Interpreter, + resetStorage func(), + dictionary *interpreter.DictionaryValue, + expectedValue cadence.Dictionary, + expectedOwner common.Address, + ) { + // Check the values of the dictionary. + // Once right away, and once after a reset (commit and reload) of the storage. + + for i := 0; i < 2; i++ { - newEntries.put(inter, key, value) + checkDictionary( + inter, + dictionary, + expectedValue, + expectedOwner, + ) - keyValues[i][0] = key - keyValues[i][1] = value + resetStorage() } + } - dictionary := interpreter.NewDictionaryValueWithAddress( - inter, - interpreter.EmptyLocationRange, - &interpreter.DictionaryStaticType{ - KeyType: interpreter.PrimitiveStaticTypeAnyStruct, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - ) + checkIteration := func( + inter *interpreter.Interpreter, + resetStorage func(), + dictionary *interpreter.DictionaryValue, + expectedValue cadence.Dictionary, + ) { + // Index the expected key-value pairs for lookup during iteration - require.Equal(t, 0, dictionary.Count()) + indexedExpected := map[any]interpreter.DictionaryEntryValues{} + for _, pair := range expectedValue.Pairs { + pairKey := importValue(t, inter, pair.Key) - // Get the initial storage size before inserting values - startingStorageSize, startingSlabCounts := getSlabStorageSize(t, storage) + mapKey := mapKey(inter, pairKey) - // Insert - for _, keyValue := range keyValues { - dictionary.Insert(inter, interpreter.EmptyLocationRange, keyValue[0], keyValue[1]) + require.NotContains(t, indexedExpected, mapKey) + indexedExpected[mapKey] = interpreter.DictionaryEntryValues{ + Key: pairKey, + Value: importValue(t, inter, pair.Value), + } } - require.Equal(t, newEntries.size(), dictionary.Count()) + // Iterate over the values of the created dictionary. + // Once right after construction, and once after a reset (commit and reload) of the storage. - // Remove - newEntries.foreach(func(orgKey, orgValue interpreter.Value) (exit bool) { - removedValue := dictionary.Remove(inter, interpreter.EmptyLocationRange, orgKey) + for i := 0; i < 2; i++ { - require.IsType(t, &interpreter.SomeValue{}, removedValue) - someValue := removedValue.(*interpreter.SomeValue) + require.Equal(t, len(expectedValue.Pairs), dictionary.Count()) - // Removed value must be same as the original value - innerValue := someValue.InnerValue(inter, interpreter.EmptyLocationRange) - utils.AssertValuesEqual(t, inter, orgValue, innerValue) + var iterations int - return false - }) + dictionary.Iterate( + inter, + interpreter.EmptyLocationRange, + func(key, value interpreter.Value) (resume bool) { - // Dictionary must be empty - require.Equal(t, 0, dictionary.Count()) + mapKey := mapKey(inter, key) + require.Contains(t, indexedExpected, mapKey) - storageSize, slabCounts := getSlabStorageSize(t, storage) + pair := indexedExpected[mapKey] - // Storage size after removals should be same as the size before insertion. - assert.Equal(t, startingStorageSize, storageSize) - assert.Equal(t, startingSlabCounts, slabCounts) - }) + utils.AssertValuesEqual(t, inter, pair.Key, key) + utils.AssertValuesEqual(t, inter, pair.Value, value) + + iterations += 1 + + return true + }, + ) + + assert.Equal(t, len(expectedValue.Pairs), iterations) + + resetStorage() + } + } + + t.Run("construction", func(t *testing.T) { - t.Run("remove enum key", func(t *testing.T) { + t.Parallel() - dictionary := interpreter.NewDictionaryValueWithAddress( + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) + + inter, resetStorage := newRandomValueTestInterpreter(t) + + dictionary, expectedValue, _ := createDictionary(&r, inter) + + doubleCheckDictionary( inter, - interpreter.EmptyLocationRange, - &interpreter.DictionaryStaticType{ - KeyType: interpreter.PrimitiveStaticTypeAnyStruct, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, - }, + resetStorage, + dictionary, + expectedValue, orgOwner, ) + }) - require.Equal(t, 0, dictionary.Count()) - - // Get the initial storage size after creating empty dictionary - startingStorageSize, startingSlabCounts := getSlabStorageSize(t, storage) + t.Run("iterate", func(t *testing.T) { - newEntries := newValueMap(numberOfValues) + t.Parallel() - keyValues := make([][2]interpreter.Value, numberOfValues) - for i := 0; i < numberOfValues; i++ { - // Create a random enum as key - key := r.generateRandomHashableValue(inter, randomValueKindEnum) - value := interpreter.Void + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) - newEntries.put(inter, key, value) + inter, resetStorage := newRandomValueTestInterpreter(t) - keyValues[i][0] = key - keyValues[i][1] = value - } + dictionary, expectedValue, _ := createDictionary(&r, inter) - // Insert - for _, keyValue := range keyValues { - dictionary.Insert(inter, interpreter.EmptyLocationRange, keyValue[0], keyValue[1]) - } + checkIteration( + inter, + resetStorage, + dictionary, + expectedValue, + ) + }) - // Remove - newEntries.foreach(func(orgKey, orgValue interpreter.Value) (exit bool) { - removedValue := dictionary.Remove(inter, interpreter.EmptyLocationRange, orgKey) + t.Run("move (transfer and deep remove)", func(t *testing.T) { - require.IsType(t, &interpreter.SomeValue{}, removedValue) - someValue := removedValue.(*interpreter.SomeValue) + t.Parallel() - // Removed value must be same as the original value - innerValue := someValue.InnerValue(inter, interpreter.EmptyLocationRange) - utils.AssertValuesEqual(t, inter, orgValue, innerValue) + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) - return false - }) + inter, resetStorage := newRandomValueTestInterpreter(t) - // Dictionary must be empty - require.Equal(t, 0, dictionary.Count()) + original, expectedValue, _ := createDictionary(&r, inter) - storageSize, slabCounts = getSlabStorageSize(t, storage) + resetStorage() - // Storage size after removals should be same as the size before insertion. - assert.Equal(t, startingStorageSize, storageSize) - assert.Equal(t, startingSlabCounts, slabCounts) - }) + // Transfer the dictionary to a new owner - t.Run("update enum key", func(t *testing.T) { + newOwner := common.Address{'B'} - dictionary := interpreter.NewDictionaryValueWithAddress( + transferred := original.Transfer( inter, interpreter.EmptyLocationRange, - &interpreter.DictionaryStaticType{ - KeyType: interpreter.PrimitiveStaticTypeAnyStruct, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - ) + atree.Address(newOwner), + false, + nil, + nil, + // TODO: is has no parent container = true correct? + true, + ).(*interpreter.DictionaryValue) - require.Equal(t, 0, dictionary.Count()) + // Store the transferred dictionary in a storage map, so that the dictionary's slab + // is referenced by the root of the storage. - value1 := interpreter.NewUnmeteredIntValueFromInt64(1) - value2 := interpreter.NewUnmeteredIntValueFromInt64(2) + inter.Storage(). + GetStorageMap( + newOwner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + interpreter.StringStorageMapKey("transferred_dictionary"), + transferred, + ) - keys := make([]interpreter.Value, numberOfValues) - for i := 0; i < numberOfValues; i++ { - // Create a random enum as key - key := r.generateRandomHashableValue(inter, randomValueKindEnum) + // Both original and transferred dictionary should contain the expected values + // Check once right away, and once after a reset (commit and reload) of the storage. - keys[i] = key - } + for i := 0; i < 2; i++ { - // Insert - for _, key := range keys { - dictionary.Insert( + checkDictionary( inter, - interpreter.EmptyLocationRange, - // Need to clone the key, as it is transferred, and we want to keep using it. - key.Clone(inter), - // Always insert value1 - value1, + original, + expectedValue, + orgOwner, ) - } - // Update - for _, key := range keys { - oldValue := dictionary.Insert( + checkDictionary( inter, - interpreter.EmptyLocationRange, - // Need to clone the key, as it is transferred, and we want to keep using it. - key.Clone(inter), - // Change all value1 to value2 - value2, + transferred, + expectedValue, + newOwner, ) - require.IsType(t, &interpreter.SomeValue{}, oldValue) - someValue := oldValue.(*interpreter.SomeValue) - - // Removed value must be same as the original value - innerValue := someValue.InnerValue(inter, interpreter.EmptyLocationRange) - utils.AssertValuesEqual(t, inter, value1, innerValue) + resetStorage() } - // Check the values - for _, key := range keys { - readValue := dictionary.GetKey( - inter, - interpreter.EmptyLocationRange, - key, - ) + // Deep remove the original dictionary - require.IsType(t, &interpreter.SomeValue{}, readValue) - someValue := readValue.(*interpreter.SomeValue) + // TODO: is has no parent container = true correct? + original.DeepRemove(inter, true) - // Read value must be updated value - innerValue := someValue.InnerValue(inter, interpreter.EmptyLocationRange) - utils.AssertValuesEqual(t, inter, value2, innerValue) + if !original.Inlined() { + err := inter.Storage().Remove(original.SlabID()) + require.NoError(t, err) } + + err := inter.Storage().CheckHealth() + require.NoError(t, err) + + // New dictionary should still be accessible + + doubleCheckDictionary( + inter, + resetStorage, + transferred, + expectedValue, + newOwner, + ) + + // TODO: check deep removal cleaned up everything in original account (storage size, slab count) }) - t.Run("random insert & remove", func(t *testing.T) { - keyValues := make([][2]interpreter.Value, numberOfValues) + t.Run("insert", func(t *testing.T) { + t.Parallel() + + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) + + inter, resetStorage := newRandomValueTestInterpreter(t) + + dictionary, expectedValue, reloadDictionary := createDictionary(&r, inter) + + resetStorage() + + // Reload the dictionary after the reset + + dictionary = reloadDictionary() + + // Insert new values into the dictionary. + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab errors. + + numberOfValues := r.randomInt(containerMaxSize) + for i := 0; i < numberOfValues; i++ { - // Generate unique key - var key interpreter.Value + + // Generate a unique key + var key cadence.Value + var importedKey interpreter.Value for { key = r.randomHashableValue(inter) + importedKey = importValue(t, inter, key) - var foundConflict bool - for j := 0; j < i; j++ { - existingKey := keyValues[j][0] - if key.(interpreter.EquatableValue).Equal(inter, interpreter.EmptyLocationRange, existingKey) { - foundConflict = true - break - } - } - if !foundConflict { + if !dictionary.ContainsKey( + inter, + interpreter.EmptyLocationRange, + importedKey, + ) { break } } - keyValues[i][0] = key - keyValues[i][1] = r.randomStorableValue(inter, 0) + value := r.randomStorableValue(inter, 0) + importedValue := importValue(t, inter, value) + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + _ = withoutAtreeStorageValidationEnabled(inter, func() struct{} { + + existing := dictionary.Insert( + inter, + interpreter.EmptyLocationRange, + importedKey, + importedValue, + ) + require.Equal(t, + interpreter.NilOptionalValue, + existing, + ) + return struct{}{} + }) + + expectedValue.Pairs = append( + expectedValue.Pairs, + cadence.KeyValuePair{ + Key: key, + Value: value, + }, + ) } - dictionary := interpreter.NewDictionaryValueWithAddress( + err := inter.Storage().CheckHealth() + require.NoError(t, err) + + doubleCheckDictionary( inter, - interpreter.EmptyLocationRange, - &interpreter.DictionaryStaticType{ - KeyType: interpreter.PrimitiveStaticTypeAnyStruct, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, - }, + resetStorage, + dictionary, + expectedValue, orgOwner, ) + }) - require.Equal(t, 0, dictionary.Count()) + t.Run("remove", func(t *testing.T) { + t.Parallel() - // Get the initial storage size before inserting values - startingStorageSize, startingSlabCounts := getSlabStorageSize(t, storage) + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) - insertCount := 0 - deleteCount := 0 + inter, resetStorage := newRandomValueTestInterpreter(t) - isInsert := func() bool { - if dictionary.Count() == 0 { - return true - } + dictionary, expectedValue, reloadDictionary := createDictionary(&r, inter) - if insertCount >= numberOfValues { - return false - } + resetStorage() - return r.randomInt(1) == 1 - } + // Reload the dictionary after the reset - for insertCount < numberOfValues || dictionary.Count() > 0 { - // Perform a random operation out of insert/remove - if isInsert() { - key := keyValues[insertCount][0] - if _, ok := key.(*interpreter.CompositeValue); ok { - key = key.Clone(inter) - } + dictionary = reloadDictionary() - value := keyValues[insertCount][1].Clone(inter) + // Remove + for _, pair := range expectedValue.Pairs { - dictionary.Insert( - inter, - interpreter.EmptyLocationRange, - key, - value, - ) - insertCount++ - } else { - key := keyValues[deleteCount][0] - orgValue := keyValues[deleteCount][1] + key := importValue(t, inter, pair.Key) - removedValue := dictionary.Remove(inter, interpreter.EmptyLocationRange, key) + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. - require.IsType(t, &interpreter.SomeValue{}, removedValue) - someValue := removedValue.(*interpreter.SomeValue) + removedValue := withoutAtreeStorageValidationEnabled(inter, func() interpreter.OptionalValue { + return dictionary.Remove(inter, interpreter.EmptyLocationRange, key) + }) - // Removed value must be same as the original value - innerValue := someValue.InnerValue(inter, interpreter.EmptyLocationRange) - utils.AssertValuesEqual(t, inter, orgValue, innerValue) + require.IsType(t, &interpreter.SomeValue{}, removedValue) + someValue := removedValue.(*interpreter.SomeValue) - deleteCount++ - } + // TODO: panic: duplicate slab 0x4100000000000000.14 for seed 1736809620917220000 + value := importValue(t, inter, pair.Value) + + // Removed value must be same as the original value + innerValue := someValue.InnerValue(inter, interpreter.EmptyLocationRange) + utils.AssertValuesEqual(t, inter, value, innerValue) } + err := inter.Storage().CheckHealth() + require.NoError(t, err) + + expectedValue = cadence.Dictionary{}. + WithType(expectedValue.Type().(*cadence.DictionaryType)) + // Dictionary must be empty require.Equal(t, 0, dictionary.Count()) - storageSize, slabCounts := getSlabStorageSize(t, storage) + doubleCheckDictionary( + inter, + resetStorage, + dictionary, + expectedValue, + orgOwner, + ) - // Storage size after removals should be same as the size before insertion. - assert.Equal(t, startingStorageSize, storageSize) - assert.Equal(t, startingSlabCounts, slabCounts) + // TODO: check storage size, slab count }) - t.Run("move", func(t *testing.T) { - newOwner := atree.Address{'B'} + t.Run("update", func(t *testing.T) { + t.Parallel() - entries := newValueMap(numberOfValues) + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) - keyValues := make([]interpreter.Value, numberOfValues*2) - for i := 0; i < numberOfValues; i++ { - key := r.randomHashableValue(inter) - value := r.randomStorableValue(inter, 0) + inter, resetStorage := newRandomValueTestInterpreter(t) - entries.put(inter, key, value) + dictionary, expectedValue, reloadDictionary := createDictionary(&r, inter) - keyValues[i*2] = key - keyValues[i*2+1] = value + resetStorage() + + // Reload the dictionary after the reset + + dictionary = reloadDictionary() + + elementCount := dictionary.Count() + + // Generate new values + + newValues := make([]cadence.Value, len(expectedValue.Pairs)) + for i := range expectedValue.Pairs { + newValues[i] = r.randomStorableValue(inter, 0) } - dictionary := interpreter.NewDictionaryValueWithAddress( - inter, - interpreter.EmptyLocationRange, - &interpreter.DictionaryStaticType{ - KeyType: interpreter.PrimitiveStaticTypeAnyStruct, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - keyValues..., - ) + // Update + for i, pair := range expectedValue.Pairs { - require.Equal(t, entries.size(), dictionary.Count()) + key := importValue(t, inter, pair.Key) + newValue := importValue(t, inter, newValues[i]) - movedDictionary := dictionary.Transfer( - inter, - interpreter.EmptyLocationRange, - newOwner, - true, - nil, - nil, - true, // dictionary is standalone. - ).(*interpreter.DictionaryValue) + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. - require.Equal(t, entries.size(), movedDictionary.Count()) + existingValue := withoutAtreeStorageValidationEnabled(inter, func() interpreter.OptionalValue { + return dictionary.Insert( + inter, + interpreter.EmptyLocationRange, + key, + newValue, + ) + }) - // Cleanup the slab of original dictionary. - err := storage.Remove(dictionary.SlabID()) - require.NoError(t, err) + require.IsType(t, &interpreter.SomeValue{}, existingValue) + someValue := existingValue.(*interpreter.SomeValue) - // Check the values - entries.foreach(func(orgKey, orgValue interpreter.Value) (exit bool) { - exists := movedDictionary.ContainsKey(inter, interpreter.EmptyLocationRange, orgKey) - require.True(t, bool(exists)) + value := importValue(t, inter, pair.Value) - value, found := movedDictionary.Get(inter, interpreter.EmptyLocationRange, orgKey) - require.True(t, found) - utils.AssertValuesEqual(t, inter, orgValue, value) + // Removed value must be same as the original value + innerValue := someValue.InnerValue(inter, interpreter.EmptyLocationRange) + utils.AssertValuesEqual(t, inter, value, innerValue) + + expectedValue.Pairs[i].Value = newValues[i] + } + + err := inter.Storage().CheckHealth() + require.NoError(t, err) - return false - }) + // Dictionary must have same number of key-value pairs + require.Equal(t, elementCount, dictionary.Count()) - owner := movedDictionary.GetOwner() - assert.Equal(t, newOwner[:], owner[:]) + doubleCheckDictionary( + inter, + resetStorage, + dictionary, + expectedValue, + orgOwner, + ) + + // TODO: check storage size, slab count }) } @@ -598,614 +744,464 @@ func TestInterpretRandomArrayOperations(t *testing.T) { t.Skip("smoke tests are disabled") } - r := newRandomValueGenerator() - - testInterpretArrayOperations(t, r) -} - -func testInterpretArrayOperations(t *testing.T, r randomValueGenerator) { - - t.Logf("seed: %d", r.seed) - - storage := newUnmeteredInMemoryStorage() - inter, err := interpreter.NewInterpreter( - &interpreter.Program{ - Program: ast.NewProgram(nil, []ast.Declaration{}), - Elaboration: sema.NewElaboration(nil), - }, - utils.TestLocation, - &interpreter.Config{ - Storage: storage, - ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { - return interpreter.VirtualImport{ - Elaboration: inter.Program.Elaboration, - } - }, - }, - ) - require.NoError(t, err) - - numberOfValues := r.randomInt(containerMaxSize) - - var testArray, copyOfTestArray *interpreter.ArrayValue - var storageSize, slabCounts int + t.Parallel() - elements := make([]interpreter.Value, numberOfValues) orgOwner := common.Address{'A'} - t.Run("construction", func(t *testing.T) { - values := make([]interpreter.Value, numberOfValues) - for i := 0; i < numberOfValues; i++ { - value := r.randomStorableValue(inter, 0) - elements[i] = value - values[i] = value.Clone(inter) + const arrayStorageMapKey = interpreter.StringStorageMapKey("array") + + createArray := func( + r *randomValueGenerator, + inter *interpreter.Interpreter, + ) ( + *interpreter.ArrayValue, + cadence.Array, + func() *interpreter.ArrayValue, + ) { + expectedValue := r.randomArrayValue(inter, 0) + + elements := make([]interpreter.Value, len(expectedValue.Values)) + for i, value := range expectedValue.Values { + elements[i] = importValue(t, inter, value) } - testArray = interpreter.NewArrayValue( + // Construct an array directly in the owner's account. + // However, the array is not referenced by the root of the storage yet + // (a storage map), so atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + array := withoutAtreeStorageValidationEnabled( inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, + func() *interpreter.ArrayValue { + return interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + orgOwner, + elements..., + ) }, - orgOwner, - values..., ) - storageSize, slabCounts = getSlabStorageSize(t, storage) + // Store the array in a storage map, so that the array's slab + // is referenced by the root of the storage. - require.Equal(t, len(elements), testArray.Count()) + inter.Storage(). + GetStorageMap( + orgOwner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + arrayStorageMapKey, + array, + ) - for index, orgElement := range elements { - element := testArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, orgElement, element) - } + reloadArray := func() *interpreter.ArrayValue { + storageMap := inter.Storage().GetStorageMap( + orgOwner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) - owner := testArray.GetOwner() - assert.Equal(t, orgOwner, owner) - }) + readValue := storageMap.ReadValue(inter, arrayStorageMapKey) + require.NotNil(t, readValue) - t.Run("iterate", func(t *testing.T) { - require.Equal(t, testArray.Count(), len(elements)) + require.IsType(t, &interpreter.ArrayValue{}, readValue) + return readValue.(*interpreter.ArrayValue) + } - index := 0 - testArray.Iterate( - inter, - func(element interpreter.Value) (resume bool) { - orgElement := elements[index] - utils.AssertValuesEqual(t, inter, orgElement, element) + return array, expectedValue, reloadArray + } - elementByIndex := testArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, element, elementByIndex) + checkArray := func( + inter *interpreter.Interpreter, + array *interpreter.ArrayValue, + expectedValue cadence.Array, + expectedOwner common.Address, + ) { + require.Equal(t, len(expectedValue.Values), array.Count()) - index++ - return true - }, - false, - interpreter.EmptyLocationRange, - ) - }) + for i, value := range expectedValue.Values { + value := importValue(t, inter, value) - t.Run("deep copy", func(t *testing.T) { - newOwner := atree.Address{'B'} - copyOfTestArray = testArray.Transfer( - inter, - interpreter.EmptyLocationRange, - newOwner, - false, - nil, - nil, - true, // testArray is standalone. - ).(*interpreter.ArrayValue) + element := array.Get(inter, interpreter.EmptyLocationRange, i) - require.Equal(t, len(elements), copyOfTestArray.Count()) - - for index, orgElement := range elements { - element := copyOfTestArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, orgElement, element) + utils.AssertValuesEqual(t, inter, value, element) } - owner := copyOfTestArray.GetOwner() - assert.Equal(t, newOwner[:], owner[:]) - }) + owner := array.GetOwner() + assert.Equal(t, expectedOwner, owner) + } - t.Run("deep removal", func(t *testing.T) { - copyOfTestArray.DeepRemove(inter, true) - err = storage.Remove(copyOfTestArray.SlabID()) - require.NoError(t, err) + doubleCheckArray := func( + inter *interpreter.Interpreter, + resetStorage func(), + array *interpreter.ArrayValue, + expectedValue cadence.Array, + expectedOwner common.Address, + ) { + // Check the values of the array. + // Once right away, and once after a reset (commit and reload) of the storage. - // deep removal should clean up everything - newStorageSize, newSlabCounts := getSlabStorageSize(t, storage) - assert.Equal(t, slabCounts, newSlabCounts) - assert.Equal(t, storageSize, newStorageSize) + for i := 0; i < 2; i++ { - assert.Equal(t, len(elements), testArray.Count()) + checkArray( + inter, + array, + expectedValue, + expectedOwner, + ) - // go over original elements again and check no missing data (no side effect should be found) - for index, orgElement := range elements { - element := testArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, orgElement, element) + resetStorage() } + } - owner := testArray.GetOwner() - assert.Equal(t, orgOwner, owner) - }) + checkIteration := func( + inter *interpreter.Interpreter, + resetStorage func(), + array *interpreter.ArrayValue, + expectedValue cadence.Array, + ) { - t.Run("insert", func(t *testing.T) { - newElements := make([]interpreter.Value, numberOfValues) + // Iterate over the values of the created array. + // Once right after construction, and once after a reset (commit and reload) of the storage. - testArray = interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - ) + for i := 0; i < 2; i++ { - require.Equal(t, 0, testArray.Count()) + require.Equal(t, len(expectedValue.Values), array.Count()) - for i := 0; i < numberOfValues; i++ { - element := r.randomStorableValue(inter, 0) - newElements[i] = element + var iterations int - testArray.Insert( + array.Iterate( inter, + func(element interpreter.Value) (resume bool) { + value := importValue(t, inter, expectedValue.Values[iterations]) + + utils.AssertValuesEqual(t, inter, value, element) + + iterations += 1 + + return true + }, + false, interpreter.EmptyLocationRange, - i, - element.Clone(inter), ) - } - require.Equal(t, len(newElements), testArray.Count()) + assert.Equal(t, len(expectedValue.Values), iterations) - // Go over original values again and check no missing data (no side effect should be found) - for index, element := range newElements { - value := testArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, element, value) + resetStorage() } - }) - - t.Run("append", func(t *testing.T) { - newElements := make([]interpreter.Value, numberOfValues) + } - testArray = interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - ) + t.Run("construction", func(t *testing.T) { - require.Equal(t, 0, testArray.Count()) + t.Parallel() - for i := 0; i < numberOfValues; i++ { - element := r.randomStorableValue(inter, 0) - newElements[i] = element + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) - testArray.Append( - inter, - interpreter.EmptyLocationRange, - element.Clone(inter), - ) - } + inter, resetStorage := newRandomValueTestInterpreter(t) - require.Equal(t, len(newElements), testArray.Count()) + array, expectedValue, _ := createArray(&r, inter) - // Go over original values again and check no missing data (no side effect should be found) - for index, element := range newElements { - value := testArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, element, value) - } + doubleCheckArray( + inter, + resetStorage, + array, + expectedValue, + orgOwner, + ) }) - t.Run("remove", func(t *testing.T) { - newElements := make([]interpreter.Value, numberOfValues) + t.Run("iterate", func(t *testing.T) { - for i := 0; i < numberOfValues; i++ { - newElements[i] = r.randomStorableValue(inter, 0) - } + t.Parallel() - testArray = interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - ) + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) - require.Equal(t, 0, testArray.Count()) + inter, resetStorage := newRandomValueTestInterpreter(t) - // Get the initial storage size before inserting values - startingStorageSize, startingSlabCounts := getSlabStorageSize(t, storage) + array, expectedValue, _ := createArray(&r, inter) - // Insert - for index, element := range newElements { - testArray.Insert( - inter, - interpreter.EmptyLocationRange, - index, - element.Clone(inter), - ) - } + checkIteration( + inter, + resetStorage, + array, + expectedValue, + ) + }) - require.Equal(t, len(newElements), testArray.Count()) + t.Run("move (transfer and deep remove)", func(t *testing.T) { - // Remove - for _, element := range newElements { - removedValue := testArray.Remove(inter, interpreter.EmptyLocationRange, 0) + t.Parallel() - // Removed value must be same as the original value - utils.AssertValuesEqual(t, inter, element, removedValue) - } + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) - // Array must be empty - require.Equal(t, 0, testArray.Count()) + inter, resetStorage := newRandomValueTestInterpreter(t) - storageSize, slabCounts := getSlabStorageSize(t, storage) + original, expectedValue, _ := createArray(&r, inter) - // Storage size after removals should be same as the size before insertion. - assert.Equal(t, startingStorageSize, storageSize) - assert.Equal(t, startingSlabCounts, slabCounts) - }) + resetStorage() - t.Run("random insert & remove", func(t *testing.T) { - elements := make([]interpreter.Value, numberOfValues) + // Transfer the array to a new owner - for i := 0; i < numberOfValues; i++ { - elements[i] = r.randomStorableValue(inter, 0) - } + newOwner := common.Address{'B'} - testArray = interpreter.NewArrayValue( + transferred := original.Transfer( inter, interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - ) + atree.Address(newOwner), + false, + nil, + nil, + // TODO: is has no parent container = true correct? + true, + ).(*interpreter.ArrayValue) - require.Equal(t, 0, testArray.Count()) + // Store the transferred array in a storage map, so that the array's slab + // is referenced by the root of the storage. - // Get the initial storage size before inserting values - startingStorageSize, startingSlabCounts := getSlabStorageSize(t, storage) + inter.Storage(). + GetStorageMap( + newOwner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + interpreter.StringStorageMapKey("transferred_array"), + transferred, + ) - insertCount := 0 - deleteCount := 0 + // Both original and transferred array should contain the expected values + // Check once right away, and once after a reset (commit and reload) of the storage. - isInsert := func() bool { - if testArray.Count() == 0 { - return true - } + for i := 0; i < 2; i++ { - if insertCount >= numberOfValues { - return false - } + checkArray( + inter, + original, + expectedValue, + orgOwner, + ) - return r.randomInt(1) == 1 - } + checkArray( + inter, + transferred, + expectedValue, + newOwner, + ) - for insertCount < numberOfValues || testArray.Count() > 0 { - // Perform a random operation out of insert/remove - if isInsert() { - value := elements[insertCount].Clone(inter) + resetStorage() + } - testArray.Append( - inter, - interpreter.EmptyLocationRange, - value, - ) - insertCount++ - } else { - orgValue := elements[deleteCount] - removedValue := testArray.RemoveFirst(inter, interpreter.EmptyLocationRange) + // Deep remove the original array - // Removed value must be same as the original value - utils.AssertValuesEqual(t, inter, orgValue, removedValue) + // TODO: is has no parent container = true correct? + original.DeepRemove(inter, true) - deleteCount++ - } + if !original.Inlined() { + err := inter.Storage().Remove(original.SlabID()) + require.NoError(t, err) } - // Dictionary must be empty - require.Equal(t, 0, testArray.Count()) + err := inter.Storage().CheckHealth() + require.NoError(t, err) - storageSize, slabCounts := getSlabStorageSize(t, storage) + // New array should still be accessible - // Storage size after removals should be same as the size before insertion. - assert.Equal(t, startingStorageSize, storageSize) - assert.Equal(t, startingSlabCounts, slabCounts) - }) + doubleCheckArray( + inter, + resetStorage, + transferred, + expectedValue, + newOwner, + ) - t.Run("move", func(t *testing.T) { - values := make([]interpreter.Value, numberOfValues) - elements := make([]interpreter.Value, numberOfValues) + // TODO: check deep removal cleaned up everything in original account (storage size, slab count) + }) - for i := 0; i < numberOfValues; i++ { - value := r.randomStorableValue(inter, 0) - elements[i] = value - values[i] = value.Clone(inter) - } + t.Run("insert", func(t *testing.T) { + t.Parallel() - array := interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - orgOwner, - values..., - ) + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) - require.Equal(t, len(elements), array.Count()) + inter, resetStorage := newRandomValueTestInterpreter(t) - owner := array.GetOwner() - assert.Equal(t, orgOwner, owner) + array, expectedValue, reloadArray := createArray(&r, inter) - newOwner := atree.Address{'B'} - movedArray := array.Transfer( - inter, - interpreter.EmptyLocationRange, - newOwner, - true, - nil, - nil, - true, // array is standalone. - ).(*interpreter.ArrayValue) + resetStorage() - require.Equal(t, len(elements), movedArray.Count()) + // Reload the array after the reset - // Cleanup the slab of original array. - err := storage.Remove(array.SlabID()) - require.NoError(t, err) + array = reloadArray() - // Check the elements - for index, orgElement := range elements { - element := movedArray.Get(inter, interpreter.EmptyLocationRange, index) - utils.AssertValuesEqual(t, inter, orgElement, element) - } + // Insert new values into the array. - owner = movedArray.GetOwner() - assert.Equal(t, newOwner[:], owner[:]) - }) -} + numberOfValues := r.randomInt(containerMaxSize) -func TestInterpretRandomCompositeValueOperations(t *testing.T) { - if !*runSmokeTests { - t.Skip("smoke tests are disabled") - } + for i := 0; i < numberOfValues; i++ { - r := newRandomValueGenerator() - t.Logf("seed: %d", r.seed) + value := r.randomStorableValue(inter, 0) + importedValue := importValue(t, inter, value) - storage := newUnmeteredInMemoryStorage() - inter, err := interpreter.NewInterpreter( - &interpreter.Program{ - Program: ast.NewProgram(nil, []ast.Declaration{}), - Elaboration: sema.NewElaboration(nil), - }, - utils.TestLocation, - &interpreter.Config{ - Storage: storage, - ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { - return interpreter.VirtualImport{ - Elaboration: inter.Program.Elaboration, - } - }, - }, - ) - require.NoError(t, err) + // Generate a random index + index := r.rand.Intn(len(expectedValue.Values)) - var testComposite, copyOfTestComposite *interpreter.CompositeValue - var storageSize, slabCounts int - var orgFields map[string]interpreter.Value + expectedValue.Values = append(expectedValue.Values, nil) + copy(expectedValue.Values[index+1:], expectedValue.Values[index:]) + expectedValue.Values[index] = value - fieldsCount := r.randomInt(compositeMaxFields) - orgOwner := common.Address{'A'} + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. - t.Run("construction", func(t *testing.T) { - testComposite, orgFields = r.randomCompositeValue(orgOwner, fieldsCount, inter, 0) + _ = withoutAtreeStorageValidationEnabled(inter, func() struct{} { - storageSize, slabCounts = getSlabStorageSize(t, storage) + array.Insert( + inter, + interpreter.EmptyLocationRange, + index, + importedValue, + ) - for fieldName, orgFieldValue := range orgFields { - fieldValue := testComposite.GetField(inter, interpreter.EmptyLocationRange, fieldName) - utils.AssertValuesEqual(t, inter, orgFieldValue, fieldValue) + return struct{}{} + }) } - owner := testComposite.GetOwner() - assert.Equal(t, orgOwner, owner) - }) + err := inter.Storage().CheckHealth() + require.NoError(t, err) - t.Run("iterate", func(t *testing.T) { - fieldCount := 0 - testComposite.ForEachField(inter, func(name string, value interpreter.Value) (resume bool) { - orgValue, ok := orgFields[name] - require.True(t, ok) - utils.AssertValuesEqual(t, inter, orgValue, value) - fieldCount++ - - // continue iteration - return true - }, interpreter.EmptyLocationRange) - - assert.Equal(t, len(orgFields), fieldCount) + doubleCheckArray( + inter, + resetStorage, + array, + expectedValue, + orgOwner, + ) }) - t.Run("deep copy", func(t *testing.T) { - newOwner := atree.Address{'B'} - - copyOfTestComposite = testComposite.Transfer( - inter, - interpreter.EmptyLocationRange, - newOwner, - false, - nil, - nil, - true, // testComposite is standalone. - ).(*interpreter.CompositeValue) + t.Run("remove", func(t *testing.T) { + t.Parallel() - for name, orgValue := range orgFields { - value := copyOfTestComposite.GetField(inter, interpreter.EmptyLocationRange, name) - utils.AssertValuesEqual(t, inter, orgValue, value) - } + r := newRandomValueGenerator(1736809620917220000) + t.Logf("seed: %d", r.seed) - owner := copyOfTestComposite.GetOwner() - assert.Equal(t, newOwner[:], owner[:]) - }) + inter, resetStorage := newRandomValueTestInterpreter(t) - t.Run("deep remove", func(t *testing.T) { - copyOfTestComposite.DeepRemove(inter, true) - err = storage.Remove(copyOfTestComposite.SlabID()) - require.NoError(t, err) + array, expectedValue, reloadArray := createArray(&r, inter) - // deep removal should clean up everything - newStorageSize, newSlabCounts := getSlabStorageSize(t, storage) - assert.Equal(t, slabCounts, newSlabCounts) - assert.Equal(t, storageSize, newStorageSize) + resetStorage() - // go over original values again and check no missing data (no side effect should be found) - for name, orgValue := range orgFields { - value := testComposite.GetField(inter, interpreter.EmptyLocationRange, name) - utils.AssertValuesEqual(t, inter, orgValue, value) - } + // Reload the array after the reset - owner := testComposite.GetOwner() - assert.Equal(t, orgOwner, owner) - }) + array = reloadArray() - t.Run("remove field", func(t *testing.T) { - newOwner := atree.Address{'c'} + // Random remove + numberOfValues := len(expectedValue.Values) + for i := 0; i < numberOfValues; i++ { - composite := testComposite.Transfer( - inter, - interpreter.EmptyLocationRange, - newOwner, - false, - nil, - nil, - true, // testComposite is standalone. - ).(*interpreter.CompositeValue) + index := r.rand.Intn(len(expectedValue.Values)) - require.NoError(t, err) + value := importValue(t, inter, expectedValue.Values[index]) - for name := range orgFields { - composite.RemoveField(inter, interpreter.EmptyLocationRange, name) - value := composite.GetField(inter, interpreter.EmptyLocationRange, name) - assert.Nil(t, value) - } - }) + expectedValue.Values = append( + expectedValue.Values[:index], + expectedValue.Values[index+1:]..., + ) - t.Run("move", func(t *testing.T) { - composite, fields := r.randomCompositeValue(orgOwner, fieldsCount, inter, 0) + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. - owner := composite.GetOwner() - assert.Equal(t, orgOwner, owner) + removedValue := withoutAtreeStorageValidationEnabled(inter, func() interpreter.Value { + return array.Remove(inter, interpreter.EmptyLocationRange, index) + }) - newOwner := atree.Address{'B'} - movedComposite := composite.Transfer( - inter, - interpreter.EmptyLocationRange, - newOwner, - true, - nil, - nil, - true, // composite is standalone. - ).(*interpreter.CompositeValue) + // Removed value must be same as the original value + utils.AssertValuesEqual(t, inter, value, removedValue) + } - // Cleanup the slab of original composite. - err := storage.Remove(composite.SlabID()) + err := inter.Storage().CheckHealth() require.NoError(t, err) - // Check the elements - for fieldName, orgFieldValue := range fields { - fieldValue := movedComposite.GetField(inter, interpreter.EmptyLocationRange, fieldName) - utils.AssertValuesEqual(t, inter, orgFieldValue, fieldValue) - } + // Array must be empty + require.Equal(t, 0, array.Count()) + + doubleCheckArray( + inter, + resetStorage, + array, + expectedValue, + orgOwner, + ) - owner = composite.GetOwner() - assert.Equal(t, orgOwner, owner) + // TODO: check storage size, slab count }) -} -func (r randomValueGenerator) randomCompositeValue( - orgOwner common.Address, - fieldsCount int, - inter *interpreter.Interpreter, - currentDepth int, -) (*interpreter.CompositeValue, map[string]interpreter.Value) { + t.Run("update", func(t *testing.T) { + t.Parallel() - orgFields := make(map[string]interpreter.Value, fieldsCount) + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) - identifier := r.randomUTF8String() + inter, resetStorage := newRandomValueTestInterpreter(t) - location := common.AddressLocation{ - Address: orgOwner, - Name: identifier, - } + array, expectedValue, reloadArray := createArray(&r, inter) - fields := make([]interpreter.CompositeField, fieldsCount) + resetStorage() - fieldNames := make(map[string]any, fieldsCount) + // Reload the array after the reset - for i := 0; i < fieldsCount; { - fieldName := r.randomUTF8String() + array = reloadArray() - // avoid duplicate field names - if _, ok := fieldNames[fieldName]; ok { - continue - } - fieldNames[fieldName] = struct{}{} + elementCount := array.Count() - field := interpreter.NewUnmeteredCompositeField( - fieldName, - r.randomStorableValue(inter, currentDepth+1), - ) + // Random update + for i := 0; i < len(expectedValue.Values); i++ { - fields[i] = field - orgFields[field.Name] = field.Value.Clone(inter) + index := r.rand.Intn(len(expectedValue.Values)) - i++ - } + expectedValue.Values[index] = r.randomStorableValue(inter, 0) + newValue := importValue(t, inter, expectedValue.Values[index]) - kind := common.CompositeKindStructure + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. - compositeType := &sema.CompositeType{ - Location: location, - Identifier: identifier, - Kind: kind, - } + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + array.Set( + inter, + interpreter.EmptyLocationRange, + index, + newValue, + ) + return struct{}{} + }) - compositeType.Members = &sema.StringMemberOrderedMap{} - for _, field := range fields { - compositeType.Members.Set( - field.Name, - sema.NewUnmeteredPublicConstantFieldMember( - compositeType, - field.Name, - sema.AnyStructType, - "", - ), - ) - } + } - // Add the type to the elaboration, to short-circuit the type-lookup - inter.Program.Elaboration.SetCompositeType( - compositeType.ID(), - compositeType, - ) + err := inter.Storage().CheckHealth() + require.NoError(t, err) - testComposite := interpreter.NewCompositeValue( - inter, - interpreter.EmptyLocationRange, - location, - identifier, - kind, - fields, - orgOwner, - ) - return testComposite, orgFields + // Array must have same number of key-value pairs + require.Equal(t, elementCount, array.Count()) + + doubleCheckArray( + inter, + resetStorage, + array, + expectedValue, + orgOwner, + ) + + // TODO: check storage size, slab count + }) } func getSlabStorageSize(t *testing.T, storage interpreter.InMemoryStorage) (totalSize int, slabCounts int) { @@ -1229,8 +1225,7 @@ type randomValueGenerator struct { rand *rand.Rand } -func newRandomValueGenerator() randomValueGenerator { - seed := *smokeTestSeed +func newRandomValueGenerator(seed int64) randomValueGenerator { if seed == -1 { seed = time.Now().UnixNano() } @@ -1240,10 +1235,10 @@ func newRandomValueGenerator() randomValueGenerator { rand: rand.New(rand.NewSource(seed)), } } -func (r randomValueGenerator) randomStorableValue(inter *interpreter.Interpreter, currentDepth int) interpreter.Value { +func (r randomValueGenerator) randomStorableValue(inter *interpreter.Interpreter, currentDepth int) cadence.Value { n := 0 if currentDepth < containerMaxDepth { - n = r.randomInt(randomValueKindComposite) + n = r.randomInt(randomValueKindStruct) } else { n = r.randomInt(randomValueKindCapability) } @@ -1252,30 +1247,27 @@ func (r randomValueGenerator) randomStorableValue(inter *interpreter.Interpreter // Non-hashable case randomValueKindVoid: - return interpreter.Void + return cadence.Void{} + case randomValueKindNil: - return interpreter.Nil + return cadence.NewOptional(nil) + case randomValueKindDictionaryVariant1, randomValueKindDictionaryVariant2: return r.randomDictionaryValue(inter, currentDepth) + case randomValueKindArrayVariant1, randomValueKindArrayVariant2: return r.randomArrayValue(inter, currentDepth) - case randomValueKindComposite: - fieldsCount := r.randomInt(compositeMaxFields) - v, _ := r.randomCompositeValue(common.ZeroAddress, fieldsCount, inter, currentDepth) - return v + + case randomValueKindStruct: + return r.randomStructValue(inter, currentDepth) + case randomValueKindCapability: - return interpreter.NewUnmeteredCapabilityValue( - interpreter.UInt64Value(r.randomInt(math.MaxInt-1)), - r.randomAddressValue(), - &interpreter.ReferenceStaticType{ - Authorization: interpreter.UnauthorizedAccess, - ReferencedType: interpreter.PrimitiveStaticTypeAnyStruct, - }, - ) + return r.randomCapabilityValue() + case randomValueKindSome: - return interpreter.NewUnmeteredSomeValueNonCopying( + return cadence.NewOptional( r.randomStorableValue(inter, currentDepth+1), ) @@ -1285,74 +1277,78 @@ func (r randomValueGenerator) randomStorableValue(inter *interpreter.Interpreter } } -func (r randomValueGenerator) randomHashableValue(interpreter *interpreter.Interpreter) interpreter.Value { - return r.generateRandomHashableValue(interpreter, r.randomInt(randomValueKindEnum)) +func (r randomValueGenerator) randomHashableValue(inter *interpreter.Interpreter) cadence.Value { + return r.generateRandomHashableValue(inter, r.randomInt(randomValueKindEnum)) } -func (r randomValueGenerator) generateRandomHashableValue(inter *interpreter.Interpreter, n int) interpreter.Value { +func (r randomValueGenerator) generateRandomHashableValue(inter *interpreter.Interpreter, n int) cadence.Value { switch n { // Int* case randomValueKindInt: - return interpreter.NewUnmeteredIntValueFromInt64(int64(r.randomSign()) * r.rand.Int63()) + // TODO: generate larger numbers + return cadence.NewInt(r.randomSign() * int(r.rand.Int63())) case randomValueKindInt8: - return interpreter.NewUnmeteredInt8Value(int8(r.randomInt(math.MaxUint8))) + return cadence.NewInt8(int8(r.randomInt(math.MaxUint8))) case randomValueKindInt16: - return interpreter.NewUnmeteredInt16Value(int16(r.randomInt(math.MaxUint16))) + return cadence.NewInt16(int16(r.randomInt(math.MaxUint16))) case randomValueKindInt32: - return interpreter.NewUnmeteredInt32Value(int32(r.randomSign()) * r.rand.Int31()) + return cadence.NewInt32(int32(r.randomSign()) * r.rand.Int31()) case randomValueKindInt64: - return interpreter.NewUnmeteredInt64Value(int64(r.randomSign()) * r.rand.Int63()) + return cadence.NewInt64(int64(r.randomSign()) * r.rand.Int63()) case randomValueKindInt128: - return interpreter.NewUnmeteredInt128ValueFromInt64(int64(r.randomSign()) * r.rand.Int63()) + // TODO: generate larger numbers + return cadence.NewInt128(r.randomSign() * int(r.rand.Int63())) case randomValueKindInt256: - return interpreter.NewUnmeteredInt256ValueFromInt64(int64(r.randomSign()) * r.rand.Int63()) + // TODO: generate larger numbers + return cadence.NewInt256(r.randomSign() * int(r.rand.Int63())) // UInt* case randomValueKindUInt: - return interpreter.NewUnmeteredUIntValueFromUint64(r.rand.Uint64()) + // TODO: generate larger numbers + return cadence.NewUInt(uint(r.rand.Uint64())) case randomValueKindUInt8: - return interpreter.NewUnmeteredUInt8Value(uint8(r.randomInt(math.MaxUint8))) + return cadence.NewUInt8(uint8(r.randomInt(math.MaxUint8))) case randomValueKindUInt16: - return interpreter.NewUnmeteredUInt16Value(uint16(r.randomInt(math.MaxUint16))) + return cadence.NewUInt16(uint16(r.randomInt(math.MaxUint16))) case randomValueKindUInt32: - return interpreter.NewUnmeteredUInt32Value(r.rand.Uint32()) + return cadence.NewUInt32(r.rand.Uint32()) case randomValueKindUInt64Variant1, randomValueKindUInt64Variant2, randomValueKindUInt64Variant3, randomValueKindUInt64Variant4: // should be more common - return interpreter.NewUnmeteredUInt64Value(r.rand.Uint64()) + return cadence.NewUInt64(r.rand.Uint64()) case randomValueKindUInt128: - return interpreter.NewUnmeteredUInt128ValueFromUint64(r.rand.Uint64()) + // TODO: generate larger numbers + return cadence.NewUInt128(uint(r.rand.Uint64())) case randomValueKindUInt256: - return interpreter.NewUnmeteredUInt256ValueFromUint64(r.rand.Uint64()) + // TODO: generate larger numbers + return cadence.NewUInt256(uint(r.rand.Uint64())) // Word* case randomValueKindWord8: - return interpreter.NewUnmeteredWord8Value(uint8(r.randomInt(math.MaxUint8))) + return cadence.NewWord8(uint8(r.randomInt(math.MaxUint8))) case randomValueKindWord16: - return interpreter.NewUnmeteredWord16Value(uint16(r.randomInt(math.MaxUint16))) + return cadence.NewWord16(uint16(r.randomInt(math.MaxUint16))) case randomValueKindWord32: - return interpreter.NewUnmeteredWord32Value(r.rand.Uint32()) + return cadence.NewWord32(r.rand.Uint32()) case randomValueKindWord64: - return interpreter.NewUnmeteredWord64Value(r.rand.Uint64()) + return cadence.NewWord64(r.rand.Uint64()) case randomValueKindWord128: - return interpreter.NewUnmeteredWord128ValueFromUint64(r.rand.Uint64()) + // TODO: generate larger numbers + return cadence.NewWord128(uint(r.rand.Uint64())) case randomValueKindWord256: - return interpreter.NewUnmeteredWord256ValueFromUint64(r.rand.Uint64()) + // TODO: generate larger numbers + return cadence.NewWord256(uint(r.rand.Uint64())) // (U)Fix* case randomValueKindFix64: - return interpreter.NewUnmeteredFix64ValueWithInteger( - int64(r.randomSign())*r.rand.Int63n(sema.Fix64TypeMaxInt), - interpreter.EmptyLocationRange, + return cadence.Fix64( + int64(r.randomSign()) * r.rand.Int63n(sema.Fix64TypeMaxInt), ) case randomValueKindUFix64: - return interpreter.NewUnmeteredUFix64ValueWithInteger( - uint64(r.rand.Int63n( - int64(sema.UFix64TypeMaxInt), - )), - interpreter.EmptyLocationRange, + return cadence.UFix64( + uint64(r.rand.Int63n(int64(sema.UFix64TypeMaxInt))), ) // String @@ -1361,15 +1357,15 @@ func (r randomValueGenerator) generateRandomHashableValue(inter *interpreter.Int randomValueKindStringVariant3, randomValueKindStringVariant4: // small string - should be more common size := r.randomInt(255) - return interpreter.NewUnmeteredStringValue(r.randomUTF8StringOfSize(size)) + return cadence.String(r.randomUTF8StringOfSize(size)) case randomValueKindStringVariant5: // large string size := r.randomInt(4048) + 255 - return interpreter.NewUnmeteredStringValue(r.randomUTF8StringOfSize(size)) + return cadence.String(r.randomUTF8StringOfSize(size)) case randomValueKindBoolVariantTrue: - return interpreter.TrueValue + return cadence.NewBool(true) case randomValueKindBoolVariantFalse: - return interpreter.FalseValue + return cadence.NewBool(false) case randomValueKindAddress: return r.randomAddressValue() @@ -1378,52 +1374,7 @@ func (r randomValueGenerator) generateRandomHashableValue(inter *interpreter.Int return r.randomPathValue() case randomValueKindEnum: - // Get a random integer subtype to be used as the raw-type of enum - typ := r.randomInt(randomValueKindWord64) - - rawValue := r.generateRandomHashableValue(inter, typ).(interpreter.NumberValue) - - identifier := r.randomUTF8String() - - address := r.randomAddressValue() - - location := common.AddressLocation{ - Address: common.Address(address), - Name: identifier, - } - - enumType := &sema.CompositeType{ - Identifier: identifier, - EnumRawType: r.intSubtype(typ), - Kind: common.CompositeKindEnum, - Location: location, - } - - inter.Program.Elaboration.SetCompositeType( - enumType.ID(), - enumType, - ) - - enum := interpreter.NewCompositeValue( - inter, - interpreter.EmptyLocationRange, - location, - enumType.QualifiedIdentifier(), - enumType.Kind, - []interpreter.CompositeField{ - { - Name: sema.EnumRawValueFieldName, - Value: rawValue, - }, - }, - common.ZeroAddress, - ) - - if enum.GetField(inter, interpreter.EmptyLocationRange, sema.EnumRawValueFieldName) == nil { - panic("enum without raw value") - } - - return enum + return r.randomEnumValue(inter) default: panic(fmt.Sprintf("unsupported: %d", n)) @@ -1438,74 +1389,216 @@ func (r randomValueGenerator) randomSign() int { return -1 } -func (r randomValueGenerator) randomAddressValue() interpreter.AddressValue { - data := make([]byte, 8) - r.rand.Read(data) - return interpreter.NewUnmeteredAddressValueFromBytes(data) +func (r randomValueGenerator) randomAddressValue() (address cadence.Address) { + r.rand.Read(address[:]) + return address } -func (r randomValueGenerator) randomPathValue() interpreter.PathValue { +func (r randomValueGenerator) randomPathValue() cadence.Path { randomDomain := r.rand.Intn(len(common.AllPathDomains)) identifier := r.randomUTF8String() - return interpreter.PathValue{ + return cadence.Path{ Domain: common.AllPathDomains[randomDomain], Identifier: identifier, } } -func (r randomValueGenerator) randomDictionaryValue( - inter *interpreter.Interpreter, - currentDepth int, -) interpreter.Value { +func (r randomValueGenerator) randomCapabilityValue() cadence.Capability { + return cadence.NewCapability( + cadence.UInt64(r.randomInt(math.MaxInt-1)), + r.randomAddressValue(), + cadence.NewReferenceType( + cadence.UnauthorizedAccess, + cadence.AnyStructType, + ), + ) +} + +func (r randomValueGenerator) randomDictionaryValue(inter *interpreter.Interpreter, currentDepth int) cadence.Dictionary { entryCount := r.randomInt(containerMaxSize) - keyValues := make([]interpreter.Value, entryCount*2) + keyValues := make([]cadence.KeyValuePair, entryCount) + + existingKeys := map[string]struct{}{} for i := 0; i < entryCount; i++ { - key := r.randomHashableValue(inter) - value := r.randomStorableValue(inter, currentDepth+1) - keyValues[i*2] = key - keyValues[i*2+1] = value + + // generate a unique key + var key cadence.Value + for { + key = r.randomHashableValue(inter) + keyStr := key.String() + + // avoid duplicate keys + _, exists := existingKeys[keyStr] + if !exists { + existingKeys[keyStr] = struct{}{} + break + } + } + + keyValues[i] = cadence.KeyValuePair{ + Key: key, + Value: r.randomStorableValue(inter, currentDepth+1), + } } - return interpreter.NewDictionaryValueWithAddress( - inter, - interpreter.EmptyLocationRange, - &interpreter.DictionaryStaticType{ - KeyType: interpreter.PrimitiveStaticTypeAnyStruct, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, - }, - common.ZeroAddress, - keyValues..., - ) + return cadence.NewDictionary(keyValues). + WithType( + cadence.NewDictionaryType( + cadence.HashableStructType, + cadence.AnyStructType, + ), + ) } func (r randomValueGenerator) randomInt(upperBound int) int { return r.rand.Intn(upperBound + 1) } -func (r randomValueGenerator) randomArrayValue(inter *interpreter.Interpreter, currentDepth int) interpreter.Value { +func (r randomValueGenerator) randomArrayValue(inter *interpreter.Interpreter, currentDepth int) cadence.Array { elementsCount := r.randomInt(containerMaxSize) - elements := make([]interpreter.Value, elementsCount) + elements := make([]cadence.Value, elementsCount) for i := 0; i < elementsCount; i++ { - value := r.randomStorableValue(inter, currentDepth+1) - elements[i] = value.Clone(inter) + elements[i] = r.randomStorableValue(inter, currentDepth+1) } - return interpreter.NewArrayValue( - inter, - interpreter.EmptyLocationRange, - &interpreter.VariableSizedStaticType{ - Type: interpreter.PrimitiveStaticTypeAnyStruct, - }, - common.ZeroAddress, - elements..., + return cadence.NewArray(elements). + WithType(cadence.NewVariableSizedArrayType(cadence.AnyStructType)) +} + +func (r randomValueGenerator) randomStructValue(inter *interpreter.Interpreter, currentDepth int) cadence.Struct { + fieldsCount := r.randomInt(compositeMaxFields) + + fields := make([]cadence.Field, fieldsCount) + fieldValues := make([]cadence.Value, fieldsCount) + + existingFieldNames := make(map[string]any, fieldsCount) + + for i := 0; i < fieldsCount; i++ { + // generate a unique field name + var fieldName string + for { + fieldName = r.randomUTF8String() + + // avoid duplicate field names + _, exists := existingFieldNames[fieldName] + if !exists { + existingFieldNames[fieldName] = struct{}{} + break + } + } + + fields[i] = cadence.NewField(fieldName, cadence.AnyStructType) + fieldValues[i] = r.randomStorableValue(inter, currentDepth+1) + } + + identifier := fmt.Sprintf("S%d", r.rand.Uint64()) + + address := r.randomAddressValue() + + location := common.AddressLocation{ + Address: common.Address(address), + Name: identifier, + } + + kind := common.CompositeKindStructure + + compositeType := &sema.CompositeType{ + Location: location, + Identifier: identifier, + Kind: kind, + Members: &sema.StringMemberOrderedMap{}, + } + + for i := 0; i < fieldsCount; i++ { + fieldName := fields[i].Identifier + compositeType.Members.Set( + fieldName, + sema.NewUnmeteredPublicConstantFieldMember( + compositeType, + fieldName, + sema.AnyStructType, + "", + ), + ) + } + + // Add the type to the elaboration, to short-circuit the type-lookup. + inter.Program.Elaboration.SetCompositeType( + compositeType.ID(), + compositeType, + ) + + return cadence.NewStruct(fieldValues).WithType( + cadence.NewStructType( + location, + identifier, + fields, + nil, + ), ) } -func (r randomValueGenerator) intSubtype(n int) sema.Type { +func (r randomValueGenerator) cadenceIntegerType(n int) cadence.Type { + switch n { + // Int + case randomValueKindInt: + return cadence.IntType + case randomValueKindInt8: + return cadence.Int8Type + case randomValueKindInt16: + return cadence.Int16Type + case randomValueKindInt32: + return cadence.Int32Type + case randomValueKindInt64: + return cadence.Int64Type + case randomValueKindInt128: + return cadence.Int128Type + case randomValueKindInt256: + return cadence.Int256Type + + // UInt + case randomValueKindUInt: + return cadence.UIntType + case randomValueKindUInt8: + return cadence.UInt8Type + case randomValueKindUInt16: + return cadence.UInt16Type + case randomValueKindUInt32: + return cadence.UInt32Type + case randomValueKindUInt64Variant1, + randomValueKindUInt64Variant2, + randomValueKindUInt64Variant3, + randomValueKindUInt64Variant4: + return cadence.UInt64Type + case randomValueKindUInt128: + return cadence.UInt128Type + case randomValueKindUInt256: + return cadence.UInt256Type + + // Word + case randomValueKindWord8: + return cadence.Word8Type + case randomValueKindWord16: + return cadence.Word16Type + case randomValueKindWord32: + return cadence.Word32Type + case randomValueKindWord64: + return cadence.Word64Type + case randomValueKindWord128: + return cadence.Word128Type + case randomValueKindWord256: + return cadence.Word256Type + + default: + panic(fmt.Sprintf("unsupported: %d", n)) + } +} + +func (r randomValueGenerator) semaIntegerType(n int) sema.Type { switch n { // Int case randomValueKindInt: @@ -1620,7 +1713,7 @@ const ( randomValueKindArrayVariant2 randomValueKindDictionaryVariant1 randomValueKindDictionaryVariant2 - randomValueKindComposite + randomValueKindStruct ) func (r randomValueGenerator) randomUTF8String() string { @@ -1633,77 +1726,113 @@ func (r randomValueGenerator) randomUTF8StringOfSize(size int) string { return strings.ToValidUTF8(string(identifier), "$") } -type valueMap struct { - values map[any]interpreter.Value - keys map[any]interpreter.Value -} +func (r randomValueGenerator) randomEnumValue(inter *interpreter.Interpreter) cadence.Enum { + // Get a random integer subtype to be used as the raw-type of enum + typ := r.randomInt(randomValueKindWord64) + + rawValue := r.generateRandomHashableValue(inter, typ).(cadence.NumberValue) + + identifier := fmt.Sprintf("E%d", r.rand.Uint64()) + + address := r.randomAddressValue() + + location := common.AddressLocation{ + Address: common.Address(address), + Name: identifier, + } + + semaRawType := r.semaIntegerType(typ) -func newValueMap(size int) *valueMap { - return &valueMap{ - values: make(map[any]interpreter.Value, size), - keys: make(map[any]interpreter.Value, size), + semaEnumType := &sema.CompositeType{ + Identifier: identifier, + EnumRawType: semaRawType, + Kind: common.CompositeKindEnum, + Location: location, + Members: &sema.StringMemberOrderedMap{}, } -} -type enumKey struct { - location common.Location - qualifiedIdentifier string - kind common.CompositeKind - rawValue interpreter.Value -} + semaEnumType.Members.Set( + sema.EnumRawValueFieldName, + sema.NewUnmeteredPublicConstantFieldMember( + semaEnumType, + sema.EnumRawValueFieldName, + semaRawType, + "", + ), + ) + + // Add the type to the elaboration, to short-circuit the type-lookup. + inter.Program.Elaboration.SetCompositeType( + semaEnumType.ID(), + semaEnumType, + ) -func (m *valueMap) put(inter *interpreter.Interpreter, key, value interpreter.Value) { - internalKey := m.internalKey(inter, key) + rawType := r.cadenceIntegerType(typ) - // Deep copy enum keys. This should be fine since we use an internal key for enums. - // Deep copying other values would mess key-lookup. - if _, ok := key.(*interpreter.CompositeValue); ok { - key = key.Clone(inter) + fields := []cadence.Value{ + rawValue, } - m.keys[internalKey] = key - m.values[internalKey] = value.Clone(inter) + return cadence.NewEnum(fields).WithType( + cadence.NewEnumType( + location, + identifier, + rawType, + []cadence.Field{ + { + Identifier: sema.EnumRawValueFieldName, + Type: rawType, + }, + }, + nil, + ), + ) } -func (m *valueMap) get(inter *interpreter.Interpreter, key interpreter.Value) (interpreter.Value, bool) { - internalKey := m.internalKey(inter, key) - value, ok := m.values[internalKey] - return value, ok -} +func TestRandomValueGeneration(t *testing.T) { -func (m *valueMap) foreach(apply func(key, value interpreter.Value) (exit bool)) { - for internalKey, key := range m.keys { - value := m.values[internalKey] - exit := apply(key, value) + inter, _ := newRandomValueTestInterpreter(t) - if exit { - return - } + // Generate random values + for i := 0; i < 1000; i++ { + r1 := newRandomValueGenerator(int64(i)) + v1 := r1.randomStorableValue(inter, 0) + + r2 := newRandomValueGenerator(int64(i)) + v2 := r2.randomStorableValue(inter, 0) + + // Check if the generated values are equal + assert.Equal(t, v1, v2) } } -func (m *valueMap) internalKey(inter *interpreter.Interpreter, key interpreter.Value) any { +func mapKey(inter *interpreter.Interpreter, key interpreter.Value) any { switch key := key.(type) { case *interpreter.StringValue: return *key + case *interpreter.CompositeValue: + type enumKey struct { + location common.Location + qualifiedIdentifier string + kind common.CompositeKind + rawValue interpreter.Value + } return enumKey{ location: key.Location, qualifiedIdentifier: key.QualifiedIdentifier, kind: key.Kind, rawValue: key.GetField(inter, interpreter.EmptyLocationRange, sema.EnumRawValueFieldName), } + case interpreter.Value: return key + default: - panic("unreachable") + panic(errors.NewUnexpectedError("unsupported map key type: %T", key)) } } -func (m *valueMap) size() int { - return len(m.keys) -} - // This test is a reproducer for "slab was not reachable from leaves" false alarm. // https://github.com/onflow/cadence/pull/2882#issuecomment-1781298107 // In this test, storage.CheckHealth() should be called after array.DeepRemove(), @@ -1789,7 +1918,7 @@ func TestCheckStorageHealthInMiddleOfDeepRemove(t *testing.T) { // In this test, storage.CheckHealth() should be called after DictionaryValue.Transfer() // with remove flag, not in the middle of DictionaryValue.Transfer(). func TestCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { - r := newRandomValueGenerator() + r := newRandomValueGenerator(*smokeTestSeed) t.Logf("seed: %d", r.seed) storage := newUnmeteredInMemoryStorage() @@ -1931,28 +2060,3 @@ func TestCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { require.NoError(t, storage.CheckHealth()) } - -// TestInterpretArrayOperations tests ArrayValue operations with -// the a hardcoded seed that produced a past test failure when using -// the atree.WrapperValue interface (added to atree in January 2025). -// -// When WrapperValue (e.g. SomeValue) is inserted into atree array, -// atree array unwraps WrapperValue and tracks its inserted index in array. -// When such value is removed, atree array also needs to unwrap the removed -// value and untracks its index in the array. -// For more info about Wrappervalue and WrapperStorable, see: -// https://github.com/onflow/cadence-internal/issues/286 -// https://github.com/onflow/atree-internal/pull/2 -// https://github.com/onflow/cadence-internal/pull/287 -func TestInterpretArrayOperations(t *testing.T) { - - // Use this random seed because it reproduces prior test failure. - seed := int64(1735328920693279431) - - r := randomValueGenerator{ - seed: 1735328920693279431, - rand: rand.New(rand.NewSource(seed)), - } - - testInterpretArrayOperations(t, r) -} From e8d5a3a1b0c0c0528d2976429f981b90d1d7bef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 14 Jan 2025 09:35:57 -0800 Subject: [PATCH 26/80] check values before reset of storage --- runtime/tests/interpreter/values_test.go | 54 +++++++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 8330beddd..3dd40425a 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -535,7 +535,14 @@ func TestInterpretRandomMapOperations(t *testing.T) { dictionary, expectedValue, reloadDictionary := createDictionary(&r, inter) - resetStorage() + // Check dictionary and reset storage + doubleCheckDictionary( + inter, + resetStorage, + dictionary, + expectedValue, + orgOwner, + ) // Reload the dictionary after the reset @@ -617,7 +624,14 @@ func TestInterpretRandomMapOperations(t *testing.T) { dictionary, expectedValue, reloadDictionary := createDictionary(&r, inter) - resetStorage() + // Check dictionary and reset storage + doubleCheckDictionary( + inter, + resetStorage, + dictionary, + expectedValue, + orgOwner, + ) // Reload the dictionary after the reset @@ -676,7 +690,14 @@ func TestInterpretRandomMapOperations(t *testing.T) { dictionary, expectedValue, reloadDictionary := createDictionary(&r, inter) - resetStorage() + // Check dictionary and reset storage + doubleCheckDictionary( + inter, + resetStorage, + dictionary, + expectedValue, + orgOwner, + ) // Reload the dictionary after the reset @@ -1038,7 +1059,14 @@ func TestInterpretRandomArrayOperations(t *testing.T) { array, expectedValue, reloadArray := createArray(&r, inter) - resetStorage() + // Check array and reset storage + doubleCheckArray( + inter, + resetStorage, + array, + expectedValue, + orgOwner, + ) // Reload the array after the reset @@ -1098,7 +1126,14 @@ func TestInterpretRandomArrayOperations(t *testing.T) { array, expectedValue, reloadArray := createArray(&r, inter) - resetStorage() + // Check array and reset storage + doubleCheckArray( + inter, + resetStorage, + array, + expectedValue, + orgOwner, + ) // Reload the array after the reset @@ -1155,7 +1190,14 @@ func TestInterpretRandomArrayOperations(t *testing.T) { array, expectedValue, reloadArray := createArray(&r, inter) - resetStorage() + // Check array and reset storage + doubleCheckArray( + inter, + resetStorage, + array, + expectedValue, + orgOwner, + ) // Reload the array after the reset From 10909c36cf7b3217c49ccf3a2fa98980eaec11f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 14 Jan 2025 09:36:27 -0800 Subject: [PATCH 27/80] revert defaults for flags, remove dead code --- runtime/tests/interpreter/values_test.go | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 3dd40425a..f343bd73f 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -48,8 +48,8 @@ const containerMaxDepth = 3 const containerMaxSize = 50 const compositeMaxFields = 10 -var runSmokeTests = flag.Bool("runSmokeTests", true, "Run smoke tests on values") -var validateAtree = flag.Bool("validateAtree", true, "Enable atree validation") +var runSmokeTests = flag.Bool("runSmokeTests", false, "Run smoke tests on values") +var validateAtree = flag.Bool("validateAtree", false, "Enable atree validation") var smokeTestSeed = flag.Int64("smokeTestSeed", -1, "Seed for prng (-1 specifies current Unix time)") func newRandomValueTestInterpreter(t *testing.T) (inter *interpreter.Interpreter, resetStorage func()) { @@ -1246,22 +1246,6 @@ func TestInterpretRandomArrayOperations(t *testing.T) { }) } -func getSlabStorageSize(t *testing.T, storage interpreter.InMemoryStorage) (totalSize int, slabCounts int) { - slabs, err := storage.Encode() - require.NoError(t, err) - - for id, slab := range slabs { - if id.HasTempAddress() { - continue - } - - totalSize += len(slab) - slabCounts++ - } - - return -} - type randomValueGenerator struct { seed int64 rand *rand.Rand From fa33dabbaabb8557e17829439943ccc4c6bf6cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 14 Jan 2025 09:37:45 -0800 Subject: [PATCH 28/80] only check storage health if atree validation is enabled --- runtime/tests/interpreter/values_test.go | 48 ++++++++++++++++-------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index f343bd73f..d0561b9a4 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -509,8 +509,10 @@ func TestInterpretRandomMapOperations(t *testing.T) { require.NoError(t, err) } - err := inter.Storage().CheckHealth() - require.NoError(t, err) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } // New dictionary should still be accessible @@ -602,8 +604,10 @@ func TestInterpretRandomMapOperations(t *testing.T) { ) } - err := inter.Storage().CheckHealth() - require.NoError(t, err) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } doubleCheckDictionary( inter, @@ -660,8 +664,10 @@ func TestInterpretRandomMapOperations(t *testing.T) { utils.AssertValuesEqual(t, inter, value, innerValue) } - err := inter.Storage().CheckHealth() - require.NoError(t, err) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } expectedValue = cadence.Dictionary{}. WithType(expectedValue.Type().(*cadence.DictionaryType)) @@ -742,8 +748,10 @@ func TestInterpretRandomMapOperations(t *testing.T) { expectedValue.Pairs[i].Value = newValues[i] } - err := inter.Storage().CheckHealth() - require.NoError(t, err) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } // Dictionary must have same number of key-value pairs require.Equal(t, elementCount, dictionary.Count()) @@ -1033,8 +1041,10 @@ func TestInterpretRandomArrayOperations(t *testing.T) { require.NoError(t, err) } - err := inter.Storage().CheckHealth() - require.NoError(t, err) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } // New array should still be accessible @@ -1104,8 +1114,10 @@ func TestInterpretRandomArrayOperations(t *testing.T) { }) } - err := inter.Storage().CheckHealth() - require.NoError(t, err) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } doubleCheckArray( inter, @@ -1163,8 +1175,10 @@ func TestInterpretRandomArrayOperations(t *testing.T) { utils.AssertValuesEqual(t, inter, value, removedValue) } - err := inter.Storage().CheckHealth() - require.NoError(t, err) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } // Array must be empty require.Equal(t, 0, array.Count()) @@ -1228,8 +1242,10 @@ func TestInterpretRandomArrayOperations(t *testing.T) { } - err := inter.Storage().CheckHealth() - require.NoError(t, err) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } // Array must have same number of key-value pairs require.Equal(t, elementCount, array.Count()) From 07c12fbfb5910b7123ec5c2569a5390c5ade0733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 14 Jan 2025 09:47:24 -0800 Subject: [PATCH 29/80] fix index generation --- runtime/tests/interpreter/values_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index d0561b9a4..31bafb6a2 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -1082,17 +1082,22 @@ func TestInterpretRandomArrayOperations(t *testing.T) { array = reloadArray() + existingValueCount := len(expectedValue.Values) + // Insert new values into the array. - numberOfValues := r.randomInt(containerMaxSize) + newValueCount := r.randomInt(containerMaxSize) - for i := 0; i < numberOfValues; i++ { + for i := 0; i < newValueCount; i++ { value := r.randomStorableValue(inter, 0) importedValue := importValue(t, inter, value) // Generate a random index - index := r.rand.Intn(len(expectedValue.Values)) + index := 0 + if existingValueCount > 0 { + index = r.rand.Intn(existingValueCount) + } expectedValue.Values = append(expectedValue.Values, nil) copy(expectedValue.Values[index+1:], expectedValue.Values[index:]) From 9da9027c0cc7974be56fb383a419f928f5598c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 14 Jan 2025 11:00:51 -0800 Subject: [PATCH 30/80] add tests for composite values, set field names of generated composite type --- runtime/interpreter/value.go | 4 + runtime/tests/interpreter/values_test.go | 340 ++++++++++++++++++++++- 2 files changed, 339 insertions(+), 5 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 39030a2ce..8c590737a 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -19142,6 +19142,10 @@ func (v *CompositeValue) ForEach( } } +func (v *CompositeValue) Inlined() bool { + return v.dictionary.Inlined() +} + type InclusiveRangeIterator struct { rangeValue *CompositeValue next IntegerValue diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 31bafb6a2..2629eaacb 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -207,7 +207,7 @@ func withoutAtreeStorageValidationEnabled[T any](inter *interpreter.Interpreter, return result } -func TestInterpretRandomMapOperations(t *testing.T) { +func TestInterpretRandomDictionaryOperations(t *testing.T) { if !*runSmokeTests { t.Skip("smoke tests are disabled") } @@ -768,6 +768,332 @@ func TestInterpretRandomMapOperations(t *testing.T) { }) } +func TestInterpretRandomCompositeOperations(t *testing.T) { + if !*runSmokeTests { + t.Skip("smoke tests are disabled") + } + + t.Parallel() + + orgOwner := common.Address{'A'} + + const compositeStorageMapKey = interpreter.StringStorageMapKey("composite") + + createComposite := func( + r *randomValueGenerator, + inter *interpreter.Interpreter, + ) ( + *interpreter.CompositeValue, + cadence.Struct, + func() *interpreter.CompositeValue, + ) { + expectedValue := r.randomStructValue(inter, 0) + + fieldsMappedByName := expectedValue.FieldsMappedByName() + fields := make([]interpreter.CompositeField, 0, len(fieldsMappedByName)) + for name, field := range fieldsMappedByName { + + value := importValue(t, inter, field) + + fields = append(fields, interpreter.CompositeField{ + Name: name, + Value: value, + }) + } + + // Construct a composite directly in the owner's account. + // However, the composite is not referenced by the root of the storage yet + // (a storage map), so atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + composite := withoutAtreeStorageValidationEnabled( + inter, + func() *interpreter.CompositeValue { + return interpreter.NewCompositeValue( + inter, + interpreter.EmptyLocationRange, + expectedValue.StructType.Location, + expectedValue.StructType.QualifiedIdentifier, + common.CompositeKindStructure, + fields, + orgOwner, + ) + }, + ) + + // Store the composite in a storage map, so that the composite's slab + // is referenced by the root of the storage. + + inter.Storage(). + GetStorageMap( + orgOwner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + compositeStorageMapKey, + composite, + ) + + reloadComposite := func() *interpreter.CompositeValue { + storageMap := inter.Storage().GetStorageMap( + orgOwner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, compositeStorageMapKey) + require.NotNil(t, readValue) + + require.IsType(t, &interpreter.CompositeValue{}, readValue) + return readValue.(*interpreter.CompositeValue) + } + + return composite, expectedValue, reloadComposite + } + + checkComposite := func( + inter *interpreter.Interpreter, + composite *interpreter.CompositeValue, + expectedValue cadence.Struct, + expectedOwner common.Address, + ) { + fieldsMappedByName := expectedValue.FieldsMappedByName() + + require.Equal(t, len(fieldsMappedByName), composite.FieldCount()) + + for name, field := range fieldsMappedByName { + + value := composite.GetMember(inter, interpreter.EmptyLocationRange, name) + + fieldValue := importValue(t, inter, field) + utils.AssertValuesEqual(t, inter, fieldValue, value) + } + + owner := composite.GetOwner() + assert.Equal(t, expectedOwner, owner) + } + + doubleCheckComposite := func( + inter *interpreter.Interpreter, + resetStorage func(), + composite *interpreter.CompositeValue, + expectedValue cadence.Struct, + expectedOwner common.Address, + ) { + // Check the values of the composite. + // Once right away, and once after a reset (commit and reload) of the storage. + + for i := 0; i < 2; i++ { + + checkComposite( + inter, + composite, + expectedValue, + expectedOwner, + ) + + resetStorage() + } + } + + t.Run("construction", func(t *testing.T) { + + t.Parallel() + + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) + + inter, resetStorage := newRandomValueTestInterpreter(t) + + composite, expectedValue, _ := createComposite(&r, inter) + + doubleCheckComposite( + inter, + resetStorage, + composite, + expectedValue, + orgOwner, + ) + }) + + t.Run("move (transfer and deep remove)", func(t *testing.T) { + + t.Parallel() + + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) + + inter, resetStorage := newRandomValueTestInterpreter(t) + + original, expectedValue, _ := createComposite(&r, inter) + + resetStorage() + + // Transfer the composite to a new owner + + newOwner := common.Address{'B'} + + transferred := original.Transfer( + inter, + interpreter.EmptyLocationRange, + atree.Address(newOwner), + false, + nil, + nil, + // TODO: is has no parent container = true correct? + true, + ).(*interpreter.CompositeValue) + + // Store the transferred composite in a storage map, so that the composote's slab + // is referenced by the root of the storage. + + inter.Storage(). + GetStorageMap( + newOwner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + interpreter.StringStorageMapKey("transferred_composite"), + transferred, + ) + + // Both original and transferred composite should contain the expected values + // Check once right away, and once after a reset (commit and reload) of the storage. + + for i := 0; i < 2; i++ { + + checkComposite( + inter, + original, + expectedValue, + orgOwner, + ) + + checkComposite( + inter, + transferred, + expectedValue, + newOwner, + ) + + resetStorage() + } + + // Deep remove the original composite + + // TODO: is has no parent container = true correct? + original.DeepRemove(inter, true) + + if !original.Inlined() { + err := inter.Storage().Remove(original.SlabID()) + require.NoError(t, err) + } + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // New composite should still be accessible + + doubleCheckComposite( + inter, + resetStorage, + transferred, + expectedValue, + newOwner, + ) + + // TODO: check deep removal cleaned up everything in original account (storage size, slab count) + }) + + t.Run("update", func(t *testing.T) { + t.Parallel() + + r := newRandomValueGenerator(*smokeTestSeed) + t.Logf("seed: %d", r.seed) + + inter, resetStorage := newRandomValueTestInterpreter(t) + + composite, expectedValue, reloadComposite := createComposite(&r, inter) + + // Check composite and reset storage + doubleCheckComposite( + inter, + resetStorage, + composite, + expectedValue, + orgOwner, + ) + + // Reload the composite after the reset + + composite = reloadComposite() + + typeID := expectedValue.StructType.Location. + TypeID(nil, expectedValue.StructType.QualifiedIdentifier) + compositeType := inter.Program.Elaboration.CompositeType(typeID) + + typeFieldCount := len(compositeType.Fields) + require.Equal(t, typeFieldCount, len(expectedValue.FieldsMappedByName())) + require.Equal(t, typeFieldCount, composite.FieldCount()) + + // Generate new values + + newValues := make([]cadence.Value, typeFieldCount) + + for i := range compositeType.Fields { + newValues[i] = r.randomStorableValue(inter, 0) + } + + // Update + for i, name := range compositeType.Fields { + + newValue := importValue(t, inter, newValues[i]) + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + existed := withoutAtreeStorageValidationEnabled(inter, func() bool { + return composite.SetMember( + inter, + interpreter.EmptyLocationRange, + name, + newValue, + ) + }) + + require.True(t, existed) + } + + expectedValue = cadence.NewStruct(newValues). + WithType(expectedValue.Type().(*cadence.StructType)) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // Composite must have same number of key-value pairs + require.Equal(t, typeFieldCount, composite.FieldCount()) + + doubleCheckComposite( + inter, + resetStorage, + composite, + expectedValue, + orgOwner, + ) + + // TODO: check storage size, slab count + }) +} + func TestInterpretRandomArrayOperations(t *testing.T) { if !*runSmokeTests { t.Skip("smoke tests are disabled") @@ -1320,15 +1646,15 @@ func (r randomValueGenerator) randomStorableValue(inter *interpreter.Interpreter // Hashable default: - return r.generateRandomHashableValue(inter, n) + return r.generateHashableValueOfType(inter, n) } } func (r randomValueGenerator) randomHashableValue(inter *interpreter.Interpreter) cadence.Value { - return r.generateRandomHashableValue(inter, r.randomInt(randomValueKindEnum)) + return r.generateHashableValueOfType(inter, r.randomInt(randomValueKindEnum)) } -func (r randomValueGenerator) generateRandomHashableValue(inter *interpreter.Interpreter, n int) cadence.Value { +func (r randomValueGenerator) generateHashableValueOfType(inter *interpreter.Interpreter, n int) cadence.Value { switch n { // Int* @@ -1560,6 +1886,8 @@ func (r randomValueGenerator) randomStructValue(inter *interpreter.Interpreter, Members: &sema.StringMemberOrderedMap{}, } + fieldNames := make([]string, fieldsCount) + for i := 0; i < fieldsCount; i++ { fieldName := fields[i].Identifier compositeType.Members.Set( @@ -1571,7 +1899,9 @@ func (r randomValueGenerator) randomStructValue(inter *interpreter.Interpreter, "", ), ) + fieldNames[i] = fieldName } + compositeType.Fields = fieldNames // Add the type to the elaboration, to short-circuit the type-lookup. inter.Program.Elaboration.SetCompositeType( @@ -1777,7 +2107,7 @@ func (r randomValueGenerator) randomEnumValue(inter *interpreter.Interpreter) ca // Get a random integer subtype to be used as the raw-type of enum typ := r.randomInt(randomValueKindWord64) - rawValue := r.generateRandomHashableValue(inter, typ).(cadence.NumberValue) + rawValue := r.generateHashableValueOfType(inter, typ).(cadence.NumberValue) identifier := fmt.Sprintf("E%d", r.rand.Uint64()) From 6e84f1ea2fc805728407d20f46a7b3741eb4c2d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 14 Jan 2025 12:16:37 -0800 Subject: [PATCH 31/80] make limits of random value generator configurable --- runtime/tests/interpreter/values_test.go | 77 ++++++++++++++---------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 2629eaacb..4e10dced4 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -43,10 +43,11 @@ import ( "github.com/onflow/cadence/runtime/tests/utils" ) -// TODO: make these program args? -const containerMaxDepth = 3 -const containerMaxSize = 50 -const compositeMaxFields = 10 +var defaultRandomValueLimits = randomValueLimits{ + containerMaxDepth: 3, + containerMaxSize: 50, + compositeMaxFields: 10, +} var runSmokeTests = flag.Bool("runSmokeTests", false, "Run smoke tests on values") var validateAtree = flag.Bool("validateAtree", false, "Enable atree validation") @@ -399,7 +400,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -419,7 +420,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -438,7 +439,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -530,7 +531,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { t.Run("insert", func(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -554,7 +555,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { // Atree storage validation must be temporarily disabled // to not report any "unreferenced slab errors. - numberOfValues := r.randomInt(containerMaxSize) + numberOfValues := r.randomInt(r.containerMaxSize) for i := 0; i < numberOfValues; i++ { @@ -621,7 +622,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { t.Run("remove", func(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -689,7 +690,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { t.Run("update", func(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -903,7 +904,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -923,7 +924,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -1015,7 +1016,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { t.Run("update", func(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -1257,7 +1258,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -1277,7 +1278,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -1296,7 +1297,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -1388,7 +1389,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { t.Run("insert", func(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -1412,7 +1413,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { // Insert new values into the array. - newValueCount := r.randomInt(containerMaxSize) + newValueCount := r.randomInt(r.containerMaxSize) for i := 0; i < newValueCount; i++ { @@ -1462,7 +1463,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { t.Run("remove", func(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(1736809620917220000) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -1528,7 +1529,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { t.Run("update", func(t *testing.T) { t.Parallel() - r := newRandomValueGenerator(*smokeTestSeed) + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) inter, resetStorage := newRandomValueTestInterpreter(t) @@ -1593,24 +1594,33 @@ func TestInterpretRandomArrayOperations(t *testing.T) { }) } + +type randomValueLimits struct { + containerMaxDepth int + containerMaxSize int + compositeMaxFields int +} + type randomValueGenerator struct { seed int64 rand *rand.Rand + randomValueLimits } -func newRandomValueGenerator(seed int64) randomValueGenerator { +func newRandomValueGenerator(seed int64, limits randomValueLimits) randomValueGenerator { if seed == -1 { seed = time.Now().UnixNano() } return randomValueGenerator{ - seed: seed, - rand: rand.New(rand.NewSource(seed)), + seed: seed, + rand: rand.New(rand.NewSource(seed)), + randomValueLimits: limits, } } func (r randomValueGenerator) randomStorableValue(inter *interpreter.Interpreter, currentDepth int) cadence.Value { n := 0 - if currentDepth < containerMaxDepth { + if currentDepth < r.containerMaxDepth { n = r.randomInt(randomValueKindStruct) } else { n = r.randomInt(randomValueKindCapability) @@ -1790,7 +1800,7 @@ func (r randomValueGenerator) randomCapabilityValue() cadence.Capability { func (r randomValueGenerator) randomDictionaryValue(inter *interpreter.Interpreter, currentDepth int) cadence.Dictionary { - entryCount := r.randomInt(containerMaxSize) + entryCount := r.randomInt(r.containerMaxSize) keyValues := make([]cadence.KeyValuePair, entryCount) existingKeys := map[string]struct{}{} @@ -1831,7 +1841,7 @@ func (r randomValueGenerator) randomInt(upperBound int) int { } func (r randomValueGenerator) randomArrayValue(inter *interpreter.Interpreter, currentDepth int) cadence.Array { - elementsCount := r.randomInt(containerMaxSize) + elementsCount := r.randomInt(r.containerMaxSize) elements := make([]cadence.Value, elementsCount) for i := 0; i < elementsCount; i++ { @@ -1843,7 +1853,7 @@ func (r randomValueGenerator) randomArrayValue(inter *interpreter.Interpreter, c } func (r randomValueGenerator) randomStructValue(inter *interpreter.Interpreter, currentDepth int) cadence.Struct { - fieldsCount := r.randomInt(compositeMaxFields) + fieldsCount := r.randomInt(r.compositeMaxFields) fields := make([]cadence.Field, fieldsCount) fieldValues := make([]cadence.Value, fieldsCount) @@ -2170,12 +2180,14 @@ func TestRandomValueGeneration(t *testing.T) { inter, _ := newRandomValueTestInterpreter(t) + limits := defaultRandomValueLimits + // Generate random values for i := 0; i < 1000; i++ { - r1 := newRandomValueGenerator(int64(i)) + r1 := newRandomValueGenerator(int64(i), limits) v1 := r1.randomStorableValue(inter, 0) - r2 := newRandomValueGenerator(int64(i)) + r2 := newRandomValueGenerator(int64(i), limits) v2 := r2.randomStorableValue(inter, 0) // Check if the generated values are equal @@ -2295,7 +2307,10 @@ func TestCheckStorageHealthInMiddleOfDeepRemove(t *testing.T) { // In this test, storage.CheckHealth() should be called after DictionaryValue.Transfer() // with remove flag, not in the middle of DictionaryValue.Transfer(). func TestCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { - r := newRandomValueGenerator(*smokeTestSeed) + + t.Parallel() + + r := newRandomValueGenerator(*smokeTestSeed, defaultRandomValueLimits) t.Logf("seed: %d", r.seed) storage := newUnmeteredInMemoryStorage() From 00032e3fb1cc0164bb0305c2d797383a36a1fafd Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:16:22 -0600 Subject: [PATCH 32/80] Bump atree version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 032aa3e2e..24ce45b8e 100644 --- a/go.mod +++ b/go.mod @@ -63,4 +63,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20250109185652-759a26eda3c3 +replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20250115020903-b5671d3430c6 diff --git a/go.sum b/go.sum index c590d67cc..cb47f5398 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onflow/atree-internal v0.8.2-0.20250109185652-759a26eda3c3 h1:kwuIssnbosjsGnaxNXSIEzV8u1+Lhd9bDJTrkRB8x0w= -github.com/onflow/atree-internal v0.8.2-0.20250109185652-759a26eda3c3/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/atree-internal v0.8.2-0.20250115020903-b5671d3430c6 h1:9ptAgDT4hq6iy6ZCWvMR/aHeXVfE3vK+lGt1W3j32l8= +github.com/onflow/atree-internal v0.8.2-0.20250115020903-b5671d3430c6/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= From 9db2d99b907246a4665ea778970e2abcc20912dd Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:24:49 -0600 Subject: [PATCH 33/80] Lint --- runtime/tests/interpreter/values_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 4e10dced4..882a548e5 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -1594,7 +1594,6 @@ func TestInterpretRandomArrayOperations(t *testing.T) { }) } - type randomValueLimits struct { containerMaxDepth int containerMaxSize int From eaa609b7889b06beaa551219ebbc42d10f66aabd Mon Sep 17 00:00:00 2001 From: Joe Sprowes Date: Wed, 15 Jan 2025 10:00:17 -0600 Subject: [PATCH 34/80] remove extra comparisons and use bits.UintSize --- runtime/interpreter/value.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 8c590737a..a3f477aef 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -25,6 +25,7 @@ import ( "fmt" "math" "math/big" + "math/bits" "strings" "time" "unicode" @@ -8065,7 +8066,7 @@ func (v Int128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVal valueGetter := func() *big.Int { res := new(big.Int) res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) - return truncate(res, 2) + return truncate(res, 128/bits.UintSize) } return NewInt128ValueFromBigInt(interpreter, valueGetter) @@ -8087,7 +8088,7 @@ func (v Int128Value) BitwiseRightShift(interpreter *Interpreter, other IntegerVa LocationRange: locationRange, }) } - if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 128 { + if !o.BigInt.IsUint64() { return NewInt128ValueFromUint64(interpreter, 0) } @@ -8809,7 +8810,7 @@ func (v Int256Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVal valueGetter := func() *big.Int { res := new(big.Int) res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) - return truncate(res, 4) + return truncate(res, 256/bits.UintSize) } return NewInt256ValueFromBigInt(interpreter, valueGetter) @@ -8831,7 +8832,7 @@ func (v Int256Value) BitwiseRightShift(interpreter *Interpreter, other IntegerVa LocationRange: locationRange, }) } - if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 256 { + if !o.BigInt.IsUint64() { return NewInt256ValueFromUint64(interpreter, 0) } @@ -12360,7 +12361,7 @@ func (v UInt128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa func() *big.Int { res := new(big.Int) res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) - return truncate(res, 2) + return truncate(res, 128/bits.UintSize) }, ) } @@ -12381,7 +12382,7 @@ func (v UInt128Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV LocationRange: locationRange, }) } - if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 128 { + if !o.BigInt.IsUint64() { return NewUInt128ValueFromUint64(interpreter, 0) } @@ -13040,7 +13041,7 @@ func (v UInt256Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa func() *big.Int { res := new(big.Int) res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) - return truncate(res, 4) + return truncate(res, 256/bits.UintSize) }, ) } @@ -13061,7 +13062,7 @@ func (v UInt256Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV LocationRange: locationRange, }) } - if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 256 { + if !o.BigInt.IsUint64() { return NewUInt256ValueFromUint64(interpreter, 0) } @@ -15408,7 +15409,7 @@ func (v Word128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa func() *big.Int { res := new(big.Int) res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) - return truncate(res, 2) + return truncate(res, 128/bits.UintSize) }, ) } @@ -15428,7 +15429,7 @@ func (v Word128Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV LocationRange: locationRange, }) } - if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 128 { + if !o.BigInt.IsUint64() { return NewWord128ValueFromUint64(interpreter, 0) } @@ -15993,7 +15994,7 @@ func (v Word256Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVa func() *big.Int { res := new(big.Int) res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) - return truncate(res, 4) + return truncate(res, 256/bits.UintSize) }, ) } @@ -16014,7 +16015,7 @@ func (v Word256Value) BitwiseRightShift(interpreter *Interpreter, other IntegerV LocationRange: locationRange, }) } - if !o.BigInt.IsUint64() || o.BigInt.Uint64() >= 256 { + if !o.BigInt.IsUint64() { return NewWord256ValueFromUint64(interpreter, 0) } From ca25a8fc0ca31623f38616644065584734fca91c Mon Sep 17 00:00:00 2001 From: Joe Sprowes Date: Wed, 15 Jan 2025 10:29:50 -0600 Subject: [PATCH 35/80] add tests for truncate function --- runtime/tests/interpreter/bitwise_test.go | 31 +++++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/runtime/tests/interpreter/bitwise_test.go b/runtime/tests/interpreter/bitwise_test.go index b630639ee..61f3a2bca 100644 --- a/runtime/tests/interpreter/bitwise_test.go +++ b/runtime/tests/interpreter/bitwise_test.go @@ -20,6 +20,7 @@ package interpreter_test import ( "fmt" + "math/big" "testing" "github.com/stretchr/testify/require" @@ -280,16 +281,18 @@ func TestInterpretBitwiseLeftShift128(t *testing.T) { inter := parseCheckAndInterpret(t, ` - let a: Int128 = 0x7fff_ffff + let a: Int128 = 0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ffff let b: Int128 = 32 let c = a << b `, ) + bigInt, _ := big.NewInt(0).SetString("0xffff_ffff_ffff_ffff_ffff_ffff_0000_0000", 0) + AssertValuesEqual( t, inter, - interpreter.NewUnmeteredInt128ValueFromInt64(int64(0x7fff_ffff_0000_0000)), + interpreter.NewUnmeteredInt128ValueFromBigInt(bigInt), inter.Globals.Get("c").GetValue(inter), ) }) @@ -333,16 +336,18 @@ func TestInterpretBitwiseLeftShift128(t *testing.T) { inter := parseCheckAndInterpret(t, ` - let a: UInt128 = 0xffff_ffff + let a: UInt128 = 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff let b: UInt128 = 32 let c = a << b `, ) + bigInt, _ := big.NewInt(0).SetString("0xffff_ffff_ffff_ffff_ffff_ffff_0000_0000", 0) + AssertValuesEqual( t, inter, - interpreter.NewUnmeteredUInt128ValueFromUint64(uint64(0xffff_ffff_0000_0000)), + interpreter.NewUnmeteredUInt128ValueFromBigInt(bigInt), inter.Globals.Get("c").GetValue(inter), ) }) @@ -369,16 +374,18 @@ func TestInterpretBitwiseLeftShift128(t *testing.T) { inter := parseCheckAndInterpret(t, ` - let a: Word128 = 0xffff_ffff + let a: Word128 = 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff let b: Word128 = 32 let c = a << b `, ) + bigInt, _ := big.NewInt(0).SetString("0xffff_ffff_ffff_ffff_ffff_ffff_0000_0000", 0) + AssertValuesEqual( t, inter, - interpreter.NewUnmeteredWord128ValueFromUint64(uint64(0xffff_ffff_0000_0000)), + interpreter.NewUnmeteredWord128ValueFromBigInt(bigInt), inter.Globals.Get("c").GetValue(inter), ) }) @@ -406,20 +413,22 @@ func TestInterpretBitwiseLeftShift256(t *testing.T) { ) }) - t.Run("Int256 << 192", func(t *testing.T) { + t.Run("Int256 << 32", func(t *testing.T) { inter := parseCheckAndInterpret(t, ` - let a: Int256 = 0x7fff_ffff + let a: Int256 = 0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff let b: Int256 = 32 let c = a << b `, ) + bigInt, _ := big.NewInt(0).SetString("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_0000_0000", 0) + AssertValuesEqual( t, inter, - interpreter.NewUnmeteredInt256ValueFromInt64(int64(0x7fff_ffff_0000_0000)), + interpreter.NewUnmeteredInt256ValueFromBigInt(bigInt), inter.Globals.Get("c").GetValue(inter), ) }) @@ -459,7 +468,7 @@ func TestInterpretBitwiseLeftShift256(t *testing.T) { ) }) - t.Run("UInt256 << 192", func(t *testing.T) { + t.Run("UInt256 << 32", func(t *testing.T) { inter := parseCheckAndInterpret(t, ` @@ -495,7 +504,7 @@ func TestInterpretBitwiseLeftShift256(t *testing.T) { ) }) - t.Run("Word256 << 192", func(t *testing.T) { + t.Run("Word256 << 32", func(t *testing.T) { inter := parseCheckAndInterpret(t, ` From 1154cc5c27c65f622d3998d4ab500c8e027b4bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 15 Jan 2025 13:54:55 -0800 Subject: [PATCH 36/80] clean up Co-authored-by: Faye Amacker <33205765+fxamacker@users.noreply.github.com> --- runtime/tests/interpreter/values_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 882a548e5..0992b58f6 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -657,7 +657,6 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { require.IsType(t, &interpreter.SomeValue{}, removedValue) someValue := removedValue.(*interpreter.SomeValue) - // TODO: panic: duplicate slab 0x4100000000000000.14 for seed 1736809620917220000 value := importValue(t, inter, pair.Value) // Removed value must be same as the original value From 3adbcab5d9e226caff5607ef4e6a3c5b8a231983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 15 Jan 2025 13:50:19 -0800 Subject: [PATCH 37/80] pass subtest's testing.T, improve random value generation --- runtime/tests/interpreter/values_test.go | 120 ++++++++++++++++------- 1 file changed, 84 insertions(+), 36 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 0992b58f6..36dfc65b9 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -220,6 +220,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { const dictionaryStorageMapKey = interpreter.StringStorageMapKey("dictionary") createDictionary := func( + t *testing.T, r *randomValueGenerator, inter *interpreter.Interpreter, ) ( @@ -294,6 +295,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { } checkDictionary := func( + t *testing.T, inter *interpreter.Interpreter, dictionary *interpreter.DictionaryValue, expectedValue cadence.Dictionary, @@ -319,6 +321,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { } doubleCheckDictionary := func( + t *testing.T, inter *interpreter.Interpreter, resetStorage func(), dictionary *interpreter.DictionaryValue, @@ -331,6 +334,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { for i := 0; i < 2; i++ { checkDictionary( + t, inter, dictionary, expectedValue, @@ -342,6 +346,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { } checkIteration := func( + t *testing.T, inter *interpreter.Interpreter, resetStorage func(), dictionary *interpreter.DictionaryValue, @@ -405,9 +410,10 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - dictionary, expectedValue, _ := createDictionary(&r, inter) + dictionary, expectedValue, _ := createDictionary(t, &r, inter) doubleCheckDictionary( + t, inter, resetStorage, dictionary, @@ -425,9 +431,10 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - dictionary, expectedValue, _ := createDictionary(&r, inter) + dictionary, expectedValue, _ := createDictionary(t, &r, inter) checkIteration( + t, inter, resetStorage, dictionary, @@ -444,7 +451,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - original, expectedValue, _ := createDictionary(&r, inter) + original, expectedValue, _ := createDictionary(t, &r, inter) resetStorage() @@ -484,6 +491,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { for i := 0; i < 2; i++ { checkDictionary( + t, inter, original, expectedValue, @@ -491,6 +499,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { ) checkDictionary( + t, inter, transferred, expectedValue, @@ -518,6 +527,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { // New dictionary should still be accessible doubleCheckDictionary( + t, inter, resetStorage, transferred, @@ -536,10 +546,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - dictionary, expectedValue, reloadDictionary := createDictionary(&r, inter) + dictionary, expectedValue, reloadDictionary := createDictionary(t, &r, inter) // Check dictionary and reset storage doubleCheckDictionary( + t, inter, resetStorage, dictionary, @@ -553,7 +564,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { // Insert new values into the dictionary. // Atree storage validation must be temporarily disabled - // to not report any "unreferenced slab errors. + // to not report any "unreferenced slab" errors. numberOfValues := r.randomInt(r.containerMaxSize) @@ -611,6 +622,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { } doubleCheckDictionary( + t, inter, resetStorage, dictionary, @@ -627,10 +639,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - dictionary, expectedValue, reloadDictionary := createDictionary(&r, inter) + dictionary, expectedValue, reloadDictionary := createDictionary(t, &r, inter) // Check dictionary and reset storage doubleCheckDictionary( + t, inter, resetStorage, dictionary, @@ -676,6 +689,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { require.Equal(t, 0, dictionary.Count()) doubleCheckDictionary( + t, inter, resetStorage, dictionary, @@ -694,10 +708,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - dictionary, expectedValue, reloadDictionary := createDictionary(&r, inter) + dictionary, expectedValue, reloadDictionary := createDictionary(t, &r, inter) // Check dictionary and reset storage doubleCheckDictionary( + t, inter, resetStorage, dictionary, @@ -757,6 +772,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { require.Equal(t, elementCount, dictionary.Count()) doubleCheckDictionary( + t, inter, resetStorage, dictionary, @@ -780,6 +796,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { const compositeStorageMapKey = interpreter.StringStorageMapKey("composite") createComposite := func( + t *testing.T, r *randomValueGenerator, inter *interpreter.Interpreter, ) ( @@ -855,6 +872,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { } checkComposite := func( + t *testing.T, inter *interpreter.Interpreter, composite *interpreter.CompositeValue, expectedValue cadence.Struct, @@ -877,6 +895,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { } doubleCheckComposite := func( + t *testing.T, inter *interpreter.Interpreter, resetStorage func(), composite *interpreter.CompositeValue, @@ -889,6 +908,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { for i := 0; i < 2; i++ { checkComposite( + t, inter, composite, expectedValue, @@ -908,9 +928,10 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - composite, expectedValue, _ := createComposite(&r, inter) + composite, expectedValue, _ := createComposite(t, &r, inter) doubleCheckComposite( + t, inter, resetStorage, composite, @@ -928,7 +949,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - original, expectedValue, _ := createComposite(&r, inter) + original, expectedValue, _ := createComposite(t, &r, inter) resetStorage() @@ -947,7 +968,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { true, ).(*interpreter.CompositeValue) - // Store the transferred composite in a storage map, so that the composote's slab + // Store the transferred composite in a storage map, so that the composite's slab // is referenced by the root of the storage. inter.Storage(). @@ -968,6 +989,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { for i := 0; i < 2; i++ { checkComposite( + t, inter, original, expectedValue, @@ -975,6 +997,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { ) checkComposite( + t, inter, transferred, expectedValue, @@ -1002,6 +1025,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { // New composite should still be accessible doubleCheckComposite( + t, inter, resetStorage, transferred, @@ -1020,10 +1044,11 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - composite, expectedValue, reloadComposite := createComposite(&r, inter) + composite, expectedValue, reloadComposite := createComposite(t, &r, inter) // Check composite and reset storage doubleCheckComposite( + t, inter, resetStorage, composite, @@ -1083,6 +1108,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { require.Equal(t, typeFieldCount, composite.FieldCount()) doubleCheckComposite( + t, inter, resetStorage, composite, @@ -1106,6 +1132,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { const arrayStorageMapKey = interpreter.StringStorageMapKey("array") createArray := func( + t *testing.T, r *randomValueGenerator, inter *interpreter.Interpreter, ) ( @@ -1174,6 +1201,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { } checkArray := func( + t *testing.T, inter *interpreter.Interpreter, array *interpreter.ArrayValue, expectedValue cadence.Array, @@ -1194,6 +1222,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { } doubleCheckArray := func( + t *testing.T, inter *interpreter.Interpreter, resetStorage func(), array *interpreter.ArrayValue, @@ -1206,6 +1235,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { for i := 0; i < 2; i++ { checkArray( + t, inter, array, expectedValue, @@ -1217,6 +1247,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { } checkIteration := func( + t *testing.T, inter *interpreter.Interpreter, resetStorage func(), array *interpreter.ArrayValue, @@ -1262,9 +1293,10 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - array, expectedValue, _ := createArray(&r, inter) + array, expectedValue, _ := createArray(t, &r, inter) doubleCheckArray( + t, inter, resetStorage, array, @@ -1282,9 +1314,10 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - array, expectedValue, _ := createArray(&r, inter) + array, expectedValue, _ := createArray(t, &r, inter) checkIteration( + t, inter, resetStorage, array, @@ -1301,7 +1334,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - original, expectedValue, _ := createArray(&r, inter) + original, expectedValue, _ := createArray(t, &r, inter) resetStorage() @@ -1341,6 +1374,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { for i := 0; i < 2; i++ { checkArray( + t, inter, original, expectedValue, @@ -1348,6 +1382,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { ) checkArray( + t, inter, transferred, expectedValue, @@ -1375,6 +1410,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { // New array should still be accessible doubleCheckArray( + t, inter, resetStorage, transferred, @@ -1393,10 +1429,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - array, expectedValue, reloadArray := createArray(&r, inter) + array, expectedValue, reloadArray := createArray(t, &r, inter) // Check array and reset storage doubleCheckArray( + t, inter, resetStorage, array, @@ -1451,6 +1488,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { } doubleCheckArray( + t, inter, resetStorage, array, @@ -1467,10 +1505,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - array, expectedValue, reloadArray := createArray(&r, inter) + array, expectedValue, reloadArray := createArray(t, &r, inter) // Check array and reset storage doubleCheckArray( + t, inter, resetStorage, array, @@ -1515,6 +1554,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { require.Equal(t, 0, array.Count()) doubleCheckArray( + t, inter, resetStorage, array, @@ -1533,10 +1573,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - array, expectedValue, reloadArray := createArray(&r, inter) + array, expectedValue, reloadArray := createArray(t, &r, inter) // Check array and reset storage doubleCheckArray( + t, inter, resetStorage, array, @@ -1578,10 +1619,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { require.NoError(t, err) } - // Array must have same number of key-value pairs + // Array must have same number of elements require.Equal(t, elementCount, array.Count()) doubleCheckArray( + t, inter, resetStorage, array, @@ -1617,14 +1659,14 @@ func newRandomValueGenerator(seed int64, limits randomValueLimits) randomValueGe } } func (r randomValueGenerator) randomStorableValue(inter *interpreter.Interpreter, currentDepth int) cadence.Value { - n := 0 + var kind randomValueKind if currentDepth < r.containerMaxDepth { - n = r.randomInt(randomValueKindStruct) + kind = r.randomValueKind(randomValueKindStruct) } else { - n = r.randomInt(randomValueKindCapability) + kind = r.randomValueKind(randomValueKindCapability) } - switch n { + switch kind { // Non-hashable case randomValueKindVoid: @@ -1654,16 +1696,16 @@ func (r randomValueGenerator) randomStorableValue(inter *interpreter.Interpreter // Hashable default: - return r.generateHashableValueOfType(inter, n) + return r.generateHashableValueOfKind(inter, kind) } } func (r randomValueGenerator) randomHashableValue(inter *interpreter.Interpreter) cadence.Value { - return r.generateHashableValueOfType(inter, r.randomInt(randomValueKindEnum)) + return r.generateHashableValueOfKind(inter, r.randomValueKind(randomValueKindEnum)) } -func (r randomValueGenerator) generateHashableValueOfType(inter *interpreter.Interpreter, n int) cadence.Value { - switch n { +func (r randomValueGenerator) generateHashableValueOfKind(inter *interpreter.Interpreter, kind randomValueKind) cadence.Value { + switch kind { // Int* case randomValueKindInt: @@ -1758,7 +1800,7 @@ func (r randomValueGenerator) generateHashableValueOfType(inter *interpreter.Int return r.randomEnumValue(inter) default: - panic(fmt.Sprintf("unsupported: %d", n)) + panic(fmt.Sprintf("unsupported: %d", kind)) } } @@ -1927,8 +1969,8 @@ func (r randomValueGenerator) randomStructValue(inter *interpreter.Interpreter, ) } -func (r randomValueGenerator) cadenceIntegerType(n int) cadence.Type { - switch n { +func (r randomValueGenerator) cadenceIntegerType(kind randomValueKind) cadence.Type { + switch kind { // Int case randomValueKindInt: return cadence.IntType @@ -1979,12 +2021,12 @@ func (r randomValueGenerator) cadenceIntegerType(n int) cadence.Type { return cadence.Word256Type default: - panic(fmt.Sprintf("unsupported: %d", n)) + panic(fmt.Sprintf("unsupported kind: %d", kind)) } } -func (r randomValueGenerator) semaIntegerType(n int) sema.Type { - switch n { +func (r randomValueGenerator) semaIntegerType(kind randomValueKind) sema.Type { + switch kind { // Int case randomValueKindInt: return sema.IntType @@ -2035,14 +2077,16 @@ func (r randomValueGenerator) semaIntegerType(n int) sema.Type { return sema.Word256Type default: - panic(fmt.Sprintf("unsupported: %d", n)) + panic(fmt.Sprintf("unsupported kind: %d", kind)) } } +type randomValueKind uint8 + const ( // Hashable values // Int* - randomValueKindInt = iota + randomValueKindInt randomValueKind = iota randomValueKindInt8 randomValueKindInt16 randomValueKindInt32 @@ -2113,9 +2157,9 @@ func (r randomValueGenerator) randomUTF8StringOfSize(size int) string { func (r randomValueGenerator) randomEnumValue(inter *interpreter.Interpreter) cadence.Enum { // Get a random integer subtype to be used as the raw-type of enum - typ := r.randomInt(randomValueKindWord64) + typ := r.randomValueKind(randomValueKindWord64) - rawValue := r.generateHashableValueOfType(inter, typ).(cadence.NumberValue) + rawValue := r.generateHashableValueOfKind(inter, typ).(cadence.NumberValue) identifier := fmt.Sprintf("E%d", r.rand.Uint64()) @@ -2174,6 +2218,10 @@ func (r randomValueGenerator) randomEnumValue(inter *interpreter.Interpreter) ca ) } +func (r randomValueGenerator) randomValueKind(kind randomValueKind) randomValueKind { + return randomValueKind(r.randomInt(int(kind))) +} + func TestRandomValueGeneration(t *testing.T) { inter, _ := newRandomValueTestInterpreter(t) From 6dfc2e90fb62c60271f2ecc4f1b9ba584875d653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 15 Jan 2025 15:13:13 -0800 Subject: [PATCH 38/80] refactor dictionary tests --- runtime/tests/interpreter/values_test.go | 553 ++++++++++++++++------- 1 file changed, 400 insertions(+), 153 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 36dfc65b9..d247a6ca4 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -219,6 +219,44 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { const dictionaryStorageMapKey = interpreter.StringStorageMapKey("dictionary") + writeDictionary := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + dictionary *interpreter.DictionaryValue, + ) { + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + storageMapKey, + dictionary, + ) + } + + readDictionary := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) *interpreter.DictionaryValue { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, storageMapKey) + require.NotNil(t, readValue) + + require.IsType(t, &interpreter.DictionaryValue{}, readValue) + return readValue.(*interpreter.DictionaryValue) + } + createDictionary := func( t *testing.T, r *randomValueGenerator, @@ -226,7 +264,6 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { ) ( *interpreter.DictionaryValue, cadence.Dictionary, - func() *interpreter.DictionaryValue, ) { expectedValue := r.randomDictionaryValue(inter, 0) @@ -264,34 +301,14 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { // Store the dictionary in a storage map, so that the dictionary's slab // is referenced by the root of the storage. - inter.Storage(). - GetStorageMap( - orgOwner, - common.PathDomainStorage.Identifier(), - true, - ). - WriteValue( - inter, - dictionaryStorageMapKey, - dictionary, - ) - - reloadDictionary := func() *interpreter.DictionaryValue { - storageMap := inter.Storage().GetStorageMap( - orgOwner, - common.PathDomainStorage.Identifier(), - false, - ) - require.NotNil(t, storageMap) - - readValue := storageMap.ReadValue(inter, dictionaryStorageMapKey) - require.NotNil(t, readValue) - - require.IsType(t, &interpreter.DictionaryValue{}, readValue) - return readValue.(*interpreter.DictionaryValue) - } + writeDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + dictionary, + ) - return dictionary, expectedValue, reloadDictionary + return dictionary, expectedValue } checkDictionary := func( @@ -320,35 +337,9 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { assert.Equal(t, expectedOwner, owner) } - doubleCheckDictionary := func( - t *testing.T, - inter *interpreter.Interpreter, - resetStorage func(), - dictionary *interpreter.DictionaryValue, - expectedValue cadence.Dictionary, - expectedOwner common.Address, - ) { - // Check the values of the dictionary. - // Once right away, and once after a reset (commit and reload) of the storage. - - for i := 0; i < 2; i++ { - - checkDictionary( - t, - inter, - dictionary, - expectedValue, - expectedOwner, - ) - - resetStorage() - } - } - checkIteration := func( t *testing.T, inter *interpreter.Interpreter, - resetStorage func(), dictionary *interpreter.DictionaryValue, expectedValue cadence.Dictionary, ) { @@ -367,38 +358,30 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { } } - // Iterate over the values of the created dictionary. - // Once right after construction, and once after a reset (commit and reload) of the storage. - - for i := 0; i < 2; i++ { - - require.Equal(t, len(expectedValue.Pairs), dictionary.Count()) - - var iterations int + require.Equal(t, len(expectedValue.Pairs), dictionary.Count()) - dictionary.Iterate( - inter, - interpreter.EmptyLocationRange, - func(key, value interpreter.Value) (resume bool) { + var iterations int - mapKey := mapKey(inter, key) - require.Contains(t, indexedExpected, mapKey) + dictionary.Iterate( + inter, + interpreter.EmptyLocationRange, + func(key, value interpreter.Value) (resume bool) { - pair := indexedExpected[mapKey] + mapKey := mapKey(inter, key) + require.Contains(t, indexedExpected, mapKey) - utils.AssertValuesEqual(t, inter, pair.Key, key) - utils.AssertValuesEqual(t, inter, pair.Value, value) + pair := indexedExpected[mapKey] - iterations += 1 + utils.AssertValuesEqual(t, inter, pair.Key, key) + utils.AssertValuesEqual(t, inter, pair.Value, value) - return true - }, - ) + iterations += 1 - assert.Equal(t, len(expectedValue.Pairs), iterations) + return true + }, + ) - resetStorage() - } + assert.Equal(t, len(expectedValue.Pairs), iterations) } t.Run("construction", func(t *testing.T) { @@ -410,12 +393,27 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - dictionary, expectedValue, _ := createDictionary(t, &r, inter) + dictionary, expectedValue := createDictionary(t, &r, inter) - doubleCheckDictionary( + checkDictionary( + t, + inter, + dictionary, + expectedValue, + orgOwner, + ) + + resetStorage() + + dictionary = readDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + ) + + checkDictionary( t, inter, - resetStorage, dictionary, expectedValue, orgOwner, @@ -431,12 +429,42 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - dictionary, expectedValue, _ := createDictionary(t, &r, inter) + dictionary, expectedValue := createDictionary(t, &r, inter) + + checkDictionary( + t, + inter, + dictionary, + expectedValue, + orgOwner, + ) + + checkIteration( + t, + inter, + dictionary, + expectedValue, + ) + + resetStorage() + + dictionary = readDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + ) + + checkDictionary( + t, + inter, + dictionary, + expectedValue, + orgOwner, + ) checkIteration( t, inter, - resetStorage, dictionary, expectedValue, ) @@ -451,10 +479,32 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - original, expectedValue, _ := createDictionary(t, &r, inter) + original, expectedValue := createDictionary(t, &r, inter) + + checkDictionary( + t, + inter, + original, + expectedValue, + orgOwner, + ) resetStorage() + original = readDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + ) + + checkDictionary( + t, + inter, + original, + expectedValue, + orgOwner, + ) + // Transfer the dictionary to a new owner newOwner := common.Address{'B'} @@ -466,59 +516,58 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { false, nil, nil, - // TODO: is has no parent container = true correct? - true, + false, ).(*interpreter.DictionaryValue) // Store the transferred dictionary in a storage map, so that the dictionary's slab // is referenced by the root of the storage. - inter.Storage(). - GetStorageMap( - newOwner, - common.PathDomainStorage.Identifier(), - true, - ). - WriteValue( - inter, - interpreter.StringStorageMapKey("transferred_dictionary"), - transferred, - ) - - // Both original and transferred dictionary should contain the expected values - // Check once right away, and once after a reset (commit and reload) of the storage. - - for i := 0; i < 2; i++ { - - checkDictionary( - t, - inter, - original, - expectedValue, - orgOwner, - ) + const transferredStorageMapKey = interpreter.StringStorageMapKey("transferred") - checkDictionary( - t, - inter, - transferred, - expectedValue, - newOwner, - ) - - resetStorage() - } + writeDictionary( + inter, + newOwner, + transferredStorageMapKey, + transferred, + ) - // Deep remove the original dictionary + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + inter.Storage(). + GetStorageMap(orgOwner, common.PathDomainStorage.Identifier(), false). + RemoveValue(inter, dictionaryStorageMapKey) - // TODO: is has no parent container = true correct? - original.DeepRemove(inter, true) + return struct{}{} + }) if !original.Inlined() { err := inter.Storage().Remove(original.SlabID()) require.NoError(t, err) } + checkDictionary( + t, + inter, + transferred, + expectedValue, + newOwner, + ) + + resetStorage() + + transferred = readDictionary( + inter, + newOwner, + transferredStorageMapKey, + ) + + checkDictionary( + t, + inter, + transferred, + expectedValue, + newOwner, + ) + if *validateAtree { err := inter.Storage().CheckHealth() require.NoError(t, err) @@ -526,10 +575,25 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { // New dictionary should still be accessible - doubleCheckDictionary( + checkDictionary( + t, + inter, + transferred, + expectedValue, + newOwner, + ) + + resetStorage() + + transferred = readDictionary( + inter, + newOwner, + transferredStorageMapKey, + ) + + checkDictionary( t, inter, - resetStorage, transferred, expectedValue, newOwner, @@ -546,21 +610,31 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - dictionary, expectedValue, reloadDictionary := createDictionary(t, &r, inter) + dictionary, expectedValue := createDictionary(t, &r, inter) - // Check dictionary and reset storage - doubleCheckDictionary( + checkDictionary( t, inter, - resetStorage, dictionary, expectedValue, orgOwner, ) - // Reload the dictionary after the reset + resetStorage() - dictionary = reloadDictionary() + dictionary = readDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + ) + + checkDictionary( + t, + inter, + dictionary, + expectedValue, + orgOwner, + ) // Insert new values into the dictionary. // Atree storage validation must be temporarily disabled @@ -621,10 +695,25 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { require.NoError(t, err) } - doubleCheckDictionary( + checkDictionary( + t, + inter, + dictionary, + expectedValue, + orgOwner, + ) + + resetStorage() + + dictionary = readDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + ) + + checkDictionary( t, inter, - resetStorage, dictionary, expectedValue, orgOwner, @@ -639,21 +728,31 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - dictionary, expectedValue, reloadDictionary := createDictionary(t, &r, inter) + dictionary, expectedValue := createDictionary(t, &r, inter) - // Check dictionary and reset storage - doubleCheckDictionary( + checkDictionary( t, inter, - resetStorage, dictionary, expectedValue, orgOwner, ) - // Reload the dictionary after the reset + resetStorage() + + dictionary = readDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + ) - dictionary = reloadDictionary() + checkDictionary( + t, + inter, + dictionary, + expectedValue, + orgOwner, + ) // Remove for _, pair := range expectedValue.Pairs { @@ -688,10 +787,25 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { // Dictionary must be empty require.Equal(t, 0, dictionary.Count()) - doubleCheckDictionary( + checkDictionary( + t, + inter, + dictionary, + expectedValue, + orgOwner, + ) + + resetStorage() + + dictionary = readDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + ) + + checkDictionary( t, inter, - resetStorage, dictionary, expectedValue, orgOwner, @@ -708,21 +822,31 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - dictionary, expectedValue, reloadDictionary := createDictionary(t, &r, inter) + dictionary, expectedValue := createDictionary(t, &r, inter) - // Check dictionary and reset storage - doubleCheckDictionary( + checkDictionary( t, inter, - resetStorage, dictionary, expectedValue, orgOwner, ) - // Reload the dictionary after the reset + resetStorage() + + dictionary = readDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + ) - dictionary = reloadDictionary() + checkDictionary( + t, + inter, + dictionary, + expectedValue, + orgOwner, + ) elementCount := dictionary.Count() @@ -771,10 +895,25 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { // Dictionary must have same number of key-value pairs require.Equal(t, elementCount, dictionary.Count()) - doubleCheckDictionary( + checkDictionary( + t, + inter, + dictionary, + expectedValue, + orgOwner, + ) + + resetStorage() + + dictionary = readDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + ) + + checkDictionary( t, inter, - resetStorage, dictionary, expectedValue, orgOwner, @@ -2242,27 +2381,135 @@ func TestRandomValueGeneration(t *testing.T) { } func mapKey(inter *interpreter.Interpreter, key interpreter.Value) any { + switch key := key.(type) { case *interpreter.StringValue: - return *key + type stringValue string + return stringValue(key.Str) + + case interpreter.CharacterValue: + type characterValue string + return characterValue(key.Str) + + case interpreter.TypeValue: + type typeValue common.TypeID + return typeValue(key.Type.ID()) case *interpreter.CompositeValue: type enumKey struct { location common.Location qualifiedIdentifier string kind common.CompositeKind - rawValue interpreter.Value + rawValue string } return enumKey{ location: key.Location, qualifiedIdentifier: key.QualifiedIdentifier, kind: key.Kind, - rawValue: key.GetField(inter, interpreter.EmptyLocationRange, sema.EnumRawValueFieldName), + rawValue: key.GetField( + inter, + interpreter.EmptyLocationRange, + sema.EnumRawValueFieldName, + ).String(), } - case interpreter.Value: + case interpreter.IntValue: + type intValue string + return intValue(key.String()) + + case interpreter.UIntValue: + type uintValue string + return uintValue(key.String()) + + case interpreter.Int8Value: + type int8Value string + return int8Value(key.String()) + + case interpreter.UInt8Value: + type uint8Value string + return uint8Value(key.String()) + + case interpreter.Int16Value: + type int16Value string + return int16Value(key.String()) + + case interpreter.UInt16Value: + type uint16Value string + return uint16Value(key.String()) + + case interpreter.Int32Value: + type int32Value string + return int32Value(key.String()) + + case interpreter.UInt32Value: + type uint32Value string + return uint32Value(key.String()) + + case interpreter.Int64Value: + type int64Value string + return int64Value(key.String()) + + case interpreter.UInt64Value: + type uint64Value string + return uint64Value(key.String()) + + case interpreter.Int128Value: + type int128Value string + return int128Value(key.String()) + + case interpreter.UInt128Value: + type uint128Value string + return uint128Value(key.String()) + + case interpreter.Int256Value: + type int256Value string + return int256Value(key.String()) + + case interpreter.UInt256Value: + type uint256Value string + return uint256Value(key.String()) + + case interpreter.Word8Value: + type word8Value string + return word8Value(key.String()) + + case interpreter.Word16Value: + type word16Value string + return word16Value(key.String()) + + case interpreter.Word32Value: + type word32Value string + return word32Value(key.String()) + + case interpreter.Word64Value: + type word64Value string + return word64Value(key.String()) + + case interpreter.Word128Value: + type word128Value string + return word128Value(key.String()) + + case interpreter.Word256Value: + type word256Value string + return word256Value(key.String()) + + case interpreter.PathValue: + return key + + case interpreter.AddressValue: + return key + + case interpreter.BoolValue: return key + case interpreter.Fix64Value: + type fix64Value string + return fix64Value(key.String()) + + case interpreter.UFix64Value: + type ufix64Value string + return ufix64Value(key.String()) + default: panic(errors.NewUnexpectedError("unsupported map key type: %T", key)) } From 2a09f8f0f4c77ef79331cfab88a64e139175c074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 15 Jan 2025 16:00:48 -0800 Subject: [PATCH 39/80] refactor composite tests --- runtime/tests/interpreter/values_test.go | 281 ++++++++++++++--------- 1 file changed, 175 insertions(+), 106 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index d247a6ca4..ea34c7b39 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -934,6 +934,44 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { const compositeStorageMapKey = interpreter.StringStorageMapKey("composite") + writeComposite := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + composite *interpreter.CompositeValue, + ) { + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + storageMapKey, + composite, + ) + } + + readComposite := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) *interpreter.CompositeValue { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, storageMapKey) + require.NotNil(t, readValue) + + require.IsType(t, &interpreter.CompositeValue{}, readValue) + return readValue.(*interpreter.CompositeValue) + } + createComposite := func( t *testing.T, r *randomValueGenerator, @@ -941,7 +979,6 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { ) ( *interpreter.CompositeValue, cadence.Struct, - func() *interpreter.CompositeValue, ) { expectedValue := r.randomStructValue(inter, 0) @@ -980,34 +1017,14 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { // Store the composite in a storage map, so that the composite's slab // is referenced by the root of the storage. - inter.Storage(). - GetStorageMap( - orgOwner, - common.PathDomainStorage.Identifier(), - true, - ). - WriteValue( - inter, - compositeStorageMapKey, - composite, - ) - - reloadComposite := func() *interpreter.CompositeValue { - storageMap := inter.Storage().GetStorageMap( - orgOwner, - common.PathDomainStorage.Identifier(), - false, - ) - require.NotNil(t, storageMap) - - readValue := storageMap.ReadValue(inter, compositeStorageMapKey) - require.NotNil(t, readValue) - - require.IsType(t, &interpreter.CompositeValue{}, readValue) - return readValue.(*interpreter.CompositeValue) - } + writeComposite( + inter, + orgOwner, + compositeStorageMapKey, + composite, + ) - return composite, expectedValue, reloadComposite + return composite, expectedValue } checkComposite := func( @@ -1033,31 +1050,6 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { assert.Equal(t, expectedOwner, owner) } - doubleCheckComposite := func( - t *testing.T, - inter *interpreter.Interpreter, - resetStorage func(), - composite *interpreter.CompositeValue, - expectedValue cadence.Struct, - expectedOwner common.Address, - ) { - // Check the values of the composite. - // Once right away, and once after a reset (commit and reload) of the storage. - - for i := 0; i < 2; i++ { - - checkComposite( - t, - inter, - composite, - expectedValue, - expectedOwner, - ) - - resetStorage() - } - } - t.Run("construction", func(t *testing.T) { t.Parallel() @@ -1067,16 +1059,32 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - composite, expectedValue, _ := createComposite(t, &r, inter) + composite, expectedValue := createComposite(t, &r, inter) - doubleCheckComposite( + checkComposite( t, inter, - resetStorage, composite, expectedValue, orgOwner, ) + + resetStorage() + + composite = readComposite( + inter, + orgOwner, + compositeStorageMapKey, + ) + + checkComposite( + t, + inter, + composite, + expectedValue, + orgOwner, + ) + }) t.Run("move (transfer and deep remove)", func(t *testing.T) { @@ -1088,10 +1096,32 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - original, expectedValue, _ := createComposite(t, &r, inter) + original, expectedValue := createComposite(t, &r, inter) + + checkComposite( + t, + inter, + original, + expectedValue, + orgOwner, + ) resetStorage() + original = readComposite( + inter, + orgOwner, + compositeStorageMapKey, + ) + + checkComposite( + t, + inter, + original, + expectedValue, + orgOwner, + ) + // Transfer the composite to a new owner newOwner := common.Address{'B'} @@ -1103,59 +1133,58 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { false, nil, nil, - // TODO: is has no parent container = true correct? - true, + false, ).(*interpreter.CompositeValue) // Store the transferred composite in a storage map, so that the composite's slab // is referenced by the root of the storage. - inter.Storage(). - GetStorageMap( - newOwner, - common.PathDomainStorage.Identifier(), - true, - ). - WriteValue( - inter, - interpreter.StringStorageMapKey("transferred_composite"), - transferred, - ) - - // Both original and transferred composite should contain the expected values - // Check once right away, and once after a reset (commit and reload) of the storage. - - for i := 0; i < 2; i++ { - - checkComposite( - t, - inter, - original, - expectedValue, - orgOwner, - ) - - checkComposite( - t, - inter, - transferred, - expectedValue, - newOwner, - ) + const transferredStorageMapKey = interpreter.StringStorageMapKey("transferred") - resetStorage() - } + writeComposite( + inter, + newOwner, + transferredStorageMapKey, + transferred, + ) - // Deep remove the original composite + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + inter.Storage(). + GetStorageMap(orgOwner, common.PathDomainStorage.Identifier(), false). + RemoveValue(inter, compositeStorageMapKey) - // TODO: is has no parent container = true correct? - original.DeepRemove(inter, true) + return struct{}{} + }) if !original.Inlined() { err := inter.Storage().Remove(original.SlabID()) require.NoError(t, err) } + checkComposite( + t, + inter, + transferred, + expectedValue, + newOwner, + ) + + resetStorage() + + transferred = readComposite( + inter, + newOwner, + transferredStorageMapKey, + ) + + checkComposite( + t, + inter, + transferred, + expectedValue, + newOwner, + ) + if *validateAtree { err := inter.Storage().CheckHealth() require.NoError(t, err) @@ -1163,10 +1192,25 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { // New composite should still be accessible - doubleCheckComposite( + checkComposite( + t, + inter, + transferred, + expectedValue, + newOwner, + ) + + resetStorage() + + transferred = readComposite( + inter, + newOwner, + transferredStorageMapKey, + ) + + checkComposite( t, inter, - resetStorage, transferred, expectedValue, newOwner, @@ -1183,21 +1227,31 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - composite, expectedValue, reloadComposite := createComposite(t, &r, inter) + composite, expectedValue := createComposite(t, &r, inter) - // Check composite and reset storage - doubleCheckComposite( + checkComposite( t, inter, - resetStorage, composite, expectedValue, orgOwner, ) - // Reload the composite after the reset + resetStorage() + + composite = readComposite( + inter, + orgOwner, + compositeStorageMapKey, + ) - composite = reloadComposite() + checkComposite( + t, + inter, + composite, + expectedValue, + orgOwner, + ) typeID := expectedValue.StructType.Location. TypeID(nil, expectedValue.StructType.QualifiedIdentifier) @@ -1246,10 +1300,25 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { // Composite must have same number of key-value pairs require.Equal(t, typeFieldCount, composite.FieldCount()) - doubleCheckComposite( + checkComposite( + t, + inter, + composite, + expectedValue, + orgOwner, + ) + + resetStorage() + + composite = readComposite( + inter, + orgOwner, + compositeStorageMapKey, + ) + + checkComposite( t, inter, - resetStorage, composite, expectedValue, orgOwner, From bf7f7e63c518c5b57070acca754419fbb6201417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 15 Jan 2025 16:11:00 -0800 Subject: [PATCH 40/80] refactor array tests --- runtime/tests/interpreter/values_test.go | 437 +++++++++++++++-------- 1 file changed, 285 insertions(+), 152 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index ea34c7b39..df9b843ec 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -1339,6 +1339,44 @@ func TestInterpretRandomArrayOperations(t *testing.T) { const arrayStorageMapKey = interpreter.StringStorageMapKey("array") + writeArray := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + array *interpreter.ArrayValue, + ) { + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + storageMapKey, + array, + ) + } + + readArray := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) *interpreter.ArrayValue { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, storageMapKey) + require.NotNil(t, readValue) + + require.IsType(t, &interpreter.ArrayValue{}, readValue) + return readValue.(*interpreter.ArrayValue) + } + createArray := func( t *testing.T, r *randomValueGenerator, @@ -1346,7 +1384,6 @@ func TestInterpretRandomArrayOperations(t *testing.T) { ) ( *interpreter.ArrayValue, cadence.Array, - func() *interpreter.ArrayValue, ) { expectedValue := r.randomArrayValue(inter, 0) @@ -1378,34 +1415,14 @@ func TestInterpretRandomArrayOperations(t *testing.T) { // Store the array in a storage map, so that the array's slab // is referenced by the root of the storage. - inter.Storage(). - GetStorageMap( - orgOwner, - common.PathDomainStorage.Identifier(), - true, - ). - WriteValue( - inter, - arrayStorageMapKey, - array, - ) - - reloadArray := func() *interpreter.ArrayValue { - storageMap := inter.Storage().GetStorageMap( - orgOwner, - common.PathDomainStorage.Identifier(), - false, - ) - require.NotNil(t, storageMap) - - readValue := storageMap.ReadValue(inter, arrayStorageMapKey) - require.NotNil(t, readValue) - - require.IsType(t, &interpreter.ArrayValue{}, readValue) - return readValue.(*interpreter.ArrayValue) - } + writeArray( + inter, + orgOwner, + arrayStorageMapKey, + array, + ) - return array, expectedValue, reloadArray + return array, expectedValue } checkArray := func( @@ -1429,67 +1446,32 @@ func TestInterpretRandomArrayOperations(t *testing.T) { assert.Equal(t, expectedOwner, owner) } - doubleCheckArray := func( - t *testing.T, - inter *interpreter.Interpreter, - resetStorage func(), - array *interpreter.ArrayValue, - expectedValue cadence.Array, - expectedOwner common.Address, - ) { - // Check the values of the array. - // Once right away, and once after a reset (commit and reload) of the storage. - - for i := 0; i < 2; i++ { - - checkArray( - t, - inter, - array, - expectedValue, - expectedOwner, - ) - - resetStorage() - } - } - checkIteration := func( t *testing.T, inter *interpreter.Interpreter, - resetStorage func(), array *interpreter.ArrayValue, expectedValue cadence.Array, ) { + require.Equal(t, len(expectedValue.Values), array.Count()) - // Iterate over the values of the created array. - // Once right after construction, and once after a reset (commit and reload) of the storage. - - for i := 0; i < 2; i++ { - - require.Equal(t, len(expectedValue.Values), array.Count()) - - var iterations int - - array.Iterate( - inter, - func(element interpreter.Value) (resume bool) { - value := importValue(t, inter, expectedValue.Values[iterations]) + var iterations int - utils.AssertValuesEqual(t, inter, value, element) + array.Iterate( + inter, + func(element interpreter.Value) (resume bool) { + value := importValue(t, inter, expectedValue.Values[iterations]) - iterations += 1 + utils.AssertValuesEqual(t, inter, value, element) - return true - }, - false, - interpreter.EmptyLocationRange, - ) + iterations += 1 - assert.Equal(t, len(expectedValue.Values), iterations) + return true + }, + false, + interpreter.EmptyLocationRange, + ) - resetStorage() - } + assert.Equal(t, len(expectedValue.Values), iterations) } t.Run("construction", func(t *testing.T) { @@ -1501,12 +1483,27 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - array, expectedValue, _ := createArray(t, &r, inter) + array, expectedValue := createArray(t, &r, inter) - doubleCheckArray( + checkArray( + t, + inter, + array, + expectedValue, + orgOwner, + ) + + resetStorage() + + array = readArray( + inter, + orgOwner, + arrayStorageMapKey, + ) + + checkArray( t, inter, - resetStorage, array, expectedValue, orgOwner, @@ -1522,12 +1519,42 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - array, expectedValue, _ := createArray(t, &r, inter) + array, expectedValue := createArray(t, &r, inter) + + checkArray( + t, + inter, + array, + expectedValue, + orgOwner, + ) + + checkIteration( + t, + inter, + array, + expectedValue, + ) + + resetStorage() + + array = readArray( + inter, + orgOwner, + arrayStorageMapKey, + ) + + checkArray( + t, + inter, + array, + expectedValue, + orgOwner, + ) checkIteration( t, inter, - resetStorage, array, expectedValue, ) @@ -1542,10 +1569,32 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - original, expectedValue, _ := createArray(t, &r, inter) + original, expectedValue := createArray(t, &r, inter) + + checkArray( + t, + inter, + original, + expectedValue, + orgOwner, + ) resetStorage() + original = readArray( + inter, + orgOwner, + arrayStorageMapKey, + ) + + checkArray( + t, + inter, + original, + expectedValue, + orgOwner, + ) + // Transfer the array to a new owner newOwner := common.Address{'B'} @@ -1557,59 +1606,58 @@ func TestInterpretRandomArrayOperations(t *testing.T) { false, nil, nil, - // TODO: is has no parent container = true correct? - true, + false, ).(*interpreter.ArrayValue) // Store the transferred array in a storage map, so that the array's slab // is referenced by the root of the storage. - inter.Storage(). - GetStorageMap( - newOwner, - common.PathDomainStorage.Identifier(), - true, - ). - WriteValue( - inter, - interpreter.StringStorageMapKey("transferred_array"), - transferred, - ) - - // Both original and transferred array should contain the expected values - // Check once right away, and once after a reset (commit and reload) of the storage. - - for i := 0; i < 2; i++ { - - checkArray( - t, - inter, - original, - expectedValue, - orgOwner, - ) - - checkArray( - t, - inter, - transferred, - expectedValue, - newOwner, - ) + const transferredStorageMapKey = interpreter.StringStorageMapKey("transferred") - resetStorage() - } + writeArray( + inter, + newOwner, + transferredStorageMapKey, + transferred, + ) - // Deep remove the original array + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + inter.Storage(). + GetStorageMap(orgOwner, common.PathDomainStorage.Identifier(), false). + RemoveValue(inter, arrayStorageMapKey) - // TODO: is has no parent container = true correct? - original.DeepRemove(inter, true) + return struct{}{} + }) if !original.Inlined() { err := inter.Storage().Remove(original.SlabID()) require.NoError(t, err) } + checkArray( + t, + inter, + transferred, + expectedValue, + newOwner, + ) + + resetStorage() + + transferred = readArray( + inter, + newOwner, + transferredStorageMapKey, + ) + + checkArray( + t, + inter, + transferred, + expectedValue, + newOwner, + ) + if *validateAtree { err := inter.Storage().CheckHealth() require.NoError(t, err) @@ -1617,10 +1665,25 @@ func TestInterpretRandomArrayOperations(t *testing.T) { // New array should still be accessible - doubleCheckArray( + checkArray( + t, + inter, + transferred, + expectedValue, + newOwner, + ) + + resetStorage() + + transferred = readArray( + inter, + newOwner, + transferredStorageMapKey, + ) + + checkArray( t, inter, - resetStorage, transferred, expectedValue, newOwner, @@ -1637,21 +1700,31 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - array, expectedValue, reloadArray := createArray(t, &r, inter) + array, expectedValue := createArray(t, &r, inter) - // Check array and reset storage - doubleCheckArray( + checkArray( t, inter, - resetStorage, array, expectedValue, orgOwner, ) - // Reload the array after the reset + resetStorage() - array = reloadArray() + array = readArray( + inter, + orgOwner, + arrayStorageMapKey, + ) + + checkArray( + t, + inter, + array, + expectedValue, + orgOwner, + ) existingValueCount := len(expectedValue.Values) @@ -1690,15 +1763,25 @@ func TestInterpretRandomArrayOperations(t *testing.T) { }) } - if *validateAtree { - err := inter.Storage().CheckHealth() - require.NoError(t, err) - } + checkArray( + t, + inter, + array, + expectedValue, + orgOwner, + ) + + resetStorage() - doubleCheckArray( + array = readArray( + inter, + orgOwner, + arrayStorageMapKey, + ) + + checkArray( t, inter, - resetStorage, array, expectedValue, orgOwner, @@ -1713,21 +1796,31 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - array, expectedValue, reloadArray := createArray(t, &r, inter) + array, expectedValue := createArray(t, &r, inter) - // Check array and reset storage - doubleCheckArray( + checkArray( t, inter, - resetStorage, array, expectedValue, orgOwner, ) - // Reload the array after the reset + resetStorage() - array = reloadArray() + array = readArray( + inter, + orgOwner, + arrayStorageMapKey, + ) + + checkArray( + t, + inter, + array, + expectedValue, + orgOwner, + ) // Random remove numberOfValues := len(expectedValue.Values) @@ -1761,10 +1854,25 @@ func TestInterpretRandomArrayOperations(t *testing.T) { // Array must be empty require.Equal(t, 0, array.Count()) - doubleCheckArray( + checkArray( + t, + inter, + array, + expectedValue, + orgOwner, + ) + + resetStorage() + + array = readArray( + inter, + orgOwner, + arrayStorageMapKey, + ) + + checkArray( t, inter, - resetStorage, array, expectedValue, orgOwner, @@ -1781,21 +1889,31 @@ func TestInterpretRandomArrayOperations(t *testing.T) { inter, resetStorage := newRandomValueTestInterpreter(t) - array, expectedValue, reloadArray := createArray(t, &r, inter) + array, expectedValue := createArray(t, &r, inter) - // Check array and reset storage - doubleCheckArray( + checkArray( t, inter, - resetStorage, array, expectedValue, orgOwner, ) - // Reload the array after the reset + resetStorage() - array = reloadArray() + array = readArray( + inter, + orgOwner, + arrayStorageMapKey, + ) + + checkArray( + t, + inter, + array, + expectedValue, + orgOwner, + ) elementCount := array.Count() @@ -1830,10 +1948,25 @@ func TestInterpretRandomArrayOperations(t *testing.T) { // Array must have same number of elements require.Equal(t, elementCount, array.Count()) - doubleCheckArray( + checkArray( + t, + inter, + array, + expectedValue, + orgOwner, + ) + + resetStorage() + + array = readArray( + inter, + orgOwner, + arrayStorageMapKey, + ) + + checkArray( t, inter, - resetStorage, array, expectedValue, orgOwner, From 00c2d4439f82561f4246f76543fb25a61bbe6059 Mon Sep 17 00:00:00 2001 From: Tarak Ben youssef Date: Thu, 16 Jan 2025 01:35:44 +0100 Subject: [PATCH 41/80] add tests for signed cases --- runtime/tests/interpreter/bitwise_test.go | 321 +++++++++++++++++++++- 1 file changed, 308 insertions(+), 13 deletions(-) diff --git a/runtime/tests/interpreter/bitwise_test.go b/runtime/tests/interpreter/bitwise_test.go index 61f3a2bca..e5aae3ea7 100644 --- a/runtime/tests/interpreter/bitwise_test.go +++ b/runtime/tests/interpreter/bitwise_test.go @@ -255,11 +255,194 @@ func TestInterpretBitwiseRightShift(t *testing.T) { } } +func TestInterpretBitwiseLeftShift8(t *testing.T) { + + t.Parallel() + + t.Run("Int8 << 9 (zero result)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int8 = 0x7f + let b: Int8 = 9 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt8Value(0), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int8 << 1 (positive to positive)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int8 = 5 + let b: Int8 = 1 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt8Value(10), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int8 << 1 (negative to negative)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int8 = -5 // 0b1111_1011 + let b: Int8 = 1 + let c = a << b // 0b1111_0110 --> -10 + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt8Value(-10), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int8 << 1 (positive to negative)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int8 = 5 // 0b0000_0101 + let b: Int8 = 7 + let c = a << b // 0b1000_0000 --> -128 + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt8Value(-128), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int8 << 1 (negative to positive)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int8 = -5 // 0b1111_1011 + let b: Int8 = 5 + let c = a << b // 0b0110_0000 --> 96 + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt8Value(0x60), // or 96 + inter.Globals.Get("c").GetValue(inter), + ) + }) + t.Run("Int8 << -3", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + fun test() { + let a: Int8 = 0x7f + let b: Int8 = -3 + let c = a << b + } + `) + _, err := inter.Invoke("test") + RequireError(t, err) + + var shiftErr interpreter.NegativeShiftError + require.ErrorAs(t, err, &shiftErr) + }) + + t.Run("UInt8 << 9", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: UInt8 = 0x7f + let b: UInt8 = 9 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredUInt8Value(0), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("UInt8 << 1", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: UInt8 = 0xff + let b: UInt8 = 1 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredUInt8Value(0xfe), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Word8 << 9", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Word8 = 0xff + let b: Word8 = 9 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredWord8Value(0), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Word8 << 1", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Word8 = 0xff + let b: Word8 = 1 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredWord8Value(0xfe), + inter.Globals.Get("c").GetValue(inter), + ) + }) +} + func TestInterpretBitwiseLeftShift128(t *testing.T) { t.Parallel() - t.Run("Int128 << 130", func(t *testing.T) { + t.Run("Int128 << 130 (zero result)", func(t *testing.T) { inter := parseCheckAndInterpret(t, ` @@ -277,17 +460,73 @@ func TestInterpretBitwiseLeftShift128(t *testing.T) { ) }) - t.Run("Int128 << 32", func(t *testing.T) { + t.Run("Int128 << 1 (positive to positive)", func(t *testing.T) { inter := parseCheckAndInterpret(t, ` - let a: Int128 = 0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ffff - let b: Int128 = 32 + let a: Int128 = 5 + let b: Int128 = 1 let c = a << b - `, + `, ) - bigInt, _ := big.NewInt(0).SetString("0xffff_ffff_ffff_ffff_ffff_ffff_0000_0000", 0) + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt128ValueFromInt64(10), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int128 << 1 (negative to negative)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int128 = -5 + let b: Int128 = 1 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt128ValueFromInt64(-10), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int128 << 127 (positive to negative)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int128 = 5 // 0b0000_0101 + let b: Int128 = 127 + let c = a << b // 0b1000_0000_..._0000 --> -2^127 + `, + ) + + bigInt, _ := big.NewInt(0).SetString("-0x80000000_00000000_00000000_00000000", 0) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt128ValueFromBigInt(bigInt), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int128 << 125 (negative to positive)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int128 = -5 // 0b1111_1111_..._1111_1011 + let b: Int128 = 125 + let c = a << b // 0b0110_0000_..._0000 + `, + ) + + bigInt, _ := big.NewInt(0).SetString("0x60000000_00000000_00000000_00000000", 0) AssertValuesEqual( t, @@ -395,7 +634,7 @@ func TestInterpretBitwiseLeftShift256(t *testing.T) { t.Parallel() - t.Run("Int256 << 260", func(t *testing.T) { + t.Run("Int256 << 260 (zero result)", func(t *testing.T) { inter := parseCheckAndInterpret(t, ` @@ -413,17 +652,73 @@ func TestInterpretBitwiseLeftShift256(t *testing.T) { ) }) - t.Run("Int256 << 32", func(t *testing.T) { + t.Run("Int256 << 1 (positive to positive)", func(t *testing.T) { inter := parseCheckAndInterpret(t, ` - let a: Int256 = 0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff - let b: Int256 = 32 - let c = a << b - `, + let a: Int256 = 5 + let b: Int256 = 1 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt256ValueFromInt64(10), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int256 << 1 (negative to negative)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int256 = -5 + let b: Int256 = 1 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt256ValueFromInt64(-10), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int256 << 255 (positive to negative)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int256 = 5 // 0b0000_0101 + let b: Int256 = 255 + let c = a << b // 0b1000_0000_..._0000 --> -2^127 + `, + ) + + bigInt, _ := big.NewInt(0).SetString("-0x80000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000", 0) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt256ValueFromBigInt(bigInt), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int256 << 253 (negative to positive)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int256 = -5 // 0b1111_1111_..._1111_1011 + let b: Int256 = 253 + let c = a << b // 0b0110_0000_..._0000 + `, ) - bigInt, _ := big.NewInt(0).SetString("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_0000_0000", 0) + bigInt, _ := big.NewInt(0).SetString("0x60000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000", 0) AssertValuesEqual( t, From b025344cba3963ec4eef564d7263711cbea68dd6 Mon Sep 17 00:00:00 2001 From: Tarak Ben youssef Date: Thu, 16 Jan 2025 01:37:20 +0100 Subject: [PATCH 42/80] adjust left shift implementation for signed integers --- runtime/interpreter/value.go | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index a3f477aef..dc4f58b19 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -7412,6 +7412,21 @@ func (Int64Value) ChildStorables() []atree.Storable { return nil } +// toTwosComplement sets `res` to the two's complement representation of a big.Int `x` in the given target bit size. +// `res` is returned and is awlways a positive big.Int. +func toTwosComplement(res, x *big.Int, targetBitSize uint) *big.Int { + bytes := SignedBigIntToSizedBigEndianBytes(x, targetBitSize/8) + return res.SetBytes(bytes) +} + +// toTwosComplement converts `res` to the big.Int representation from the two's complement format of a +// signed integer. +// `res` is returned and can be positive or negative. +func fromTwosComplement(res *big.Int) *big.Int { + bytes := res.Bytes() + return BigEndianBytesToSignedBigInt(bytes) +} + // truncate trims a big.Int to maxWords by directly modifying its underlying representation. func truncate(x *big.Int, maxWords int) *big.Int { // Get the absolute value of x as a nat slice. @@ -8065,8 +8080,10 @@ func (v Int128Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVal valueGetter := func() *big.Int { res := new(big.Int) - res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) - return truncate(res, 128/bits.UintSize) + res = toTwosComplement(res, v.BigInt, 128) + res = res.Lsh(res, uint(o.BigInt.Uint64())) + res = truncate(res, 128/bits.UintSize) + return fromTwosComplement(res) } return NewInt128ValueFromBigInt(interpreter, valueGetter) @@ -8809,8 +8826,10 @@ func (v Int256Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerVal valueGetter := func() *big.Int { res := new(big.Int) - res = res.Lsh(v.BigInt, uint(o.BigInt.Uint64())) - return truncate(res, 256/bits.UintSize) + res = toTwosComplement(res, v.BigInt, 256) + res = res.Lsh(res, uint(o.BigInt.Uint64())) + res = truncate(res, 256/bits.UintSize) + return fromTwosComplement(res) } return NewInt256ValueFromBigInt(interpreter, valueGetter) From dad02f70e24ff19377c65f73a5be53475ab104df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 15 Jan 2025 17:16:31 -0800 Subject: [PATCH 43/80] remove unnecessary manual slab removal --- runtime/tests/interpreter/values_test.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index df9b843ec..24289b96d 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -539,11 +539,6 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { return struct{}{} }) - if !original.Inlined() { - err := inter.Storage().Remove(original.SlabID()) - require.NoError(t, err) - } - checkDictionary( t, inter, @@ -1156,11 +1151,6 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { return struct{}{} }) - if !original.Inlined() { - err := inter.Storage().Remove(original.SlabID()) - require.NoError(t, err) - } - checkComposite( t, inter, @@ -1629,11 +1619,6 @@ func TestInterpretRandomArrayOperations(t *testing.T) { return struct{}{} }) - if !original.Inlined() { - err := inter.Storage().Remove(original.SlabID()) - require.NoError(t, err) - } - checkArray( t, inter, From c72a22d11a5d8b2cd49673821cb06caac6dcc1b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 15 Jan 2025 17:26:28 -0800 Subject: [PATCH 44/80] add more health checks --- runtime/tests/interpreter/values_test.go | 107 +++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 24289b96d..fa210c767 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -403,6 +403,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { orgOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + resetStorage() dictionary = readDictionary( @@ -418,6 +423,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { expectedValue, orgOwner, ) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } }) t.Run("iterate", func(t *testing.T) { @@ -446,6 +456,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { expectedValue, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + resetStorage() dictionary = readDictionary( @@ -468,6 +483,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { dictionary, expectedValue, ) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } }) t.Run("move (transfer and deep remove)", func(t *testing.T) { @@ -594,6 +614,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { newOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + // TODO: check deep removal cleaned up everything in original account (storage size, slab count) }) @@ -713,6 +738,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { expectedValue, orgOwner, ) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } }) t.Run("remove", func(t *testing.T) { @@ -806,6 +836,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { orgOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + // TODO: check storage size, slab count }) @@ -914,6 +949,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { orgOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + // TODO: check storage size, slab count }) } @@ -1064,6 +1104,11 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { orgOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + resetStorage() composite = readComposite( @@ -1080,6 +1125,11 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { orgOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + }) t.Run("move (transfer and deep remove)", func(t *testing.T) { @@ -1206,6 +1256,11 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { newOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + // TODO: check deep removal cleaned up everything in original account (storage size, slab count) }) @@ -1314,6 +1369,11 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { orgOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + // TODO: check storage size, slab count }) } @@ -1483,6 +1543,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { orgOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + resetStorage() array = readArray( @@ -1498,6 +1563,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { expectedValue, orgOwner, ) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } }) t.Run("iterate", func(t *testing.T) { @@ -1526,6 +1596,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { expectedValue, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + resetStorage() array = readArray( @@ -1548,6 +1623,12 @@ func TestInterpretRandomArrayOperations(t *testing.T) { array, expectedValue, ) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + }) t.Run("move (transfer and deep remove)", func(t *testing.T) { @@ -1674,6 +1755,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { newOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + // TODO: check deep removal cleaned up everything in original account (storage size, slab count) }) @@ -1756,6 +1842,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { orgOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + resetStorage() array = readArray( @@ -1771,6 +1862,12 @@ func TestInterpretRandomArrayOperations(t *testing.T) { expectedValue, orgOwner, ) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + }) t.Run("remove", func(t *testing.T) { @@ -1863,6 +1960,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { orgOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + // TODO: check storage size, slab count }) @@ -1957,6 +2059,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { orgOwner, ) + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + // TODO: check storage size, slab count }) } From 5194f52c1becbf150f8b92e013e808a53dce01a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 15 Jan 2025 17:28:56 -0800 Subject: [PATCH 45/80] make generated values deeper and less wide --- runtime/tests/interpreter/values_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index fa210c767..51a645dc1 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -44,8 +44,8 @@ import ( ) var defaultRandomValueLimits = randomValueLimits{ - containerMaxDepth: 3, - containerMaxSize: 50, + containerMaxDepth: 4, + containerMaxSize: 40, compositeMaxFields: 10, } From 1ae4799d62d9ccfb5ab9b3b40c2e91b8573857b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 16 Jan 2025 10:16:04 -0800 Subject: [PATCH 46/80] Update runtime/tests/interpreter/values_test.go Co-authored-by: Faye Amacker <33205765+fxamacker@users.noreply.github.com> --- runtime/tests/interpreter/values_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 51a645dc1..35b22815c 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -50,7 +50,7 @@ var defaultRandomValueLimits = randomValueLimits{ } var runSmokeTests = flag.Bool("runSmokeTests", false, "Run smoke tests on values") -var validateAtree = flag.Bool("validateAtree", false, "Enable atree validation") +var validateAtree = flag.Bool("validateAtree", true, "Enable atree validation") var smokeTestSeed = flag.Int64("smokeTestSeed", -1, "Seed for prng (-1 specifies current Unix time)") func newRandomValueTestInterpreter(t *testing.T) (inter *interpreter.Interpreter, resetStorage func()) { From 3ddb0bc3bc619c5e3ec806cb0c3406cb17c4fd75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 16 Jan 2025 10:22:08 -0800 Subject: [PATCH 47/80] remove unnecessary duplicate resets and checks --- runtime/tests/interpreter/values_test.go | 78 ------------------------ 1 file changed, 78 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 35b22815c..6029d1d1b 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -567,37 +567,11 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { newOwner, ) - resetStorage() - - transferred = readDictionary( - inter, - newOwner, - transferredStorageMapKey, - ) - - checkDictionary( - t, - inter, - transferred, - expectedValue, - newOwner, - ) - if *validateAtree { err := inter.Storage().CheckHealth() require.NoError(t, err) } - // New dictionary should still be accessible - - checkDictionary( - t, - inter, - transferred, - expectedValue, - newOwner, - ) - resetStorage() transferred = readDictionary( @@ -1209,37 +1183,11 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { newOwner, ) - resetStorage() - - transferred = readComposite( - inter, - newOwner, - transferredStorageMapKey, - ) - - checkComposite( - t, - inter, - transferred, - expectedValue, - newOwner, - ) - if *validateAtree { err := inter.Storage().CheckHealth() require.NoError(t, err) } - // New composite should still be accessible - - checkComposite( - t, - inter, - transferred, - expectedValue, - newOwner, - ) - resetStorage() transferred = readComposite( @@ -1708,37 +1656,11 @@ func TestInterpretRandomArrayOperations(t *testing.T) { newOwner, ) - resetStorage() - - transferred = readArray( - inter, - newOwner, - transferredStorageMapKey, - ) - - checkArray( - t, - inter, - transferred, - expectedValue, - newOwner, - ) - if *validateAtree { err := inter.Storage().CheckHealth() require.NoError(t, err) } - // New array should still be accessible - - checkArray( - t, - inter, - transferred, - expectedValue, - newOwner, - ) - resetStorage() transferred = readArray( From bd43050f60933900cbdaffb53b65b9f8691d1db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 16 Jan 2025 16:07:21 -0800 Subject: [PATCH 48/80] add value smoke tests that mutate nested containers --- runtime/tests/interpreter/values_test.go | 1626 ++++++++++++++++++++++ 1 file changed, 1626 insertions(+) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 6029d1d1b..adb9c1908 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -1990,6 +1990,1629 @@ func TestInterpretRandomArrayOperations(t *testing.T) { }) } +func TestInterpretRandomNestedArrayOperations(t *testing.T) { + if !*runSmokeTests { + t.Skip("smoke tests are disabled") + } + + owner := common.Address{'A'} + + limits := randomValueLimits{ + containerMaxDepth: 6, + containerMaxSize: 20, + compositeMaxFields: 10, + } + + const opCount = 5 + + createValue := func( + t *testing.T, + r *randomValueGenerator, + inter *interpreter.Interpreter, + predicate func(cadence.Array) bool, + ) ( + actualRootValue interpreter.Value, + generatedValue cadence.Value, + reloadActualRootValue func() interpreter.Value, + getNestedArray func(rootValue interpreter.Value, owner common.Address) *interpreter.ArrayValue, + ) { + + // It does not matter what the root value is, + // as long as it contains a nested array, + // which it is nested inside an optional, + // and it satisfies the given predicate. + + var path []pathElement + for { + generatedValue = r.randomArrayValue(inter, 0) + + path = findNestedValue( + generatedValue, + func(value cadence.Value, path []pathElement) bool { + array, ok := value.(cadence.Array) + if !ok { + return false + } + + if !predicate(array) { + return false + } + + var foundSome bool + for _, element := range path { + if _, ok := element.(somePathElement); ok { + foundSome = true + break + } + } + if !foundSome { + return false + } + + return true + }, + ) + if path != nil { + break + } + } + + actualRootValue = importValue(t, inter, generatedValue).Transfer( + inter, + interpreter.EmptyLocationRange, + atree.Address(owner), + false, + nil, + nil, + // TODO: is has no parent container = true correct? + true, + ) + + const arrayStorageMapKey = interpreter.StringStorageMapKey("array") + + // Store the array in a storage map, so that the array's slab + // is referenced by the root of the storage. + + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + arrayStorageMapKey, + actualRootValue, + ) + + reloadActualRootValue = func() interpreter.Value { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, arrayStorageMapKey) + require.NotNil(t, readValue) + + require.IsType(t, &interpreter.ArrayValue{}, readValue) + return readValue.(*interpreter.ArrayValue) + } + + getNestedArray = func(rootValue interpreter.Value, owner common.Address) *interpreter.ArrayValue { + nestedValue := getNestedValue(t, inter, rootValue, path) + require.IsType(t, &interpreter.ArrayValue{}, nestedValue) + nestedArray := nestedValue.(*interpreter.ArrayValue) + require.Equal(t, owner, nestedArray.GetOwner()) + return nestedArray + } + + return + } + + t.Run("insert", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + r := newRandomValueGenerator( + *smokeTestSeed, + limits, + ) + t.Logf("seed: %d", r.seed) + + actualRootValue, generatedValue, reloadActualRootValue, getNestedArray := + createValue( + t, + &r, + inter, + // Accept any array, even empty ones, + // given we're only inserting + func(array cadence.Array) bool { + return true + }, + ) + + actualNestedArray := getNestedArray(actualRootValue, owner) + + type insert struct { + index int + value cadence.Value + } + + performInsert := func(array *interpreter.ArrayValue, insert insert) { + + newValue := importValue(t, inter, insert.value) + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + array.Insert( + inter, + interpreter.EmptyLocationRange, + insert.index, + newValue, + ) + return struct{}{} + }) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + } + + // We use the generated value twice: once as the expected value, and once as the actual value. + // We first perform mutations on the actual value, and then compare it to the expected value. + // The actual value is stored in an account and reloaded. + // The expected value is temporary (zero address), and is not stored in storage. + // Given that the storage reset destroys the data for the expected value because it is temporary, + // we re-import it each time and perform all operations on it from scratch. + + var inserts []insert + + elementCount := actualNestedArray.Count() + + for i := 0; i < opCount; i++ { + var index int + elementCountAfterInserts := elementCount + i + if elementCountAfterInserts > 0 { + index = r.rand.Intn(elementCountAfterInserts) + } + + inserts = append( + inserts, + insert{ + index: index, + value: r.randomStorableValue(inter, 0), + }, + ) + } + + for i, insert := range inserts { + + resetStorage() + + actualRootValue = reloadActualRootValue() + actualNestedArray = getNestedArray(actualRootValue, owner) + + performInsert( + actualNestedArray, + insert, + ) + + // Re-create the expected value from scratch, + // by importing the generated value, and performing all updates on it + // that have been performed on the actual value so far. + + expectedRootValue := importValue(t, inter, generatedValue) + expectedNestedArray := getNestedArray(expectedRootValue, common.ZeroAddress) + + for _, insert := range inserts[:i+1] { + + performInsert( + expectedNestedArray, + insert, + ) + } + utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + } + }) + + t.Run("update", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + r := newRandomValueGenerator( + *smokeTestSeed, + limits, + ) + t.Logf("seed: %d", r.seed) + + actualRootValue, generatedValue, reloadActualRootValue, getNestedArray := + createValue( + t, + &r, + inter, + // Generate a non-empty array, + // so we have at least one element to update + func(array cadence.Array) bool { + return len(array.Values) > 0 + }, + ) + + actualNestedArray := getNestedArray(actualRootValue, owner) + + elementCount := actualNestedArray.Count() + require.Greater(t, elementCount, 0) + + type update struct { + index int + value cadence.Value + } + + performUpdate := func(array *interpreter.ArrayValue, update update) { + + newValue := importValue(t, inter, update.value) + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + array.Set( + inter, + interpreter.EmptyLocationRange, + update.index, + newValue, + ) + return struct{}{} + }) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // Array must have same number of elements + require.Equal(t, elementCount, array.Count()) + } + + // We use the generated value twice: once as the expected value, and once as the actual value. + // We first perform mutations on the actual value, and then compare it to the expected value. + // The actual value is stored in an account and reloaded. + // The expected value is temporary (zero address), and is not stored in storage. + // Given that the storage reset destroys the data for the expected value because it is temporary, + // we re-import it each time and perform all operations on it from scratch. + + var updates []update + + for i := 0; i < opCount; i++ { + updates = append( + updates, + update{ + index: r.rand.Intn(elementCount), + value: r.randomStorableValue(inter, 0), + }, + ) + } + + for i, update := range updates { + + resetStorage() + + actualRootValue = reloadActualRootValue() + actualNestedArray = getNestedArray(actualRootValue, owner) + + performUpdate( + actualNestedArray, + update, + ) + + // Re-create the expected value from scratch, + // by importing the generated value, and performing all updates on it + // that have been performed on the actual value so far. + + expectedRootValue := importValue(t, inter, generatedValue) + expectedNestedArray := getNestedArray(expectedRootValue, common.ZeroAddress) + + for _, update := range updates[:i+1] { + + performUpdate( + expectedNestedArray, + update, + ) + } + utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + } + }) + + t.Run("remove", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + r := newRandomValueGenerator( + *smokeTestSeed, + limits, + ) + t.Logf("seed: %d", r.seed) + + actualRootValue, generatedValue, reloadActualRootValue, getNestedArray := + createValue( + t, + &r, + inter, + func(array cadence.Array) bool { + return len(array.Values) >= opCount + }, + ) + + actualNestedArray := getNestedArray(actualRootValue, owner) + elementCount := actualNestedArray.Count() + require.GreaterOrEqual(t, elementCount, opCount) + + performRemove := func(array *interpreter.ArrayValue, index int) { + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + array.Remove( + inter, + interpreter.EmptyLocationRange, + index, + ) + return struct{}{} + }) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + } + + // We use the generated value twice: once as the expected value, and once as the actual value. + // We first perform mutations on the actual value, and then compare it to the expected value. + // The actual value is stored in an account and reloaded. + // The expected value is temporary (zero address), and is not stored in storage. + // Given that the storage reset destroys the data for the expected value because it is temporary, + // we re-import it each time and perform all operations on it from scratch. + + var removes []int + + for i := 0; i < opCount; i++ { + index := r.rand.Intn(elementCount - i) + removes = append(removes, index) + } + + for i, index := range removes { + + resetStorage() + + actualRootValue = reloadActualRootValue() + actualNestedArray = getNestedArray(actualRootValue, owner) + + performRemove( + actualNestedArray, + index, + ) + + // Re-create the expected value from scratch, + // by importing the generated value, and performing all updates on it + // that have been performed on the actual value so far. + + expectedRootValue := importValue(t, inter, generatedValue) + expectedNestedArray := getNestedArray(expectedRootValue, common.ZeroAddress) + + for _, index := range removes[:i+1] { + + performRemove( + expectedNestedArray, + index, + ) + } + utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + } + }) +} + +func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { + if !*runSmokeTests { + t.Skip("smoke tests are disabled") + } + + owner := common.Address{'A'} + + limits := randomValueLimits{ + containerMaxDepth: 6, + containerMaxSize: 20, + compositeMaxFields: 10, + } + + const opCount = 5 + + createValue := func( + t *testing.T, + r *randomValueGenerator, + inter *interpreter.Interpreter, + predicate func(cadence.Dictionary) bool, + ) ( + actualRootValue interpreter.Value, + generatedValue cadence.Value, + reloadActualRootValue func() interpreter.Value, + getNestedDictionary func(rootValue interpreter.Value, owner common.Address) *interpreter.DictionaryValue, + ) { + + // It does not matter what the root value is, + // as long as it contains a nested dictionary, + // which it is nested inside an optional, + // and it satisfies the given predicate. + + var path []pathElement + for { + generatedValue = r.randomDictionaryValue(inter, 0) + + path = findNestedValue( + generatedValue, + func(value cadence.Value, path []pathElement) bool { + dictionary, ok := value.(cadence.Dictionary) + if !ok { + return false + } + + if !predicate(dictionary) { + return false + } + + var foundSome bool + for _, element := range path { + if _, ok := element.(somePathElement); ok { + foundSome = true + break + } + } + if !foundSome { + return false + } + + return true + }, + ) + if path != nil { + break + } + } + + actualRootValue = importValue(t, inter, generatedValue).Transfer( + inter, + interpreter.EmptyLocationRange, + atree.Address(owner), + false, + nil, + nil, + // TODO: is has no parent container = true correct? + true, + ) + + const dictionaryStorageMapKey = interpreter.StringStorageMapKey("dictionary") + + // Store the dictionary in a storage map, so that the dictionary's slab + // is referenced by the root of the storage. + + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + dictionaryStorageMapKey, + actualRootValue, + ) + + reloadActualRootValue = func() interpreter.Value { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, dictionaryStorageMapKey) + require.NotNil(t, readValue) + + require.IsType(t, &interpreter.DictionaryValue{}, readValue) + return readValue.(*interpreter.DictionaryValue) + } + + getNestedDictionary = func(rootValue interpreter.Value, owner common.Address) *interpreter.DictionaryValue { + nestedValue := getNestedValue(t, inter, rootValue, path) + require.IsType(t, &interpreter.DictionaryValue{}, nestedValue) + nestedDictionary := nestedValue.(*interpreter.DictionaryValue) + require.Equal(t, owner, nestedDictionary.GetOwner()) + return nestedDictionary + } + + return + } + + t.Run("insert", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + r := newRandomValueGenerator( + *smokeTestSeed, + limits, + ) + t.Logf("seed: %d", r.seed) + + actualRootValue, generatedValue, reloadActualRootValue, getNestedDictionary := + createValue( + t, + &r, + inter, + // Accept any dictionary, even empty ones, + // given we're only inserting + func(dictionary cadence.Dictionary) bool { + return true + }, + ) + + actualNestedDictionary := getNestedDictionary(actualRootValue, owner) + + type insert struct { + key cadence.Value + value cadence.Value + } + + performInsert := func(dictionary *interpreter.DictionaryValue, insert insert) { + + newKey := importValue(t, inter, insert.key) + newValue := importValue(t, inter, insert.value) + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + dictionary.Insert( + inter, + interpreter.EmptyLocationRange, + newKey, + newValue, + ) + return struct{}{} + }) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + } + + // We use the generated value twice: once as the expected value, and once as the actual value. + // We first perform mutations on the actual value, and then compare it to the expected value. + // The actual value is stored in an account and reloaded. + // The expected value is temporary (zero address), and is not stored in storage. + // Given that the storage reset destroys the data for the expected value because it is temporary, + // we re-import it each time and perform all operations on it from scratch. + + var inserts []insert + insertSet := map[any]struct{}{} + + for i := 0; i < opCount; i++ { + // Generate a unique key + var key cadence.Value + for { + key = r.randomHashableValue(inter) + + importedKey := importValue(t, inter, key) + if actualNestedDictionary.ContainsKey( + inter, + interpreter.EmptyLocationRange, + importedKey, + ) { + continue + } + + mapKey := mapKey(inter, importedKey) + if _, ok := insertSet[mapKey]; ok { + continue + } + insertSet[mapKey] = struct{}{} + + break + } + + inserts = append( + inserts, + insert{ + key: key, + value: r.randomStorableValue(inter, 0), + }, + ) + } + + for i, insert := range inserts { + + resetStorage() + + actualRootValue = reloadActualRootValue() + actualNestedDictionary = getNestedDictionary(actualRootValue, owner) + + performInsert( + actualNestedDictionary, + insert, + ) + + // Re-create the expected value from scratch, + // by importing the generated value, and performing all updates on it + // that have been performed on the actual value so far. + + expectedRootValue := importValue(t, inter, generatedValue) + expectedNestedDictionary := getNestedDictionary(expectedRootValue, common.ZeroAddress) + + for _, insert := range inserts[:i+1] { + + performInsert( + expectedNestedDictionary, + insert, + ) + } + utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + } + }) + + t.Run("update", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + r := newRandomValueGenerator( + *smokeTestSeed, + limits, + ) + t.Logf("seed: %d", r.seed) + + actualRootValue, generatedValue, reloadActualRootValue, getNestedDictionary := + createValue( + t, + &r, + inter, + // Generate a non-empty dictionary, + // so we have at least one element to update + func(dictionary cadence.Dictionary) bool { + return len(dictionary.Pairs) > 0 + }, + ) + + actualNestedDictionary := getNestedDictionary(actualRootValue, owner) + + elementCount := actualNestedDictionary.Count() + require.Greater(t, elementCount, 0) + + type update struct { + key cadence.Value + value cadence.Value + } + + performUpdate := func(dictionary *interpreter.DictionaryValue, update update) { + + key := importValue(t, inter, update.key) + newValue := importValue(t, inter, update.value) + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + dictionary.SetKey( + inter, + interpreter.EmptyLocationRange, + key, + interpreter.NewUnmeteredSomeValueNonCopying(newValue), + ) + return struct{}{} + }) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // Dictionary must have same number of elements + require.Equal(t, elementCount, dictionary.Count()) + } + + // We use the generated value twice: once as the expected value, and once as the actual value. + // We first perform mutations on the actual value, and then compare it to the expected value. + // The actual value is stored in an account and reloaded. + // The expected value is temporary (zero address), and is not stored in storage. + // Given that the storage reset destroys the data for the expected value because it is temporary, + // we re-import it each time and perform all operations on it from scratch. + + keys := make([]cadence.Value, 0, elementCount) + + actualNestedDictionary.IterateKeys( + inter, + interpreter.EmptyLocationRange, + func(key interpreter.Value) (resume bool) { + cadenceKey, err := runtime.ExportValue( + key, + inter, + interpreter.EmptyLocationRange, + ) + require.NoError(t, err) + + keys = append(keys, cadenceKey) + + return true + }, + ) + + var updates []update + + for i := 0; i < opCount; i++ { + index := r.rand.Intn(elementCount) + + updates = append( + updates, + update{ + key: keys[index], + value: r.randomStorableValue(inter, 0), + }, + ) + } + + for i, update := range updates { + + resetStorage() + + actualRootValue = reloadActualRootValue() + actualNestedDictionary = getNestedDictionary(actualRootValue, owner) + + performUpdate( + actualNestedDictionary, + update, + ) + + // Re-create the expected value from scratch, + // by importing the generated value, and performing all updates on it + // that have been performed on the actual value so far. + + expectedRootValue := importValue(t, inter, generatedValue) + expectedNestedDictionary := getNestedDictionary(expectedRootValue, common.ZeroAddress) + + for _, update := range updates[:i+1] { + + performUpdate( + expectedNestedDictionary, + update, + ) + } + utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + } + }) + + t.Run("remove", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + r := newRandomValueGenerator( + *smokeTestSeed, + limits, + ) + t.Logf("seed: %d", r.seed) + + actualRootValue, generatedValue, reloadActualRootValue, getNestedDictionary := + createValue( + t, + &r, + inter, + func(dictionary cadence.Dictionary) bool { + return len(dictionary.Pairs) >= opCount + }, + ) + + actualNestedDictionary := getNestedDictionary(actualRootValue, owner) + + elementCount := actualNestedDictionary.Count() + require.GreaterOrEqual(t, elementCount, opCount) + + performRemove := func(dictionary *interpreter.DictionaryValue, key cadence.Value) { + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + dictionary.Remove( + inter, + interpreter.EmptyLocationRange, + importValue(t, inter, key), + ) + return struct{}{} + }) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + } + + // We use the generated value twice: once as the expected value, and once as the actual value. + // We first perform mutations on the actual value, and then compare it to the expected value. + // The actual value is stored in an account and reloaded. + // The expected value is temporary (zero address), and is not stored in storage. + // Given that the storage reset destroys the data for the expected value because it is temporary, + // we re-import it each time and perform all operations on it from scratch. + + keys := make([]interpreter.Value, 0, elementCount) + + actualNestedDictionary.IterateKeys( + inter, + interpreter.EmptyLocationRange, + func(key interpreter.Value) (resume bool) { + + keys = append(keys, key) + + return true + }, + ) + + var removes []cadence.Value + removeSet := map[any]struct{}{} + + for i := 0; i < opCount; i++ { + // Find a unique key + var key interpreter.Value + for { + key = keys[r.rand.Intn(elementCount)] + + mapKey := mapKey(inter, key) + if _, ok := removeSet[mapKey]; ok { + continue + } + removeSet[mapKey] = struct{}{} + + break + } + + cadenceKey, err := runtime.ExportValue( + key, + inter, + interpreter.EmptyLocationRange, + ) + require.NoError(t, err) + + removes = append(removes, cadenceKey) + } + + for i, index := range removes { + + resetStorage() + + actualRootValue = reloadActualRootValue() + actualNestedDictionary = getNestedDictionary(actualRootValue, owner) + + performRemove( + actualNestedDictionary, + index, + ) + + // Re-create the expected value from scratch, + // by importing the generated value, and performing all updates on it + // that have been performed on the actual value so far. + + expectedRootValue := importValue(t, inter, generatedValue) + expectedNestedDictionary := getNestedDictionary(expectedRootValue, common.ZeroAddress) + + for _, index := range removes[:i+1] { + + performRemove( + expectedNestedDictionary, + index, + ) + } + utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + } + }) +} + +func TestInterpretRandomNestedCompositeOperations(t *testing.T) { + if !*runSmokeTests { + t.Skip("smoke tests are disabled") + } + + owner := common.Address{'A'} + + limits := randomValueLimits{ + containerMaxDepth: 6, + containerMaxSize: 20, + compositeMaxFields: 10, + } + + const opCount = 5 + + createValue := func( + t *testing.T, + r *randomValueGenerator, + inter *interpreter.Interpreter, + predicate func(cadence.Composite) bool, + ) ( + actualRootValue interpreter.Value, + generatedValue cadence.Value, + reloadActualRootValue func() interpreter.Value, + getNestedComposite func(rootValue interpreter.Value, owner common.Address) *interpreter.CompositeValue, + ) { + + // It does not matter what the root value is, + // as long as it contains a nested composite, + // which it is nested inside an optional, + // and it satisfies the given predicate. + + var path []pathElement + for { + generatedValue = r.randomStructValue(inter, 0) + + path = findNestedValue( + generatedValue, + func(value cadence.Value, path []pathElement) bool { + composite, ok := value.(cadence.Struct) + if !ok { + return false + } + + if !predicate(composite) { + return false + } + + var foundSome bool + for _, element := range path { + if _, ok := element.(somePathElement); ok { + foundSome = true + break + } + } + if !foundSome { + return false + } + + return true + }, + ) + if path != nil { + break + } + } + + actualRootValue = importValue(t, inter, generatedValue).Transfer( + inter, + interpreter.EmptyLocationRange, + atree.Address(owner), + false, + nil, + nil, + // TODO: is has no parent container = true correct? + true, + ) + + const compositeStorageMapKey = interpreter.StringStorageMapKey("composite") + + // Store the composite in a storage map, so that the composite's slab + // is referenced by the root of the storage. + + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + compositeStorageMapKey, + actualRootValue, + ) + + reloadActualRootValue = func() interpreter.Value { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, compositeStorageMapKey) + require.NotNil(t, readValue) + + require.IsType(t, &interpreter.CompositeValue{}, readValue) + return readValue.(*interpreter.CompositeValue) + } + + getNestedComposite = func(rootValue interpreter.Value, owner common.Address) *interpreter.CompositeValue { + nestedValue := getNestedValue(t, inter, rootValue, path) + require.IsType(t, &interpreter.CompositeValue{}, nestedValue) + nestedComposite := nestedValue.(*interpreter.CompositeValue) + require.Equal(t, owner, nestedComposite.GetOwner()) + return nestedComposite + } + + return + } + + t.Run("insert", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + r := newRandomValueGenerator( + *smokeTestSeed, + limits, + ) + t.Logf("seed: %d", r.seed) + + actualRootValue, generatedValue, reloadActualRootValue, getNestedComposite := + createValue( + t, + &r, + inter, + // Accept any composite, even empty ones, + // given we're only inserting + func(composite cadence.Composite) bool { + return true + }, + ) + + actualNestedComposite := getNestedComposite(actualRootValue, owner) + + type insert struct { + name string + value cadence.Value + } + + performInsert := func(composite *interpreter.CompositeValue, insert insert) { + + newValue := importValue(t, inter, insert.value) + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + composite.SetMember( + inter, + interpreter.EmptyLocationRange, + insert.name, + newValue, + ) + return struct{}{} + }) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + } + + // We use the generated value twice: once as the expected value, and once as the actual value. + // We first perform mutations on the actual value, and then compare it to the expected value. + // The actual value is stored in an account and reloaded. + // The expected value is temporary (zero address), and is not stored in storage. + // Given that the storage reset destroys the data for the expected value because it is temporary, + // we re-import it each time and perform all operations on it from scratch. + + var inserts []insert + insertSet := map[string]struct{}{} + + for i := 0; i < opCount; i++ { + // Generate a unique name + var name string + for { + name = r.randomUTF8String() + + if actualNestedComposite.GetMember( + inter, + interpreter.EmptyLocationRange, + name, + ) != nil { + continue + } + + if _, ok := insertSet[name]; ok { + continue + } + insertSet[name] = struct{}{} + + break + } + + inserts = append( + inserts, + insert{ + name: name, + value: r.randomStorableValue(inter, 0), + }, + ) + } + + for i, insert := range inserts { + + resetStorage() + + actualRootValue = reloadActualRootValue() + actualNestedComposite = getNestedComposite(actualRootValue, owner) + + performInsert( + actualNestedComposite, + insert, + ) + + // Re-create the expected value from scratch, + // by importing the generated value, and performing all updates on it + // that have been performed on the actual value so far. + + expectedRootValue := importValue(t, inter, generatedValue) + expectedNestedComposite := getNestedComposite(expectedRootValue, common.ZeroAddress) + + for _, insert := range inserts[:i+1] { + + performInsert( + expectedNestedComposite, + insert, + ) + } + utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + } + }) + + t.Run("update", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + r := newRandomValueGenerator( + *smokeTestSeed, + limits, + ) + t.Logf("seed: %d", r.seed) + + actualRootValue, generatedValue, reloadActualRootValue, getNestedComposite := + createValue( + t, + &r, + inter, + // Generate a non-empty composite, + // so we have at least one element to update + func(composite cadence.Composite) bool { + return len(composite.FieldsMappedByName()) > 0 + }, + ) + + actualNestedComposite := getNestedComposite(actualRootValue, owner) + + fieldCount := actualNestedComposite.FieldCount() + require.Greater(t, fieldCount, 0) + + type update struct { + name string + value cadence.Value + } + + performUpdate := func(composite *interpreter.CompositeValue, update update) { + + newValue := importValue(t, inter, update.value) + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + composite.SetMember( + inter, + interpreter.EmptyLocationRange, + update.name, + interpreter.NewUnmeteredSomeValueNonCopying(newValue), + ) + return struct{}{} + }) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // Composite must have same number of elements + require.Equal(t, fieldCount, composite.FieldCount()) + } + + // We use the generated value twice: once as the expected value, and once as the actual value. + // We first perform mutations on the actual value, and then compare it to the expected value. + // The actual value is stored in an account and reloaded. + // The expected value is temporary (zero address), and is not stored in storage. + // Given that the storage reset destroys the data for the expected value because it is temporary, + // we re-import it each time and perform all operations on it from scratch. + + var updates []update + + fieldNames := make([]string, 0, fieldCount) + + actualNestedComposite.ForEachFieldName( + func(name string) (resume bool) { + fieldNames = append(fieldNames, name) + return true + }, + ) + + for i := 0; i < opCount; i++ { + index := r.rand.Intn(fieldCount) + + updates = append( + updates, + update{ + name: fieldNames[index], + value: r.randomStorableValue(inter, 0), + }, + ) + } + + for i, update := range updates { + + resetStorage() + + actualRootValue = reloadActualRootValue() + actualNestedComposite = getNestedComposite(actualRootValue, owner) + + performUpdate( + actualNestedComposite, + update, + ) + + // Re-create the expected value from scratch, + // by importing the generated value, and performing all updates on it + // that have been performed on the actual value so far. + + expectedRootValue := importValue(t, inter, generatedValue) + expectedNestedComposite := getNestedComposite(expectedRootValue, common.ZeroAddress) + + for _, update := range updates[:i+1] { + + performUpdate( + expectedNestedComposite, + update, + ) + } + utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + } + }) + + t.Run("remove", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + r := newRandomValueGenerator( + *smokeTestSeed, + limits, + ) + t.Logf("seed: %d", r.seed) + + actualRootValue, generatedValue, reloadActualRootValue, getNestedComposite := + createValue( + t, + &r, + inter, + func(composite cadence.Composite) bool { + return len(composite.FieldsMappedByName()) >= opCount + }, + ) + + actualNestedComposite := getNestedComposite(actualRootValue, owner) + + fieldCount := actualNestedComposite.FieldCount() + require.GreaterOrEqual(t, fieldCount, opCount) + + performRemove := func(composite *interpreter.CompositeValue, name string) { + + // Atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + + withoutAtreeStorageValidationEnabled(inter, func() struct{} { + composite.RemoveMember( + inter, + interpreter.EmptyLocationRange, + name, + ) + return struct{}{} + }) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + } + + // We use the generated value twice: once as the expected value, and once as the actual value. + // We first perform mutations on the actual value, and then compare it to the expected value. + // The actual value is stored in an account and reloaded. + // The expected value is temporary (zero address), and is not stored in storage. + // Given that the storage reset destroys the data for the expected value because it is temporary, + // we re-import it each time and perform all operations on it from scratch. + + fieldNames := make([]string, 0, fieldCount) + + actualNestedComposite.ForEachFieldName( + func(name string) (resume bool) { + + fieldNames = append(fieldNames, name) + + return true + }, + ) + + var removes []string + removeSet := map[string]struct{}{} + + for i := 0; i < opCount; i++ { + // Find a unique name + var name string + for { + name = fieldNames[r.rand.Intn(fieldCount)] + + if _, ok := removeSet[name]; ok { + continue + } + removeSet[name] = struct{}{} + + break + } + + removes = append(removes, name) + } + + for i, index := range removes { + + resetStorage() + + actualRootValue = reloadActualRootValue() + actualNestedComposite = getNestedComposite(actualRootValue, owner) + + performRemove( + actualNestedComposite, + index, + ) + + // Re-create the expected value from scratch, + // by importing the generated value, and performing all updates on it + // that have been performed on the actual value so far. + + expectedRootValue := importValue(t, inter, generatedValue) + expectedNestedComposite := getNestedComposite(expectedRootValue, common.ZeroAddress) + + for _, index := range removes[:i+1] { + + performRemove( + expectedNestedComposite, + index, + ) + } + utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + } + }) +} + +func findNestedValue( + value cadence.Value, + predicate func(value cadence.Value, path []pathElement) bool, +) []pathElement { + return findNestedRecursive(value, nil, predicate) +} + +func findNestedRecursive( + value cadence.Value, + path []pathElement, + predicate func(value cadence.Value, path []pathElement) bool, +) []pathElement { + if predicate(value, path) { + return path + } + + switch value := value.(type) { + case cadence.Array: + for index, element := range value.Values { + + nestedPath := append(path, arrayPathElement{index}) + + result := findNestedRecursive(element, nestedPath, predicate) + if result != nil { + return result + } + } + + case cadence.Dictionary: + for _, pair := range value.Pairs { + + nestedPath := append(path, dictionaryPathElement{pair.Key}) + + result := findNestedRecursive(pair.Value, nestedPath, predicate) + if result != nil { + return result + } + } + + case cadence.Struct: + for name, field := range value.FieldsMappedByName() { + + nestedPath := append(path, structPathElement{name}) + + result := findNestedRecursive(field, nestedPath, predicate) + if result != nil { + return result + } + } + + case cadence.Optional: + nestedValue := value.Value + if nestedValue == nil { + break + } + + nestedPath := append(path, somePathElement{}) + + result := findNestedRecursive(nestedValue, nestedPath, predicate) + if result != nil { + return result + } + } + + return nil +} + +func getNestedValue( + t *testing.T, + inter *interpreter.Interpreter, + value interpreter.Value, + path []pathElement, +) interpreter.Value { + for i, element := range path { + switch element := element.(type) { + case arrayPathElement: + require.IsType( + t, + &interpreter.ArrayValue{}, + value, + "path: %v", + path[:i], + ) + array := value.(*interpreter.ArrayValue) + + value = array.Get( + inter, + interpreter.EmptyLocationRange, + element.index, + ) + + require.NotNil(t, + value, + "missing value for array element %d (path: %v)", + element.index, + path[:i], + ) + + case dictionaryPathElement: + require.IsType( + t, + &interpreter.DictionaryValue{}, + value, + "path: %v", + path[:i], + ) + dictionary := value.(*interpreter.DictionaryValue) + + key := importValue(t, inter, element.key) + + var found bool + value, found = dictionary.Get( + inter, + interpreter.EmptyLocationRange, + key, + ) + require.True(t, + found, + "missing value for dictionary key %s (path: %v)", + element.key, + path[:i], + ) + require.NotNil(t, + value, + "missing value for dictionary key %s (path: %v)", + element.key, + path[:i], + ) + + case structPathElement: + require.IsType( + t, + &interpreter.CompositeValue{}, + value, + "path: %v", + path[:i], + ) + composite := value.(*interpreter.CompositeValue) + + value = composite.GetMember( + inter, + interpreter.EmptyLocationRange, + element.name, + ) + + require.NotNil(t, + value, + "missing value for composite field %q (path: %v)", + element.name, + path[:i], + ) + + case somePathElement: + require.IsType( + t, + &interpreter.SomeValue{}, + value, + "path: %v", + path[:i], + ) + optional := value.(*interpreter.SomeValue) + + value = optional.InnerValue(inter, interpreter.EmptyLocationRange) + + require.NotNil(t, + value, + "missing value for optional (path: %v)", + path[:i], + ) + + default: + panic(errors.NewUnexpectedError("unsupported path element: %T", element)) + } + } + + return value +} + +type pathElement interface { + isPathElement() +} + +type arrayPathElement struct { + index int +} + +var _ pathElement = arrayPathElement{} + +func (arrayPathElement) isPathElement() {} + +type dictionaryPathElement struct { + key cadence.Value +} + +var _ pathElement = dictionaryPathElement{} + +func (dictionaryPathElement) isPathElement() {} + +type structPathElement struct { + name string +} + +var _ pathElement = structPathElement{} + +func (structPathElement) isPathElement() {} + +type somePathElement struct{} + +var _ pathElement = somePathElement{} + +func (somePathElement) isPathElement() {} + type randomValueLimits struct { containerMaxDepth int containerMaxSize int @@ -2533,6 +4156,9 @@ func (r randomValueGenerator) randomEnumValue(inter *interpreter.Interpreter) ca Kind: common.CompositeKindEnum, Location: location, Members: &sema.StringMemberOrderedMap{}, + Fields: []string{ + sema.EnumRawValueFieldName, + }, } semaEnumType.Members.Set( From 4dde5d0196ff153578cb79fbbbb2b7076db56478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 16 Jan 2025 16:16:03 -0800 Subject: [PATCH 49/80] simplify --- runtime/tests/interpreter/values_test.go | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index adb9c1908..cf5d22ee7 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -2045,11 +2045,7 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { break } } - if !foundSome { - return false - } - - return true + return foundSome }, ) if path != nil { @@ -2474,11 +2470,7 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { break } } - if !foundSome { - return false - } - - return true + return foundSome }, ) if path != nil { @@ -2977,11 +2969,7 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { break } } - if !foundSome { - return false - } - - return true + return foundSome }, ) if path != nil { From c6c8dc350617c26e07f03813d1dc41d7233552ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 17 Jan 2025 09:55:36 -0800 Subject: [PATCH 50/80] fix lint --- runtime/tests/interpreter/values_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index cf5d22ee7..de694a4f5 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -3409,7 +3409,8 @@ func findNestedRecursive( case cadence.Array: for index, element := range value.Values { - nestedPath := append(path, arrayPathElement{index}) + nestedPath := path + nestedPath = append(nestedPath, arrayPathElement{index}) result := findNestedRecursive(element, nestedPath, predicate) if result != nil { @@ -3420,7 +3421,8 @@ func findNestedRecursive( case cadence.Dictionary: for _, pair := range value.Pairs { - nestedPath := append(path, dictionaryPathElement{pair.Key}) + nestedPath := path + nestedPath = append(nestedPath, dictionaryPathElement{pair.Key}) result := findNestedRecursive(pair.Value, nestedPath, predicate) if result != nil { @@ -3431,7 +3433,8 @@ func findNestedRecursive( case cadence.Struct: for name, field := range value.FieldsMappedByName() { - nestedPath := append(path, structPathElement{name}) + nestedPath := path + nestedPath = append(nestedPath, structPathElement{name}) result := findNestedRecursive(field, nestedPath, predicate) if result != nil { @@ -3445,7 +3448,8 @@ func findNestedRecursive( break } - nestedPath := append(path, somePathElement{}) + nestedPath := path + nestedPath = append(nestedPath, somePathElement{}) result := findNestedRecursive(nestedValue, nestedPath, predicate) if result != nil { From e7f9109172397611ec63c8395deff16bc6c4391c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 17 Jan 2025 13:16:04 -0800 Subject: [PATCH 51/80] refactor removal from storage map into function --- runtime/tests/interpreter/values_test.go | 78 +++++++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index de694a4f5..c96c75909 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -257,6 +257,23 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { return readValue.(*interpreter.DictionaryValue) } + removeDictionary := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) { + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ). + RemoveValue( + inter, + storageMapKey, + ) + } + createDictionary := func( t *testing.T, r *randomValueGenerator, @@ -265,6 +282,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { *interpreter.DictionaryValue, cadence.Dictionary, ) { + expectedValue := r.randomDictionaryValue(inter, 0) keyValues := make([]interpreter.Value, 2*len(expectedValue.Pairs)) @@ -552,9 +570,12 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { ) withoutAtreeStorageValidationEnabled(inter, func() struct{} { - inter.Storage(). - GetStorageMap(orgOwner, common.PathDomainStorage.Identifier(), false). - RemoveValue(inter, dictionaryStorageMapKey) + + removeDictionary( + inter, + orgOwner, + dictionaryStorageMapKey, + ) return struct{}{} }) @@ -962,6 +983,23 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { ) } + removeComposite := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) { + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + RemoveValue( + inter, + storageMapKey, + ) + } + readComposite := func( inter *interpreter.Interpreter, owner common.Address, @@ -1168,9 +1206,11 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { ) withoutAtreeStorageValidationEnabled(inter, func() struct{} { - inter.Storage(). - GetStorageMap(orgOwner, common.PathDomainStorage.Identifier(), false). - RemoveValue(inter, compositeStorageMapKey) + removeComposite( + inter, + orgOwner, + compositeStorageMapKey, + ) return struct{}{} }) @@ -1356,6 +1396,23 @@ func TestInterpretRandomArrayOperations(t *testing.T) { ) } + removeArray := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) { + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + RemoveValue( + inter, + storageMapKey, + ) + } + readArray := func( inter *interpreter.Interpreter, owner common.Address, @@ -1641,9 +1698,12 @@ func TestInterpretRandomArrayOperations(t *testing.T) { ) withoutAtreeStorageValidationEnabled(inter, func() struct{} { - inter.Storage(). - GetStorageMap(orgOwner, common.PathDomainStorage.Identifier(), false). - RemoveValue(inter, arrayStorageMapKey) + + removeArray( + inter, + orgOwner, + arrayStorageMapKey, + ) return struct{}{} }) From 99913642e668aa7a50d0ae4ea4f708a397115e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 17 Jan 2025 13:16:53 -0800 Subject: [PATCH 52/80] check iteration after performing operation --- runtime/tests/interpreter/values_test.go | 161 +++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index c96c75909..40a47b10f 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -2167,6 +2167,39 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { return } + checkIteration := func( + t *testing.T, + inter *interpreter.Interpreter, + actualArray *interpreter.ArrayValue, + expectedArray *interpreter.ArrayValue, + ) { + expectedCount := expectedArray.Count() + require.Equal(t, expectedCount, actualArray.Count()) + + var iterations int + + actualArray.Iterate( + inter, + func(element interpreter.Value) (resume bool) { + + expectedElement := expectedArray.Get( + inter, + interpreter.EmptyLocationRange, + iterations, + ) + utils.AssertValuesEqual(t, inter, expectedElement, element) + + iterations += 1 + + return true + }, + false, + interpreter.EmptyLocationRange, + ) + + assert.Equal(t, expectedCount, iterations) + } + t.Run("insert", func(t *testing.T) { t.Parallel() @@ -2274,6 +2307,13 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { ) } utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + + checkIteration( + t, + inter, + actualNestedArray, + expectedNestedArray, + ) } }) @@ -2382,6 +2422,13 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { ) } utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + + checkIteration( + t, + inter, + actualNestedArray, + expectedNestedArray, + ) } }) @@ -2471,6 +2518,13 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { ) } utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + + checkIteration( + t, + inter, + actualNestedArray, + expectedNestedArray, + ) } }) } @@ -2592,6 +2646,39 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { return } + checkIteration := func( + t *testing.T, + inter *interpreter.Interpreter, + actualDictionary *interpreter.DictionaryValue, + expectedDictionary *interpreter.DictionaryValue, + ) { + expectedCount := expectedDictionary.Count() + require.Equal(t, expectedCount, actualDictionary.Count()) + + var iterations int + + actualDictionary.Iterate( + inter, + interpreter.EmptyLocationRange, + func(key, element interpreter.Value) (resume bool) { + + expectedElement, exists := expectedDictionary.Get( + inter, + interpreter.EmptyLocationRange, + key, + ) + require.True(t, exists) + utils.AssertValuesEqual(t, inter, expectedElement, element) + + iterations += 1 + + return true + }, + ) + + assert.Equal(t, expectedCount, iterations) + } + t.Run("insert", func(t *testing.T) { t.Parallel() @@ -2716,6 +2803,13 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { ) } utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + + checkIteration( + t, + inter, + actualNestedDictionary, + expectedNestedDictionary, + ) } }) @@ -2846,6 +2940,13 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { ) } utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + + checkIteration( + t, + inter, + actualNestedDictionary, + expectedNestedDictionary, + ) } }) @@ -2970,6 +3071,13 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { ) } utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + + checkIteration( + t, + inter, + actualNestedDictionary, + expectedNestedDictionary, + ) } }) } @@ -3091,6 +3199,38 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { return } + checkIteration := func( + t *testing.T, + inter *interpreter.Interpreter, + actualComposite *interpreter.CompositeValue, + expectedComposite *interpreter.CompositeValue, + ) { + expectedCount := expectedComposite.FieldCount() + require.Equal(t, expectedCount, actualComposite.FieldCount()) + + var iterations int + + actualComposite.ForEachField( + inter, + func(name string, element interpreter.Value) (resume bool) { + + expectedElement := expectedComposite.GetMember( + inter, + interpreter.EmptyLocationRange, + name, + ) + utils.AssertValuesEqual(t, inter, expectedElement, element) + + iterations += 1 + + return true + }, + interpreter.EmptyLocationRange, + ) + + assert.Equal(t, expectedCount, iterations) + } + t.Run("insert", func(t *testing.T) { t.Parallel() @@ -3212,6 +3352,13 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { ) } utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + + checkIteration( + t, + inter, + actualNestedComposite, + expectedNestedComposite, + ) } }) @@ -3331,6 +3478,13 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { ) } utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + + checkIteration( + t, + inter, + actualNestedComposite, + expectedNestedComposite, + ) } }) @@ -3445,6 +3599,13 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { ) } utils.AssertValuesEqual(t, inter, expectedRootValue, actualRootValue) + + checkIteration( + t, + inter, + actualNestedComposite, + expectedNestedComposite, + ) } }) } From 0bd57859f317e5426dcdccc187ff1bae69f79953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 17 Jan 2025 13:17:25 -0800 Subject: [PATCH 53/80] implement nested value finding for interpreter.Value --- runtime/tests/interpreter/values_test.go | 164 +++++++++++++++++++++-- 1 file changed, 154 insertions(+), 10 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 40a47b10f..571db890a 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -2086,7 +2086,7 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { for { generatedValue = r.randomArrayValue(inter, 0) - path = findNestedValue( + path = findNestedCadenceValue( generatedValue, func(value cadence.Value, path []pathElement) bool { array, ok := value.(cadence.Array) @@ -2565,7 +2565,7 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { for { generatedValue = r.randomDictionaryValue(inter, 0) - path = findNestedValue( + path = findNestedCadenceValue( generatedValue, func(value cadence.Value, path []pathElement) bool { dictionary, ok := value.(cadence.Dictionary) @@ -3118,7 +3118,7 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { for { generatedValue = r.randomStructValue(inter, 0) - path = findNestedValue( + path = findNestedCadenceValue( generatedValue, func(value cadence.Value, path []pathElement) bool { composite, ok := value.(cadence.Struct) @@ -3610,14 +3610,14 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { }) } -func findNestedValue( +func findNestedCadenceValue( value cadence.Value, predicate func(value cadence.Value, path []pathElement) bool, ) []pathElement { - return findNestedRecursive(value, nil, predicate) + return findNestedCadenceRecursive(value, nil, predicate) } -func findNestedRecursive( +func findNestedCadenceRecursive( value cadence.Value, path []pathElement, predicate func(value cadence.Value, path []pathElement) bool, @@ -3633,7 +3633,7 @@ func findNestedRecursive( nestedPath := path nestedPath = append(nestedPath, arrayPathElement{index}) - result := findNestedRecursive(element, nestedPath, predicate) + result := findNestedCadenceRecursive(element, nestedPath, predicate) if result != nil { return result } @@ -3645,7 +3645,7 @@ func findNestedRecursive( nestedPath := path nestedPath = append(nestedPath, dictionaryPathElement{pair.Key}) - result := findNestedRecursive(pair.Value, nestedPath, predicate) + result := findNestedCadenceRecursive(pair.Value, nestedPath, predicate) if result != nil { return result } @@ -3657,7 +3657,7 @@ func findNestedRecursive( nestedPath := path nestedPath = append(nestedPath, structPathElement{name}) - result := findNestedRecursive(field, nestedPath, predicate) + result := findNestedCadenceRecursive(field, nestedPath, predicate) if result != nil { return result } @@ -3672,7 +3672,151 @@ func findNestedRecursive( nestedPath := path nestedPath = append(nestedPath, somePathElement{}) - result := findNestedRecursive(nestedValue, nestedPath, predicate) + result := findNestedCadenceRecursive(nestedValue, nestedPath, predicate) + if result != nil { + return result + } + } + + return nil +} + +func findNestedValue( + value interpreter.Value, + inter *interpreter.Interpreter, + predicate func(value interpreter.Value, path []pathElement) bool, +) []pathElement { + return findNestedRecursive( + value, + inter, + nil, + predicate, + ) +} + +func findNestedRecursive( + value interpreter.Value, + inter *interpreter.Interpreter, + path []pathElement, + predicate func(value interpreter.Value, path []pathElement) bool, +) (result []pathElement) { + if predicate(value, path) { + return path + } + + switch value := value.(type) { + case *interpreter.ArrayValue: + + var index int + + value.Iterate( + inter, + func(element interpreter.Value) (resume bool) { + + nestedPath := path + nestedPath = append(nestedPath, arrayPathElement{index}) + + result = findNestedRecursive( + element, + inter, + nestedPath, + predicate, + ) + if result != nil { + return false + } + + index += 1 + + // continue iteration + return true + }, + false, + interpreter.EmptyLocationRange, + ) + + if result != nil { + return result + } + + case *interpreter.DictionaryValue: + + value.Iterate( + inter, + interpreter.EmptyLocationRange, + func(key, element interpreter.Value) (resume bool) { + + cadenceKey, err := runtime.ExportValue(key, inter, interpreter.EmptyLocationRange) + if err != nil { + panic(errors.NewUnexpectedErrorFromCause(err)) + } + + nestedPath := path + nestedPath = append(nestedPath, dictionaryPathElement{cadenceKey}) + + result = findNestedRecursive( + element, + inter, + nestedPath, + predicate, + ) + if result != nil { + return false + } + + // continue iteration + return true + }, + ) + + if result != nil { + return result + } + + case *interpreter.CompositeValue: + + value.ForEachFieldName(func(fieldName string) (resume bool) { + + nestedPath := path + nestedPath = append(nestedPath, structPathElement{fieldName}) + + field := value.GetMember( + inter, + interpreter.EmptyLocationRange, + fieldName, + ) + + result = findNestedRecursive( + field, + inter, + nestedPath, + predicate, + ) + if result != nil { + return false + } + + // continue iteration + return true + }) + + if result != nil { + return result + } + + case *interpreter.SomeValue: + + nestedPath := path + nestedPath = append(nestedPath, somePathElement{}) + + innerValue := value.InnerValue(inter, interpreter.EmptyLocationRange) + + result = findNestedRecursive( + innerValue, + inter, + nestedPath, + predicate, + ) if result != nil { return result } From 43b54ff3aeb72114ba1710ea307d2411ef8856ce Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Fri, 17 Jan 2025 15:22:49 -0600 Subject: [PATCH 54/80] Do not always cache SomeStorable in SomeValue Currently, SomeValue always caches SomeStorable after SomeValue.Storable() is called for the first time. Calling inner value's Storable() can create register in storage (e.g. large string is stored in its owner register), and we want to reuse cached storable in such cases. However, SomeStorable must not be cached if SomeValue wraps a container type. We should always call container.Storable() to trigger container inlining. This PR updates SomeValue.Storable() to always call Storable() for wrapped container. --- runtime/interpreter/value.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index dc4f58b19..c45df5c5c 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -263,6 +263,13 @@ type ValueIterator interface { Next(interpreter *Interpreter, locationRange LocationRange) Value } +// containerValue is an interface for values using atree containers +// (atree.Array or atree.OrderedMap) under the hood. +type containerValue interface { + Value + isContainerValue() +} + func safeAdd(a, b int, locationRange LocationRange) int { // INT32-C if (b > 0) && (a > (goMaxInt - b)) { @@ -2290,9 +2297,12 @@ var _ ValueIndexableValue = &ArrayValue{} var _ MemberAccessibleValue = &ArrayValue{} var _ ReferenceTrackedResourceKindedValue = &ArrayValue{} var _ IterableValue = &ArrayValue{} +var _ containerValue = &ArrayValue{} func (*ArrayValue) isValue() {} +func (*ArrayValue) isContainerValue() {} + func (v *ArrayValue) Accept(interpreter *Interpreter, visitor Visitor, locationRange LocationRange) { descend := visitor.VisitArrayValue(interpreter, v) if !descend { @@ -17450,9 +17460,12 @@ var _ ReferenceTrackedResourceKindedValue = &CompositeValue{} var _ ContractValue = &CompositeValue{} var _ atree.Value = &CompositeValue{} var _ atree.WrapperValue = &CompositeValue{} +var _ containerValue = &CompositeValue{} func (*CompositeValue) isValue() {} +func (*CompositeValue) isContainerValue() {} + func (v *CompositeValue) Accept(interpreter *Interpreter, visitor Visitor, locationRange LocationRange) { descend := visitor.VisitCompositeValue(interpreter, v) if !descend { @@ -19439,9 +19452,12 @@ var _ EquatableValue = &DictionaryValue{} var _ ValueIndexableValue = &DictionaryValue{} var _ MemberAccessibleValue = &DictionaryValue{} var _ ReferenceTrackedResourceKindedValue = &DictionaryValue{} +var _ containerValue = &DictionaryValue{} func (*DictionaryValue) isValue() {} +func (*DictionaryValue) isContainerValue() {} + func (v *DictionaryValue) Accept(interpreter *Interpreter, visitor Visitor, locationRange LocationRange) { descend := visitor.VisitDictionaryValue(interpreter, v) if !descend { @@ -21182,9 +21198,18 @@ func (v *SomeValue) Storable( // The above applies to both immutable non-SomeValue (such as StringValue), // and mutable non-SomeValue (such as ArrayValue). - if v.valueStorable == nil { + // NOTE: + // - If SomeValue's inner value is a value with atree.Array or atree.OrderedMap, + // we MUST NOT cache SomeStorable because we need to call nonSomeValue.Storable() + // to trigger container inlining or un-inlining. + // - Otherwise, we need to cache SomeStorable because nonSomeValue.Storable() can + // create registers in storage, such as large string. + + nonSomeValue, nestedLevels := v.nonSomeValue() + + _, isContainerValue := nonSomeValue.(containerValue) - nonSomeValue, nestedLevels := v.nonSomeValue() + if v.valueStorable == nil || isContainerValue { someStorableEncodedPrefixSize := getSomeStorableEncodedPrefixSize(nestedLevels) From 615dcf7d344e5b536ec2dcc1c7909339a28534be Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:17:20 -0600 Subject: [PATCH 55/80] Bump atree version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 24ce45b8e..1fdda74ab 100644 --- a/go.mod +++ b/go.mod @@ -63,4 +63,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20250115020903-b5671d3430c6 +replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20250117221137-acdb0e04ab01 diff --git a/go.sum b/go.sum index cb47f5398..a2e0088b3 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onflow/atree-internal v0.8.2-0.20250115020903-b5671d3430c6 h1:9ptAgDT4hq6iy6ZCWvMR/aHeXVfE3vK+lGt1W3j32l8= -github.com/onflow/atree-internal v0.8.2-0.20250115020903-b5671d3430c6/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/atree-internal v0.8.2-0.20250117221137-acdb0e04ab01 h1:UmPbGWshC0HvIrqwTiTlVZc8BqkxgFMyC8uHcDquMOE= +github.com/onflow/atree-internal v0.8.2-0.20250117221137-acdb0e04ab01/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= From e237598bd4c50184d62b0bd883086980f01fad68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 17 Jan 2025 16:23:22 -0800 Subject: [PATCH 56/80] refactor nested container tests --- runtime/tests/interpreter/values_test.go | 523 ++++++++++++++++------- 1 file changed, 361 insertions(+), 162 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 571db890a..8c776da55 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -2065,6 +2065,59 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { const opCount = 5 + const arrayStorageMapKey = interpreter.StringStorageMapKey("array") + + writeArray := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + array *interpreter.ArrayValue, + ) { + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + storageMapKey, + array, + ) + } + + readArray := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) *interpreter.ArrayValue { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, storageMapKey) + require.NotNil(t, readValue) + + require.IsType(t, &interpreter.ArrayValue{}, readValue) + return readValue.(*interpreter.ArrayValue) + } + + getNestedArray := func( + inter *interpreter.Interpreter, + rootValue interpreter.Value, + owner common.Address, + path []pathElement, + ) *interpreter.ArrayValue { + nestedValue := getNestedValue(t, inter, rootValue, path) + require.IsType(t, &interpreter.ArrayValue{}, nestedValue) + nestedArray := nestedValue.(*interpreter.ArrayValue) + require.Equal(t, owner, nestedArray.GetOwner()) + return nestedArray + } + createValue := func( t *testing.T, r *randomValueGenerator, @@ -2073,8 +2126,7 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { ) ( actualRootValue interpreter.Value, generatedValue cadence.Value, - reloadActualRootValue func() interpreter.Value, - getNestedArray func(rootValue interpreter.Value, owner common.Address) *interpreter.ArrayValue, + path []pathElement, ) { // It does not matter what the root value is, @@ -2082,7 +2134,6 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { // which it is nested inside an optional, // and it satisfies the given predicate. - var path []pathElement for { generatedValue = r.randomArrayValue(inter, 0) @@ -2124,45 +2175,15 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { true, ) - const arrayStorageMapKey = interpreter.StringStorageMapKey("array") - // Store the array in a storage map, so that the array's slab // is referenced by the root of the storage. - inter.Storage(). - GetStorageMap( - owner, - common.PathDomainStorage.Identifier(), - true, - ). - WriteValue( - inter, - arrayStorageMapKey, - actualRootValue, - ) - - reloadActualRootValue = func() interpreter.Value { - storageMap := inter.Storage().GetStorageMap( - owner, - common.PathDomainStorage.Identifier(), - false, - ) - require.NotNil(t, storageMap) - - readValue := storageMap.ReadValue(inter, arrayStorageMapKey) - require.NotNil(t, readValue) - - require.IsType(t, &interpreter.ArrayValue{}, readValue) - return readValue.(*interpreter.ArrayValue) - } - - getNestedArray = func(rootValue interpreter.Value, owner common.Address) *interpreter.ArrayValue { - nestedValue := getNestedValue(t, inter, rootValue, path) - require.IsType(t, &interpreter.ArrayValue{}, nestedValue) - nestedArray := nestedValue.(*interpreter.ArrayValue) - require.Equal(t, owner, nestedArray.GetOwner()) - return nestedArray - } + writeArray( + inter, + owner, + arrayStorageMapKey, + actualRootValue.(*interpreter.ArrayValue), + ) return } @@ -2211,7 +2232,7 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { ) t.Logf("seed: %d", r.seed) - actualRootValue, generatedValue, reloadActualRootValue, getNestedArray := + actualRootValue, generatedValue, path := createValue( t, &r, @@ -2223,7 +2244,12 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { }, ) - actualNestedArray := getNestedArray(actualRootValue, owner) + actualNestedArray := getNestedArray( + inter, + actualRootValue, + owner, + path, + ) type insert struct { index int @@ -2284,8 +2310,13 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { resetStorage() - actualRootValue = reloadActualRootValue() - actualNestedArray = getNestedArray(actualRootValue, owner) + actualRootValue = readArray(inter, owner, arrayStorageMapKey) + actualNestedArray = getNestedArray( + inter, + actualRootValue, + owner, + path, + ) performInsert( actualNestedArray, @@ -2297,7 +2328,12 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { // that have been performed on the actual value so far. expectedRootValue := importValue(t, inter, generatedValue) - expectedNestedArray := getNestedArray(expectedRootValue, common.ZeroAddress) + expectedNestedArray := getNestedArray( + inter, + expectedRootValue, + common.ZeroAddress, + path, + ) for _, insert := range inserts[:i+1] { @@ -2328,7 +2364,7 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { ) t.Logf("seed: %d", r.seed) - actualRootValue, generatedValue, reloadActualRootValue, getNestedArray := + actualRootValue, generatedValue, path := createValue( t, &r, @@ -2340,7 +2376,12 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { }, ) - actualNestedArray := getNestedArray(actualRootValue, owner) + actualNestedArray := getNestedArray( + inter, + actualRootValue, + owner, + path, + ) elementCount := actualNestedArray.Count() require.Greater(t, elementCount, 0) @@ -2399,8 +2440,13 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { resetStorage() - actualRootValue = reloadActualRootValue() - actualNestedArray = getNestedArray(actualRootValue, owner) + actualRootValue = readArray(inter, owner, arrayStorageMapKey) + actualNestedArray = getNestedArray( + inter, + actualRootValue, + owner, + path, + ) performUpdate( actualNestedArray, @@ -2412,7 +2458,12 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { // that have been performed on the actual value so far. expectedRootValue := importValue(t, inter, generatedValue) - expectedNestedArray := getNestedArray(expectedRootValue, common.ZeroAddress) + expectedNestedArray := getNestedArray( + inter, + expectedRootValue, + common.ZeroAddress, + path, + ) for _, update := range updates[:i+1] { @@ -2443,7 +2494,7 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { ) t.Logf("seed: %d", r.seed) - actualRootValue, generatedValue, reloadActualRootValue, getNestedArray := + actualRootValue, generatedValue, path := createValue( t, &r, @@ -2453,7 +2504,13 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { }, ) - actualNestedArray := getNestedArray(actualRootValue, owner) + actualNestedArray := getNestedArray( + inter, + actualRootValue, + owner, + path, + ) + elementCount := actualNestedArray.Count() require.GreaterOrEqual(t, elementCount, opCount) @@ -2495,8 +2552,13 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { resetStorage() - actualRootValue = reloadActualRootValue() - actualNestedArray = getNestedArray(actualRootValue, owner) + actualRootValue = readArray(inter, owner, arrayStorageMapKey) + actualNestedArray = getNestedArray( + inter, + actualRootValue, + owner, + path, + ) performRemove( actualNestedArray, @@ -2508,7 +2570,12 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { // that have been performed on the actual value so far. expectedRootValue := importValue(t, inter, generatedValue) - expectedNestedArray := getNestedArray(expectedRootValue, common.ZeroAddress) + expectedNestedArray := getNestedArray( + inter, + expectedRootValue, + common.ZeroAddress, + path, + ) for _, index := range removes[:i+1] { @@ -2544,6 +2611,59 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { const opCount = 5 + const dictionaryStorageMapKey = interpreter.StringStorageMapKey("dictionary") + + writeDictionary := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + dictionary *interpreter.DictionaryValue, + ) { + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + storageMapKey, + dictionary, + ) + } + + readDictionary := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) *interpreter.DictionaryValue { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, storageMapKey) + require.NotNil(t, readValue) + + require.IsType(t, &interpreter.DictionaryValue{}, readValue) + return readValue.(*interpreter.DictionaryValue) + } + + getNestedDictionary := func( + inter *interpreter.Interpreter, + rootValue interpreter.Value, + owner common.Address, + path []pathElement, + ) *interpreter.DictionaryValue { + nestedValue := getNestedValue(t, inter, rootValue, path) + require.IsType(t, &interpreter.DictionaryValue{}, nestedValue) + nestedDictionary := nestedValue.(*interpreter.DictionaryValue) + require.Equal(t, owner, nestedDictionary.GetOwner()) + return nestedDictionary + } + createValue := func( t *testing.T, r *randomValueGenerator, @@ -2552,8 +2672,7 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { ) ( actualRootValue interpreter.Value, generatedValue cadence.Value, - reloadActualRootValue func() interpreter.Value, - getNestedDictionary func(rootValue interpreter.Value, owner common.Address) *interpreter.DictionaryValue, + path []pathElement, ) { // It does not matter what the root value is, @@ -2561,7 +2680,6 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { // which it is nested inside an optional, // and it satisfies the given predicate. - var path []pathElement for { generatedValue = r.randomDictionaryValue(inter, 0) @@ -2603,45 +2721,15 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { true, ) - const dictionaryStorageMapKey = interpreter.StringStorageMapKey("dictionary") - // Store the dictionary in a storage map, so that the dictionary's slab // is referenced by the root of the storage. - inter.Storage(). - GetStorageMap( - owner, - common.PathDomainStorage.Identifier(), - true, - ). - WriteValue( - inter, - dictionaryStorageMapKey, - actualRootValue, - ) - - reloadActualRootValue = func() interpreter.Value { - storageMap := inter.Storage().GetStorageMap( - owner, - common.PathDomainStorage.Identifier(), - false, - ) - require.NotNil(t, storageMap) - - readValue := storageMap.ReadValue(inter, dictionaryStorageMapKey) - require.NotNil(t, readValue) - - require.IsType(t, &interpreter.DictionaryValue{}, readValue) - return readValue.(*interpreter.DictionaryValue) - } - - getNestedDictionary = func(rootValue interpreter.Value, owner common.Address) *interpreter.DictionaryValue { - nestedValue := getNestedValue(t, inter, rootValue, path) - require.IsType(t, &interpreter.DictionaryValue{}, nestedValue) - nestedDictionary := nestedValue.(*interpreter.DictionaryValue) - require.Equal(t, owner, nestedDictionary.GetOwner()) - return nestedDictionary - } + writeDictionary( + inter, + owner, + dictionaryStorageMapKey, + actualRootValue.(*interpreter.DictionaryValue), + ) return } @@ -2690,7 +2778,7 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { ) t.Logf("seed: %d", r.seed) - actualRootValue, generatedValue, reloadActualRootValue, getNestedDictionary := + actualRootValue, generatedValue, path := createValue( t, &r, @@ -2702,7 +2790,12 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { }, ) - actualNestedDictionary := getNestedDictionary(actualRootValue, owner) + actualNestedDictionary := getNestedDictionary( + inter, + actualRootValue, + owner, + path, + ) type insert struct { key cadence.Value @@ -2780,8 +2873,13 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { resetStorage() - actualRootValue = reloadActualRootValue() - actualNestedDictionary = getNestedDictionary(actualRootValue, owner) + actualRootValue = readDictionary(inter, owner, dictionaryStorageMapKey) + actualNestedDictionary = getNestedDictionary( + inter, + actualRootValue, + owner, + path, + ) performInsert( actualNestedDictionary, @@ -2793,7 +2891,12 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { // that have been performed on the actual value so far. expectedRootValue := importValue(t, inter, generatedValue) - expectedNestedDictionary := getNestedDictionary(expectedRootValue, common.ZeroAddress) + expectedNestedDictionary := getNestedDictionary( + inter, + expectedRootValue, + common.ZeroAddress, + path, + ) for _, insert := range inserts[:i+1] { @@ -2824,7 +2927,7 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { ) t.Logf("seed: %d", r.seed) - actualRootValue, generatedValue, reloadActualRootValue, getNestedDictionary := + actualRootValue, generatedValue, path := createValue( t, &r, @@ -2836,7 +2939,12 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { }, ) - actualNestedDictionary := getNestedDictionary(actualRootValue, owner) + actualNestedDictionary := getNestedDictionary( + inter, + actualRootValue, + owner, + path, + ) elementCount := actualNestedDictionary.Count() require.Greater(t, elementCount, 0) @@ -2917,8 +3025,13 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { resetStorage() - actualRootValue = reloadActualRootValue() - actualNestedDictionary = getNestedDictionary(actualRootValue, owner) + actualRootValue = readDictionary(inter, owner, dictionaryStorageMapKey) + actualNestedDictionary = getNestedDictionary( + inter, + actualRootValue, + owner, + path, + ) performUpdate( actualNestedDictionary, @@ -2930,7 +3043,12 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { // that have been performed on the actual value so far. expectedRootValue := importValue(t, inter, generatedValue) - expectedNestedDictionary := getNestedDictionary(expectedRootValue, common.ZeroAddress) + expectedNestedDictionary := getNestedDictionary( + inter, + expectedRootValue, + common.ZeroAddress, + path, + ) for _, update := range updates[:i+1] { @@ -2961,7 +3079,7 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { ) t.Logf("seed: %d", r.seed) - actualRootValue, generatedValue, reloadActualRootValue, getNestedDictionary := + actualRootValue, generatedValue, path := createValue( t, &r, @@ -2971,7 +3089,12 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { }, ) - actualNestedDictionary := getNestedDictionary(actualRootValue, owner) + actualNestedDictionary := getNestedDictionary( + inter, + actualRootValue, + owner, + path, + ) elementCount := actualNestedDictionary.Count() require.GreaterOrEqual(t, elementCount, opCount) @@ -3048,8 +3171,13 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { resetStorage() - actualRootValue = reloadActualRootValue() - actualNestedDictionary = getNestedDictionary(actualRootValue, owner) + actualRootValue = readDictionary(inter, owner, dictionaryStorageMapKey) + actualNestedDictionary = getNestedDictionary( + inter, + actualRootValue, + owner, + path, + ) performRemove( actualNestedDictionary, @@ -3061,7 +3189,12 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { // that have been performed on the actual value so far. expectedRootValue := importValue(t, inter, generatedValue) - expectedNestedDictionary := getNestedDictionary(expectedRootValue, common.ZeroAddress) + expectedNestedDictionary := getNestedDictionary( + inter, + expectedRootValue, + common.ZeroAddress, + path, + ) for _, index := range removes[:i+1] { @@ -3097,6 +3230,59 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { const opCount = 5 + const compositeStorageMapKey = interpreter.StringStorageMapKey("composite") + + writeComposite := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + composite *interpreter.CompositeValue, + ) { + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + storageMapKey, + composite, + ) + } + + readComposite := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) *interpreter.CompositeValue { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, storageMapKey) + require.NotNil(t, readValue) + + require.IsType(t, &interpreter.CompositeValue{}, readValue) + return readValue.(*interpreter.CompositeValue) + } + + getNestedComposite := func( + inter *interpreter.Interpreter, + rootValue interpreter.Value, + owner common.Address, + path []pathElement, + ) *interpreter.CompositeValue { + nestedValue := getNestedValue(t, inter, rootValue, path) + require.IsType(t, &interpreter.CompositeValue{}, nestedValue) + nestedComposite := nestedValue.(*interpreter.CompositeValue) + require.Equal(t, owner, nestedComposite.GetOwner()) + return nestedComposite + } + createValue := func( t *testing.T, r *randomValueGenerator, @@ -3105,8 +3291,7 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { ) ( actualRootValue interpreter.Value, generatedValue cadence.Value, - reloadActualRootValue func() interpreter.Value, - getNestedComposite func(rootValue interpreter.Value, owner common.Address) *interpreter.CompositeValue, + path []pathElement, ) { // It does not matter what the root value is, @@ -3114,7 +3299,6 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { // which it is nested inside an optional, // and it satisfies the given predicate. - var path []pathElement for { generatedValue = r.randomStructValue(inter, 0) @@ -3156,45 +3340,15 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { true, ) - const compositeStorageMapKey = interpreter.StringStorageMapKey("composite") - // Store the composite in a storage map, so that the composite's slab // is referenced by the root of the storage. - inter.Storage(). - GetStorageMap( - owner, - common.PathDomainStorage.Identifier(), - true, - ). - WriteValue( - inter, - compositeStorageMapKey, - actualRootValue, - ) - - reloadActualRootValue = func() interpreter.Value { - storageMap := inter.Storage().GetStorageMap( - owner, - common.PathDomainStorage.Identifier(), - false, - ) - require.NotNil(t, storageMap) - - readValue := storageMap.ReadValue(inter, compositeStorageMapKey) - require.NotNil(t, readValue) - - require.IsType(t, &interpreter.CompositeValue{}, readValue) - return readValue.(*interpreter.CompositeValue) - } - - getNestedComposite = func(rootValue interpreter.Value, owner common.Address) *interpreter.CompositeValue { - nestedValue := getNestedValue(t, inter, rootValue, path) - require.IsType(t, &interpreter.CompositeValue{}, nestedValue) - nestedComposite := nestedValue.(*interpreter.CompositeValue) - require.Equal(t, owner, nestedComposite.GetOwner()) - return nestedComposite - } + writeComposite( + inter, + owner, + compositeStorageMapKey, + actualRootValue.(*interpreter.CompositeValue), + ) return } @@ -3242,7 +3396,7 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { ) t.Logf("seed: %d", r.seed) - actualRootValue, generatedValue, reloadActualRootValue, getNestedComposite := + actualRootValue, generatedValue, path := createValue( t, &r, @@ -3254,7 +3408,12 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { }, ) - actualNestedComposite := getNestedComposite(actualRootValue, owner) + actualNestedComposite := getNestedComposite( + inter, + actualRootValue, + owner, + path, + ) type insert struct { name string @@ -3329,8 +3488,13 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { resetStorage() - actualRootValue = reloadActualRootValue() - actualNestedComposite = getNestedComposite(actualRootValue, owner) + actualRootValue = readComposite(inter, owner, compositeStorageMapKey) + actualNestedComposite = getNestedComposite( + inter, + actualRootValue, + owner, + path, + ) performInsert( actualNestedComposite, @@ -3342,7 +3506,12 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { // that have been performed on the actual value so far. expectedRootValue := importValue(t, inter, generatedValue) - expectedNestedComposite := getNestedComposite(expectedRootValue, common.ZeroAddress) + expectedNestedComposite := getNestedComposite( + inter, + expectedRootValue, + common.ZeroAddress, + path, + ) for _, insert := range inserts[:i+1] { @@ -3373,7 +3542,7 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { ) t.Logf("seed: %d", r.seed) - actualRootValue, generatedValue, reloadActualRootValue, getNestedComposite := + actualRootValue, generatedValue, path := createValue( t, &r, @@ -3385,7 +3554,12 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { }, ) - actualNestedComposite := getNestedComposite(actualRootValue, owner) + actualNestedComposite := getNestedComposite( + inter, + actualRootValue, + owner, + path, + ) fieldCount := actualNestedComposite.FieldCount() require.Greater(t, fieldCount, 0) @@ -3455,8 +3629,13 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { resetStorage() - actualRootValue = reloadActualRootValue() - actualNestedComposite = getNestedComposite(actualRootValue, owner) + actualRootValue = readComposite(inter, owner, compositeStorageMapKey) + actualNestedComposite = getNestedComposite( + inter, + actualRootValue, + owner, + path, + ) performUpdate( actualNestedComposite, @@ -3468,7 +3647,12 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { // that have been performed on the actual value so far. expectedRootValue := importValue(t, inter, generatedValue) - expectedNestedComposite := getNestedComposite(expectedRootValue, common.ZeroAddress) + expectedNestedComposite := getNestedComposite( + inter, + expectedRootValue, + common.ZeroAddress, + path, + ) for _, update := range updates[:i+1] { @@ -3499,7 +3683,7 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { ) t.Logf("seed: %d", r.seed) - actualRootValue, generatedValue, reloadActualRootValue, getNestedComposite := + actualRootValue, generatedValue, path := createValue( t, &r, @@ -3509,7 +3693,12 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { }, ) - actualNestedComposite := getNestedComposite(actualRootValue, owner) + actualNestedComposite := getNestedComposite( + inter, + actualRootValue, + owner, + path, + ) fieldCount := actualNestedComposite.FieldCount() require.GreaterOrEqual(t, fieldCount, opCount) @@ -3576,8 +3765,13 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { resetStorage() - actualRootValue = reloadActualRootValue() - actualNestedComposite = getNestedComposite(actualRootValue, owner) + actualRootValue = readComposite(inter, owner, compositeStorageMapKey) + actualNestedComposite = getNestedComposite( + inter, + actualRootValue, + owner, + path, + ) performRemove( actualNestedComposite, @@ -3589,7 +3783,12 @@ func TestInterpretRandomNestedCompositeOperations(t *testing.T) { // that have been performed on the actual value so far. expectedRootValue := importValue(t, inter, generatedValue) - expectedNestedComposite := getNestedComposite(expectedRootValue, common.ZeroAddress) + expectedNestedComposite := getNestedComposite( + inter, + expectedRootValue, + common.ZeroAddress, + path, + ) for _, index := range removes[:i+1] { From b35fa772dd856b74146fa16acf581fe1dfd506f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 17 Jan 2025 17:22:36 -0800 Subject: [PATCH 57/80] test read-only loaded iteration when child is some non-inlined value --- runtime/tests/interpreter/values_test.go | 429 +++++++++++++++++++++++ 1 file changed, 429 insertions(+) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 8c776da55..892eb835a 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -5143,3 +5143,432 @@ func TestCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { require.NoError(t, storage.CheckHealth()) } + +// TestInterpretIterateReadOnlyLoadedWithSomeValueChildren tests https://github.com/onflow/atree-internal/pull/7 +func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { + + owner := common.Address{'A'} + + const storageMapKey = interpreter.StringStorageMapKey("value") + + writeValue := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + value interpreter.Value, + ) { + value = value.Transfer( + inter, + interpreter.EmptyLocationRange, + atree.Address(owner), + false, + nil, + nil, + // TODO: is has no parent container = true correct? + true, + ) + + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + storageMapKey, + value, + ) + } + + readValue := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) interpreter.Value { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, storageMapKey) + require.NotNil(t, readValue) + + return readValue + } + + t.Run("dictionary", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + var cadenceRootPairs []cadence.KeyValuePair + + const expectedRootCount = 10 + const expectedInnerCount = 100 + + for i := 0; i < expectedRootCount; i++ { + var cadenceInnerPairs []cadence.KeyValuePair + + for j := 0; j < expectedInnerCount; j++ { + cadenceInnerPairs = append( + cadenceInnerPairs, + cadence.KeyValuePair{ + Key: cadence.NewInt(j), + Value: cadence.String(strings.Repeat("cadence", 1000)), + }, + ) + } + + cadenceRootPairs = append( + cadenceRootPairs, + cadence.KeyValuePair{ + Key: cadence.NewInt(i), + Value: cadence.NewOptional( + cadence.NewDictionary(cadenceInnerPairs), + ), + }, + ) + } + + cadenceRootDictionary := cadence.NewDictionary(cadenceRootPairs) + + rootDictionary := importValue(t, inter, cadenceRootDictionary).(*interpreter.DictionaryValue) + + // Check that the inner dictionaries are not inlined. + // If the test fails here, adjust the value generation code above + // to ensure that the inner dictionaries are not inlined. + + rootDictionary.Iterate( + inter, + interpreter.EmptyLocationRange, + func(key, value interpreter.Value) (resume bool) { + + require.IsType(t, &interpreter.SomeValue{}, value) + someValue := value.(*interpreter.SomeValue) + + innerValue := someValue.InnerValue(inter, interpreter.EmptyLocationRange) + + require.IsType(t, &interpreter.DictionaryValue{}, innerValue) + innerDictionary := innerValue.(*interpreter.DictionaryValue) + require.False(t, innerDictionary.Inlined()) + + // continue iteration + return true + }, + ) + + writeValue( + inter, + owner, + storageMapKey, + rootDictionary, + ) + + resetStorage() + + rootDictionary = readValue( + inter, + owner, + storageMapKey, + ).(*interpreter.DictionaryValue) + + var iterations int + rootDictionary.IterateReadOnlyLoaded( + inter, + interpreter.EmptyLocationRange, + func(_, _ interpreter.Value) (resume bool) { + iterations += 1 + + // continue iteration + return true + }, + ) + + require.Equal(t, 0, iterations) + + iterations = 0 + rootDictionary.Iterate( + inter, + interpreter.EmptyLocationRange, + func(_, _ interpreter.Value) (resume bool) { + iterations += 1 + + // continue iteration + return true + }, + ) + + require.Equal(t, expectedRootCount, iterations) + }) + + t.Run("array", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + var cadenceRootElements []cadence.Value + + const expectedRootCount = 10 + const expectedInnerCount = 100 + + for i := 0; i < expectedRootCount; i++ { + var cadenceInnerElements []cadence.Value + + for j := 0; j < expectedInnerCount; j++ { + cadenceInnerElements = append( + cadenceInnerElements, + cadence.String(strings.Repeat("cadence", 1000)), + ) + } + + cadenceRootElements = append( + cadenceRootElements, + cadence.NewOptional( + cadence.NewArray(cadenceInnerElements), + ), + ) + } + + cadenceRootArray := cadence.NewArray(cadenceRootElements) + + rootArray := importValue(t, inter, cadenceRootArray).(*interpreter.ArrayValue) + + // Check that the inner arrays are not inlined. + // If the test fails here, adjust the value generation code above + // to ensure that the inner arrays are not inlined. + + rootArray.Iterate( + inter, + func(value interpreter.Value) (resume bool) { + + require.IsType(t, &interpreter.SomeValue{}, value) + someValue := value.(*interpreter.SomeValue) + + innerValue := someValue.InnerValue(inter, interpreter.EmptyLocationRange) + + require.IsType(t, &interpreter.ArrayValue{}, innerValue) + innerArray := innerValue.(*interpreter.ArrayValue) + require.False(t, innerArray.Inlined()) + + // continue iteration + return true + }, + false, + interpreter.EmptyLocationRange, + ) + + writeValue( + inter, + owner, + storageMapKey, + rootArray, + ) + + resetStorage() + + rootArray = readValue( + inter, + owner, + storageMapKey, + ).(*interpreter.ArrayValue) + + var iterations int + rootArray.IterateReadOnlyLoaded( + inter, + func(_ interpreter.Value) (resume bool) { + iterations += 1 + + // continue iteration + return true + }, + interpreter.EmptyLocationRange, + ) + + require.Equal(t, 0, iterations) + + iterations = 0 + + rootArray.Iterate( + inter, + func(_ interpreter.Value) (resume bool) { + iterations += 1 + + // continue iteration + return true + }, + false, + interpreter.EmptyLocationRange, + ) + + require.Equal(t, expectedRootCount, iterations) + }) + + t.Run("composite", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + newCadenceType := func(fieldCount int) *cadence.StructType { + typeIdentifier := fmt.Sprintf("S%d", fieldCount) + + typeLocation := common.AddressLocation{ + Address: owner, + Name: typeIdentifier, + } + + fieldNames := make([]string, 0, fieldCount) + for i := 0; i < fieldCount; i++ { + fieldName := fmt.Sprintf("field%d", i) + fieldNames = append(fieldNames, fieldName) + } + + cadenceFields := make([]cadence.Field, 0, fieldCount) + for _, fieldName := range fieldNames { + cadenceFields = append( + cadenceFields, + cadence.Field{ + Identifier: fieldName, + Type: cadence.AnyStructType, + }, + ) + } + + structType := cadence.NewStructType( + typeLocation, + typeIdentifier, + cadenceFields, + nil, + ) + + compositeType := &sema.CompositeType{ + Location: typeLocation, + Identifier: typeIdentifier, + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + Fields: fieldNames, + } + + for _, fieldName := range fieldNames { + compositeType.Members.Set( + fieldName, + sema.NewUnmeteredPublicConstantFieldMember( + compositeType, + fieldName, + sema.AnyStructType, + "", + ), + ) + } + + // Add the type to the elaboration, to short-circuit the type-lookup. + inter.Program.Elaboration.SetCompositeType( + compositeType.ID(), + compositeType, + ) + + return structType + } + + var cadenceRootValues []cadence.Value + + const expectedRootCount = 10 + const expectedInnerCount = 100 + + rootStructType := newCadenceType(expectedRootCount) + innerStructType := newCadenceType(expectedInnerCount) + + for i := 0; i < expectedRootCount; i++ { + var cadenceInnerValues []cadence.Value + + for j := 0; j < expectedInnerCount; j++ { + cadenceInnerValues = append( + cadenceInnerValues, + cadence.String(strings.Repeat("cadence", 1000)), + ) + } + + cadenceRootValues = append( + cadenceRootValues, + cadence.NewOptional( + cadence.NewStruct(cadenceInnerValues). + WithType(innerStructType), + ), + ) + } + + cadenceRootStruct := cadence.NewStruct(cadenceRootValues). + WithType(rootStructType) + + rootStruct := importValue(t, inter, cadenceRootStruct).(*interpreter.CompositeValue) + + // Check that the inner structs are not inlined. + // If the test fails here, adjust the value generation code above + // to ensure that the inner structs are not inlined. + + rootStruct.ForEachField( + inter, + func(fieldName string, value interpreter.Value) (resume bool) { + + require.IsType(t, &interpreter.SomeValue{}, value) + someValue := value.(*interpreter.SomeValue) + + innerValue := someValue.InnerValue(inter, interpreter.EmptyLocationRange) + + require.IsType(t, &interpreter.CompositeValue{}, innerValue) + innerStruct := innerValue.(*interpreter.CompositeValue) + require.False(t, innerStruct.Inlined()) + + // continue iteration + return true + }, + interpreter.EmptyLocationRange, + ) + + writeValue( + inter, + owner, + storageMapKey, + rootStruct, + ) + + resetStorage() + + rootStruct = readValue( + inter, + owner, + storageMapKey, + ).(*interpreter.CompositeValue) + + var iterations int + rootStruct.ForEachReadOnlyLoadedField( + inter, + func(_ string, _ interpreter.Value) (resume bool) { + iterations += 1 + + // continue iteration + return true + }, + interpreter.EmptyLocationRange, + ) + + require.Equal(t, 0, iterations) + + iterations = 0 + rootStruct.ForEachField( + inter, + func(_ string, _ interpreter.Value) (resume bool) { + iterations += 1 + + // continue iteration + return true + }, + interpreter.EmptyLocationRange, + ) + + require.Equal(t, expectedRootCount, iterations) + }) + +} From cf1cd1dab6000360d1d6a8bc3c127b49ae6aee02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 10:43:03 -0800 Subject: [PATCH 58/80] Update runtime/tests/interpreter/values_test.go --- runtime/tests/interpreter/values_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 892eb835a..31bc1aba8 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -5146,6 +5146,7 @@ func TestCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { // TestInterpretIterateReadOnlyLoadedWithSomeValueChildren tests https://github.com/onflow/atree-internal/pull/7 func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { + t.Parallel() owner := common.Address{'A'} From 991a03c8186789fcdd813fb90df4eff2febded93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 11:33:35 -0800 Subject: [PATCH 59/80] test inlining and uninlining of dictionary nested in some value --- runtime/interpreter/value.go | 4 + runtime/tests/interpreter/values_test.go | 152 +++++++++++++++++++++++ tools/storage-explorer/go.mod | 4 +- tools/storage-explorer/go.sum | 2 + 4 files changed, 160 insertions(+), 2 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index c45df5c5c..ff7d6195c 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -20807,6 +20807,10 @@ func (v *DictionaryValue) Inlined() bool { return v.dictionary.Inlined() } +func (v *DictionaryValue) IsInlined() bool { + return v.dictionary.Inlined() +} + // OptionalValue type OptionalValue interface { diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 31bc1aba8..013dfeb2c 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -23,6 +23,7 @@ import ( "fmt" "math" "math/rand" + "strconv" "strings" "testing" "time" @@ -5573,3 +5574,154 @@ func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { }) } + +func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) { + t.Parallel() + + owner := common.Address{'A'} + + const storageMapKey = interpreter.StringStorageMapKey("value") + + writeValue := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + value interpreter.Value, + ) { + value = value.Transfer( + inter, + interpreter.EmptyLocationRange, + atree.Address(owner), + false, + nil, + nil, + // TODO: is has no parent container = true correct? + true, + ) + + inter.Storage(). + GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + true, + ). + WriteValue( + inter, + storageMapKey, + value, + ) + } + + readValue := func( + inter *interpreter.Interpreter, + owner common.Address, + storageMapKey interpreter.StorageMapKey, + ) interpreter.Value { + storageMap := inter.Storage().GetStorageMap( + owner, + common.PathDomainStorage.Identifier(), + false, + ) + require.NotNil(t, storageMap) + + readValue := storageMap.ReadValue(inter, storageMapKey) + require.NotNil(t, readValue) + + return readValue + } + + t.Run("dictionary (inlined -> uninlined -> inlined)", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + // Start with an empty dictionary + + cadenceChildDictionary := cadence.NewDictionary(nil) + + cadenceRootOptionalValue := cadence.NewOptional(cadenceChildDictionary) + + rootSomeValue := importValue(t, inter, cadenceRootOptionalValue).(*interpreter.SomeValue) + + writeValue( + inter, + owner, + storageMapKey, + rootSomeValue, + ) + + resetStorage() + + rootSomeValue = readValue( + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + // Fill the dictionary until it becomes uninlined + + childDictionary := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.DictionaryValue) + + require.True(t, childDictionary.IsInlined()) + + for i := 0; childDictionary.IsInlined(); i++ { + childDictionary.Insert( + inter, + interpreter.EmptyLocationRange, + interpreter.NewUnmeteredStringValue(strconv.Itoa(i)), + interpreter.NewUnmeteredIntValueFromInt64(int64(i)), + ) + } + + require.False(t, childDictionary.IsInlined()) + + uninlinedCount := childDictionary.Count() + + // Verify the contents of the dictionary + + childDictionary = rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.DictionaryValue) + + verify := func(count int) { + for i := 0; i < count; i++ { + key := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) + value, exists := childDictionary.Get(inter, interpreter.EmptyLocationRange, key) + require.True(t, exists) + expectedValue := interpreter.NewUnmeteredIntValueFromInt64(int64(i)) + utils.AssertValuesEqual(t, inter, expectedValue, value) + } + } + + verify(uninlinedCount) + + // Remove the last element to make the dictionary inlined again + + inlinedCount := uninlinedCount - 1 + + childDictionary.Remove( + inter, + interpreter.EmptyLocationRange, + interpreter.NewUnmeteredStringValue(strconv.Itoa(inlinedCount)), + ) + + require.True(t, childDictionary.IsInlined()) + + // Verify the contents of the dictionary again + + verify(inlinedCount) + + // Add a new element to make the dictionary uninlined again + + childDictionary.Insert( + inter, + interpreter.EmptyLocationRange, + interpreter.NewUnmeteredStringValue(strconv.Itoa(inlinedCount)), + interpreter.NewUnmeteredIntValueFromInt64(int64(inlinedCount)), + ) + + require.False(t, childDictionary.IsInlined()) + + // Verify the contents of the dictionary again + + verify(uninlinedCount) + }) +} diff --git a/tools/storage-explorer/go.mod b/tools/storage-explorer/go.mod index ea57ef6fa..c301cdbb9 100644 --- a/tools/storage-explorer/go.mod +++ b/tools/storage-explorer/go.mod @@ -147,7 +147,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.15.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect @@ -159,7 +159,7 @@ require ( github.com/vmihailenco/msgpack/v4 v4.3.11 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect diff --git a/tools/storage-explorer/go.sum b/tools/storage-explorer/go.sum index 56e4f510f..078fb2409 100644 --- a/tools/storage-explorer/go.sum +++ b/tools/storage-explorer/go.sum @@ -2135,6 +2135,7 @@ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= @@ -2203,6 +2204,7 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= From aaf07b45fe985b53f234b126c9766d66621aaf07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 11:44:48 -0800 Subject: [PATCH 60/80] use existing Inlined method --- runtime/interpreter/value.go | 4 ---- runtime/tests/interpreter/values_test.go | 13 +++++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index ff7d6195c..c45df5c5c 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -20807,10 +20807,6 @@ func (v *DictionaryValue) Inlined() bool { return v.dictionary.Inlined() } -func (v *DictionaryValue) IsInlined() bool { - return v.dictionary.Inlined() -} - // OptionalValue type OptionalValue interface { diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 013dfeb2c..b209fa7c7 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -5662,9 +5662,9 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) childDictionary := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.DictionaryValue) - require.True(t, childDictionary.IsInlined()) + require.True(t, childDictionary.Inlined()) - for i := 0; childDictionary.IsInlined(); i++ { + for i := 0; childDictionary.Inlined(); i++ { childDictionary.Insert( inter, interpreter.EmptyLocationRange, @@ -5673,7 +5673,7 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) ) } - require.False(t, childDictionary.IsInlined()) + require.False(t, childDictionary.Inlined()) uninlinedCount := childDictionary.Count() @@ -5697,13 +5697,14 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) inlinedCount := uninlinedCount - 1 - childDictionary.Remove( + existingValue := childDictionary.Remove( inter, interpreter.EmptyLocationRange, interpreter.NewUnmeteredStringValue(strconv.Itoa(inlinedCount)), ) + require.IsType(t, &interpreter.SomeValue{}, existingValue) - require.True(t, childDictionary.IsInlined()) + require.True(t, childDictionary.Inlined()) // Verify the contents of the dictionary again @@ -5718,7 +5719,7 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) interpreter.NewUnmeteredIntValueFromInt64(int64(inlinedCount)), ) - require.False(t, childDictionary.IsInlined()) + require.False(t, childDictionary.Inlined()) // Verify the contents of the dictionary again From a53476d0da33153fc811eb5ae8ee7478bc94091d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 11:45:39 -0800 Subject: [PATCH 61/80] test inlining and uninlining of array nested in some value --- runtime/tests/interpreter/values_test.go | 118 +++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index b209fa7c7..907cc7e5c 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -5724,5 +5724,123 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) // Verify the contents of the dictionary again verify(uninlinedCount) + + // Remove all elements + + for i := 0; i < uninlinedCount; i++ { + existingValue := childDictionary.Remove( + inter, + interpreter.EmptyLocationRange, + interpreter.NewUnmeteredStringValue(strconv.Itoa(i)), + ) + require.IsType(t, &interpreter.SomeValue{}, existingValue) + } + + require.Equal(t, 0, childDictionary.Count()) + require.True(t, childDictionary.Inlined()) + }) + + t.Run("array (inlined -> uninlined -> inlined)", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + // Start with an empty array + + cadenceChildArray := cadence.NewArray(nil) + + cadenceRootOptionalValue := cadence.NewOptional(cadenceChildArray) + + rootSomeValue := importValue(t, inter, cadenceRootOptionalValue).(*interpreter.SomeValue) + + writeValue( + inter, + owner, + storageMapKey, + rootSomeValue, + ) + + resetStorage() + + rootSomeValue = readValue( + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + // Fill the array until it becomes uninlined + + childArray := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.ArrayValue) + + require.True(t, childArray.Inlined()) + + for i := 0; childArray.Inlined(); i++ { + childArray.Append( + inter, + interpreter.EmptyLocationRange, + interpreter.NewUnmeteredStringValue(strconv.Itoa(i)), + ) + } + + require.False(t, childArray.Inlined()) + + uninlinedCount := childArray.Count() + + // Verify the contents of the array + + childArray = rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.ArrayValue) + + verify := func(count int) { + for i := 0; i < count; i++ { + value := childArray.Get(inter, interpreter.EmptyLocationRange, i) + expectedValue := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) + utils.AssertValuesEqual(t, inter, expectedValue, value) + } + } + + verify(uninlinedCount) + + // Remove the last element to make the array inlined again + + inlinedCount := uninlinedCount - 1 + + childArray.Remove( + inter, + interpreter.EmptyLocationRange, + inlinedCount, + ) + + require.True(t, childArray.Inlined()) + + // Verify the contents of the array again + + verify(inlinedCount) + + // Add a new element to make the array uninlined again + + childArray.Append( + inter, + interpreter.EmptyLocationRange, + interpreter.NewUnmeteredStringValue(strconv.Itoa(inlinedCount)), + ) + + require.False(t, childArray.Inlined()) + + // Verify the contents of the array again + + verify(uninlinedCount) + + // Remove all elements + + for i := uninlinedCount - 1; i >= 0; i-- { + childArray.Remove( + inter, + interpreter.EmptyLocationRange, + i, + ) + } + + require.Equal(t, 0, childArray.Count()) + require.True(t, childArray.Inlined()) }) } From 3ce173be0c46c643429f74e154551a2ac846076e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 12:13:15 -0800 Subject: [PATCH 62/80] test starting with unlined state --- runtime/tests/interpreter/values_test.go | 208 +++++++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 907cc7e5c..97bff6e41 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -5740,6 +5740,116 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) require.True(t, childDictionary.Inlined()) }) + t.Run("dictionary (uninlined -> inlined -> uninlined)", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + // Start with a large dictionary which will get uninlined + + var cadenceChildPairs []cadence.KeyValuePair + + for i := 0; i < 1000; i++ { + cadenceChildPairs = append( + cadenceChildPairs, + cadence.KeyValuePair{ + Key: cadence.String(strconv.Itoa(i)), + Value: cadence.NewInt(i), + }, + ) + } + + cadenceChildDictionary := cadence.NewDictionary(cadenceChildPairs) + + cadenceRootOptionalValue := cadence.NewOptional(cadenceChildDictionary) + + rootSomeValue := importValue(t, inter, cadenceRootOptionalValue).(*interpreter.SomeValue) + + writeValue( + inter, + owner, + storageMapKey, + rootSomeValue, + ) + + resetStorage() + + rootSomeValue = readValue( + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + childDictionary := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.DictionaryValue) + + // Check that the inner dictionary is not inlined. + // If the test fails here, adjust the value generation code above + // to ensure that the inner dictionary is not inlined. + + require.False(t, childDictionary.Inlined()) + + // Verify the contents of the dictionary + + inlinedCount := childDictionary.Count() + + // Verify the contents of the dictionary + + verify := func(count int) { + for i := 0; i < count; i++ { + key := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) + value, exists := childDictionary.Get(inter, interpreter.EmptyLocationRange, key) + require.True(t, exists) + expectedValue := interpreter.NewUnmeteredIntValueFromInt64(int64(i)) + utils.AssertValuesEqual(t, inter, expectedValue, value) + } + } + + verify(inlinedCount) + + // Remove elements until the dictionary is inlined + + for i := inlinedCount - 1; !childDictionary.Inlined(); i-- { + existingValue := childDictionary.Remove( + inter, + interpreter.EmptyLocationRange, + interpreter.NewUnmeteredStringValue(strconv.Itoa(i)), + ) + + require.IsType(t, &interpreter.SomeValue{}, existingValue) + existingSomeValue := existingValue.(*interpreter.SomeValue) + + existingInnerValue := existingSomeValue.InnerValue(inter, interpreter.EmptyLocationRange) + expectedValue := interpreter.NewUnmeteredIntValueFromInt64(int64(i)) + utils.AssertValuesEqual(t, inter, expectedValue, existingInnerValue) + + } + + inlinedCount = childDictionary.Count() + + require.True(t, childDictionary.Inlined()) + + // Verify the contents of the dictionary again + + verify(inlinedCount) + + // Add element to make the dictionary uninlined again + + childDictionary.Insert( + inter, + interpreter.EmptyLocationRange, + interpreter.NewUnmeteredStringValue(strconv.Itoa(inlinedCount)), + interpreter.NewUnmeteredIntValueFromInt64(int64(inlinedCount)), + ) + + require.False(t, childDictionary.Inlined()) + + // Verify the contents of the dictionary again + + uninlinedCount := inlinedCount + 1 + + verify(uninlinedCount) + }) + t.Run("array (inlined -> uninlined -> inlined)", func(t *testing.T) { t.Parallel() @@ -5843,4 +5953,102 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) require.Equal(t, 0, childArray.Count()) require.True(t, childArray.Inlined()) }) + + t.Run("array (uninlined -> inlined -> uninlined)", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + // Start with a large array which will get uninlined + + var cadenceChildElements []cadence.Value + + for i := 0; i < 1000; i++ { + cadenceChildElements = append( + cadenceChildElements, + cadence.String(strconv.Itoa(i)), + ) + } + + cadenceChildArray := cadence.NewArray(cadenceChildElements) + + cadenceRootOptionalValue := cadence.NewOptional(cadenceChildArray) + + rootSomeValue := importValue(t, inter, cadenceRootOptionalValue).(*interpreter.SomeValue) + + writeValue( + inter, + owner, + storageMapKey, + rootSomeValue, + ) + + resetStorage() + + rootSomeValue = readValue( + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + childArray := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.ArrayValue) + + // Check that the inner array is not inlined. + // If the test fails here, adjust the value generation code above + // to ensure that the inner array is not inlined. + + require.False(t, childArray.Inlined()) + + // Verify the contents of the array + + inlinedCount := childArray.Count() + + // Verify the contents of the array + + verify := func(count int) { + for i := 0; i < count; i++ { + value := childArray.Get(inter, interpreter.EmptyLocationRange, i) + expectedValue := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) + utils.AssertValuesEqual(t, inter, expectedValue, value) + } + } + + verify(inlinedCount) + + // Remove elements until the array is inlined + + for i := inlinedCount - 1; !childArray.Inlined(); i-- { + existingValue := childArray.Remove( + inter, + interpreter.EmptyLocationRange, + i, + ) + expectedValue := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) + utils.AssertValuesEqual(t, inter, expectedValue, existingValue) + } + + inlinedCount = childArray.Count() + + require.True(t, childArray.Inlined()) + + // Verify the contents of the array again + + verify(inlinedCount) + + // Add element to make the array uninlined again + + childArray.Append( + inter, + interpreter.EmptyLocationRange, + interpreter.NewUnmeteredStringValue(strconv.Itoa(inlinedCount)), + ) + + require.False(t, childArray.Inlined()) + + // Verify the contents of the array again + + uninlinedCount := inlinedCount + 1 + + verify(uninlinedCount) + }) } From 6f8d222025df58ccdad9d8944f2d133e2938398b Mon Sep 17 00:00:00 2001 From: Joe Sprowes Date: Tue, 21 Jan 2025 14:36:58 -0600 Subject: [PATCH 63/80] use NegativeShiftError consistently --- runtime/interpreter/value.go | 58 +++++++++++++-- runtime/tests/interpreter/bitwise_test.go | 86 +++++++++++++++++++++++ 2 files changed, 139 insertions(+), 5 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index c45df5c5c..635d67470 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -4733,7 +4733,7 @@ func (v IntValue) BitwiseLeftShift(interpreter *Interpreter, other IntegerValue, } if o.BigInt.Sign() < 0 { - panic(UnderflowError{ + panic(NegativeShiftError{ LocationRange: locationRange, }) } @@ -4766,7 +4766,7 @@ func (v IntValue) BitwiseRightShift(interpreter *Interpreter, other IntegerValue } if o.BigInt.Sign() < 0 { - panic(UnderflowError{ + panic(NegativeShiftError{ LocationRange: locationRange, }) } @@ -5403,6 +5403,12 @@ func (v Int8Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerValue }) } + if o < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + valueGetter := func() int8 { return int8(v << o) } @@ -5421,6 +5427,12 @@ func (v Int8Value) BitwiseRightShift(interpreter *Interpreter, other IntegerValu }) } + if o < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + valueGetter := func() int8 { return int8(v >> o) } @@ -6044,6 +6056,12 @@ func (v Int16Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerValu }) } + if o < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + valueGetter := func() int16 { return int16(v << o) } @@ -6062,6 +6080,12 @@ func (v Int16Value) BitwiseRightShift(interpreter *Interpreter, other IntegerVal }) } + if o < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + valueGetter := func() int16 { return int16(v >> o) } @@ -6687,6 +6711,12 @@ func (v Int32Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerValu }) } + if o < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + valueGetter := func() int32 { return int32(v << o) } @@ -6705,6 +6735,12 @@ func (v Int32Value) BitwiseRightShift(interpreter *Interpreter, other IntegerVal }) } + if o < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + valueGetter := func() int32 { return int32(v >> o) } @@ -7322,6 +7358,12 @@ func (v Int64Value) BitwiseLeftShift(interpreter *Interpreter, other IntegerValu }) } + if o < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + valueGetter := func() int64 { return int64(v << o) } @@ -7340,6 +7382,12 @@ func (v Int64Value) BitwiseRightShift(interpreter *Interpreter, other IntegerVal }) } + if o < 0 { + panic(NegativeShiftError{ + LocationRange: locationRange, + }) + } + valueGetter := func() int64 { return int64(v >> o) } @@ -9450,7 +9498,7 @@ func (v UIntValue) BitwiseLeftShift(interpreter *Interpreter, other IntegerValue } if o.BigInt.Sign() < 0 { - panic(UnderflowError{ + panic(NegativeShiftError{ LocationRange: locationRange, }) } @@ -9484,7 +9532,7 @@ func (v UIntValue) BitwiseRightShift(interpreter *Interpreter, other IntegerValu } if o.BigInt.Sign() < 0 { - panic(UnderflowError{ + panic(NegativeShiftError{ LocationRange: locationRange, }) } @@ -18894,7 +18942,7 @@ func (v *CompositeValue) getBaseValue( return NewEphemeralReferenceValue(interpreter, functionAuthorization, v.base, baseType, locationRange) } -func (v *CompositeValue) setBaseValue(interpreter *Interpreter, base *CompositeValue) { +func (v *CompositeValue) setBaseValue(_ *Interpreter, base *CompositeValue) { v.base = base } diff --git a/runtime/tests/interpreter/bitwise_test.go b/runtime/tests/interpreter/bitwise_test.go index e5aae3ea7..a099d3c77 100644 --- a/runtime/tests/interpreter/bitwise_test.go +++ b/runtime/tests/interpreter/bitwise_test.go @@ -348,6 +348,7 @@ func TestInterpretBitwiseLeftShift8(t *testing.T) { inter.Globals.Get("c").GetValue(inter), ) }) + t.Run("Int8 << -3", func(t *testing.T) { inter := parseCheckAndInterpret(t, @@ -365,6 +366,91 @@ func TestInterpretBitwiseLeftShift8(t *testing.T) { require.ErrorAs(t, err, &shiftErr) }) + t.Run("Int16 << -3", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + fun test() { + let a: Int16 = 0x7f + let b: Int16 = -3 + let c = a << b + } + `) + _, err := inter.Invoke("test") + RequireError(t, err) + + var shiftErr interpreter.NegativeShiftError + require.ErrorAs(t, err, &shiftErr) + }) + + t.Run("Int32 << -3", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + fun test() { + let a: Int32 = 0x7f + let b: Int32 = -3 + let c = a << b + } + `) + _, err := inter.Invoke("test") + RequireError(t, err) + + var shiftErr interpreter.NegativeShiftError + require.ErrorAs(t, err, &shiftErr) + }) + + t.Run("Int64 << -3", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + fun test() { + let a: Int64 = 0x7f + let b: Int64 = -3 + let c = a << b + } + `) + _, err := inter.Invoke("test") + RequireError(t, err) + + var shiftErr interpreter.NegativeShiftError + require.ErrorAs(t, err, &shiftErr) + }) + + t.Run("Int128 << -3", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + fun test() { + let a: Int128 = 0x7f + let b: Int128 = -3 + let c = a << b + } + `) + _, err := inter.Invoke("test") + RequireError(t, err) + + var shiftErr interpreter.NegativeShiftError + require.ErrorAs(t, err, &shiftErr) + }) + + t.Run("Int256 << -3", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + fun test() { + let a: Int256 = 0x7f + let b: Int256 = -3 + let c = a << b + } + `) + _, err := inter.Invoke("test") + RequireError(t, err) + + var shiftErr interpreter.NegativeShiftError + require.ErrorAs(t, err, &shiftErr) + }) + t.Run("UInt8 << 9", func(t *testing.T) { inter := parseCheckAndInterpret(t, From ff17065fe9486c6c67fa07f4d2347a2c96e2d3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 13:20:23 -0800 Subject: [PATCH 64/80] test composite values --- runtime/tests/interpreter/values_test.go | 307 ++++++++++++++++++++++- 1 file changed, 305 insertions(+), 2 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 97bff6e41..ed3fe63fc 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -5684,7 +5684,11 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) verify := func(count int) { for i := 0; i < count; i++ { key := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) - value, exists := childDictionary.Get(inter, interpreter.EmptyLocationRange, key) + value, exists := childDictionary.Get( + inter, + interpreter.EmptyLocationRange, + key, + ) require.True(t, exists) expectedValue := interpreter.NewUnmeteredIntValueFromInt64(int64(i)) utils.AssertValuesEqual(t, inter, expectedValue, value) @@ -5797,7 +5801,11 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) verify := func(count int) { for i := 0; i < count; i++ { key := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) - value, exists := childDictionary.Get(inter, interpreter.EmptyLocationRange, key) + value, exists := childDictionary.Get( + inter, + interpreter.EmptyLocationRange, + key, + ) require.True(t, exists) expectedValue := interpreter.NewUnmeteredIntValueFromInt64(int64(i)) utils.AssertValuesEqual(t, inter, expectedValue, value) @@ -6051,4 +6059,299 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) verify(uninlinedCount) }) + + t.Run("composite (inlined -> uninlined -> inlined)", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + // Start with an empty composite + + const qualifiedIdentifier = "Test" + location := common.AddressLocation{ + Address: owner, + Name: qualifiedIdentifier, + } + + cadenceStructType := cadence.NewStructType( + location, + qualifiedIdentifier, + nil, + nil, + ) + + semaStructType := &sema.CompositeType{ + Location: location, + Identifier: qualifiedIdentifier, + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + // Add the type to the elaboration, to short-circuit the type-lookup. + inter.Program.Elaboration.SetCompositeType( + semaStructType.ID(), + semaStructType, + ) + + cadenceChildComposite := cadence.NewStruct(nil).WithType(cadenceStructType) + + cadenceRootOptionalValue := cadence.NewOptional(cadenceChildComposite) + + rootSomeValue := importValue(t, inter, cadenceRootOptionalValue).(*interpreter.SomeValue) + + writeValue( + inter, + owner, + storageMapKey, + rootSomeValue, + ) + + resetStorage() + + rootSomeValue = readValue( + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + // Fill the composite until it becomes uninlined + + childComposite := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.CompositeValue) + + require.True(t, childComposite.Inlined()) + + for i := 0; childComposite.Inlined(); i++ { + childComposite.SetMember( + inter, + interpreter.EmptyLocationRange, + strconv.Itoa(i), + interpreter.NewUnmeteredIntValueFromInt64(int64(i)), + ) + } + + require.False(t, childComposite.Inlined()) + + uninlinedCount := childComposite.FieldCount() + + // Verify the contents of the composite + + childComposite = rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.CompositeValue) + + verify := func(count int) { + for i := 0; i < count; i++ { + value := childComposite.GetMember( + inter, + interpreter.EmptyLocationRange, + strconv.Itoa(i), + ) + expectedValue := interpreter.NewUnmeteredIntValueFromInt64(int64(i)) + utils.AssertValuesEqual(t, inter, expectedValue, value) + } + } + + verify(uninlinedCount) + + // Remove the last element to make the composite inlined again + + inlinedCount := uninlinedCount - 1 + + childComposite.RemoveMember( + inter, + interpreter.EmptyLocationRange, + strconv.Itoa(inlinedCount), + ) + + require.True(t, childComposite.Inlined()) + + // Verify the contents of the composite again + + verify(inlinedCount) + + // Add a new element to make the composite uninlined again + + childComposite.SetMember( + inter, + interpreter.EmptyLocationRange, + strconv.Itoa(inlinedCount), + interpreter.NewUnmeteredIntValueFromInt64(int64(inlinedCount)), + ) + + require.False(t, childComposite.Inlined()) + + // Verify the contents of the composite again + + verify(uninlinedCount) + + // Remove all elements + + for i := 0; i < uninlinedCount; i++ { + childComposite.RemoveMember( + inter, + interpreter.EmptyLocationRange, + strconv.Itoa(i), + ) + } + + require.Equal(t, 0, childComposite.FieldCount()) + require.True(t, childComposite.Inlined()) + }) + + t.Run("composite (uninlined -> inlined -> uninlined)", func(t *testing.T) { + t.Parallel() + + inter, resetStorage := newRandomValueTestInterpreter(t) + + // Start with a large composite which will get uninlined + + const qualifiedIdentifier = "Test" + location := common.AddressLocation{ + Address: owner, + Name: qualifiedIdentifier, + } + + const fieldCount = 1000 + + fields := make([]cadence.Field, fieldCount) + for i := 0; i < fieldCount; i++ { + fields[i] = cadence.Field{ + Identifier: strconv.Itoa(i), + Type: cadence.IntType, + } + } + + cadenceStructType := cadence.NewStructType( + location, + qualifiedIdentifier, + fields, + nil, + ) + + semaStructType := &sema.CompositeType{ + Location: location, + Identifier: qualifiedIdentifier, + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + // Add the type to the elaboration, to short-circuit the type-lookup. + inter.Program.Elaboration.SetCompositeType( + semaStructType.ID(), + semaStructType, + ) + fieldNames := make([]string, fieldCount) + + for i := 0; i < fieldCount; i++ { + fieldName := fields[0].Identifier + semaStructType.Members.Set( + fieldName, + sema.NewUnmeteredPublicConstantFieldMember( + semaStructType, + fieldName, + sema.IntType, + "", + ), + ) + fieldNames[i] = fieldName + } + semaStructType.Fields = fieldNames + + var cadenceChildElements []cadence.Value + + for i := 0; i < fieldCount; i++ { + cadenceChildElements = append( + cadenceChildElements, + cadence.NewInt(i), + ) + + } + + cadenceChildComposite := cadence.NewStruct(cadenceChildElements). + WithType(cadenceStructType) + + cadenceRootOptionalValue := cadence.NewOptional(cadenceChildComposite) + + rootSomeValue := importValue(t, inter, cadenceRootOptionalValue).(*interpreter.SomeValue) + + writeValue( + inter, + owner, + storageMapKey, + rootSomeValue, + ) + + resetStorage() + + rootSomeValue = readValue( + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + childComposite := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.CompositeValue) + + // Check that the inner composite is not inlined. + // If the test fails here, adjust the value generation code above + // to ensure that the inner composite is not inlined. + + require.False(t, childComposite.Inlined()) + + // Verify the contents of the composite + + inlinedCount := childComposite.FieldCount() + + // Verify the contents of the composite + + verify := func(count int) { + for i := 0; i < count; i++ { + value := childComposite.GetMember( + inter, + interpreter.EmptyLocationRange, + strconv.Itoa(i), + ) + expectedValue := interpreter.NewUnmeteredIntValueFromInt64(int64(i)) + utils.AssertValuesEqual(t, inter, expectedValue, value) + } + } + + verify(inlinedCount) + + // Remove elements until the composite is inlined + + for i := inlinedCount - 1; !childComposite.Inlined(); i-- { + existingValue := childComposite.RemoveMember( + inter, + interpreter.EmptyLocationRange, + strconv.Itoa(i), + ) + + expectedValue := interpreter.NewUnmeteredIntValueFromInt64(int64(i)) + utils.AssertValuesEqual(t, inter, expectedValue, existingValue) + + } + + inlinedCount = childComposite.FieldCount() + + require.True(t, childComposite.Inlined()) + + // Verify the contents of the composite again + + verify(inlinedCount) + + // Add element to make the composite uninlined again + + childComposite.SetMember( + inter, + interpreter.EmptyLocationRange, + strconv.Itoa(inlinedCount), + interpreter.NewUnmeteredIntValueFromInt64(int64(inlinedCount)), + ) + + require.False(t, childComposite.Inlined()) + + // Verify the contents of the composite again + + uninlinedCount := inlinedCount + 1 + + verify(uninlinedCount) + }) } From a1a3d279c71750d7dd6afc4d4f21b7d1f9e91577 Mon Sep 17 00:00:00 2001 From: Joe Sprowes Date: Tue, 21 Jan 2025 15:37:18 -0600 Subject: [PATCH 65/80] move negative shift tests into their own test function --- runtime/tests/interpreter/bitwise_test.go | 190 +++++++++++----------- 1 file changed, 96 insertions(+), 94 deletions(-) diff --git a/runtime/tests/interpreter/bitwise_test.go b/runtime/tests/interpreter/bitwise_test.go index a099d3c77..8843d497b 100644 --- a/runtime/tests/interpreter/bitwise_test.go +++ b/runtime/tests/interpreter/bitwise_test.go @@ -255,100 +255,7 @@ func TestInterpretBitwiseRightShift(t *testing.T) { } } -func TestInterpretBitwiseLeftShift8(t *testing.T) { - - t.Parallel() - - t.Run("Int8 << 9 (zero result)", func(t *testing.T) { - - inter := parseCheckAndInterpret(t, - ` - let a: Int8 = 0x7f - let b: Int8 = 9 - let c = a << b - `, - ) - - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredInt8Value(0), - inter.Globals.Get("c").GetValue(inter), - ) - }) - - t.Run("Int8 << 1 (positive to positive)", func(t *testing.T) { - - inter := parseCheckAndInterpret(t, - ` - let a: Int8 = 5 - let b: Int8 = 1 - let c = a << b - `, - ) - - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredInt8Value(10), - inter.Globals.Get("c").GetValue(inter), - ) - }) - - t.Run("Int8 << 1 (negative to negative)", func(t *testing.T) { - - inter := parseCheckAndInterpret(t, - ` - let a: Int8 = -5 // 0b1111_1011 - let b: Int8 = 1 - let c = a << b // 0b1111_0110 --> -10 - `, - ) - - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredInt8Value(-10), - inter.Globals.Get("c").GetValue(inter), - ) - }) - - t.Run("Int8 << 1 (positive to negative)", func(t *testing.T) { - - inter := parseCheckAndInterpret(t, - ` - let a: Int8 = 5 // 0b0000_0101 - let b: Int8 = 7 - let c = a << b // 0b1000_0000 --> -128 - `, - ) - - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredInt8Value(-128), - inter.Globals.Get("c").GetValue(inter), - ) - }) - - t.Run("Int8 << 1 (negative to positive)", func(t *testing.T) { - - inter := parseCheckAndInterpret(t, - ` - let a: Int8 = -5 // 0b1111_1011 - let b: Int8 = 5 - let c = a << b // 0b0110_0000 --> 96 - `, - ) - - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredInt8Value(0x60), // or 96 - inter.Globals.Get("c").GetValue(inter), - ) - }) - +func TestInterpretBitwiseNegativeShift(t *testing.T) { t.Run("Int8 << -3", func(t *testing.T) { inter := parseCheckAndInterpret(t, @@ -450,6 +357,101 @@ func TestInterpretBitwiseLeftShift8(t *testing.T) { var shiftErr interpreter.NegativeShiftError require.ErrorAs(t, err, &shiftErr) }) +} + +func TestInterpretBitwiseLeftShift8(t *testing.T) { + + t.Parallel() + + t.Run("Int8 << 9 (zero result)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int8 = 0x7f + let b: Int8 = 9 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt8Value(0), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int8 << 1 (positive to positive)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int8 = 5 + let b: Int8 = 1 + let c = a << b + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt8Value(10), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int8 << 1 (negative to negative)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int8 = -5 // 0b1111_1011 + let b: Int8 = 1 + let c = a << b // 0b1111_0110 --> -10 + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt8Value(-10), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int8 << 1 (positive to negative)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int8 = 5 // 0b0000_0101 + let b: Int8 = 7 + let c = a << b // 0b1000_0000 --> -128 + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt8Value(-128), + inter.Globals.Get("c").GetValue(inter), + ) + }) + + t.Run("Int8 << 1 (negative to positive)", func(t *testing.T) { + + inter := parseCheckAndInterpret(t, + ` + let a: Int8 = -5 // 0b1111_1011 + let b: Int8 = 5 + let c = a << b // 0b0110_0000 --> 96 + `, + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredInt8Value(0x60), // or 96 + inter.Globals.Get("c").GetValue(inter), + ) + }) t.Run("UInt8 << 9", func(t *testing.T) { From 6509dd6783eaefa6513cec89dd31cdd42b586adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 15:42:25 -0800 Subject: [PATCH 66/80] pass correct testing.T of subtest --- runtime/tests/interpreter/values_test.go | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index ed3fe63fc..fbad19be3 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -240,6 +240,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { } readDictionary := func( + t *testing.T, inter *interpreter.Interpreter, owner common.Address, storageMapKey interpreter.StorageMapKey, @@ -430,6 +431,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { resetStorage() dictionary = readDictionary( + t, inter, orgOwner, dictionaryStorageMapKey, @@ -483,6 +485,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { resetStorage() dictionary = readDictionary( + t, inter, orgOwner, dictionaryStorageMapKey, @@ -531,6 +534,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { resetStorage() original = readDictionary( + t, inter, orgOwner, dictionaryStorageMapKey, @@ -597,6 +601,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { resetStorage() transferred = readDictionary( + t, inter, newOwner, transferredStorageMapKey, @@ -639,6 +644,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { resetStorage() dictionary = readDictionary( + t, inter, orgOwner, dictionaryStorageMapKey, @@ -722,6 +728,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { resetStorage() dictionary = readDictionary( + t, inter, orgOwner, dictionaryStorageMapKey, @@ -762,6 +769,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { resetStorage() dictionary = readDictionary( + t, inter, orgOwner, dictionaryStorageMapKey, @@ -819,6 +827,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { resetStorage() dictionary = readDictionary( + t, inter, orgOwner, dictionaryStorageMapKey, @@ -861,6 +870,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { resetStorage() dictionary = readDictionary( + t, inter, orgOwner, dictionaryStorageMapKey, @@ -932,6 +942,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { resetStorage() dictionary = readDictionary( + t, inter, orgOwner, dictionaryStorageMapKey, @@ -1002,6 +1013,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { } readComposite := func( + t *testing.T, inter *interpreter.Interpreter, owner common.Address, storageMapKey interpreter.StorageMapKey, @@ -1125,6 +1137,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { resetStorage() composite = readComposite( + t, inter, orgOwner, compositeStorageMapKey, @@ -1167,6 +1180,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { resetStorage() original = readComposite( + t, inter, orgOwner, compositeStorageMapKey, @@ -1232,6 +1246,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { resetStorage() transferred = readComposite( + t, inter, newOwner, transferredStorageMapKey, @@ -1274,6 +1289,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { resetStorage() composite = readComposite( + t, inter, orgOwner, compositeStorageMapKey, @@ -1345,6 +1361,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { resetStorage() composite = readComposite( + t, inter, orgOwner, compositeStorageMapKey, @@ -1415,6 +1432,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { } readArray := func( + t *testing.T, inter *interpreter.Interpreter, owner common.Address, storageMapKey interpreter.StorageMapKey, @@ -1557,6 +1575,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { resetStorage() array = readArray( + t, inter, orgOwner, arrayStorageMapKey, @@ -1610,6 +1629,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { resetStorage() array = readArray( + t, inter, orgOwner, arrayStorageMapKey, @@ -1659,6 +1679,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { resetStorage() original = readArray( + t, inter, orgOwner, arrayStorageMapKey, @@ -1725,6 +1746,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { resetStorage() transferred = readArray( + t, inter, newOwner, transferredStorageMapKey, @@ -1767,6 +1789,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { resetStorage() array = readArray( + t, inter, orgOwner, arrayStorageMapKey, @@ -1833,6 +1856,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { resetStorage() array = readArray( + t, inter, orgOwner, arrayStorageMapKey, @@ -1874,6 +1898,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { resetStorage() array = readArray( + t, inter, orgOwner, arrayStorageMapKey, @@ -1930,6 +1955,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { resetStorage() array = readArray( + t, inter, orgOwner, arrayStorageMapKey, @@ -1972,6 +1998,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { resetStorage() array = readArray( + t, inter, orgOwner, arrayStorageMapKey, @@ -2029,6 +2056,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { resetStorage() array = readArray( + t, inter, orgOwner, arrayStorageMapKey, @@ -5184,6 +5212,7 @@ func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { } readValue := func( + t *testing.T, inter *interpreter.Interpreter, owner common.Address, storageMapKey interpreter.StorageMapKey, @@ -5653,6 +5682,7 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) resetStorage() rootSomeValue = readValue( + t, inter, owner, storageMapKey, @@ -5779,6 +5809,7 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) resetStorage() rootSomeValue = readValue( + t, inter, owner, storageMapKey, @@ -5881,6 +5912,7 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) resetStorage() rootSomeValue = readValue( + t, inter, owner, storageMapKey, @@ -5994,6 +6026,7 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) resetStorage() rootSomeValue = readValue( + t, inter, owner, storageMapKey, @@ -6109,6 +6142,7 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) resetStorage() rootSomeValue = readValue( + t, inter, owner, storageMapKey, @@ -6282,6 +6316,7 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) resetStorage() rootSomeValue = readValue( + t, inter, owner, storageMapKey, From 308c682d2ca2d2c96cb7e64e2ecc43db006c7183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 15:53:47 -0800 Subject: [PATCH 67/80] end tests with health checks and validation after storage reset and reload --- runtime/tests/interpreter/values_test.go | 229 ++++++++++++++++++++++- 1 file changed, 221 insertions(+), 8 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index fbad19be3..5d40045c6 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -5690,7 +5690,10 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) // Fill the dictionary until it becomes uninlined - childDictionary := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.DictionaryValue) + childDictionary := rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.DictionaryValue) require.True(t, childDictionary.Inlined()) @@ -5712,6 +5715,8 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) childDictionary = rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.DictionaryValue) verify := func(count int) { + require.Equal(t, count, childDictionary.Count()) + for i := 0; i < count; i++ { key := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) value, exists := childDictionary.Get( @@ -5772,6 +5777,35 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) require.Equal(t, 0, childDictionary.Count()) require.True(t, childDictionary.Inlined()) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // Validate after storage reset and reload of root value + + resetStorage() + + rootSomeValue = readValue( + t, + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + childDictionary = rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.DictionaryValue) + + require.Equal(t, 0, childDictionary.Count()) + require.True(t, childDictionary.Inlined()) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } }) t.Run("dictionary (uninlined -> inlined -> uninlined)", func(t *testing.T) { @@ -5815,7 +5849,10 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) storageMapKey, ).(*interpreter.SomeValue) - childDictionary := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.DictionaryValue) + childDictionary := rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.DictionaryValue) // Check that the inner dictionary is not inlined. // If the test fails here, adjust the value generation code above @@ -5830,6 +5867,8 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) // Verify the contents of the dictionary verify := func(count int) { + require.Equal(t, count, childDictionary.Count()) + for i := 0; i < count; i++ { key := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) value, exists := childDictionary.Get( @@ -5887,6 +5926,36 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) uninlinedCount := inlinedCount + 1 verify(uninlinedCount) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // Validate after storage reset and reload of root value + + resetStorage() + + rootSomeValue = readValue( + t, + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + childDictionary = rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.DictionaryValue) + + verify(uninlinedCount) + + require.False(t, childDictionary.Inlined()) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } }) t.Run("array (inlined -> uninlined -> inlined)", func(t *testing.T) { @@ -5920,7 +5989,10 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) // Fill the array until it becomes uninlined - childArray := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.ArrayValue) + childArray := rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.ArrayValue) require.True(t, childArray.Inlined()) @@ -5938,9 +6010,14 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) // Verify the contents of the array - childArray = rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.ArrayValue) + childArray = rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.ArrayValue) verify := func(count int) { + require.Equal(t, count, childArray.Count()) + for i := 0; i < count; i++ { value := childArray.Get(inter, interpreter.EmptyLocationRange, i) expectedValue := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) @@ -5992,6 +6069,35 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) require.Equal(t, 0, childArray.Count()) require.True(t, childArray.Inlined()) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // Validate after storage reset and reload of root value + + resetStorage() + + rootSomeValue = readValue( + t, + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + childArray = rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.ArrayValue) + + require.Equal(t, 0, childArray.Count()) + require.True(t, childArray.Inlined()) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } }) t.Run("array (uninlined -> inlined -> uninlined)", func(t *testing.T) { @@ -6032,7 +6138,10 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) storageMapKey, ).(*interpreter.SomeValue) - childArray := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.ArrayValue) + childArray := rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.ArrayValue) // Check that the inner array is not inlined. // If the test fails here, adjust the value generation code above @@ -6047,6 +6156,8 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) // Verify the contents of the array verify := func(count int) { + require.Equal(t, count, childArray.Count()) + for i := 0; i < count; i++ { value := childArray.Get(inter, interpreter.EmptyLocationRange, i) expectedValue := interpreter.NewUnmeteredStringValue(strconv.Itoa(i)) @@ -6091,6 +6202,36 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) uninlinedCount := inlinedCount + 1 verify(uninlinedCount) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // Validate after storage reset and reload of root value + + resetStorage() + + rootSomeValue = readValue( + t, + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + childArray = rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.ArrayValue) + + verify(uninlinedCount) + + require.False(t, childArray.Inlined()) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } }) t.Run("composite (inlined -> uninlined -> inlined)", func(t *testing.T) { @@ -6150,7 +6291,10 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) // Fill the composite until it becomes uninlined - childComposite := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.CompositeValue) + childComposite := rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.CompositeValue) require.True(t, childComposite.Inlined()) @@ -6169,9 +6313,14 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) // Verify the contents of the composite - childComposite = rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.CompositeValue) + childComposite = rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.CompositeValue) verify := func(count int) { + require.Equal(t, count, childComposite.FieldCount()) + for i := 0; i < count; i++ { value := childComposite.GetMember( inter, @@ -6228,6 +6377,35 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) require.Equal(t, 0, childComposite.FieldCount()) require.True(t, childComposite.Inlined()) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // Validate after storage reset and reload of root value + + resetStorage() + + rootSomeValue = readValue( + t, + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + childComposite = rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.CompositeValue) + + require.Equal(t, 0, childComposite.FieldCount()) + require.True(t, childComposite.Inlined()) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } }) t.Run("composite (uninlined -> inlined -> uninlined)", func(t *testing.T) { @@ -6322,7 +6500,10 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) storageMapKey, ).(*interpreter.SomeValue) - childComposite := rootSomeValue.InnerValue(inter, interpreter.EmptyLocationRange).(*interpreter.CompositeValue) + childComposite := rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.CompositeValue) // Check that the inner composite is not inlined. // If the test fails here, adjust the value generation code above @@ -6337,6 +6518,8 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) // Verify the contents of the composite verify := func(count int) { + require.Equal(t, count, childComposite.FieldCount()) + for i := 0; i < count; i++ { value := childComposite.GetMember( inter, @@ -6388,5 +6571,35 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) uninlinedCount := inlinedCount + 1 verify(uninlinedCount) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } + + // Validate after storage reset and reload of root value + + resetStorage() + + rootSomeValue = readValue( + t, + inter, + owner, + storageMapKey, + ).(*interpreter.SomeValue) + + childComposite = rootSomeValue.InnerValue( + inter, + interpreter.EmptyLocationRange, + ).(*interpreter.CompositeValue) + + verify(uninlinedCount) + + require.False(t, childComposite.Inlined()) + + if *validateAtree { + err := inter.Storage().CheckHealth() + require.NoError(t, err) + } }) } From 659ebc2f0513260cbacb35b8fad927f9f5dcbbd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 15:55:54 -0800 Subject: [PATCH 68/80] remove unused functions --- runtime/tests/interpreter/values_test.go | 144 ----------------------- 1 file changed, 144 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 5d40045c6..39c54e229 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -3909,150 +3909,6 @@ func findNestedCadenceRecursive( return nil } -func findNestedValue( - value interpreter.Value, - inter *interpreter.Interpreter, - predicate func(value interpreter.Value, path []pathElement) bool, -) []pathElement { - return findNestedRecursive( - value, - inter, - nil, - predicate, - ) -} - -func findNestedRecursive( - value interpreter.Value, - inter *interpreter.Interpreter, - path []pathElement, - predicate func(value interpreter.Value, path []pathElement) bool, -) (result []pathElement) { - if predicate(value, path) { - return path - } - - switch value := value.(type) { - case *interpreter.ArrayValue: - - var index int - - value.Iterate( - inter, - func(element interpreter.Value) (resume bool) { - - nestedPath := path - nestedPath = append(nestedPath, arrayPathElement{index}) - - result = findNestedRecursive( - element, - inter, - nestedPath, - predicate, - ) - if result != nil { - return false - } - - index += 1 - - // continue iteration - return true - }, - false, - interpreter.EmptyLocationRange, - ) - - if result != nil { - return result - } - - case *interpreter.DictionaryValue: - - value.Iterate( - inter, - interpreter.EmptyLocationRange, - func(key, element interpreter.Value) (resume bool) { - - cadenceKey, err := runtime.ExportValue(key, inter, interpreter.EmptyLocationRange) - if err != nil { - panic(errors.NewUnexpectedErrorFromCause(err)) - } - - nestedPath := path - nestedPath = append(nestedPath, dictionaryPathElement{cadenceKey}) - - result = findNestedRecursive( - element, - inter, - nestedPath, - predicate, - ) - if result != nil { - return false - } - - // continue iteration - return true - }, - ) - - if result != nil { - return result - } - - case *interpreter.CompositeValue: - - value.ForEachFieldName(func(fieldName string) (resume bool) { - - nestedPath := path - nestedPath = append(nestedPath, structPathElement{fieldName}) - - field := value.GetMember( - inter, - interpreter.EmptyLocationRange, - fieldName, - ) - - result = findNestedRecursive( - field, - inter, - nestedPath, - predicate, - ) - if result != nil { - return false - } - - // continue iteration - return true - }) - - if result != nil { - return result - } - - case *interpreter.SomeValue: - - nestedPath := path - nestedPath = append(nestedPath, somePathElement{}) - - innerValue := value.InnerValue(inter, interpreter.EmptyLocationRange) - - result = findNestedRecursive( - innerValue, - inter, - nestedPath, - predicate, - ) - if result != nil { - return result - } - } - - return nil -} - func getNestedValue( t *testing.T, inter *interpreter.Interpreter, From 4f4a02d9cd50644ab081f94bbcdc3732dd947768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 16:02:55 -0800 Subject: [PATCH 69/80] improve naming --- runtime/interpreter/value.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 635d67470..939727693 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -263,11 +263,11 @@ type ValueIterator interface { Next(interpreter *Interpreter, locationRange LocationRange) Value } -// containerValue is an interface for values using atree containers +// atreeContainerBackedValue is an interface for values using atree containers // (atree.Array or atree.OrderedMap) under the hood. -type containerValue interface { +type atreeContainerBackedValue interface { Value - isContainerValue() + isAtreeContainerBackedValue() } func safeAdd(a, b int, locationRange LocationRange) int { @@ -2297,11 +2297,11 @@ var _ ValueIndexableValue = &ArrayValue{} var _ MemberAccessibleValue = &ArrayValue{} var _ ReferenceTrackedResourceKindedValue = &ArrayValue{} var _ IterableValue = &ArrayValue{} -var _ containerValue = &ArrayValue{} +var _ atreeContainerBackedValue = &ArrayValue{} func (*ArrayValue) isValue() {} -func (*ArrayValue) isContainerValue() {} +func (*ArrayValue) isAtreeContainerBackedValue() {} func (v *ArrayValue) Accept(interpreter *Interpreter, visitor Visitor, locationRange LocationRange) { descend := visitor.VisitArrayValue(interpreter, v) @@ -17508,11 +17508,11 @@ var _ ReferenceTrackedResourceKindedValue = &CompositeValue{} var _ ContractValue = &CompositeValue{} var _ atree.Value = &CompositeValue{} var _ atree.WrapperValue = &CompositeValue{} -var _ containerValue = &CompositeValue{} +var _ atreeContainerBackedValue = &CompositeValue{} func (*CompositeValue) isValue() {} -func (*CompositeValue) isContainerValue() {} +func (*CompositeValue) isAtreeContainerBackedValue() {} func (v *CompositeValue) Accept(interpreter *Interpreter, visitor Visitor, locationRange LocationRange) { descend := visitor.VisitCompositeValue(interpreter, v) @@ -19500,11 +19500,11 @@ var _ EquatableValue = &DictionaryValue{} var _ ValueIndexableValue = &DictionaryValue{} var _ MemberAccessibleValue = &DictionaryValue{} var _ ReferenceTrackedResourceKindedValue = &DictionaryValue{} -var _ containerValue = &DictionaryValue{} +var _ atreeContainerBackedValue = &DictionaryValue{} func (*DictionaryValue) isValue() {} -func (*DictionaryValue) isContainerValue() {} +func (*DictionaryValue) isAtreeContainerBackedValue() {} func (v *DictionaryValue) Accept(interpreter *Interpreter, visitor Visitor, locationRange LocationRange) { descend := visitor.VisitDictionaryValue(interpreter, v) @@ -21255,7 +21255,7 @@ func (v *SomeValue) Storable( nonSomeValue, nestedLevels := v.nonSomeValue() - _, isContainerValue := nonSomeValue.(containerValue) + _, isContainerValue := nonSomeValue.(atreeContainerBackedValue) if v.valueStorable == nil || isContainerValue { From 1ea398653ff724ccb00d53dc748c81153d644452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 21 Jan 2025 16:20:36 -0800 Subject: [PATCH 70/80] pass subtest testing.T --- runtime/tests/interpreter/values_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 39c54e229..37a1e8eb9 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -5157,6 +5157,7 @@ func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { resetStorage() rootDictionary = readValue( + t, inter, owner, storageMapKey, @@ -5257,6 +5258,7 @@ func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { resetStorage() rootArray = readValue( + t, inter, owner, storageMapKey, @@ -5424,6 +5426,7 @@ func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { resetStorage() rootStruct = readValue( + t, inter, owner, storageMapKey, @@ -5498,6 +5501,7 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) } readValue := func( + t *testing.T, inter *interpreter.Interpreter, owner common.Address, storageMapKey interpreter.StorageMapKey, From 6d2df927416ff9afff74bef9dafa2b4515843acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 22 Jan 2025 09:00:27 -0800 Subject: [PATCH 71/80] improve naming of tests --- runtime/tests/interpreter/values_test.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/runtime/tests/interpreter/values_test.go b/runtime/tests/interpreter/values_test.go index 37a1e8eb9..035c10057 100644 --- a/runtime/tests/interpreter/values_test.go +++ b/runtime/tests/interpreter/values_test.go @@ -209,7 +209,7 @@ func withoutAtreeStorageValidationEnabled[T any](inter *interpreter.Interpreter, return result } -func TestInterpretRandomDictionaryOperations(t *testing.T) { +func TestInterpretSmokeRandomDictionaryOperations(t *testing.T) { if !*runSmokeTests { t.Skip("smoke tests are disabled") } @@ -965,7 +965,7 @@ func TestInterpretRandomDictionaryOperations(t *testing.T) { }) } -func TestInterpretRandomCompositeOperations(t *testing.T) { +func TestInterpretSmokeRandomCompositeOperations(t *testing.T) { if !*runSmokeTests { t.Skip("smoke tests are disabled") } @@ -1384,7 +1384,7 @@ func TestInterpretRandomCompositeOperations(t *testing.T) { }) } -func TestInterpretRandomArrayOperations(t *testing.T) { +func TestInterpretSmokeRandomArrayOperations(t *testing.T) { if !*runSmokeTests { t.Skip("smoke tests are disabled") } @@ -2079,7 +2079,7 @@ func TestInterpretRandomArrayOperations(t *testing.T) { }) } -func TestInterpretRandomNestedArrayOperations(t *testing.T) { +func TestInterpretSmokeRandomNestedArrayOperations(t *testing.T) { if !*runSmokeTests { t.Skip("smoke tests are disabled") } @@ -2625,7 +2625,7 @@ func TestInterpretRandomNestedArrayOperations(t *testing.T) { }) } -func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { +func TestInterpretSmokeRandomNestedDictionaryOperations(t *testing.T) { if !*runSmokeTests { t.Skip("smoke tests are disabled") } @@ -3244,7 +3244,7 @@ func TestInterpretRandomNestedDictionaryOperations(t *testing.T) { }) } -func TestInterpretRandomNestedCompositeOperations(t *testing.T) { +func TestInterpretSmokeRandomNestedCompositeOperations(t *testing.T) { if !*runSmokeTests { t.Skip("smoke tests are disabled") } @@ -4808,6 +4808,8 @@ func mapKey(inter *interpreter.Interpreter, key interpreter.Value) any { // - maybeValidateAtreeValue() calls CheckHealth() func TestCheckStorageHealthInMiddleOfDeepRemove(t *testing.T) { + t.Parallel() + storage := newUnmeteredInMemoryStorage() inter, err := interpreter.NewInterpreter( &interpreter.Program{ @@ -4882,7 +4884,7 @@ func TestCheckStorageHealthInMiddleOfDeepRemove(t *testing.T) { // https://github.com/onflow/cadence/pull/2882#issuecomment-1796381227 // In this test, storage.CheckHealth() should be called after DictionaryValue.Transfer() // with remove flag, not in the middle of DictionaryValue.Transfer(). -func TestCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { +func TestInterpretCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { t.Parallel() From 6e54fc97aebff1925fc2806165a9eedc483c6459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 27 Jan 2025 10:01:46 -0800 Subject: [PATCH 72/80] update to atree v0.9.0 --- go.mod | 4 +--- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 1fdda74ab..430e6cfa6 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/kr/pretty v0.3.1 github.com/leanovate/gopter v0.2.9 github.com/logrusorgru/aurora/v4 v4.0.0 - github.com/onflow/atree v0.8.0-rc.6 + github.com/onflow/atree v0.9.0 github.com/rivo/uniseg v0.4.4 github.com/schollz/progressbar/v3 v3.13.1 github.com/stretchr/testify v1.10.0 @@ -62,5 +62,3 @@ require ( gonum.org/v1/gonum v0.6.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/onflow/atree => github.com/onflow/atree-internal v0.8.2-0.20250117221137-acdb0e04ab01 diff --git a/go.sum b/go.sum index a2e0088b3..79e5c1d18 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onflow/atree-internal v0.8.2-0.20250117221137-acdb0e04ab01 h1:UmPbGWshC0HvIrqwTiTlVZc8BqkxgFMyC8uHcDquMOE= -github.com/onflow/atree-internal v0.8.2-0.20250117221137-acdb0e04ab01/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/atree v0.9.0 h1:M+Z/UPwzv0/Yy7ChI5T1ZIHD3YN1cs/hxGEs/HWhzaY= +github.com/onflow/atree v0.9.0/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= From 1c03fb714a4be6e2f6d44fa6f3c31cf41ed98385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 27 Jan 2025 10:08:13 -0800 Subject: [PATCH 73/80] Revert "Update CI" This reverts commit 9d82cd4b8999e31302f417e7a06d92c97fd3f730. --- .github/workflows/ci.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d9817d65..1ca30fdf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,6 @@ on: env: GO_VERSION: '1.22' - GOPRIVATE: github.com/onflow/atree-internal concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} @@ -43,17 +42,6 @@ jobs: - name: Install Flow CLI run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" - - name: Setup SSH for Onflow - run: git config --global url.git@github.com:onflow/.insteadOf https://github.com/onflow/ - - - name: Add GOPRIVATE - run: go env -w GOPRIVATE=${{ env.GOPRIVATE }} - - - name: Setup SSH Agent - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: ${{ secrets.ATREE_DEPLOY_KEY }} - - name: Build run: make -j8 build From ce6f5426f3482236ca66646b88016094f69ce1c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 27 Jan 2025 10:09:25 -0800 Subject: [PATCH 74/80] Revert "Fix the lint job as well" This reverts commit 590c66ec61c46b5d3cf49e6990fc97f4a69b99a6. --- .github/workflows/ci.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ca30fdf2..634088911 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,17 +73,6 @@ jobs: go-version: ${{ env.GO_VERSION }} cache: true - - name: Setup SSH for Onflow - run: git config --global url.git@github.com:onflow/.insteadOf https://github.com/onflow/ - - - name: Add GOPRIVATE - run: go env -w GOPRIVATE=${{ env.GOPRIVATE }} - - - name: Setup SSH Agent - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: ${{ secrets.ATREE_DEPLOY_KEY }} - - name: Lint run: make lint-github-actions From f919feef3dfa1aef590034dc82d28db3a45e05c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 27 Jan 2025 10:30:32 -0800 Subject: [PATCH 75/80] update dependencies --- tools/storage-explorer/go.mod | 2 +- tools/storage-explorer/go.sum | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/storage-explorer/go.mod b/tools/storage-explorer/go.mod index c301cdbb9..2b0c25c5e 100644 --- a/tools/storage-explorer/go.mod +++ b/tools/storage-explorer/go.mod @@ -4,7 +4,7 @@ go 1.22 require ( github.com/gorilla/mux v1.8.1 - github.com/onflow/atree v0.8.0-rc.6 + github.com/onflow/atree v0.9.0 github.com/onflow/cadence v1.0.0-preview-atree-register-inlining.29 github.com/onflow/flow-go v0.35.7-crescendo-preview.23-atree-inlining github.com/rs/zerolog v1.32.0 diff --git a/tools/storage-explorer/go.sum b/tools/storage-explorer/go.sum index 078fb2409..557108a7e 100644 --- a/tools/storage-explorer/go.sum +++ b/tools/storage-explorer/go.sum @@ -1910,6 +1910,7 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6 github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f/go.mod h1:xvP61FoOs95K7IYdIYRnNcYQGf4nbF/uuJ0tHf4DRuM= github.com/onflow/atree v0.8.0-rc.6 h1:GWgaylK24b5ta2Hq+TvyOF7X5tZLiLzMMn7lEt59fsA= github.com/onflow/atree v0.8.0-rc.6/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/Ct1AA5zBo= +github.com/onflow/atree v0.9.0/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/crypto v0.25.1 h1:0txy2PKPMM873JbpxQNbJmuOJtD56bfs48RQfm0ts5A= github.com/onflow/crypto v0.25.1/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= From 4bc71518d4b22cfee45c57bba1f018717a9949b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 27 Jan 2025 10:31:01 -0800 Subject: [PATCH 76/80] fix version --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 698fda3b9..b5cb28b0e 100644 --- a/version.go +++ b/version.go @@ -21,4 +21,4 @@ package cadence -const Version = "v1.0.4-rc.1" +const Version = "v1.0.3" From 7a4cfaebee663533f7baf8d1b8149850dedb355a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 27 Jan 2025 16:54:41 -0800 Subject: [PATCH 77/80] bring back missing interface --- interpreter/value.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/interpreter/value.go b/interpreter/value.go index bf698515c..cff5dad1e 100644 --- a/interpreter/value.go +++ b/interpreter/value.go @@ -246,6 +246,13 @@ type ValueIterator interface { Next(interpreter *Interpreter, locationRange LocationRange) Value } +// atreeContainerBackedValue is an interface for values using atree containers +// (atree.Array or atree.OrderedMap) under the hood. +type atreeContainerBackedValue interface { + Value + isAtreeContainerBackedValue() +} + func safeAdd(a, b int, locationRange LocationRange) int { // INT32-C if (b > 0) && (a > (goMaxInt - b)) { From 7a6db921639a917698d62b7ba6ba525f152f13c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 28 Jan 2025 08:49:37 -0800 Subject: [PATCH 78/80] add missing imports, adjust tests to new APIs --- interpreter/misc_test.go | 18 +++--- interpreter/value_int128.go | 1 + interpreter/value_int256.go | 1 + interpreter/value_some_test.go | 38 +++++------ interpreter/value_uint128.go | 1 + interpreter/value_uint256.go | 1 + interpreter/value_word128.go | 1 + interpreter/value_word256.go | 1 + interpreter/values_test.go | 105 +++++++++++++++++++------------ tools/compatibility-check/go.mod | 2 +- tools/compatibility-check/go.sum | 1 + tools/storage-explorer/go.sum | 3 + 12 files changed, 105 insertions(+), 68 deletions(-) diff --git a/interpreter/misc_test.go b/interpreter/misc_test.go index c397e2968..ff43fa120 100644 --- a/interpreter/misc_test.go +++ b/interpreter/misc_test.go @@ -41,6 +41,7 @@ import ( "github.com/onflow/cadence/stdlib" . "github.com/onflow/cadence/test_utils/common_utils" . "github.com/onflow/cadence/test_utils/interpreter_utils" + . "github.com/onflow/cadence/test_utils/runtime_utils" . "github.com/onflow/cadence/test_utils/sema_utils" ) @@ -12622,7 +12623,7 @@ func TestInterpretSomeValueChildContainerMutation(t *testing.T) { code, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - Storage: runtime.NewStorage(ledger, nil), + Storage: runtime.NewStorage(ledger, nil, runtime.StorageConfig{}), }, }, ) @@ -12642,9 +12643,10 @@ func TestInterpretSomeValueChildContainerMutation(t *testing.T) { path := interpreter.NewUnmeteredPathValue(common.PathDomainStorage, "foo") storage := inter.Storage().(*runtime.Storage) - storageMap := storage.GetStorageMap( + storageMap := storage.GetDomainStorageMap( + inter, address, - path.Domain.Identifier(), + common.StorageDomain(path.Domain), true, ) @@ -12667,9 +12669,10 @@ func TestInterpretSomeValueChildContainerMutation(t *testing.T) { inter = newInter() storage = inter.Storage().(*runtime.Storage) - storageMap = storage.GetStorageMap( + storageMap = storage.GetDomainStorageMap( + inter, address, - path.Domain.Identifier(), + common.StorageDomain(path.Domain), false, ) require.NotNil(t, storageMap) @@ -12694,9 +12697,10 @@ func TestInterpretSomeValueChildContainerMutation(t *testing.T) { inter = newInter() storage = inter.Storage().(*runtime.Storage) - storageMap = storage.GetStorageMap( + storageMap = storage.GetDomainStorageMap( + inter, address, - path.Domain.Identifier(), + common.StorageDomain(path.Domain), false, ) require.NotNil(t, storageMap) diff --git a/interpreter/value_int128.go b/interpreter/value_int128.go index 1fbf03fe8..2df6c0e4d 100644 --- a/interpreter/value_int128.go +++ b/interpreter/value_int128.go @@ -20,6 +20,7 @@ package interpreter import ( "math/big" + "math/bits" "github.com/onflow/atree" diff --git a/interpreter/value_int256.go b/interpreter/value_int256.go index 931253212..09f27f3a6 100644 --- a/interpreter/value_int256.go +++ b/interpreter/value_int256.go @@ -20,6 +20,7 @@ package interpreter import ( "math/big" + "math/bits" "github.com/onflow/atree" diff --git a/interpreter/value_some_test.go b/interpreter/value_some_test.go index 340f83676..7adb51b0a 100644 --- a/interpreter/value_some_test.go +++ b/interpreter/value_some_test.go @@ -26,18 +26,18 @@ import ( "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/ast" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/sema" - "github.com/onflow/cadence/runtime/tests/utils" + "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/interpreter" + "github.com/onflow/cadence/sema" + . "github.com/onflow/cadence/test_utils/common_utils" ) func TestSomeValueUnwrapAtreeValue(t *testing.T) { const ( cborTagSize = 2 - someStorableWithMultipleNestedlevelsArraySize = 1 + someStorableWithMultipleNestedLevelsArraySize = 1 ) t.Parallel() @@ -61,7 +61,7 @@ func TestSomeValueUnwrapAtreeValue(t *testing.T) { unwrappedValue, wrapperSize := v.UnwrapAtreeValue() require.Equal(t, bv, unwrappedValue) - require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedlevelsArraySize+1), wrapperSize) + require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedLevelsArraySize+1), wrapperSize) }) t.Run("SomeValue(SomeValue(ArrayValue(...)))", func(t *testing.T) { @@ -72,7 +72,7 @@ func TestSomeValueUnwrapAtreeValue(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { @@ -107,7 +107,7 @@ func TestSomeValueUnwrapAtreeValue(t *testing.T) { unwrappedValue, wrapperSize := v.UnwrapAtreeValue() require.IsType(t, &atree.Array{}, unwrappedValue) - require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedlevelsArraySize+1), wrapperSize) + require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedLevelsArraySize+1), wrapperSize) atreeArray := unwrappedValue.(*atree.Array) require.Equal(t, atree.Address(address), atreeArray.Address()) @@ -128,7 +128,7 @@ func TestSomeValueUnwrapAtreeValue(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { @@ -166,7 +166,7 @@ func TestSomeValueUnwrapAtreeValue(t *testing.T) { unwrappedValue, wrapperSize := v.UnwrapAtreeValue() require.IsType(t, &atree.OrderedMap{}, unwrappedValue) - require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedlevelsArraySize+1), wrapperSize) + require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedLevelsArraySize+1), wrapperSize) // Verify unwrapped value atreeMap := unwrappedValue.(*atree.OrderedMap) @@ -214,7 +214,7 @@ func TestSomeValueUnwrapAtreeValue(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { @@ -264,7 +264,7 @@ func TestSomeValueUnwrapAtreeValue(t *testing.T) { unwrappedValue, wrapperSize := v.UnwrapAtreeValue() require.IsType(t, &atree.OrderedMap{}, unwrappedValue) - require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedlevelsArraySize+1), wrapperSize) + require.Equal(t, uint64(cborTagSize+someStorableWithMultipleNestedLevelsArraySize+1), wrapperSize) // Verify unwrapped value atreeMap := unwrappedValue.(*atree.OrderedMap) @@ -328,7 +328,7 @@ func TestSomeStorableUnwrapAtreeStorable(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { @@ -390,7 +390,7 @@ func TestSomeStorableUnwrapAtreeStorable(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { @@ -452,7 +452,7 @@ func TestSomeStorableUnwrapAtreeStorable(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { @@ -546,7 +546,7 @@ func TestSomeStorableUnwrapAtreeStorable(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { @@ -643,7 +643,7 @@ func TestSomeStorableUnwrapAtreeStorable(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { @@ -727,7 +727,7 @@ func TestSomeStorableUnwrapAtreeStorable(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { diff --git a/interpreter/value_uint128.go b/interpreter/value_uint128.go index c93ea7f21..dfde3cfa4 100644 --- a/interpreter/value_uint128.go +++ b/interpreter/value_uint128.go @@ -20,6 +20,7 @@ package interpreter import ( "math/big" + "math/bits" "github.com/onflow/atree" diff --git a/interpreter/value_uint256.go b/interpreter/value_uint256.go index 225f8e77d..2050df91a 100644 --- a/interpreter/value_uint256.go +++ b/interpreter/value_uint256.go @@ -20,6 +20,7 @@ package interpreter import ( "math/big" + "math/bits" "github.com/onflow/atree" diff --git a/interpreter/value_word128.go b/interpreter/value_word128.go index d2e8f76db..a9aacc4bf 100644 --- a/interpreter/value_word128.go +++ b/interpreter/value_word128.go @@ -20,6 +20,7 @@ package interpreter import ( "math/big" + "math/bits" "github.com/onflow/atree" diff --git a/interpreter/value_word256.go b/interpreter/value_word256.go index 7af98d0a1..9446d1c81 100644 --- a/interpreter/value_word256.go +++ b/interpreter/value_word256.go @@ -20,6 +20,7 @@ package interpreter import ( "math/big" + "math/bits" "github.com/onflow/atree" diff --git a/interpreter/values_test.go b/interpreter/values_test.go index f5d4ec02e..4f679dec9 100644 --- a/interpreter/values_test.go +++ b/interpreter/values_test.go @@ -84,7 +84,7 @@ func newRandomValueTestInterpreter(t *testing.T) (inter *interpreter.Interpreter err := storage.Commit(inter, false) require.NoError(t, err) } - config.Storage = runtime.NewStorage(ledger, nil) + config.Storage = runtime.NewStorage(ledger, nil, runtime.StorageConfig{}) } resetStorage() @@ -104,6 +104,7 @@ func importValue(t *testing.T, inter *interpreter.Interpreter, value cadence.Val inter, interpreter.EmptyLocationRange, nil, + nil, cadence.Array{}, sema.NewVariableSizedType(nil, sema.AnyStructType), ) @@ -130,6 +131,7 @@ func importValue(t *testing.T, inter *interpreter.Interpreter, value cadence.Val inter, interpreter.EmptyLocationRange, nil, + nil, cadence.Dictionary{}, sema.NewDictionaryType( nil, @@ -158,6 +160,7 @@ func importValue(t *testing.T, inter *interpreter.Interpreter, value cadence.Val inter, interpreter.EmptyLocationRange, nil, + nil, cadence.Struct{ StructType: value.StructType, }, @@ -193,6 +196,7 @@ func importValue(t *testing.T, inter *interpreter.Interpreter, value cadence.Val inter, interpreter.EmptyLocationRange, nil, + nil, value, nil, ) @@ -228,9 +232,10 @@ func TestInterpretSmokeRandomDictionaryOperations(t *testing.T) { dictionary *interpreter.DictionaryValue, ) { inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, true, ). WriteValue( @@ -246,9 +251,10 @@ func TestInterpretSmokeRandomDictionaryOperations(t *testing.T) { owner common.Address, storageMapKey interpreter.StorageMapKey, ) *interpreter.DictionaryValue { - storageMap := inter.Storage().GetStorageMap( + storageMap := inter.Storage().GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, false, ) require.NotNil(t, storageMap) @@ -266,9 +272,10 @@ func TestInterpretSmokeRandomDictionaryOperations(t *testing.T) { storageMapKey interpreter.StorageMapKey, ) { inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, false, ). RemoveValue( @@ -984,9 +991,10 @@ func TestInterpretSmokeRandomCompositeOperations(t *testing.T) { composite *interpreter.CompositeValue, ) { inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, true, ). WriteValue( @@ -1002,9 +1010,10 @@ func TestInterpretSmokeRandomCompositeOperations(t *testing.T) { storageMapKey interpreter.StorageMapKey, ) { inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, true, ). RemoveValue( @@ -1019,9 +1028,10 @@ func TestInterpretSmokeRandomCompositeOperations(t *testing.T) { owner common.Address, storageMapKey interpreter.StorageMapKey, ) *interpreter.CompositeValue { - storageMap := inter.Storage().GetStorageMap( + storageMap := inter.Storage().GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, false, ) require.NotNil(t, storageMap) @@ -1403,9 +1413,10 @@ func TestInterpretSmokeRandomArrayOperations(t *testing.T) { array *interpreter.ArrayValue, ) { inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, true, ). WriteValue( @@ -1421,9 +1432,10 @@ func TestInterpretSmokeRandomArrayOperations(t *testing.T) { storageMapKey interpreter.StorageMapKey, ) { inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, true, ). RemoveValue( @@ -1438,9 +1450,10 @@ func TestInterpretSmokeRandomArrayOperations(t *testing.T) { owner common.Address, storageMapKey interpreter.StorageMapKey, ) *interpreter.ArrayValue { - storageMap := inter.Storage().GetStorageMap( + storageMap := inter.Storage().GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, false, ) require.NotNil(t, storageMap) @@ -2104,9 +2117,10 @@ func TestInterpretSmokeRandomNestedArrayOperations(t *testing.T) { array *interpreter.ArrayValue, ) { inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, true, ). WriteValue( @@ -2121,9 +2135,10 @@ func TestInterpretSmokeRandomNestedArrayOperations(t *testing.T) { owner common.Address, storageMapKey interpreter.StorageMapKey, ) *interpreter.ArrayValue { - storageMap := inter.Storage().GetStorageMap( + storageMap := inter.Storage().GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, false, ) require.NotNil(t, storageMap) @@ -2650,9 +2665,10 @@ func TestInterpretSmokeRandomNestedDictionaryOperations(t *testing.T) { dictionary *interpreter.DictionaryValue, ) { inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, true, ). WriteValue( @@ -2667,9 +2683,10 @@ func TestInterpretSmokeRandomNestedDictionaryOperations(t *testing.T) { owner common.Address, storageMapKey interpreter.StorageMapKey, ) *interpreter.DictionaryValue { - storageMap := inter.Storage().GetStorageMap( + storageMap := inter.Storage().GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, false, ) require.NotNil(t, storageMap) @@ -3269,9 +3286,10 @@ func TestInterpretSmokeRandomNestedCompositeOperations(t *testing.T) { composite *interpreter.CompositeValue, ) { inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, true, ). WriteValue( @@ -3286,9 +3304,10 @@ func TestInterpretSmokeRandomNestedCompositeOperations(t *testing.T) { owner common.Address, storageMapKey interpreter.StorageMapKey, ) *interpreter.CompositeValue { - storageMap := inter.Storage().GetStorageMap( + storageMap := inter.Storage().GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, false, ) require.NotNil(t, storageMap) @@ -4817,7 +4836,7 @@ func TestCheckStorageHealthInMiddleOfDeepRemove(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { @@ -4898,7 +4917,7 @@ func TestInterpretCheckStorageHealthInMiddleOfTransferAndRemove(t *testing.T) { Program: ast.NewProgram(nil, []ast.Declaration{}), Elaboration: sema.NewElaboration(nil), }, - utils.TestLocation, + TestLocation, &interpreter.Config{ Storage: storage, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { @@ -5058,9 +5077,10 @@ func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { ) inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, true, ). WriteValue( @@ -5076,9 +5096,10 @@ func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { owner common.Address, storageMapKey interpreter.StorageMapKey, ) interpreter.Value { - storageMap := inter.Storage().GetStorageMap( + storageMap := inter.Storage().GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, false, ) require.NotNil(t, storageMap) @@ -5491,9 +5512,10 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) ) inter.Storage(). - GetStorageMap( + GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, true, ). WriteValue( @@ -5509,9 +5531,10 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) owner common.Address, storageMapKey interpreter.StorageMapKey, ) interpreter.Value { - storageMap := inter.Storage().GetStorageMap( + storageMap := inter.Storage().GetDomainStorageMap( + inter, owner, - common.PathDomainStorage.Identifier(), + common.StorageDomainPathStorage, false, ) require.NotNil(t, storageMap) diff --git a/tools/compatibility-check/go.mod b/tools/compatibility-check/go.mod index 4e7a813b9..e4755a3d0 100644 --- a/tools/compatibility-check/go.mod +++ b/tools/compatibility-check/go.mod @@ -43,7 +43,7 @@ require ( github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/onflow/atree v0.8.1 // indirect + github.com/onflow/atree v0.9.0 // indirect github.com/onflow/crypto v0.25.2 // indirect github.com/onflow/flow-core-contracts/lib/go/templates v1.3.3-0.20241017220455-79fdc6c8ba53 // indirect github.com/onflow/flow-ft/lib/go/contracts v1.0.1 // indirect diff --git a/tools/compatibility-check/go.sum b/tools/compatibility-check/go.sum index 39d0ed901..ddb92eed9 100644 --- a/tools/compatibility-check/go.sum +++ b/tools/compatibility-check/go.sum @@ -344,6 +344,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onflow/atree v0.8.1 h1:DAnPnL9/Ks3LaAnkQVokokTBG/znTW0DJfovDtJDhLI= github.com/onflow/atree v0.8.1/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/atree v0.9.0/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= github.com/onflow/crypto v0.25.2 h1:GjHunqVt+vPcdqhxxhAXiMIF3YiLX7gTuTR5O+VG2ns= github.com/onflow/crypto v0.25.2/go.mod h1:fY7eLqUdMKV8EGOw301unP8h7PvLVy8/6gVR++/g0BY= github.com/onflow/flow-core-contracts/lib/go/contracts v1.4.0 h1:R86HaOuk6vpuECZnriEUE7bw9inC2AtdSn8lL/iwQLQ= diff --git a/tools/storage-explorer/go.sum b/tools/storage-explorer/go.sum index c8224ca06..e88101456 100644 --- a/tools/storage-explorer/go.sum +++ b/tools/storage-explorer/go.sum @@ -1906,6 +1906,8 @@ github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f/go.mod h1:xvP61FoOs github.com/onflow/atree v0.8.0-rc.6 h1:GWgaylK24b5ta2Hq+TvyOF7X5tZLiLzMMn7lEt59fsA= github.com/onflow/atree v0.8.0-rc.6/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/Ct1AA5zBo= github.com/onflow/atree v0.9.0/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/cadence v1.0.0-M3/go.mod h1:odXGZZ/wGNA5mwT8bC9v8u8EXACHllB2ABSZK65TGL8= +github.com/onflow/cadence v1.0.0-preview-atree-register-inlining.29/go.mod h1:KclJlSGWG4USgPK4CsI3V/YtCHYOwPpjyzb6iEfWlbM= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/crypto v0.25.2 h1:GjHunqVt+vPcdqhxxhAXiMIF3YiLX7gTuTR5O+VG2ns= github.com/onflow/crypto v0.25.2/go.mod h1:fY7eLqUdMKV8EGOw301unP8h7PvLVy8/6gVR++/g0BY= @@ -1917,6 +1919,7 @@ github.com/onflow/flow-ft/lib/go/contracts v1.0.0 h1:mToacZ5NWqtlWwk/7RgIl/jeKB/ github.com/onflow/flow-ft/lib/go/contracts v1.0.0/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= github.com/onflow/flow-ft/lib/go/templates v1.0.0 h1:6cMS/lUJJ17HjKBfMO/eh0GGvnpElPgBXx7h5aoWJhs= github.com/onflow/flow-ft/lib/go/templates v1.0.0/go.mod h1:uQ8XFqmMK2jxyBSVrmyuwdWjTEb+6zGjRYotfDJ5pAE= +github.com/onflow/flow-go v0.35.7-crescendo-preview.23-atree-inlining/go.mod h1:rTPlD+FVYJDKp+TbVkoOlo9cEZ1co3w438/o/IUGgH8= github.com/onflow/flow-go v0.37.10 h1:Nz2Gp63+0ubb9FuQaEZgCsXNXM5WsXq/j0ukC74N5Vw= github.com/onflow/flow-go v0.37.10/go.mod h1:bfOCsCk0v1J93vXd+zrYkCmRIVOaL9oAXvNFWgVOujE= github.com/onflow/flow-go-sdk v1.0.0-M1/go.mod h1:TDW0MNuCs4SvqYRUzkbRnRmHQL1h4X8wURsCw9P9beo= From 9fb44fdaf0742ecd92dfc8461b7ae62c50ab7e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 28 Jan 2025 09:24:47 -0800 Subject: [PATCH 79/80] disable storage validation while writing value to storage map --- interpreter/misc_test.go | 17 ++++++++- interpreter/values_test.go | 71 ++++++++++++++++++++++++-------------- 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/interpreter/misc_test.go b/interpreter/misc_test.go index ff43fa120..7ef440fac 100644 --- a/interpreter/misc_test.go +++ b/interpreter/misc_test.go @@ -12659,7 +12659,22 @@ func TestInterpretSomeValueChildContainerMutation(t *testing.T) { nil, true, ) - storageMap.WriteValue(inter, interpreter.StringStorageMapKey(path.Identifier), foo) + + // Write the value to the storage map. + // However, the value is not referenced by the root of the storage yet + // (a storage map), so atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + withoutAtreeStorageValidationEnabled( + inter, + func() struct{} { + storageMap.WriteValue( + inter, + interpreter.StringStorageMapKey(path.Identifier), + foo, + ) + return struct{}{} + }, + ) err = storage.Commit(inter, false) require.NoError(t, err) diff --git a/interpreter/values_test.go b/interpreter/values_test.go index 4f679dec9..fc8260f87 100644 --- a/interpreter/values_test.go +++ b/interpreter/values_test.go @@ -5076,18 +5076,29 @@ func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { true, ) - inter.Storage(). - GetDomainStorageMap( - inter, - owner, - common.StorageDomainPathStorage, - true, - ). - WriteValue( - inter, - storageMapKey, - value, - ) + // Write the value to the storage map. + // However, the value is not referenced by the root of the storage yet + // (a storage map), so atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + withoutAtreeStorageValidationEnabled( + inter, + func() struct{} { + inter.Storage(). + GetDomainStorageMap( + inter, + owner, + common.StorageDomainPathStorage, + true, + ). + WriteValue( + inter, + storageMapKey, + value, + ) + + return struct{}{} + }, + ) } readValue := func( @@ -5484,7 +5495,6 @@ func TestInterpretIterateReadOnlyLoadedWithSomeValueChildren(t *testing.T) { require.Equal(t, expectedRootCount, iterations) }) - } func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) { @@ -5511,18 +5521,29 @@ func TestInterpretNestedAtreeContainerInSomeValueStorableTracking(t *testing.T) true, ) - inter.Storage(). - GetDomainStorageMap( - inter, - owner, - common.StorageDomainPathStorage, - true, - ). - WriteValue( - inter, - storageMapKey, - value, - ) + // Write the value to the storage map. + // However, the value is not referenced by the root of the storage yet + // (a storage map), so atree storage validation must be temporarily disabled + // to not report any "unreferenced slab" errors. + withoutAtreeStorageValidationEnabled( + inter, + func() struct{} { + inter.Storage(). + GetDomainStorageMap( + inter, + owner, + common.StorageDomainPathStorage, + true, + ). + WriteValue( + inter, + storageMapKey, + value, + ) + + return struct{}{} + }, + ) } readValue := func( From c10343238dd5a938d0de6bc1aeba8ff630e0391f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 28 Jan 2025 09:30:52 -0800 Subject: [PATCH 80/80] go mod tidy --- tools/compatibility-check/go.sum | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/compatibility-check/go.sum b/tools/compatibility-check/go.sum index ddb92eed9..79fce2ea6 100644 --- a/tools/compatibility-check/go.sum +++ b/tools/compatibility-check/go.sum @@ -342,8 +342,7 @@ github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/n github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onflow/atree v0.8.1 h1:DAnPnL9/Ks3LaAnkQVokokTBG/znTW0DJfovDtJDhLI= -github.com/onflow/atree v0.8.1/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/atree v0.9.0 h1:M+Z/UPwzv0/Yy7ChI5T1ZIHD3YN1cs/hxGEs/HWhzaY= github.com/onflow/atree v0.9.0/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= github.com/onflow/crypto v0.25.2 h1:GjHunqVt+vPcdqhxxhAXiMIF3YiLX7gTuTR5O+VG2ns= github.com/onflow/crypto v0.25.2/go.mod h1:fY7eLqUdMKV8EGOw301unP8h7PvLVy8/6gVR++/g0BY=