diff --git a/.gitignore b/.gitignore index b5ef81d..f974cc1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ *.nef *.manifest.json config.json +!tests/testdata/**/*.nef +!tests/testdata/**/config.json /vendor/ .idea /bin/ diff --git a/tests/frostfsid_test.go b/tests/frostfsid_test.go index 2f0865a..67a5e10 100644 --- a/tests/frostfsid_test.go +++ b/tests/frostfsid_test.go @@ -2,6 +2,7 @@ package tests import ( "errors" + "fmt" "path" "testing" @@ -649,6 +650,52 @@ func TestAdditionalKeyFromPrimarySubject(t *testing.T) { invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) } +const frostfsidContractName = "frostfsid" + +func TestFrostfsid_Migration(t *testing.T) { + e := newExecutor(t) + + acc, err := wallet.NewAccount() + require.NoError(t, err) + + args := make([]any, 5) + args[0] = acc.ScriptHash() + + c := loadCompiledContract(t, e.CommitteeHash, fmt.Sprintf(contractPathFormat, v0_19_2, frostfsidContractName, frostfsidContractName), + fmt.Sprintf(manifestPathFormat, v0_19_2, frostfsidContractName)) + e.DeployContract(t, c, args) + updateContract(t, c, e, c.Hash, args) + + inv := testFrostFSIDInvoker{ + e: e, + contractHash: c.Hash, + owner: acc, + } + newSigner(t, e.CommitteeInvoker(c.Hash), acc) + require.NoError(t, err) + invoker := inv.OwnerInvoker() + + subjAPrimaryKey, err := keys.NewPrivateKey() + require.NoError(t, err) + + subjBPrimaryKey, err := keys.NewPrivateKey() + require.NoError(t, err) + subjBKeyAddr := subjBPrimaryKey.PublicKey().GetScriptHash() + + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjAPrimaryKey.PublicKey().Bytes()) + + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjBPrimaryKey.PublicKey().Bytes()) + + invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjBKeyAddr, subjAPrimaryKey.PublicKey().Bytes()) + + c1 := loadCompiledContract(t, e.CommitteeHash, fmt.Sprintf(contractPathFormat, v0_21_1, frostfsidContractName, frostfsidContractName), + fmt.Sprintf(manifestPathFormat, v0_21_1, frostfsidContractName)) + updateContractFail(t, c1, e, c.Hash, args, "frostfsid contract contains duplicate keys") + invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjBKeyAddr) + updateContract(t, c1, e, c.Hash, args) + updateContract(t, c1, e, c.Hash, args) +} + func checkPublicKeyResult(t *testing.T, s *vm.Stack, err error, key *keys.PrivateKey) { if key == nil { require.ErrorContains(t, err, "not found") diff --git a/tests/testdata/migration/0.19.2/frostfsid/config.json b/tests/testdata/migration/0.19.2/frostfsid/config.json new file mode 100755 index 0000000..7d5de27 --- /dev/null +++ b/tests/testdata/migration/0.19.2/frostfsid/config.json @@ -0,0 +1 @@ +{"name":"Identity","abi":{"methods":[{"name":"_deploy","offset":0,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addSubjectKey","offset":799,"parameters":[{"name":"addr","type":"Hash160"},{"name":"key","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"addSubjectToGroup","offset":4138,"parameters":[{"name":"addr","type":"Hash160"},{"name":"groupID","type":"Integer"}],"returntype":"Void","safe":false},{"name":"clearAdmin","offset":329,"parameters":[],"returntype":"Void","safe":false},{"name":"createGroup","offset":3165,"parameters":[{"name":"ns","type":"String"},{"name":"group","type":"String"}],"returntype":"Integer","safe":false},{"name":"createNamespace","offset":2771,"parameters":[{"name":"ns","type":"String"}],"returntype":"Void","safe":false},{"name":"createSubject","offset":495,"parameters":[{"name":"ns","type":"String"},{"name":"key","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"deleteGroup","offset":4763,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"}],"returntype":"Void","safe":false},{"name":"deleteGroupKV","offset":3989,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"},{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"deleteSubject","offset":1936,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"deleteSubjectKV","offset":1771,"parameters":[{"name":"addr","type":"Hash160"},{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"getAdmin","offset":369,"parameters":[],"returntype":"Hash160","safe":true},{"name":"getGroup","offset":3359,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getGroupByName","offset":3640,"parameters":[{"name":"ns","type":"String"},{"name":"name","type":"String"}],"returntype":"Array","safe":true},{"name":"getGroupExtended","offset":3427,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getGroupIDByName","offset":3551,"parameters":[{"name":"ns","type":"String"},{"name":"name","type":"String"}],"returntype":"Integer","safe":true},{"name":"getNamespace","offset":2887,"parameters":[{"name":"ns","type":"String"}],"returntype":"Array","safe":true},{"name":"getNamespaceExtended","offset":2957,"parameters":[{"name":"ns","type":"String"}],"returntype":"Array","safe":true},{"name":"getSubject","offset":2135,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getSubjectByKey","offset":2448,"parameters":[{"name":"key","type":"PublicKey"}],"returntype":"Array","safe":true},{"name":"getSubjectByName","offset":2636,"parameters":[{"name":"ns","type":"String"},{"name":"name","type":"String"}],"returntype":"Array","safe":true},{"name":"getSubjectExtended","offset":2244,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getSubjectKeyByName","offset":2659,"parameters":[{"name":"ns","type":"String"},{"name":"name","type":"String"}],"returntype":"PublicKey","safe":true},{"name":"listGroupSubjects","offset":4735,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"}],"returntype":"InteropInterface","safe":true},{"name":"listGroups","offset":4112,"parameters":[{"name":"ns","type":"String"}],"returntype":"InteropInterface","safe":true},{"name":"listNamespaceSubjects","offset":3139,"parameters":[{"name":"ns","type":"String"}],"returntype":"InteropInterface","safe":true},{"name":"listNamespaces","offset":3111,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"listSubjects","offset":2743,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"removeSubjectFromGroup","offset":4439,"parameters":[{"name":"addr","type":"Hash160"},{"name":"groupID","type":"Integer"}],"returntype":"Void","safe":false},{"name":"removeSubjectKey","offset":1084,"parameters":[{"name":"addr","type":"Hash160"},{"name":"key","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"setAdmin","offset":288,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"setGroupKV","offset":3854,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"},{"name":"key","type":"String"},{"name":"val","type":"String"}],"returntype":"Void","safe":false},{"name":"setGroupName","offset":3714,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"},{"name":"name","type":"String"}],"returntype":"Void","safe":false},{"name":"setSubjectKV","offset":1594,"parameters":[{"name":"addr","type":"Hash160"},{"name":"key","type":"String"},{"name":"val","type":"String"}],"returntype":"Void","safe":false},{"name":"setSubjectName","offset":1412,"parameters":[{"name":"addr","type":"Hash160"},{"name":"name","type":"String"}],"returntype":"Void","safe":false},{"name":"update","offset":395,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"version","offset":491,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"CreateSubject","parameters":[{"name":"subjectAddress","type":"Hash160"}]},{"name":"AddSubjectKey","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"subjectKey","type":"PublicKey"}]},{"name":"RemoveSubjectKey","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"subjectKey","type":"PublicKey"}]},{"name":"SetSubjectName","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"name","type":"String"}]},{"name":"SetSubjectKV","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"key","type":"String"},{"name":"value","type":"String"}]},{"name":"DeleteSubjectKV","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"key","type":"String"}]},{"name":"DeleteSubject","parameters":[{"name":"subjectAddress","type":"Hash160"}]},{"name":"CreateNamespace","parameters":[{"name":"namespace","type":"String"}]},{"name":"AddSubjectToNamespace","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"namespace","type":"String"}]},{"name":"RemoveSubjectFromNamespace","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"namespace","type":"String"}]},{"name":"CreateGroup","parameters":[{"name":"namespace","type":"String"},{"name":"group","type":"String"}]},{"name":"SetGroupName","parameters":[{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"},{"name":"name","type":"String"}]},{"name":"SetGroupKV","parameters":[{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"},{"name":"key","type":"String"},{"name":"value","type":"String"}]},{"name":"DeleteGroupKV","parameters":[{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"},{"name":"key","type":"String"}]},{"name":"AddSubjectToGroup","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"}]},{"name":"RemoveSubjectFromGroup","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"}]},{"name":"DeleteGroup","parameters":[{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file diff --git a/tests/testdata/migration/0.19.2/frostfsid/frostfsid_contract.nef b/tests/testdata/migration/0.19.2/frostfsid/frostfsid_contract.nef new file mode 100755 index 0000000..e952f07 Binary files /dev/null and b/tests/testdata/migration/0.19.2/frostfsid/frostfsid_contract.nef differ diff --git a/tests/testdata/migration/0.21.1/frostfsid/config.json b/tests/testdata/migration/0.21.1/frostfsid/config.json new file mode 100755 index 0000000..4e42b82 --- /dev/null +++ b/tests/testdata/migration/0.21.1/frostfsid/config.json @@ -0,0 +1 @@ +{"name":"Identity","abi":{"methods":[{"name":"_deploy","offset":0,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addSubjectKey","offset":1157,"parameters":[{"name":"addr","type":"Hash160"},{"name":"key","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"addSubjectToGroup","offset":4819,"parameters":[{"name":"addr","type":"Hash160"},{"name":"groupID","type":"Integer"}],"returntype":"Void","safe":false},{"name":"clearAdmin","offset":614,"parameters":[],"returntype":"Void","safe":false},{"name":"createGroup","offset":3846,"parameters":[{"name":"ns","type":"String"},{"name":"group","type":"String"}],"returntype":"Integer","safe":false},{"name":"createNamespace","offset":3452,"parameters":[{"name":"ns","type":"String"}],"returntype":"Void","safe":false},{"name":"createSubject","offset":780,"parameters":[{"name":"ns","type":"String"},{"name":"key","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"deleteGroup","offset":5444,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"}],"returntype":"Void","safe":false},{"name":"deleteGroupKV","offset":4670,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"},{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"deleteSubject","offset":2368,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"deleteSubjectKV","offset":2203,"parameters":[{"name":"addr","type":"Hash160"},{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"getAdmin","offset":654,"parameters":[],"returntype":"Hash160","safe":true},{"name":"getGroup","offset":4040,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getGroupByName","offset":4321,"parameters":[{"name":"ns","type":"String"},{"name":"name","type":"String"}],"returntype":"Array","safe":true},{"name":"getGroupExtended","offset":4108,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getGroupIDByName","offset":4232,"parameters":[{"name":"ns","type":"String"},{"name":"name","type":"String"}],"returntype":"Integer","safe":true},{"name":"getNamespace","offset":3568,"parameters":[{"name":"ns","type":"String"}],"returntype":"Array","safe":true},{"name":"getNamespaceExtended","offset":3638,"parameters":[{"name":"ns","type":"String"}],"returntype":"Array","safe":true},{"name":"getSubject","offset":2593,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getSubjectByKey","offset":2942,"parameters":[{"name":"key","type":"PublicKey"}],"returntype":"Array","safe":true},{"name":"getSubjectByName","offset":3174,"parameters":[{"name":"ns","type":"String"},{"name":"name","type":"String"}],"returntype":"Array","safe":true},{"name":"getSubjectExtended","offset":2738,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getSubjectKV","offset":3281,"parameters":[{"name":"addr","type":"Hash160"},{"name":"name","type":"String"}],"returntype":"String","safe":true},{"name":"getSubjectKeyByName","offset":3197,"parameters":[{"name":"ns","type":"String"},{"name":"name","type":"String"}],"returntype":"PublicKey","safe":true},{"name":"listGroupSubjects","offset":5416,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"}],"returntype":"InteropInterface","safe":true},{"name":"listGroups","offset":4793,"parameters":[{"name":"ns","type":"String"}],"returntype":"InteropInterface","safe":true},{"name":"listNamespaceSubjects","offset":3820,"parameters":[{"name":"ns","type":"String"}],"returntype":"InteropInterface","safe":true},{"name":"listNamespaces","offset":3792,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"listSubjects","offset":3424,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"removeSubjectFromGroup","offset":5120,"parameters":[{"name":"addr","type":"Hash160"},{"name":"groupID","type":"Integer"}],"returntype":"Void","safe":false},{"name":"removeSubjectKey","offset":1494,"parameters":[{"name":"addr","type":"Hash160"},{"name":"key","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"setAdmin","offset":573,"parameters":[{"name":"addr","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"setGroupKV","offset":4535,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"},{"name":"key","type":"String"},{"name":"val","type":"String"}],"returntype":"Void","safe":false},{"name":"setGroupName","offset":4395,"parameters":[{"name":"ns","type":"String"},{"name":"groupID","type":"Integer"},{"name":"name","type":"String"}],"returntype":"Void","safe":false},{"name":"setSubjectKV","offset":2026,"parameters":[{"name":"addr","type":"Hash160"},{"name":"key","type":"String"},{"name":"val","type":"String"}],"returntype":"Void","safe":false},{"name":"setSubjectName","offset":1844,"parameters":[{"name":"addr","type":"Hash160"},{"name":"name","type":"String"}],"returntype":"Void","safe":false},{"name":"update","offset":680,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"version","offset":776,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"CreateSubject","parameters":[{"name":"subjectAddress","type":"Hash160"}]},{"name":"AddSubjectKey","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"subjectKey","type":"PublicKey"}]},{"name":"RemoveSubjectKey","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"subjectKey","type":"PublicKey"}]},{"name":"SetSubjectName","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"name","type":"String"}]},{"name":"SetSubjectKV","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"key","type":"String"},{"name":"value","type":"String"}]},{"name":"DeleteSubjectKV","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"key","type":"String"}]},{"name":"DeleteSubject","parameters":[{"name":"subjectAddress","type":"Hash160"}]},{"name":"CreateNamespace","parameters":[{"name":"namespace","type":"String"}]},{"name":"AddSubjectToNamespace","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"namespace","type":"String"}]},{"name":"RemoveSubjectFromNamespace","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"namespace","type":"String"}]},{"name":"CreateGroup","parameters":[{"name":"namespace","type":"String"},{"name":"group","type":"String"}]},{"name":"SetGroupName","parameters":[{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"},{"name":"name","type":"String"}]},{"name":"SetGroupKV","parameters":[{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"},{"name":"key","type":"String"},{"name":"value","type":"String"}]},{"name":"DeleteGroupKV","parameters":[{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"},{"name":"key","type":"String"}]},{"name":"AddSubjectToGroup","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"}]},{"name":"RemoveSubjectFromGroup","parameters":[{"name":"subjectAddress","type":"Hash160"},{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"}]},{"name":"DeleteGroup","parameters":[{"name":"namespace","type":"String"},{"name":"groupID","type":"Integer"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file diff --git a/tests/testdata/migration/0.21.1/frostfsid/frostfsid_contract.nef b/tests/testdata/migration/0.21.1/frostfsid/frostfsid_contract.nef new file mode 100755 index 0000000..5250178 Binary files /dev/null and b/tests/testdata/migration/0.21.1/frostfsid/frostfsid_contract.nef differ diff --git a/tests/util.go b/tests/util.go index ed7c119..617528a 100644 --- a/tests/util.go +++ b/tests/util.go @@ -7,6 +7,7 @@ import ( "testing" "time" + json "github.com/nspcc-dev/go-ordered-json" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/consensus" "github.com/nspcc-dev/neo-go/pkg/core" @@ -20,6 +21,8 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/services/rpcsrv" "github.com/nspcc-dev/neo-go/pkg/services/stateroot" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -226,3 +229,56 @@ func runRPC(ctx context.Context, t *testing.T, chain *core.Blockchain, walletPat } } } + +func loadCompiledContract(t testing.TB, sender util.Uint160, nefPath, manifestPath string) *neotest.Contract { + nefBytes, err := os.ReadFile(nefPath) + require.NoError(t, err) + + f, err := nef.FileFromBytes(nefBytes) + require.NoError(t, err) + + manifestBytes, err := os.ReadFile(manifestPath) + require.NoError(t, err) + + m := new(manifest.Manifest) + err = json.Unmarshal(manifestBytes, m) + require.NoError(t, err) + + hash := state.CreateContractHash(sender, f.Checksum, m.Name) + + return &neotest.Contract{ + Hash: hash, + NEF: &f, + Manifest: m, + } +} + +const contractPathFormat = "testdata/migration/%s/%s/%s_contract.nef" +const manifestPathFormat = "testdata/migration/%s/%s/config.json" + +const v0_19_2 = "0.19.2" +const v0_21_1 = "0.21.1" + +func updateContract(t testing.TB, c *neotest.Contract, e *neotest.Executor, contractHash util.Uint160, data any) { + neb, err := c.NEF.Bytes() + require.NoError(t, err) + + rawManifest, err := json.Marshal(c.Manifest) + require.NoError(t, err) + + inv := e.NewInvoker(contractHash, e.Committee) + + inv.Invoke(t, nil, "update", neb, rawManifest, data) +} + +func updateContractFail(t testing.TB, c *neotest.Contract, e *neotest.Executor, contractHash util.Uint160, data any, faultException string) { + neb, err := c.NEF.Bytes() + require.NoError(t, err) + + rawManifest, err := json.Marshal(c.Manifest) + require.NoError(t, err) + + inv := e.NewInvoker(contractHash, e.Committee) + + inv.InvokeFail(t, faultException, "update", neb, rawManifest, data) +}