diff --git a/gno.land/cmd/gnoland/testdata/issue-1167.txtar b/gno.land/cmd/gnoland/testdata/issue-1167.txtar index a24cca5dce9..dfab3976a15 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1167.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1167.txtar @@ -3,40 +3,33 @@ gnoland start # add contract -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/xx -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/xx -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! # execute New -gnokey maketx call -pkgpath gno.land/r/demo/xx -func New -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/xx -func New -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! # execute Delta for the first time -gnokey maketx call -pkgpath gno.land/r/demo/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '"1,1,1;" string' # execute Delta for the second time -gnokey maketx call -pkgpath gno.land/r/demo/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '1,1,1;2,2,2;" string' # execute Delta for the third time -gnokey maketx call -pkgpath gno.land/r/demo/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '1,1,1;2,2,2;3,3,3;" string' # execute Render -gnokey maketx call -pkgpath gno.land/r/demo/xx -func Render -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/xx -func Render -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '1,1,1;2,2,2;3,3,3;" string' --- gno.mod -- -module gno.land/r/demo/xx - -require ( - gno.land/p/demo/avl v0.0.0-latest -) - -- realm.gno -- package xx diff --git a/gno.land/cmd/gnoland/testdata/issue-1326.txtar b/gno.land/cmd/gnoland/testdata/issue-1326.txtar new file mode 100644 index 00000000000..7da0931f8a3 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/issue-1326.txtar @@ -0,0 +1,67 @@ +# Reproducible Test for https://github.com/gnolang/gno/issues/1167 + +gnoland start + +# add contract +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/xx -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# execute New +gnokey maketx call -pkgpath gno.land/r/xx -func New -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# execute Delta for the first time +gnokey maketx call -pkgpath gno.land/r/xx -func Delta -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +-- realm.gno -- +package xx + +import ( + "strconv" + + "gno.land/p/demo/avl" +) + +type Move struct { + N1, N2, N3 byte +} + +type S struct { + Moves []Move +} + +func (s S) clone() S { + mv := s.Moves + return S{Moves: mv} +} + +func (olds S) change() S { + s := olds.clone() + + counter++ + s.Moves = append([]Move{}, s.Moves...) + s.Moves = append(s.Moves, Move{counter, counter, counter}) + return s +} + +var el *S +var counter byte + +func New() { + el = &S{} +} + +func Delta() string { + n := el.change() + *el = n + return Values() +} + +func Values() string { + s := "" + for _, val := range el.Moves { + s += strconv.Itoa(int(val.N1)) + "," + strconv.Itoa(int(val.N2)) + "," + strconv.Itoa(int(val.N3)) + ";" + } + return s +} diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 53d482613a1..09df5725200 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -274,7 +274,35 @@ func (pv PointerValue) Assign2(alloc *Allocator, store Store, rlm *Realm, tv2 Ty // Special case of DataByte into (base=*SliceValue).Data. pv.TV.SetDataByte(tv2.GetUint8()) return + } else if rlm != nil && pv.Base == nil && pv.Index == 0 { + // An index of zero indicates this is an assignment to a realm + // object. The base will be nil if this is being assigned to + // a dereferenced pointer value. + + // First resolve the reference value and copy the underlying object + // info along with it. + + // TODO: Should this check that tv2 type is not a reference? + if _, ok := pv.TV.V.(RefValue); ok { + *pv.TV = pv.TV.unrefCopySetRefObjectInfo(alloc, store) + } + + // We need the owner to know which parent object to mark as dirty. + // Otherwise the change will never be persisted. + obj := pv.TV.GetFirstObject(store) + pv.Base = obj.GetOwner() + if pv.Base == nil { + // Can't use GetOwnerID() if GetOwner() returns nil. Not sure why this + // returns nil, the the object ID is likely there. Use that to get the object. + objInfo := obj.GetObjectInfo() + if !objInfo.OwnerID.IsZero() { + // Finally use this as the base so DidUpdate is called and all objects + // are correctly persisted to realm storage. + pv.Base = store.GetObject(objInfo.OwnerID) + } + } } + // General case if rlm != nil && pv.Base != nil { oo1 := pv.TV.GetFirstObject(store) @@ -1029,16 +1057,32 @@ func (tv TypedValue) Copy(alloc *Allocator) (cp TypedValue) { // unrefCopy makes a copy of the underlying value in the case of reference values. // It copies other values as expected using the normal Copy method. -func (tv TypedValue) unrefCopy(alloc *Allocator, store Store) (cp TypedValue) { +func (tv TypedValue) unrefCopy(alloc *Allocator, store Store) TypedValue { + return tv.unrefCopyCore(alloc, store, false) +} + +func (tv TypedValue) unrefCopySetRefObjectInfo(alloc *Allocator, store Store) TypedValue { + return tv.unrefCopyCore(alloc, store, true) +} + +func (tv TypedValue) unrefCopyCore(alloc *Allocator, store Store, setRefObjectInfo bool) (cp TypedValue) { switch tv.V.(type) { case RefValue: cp.T = tv.T refObject := tv.GetFirstObject(store) switch refObjectValue := refObject.(type) { case *ArrayValue: - cp.V = refObjectValue.Copy(alloc) + arrayValue := refObjectValue.Copy(alloc) + if setRefObjectInfo { + arrayValue.ObjectInfo = *refObject.GetObjectInfo() + } + cp.V = arrayValue case *StructValue: - cp.V = refObjectValue.Copy(alloc) + structValue := refObjectValue.Copy(alloc) + if setRefObjectInfo { + structValue.ObjectInfo = *refObject.GetObjectInfo() + } + cp.V = structValue default: cp = tv }