diff --git a/contracts/container/config.yml b/contracts/container/config.yml index 4cd139a9..73a74b47 100644 --- a/contracts/container/config.yml +++ b/contracts/container/config.yml @@ -32,3 +32,11 @@ events: parameters: - name: ContainerID type: hash256 + - name: ObjectPut + parameters: + - name: ContainerID + type: hash256 + - name: ObjectID + type: hash256 +overloads: + putMeta: put diff --git a/contracts/container/contract.go b/contracts/container/contract.go index 110c2722..1cb6362c 100644 --- a/contracts/container/contract.go +++ b/contracts/container/contract.go @@ -61,15 +61,16 @@ const ( // V2 format. containerIDSize = interop.Hash256Len // SHA256 size - singleEstimatePrefix = "est" - estimateKeyPrefix = "cnr" - containerKeyPrefix = 'x' - ownerKeyPrefix = 'o' - deletedKeyPrefix = 'd' - nodesPrefix = 'n' - replicasNumberPrefix = 'r' - nextEpochNodesPrefix = 'u' - estimatePostfixSize = 10 + singleEstimatePrefix = "est" + estimateKeyPrefix = "cnr" + containersWithMetaPrefix = 'm' + containerKeyPrefix = 'x' + ownerKeyPrefix = 'o' + deletedKeyPrefix = 'd' + nodesPrefix = 'n' + replicasNumberPrefix = 'r' + nextEpochNodesPrefix = 'u' + estimatePostfixSize = 10 // default SOA record field values. defaultRefresh = 3600 // 1 hour @@ -238,6 +239,42 @@ func Update(script []byte, manifest []byte, data any) { runtime.Log("container contract updated") } +// PutObject registers successful object PUT operation and notifies about +// it. metaInformation must be signed by container nodes according to +// container's placement, see [VerifyPlacementSignatures]. metaInformation +// must contain information about an object placed to a container that was +// created with meta-on-chain feature enabled. +func PutObject(metaInformation []byte, sigs [][]interop.Signature) { + metaMap := std.Deserialize(metaInformation).(map[string]any) + cID := metaMap["cid"].(interop.Hash256) + if len(cID) != interop.Hash256Len { + panic("incorrect container ID") + } + + if storage.Get(storage.GetContext(), append([]byte{containersWithMetaPrefix}, cID...)) == nil { + panic("container does not support meta-on-chain") + } + + if !VerifyPlacementSignatures(cID, metaInformation, sigs) { + panic("signature verification failed") + } + + oid := metaMap["oid"].(interop.Hash256) + runtime.Notify("ObjectPut", cID, oid) +} + +// PutMeta is the same as [Put] (and exposed as put from the contract via +// overload), but allows container's meta-information be handled and notified +// using the chain. +func PutMeta(container []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte, metaOnChain bool) { + if metaOnChain { + ctx := storage.GetContext() + cID := crypto.Sha256(container) + storage.Put(ctx, append([]byte{containersWithMetaPrefix}, cID...), []byte{}) + } + Put(container, signature, publicKey, token) +} + // Put method creates a new container if it has been invoked by Alphabet nodes // of the Inner Ring. Otherwise, it produces containerPut notification. // @@ -950,6 +987,7 @@ func removeContainer(ctx storage.Context, id []byte, owner []byte) { storage.Delete(ctx, containerListKey) storage.Delete(ctx, append([]byte{containerKeyPrefix}, id...)) + storage.Delete(ctx, append([]byte{containersWithMetaPrefix}, id...)) storage.Delete(ctx, append(eACLPrefix, id...)) storage.Put(ctx, append([]byte{deletedKeyPrefix}, id...), []byte{}) } diff --git a/contracts/container/contract.nef b/contracts/container/contract.nef index 031ad8bd..03c5f5bc 100755 Binary files a/contracts/container/contract.nef and b/contracts/container/contract.nef differ diff --git a/contracts/container/manifest.json b/contracts/container/manifest.json index b493d473..3ef3321a 100755 --- a/contracts/container/manifest.json +++ b/contracts/container/manifest.json @@ -1 +1 @@ -{"name":"NeoFS Container","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":83,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addNextEpochNodes","offset":3988,"parameters":[{"name":"cID","type":"Hash256"},{"name":"placementVector","type":"Integer"},{"name":"publicKeys","type":"Array"}],"returntype":"Void","safe":false},{"name":"alias","offset":3698,"parameters":[{"name":"cid","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"commitContainerListUpdate","offset":4752,"parameters":[{"name":"cID","type":"Hash256"},{"name":"replicas","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"containersOf","offset":3838,"parameters":[{"name":"owner","type":"ByteArray"}],"returntype":"InteropInterface","safe":true},{"name":"count","offset":3793,"parameters":[],"returntype":"Integer","safe":true},{"name":"delete","offset":3195,"parameters":[{"name":"containerID","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"token","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"eACL","offset":5688,"parameters":[{"name":"containerID","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"get","offset":3585,"parameters":[{"name":"containerID","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"getContainerSize","offset":5948,"parameters":[{"name":"id","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"iterateAllContainerSizes","offset":6321,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"InteropInterface","safe":true},{"name":"iterateContainerSizes","offset":6223,"parameters":[{"name":"epoch","type":"Integer"},{"name":"cid","type":"Hash256"}],"returntype":"InteropInterface","safe":true},{"name":"list","offset":3892,"parameters":[{"name":"owner","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"listContainerSizes","offset":6062,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Array","safe":true},{"name":"newEpoch","offset":6373,"parameters":[{"name":"epochNum","type":"Integer"}],"returntype":"Void","safe":false},{"name":"nodes","offset":5311,"parameters":[{"name":"cID","type":"Hash256"},{"name":"placementVector","type":"Integer"}],"returntype":"InteropInterface","safe":true},{"name":"onNEP11Payment","offset":1647,"parameters":[{"name":"a","type":"Hash160"},{"name":"b","type":"Integer"},{"name":"c","type":"ByteArray"},{"name":"d","type":"Any"}],"returntype":"Void","safe":false},{"name":"owner","offset":3647,"parameters":[{"name":"containerID","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"put","offset":2038,"parameters":[{"name":"container","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"putContainerSize","offset":5746,"parameters":[{"name":"epoch","type":"Integer"},{"name":"cid","type":"ByteArray"},{"name":"usedSize","type":"Integer"},{"name":"pubKey","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"putNamed","offset":2054,"parameters":[{"name":"container","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"},{"name":"name","type":"String"},{"name":"zone","type":"String"}],"returntype":"Void","safe":false},{"name":"replicasNumbers","offset":5213,"parameters":[{"name":"cID","type":"Hash256"}],"returntype":"InteropInterface","safe":true},{"name":"setEACL","offset":5435,"parameters":[{"name":"eACL","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"startContainerEstimation","offset":6396,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Void","safe":false},{"name":"stopContainerEstimation","offset":6470,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Void","safe":false},{"name":"update","offset":1905,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"verifyPlacementSignatures","offset":4586,"parameters":[{"name":"cid","type":"Hash256"},{"name":"msg","type":"ByteArray"},{"name":"sigs","type":"Array"}],"returntype":"Boolean","safe":true},{"name":"version","offset":6543,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"PutSuccess","parameters":[{"name":"containerID","type":"Hash256"},{"name":"publicKey","type":"PublicKey"}]},{"name":"DeleteSuccess","parameters":[{"name":"containerID","type":"ByteArray"}]},{"name":"SetEACLSuccess","parameters":[{"name":"containerID","type":"ByteArray"},{"name":"publicKey","type":"PublicKey"}]},{"name":"StartEstimation","parameters":[{"name":"epoch","type":"Integer"}]},{"name":"StopEstimation","parameters":[{"name":"epoch","type":"Integer"}]},{"name":"NodesUpdate","parameters":[{"name":"ContainerID","type":"Hash256"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update","addKey","transferX","register","registerTLD","addRecord","deleteRecords","subscribeForNewEpoch"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file +{"name":"NeoFS Container","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":83,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addNextEpochNodes","offset":4279,"parameters":[{"name":"cID","type":"Hash256"},{"name":"placementVector","type":"Integer"},{"name":"publicKeys","type":"Array"}],"returntype":"Void","safe":false},{"name":"alias","offset":3989,"parameters":[{"name":"cid","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"commitContainerListUpdate","offset":5043,"parameters":[{"name":"cID","type":"Hash256"},{"name":"replicas","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"containersOf","offset":4129,"parameters":[{"name":"owner","type":"ByteArray"}],"returntype":"InteropInterface","safe":true},{"name":"count","offset":4084,"parameters":[],"returntype":"Integer","safe":true},{"name":"delete","offset":3486,"parameters":[{"name":"containerID","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"token","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"eACL","offset":5979,"parameters":[{"name":"containerID","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"get","offset":3876,"parameters":[{"name":"containerID","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"getContainerSize","offset":6239,"parameters":[{"name":"id","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"iterateAllContainerSizes","offset":6612,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"InteropInterface","safe":true},{"name":"iterateContainerSizes","offset":6514,"parameters":[{"name":"epoch","type":"Integer"},{"name":"cid","type":"Hash256"}],"returntype":"InteropInterface","safe":true},{"name":"list","offset":4183,"parameters":[{"name":"owner","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"listContainerSizes","offset":6353,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Array","safe":true},{"name":"newEpoch","offset":6664,"parameters":[{"name":"epochNum","type":"Integer"}],"returntype":"Void","safe":false},{"name":"nodes","offset":5602,"parameters":[{"name":"cID","type":"Hash256"},{"name":"placementVector","type":"Integer"}],"returntype":"InteropInterface","safe":true},{"name":"onNEP11Payment","offset":1647,"parameters":[{"name":"a","type":"Hash160"},{"name":"b","type":"Integer"},{"name":"c","type":"ByteArray"},{"name":"d","type":"Any"}],"returntype":"Void","safe":false},{"name":"owner","offset":3938,"parameters":[{"name":"containerID","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"put","offset":2329,"parameters":[{"name":"container","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"putContainerSize","offset":6037,"parameters":[{"name":"epoch","type":"Integer"},{"name":"cid","type":"ByteArray"},{"name":"usedSize","type":"Integer"},{"name":"pubKey","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"put","offset":2269,"parameters":[{"name":"container","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"},{"name":"metaOnChain","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"putNamed","offset":2345,"parameters":[{"name":"container","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"},{"name":"name","type":"String"},{"name":"zone","type":"String"}],"returntype":"Void","safe":false},{"name":"putObject","offset":2038,"parameters":[{"name":"metaInformation","type":"ByteArray"},{"name":"sigs","type":"Array"}],"returntype":"Void","safe":false},{"name":"replicasNumbers","offset":5504,"parameters":[{"name":"cID","type":"Hash256"}],"returntype":"InteropInterface","safe":true},{"name":"setEACL","offset":5726,"parameters":[{"name":"eACL","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"startContainerEstimation","offset":6687,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Void","safe":false},{"name":"stopContainerEstimation","offset":6761,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Void","safe":false},{"name":"update","offset":1905,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"verifyPlacementSignatures","offset":4877,"parameters":[{"name":"cid","type":"Hash256"},{"name":"msg","type":"ByteArray"},{"name":"sigs","type":"Array"}],"returntype":"Boolean","safe":true},{"name":"version","offset":6834,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"PutSuccess","parameters":[{"name":"containerID","type":"Hash256"},{"name":"publicKey","type":"PublicKey"}]},{"name":"DeleteSuccess","parameters":[{"name":"containerID","type":"ByteArray"}]},{"name":"SetEACLSuccess","parameters":[{"name":"containerID","type":"ByteArray"},{"name":"publicKey","type":"PublicKey"}]},{"name":"StartEstimation","parameters":[{"name":"epoch","type":"Integer"}]},{"name":"StopEstimation","parameters":[{"name":"epoch","type":"Integer"}]},{"name":"NodesUpdate","parameters":[{"name":"ContainerID","type":"Hash256"}]},{"name":"ObjectPut","parameters":[{"name":"ContainerID","type":"Hash256"},{"name":"ObjectID","type":"Hash256"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update","addKey","transferX","register","registerTLD","addRecord","deleteRecords","subscribeForNewEpoch"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file diff --git a/rpc/container/rpcbinding.go b/rpc/container/rpcbinding.go index 23739bfb..6f367ef3 100644 --- a/rpc/container/rpcbinding.go +++ b/rpc/container/rpcbinding.go @@ -84,6 +84,12 @@ type NodesUpdateEvent struct { ContainerID util.Uint256 } +// ObjectPutEvent represents "ObjectPut" event emitted by the contract. +type ObjectPutEvent struct { + ContainerID util.Uint256 + ObjectID util.Uint256 +} + // Invoker is used by ContractReader to call various safe methods. type Invoker interface { Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) @@ -379,6 +385,28 @@ func (c *Contract) PutContainerSizeUnsigned(epoch *big.Int, cid []byte, usedSize return c.actor.MakeUnsignedCall(c.hash, "putContainerSize", nil, epoch, cid, usedSize, pubKey) } +// Put2 creates a transaction invoking `put` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Put2(container []byte, signature []byte, publicKey *keys.PublicKey, token []byte, metaOnChain bool) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "put", container, signature, publicKey, token, metaOnChain) +} + +// Put2Transaction creates a transaction invoking `put` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) Put2Transaction(container []byte, signature []byte, publicKey *keys.PublicKey, token []byte, metaOnChain bool) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "put", container, signature, publicKey, token, metaOnChain) +} + +// Put2Unsigned creates a transaction invoking `put` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) Put2Unsigned(container []byte, signature []byte, publicKey *keys.PublicKey, token []byte, metaOnChain bool) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "put", nil, container, signature, publicKey, token, metaOnChain) +} + // PutNamed creates a transaction invoking `putNamed` method of the contract. // This transaction is signed and immediately sent to the network. // The values returned are its hash, ValidUntilBlock value and error if any. @@ -401,6 +429,28 @@ func (c *Contract) PutNamedUnsigned(container []byte, signature []byte, publicKe return c.actor.MakeUnsignedCall(c.hash, "putNamed", nil, container, signature, publicKey, token, name, zone) } +// PutObject creates a transaction invoking `putObject` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) PutObject(metaInformation []byte, sigs [][][]byte) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "putObject", metaInformation, sigs) +} + +// PutObjectTransaction creates a transaction invoking `putObject` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) PutObjectTransaction(metaInformation []byte, sigs [][][]byte) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "putObject", metaInformation, sigs) +} + +// PutObjectUnsigned creates a transaction invoking `putObject` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) PutObjectUnsigned(metaInformation []byte, sigs [][][]byte) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "putObject", nil, metaInformation, sigs) +} + // SetEACL creates a transaction invoking `setEACL` method of the contract. // This transaction is signed and immediately sent to the network. // The values returned are its hash, ValidUntilBlock value and error if any. @@ -1148,3 +1198,81 @@ func (e *NodesUpdateEvent) FromStackItem(item *stackitem.Array) error { return nil } + +// ObjectPutEventsFromApplicationLog retrieves a set of all emitted events +// with "ObjectPut" name from the provided [result.ApplicationLog]. +func ObjectPutEventsFromApplicationLog(log *result.ApplicationLog) ([]*ObjectPutEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*ObjectPutEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "ObjectPut" { + continue + } + event := new(ObjectPutEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize ObjectPutEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to ObjectPutEvent or +// returns an error if it's not possible to do to so. +func (e *ObjectPutEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.ContainerID, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field ContainerID: %w", err) + } + + index++ + e.ObjectID, err = func(item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field ObjectID: %w", err) + } + + return nil +} diff --git a/tests/container_test.go b/tests/container_test.go index 0a12c32a..34a85924 100644 --- a/tests/container_test.go +++ b/tests/container_test.go @@ -804,3 +804,44 @@ func stackWithBool(t *testing.T, stack *vm.Stack, v bool) { require.True(t, ok) require.Equal(t, v, res) } + +func TestPutMeta(t *testing.T) { + c, cBal, _ := newContainerInvoker(t, false) + + t.Run("meta disabled", func(t *testing.T) { + acc := c.NewAccount(t) + cnt := dummyContainer(acc) + balanceMint(t, cBal, acc, containerFee*1, []byte{}) + + metaInfo, err := stackitem.Serialize(stackitem.NewMapWithValue( + []stackitem.MapElement{{Key: stackitem.Make("cid"), Value: stackitem.Make(cnt.id[:])}})) + require.NoError(t, err) + + c.Invoke(t, stackitem.Null{}, "put", cnt.value, cnt.sig, cnt.pub, cnt.token, false) + c.InvokeFail(t, "container does not support meta-on-chain", "putObject", metaInfo, nil) + + expected := stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray(cnt.value), + stackitem.NewByteArray(cnt.sig), + stackitem.NewByteArray(cnt.pub), + stackitem.NewByteArray(cnt.token), + }) + c.Invoke(t, expected, "get", cnt.id[:]) + }) + + t.Run("meta enabled", func(t *testing.T) { + acc := c.NewAccount(t) + cnt := dummyContainer(acc) + balanceMint(t, cBal, acc, containerFee*1, []byte{}) + + metaInfo, err := stackitem.Serialize(stackitem.NewMapWithValue( + []stackitem.MapElement{ + {Key: stackitem.Make("cid"), Value: stackitem.Make(cnt.id[:])}, + {Key: stackitem.Make("oid"), Value: stackitem.Make(cnt.id[:])}, + })) + require.NoError(t, err) + + c.Invoke(t, stackitem.Null{}, "put", cnt.value, cnt.sig, cnt.pub, cnt.token, true) + c.Invoke(t, stackitem.Null{}, "putObject", metaInfo, nil) + }) +}