From b60e73899a17f243e2dcab4c8853dbd0e9a6b060 Mon Sep 17 00:00:00 2001 From: Andrew Hariri Date: Sat, 3 Feb 2024 13:37:03 -0800 Subject: [PATCH] [move] Move language support --- packages/tm-grammars/NOTICE | 207 +++ packages/tm-grammars/README.md | 5 +- packages/tm-grammars/grammars/glimmer-js.json | 2 +- packages/tm-grammars/grammars/glimmer-ts.json | 2 +- packages/tm-grammars/grammars/move.json | 945 ++++++++++++ packages/tm-grammars/index.d.ts | 2 +- packages/tm-grammars/index.js | 31 +- samples/move.sample | 1358 +++++++++++++++++ sources-grammars.ts | 8 + 9 files changed, 2547 insertions(+), 13 deletions(-) create mode 100644 packages/tm-grammars/grammars/move.json create mode 100644 samples/move.sample diff --git a/packages/tm-grammars/NOTICE b/packages/tm-grammars/NOTICE index 5fca584..2a45f2a 100644 --- a/packages/tm-grammars/NOTICE +++ b/packages/tm-grammars/NOTICE @@ -4271,6 +4271,213 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================================================================================= +Files: move.json +License: https://raw.githubusercontent.com/pontem-network/vscode-move-ide/master/LICENSE +SPDX: Apache-2.0 +--------------------------------------------------------------------------------------------------------- + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Wings Stiftung + + 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. + ========================================================================================================= Files: narrat.json License: https://raw.githubusercontent.com/liana-p/narrat-syntax-highlighting-vscode/main/LICENSE diff --git a/packages/tm-grammars/README.md b/packages/tm-grammars/README.md index 23d77c9..0c83229 100644 --- a/packages/tm-grammars/README.md +++ b/packages/tm-grammars/README.md @@ -75,8 +75,8 @@ import { grammars } from 'tm-grammars' | `gherkin` | | [alexkrechik/VSCucumberAutoComplete](https://github.com/alexkrechik/VSCucumberAutoComplete/blob/fc8da969b1c83e13ef5124b1ec90ba5211bc1e5f/gclient/syntaxes/feature.tmLanguage) | [MIT](https://raw.githubusercontent.com/alexkrechik/VSCucumberAutoComplete/master/LICENSE) | | 12.13 kB | | `git-commit` | | [microsoft/vscode](https://github.com/microsoft/vscode/blob/f8c3f89468fea14103d8790d59ea8594d6e644eb/extensions/git-base/syntaxes/git-commit.tmLanguage.json) | [MIT](https://raw.githubusercontent.com/microsoft/vscode/main/LICENSE.txt) | `diff` | 1.36 kB | | `git-rebase` | | [microsoft/vscode](https://github.com/microsoft/vscode/blob/e95c74c4c7af876e79ec58df262464467c06df28/extensions/git-base/syntaxes/git-rebase.tmLanguage.json) | [MIT](https://raw.githubusercontent.com/microsoft/vscode/main/LICENSE.txt) | `shellscript` | 818.00 B | -| `glimmer-js` | `gjs` | [lifeart/vsc-ember-syntax](https://github.com/lifeart/vsc-ember-syntax/blob/f031ed7df2eeced760f4469fc9fe06f8688c531a/syntaxes/source.gjs.json) | [MIT](https://raw.githubusercontent.com/lifeart/vsc-ember-syntax/master/LICENSE) | `javascript` `typescript` `css` `html` | 19.08 kB | -| `glimmer-ts` | `gts` | [lifeart/vsc-ember-syntax](https://github.com/lifeart/vsc-ember-syntax/blob/f031ed7df2eeced760f4469fc9fe06f8688c531a/syntaxes/source.gts.json) | [MIT](https://raw.githubusercontent.com/lifeart/vsc-ember-syntax/master/LICENSE) | `typescript` `css` `javascript` `html` | 19.08 kB | +| `glimmer-js` | `gjs` | [lifeart/vsc-ember-syntax](https://github.com/lifeart/vsc-ember-syntax/blob/312fb20cc04374eb8768369ebccabd12143edbe4/syntaxes/source.gjs.json) | [MIT](https://raw.githubusercontent.com/lifeart/vsc-ember-syntax/master/LICENSE) | `javascript` `typescript` `css` `html` | 19.08 kB | +| `glimmer-ts` | `gts` | [lifeart/vsc-ember-syntax](https://github.com/lifeart/vsc-ember-syntax/blob/312fb20cc04374eb8768369ebccabd12143edbe4/syntaxes/source.gts.json) | [MIT](https://raw.githubusercontent.com/lifeart/vsc-ember-syntax/master/LICENSE) | `typescript` `css` `javascript` `html` | 19.08 kB | | `glsl` | | [polym0rph/GLSL.tmbundle](https://github.com/polym0rph/GLSL.tmbundle/blob/6998d3bbd204e26746a5ca580cda4c650b9057b1/Syntaxes/GLSL.tmLanguage) | | `c` | 3.69 kB | | `gnuplot` | | [MarioSchwalbe/vscode-gnuplot](https://github.com/MarioSchwalbe/vscode-gnuplot/blob/c62c1a61c9b6b4658b43d76c397dc2ad43523b6a/syntaxes/gnuplot.tmLanguage) | [GPL-3.0](https://raw.githubusercontent.com/MarioSchwalbe/vscode-gnuplot/master/LICENSE) | | 16.46 kB | | `go` | | [microsoft/vscode](https://github.com/microsoft/vscode/blob/35d97bc7e439fce0f50f42074041ab2d8571b20a/extensions/go/syntaxes/go.tmLanguage.json) | [MIT](https://raw.githubusercontent.com/microsoft/vscode/main/LICENSE.txt) | | 41.19 kB | @@ -122,6 +122,7 @@ import { grammars } from 'tm-grammars' | `mdx` | | [wooorm/markdown-tm-language](https://github.com/wooorm/markdown-tm-language/blob/371d61df9ddc3850e12aabe61b602d02e259e8a4/source.mdx.tmLanguage) | [MIT](https://raw.githubusercontent.com/wooorm/markdown-tm-language/main/license) | `tsx` `toml` `yaml` `c` `clojure` `coffee` `cpp` `csharp` `css` `diff` `docker` `elixir` `elm` `erlang` `go` `graphql` `haskell` `html` `ini` `java` `javascript` `json` `julia` `kotlin` `less` `lua` `make` `markdown` `objective-c` `perl` `python` `r` `ruby` `rust` `scala` `scss` `shellscript` `shellsession` `sql` `xml` `swift` `typescript` | 120.76 kB | | `mermaid` | | [bpruitt-goddard/vscode-mermaid-syntax-highlight](https://github.com/bpruitt-goddard/vscode-mermaid-syntax-highlight/blob/8b62f487cb7a89afcd152febfbf47f5d4787657f/syntaxes/mermaid.tmLanguage.yaml) | [MIT](https://raw.githubusercontent.com/bpruitt-goddard/vscode-mermaid-syntax-highlight/master/LICENSE) | | 19.88 kB | | `mojo` | | [modularml/mojo-syntax](https://github.com/modularml/mojo-syntax/blob/a2241dda06d9a20503394c3affa606114d007049/syntaxes/mojo.syntax.json) | [MIT](https://raw.githubusercontent.com/modularml/mojo-syntax/main/LICENSE) | | 72.95 kB | +| `move` | | [pontem-network/vscode-move-ide](https://github.com/pontem-network/vscode-move-ide/blob/74ba14e513acfc1fe8b9bc6107e88c182bb9a7b4/syntaxes/move.tmLanguage.json) | [Apache-2.0](https://raw.githubusercontent.com/pontem-network/vscode-move-ide/master/LICENSE) | | 11.23 kB | | `narrat` | `nar` | [liana-p/narrat-syntax-highlighting-vscode](https://github.com/liana-p/narrat-syntax-highlighting-vscode/blob/00d4b410338fc50ca0ce77a1f7e873c1fb66d376/syntaxes/narrat.tmLanguage.yaml) | [MIT](https://raw.githubusercontent.com/liana-p/narrat-syntax-highlighting-vscode/main/LICENSE) | | 3.41 kB | | `nextflow` | `nf` | [nextflow-io/vscode-language-nextflow](https://github.com/nextflow-io/vscode-language-nextflow/blob/7eeb9be8d01556b7c51c59307275c2f720f2ddf4/syntaxes/nextflow.tmLanguage.json) | [MIT](https://raw.githubusercontent.com/nextflow-io/vscode-language-nextflow/master/LICENSE.md) | | 4.40 kB | | `nginx` | | [hangxingliu/vscode-nginx-conf-hint](https://github.com/hangxingliu/vscode-nginx-conf-hint/blob/0582d5b71a31ff893b3587996b233f22239fba57/src/syntax/nginx.tmLanguage) | [GPL-3.0](https://raw.githubusercontent.com/hangxingliu/vscode-nginx-conf-hint/main/LICENSE) | `lua` | 32.83 kB | diff --git a/packages/tm-grammars/grammars/glimmer-js.json b/packages/tm-grammars/grammars/glimmer-js.json index d3801c3..70e4011 100644 --- a/packages/tm-grammars/grammars/glimmer-js.json +++ b/packages/tm-grammars/grammars/glimmer-js.json @@ -281,7 +281,7 @@ "match": "(::|_|\\$|\\.)" }, "glimmer-control-expression": { - "begin": "({{~?)(([-a-z/]+)\\s)", + "begin": "({{~?)(([-a-zA-Z_0-9/]+)\\s)", "captures": { "1": { "name": "keyword.operator" diff --git a/packages/tm-grammars/grammars/glimmer-ts.json b/packages/tm-grammars/grammars/glimmer-ts.json index 06a7b15..d4b862c 100644 --- a/packages/tm-grammars/grammars/glimmer-ts.json +++ b/packages/tm-grammars/grammars/glimmer-ts.json @@ -281,7 +281,7 @@ "match": "(::|_|\\$|\\.)" }, "glimmer-control-expression": { - "begin": "({{~?)(([-a-z/]+)\\s)", + "begin": "({{~?)(([-a-zA-Z_0-9/]+)\\s)", "captures": { "1": { "name": "keyword.operator" diff --git a/packages/tm-grammars/grammars/move.json b/packages/tm-grammars/grammars/move.json new file mode 100644 index 0000000..8ee887b --- /dev/null +++ b/packages/tm-grammars/grammars/move.json @@ -0,0 +1,945 @@ +{ + "displayName": "Move", + "name": "move", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#address" + }, + { + "include": "#module" + }, + { + "include": "#script" + } + ], + "repository": { + "address": { + "begin": "\\b(address)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.address.move" + } + }, + "end": "(?<=})", + "name": "meta.address.move", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "(?<=address)", + "comment": "Address literal", + "end": "(?={)", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#address_literal" + } + ] + }, + { + "include": "#module" + } + ] + }, + "address_literal": { + "patterns": [ + { + "comment": "16-byte (or shorter) hex address in Libra", + "match": "\\b(0x[A-Fa-f0-9][A-Fa-f0-9]{,31})\\b", + "name": "support.constant.diem.address.move" + }, + { + "comment": "Bech32 with wallet1 prefix", + "match": "\\b(wallet1\\w{38})", + "name": "support.constant.dfinance.address.move" + }, + { + "comment": "Polkadot address", + "match": "\\b[1-9A-HJ-NP-Za-km-z]{40}[1-9A-HJ-NP-Za-km-z]*\\b", + "name": "support.constant.pontem.address.move" + } + ] + }, + "as": { + "comment": "Keyword as", + "match": "\\b(as)\\b", + "name": "keyword.control.move" + }, + "as-import": { + "comment": "Keyword as in import statement", + "match": "\\b(as)\\b" + }, + "assert": { + "comment": "Assert built-in", + "match": "\\b(assert)\\b", + "name": "support.function.assert.move" + }, + "block": { + "begin": "{", + "comment": "Block expression or definition", + "end": "}", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#as" + }, + { + "include": "#mut" + }, + { + "include": "#let" + }, + { + "include": "#types" + }, + { + "include": "#assert" + }, + { + "include": "#literals" + }, + { + "include": "#control" + }, + { + "include": "#move_copy" + }, + { + "include": "#resource_methods" + }, + { + "include": "#module_access" + }, + { + "include": "#fun_call" + }, + { + "include": "#block" + } + ] + }, + "block-comments": { + "comment": "Supported since https://github.com/libra/libra/pull/3714", + "patterns": [ + { + "begin": "/\\*[\\*!](?![\\*/])", + "comment": "Block documentation comment", + "end": "\\*/", + "name": "comment.block.documentation.move" + }, + { + "begin": "/\\*", + "comment": "Block comment", + "end": "\\*/", + "name": "comment.block.move" + } + ] + }, + "comments": { + "patterns": [ + { + "include": "#line-comments" + }, + { + "include": "#block-comments" + } + ] + }, + "const": { + "begin": "\\b(const)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.const.move" + } + }, + "end": ";", + "name": "meta.const.move", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#primitives" + }, + { + "include": "#vector" + }, + { + "include": "#literals" + }, + { + "match": "\\b([\\w_]+)\\b", + "name": "constant.other.move" + } + ] + }, + "control": { + "comment": "Control flow", + "match": "\\b(return|while|loop|if|else|break|continue|abort)\\b", + "name": "keyword.control.move" + }, + "friend": { + "begin": "\\b(friend)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.type.move" + } + }, + "end": ";", + "name": "meta.friend.move", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#address_literal" + }, + { + "comment": "Friend module name", + "match": "\\b(\\w+)\\b", + "name": "entity.name.type.module.move" + } + ] + }, + "fun": { + "patterns": [ + { + "include": "#fun_signature" + }, + { + "include": "#fun_body" + } + ] + }, + "fun_body": { + "begin": "{", + "comment": "Function body", + "end": "}", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#as" + }, + { + "include": "#mut" + }, + { + "include": "#let" + }, + { + "include": "#types" + }, + { + "include": "#assert" + }, + { + "include": "#literals" + }, + { + "include": "#control" + }, + { + "include": "#move_copy" + }, + { + "include": "#resource_methods" + }, + { + "include": "#self_access" + }, + { + "include": "#module_access" + }, + { + "include": "#fun_call" + }, + { + "include": "#block" + } + ] + }, + "fun_call": { + "begin": "\\b(\\w+)\\s*(?:<[\\w\\s,]+>)?\\s*[(]", + "beginCaptures": { + "1": { + "name": "entity.name.function.call.move" + } + }, + "comment": "Function call", + "end": "[)]", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#resource_methods" + }, + { + "include": "#self_access" + }, + { + "include": "#module_access" + }, + { + "include": "#move_copy" + }, + { + "include": "#literals" + }, + { + "include": "#fun_call" + }, + { + "include": "#block" + }, + { + "include": "#mut" + }, + { + "include": "#as" + } + ] + }, + "fun_signature": { + "begin": "\\b(fun)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.fun.move" + } + }, + "comment": "Function signature", + "end": "(?=[;{])", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#module_access" + }, + { + "include": "#types" + }, + { + "begin": "(?<=fun)", + "comment": "Function name", + "end": "(?=[<(])", + "patterns": [ + { + "include": "#comments" + }, + { + "match": "\\b(\\w+)\\b", + "name": "entity.name.function.move" + } + ] + }, + { + "include": "#type_param" + }, + { + "begin": "[(]", + "comment": "Parentheses", + "end": "[)]", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#self_access" + }, + { + "include": "#module_access" + }, + { + "include": "#types" + }, + { + "include": "#mut" + } + ] + }, + { + "comment": "Keyword acquires", + "match": "\\b(acquires)\\b", + "name": "storage.modifier" + } + ] + }, + "import": { + "begin": "\\b(use)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.type.move" + } + }, + "end": ";", + "name": "meta.import.move", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#address_literal" + }, + { + "include": "#as-import" + }, + { + "begin": "{", + "comment": "Module members", + "end": "}", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#as-import" + }, + { + "comment": "Uppercase entities", + "match": "\\b(\\w+)\\b", + "name": "entity.name.type.move" + } + ] + }, + { + "comment": "Name of the imported module", + "match": "\\b(\\w+)\\b", + "name": "entity.name.type.module.move" + } + ] + }, + "let": { + "comment": "Keyword let", + "match": "\\b(let)\\b", + "name": "keyword.control.move" + }, + "line-comments": { + "begin": "//", + "comment": "Single-line comment", + "end": "$", + "name": "comment.line.double-slash.move" + }, + "literals": { + "patterns": [ + { + "comment": "Hex literal (still to find out)", + "match": "0x[_a-fA-F0-9]+(?:[iu](?:8|16|32|64|size))?", + "name": "constant.numeric.hex.move" + }, + { + "comment": "Numeric literal", + "match": "(? as hex literal: x", + "match": "x\"([A-F0-9a-f]+)\"" + }, + { + "comment": "Booleans", + "match": "\\b(?:true|false)\\b", + "name": "constant.language.boolean.move" + }, + { + "include": "#address_literal" + } + ] + }, + "module": { + "begin": "\\b(module)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.type.move" + } + }, + "comment": "Module definition", + "end": "(?<=})", + "name": "meta.module.move", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "(?<=module)", + "comment": "Module name", + "end": "(?={)", + "name": "entity.name.type.move", + "patterns": [ + { + "include": "#comments" + } + ] + }, + { + "begin": "{", + "comment": "Module scope", + "end": "}", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#import" + }, + { + "include": "#friend" + }, + { + "include": "#const" + }, + { + "include": "#resource" + }, + { + "include": "#struct" + }, + { + "include": "#resource" + }, + { + "include": "#public_fun" + }, + { + "include": "#native_fun" + }, + { + "include": "#fun" + }, + { + "include": "#spec" + }, + { + "include": "#block" + } + ] + } + ] + }, + "module_access": { + "captures": { + "1": { + "name": "entity.name.type.module.move" + }, + "2": { + "name": "entity.name.function.call.move" + } + }, + "comment": "Use of module type or method", + "match": "\\b(\\w+)::(\\w+)\\b" + }, + "move_copy": { + "comment": "Keywords move and copy", + "match": "\\b(move|copy)\\b", + "name": "variable.language.move" + }, + "mut": { + "comment": "&mut reference", + "match": "(?<=&)(mut)\\b", + "name": "storage.modifier.mut.move" + }, + "native_fun": { + "begin": "\\b(native)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.native.move" + } + }, + "comment": "Native function", + "end": "(?<=[;])", + "patterns": [ + { + "include": "#comments" + }, + { + "comment": "Public native fun", + "match": "\\b(public)\\b", + "name": "storage.modifier.public.move" + }, + { + "include": "#fun_signature" + } + ] + }, + "primitives": { + "comment": "Primitive types", + "match": "\\b(u8|u64|u128|address|bool|signer)\\b", + "name": "support.type.primitives.move" + }, + "public_fun": { + "begin": "\\b(public)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.public.move" + } + }, + "comment": "Public function", + "end": "(?<=})", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "\\(", + "comment": "Function visibility", + "end": "\\)", + "patterns": [ + { + "include": "#comments" + }, + { + "comment": "Visibility modifier", + "match": "\\b(script|friend)\\b", + "name": "storage.modifier.public.script.move" + } + ] + }, + { + "include": "#fun" + } + ] + }, + "resource": { + "begin": "\\b(resource)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.type.move" + } + }, + "comment": "Resource definition", + "end": "(?<=})", + "name": "meta.resource.move", + "patterns": [ + { + "include": "#struct" + } + ] + }, + "resource_methods": { + "comment": "Methods to work with resource", + "match": "\\b(borrow_global|borrow_global_mut|exists|move_from|move_to_sender|move_to)\\b", + "name": "support.function.typed.move" + }, + "script": { + "begin": "\\b(script)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.script.move" + } + }, + "end": "(?<=})", + "name": "meta.script.move", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "{", + "comment": "Script scope", + "end": "}", + "patterns": [ + { + "include": "#const" + }, + { + "include": "#comments" + }, + { + "include": "#import" + }, + { + "include": "#fun" + } + ] + } + ] + }, + "self_access": { + "captures": { + "1": { + "name": "variable.language.self.move" + }, + "2": { + "name": "entity.name.function.call.move" + } + }, + "comment": "Use of Self", + "match": "\\b(Self)::(\\w+)\\b" + }, + "spec": { + "begin": "\\b(spec)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.spec.move" + } + }, + "end": "(?<=})", + "patterns": [ + { + "comment": "Spec target", + "match": "\\b(module|schema|struct|fun)", + "name": "storage.modifier.spec.target.move" + }, + { + "comment": "Target name", + "match": "\\b(\\w+)\\b", + "name": "entity.name.function.move" + }, + { + "begin": "{", + "comment": "Spec block", + "end": "}", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#spec_block" + }, + { + "include": "#spec_types" + }, + { + "include": "#spec_define" + }, + { + "include": "#spec_keywords" + }, + { + "include": "#control" + }, + { + "include": "#fun_call" + }, + { + "include": "#literals" + }, + { + "include": "#types" + }, + { + "include": "#let" + } + ] + } + ] + }, + "spec_block": { + "begin": "{", + "comment": "Spec block", + "end": "}", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#spec_block" + }, + { + "include": "#spec_types" + }, + { + "include": "#fun_call" + }, + { + "include": "#literals" + }, + { + "include": "#control" + }, + { + "include": "#types" + }, + { + "include": "#let" + } + ] + }, + "spec_define": { + "begin": "\\b(define)\\b", + "beginCaptures": { + "1": { + "name": "keyword.control.move.spec" + } + }, + "comment": "Spec define keyword", + "end": "(?=[{])", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#spec_types" + }, + { + "include": "#types" + }, + { + "begin": "(?<=define)", + "comment": "Function name", + "end": "(?=[(])", + "patterns": [ + { + "include": "#comments" + }, + { + "match": "\\b(\\w+)\\b", + "name": "entity.name.function.move" + } + ] + } + ] + }, + "spec_keywords": { + "match": "\\b(global|pack|unpack|pragma|native|include|ensures|requires|invariant|apply|aborts_if)\\b", + "name": "keyword.control.move.spec" + }, + "spec_types": { + "comment": "Spec-only types", + "match": "\\b(range|num|vector|bool|u8|u64|u128|address)\\b", + "name": "support.type.vector.move" + }, + "struct": { + "begin": "\\b(struct)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.type.move" + } + }, + "end": "(?<=})", + "name": "meta.struct.move", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "(?<=struct)", + "comment": "Struct definition", + "end": "(?={)", + "patterns": [ + { + "include": "#comments" + }, + { + "comment": "`has` keyword", + "match": "\\b(has)\\b", + "name": "keyword.has.control.move" + }, + { + "comment": "Ability", + "match": "\\b(store|key|drop|copy)\\b", + "name": "entity.name.type.ability.move" + }, + { + "comment": "Struct name", + "match": "\\b(\\w+)\\b", + "name": "entity.name.type.move" + }, + { + "include": "#type_param" + } + ] + }, + { + "begin": "{", + "comment": "Struct body", + "end": "}", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#self_access" + }, + { + "include": "#module_access" + }, + { + "include": "#types" + } + ] + } + ] + }, + "type_param": { + "begin": "<", + "comment": "Generic type param", + "end": ">", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#module_access" + }, + { + "comment": "Trait-like condition", + "match": "\\b(store|drop|key|copy)\\b", + "name": "entity.name.type.kind.move" + } + ] + }, + "types": { + "comment": "Built-in types + vector", + "patterns": [ + { + "include": "#primitives" + }, + { + "include": "#vector" + } + ] + }, + "vector": { + "begin": "\\b(vector)<", + "beginCaptures": { + "1": { + "name": "support.type.vector.move" + } + }, + "end": ">", + "patterns": [ + { + "include": "#primitives" + }, + { + "include": "#vector" + } + ] + } + }, + "scopeName": "source.move" +} diff --git a/packages/tm-grammars/index.d.ts b/packages/tm-grammars/index.d.ts index 3ca8a79..d59e398 100644 --- a/packages/tm-grammars/index.d.ts +++ b/packages/tm-grammars/index.d.ts @@ -1,4 +1,4 @@ -export type GrammarCategory = 'web' | 'markup' | 'general' | 'scripting' | 'data' | 'dsl' | 'utility' | 'config' +export type GrammarCategory = 'web' | 'markup' | 'general' | 'scripting' | 'data' | 'dsl' | 'utility' | 'config' | 'smart contract' export interface GrammarInfo { name: string diff --git a/packages/tm-grammars/index.js b/packages/tm-grammars/index.js index 8018d5c..f6e57b2 100644 --- a/packages/tm-grammars/index.js +++ b/packages/tm-grammars/index.js @@ -810,7 +810,7 @@ export const grammars = [ aliases: [ 'gjs', ], - byteSize: 19536, + byteSize: 19543, displayName: 'Glimmer JS', embedded: [ 'javascript', @@ -818,19 +818,19 @@ export const grammars = [ 'css', 'html', ], - lastUpdate: '2024-01-18T05:06:13Z', + lastUpdate: '2024-02-03T14:28:03Z', license: 'MIT', licenseUrl: 'https://raw.githubusercontent.com/lifeart/vsc-ember-syntax/master/LICENSE', name: 'glimmer-js', scopeName: 'source.gjs', - sha: 'f031ed7df2eeced760f4469fc9fe06f8688c531a', - source: 'https://github.com/lifeart/vsc-ember-syntax/blob/f031ed7df2eeced760f4469fc9fe06f8688c531a/syntaxes/source.gjs.json', + sha: '312fb20cc04374eb8768369ebccabd12143edbe4', + source: 'https://github.com/lifeart/vsc-ember-syntax/blob/312fb20cc04374eb8768369ebccabd12143edbe4/syntaxes/source.gjs.json', }, { aliases: [ 'gts', ], - byteSize: 19536, + byteSize: 19543, displayName: 'Glimmer TS', embedded: [ 'typescript', @@ -838,13 +838,13 @@ export const grammars = [ 'javascript', 'html', ], - lastUpdate: '2024-01-18T05:06:13Z', + lastUpdate: '2024-02-03T14:28:03Z', license: 'MIT', licenseUrl: 'https://raw.githubusercontent.com/lifeart/vsc-ember-syntax/master/LICENSE', name: 'glimmer-ts', scopeName: 'source.gts', - sha: 'f031ed7df2eeced760f4469fc9fe06f8688c531a', - source: 'https://github.com/lifeart/vsc-ember-syntax/blob/f031ed7df2eeced760f4469fc9fe06f8688c531a/syntaxes/source.gts.json', + sha: '312fb20cc04374eb8768369ebccabd12143edbe4', + source: 'https://github.com/lifeart/vsc-ember-syntax/blob/312fb20cc04374eb8768369ebccabd12143edbe4/syntaxes/source.gts.json', }, { byteSize: 3774, @@ -1674,6 +1674,21 @@ export const grammars = [ sha: 'a2241dda06d9a20503394c3affa606114d007049', source: 'https://github.com/modularml/mojo-syntax/blob/a2241dda06d9a20503394c3affa606114d007049/syntaxes/mojo.syntax.json', }, + { + byteSize: 11501, + categories: [ + 'smart contract', + 'markup', + ], + displayName: 'Move', + lastUpdate: '2021-06-07T14:35:10Z', + license: 'Apache-2.0', + licenseUrl: 'https://raw.githubusercontent.com/pontem-network/vscode-move-ide/master/LICENSE', + name: 'move', + scopeName: 'source.move', + sha: '74ba14e513acfc1fe8b9bc6107e88c182bb9a7b4', + source: 'https://github.com/pontem-network/vscode-move-ide/blob/74ba14e513acfc1fe8b9bc6107e88c182bb9a7b4/syntaxes/move.tmLanguage.json', + }, { aliases: [ 'nar', diff --git a/samples/move.sample b/samples/move.sample new file mode 100644 index 0000000..89a026a --- /dev/null +++ b/samples/move.sample @@ -0,0 +1,1358 @@ +module aptos_framework::account { + use std::bcs; + use std::error; + use std::hash; + use std::option::{Self, Option}; + use std::signer; + use std::vector; + use aptos_framework::chain_id; + use aptos_framework::create_signer::create_signer; + use aptos_framework::event::{Self, EventHandle}; + use aptos_framework::guid; + use aptos_framework::system_addresses; + use aptos_std::ed25519; + use aptos_std::from_bcs; + use aptos_std::multi_ed25519; + use aptos_std::table::{Self, Table}; + use aptos_std::type_info::{Self, TypeInfo}; + + friend aptos_framework::aptos_account; + friend aptos_framework::coin; + friend aptos_framework::genesis; + friend aptos_framework::multisig_account; + friend aptos_framework::resource_account; + friend aptos_framework::transaction_validation; + + #[event] + struct KeyRotation has drop, store { + account: address, + old_authentication_key: vector, + new_authentication_key: vector, + } + + /// Resource representing an account. + struct Account has key, store { + authentication_key: vector, + sequence_number: u64, + guid_creation_num: u64, + coin_register_events: EventHandle, + key_rotation_events: EventHandle, + rotation_capability_offer: CapabilityOffer, + signer_capability_offer: CapabilityOffer, + } + + struct KeyRotationEvent has drop, store { + old_authentication_key: vector, + new_authentication_key: vector, + } + + struct CoinRegisterEvent has drop, store { + type_info: TypeInfo, + } + + struct CapabilityOffer has store { for: Option
} + + struct RotationCapability has drop, store { account: address } + + struct SignerCapability has drop, store { account: address } + + /// It is easy to fetch the authentication key of an address by simply reading it from the `Account` struct at that address. + /// The table in this struct makes it possible to do a reverse lookup: it maps an authentication key, to the address of the account which has that authentication key set. + /// + /// This mapping is needed when recovering wallets for accounts whose authentication key has been rotated. + /// + /// For example, imagine a freshly-created wallet with address `a` and thus also with authentication key `a`, derived from a PK `pk_a` with corresponding SK `sk_a`. + /// It is easy to recover such a wallet given just the secret key `sk_a`, since the PK can be derived from the SK, the authentication key can then be derived from the PK, and the address equals the authentication key (since there was no key rotation). + /// + /// However, if such a wallet rotates its authentication key to `b` derived from a different PK `pk_b` with SK `sk_b`, how would account recovery work? + /// The recovered address would no longer be 'a'; it would be `b`, which is incorrect. + /// This struct solves this problem by mapping the new authentication key `b` to the original address `a` and thus helping the wallet software during recovery find the correct address. + struct OriginatingAddress has key { + address_map: Table, + } + + /// This structs stores the challenge message that should be signed during key rotation. First, this struct is + /// signed by the account owner's current public key, which proves possession of a capability to rotate the key. + /// Second, this struct is signed by the new public key that the account owner wants to rotate to, which proves + /// knowledge of this new public key's associated secret key. These two signatures cannot be replayed in another + /// context because they include the TXN's unique sequence number. + struct RotationProofChallenge has copy, drop { + sequence_number: u64, // the sequence number of the account whose key is being rotated + originator: address, // the address of the account whose key is being rotated + current_auth_key: address, // the current authentication key of the account whose key is being rotated + new_public_key: vector, // the new public key that the account owner wants to rotate to + } + + /// Deprecated struct - newest version is `RotationCapabilityOfferProofChallengeV2` + struct RotationCapabilityOfferProofChallenge has drop { + sequence_number: u64, + recipient_address: address, + } + + /// Deprecated struct - newest version is `SignerCapabilityOfferProofChallengeV2` + struct SignerCapabilityOfferProofChallenge has drop { + sequence_number: u64, + recipient_address: address, + } + + /// This struct stores the challenge message that should be signed by the source account, when the source account + /// is delegating its rotation capability to the `recipient_address`. + /// This V2 struct adds the `chain_id` and `source_address` to the challenge message, which prevents replaying the challenge message. + struct RotationCapabilityOfferProofChallengeV2 has drop { + chain_id: u8, + sequence_number: u64, + source_address: address, + recipient_address: address, + } + + struct SignerCapabilityOfferProofChallengeV2 has copy, drop { + sequence_number: u64, + source_address: address, + recipient_address: address, + } + + const MAX_U64: u128 = 18446744073709551615; + const ZERO_AUTH_KEY: vector = x"0000000000000000000000000000000000000000000000000000000000000000"; + + /// Scheme identifier for Ed25519 signatures used to derive authentication keys for Ed25519 public keys. + const ED25519_SCHEME: u8 = 0; + /// Scheme identifier for MultiEd25519 signatures used to derive authentication keys for MultiEd25519 public keys. + const MULTI_ED25519_SCHEME: u8 = 1; + /// Scheme identifier used when hashing an account's address together with a seed to derive the address (not the + /// authentication key) of a resource account. This is an abuse of the notion of a scheme identifier which, for now, + /// serves to domain separate hashes used to derive resource account addresses from hashes used to derive + /// authentication keys. Without such separation, an adversary could create (and get a signer for) a resource account + /// whose address matches an existing address of a MultiEd25519 wallet. + const DERIVE_RESOURCE_ACCOUNT_SCHEME: u8 = 255; + + /// Account already exists + const EACCOUNT_ALREADY_EXISTS: u64 = 1; + /// Account does not exist + const EACCOUNT_DOES_NOT_EXIST: u64 = 2; + /// Sequence number exceeds the maximum value for a u64 + const ESEQUENCE_NUMBER_TOO_BIG: u64 = 3; + /// The provided authentication key has an invalid length + const EMALFORMED_AUTHENTICATION_KEY: u64 = 4; + /// Cannot create account because address is reserved + const ECANNOT_RESERVED_ADDRESS: u64 = 5; + /// Transaction exceeded its allocated max gas + const EOUT_OF_GAS: u64 = 6; + /// Specified current public key is not correct + const EWRONG_CURRENT_PUBLIC_KEY: u64 = 7; + /// Specified proof of knowledge required to prove ownership of a public key is invalid + const EINVALID_PROOF_OF_KNOWLEDGE: u64 = 8; + /// The caller does not have a digital-signature-based capability to call this function + const ENO_CAPABILITY: u64 = 9; + /// The caller does not have a valid rotation capability offer from the other account + const EINVALID_ACCEPT_ROTATION_CAPABILITY: u64 = 10; + /// Address to create is not a valid reserved address for Aptos framework + const ENO_VALID_FRAMEWORK_RESERVED_ADDRESS: u64 = 11; + /// Specified scheme required to proceed with the smart contract operation - can only be ED25519_SCHEME(0) OR MULTI_ED25519_SCHEME(1) + const EINVALID_SCHEME: u64 = 12; + /// Abort the transaction if the expected originating address is different from the originating address on-chain + const EINVALID_ORIGINATING_ADDRESS: u64 = 13; + /// The signer capability offer doesn't exist at the given address + const ENO_SUCH_SIGNER_CAPABILITY: u64 = 14; + /// An attempt to create a resource account on a claimed account + const ERESOURCE_ACCCOUNT_EXISTS: u64 = 15; + /// An attempt to create a resource account on an account that has a committed transaction + const EACCOUNT_ALREADY_USED: u64 = 16; + /// Offerer address doesn't exist + const EOFFERER_ADDRESS_DOES_NOT_EXIST: u64 = 17; + /// The specified rotation capablity offer does not exist at the specified offerer address + const ENO_SUCH_ROTATION_CAPABILITY_OFFER: u64 = 18; + // The signer capability is not offered to any address + const ENO_SIGNER_CAPABILITY_OFFERED: u64 = 19; + // This account has exceeded the allocated GUIDs it can create. It should be impossible to reach this number for real applications. + const EEXCEEDED_MAX_GUID_CREATION_NUM: u64 = 20; + + /// Explicitly separate the GUID space between Object and Account to prevent accidental overlap. + const MAX_GUID_CREATION_NUM: u64 = 0x4000000000000; + #[test_only] + /// Create signer for testing, independently of an Aptos-style `Account`. + public fun create_signer_for_test(addr: address): signer { create_signer(addr) } + + /// Only called during genesis to initialize system resources for this module. + public(friend) fun initialize(aptos_framework: &signer) { + system_addresses::assert_aptos_framework(aptos_framework); + move_to(aptos_framework, OriginatingAddress { + address_map: table::new(), + }); + } + + fun create_account_if_does_not_exist(account_address: address) { + if (!exists(account_address)) { + create_account(account_address); + } + } + + /// Publishes a new `Account` resource under `new_address`. A signer representing `new_address` + /// is returned. This way, the caller of this function can publish additional resources under + /// `new_address`. + public(friend) fun create_account(new_address: address): signer { + // there cannot be an Account resource under new_addr already. + assert!(!exists(new_address), error::already_exists(EACCOUNT_ALREADY_EXISTS)); + + // NOTE: @core_resources gets created via a `create_account` call, so we do not include it below. + assert!( + new_address != @vm_reserved && new_address != @aptos_framework && new_address != @aptos_token, + error::invalid_argument(ECANNOT_RESERVED_ADDRESS) + ); + + create_account_unchecked(new_address) + } + + fun create_account_unchecked(new_address: address): signer { + let new_account = create_signer(new_address); + let authentication_key = bcs::to_bytes(&new_address); + assert!( + vector::length(&authentication_key) == 32, + error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY) + ); + + let guid_creation_num = 0; + + let guid_for_coin = guid::create(new_address, &mut guid_creation_num); + let coin_register_events = event::new_event_handle(guid_for_coin); + + let guid_for_rotation = guid::create(new_address, &mut guid_creation_num); + let key_rotation_events = event::new_event_handle(guid_for_rotation); + + move_to( + &new_account, + Account { + authentication_key, + sequence_number: 0, + guid_creation_num, + coin_register_events, + key_rotation_events, + rotation_capability_offer: CapabilityOffer { for: option::none() }, + signer_capability_offer: CapabilityOffer { for: option::none() }, + } + ); + + new_account + } + + #[view] + public fun exists_at(addr: address): bool { + exists(addr) + } + + #[view] + public fun get_guid_next_creation_num(addr: address): u64 acquires Account { + borrow_global(addr).guid_creation_num + } + + #[view] + public fun get_sequence_number(addr: address): u64 acquires Account { + borrow_global(addr).sequence_number + } + + public(friend) fun increment_sequence_number(addr: address) acquires Account { + let sequence_number = &mut borrow_global_mut(addr).sequence_number; + + assert!( + (*sequence_number as u128) < MAX_U64, + error::out_of_range(ESEQUENCE_NUMBER_TOO_BIG) + ); + + *sequence_number = *sequence_number + 1; + } + + #[view] + public fun get_authentication_key(addr: address): vector acquires Account { + borrow_global(addr).authentication_key + } + + /// This function is used to rotate a resource account's authentication key to 0, so that no private key can control + /// the resource account. + public(friend) fun rotate_authentication_key_internal(account: &signer, new_auth_key: vector) acquires Account { + let addr = signer::address_of(account); + assert!(exists_at(addr), error::not_found(EACCOUNT_DOES_NOT_EXIST)); + assert!( + vector::length(&new_auth_key) == 32, + error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY) + ); + let account_resource = borrow_global_mut(addr); + account_resource.authentication_key = new_auth_key; + } + + /// Generic authentication key rotation function that allows the user to rotate their authentication key from any scheme to any scheme. + /// To authorize the rotation, we need two signatures: + /// - the first signature `cap_rotate_key` refers to the signature by the account owner's current key on a valid `RotationProofChallenge`, + /// demonstrating that the user intends to and has the capability to rotate the authentication key of this account; + /// - the second signature `cap_update_table` refers to the signature by the new key (that the account owner wants to rotate to) on a + /// valid `RotationProofChallenge`, demonstrating that the user owns the new private key, and has the authority to update the + /// `OriginatingAddress` map with the new address mapping ``. + /// To verify these two signatures, we need their corresponding public key and public key scheme: we use `from_scheme` and `from_public_key_bytes` + /// to verify `cap_rotate_key`, and `to_scheme` and `to_public_key_bytes` to verify `cap_update_table`. + /// A scheme of 0 refers to an Ed25519 key and a scheme of 1 refers to Multi-Ed25519 keys. + /// `originating address` refers to an account's original/first address. + /// + /// Here is an example attack if we don't ask for the second signature `cap_update_table`: + /// Alice has rotated her account `addr_a` to `new_addr_a`. As a result, the following entry is created, to help Alice when recovering her wallet: + /// `OriginatingAddress[new_addr_a]` -> `addr_a` + /// Alice has had bad day: her laptop blew up and she needs to reset her account on a new one. + /// (Fortunately, she still has her secret key `new_sk_a` associated with her new address `new_addr_a`, so she can do this.) + /// + /// But Bob likes to mess with Alice. + /// Bob creates an account `addr_b` and maliciously rotates it to Alice's new address `new_addr_a`. Since we are no longer checking a PoK, + /// Bob can easily do this. + /// + /// Now, the table will be updated to make Alice's new address point to Bob's address: `OriginatingAddress[new_addr_a]` -> `addr_b`. + /// When Alice recovers her account, her wallet will display the attacker's address (Bob's) `addr_b` as her address. + /// Now Alice will give `addr_b` to everyone to pay her, but the money will go to Bob. + /// + /// Because we ask for a valid `cap_update_table`, this kind of attack is not possible. Bob would not have the secret key of Alice's address + /// to rotate his address to Alice's address in the first place. + public entry fun rotate_authentication_key( + account: &signer, + from_scheme: u8, + from_public_key_bytes: vector, + to_scheme: u8, + to_public_key_bytes: vector, + cap_rotate_key: vector, + cap_update_table: vector, + ) acquires Account, OriginatingAddress { + let addr = signer::address_of(account); + assert!(exists_at(addr), error::not_found(EACCOUNT_DOES_NOT_EXIST)); + let account_resource = borrow_global_mut(addr); + + // Verify the given `from_public_key_bytes` matches this account's current authentication key. + if (from_scheme == ED25519_SCHEME) { + let from_pk = ed25519::new_unvalidated_public_key_from_bytes(from_public_key_bytes); + let from_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&from_pk); + assert!(account_resource.authentication_key == from_auth_key, error::unauthenticated(EWRONG_CURRENT_PUBLIC_KEY)); + } else if (from_scheme == MULTI_ED25519_SCHEME) { + let from_pk = multi_ed25519::new_unvalidated_public_key_from_bytes(from_public_key_bytes); + let from_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&from_pk); + assert!(account_resource.authentication_key == from_auth_key, error::unauthenticated(EWRONG_CURRENT_PUBLIC_KEY)); + } else { + abort error::invalid_argument(EINVALID_SCHEME) + }; + + // Construct a valid `RotationProofChallenge` that `cap_rotate_key` and `cap_update_table` will validate against. + let curr_auth_key_as_address = from_bcs::to_address(account_resource.authentication_key); + let challenge = RotationProofChallenge { + sequence_number: account_resource.sequence_number, + originator: addr, + current_auth_key: curr_auth_key_as_address, + new_public_key: to_public_key_bytes, + }; + + // Assert the challenges signed by the current and new keys are valid + assert_valid_rotation_proof_signature_and_get_auth_key(from_scheme, from_public_key_bytes, cap_rotate_key, &challenge); + let new_auth_key = assert_valid_rotation_proof_signature_and_get_auth_key(to_scheme, to_public_key_bytes, cap_update_table, &challenge); + + // Update the `OriginatingAddress` table. + update_auth_key_and_originating_address_table(addr, account_resource, new_auth_key); + } + + public entry fun rotate_authentication_key_with_rotation_capability( + delegate_signer: &signer, + rotation_cap_offerer_address: address, + new_scheme: u8, + new_public_key_bytes: vector, + cap_update_table: vector + ) acquires Account, OriginatingAddress { + assert!(exists_at(rotation_cap_offerer_address), error::not_found(EOFFERER_ADDRESS_DOES_NOT_EXIST)); + + // Check that there exists a rotation capability offer at the offerer's account resource for the delegate. + let delegate_address = signer::address_of(delegate_signer); + let offerer_account_resource = borrow_global(rotation_cap_offerer_address); + assert!(option::contains(&offerer_account_resource.rotation_capability_offer.for, &delegate_address), error::not_found(ENO_SUCH_ROTATION_CAPABILITY_OFFER)); + + let curr_auth_key = from_bcs::to_address(offerer_account_resource.authentication_key); + let challenge = RotationProofChallenge { + sequence_number: get_sequence_number(delegate_address), + originator: rotation_cap_offerer_address, + current_auth_key: curr_auth_key, + new_public_key: new_public_key_bytes, + }; + + // Verifies that the `RotationProofChallenge` from above is signed under the new public key that we are rotating to. l + let new_auth_key = assert_valid_rotation_proof_signature_and_get_auth_key(new_scheme, new_public_key_bytes, cap_update_table, &challenge); + + // Update the `OriginatingAddress` table, so we can find the originating address using the new address. + let offerer_account_resource = borrow_global_mut(rotation_cap_offerer_address); + update_auth_key_and_originating_address_table(rotation_cap_offerer_address, offerer_account_resource, new_auth_key); + } + + /// Offers rotation capability on behalf of `account` to the account at address `recipient_address`. + /// An account can delegate its rotation capability to only one other address at one time. If the account + /// has an existing rotation capability offer, calling this function will update the rotation capability offer with + /// the new `recipient_address`. + /// Here, `rotation_capability_sig_bytes` signature indicates that this key rotation is authorized by the account owner, + /// and prevents the classic "time-of-check time-of-use" attack. + /// For example, users usually rely on what the wallet displays to them as the transaction's outcome. Consider a contract that with 50% probability + /// (based on the current timestamp in Move), rotates somebody's key. The wallet might be unlucky and get an outcome where nothing is rotated, + /// incorrectly telling the user nothing bad will happen. But when the transaction actually gets executed, the attacker gets lucky and + /// the execution path triggers the account key rotation. + /// We prevent such attacks by asking for this extra signature authorizing the key rotation. + /// + /// @param rotation_capability_sig_bytes is the signature by the account owner's key on `RotationCapabilityOfferProofChallengeV2`. + /// @param account_scheme is the scheme of the account (ed25519 or multi_ed25519). + /// @param account_public_key_bytes is the public key of the account owner. + /// @param recipient_address is the address of the recipient of the rotation capability - note that if there's an existing rotation capability + /// offer, calling this function will replace the previous `recipient_address` upon successful verification. + public entry fun offer_rotation_capability( + account: &signer, + rotation_capability_sig_bytes: vector, + account_scheme: u8, + account_public_key_bytes: vector, + recipient_address: address, + ) acquires Account { + let addr = signer::address_of(account); + assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); + + // proof that this account intends to delegate its rotation capability to another account + let account_resource = borrow_global_mut(addr); + let proof_challenge = RotationCapabilityOfferProofChallengeV2 { + chain_id: chain_id::get(), + sequence_number: account_resource.sequence_number, + source_address: addr, + recipient_address, + }; + + // verify the signature on `RotationCapabilityOfferProofChallengeV2` by the account owner + if (account_scheme == ED25519_SCHEME) { + let pubkey = ed25519::new_unvalidated_public_key_from_bytes(account_public_key_bytes); + let expected_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&pubkey); + assert!(account_resource.authentication_key == expected_auth_key, error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY)); + + let rotation_capability_sig = ed25519::new_signature_from_bytes(rotation_capability_sig_bytes); + assert!(ed25519::signature_verify_strict_t(&rotation_capability_sig, &pubkey, proof_challenge), error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE)); + } else if (account_scheme == MULTI_ED25519_SCHEME) { + let pubkey = multi_ed25519::new_unvalidated_public_key_from_bytes(account_public_key_bytes); + let expected_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&pubkey); + assert!(account_resource.authentication_key == expected_auth_key, error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY)); + + let rotation_capability_sig = multi_ed25519::new_signature_from_bytes(rotation_capability_sig_bytes); + assert!(multi_ed25519::signature_verify_strict_t(&rotation_capability_sig, &pubkey, proof_challenge), error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE)); + } else { + abort error::invalid_argument(EINVALID_SCHEME) + }; + + // update the existing rotation capability offer or put in a new rotation capability offer for the current account + option::swap_or_fill(&mut account_resource.rotation_capability_offer.for, recipient_address); + } + + #[view] + /// Returns true if the account at `account_addr` has a rotation capability offer. + public fun is_rotation_capability_offered(account_addr: address): bool acquires Account { + let account_resource = borrow_global(account_addr); + option::is_some(&account_resource.rotation_capability_offer.for) + } + + #[view] + /// Returns the address of the account that has a rotation capability offer from the account at `account_addr`. + public fun get_rotation_capability_offer_for(account_addr: address): address acquires Account { + let account_resource = borrow_global(account_addr); + assert!( + option::is_some(&account_resource.rotation_capability_offer.for), + error::not_found(ENO_SIGNER_CAPABILITY_OFFERED), + ); + *option::borrow(&account_resource.rotation_capability_offer.for) + } + + /// Revoke the rotation capability offer given to `to_be_revoked_recipient_address` from `account` + public entry fun revoke_rotation_capability(account: &signer, to_be_revoked_address: address) acquires Account { + assert!(exists_at(to_be_revoked_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); + let addr = signer::address_of(account); + let account_resource = borrow_global_mut(addr); + assert!(option::contains(&account_resource.rotation_capability_offer.for, &to_be_revoked_address), error::not_found(ENO_SUCH_ROTATION_CAPABILITY_OFFER)); + revoke_any_rotation_capability(account); + } + + /// Revoke any rotation capability offer in the specified account. + public entry fun revoke_any_rotation_capability(account: &signer) acquires Account { + let account_resource = borrow_global_mut(signer::address_of(account)); + option::extract(&mut account_resource.rotation_capability_offer.for); + } + + /// Offers signer capability on behalf of `account` to the account at address `recipient_address`. + /// An account can delegate its signer capability to only one other address at one time. + /// `signer_capability_key_bytes` is the `SignerCapabilityOfferProofChallengeV2` signed by the account owner's key + /// `account_scheme` is the scheme of the account (ed25519 or multi_ed25519). + /// `account_public_key_bytes` is the public key of the account owner. + /// `recipient_address` is the address of the recipient of the signer capability - note that if there's an existing + /// `recipient_address` in the account owner's `SignerCapabilityOffer`, this will replace the + /// previous `recipient_address` upon successful verification (the previous recipient will no longer have access + /// to the account owner's signer capability). + public entry fun offer_signer_capability( + account: &signer, + signer_capability_sig_bytes: vector, + account_scheme: u8, + account_public_key_bytes: vector, + recipient_address: address + ) acquires Account { + let source_address = signer::address_of(account); + assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); + + // Proof that this account intends to delegate its signer capability to another account. + let proof_challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: get_sequence_number(source_address), + source_address, + recipient_address, + }; + verify_signed_message( + source_address, account_scheme, account_public_key_bytes, signer_capability_sig_bytes, proof_challenge); + + // Update the existing signer capability offer or put in a new signer capability offer for the recipient. + let account_resource = borrow_global_mut(source_address); + option::swap_or_fill(&mut account_resource.signer_capability_offer.for, recipient_address); + } + + #[view] + /// Returns true if the account at `account_addr` has a signer capability offer. + public fun is_signer_capability_offered(account_addr: address): bool acquires Account { + let account_resource = borrow_global(account_addr); + option::is_some(&account_resource.signer_capability_offer.for) + } + + #[view] + /// Returns the address of the account that has a signer capability offer from the account at `account_addr`. + public fun get_signer_capability_offer_for(account_addr: address): address acquires Account { + let account_resource = borrow_global(account_addr); + assert!( + option::is_some(&account_resource.signer_capability_offer.for), + error::not_found(ENO_SIGNER_CAPABILITY_OFFERED), + ); + *option::borrow(&account_resource.signer_capability_offer.for) + } + + /// Revoke the account owner's signer capability offer for `to_be_revoked_address` (i.e., the address that + /// has a signer capability offer from `account` but will be revoked in this function). + public entry fun revoke_signer_capability(account: &signer, to_be_revoked_address: address) acquires Account { + assert!(exists_at(to_be_revoked_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); + let addr = signer::address_of(account); + let account_resource = borrow_global_mut(addr); + assert!(option::contains(&account_resource.signer_capability_offer.for, &to_be_revoked_address), error::not_found(ENO_SUCH_SIGNER_CAPABILITY)); + revoke_any_signer_capability(account); + } + + /// Revoke any signer capability offer in the specified account. + public entry fun revoke_any_signer_capability(account: &signer) acquires Account { + let account_resource = borrow_global_mut(signer::address_of(account)); + option::extract(&mut account_resource.signer_capability_offer.for); + } + + /// Return an authorized signer of the offerer, if there's an existing signer capability offer for `account` + /// at the offerer's address. + public fun create_authorized_signer(account: &signer, offerer_address: address): signer acquires Account { + assert!(exists_at(offerer_address), error::not_found(EOFFERER_ADDRESS_DOES_NOT_EXIST)); + + // Check if there's an existing signer capability offer from the offerer. + let account_resource = borrow_global(offerer_address); + let addr = signer::address_of(account); + assert!(option::contains(&account_resource.signer_capability_offer.for, &addr), error::not_found(ENO_SUCH_SIGNER_CAPABILITY)); + + create_signer(offerer_address) + } + + /////////////////////////////////////////////////////////////////////////// + /// Helper functions for authentication key rotation. + /////////////////////////////////////////////////////////////////////////// + fun assert_valid_rotation_proof_signature_and_get_auth_key(scheme: u8, public_key_bytes: vector, signature: vector, challenge: &RotationProofChallenge): vector { + if (scheme == ED25519_SCHEME) { + let pk = ed25519::new_unvalidated_public_key_from_bytes(public_key_bytes); + let sig = ed25519::new_signature_from_bytes(signature); + assert!(ed25519::signature_verify_strict_t(&sig, &pk, *challenge), std::error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE)); + ed25519::unvalidated_public_key_to_authentication_key(&pk) + } else if (scheme == MULTI_ED25519_SCHEME) { + let pk = multi_ed25519::new_unvalidated_public_key_from_bytes(public_key_bytes); + let sig = multi_ed25519::new_signature_from_bytes(signature); + assert!(multi_ed25519::signature_verify_strict_t(&sig, &pk, *challenge), std::error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE)); + multi_ed25519::unvalidated_public_key_to_authentication_key(&pk) + } else { + abort error::invalid_argument(EINVALID_SCHEME) + } + } + + /// Update the `OriginatingAddress` table, so that we can find the originating address using the latest address + /// in the event of key recovery. + fun update_auth_key_and_originating_address_table( + originating_addr: address, + account_resource: &mut Account, + new_auth_key_vector: vector, + ) acquires OriginatingAddress { + let address_map = &mut borrow_global_mut(@aptos_framework).address_map; + let curr_auth_key = from_bcs::to_address(account_resource.authentication_key); + + // Checks `OriginatingAddress[curr_auth_key]` is either unmapped, or mapped to `originating_address`. + // If it's mapped to the originating address, removes that mapping. + // Otherwise, abort if it's mapped to a different address. + if (table::contains(address_map, curr_auth_key)) { + // If account_a with address_a is rotating its keypair from keypair_a to keypair_b, we expect + // the address of the account to stay the same, while its keypair updates to keypair_b. + // Here, by asserting that we're calling from the account with the originating address, we enforce + // the standard of keeping the same address and updating the keypair at the contract level. + // Without this assertion, the dapps could also update the account's address to address_b (the address that + // is programmatically related to keypaier_b) and update the keypair to keypair_b. This causes problems + // for interoperability because different dapps can implement this in different ways. + // If the account with address b calls this function with two valid signatures, it will abort at this step, + // because address b is not the account's originating address. + assert!(originating_addr == table::remove(address_map, curr_auth_key), error::not_found(EINVALID_ORIGINATING_ADDRESS)); + }; + + // Set `OriginatingAddress[new_auth_key] = originating_address`. + let new_auth_key = from_bcs::to_address(new_auth_key_vector); + table::add(address_map, new_auth_key, originating_addr); + + event::emit(KeyRotation { + account: originating_addr, + old_authentication_key: account_resource.authentication_key, + new_authentication_key: new_auth_key_vector, + }); + event::emit_event( + &mut account_resource.key_rotation_events, + KeyRotationEvent { + old_authentication_key: account_resource.authentication_key, + new_authentication_key: new_auth_key_vector, + } + ); + + // Update the account resource's authentication key. + account_resource.authentication_key = new_auth_key_vector; + } + + /////////////////////////////////////////////////////////////////////////// + /// Basic account creation methods. + /////////////////////////////////////////////////////////////////////////// + + /// This is a helper function to compute resource addresses. Computation of the address + /// involves the use of a cryptographic hash operation and should be use thoughtfully. + public fun create_resource_address(source: &address, seed: vector): address { + let bytes = bcs::to_bytes(source); + vector::append(&mut bytes, seed); + vector::push_back(&mut bytes, DERIVE_RESOURCE_ACCOUNT_SCHEME); + from_bcs::to_address(hash::sha3_256(bytes)) + } + + /// A resource account is used to manage resources independent of an account managed by a user. + /// In Aptos a resource account is created based upon the sha3 256 of the source's address and additional seed data. + /// A resource account can only be created once, this is designated by setting the + /// `Account::signer_capability_offer::for` to the address of the resource account. While an entity may call + /// `create_account` to attempt to claim an account ahead of the creation of a resource account, if found Aptos will + /// transition ownership of the account over to the resource account. This is done by validating that the account has + /// yet to execute any transactions and that the `Account::signer_capability_offer::for` is none. The probability of a + /// collision where someone has legitimately produced a private key that maps to a resource account address is less + /// than `(1/2)^(256)`. + public fun create_resource_account(source: &signer, seed: vector): (signer, SignerCapability) acquires Account { + let resource_addr = create_resource_address(&signer::address_of(source), seed); + let resource = if (exists_at(resource_addr)) { + let account = borrow_global(resource_addr); + assert!( + option::is_none(&account.signer_capability_offer.for), + error::already_exists(ERESOURCE_ACCCOUNT_EXISTS), + ); + assert!( + account.sequence_number == 0, + error::invalid_state(EACCOUNT_ALREADY_USED), + ); + create_signer(resource_addr) + } else { + create_account_unchecked(resource_addr) + }; + + // By default, only the SignerCapability should have control over the resource account and not the auth key. + // If the source account wants direct control via auth key, they would need to explicitly rotate the auth key + // of the resource account using the SignerCapability. + rotate_authentication_key_internal(&resource, ZERO_AUTH_KEY); + + let account = borrow_global_mut(resource_addr); + account.signer_capability_offer.for = option::some(resource_addr); + let signer_cap = SignerCapability { account: resource_addr }; + (resource, signer_cap) + } + + /// create the account for system reserved addresses + public(friend) fun create_framework_reserved_account(addr: address): (signer, SignerCapability) { + assert!( + addr == @0x1 || + addr == @0x2 || + addr == @0x3 || + addr == @0x4 || + addr == @0x5 || + addr == @0x6 || + addr == @0x7 || + addr == @0x8 || + addr == @0x9 || + addr == @0xa, + error::permission_denied(ENO_VALID_FRAMEWORK_RESERVED_ADDRESS), + ); + let signer = create_account_unchecked(addr); + let signer_cap = SignerCapability { account: addr }; + (signer, signer_cap) + } + + /////////////////////////////////////////////////////////////////////////// + /// GUID management methods. + /////////////////////////////////////////////////////////////////////////// + + public fun create_guid(account_signer: &signer): guid::GUID acquires Account { + let addr = signer::address_of(account_signer); + let account = borrow_global_mut(addr); + let guid = guid::create(addr, &mut account.guid_creation_num); + assert!( + account.guid_creation_num < MAX_GUID_CREATION_NUM, + error::out_of_range(EEXCEEDED_MAX_GUID_CREATION_NUM), + ); + guid + } + + /////////////////////////////////////////////////////////////////////////// + /// GUID management methods. + /////////////////////////////////////////////////////////////////////////// + + public fun new_event_handle(account: &signer): EventHandle acquires Account { + event::new_event_handle(create_guid(account)) + } + + /////////////////////////////////////////////////////////////////////////// + /// Coin management methods. + /////////////////////////////////////////////////////////////////////////// + + public(friend) fun register_coin(account_addr: address) acquires Account { + let account = borrow_global_mut(account_addr); + event::emit_event( + &mut account.coin_register_events, + CoinRegisterEvent { + type_info: type_info::type_of(), + }, + ); + } + + /////////////////////////////////////////////////////////////////////////// + // Test-only create signerCapabilityOfferProofChallengeV2 and return it + /////////////////////////////////////////////////////////////////////////// + + #[test_only] + public fun get_signer_capability_offer_proof_challenge_v2( + source_address: address, + recipient_address: address, + ): SignerCapabilityOfferProofChallengeV2 acquires Account { + SignerCapabilityOfferProofChallengeV2 { + sequence_number: borrow_global_mut(source_address).sequence_number, + source_address, + recipient_address, + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Capability based functions for efficient use. + /////////////////////////////////////////////////////////////////////////// + + public fun create_signer_with_capability(capability: &SignerCapability): signer { + let addr = &capability.account; + create_signer(*addr) + } + + public fun get_signer_capability_address(capability: &SignerCapability): address { + capability.account + } + + public fun verify_signed_message( + account: address, + account_scheme: u8, + account_public_key: vector, + signed_message_bytes: vector, + message: T, + ) acquires Account { + let account_resource = borrow_global_mut(account); + // Verify that the `SignerCapabilityOfferProofChallengeV2` has the right information and is signed by the account owner's key + if (account_scheme == ED25519_SCHEME) { + let pubkey = ed25519::new_unvalidated_public_key_from_bytes(account_public_key); + let expected_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&pubkey); + assert!( + account_resource.authentication_key == expected_auth_key, + error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY), + ); + + let signer_capability_sig = ed25519::new_signature_from_bytes(signed_message_bytes); + assert!( + ed25519::signature_verify_strict_t(&signer_capability_sig, &pubkey, message), + error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE), + ); + } else if (account_scheme == MULTI_ED25519_SCHEME) { + let pubkey = multi_ed25519::new_unvalidated_public_key_from_bytes(account_public_key); + let expected_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&pubkey); + assert!( + account_resource.authentication_key == expected_auth_key, + error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY), + ); + + let signer_capability_sig = multi_ed25519::new_signature_from_bytes(signed_message_bytes); + assert!( + multi_ed25519::signature_verify_strict_t(&signer_capability_sig, &pubkey, message), + error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE), + ); + } else { + abort error::invalid_argument(EINVALID_SCHEME) + }; + } + + #[test_only] + public fun create_account_for_test(new_address: address): signer { + // Make this easier by just allowing the account to be created again in a test + if (!exists_at(new_address)) { + create_account_unchecked(new_address) + } else { + create_signer_for_test(new_address) + } + } + + #[test] + /// Assert correct signer creation. + fun test_create_signer_for_test() { + assert!(signer::address_of(&create_signer_for_test(@aptos_framework)) == @0x1, 0); + assert!(signer::address_of(&create_signer_for_test(@0x123)) == @0x123, 0); + } + + #[test(user = @0x1)] + public entry fun test_create_resource_account(user: signer) acquires Account { + let (resource_account, resource_account_cap) = create_resource_account(&user, x"01"); + let resource_addr = signer::address_of(&resource_account); + assert!(resource_addr != signer::address_of(&user), 0); + assert!(resource_addr == get_signer_capability_address(&resource_account_cap), 1); + } + + #[test] + #[expected_failure(abort_code = 0x10007, location = Self)] + public entry fun test_cannot_control_resource_account_via_auth_key() acquires Account { + let alice_pk = x"4141414141414141414141414141414141414141414141414141414141414145"; + let alice = create_account_from_ed25519_public_key(alice_pk); + let alice_auth = get_authentication_key(signer::address_of(&alice)); // must look like a valid public key + + let (eve_sk, eve_pk) = ed25519::generate_keys(); + let eve_pk_bytes = ed25519::validated_public_key_to_bytes(&eve_pk); + let eve = create_account_from_ed25519_public_key(eve_pk_bytes); + let recipient_address = signer::address_of(&eve); + + let seed = eve_pk_bytes; // multisig public key + vector::push_back(&mut seed, 1); // multisig threshold + vector::push_back(&mut seed, 1); // signature scheme id + let (resource, _) = create_resource_account(&alice, seed); + + let resource_addr = signer::address_of(&resource); + let proof_challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: borrow_global_mut(resource_addr).sequence_number, + source_address: resource_addr, + recipient_address, + }; + + let eve_sig = ed25519::sign_struct(&eve_sk, copy proof_challenge); + + // Construct a malicious 1-out-of-2 multisig PK over Alice's authentication key and Eve's Ed25519 PK. + let account_public_key_bytes = alice_auth; + vector::append(&mut account_public_key_bytes, eve_pk_bytes); + vector::push_back(&mut account_public_key_bytes, 1); // Multisig verification threshold. + let fake_pk = multi_ed25519::new_unvalidated_public_key_from_bytes(account_public_key_bytes); + + // Construct a multisig for `proof_challenge` as if it is signed by the signers behind `fake_pk`, + // Eve being the only participant. + let signer_capability_sig_bytes = x""; + vector::append(&mut signer_capability_sig_bytes, ed25519::signature_to_bytes(&eve_sig)); + vector::append(&mut signer_capability_sig_bytes, x"40000000"); // Signers bitmap. + let fake_sig = multi_ed25519::new_signature_from_bytes(signer_capability_sig_bytes); + + assert!(multi_ed25519::signature_verify_strict_t(&fake_sig, &fake_pk, proof_challenge), error::invalid_state(EINVALID_PROOF_OF_KNOWLEDGE)); + offer_signer_capability(&resource, signer_capability_sig_bytes, MULTI_ED25519_SCHEME, account_public_key_bytes, recipient_address); + } + + #[test_only] + struct DummyResource has key {} + + #[test(user = @0x1)] + public entry fun test_module_capability(user: signer) acquires Account, DummyResource { + let (resource_account, signer_cap) = create_resource_account(&user, x"01"); + assert!(signer::address_of(&resource_account) != signer::address_of(&user), 0); + + let resource_account_from_cap = create_signer_with_capability(&signer_cap); + assert!(&resource_account == &resource_account_from_cap, 1); + + move_to(&resource_account_from_cap, DummyResource {}); + borrow_global(signer::address_of(&resource_account)); + } + + #[test(user = @0x1)] + public entry fun test_resource_account_and_create_account(user: signer) acquires Account { + let resource_addr = create_resource_address(&@0x1, x"01"); + create_account_unchecked(resource_addr); + + create_resource_account(&user, x"01"); + } + + #[test(user = @0x1)] + #[expected_failure(abort_code = 0x8000f, location = Self)] + public entry fun test_duplice_create_resource_account(user: signer) acquires Account { + create_resource_account(&user, x"01"); + create_resource_account(&user, x"01"); + } + + /////////////////////////////////////////////////////////////////////////// + // Test-only sequence number mocking for extant Account resource + /////////////////////////////////////////////////////////////////////////// + + #[test_only] + /// Increment sequence number of account at address `addr` + public fun increment_sequence_number_for_test( + addr: address, + ) acquires Account { + let acct = borrow_global_mut(addr); + acct.sequence_number = acct.sequence_number + 1; + } + + #[test_only] + /// Update address `addr` to have `s` as its sequence number + public fun set_sequence_number( + addr: address, + s: u64 + ) acquires Account { + borrow_global_mut(addr).sequence_number = s; + } + + #[test_only] + public fun create_test_signer_cap(account: address): SignerCapability { + SignerCapability { account } + } + + #[test_only] + public fun set_signer_capability_offer(offerer: address, receiver: address) acquires Account { + let account_resource = borrow_global_mut(offerer); + option::swap_or_fill(&mut account_resource.signer_capability_offer.for, receiver); + } + + #[test_only] + public fun set_rotation_capability_offer(offerer: address, receiver: address) acquires Account { + let account_resource = borrow_global_mut(offerer); + option::swap_or_fill(&mut account_resource.rotation_capability_offer.for, receiver); + } + + #[test] + /// Verify test-only sequence number mocking + public entry fun mock_sequence_numbers() + acquires Account { + let addr: address = @0x1234; // Define test address + create_account(addr); // Initialize account resource + // Assert sequence number intializes to 0 + assert!(borrow_global(addr).sequence_number == 0, 0); + increment_sequence_number_for_test(addr); // Increment sequence number + // Assert correct mock value post-increment + assert!(borrow_global(addr).sequence_number == 1, 1); + set_sequence_number(addr, 10); // Set mock sequence number + // Assert correct mock value post-modification + assert!(borrow_global(addr).sequence_number == 10, 2); + } + + /////////////////////////////////////////////////////////////////////////// + // Test account helpers + /////////////////////////////////////////////////////////////////////////// + + #[test(alice = @0xa11ce)] + #[expected_failure(abort_code = 65537, location = aptos_framework::ed25519)] + public entry fun test_empty_public_key(alice: signer) acquires Account, OriginatingAddress { + create_account(signer::address_of(&alice)); + let pk = vector::empty(); + let sig = x"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + rotate_authentication_key(&alice, ED25519_SCHEME, pk, ED25519_SCHEME, pk, sig, sig); + } + + #[test(alice = @0xa11ce)] + #[expected_failure(abort_code = 262151, location = Self)] + public entry fun test_empty_signature(alice: signer) acquires Account, OriginatingAddress { + create_account(signer::address_of(&alice)); + let test_signature = vector::empty(); + let pk = x"0000000000000000000000000000000000000000000000000000000000000000"; + rotate_authentication_key(&alice, ED25519_SCHEME, pk, ED25519_SCHEME, pk, test_signature, test_signature); + } + + #[test_only] + public fun create_account_from_ed25519_public_key(pk_bytes: vector): signer { + let pk = ed25519::new_unvalidated_public_key_from_bytes(pk_bytes); + let curr_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&pk); + let alice_address = from_bcs::to_address(curr_auth_key); + let alice = create_account_unchecked(alice_address); + alice + } + + // + // Tests for offering & revoking signer capabilities + // + + #[test(bob = @0x345)] + #[expected_failure(abort_code = 65544, location = Self)] + public entry fun test_invalid_offer_signer_capability(bob: signer) acquires Account { + let (_alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: borrow_global(alice_addr).sequence_number, + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let sig = ed25519::sign_struct(&_alice_sk, challenge); + + // Maul the signature and make sure the call would fail + let invalid_signature = ed25519::signature_to_bytes(&sig); + let first_sig_byte = vector::borrow_mut(&mut invalid_signature, 0); + *first_sig_byte = *first_sig_byte + 1; + + offer_signer_capability(&alice, invalid_signature, 0, alice_pk_bytes, bob_addr); + } + + #[test(bob = @0x345)] + public entry fun test_valid_check_signer_capability_and_create_authorized_signer(bob: signer) acquires Account { + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: borrow_global(alice_addr).sequence_number, + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_signer_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + offer_signer_capability(&alice, ed25519::signature_to_bytes(&alice_signer_capability_offer_sig), 0, alice_pk_bytes, bob_addr); + + assert!(option::contains(&borrow_global(alice_addr).signer_capability_offer.for, &bob_addr), 0); + + let signer = create_authorized_signer(&bob, alice_addr); + assert!(signer::address_of(&signer) == signer::address_of(&alice), 0); + } + + #[test(bob = @0x345)] + public entry fun test_get_signer_cap_and_is_signer_cap(bob: signer) acquires Account { + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: borrow_global(alice_addr).sequence_number, + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_signer_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + offer_signer_capability(&alice, ed25519::signature_to_bytes(&alice_signer_capability_offer_sig), 0, alice_pk_bytes, bob_addr); + + assert!(is_signer_capability_offered(alice_addr), 0); + assert!(get_signer_capability_offer_for(alice_addr) == bob_addr, 0); + } + + + #[test(bob = @0x345, charlie = @0x567)] + #[expected_failure(abort_code = 393230, location = Self)] + public entry fun test_invalid_check_signer_capability_and_create_authorized_signer(bob: signer, charlie: signer) acquires Account { + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: borrow_global(alice_addr).sequence_number, + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_signer_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + offer_signer_capability(&alice, ed25519::signature_to_bytes(&alice_signer_capability_offer_sig), 0, alice_pk_bytes, bob_addr); + + let alice_account_resource = borrow_global_mut(alice_addr); + assert!(option::contains(&alice_account_resource.signer_capability_offer.for, &bob_addr), 0); + + create_authorized_signer(&charlie, alice_addr); + } + + #[test(bob = @0x345)] + public entry fun test_valid_revoke_signer_capability(bob: signer) acquires Account { + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: borrow_global(alice_addr).sequence_number, + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_signer_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + offer_signer_capability(&alice, ed25519::signature_to_bytes(&alice_signer_capability_offer_sig), 0, alice_pk_bytes, bob_addr); + revoke_signer_capability(&alice, bob_addr); + } + + #[test(bob = @0x345, charlie = @0x567)] + #[expected_failure(abort_code = 393230, location = Self)] + public entry fun test_invalid_revoke_signer_capability(bob: signer, charlie: signer) acquires Account { + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + let alice_account_resource = borrow_global(alice_addr); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let charlie_addr = signer::address_of(&charlie); + create_account(charlie_addr); + + let challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: alice_account_resource.sequence_number, + source_address: alice_addr, + recipient_address: bob_addr, + }; + let alice_signer_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + offer_signer_capability(&alice, ed25519::signature_to_bytes(&alice_signer_capability_offer_sig), 0, alice_pk_bytes, bob_addr); + revoke_signer_capability(&alice, charlie_addr); + } + + // + // Tests for offering rotation capabilities + // + #[test(bob = @0x345, framework = @aptos_framework)] + public entry fun test_valid_offer_rotation_capability(bob: signer, framework: signer) acquires Account { + chain_id::initialize_for_test(&framework, 4); + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = RotationCapabilityOfferProofChallengeV2 { + chain_id: chain_id::get(), + sequence_number: get_sequence_number(alice_addr), + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_rotation_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + offer_rotation_capability(&alice, ed25519::signature_to_bytes(&alice_rotation_capability_offer_sig), 0, alice_pk_bytes, bob_addr); + + let alice_resource = borrow_global_mut(signer::address_of(&alice)); + assert!(option::contains(&alice_resource.rotation_capability_offer.for, &bob_addr), 0); + } + + #[test(bob = @0x345, framework = @aptos_framework)] + #[expected_failure(abort_code = 65544, location = Self)] + public entry fun test_invalid_offer_rotation_capability(bob: signer, framework: signer) acquires Account { + chain_id::initialize_for_test(&framework, 4); + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = RotationCapabilityOfferProofChallengeV2 { + chain_id: chain_id::get(), + // Intentionally make the signature invalid. + sequence_number: 2, + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_rotation_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + offer_rotation_capability(&alice, ed25519::signature_to_bytes(&alice_rotation_capability_offer_sig), 0, alice_pk_bytes, signer::address_of(&bob)); + } + + #[test(bob = @0x345, framework = @aptos_framework)] + public entry fun test_valid_revoke_rotation_capability(bob: signer, framework: signer) acquires Account { + chain_id::initialize_for_test(&framework, 4); + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = RotationCapabilityOfferProofChallengeV2 { + chain_id: chain_id::get(), + sequence_number: get_sequence_number(alice_addr), + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_rotation_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + offer_rotation_capability(&alice, ed25519::signature_to_bytes(&alice_rotation_capability_offer_sig), 0, alice_pk_bytes, signer::address_of(&bob)); + revoke_rotation_capability(&alice, signer::address_of(&bob)); + } + + #[test(bob = @0x345, charlie = @0x567, framework = @aptos_framework)] + #[expected_failure(abort_code = 393234, location = Self)] + public entry fun test_invalid_revoke_rotation_capability(bob: signer, charlie: signer, framework: signer) acquires Account { + chain_id::initialize_for_test(&framework, 4); + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + create_account(signer::address_of(&charlie)); + + let challenge = RotationCapabilityOfferProofChallengeV2 { + chain_id: chain_id::get(), + sequence_number: get_sequence_number(alice_addr), + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_rotation_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + offer_rotation_capability(&alice, ed25519::signature_to_bytes(&alice_rotation_capability_offer_sig), 0, alice_pk_bytes, signer::address_of(&bob)); + revoke_rotation_capability(&alice, signer::address_of(&charlie)); + } + + // + // Tests for key rotation + // + + #[test(account = @aptos_framework)] + public entry fun test_valid_rotate_authentication_key_multi_ed25519_to_multi_ed25519(account: signer) acquires Account, OriginatingAddress { + initialize(&account); + let (curr_sk, curr_pk) = multi_ed25519::generate_keys(2, 3); + let curr_pk_unvalidated = multi_ed25519::public_key_to_unvalidated(&curr_pk); + let curr_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&curr_pk_unvalidated); + let alice_addr = from_bcs::to_address(curr_auth_key); + let alice = create_account_unchecked(alice_addr); + + let (new_sk, new_pk) = multi_ed25519::generate_keys(4, 5); + let new_pk_unvalidated = multi_ed25519::public_key_to_unvalidated(&new_pk); + let new_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&new_pk_unvalidated); + let new_address = from_bcs::to_address(new_auth_key); + + let challenge = RotationProofChallenge { + sequence_number: borrow_global(alice_addr).sequence_number, + originator: alice_addr, + current_auth_key: alice_addr, + new_public_key: multi_ed25519::unvalidated_public_key_to_bytes(&new_pk_unvalidated), + }; + + let from_sig = multi_ed25519::sign_struct(&curr_sk, challenge); + let to_sig = multi_ed25519::sign_struct(&new_sk, challenge); + + rotate_authentication_key( + &alice, + MULTI_ED25519_SCHEME, + multi_ed25519::unvalidated_public_key_to_bytes(&curr_pk_unvalidated), + MULTI_ED25519_SCHEME, + multi_ed25519::unvalidated_public_key_to_bytes(&new_pk_unvalidated), + multi_ed25519::signature_to_bytes(&from_sig), + multi_ed25519::signature_to_bytes(&to_sig), + ); + let address_map = &mut borrow_global_mut(@aptos_framework).address_map; + let expected_originating_address = table::borrow(address_map, new_address); + assert!(*expected_originating_address == alice_addr, 0); + assert!(borrow_global(alice_addr).authentication_key == new_auth_key, 0); + } + + #[test(account = @aptos_framework)] + public entry fun test_valid_rotate_authentication_key_multi_ed25519_to_ed25519(account: signer) acquires Account, OriginatingAddress { + initialize(&account); + + let (curr_sk, curr_pk) = multi_ed25519::generate_keys(2, 3); + let curr_pk_unvalidated = multi_ed25519::public_key_to_unvalidated(&curr_pk); + let curr_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&curr_pk_unvalidated); + let alice_addr = from_bcs::to_address(curr_auth_key); + let alice = create_account_unchecked(alice_addr); + + let account_resource = borrow_global_mut(alice_addr); + + let (new_sk, new_pk) = ed25519::generate_keys(); + let new_pk_unvalidated = ed25519::public_key_to_unvalidated(&new_pk); + let new_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&new_pk_unvalidated); + let new_addr = from_bcs::to_address(new_auth_key); + + let challenge = RotationProofChallenge { + sequence_number: account_resource.sequence_number, + originator: alice_addr, + current_auth_key: alice_addr, + new_public_key: ed25519::unvalidated_public_key_to_bytes(&new_pk_unvalidated), + }; + + let from_sig = multi_ed25519::sign_struct(&curr_sk, challenge); + let to_sig = ed25519::sign_struct(&new_sk, challenge); + + rotate_authentication_key( + &alice, + MULTI_ED25519_SCHEME, + multi_ed25519::unvalidated_public_key_to_bytes(&curr_pk_unvalidated), + ED25519_SCHEME, + ed25519::unvalidated_public_key_to_bytes(&new_pk_unvalidated), + multi_ed25519::signature_to_bytes(&from_sig), + ed25519::signature_to_bytes(&to_sig), + ); + + let address_map = &mut borrow_global_mut(@aptos_framework).address_map; + let expected_originating_address = table::borrow(address_map, new_addr); + assert!(*expected_originating_address == alice_addr, 0); + assert!(borrow_global(alice_addr).authentication_key == new_auth_key, 0); + } + + #[test(account = @aptos_framework)] + #[expected_failure(abort_code = 0x20014, location = Self)] + public entry fun test_max_guid(account: &signer) acquires Account { + let addr = signer::address_of(account); + create_account_unchecked(addr); + let account_state = borrow_global_mut(addr); + account_state.guid_creation_num = MAX_GUID_CREATION_NUM - 1; + create_guid(account); + } + + #[test_only] + struct FakeCoin { } + #[test_only] + struct SadFakeCoin { } + + #[test(account = @0x1234)] + fun test_events(account: &signer) acquires Account { + let addr = signer::address_of(account); + create_account_unchecked(addr); + register_coin(addr); + + let eventhandle = &borrow_global(addr).coin_register_events; + let event = CoinRegisterEvent { type_info: type_info::type_of() }; + + let events = event::emitted_events_by_handle(eventhandle); + assert!(vector::length(&events) == 1, 0); + assert!(vector::borrow(&events, 0) == &event, 1); + assert!(event::was_event_emitted_by_handle(eventhandle, &event), 2); + + let event = CoinRegisterEvent { type_info: type_info::type_of() }; + assert!(!event::was_event_emitted_by_handle(eventhandle, &event), 3); + } +} \ No newline at end of file diff --git a/sources-grammars.ts b/sources-grammars.ts index b81e33c..b14c641 100644 --- a/sources-grammars.ts +++ b/sources-grammars.ts @@ -402,6 +402,7 @@ export const sourcesCommunity: GrammarSource[] = [ displayName: 'Cadence', aliases: ['cdc'], source: 'https://github.com/onflow/vscode-cadence/blob/master/extension/language/syntaxes/cadence.tmGrammar.json', + categories: ['smart contract'], }, { name: 'clarity', @@ -648,6 +649,12 @@ export const sourcesCommunity: GrammarSource[] = [ displayName: 'Mojo', source: 'https://github.com/modularml/mojo-syntax/blob/main/syntaxes/mojo.syntax.json', }, + { + name: 'move', + displayName: 'Move', + source: 'https://github.com/pontem-network/vscode-move-ide/blob/master/syntaxes/move.tmLanguage.json', + categories: ['smart contract', 'markup'], + }, { name: 'narrat', aliases: ['nar'], @@ -768,6 +775,7 @@ export const sourcesCommunity: GrammarSource[] = [ { name: 'solidity', source: 'https://github.com/juanfranblanco/vscode-solidity/blob/master/syntaxes/solidity.json', + categories: ['smart contract'], }, { name: 'sparql',