├── .gitignore ├── LICENSE ├── README.md ├── motoko ├── demo.sh ├── dfx.json └── src │ ├── token.mo │ └── types.mo ├── rust ├── Cargo.lock ├── Cargo.toml ├── deploy.sh ├── dfx.json ├── rust-toolchain ├── src │ └── main.rs └── token.did └── spec.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dfx/ 3 | build/ 4 | target 5 | .config 6 | .cache -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the 13 | copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other 16 | entities that control, are controlled by, or are under common control with 17 | that entity. For the purposes of this definition, "control" means (i) the 18 | power, direct or indirect, to cause the direction or management of such 19 | entity, whether by contract or otherwise, or (ii) ownership of fifty percent 20 | (50%) or more of the outstanding shares, or (iii) beneficial ownership of 21 | such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity exercising 24 | permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation source, and 28 | configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical transformation 31 | or translation of a Source form, including but not limited to compiled 32 | object code, generated documentation, and conversions to other media types. 33 | 34 | "Work" shall mean the work of authorship, whether in Source or Object form, 35 | made available under the License, as indicated by a copyright notice that is 36 | included in or attached to the work (an example is provided in the Appendix 37 | below). 38 | 39 | "Derivative Works" shall mean any work, whether in Source or Object form, 40 | that is based on (or derived from) the Work and for which the editorial 41 | revisions, annotations, elaborations, or other modifications represent, as a 42 | whole, an original work of authorship. For the purposes of this License, 43 | Derivative Works shall not include works that remain separable from, or 44 | merely link (or bind by name) to the interfaces of, the Work and Derivative 45 | Works thereof. 46 | 47 | "Contribution" shall mean any work of authorship, including the original 48 | version of the Work and any modifications or additions to that Work or 49 | Derivative Works thereof, that is intentionally submitted to Licensor for 50 | inclusion in the Work by the copyright owner or by an individual or Legal 51 | Entity authorized to submit on behalf of the copyright owner. For the 52 | purposes of this definition, "submitted" means any form of electronic, 53 | verbal, or written communication sent to the Licensor or its 54 | representatives, including but not limited to communication on electronic 55 | mailing lists, source code control systems, and issue tracking systems that 56 | are managed by, or on behalf of, the Licensor for the purpose of discussing 57 | and improving the Work, but excluding communication that is conspicuously 58 | marked or otherwise designated in writing by the copyright owner as "Not a 59 | Contribution." 60 | 61 | "Contributor" shall mean Licensor and any individual or Legal Entity on 62 | behalf of whom a Contribution has been received by Licensor and subsequently 63 | incorporated within the Work. 64 | 65 | 2. Grant of Copyright License. Subject to the terms and conditions of this 66 | License, each Contributor hereby grants to You a perpetual, worldwide, 67 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 68 | reproduce, prepare Derivative Works of, publicly display, publicly perform, 69 | sublicense, and distribute the Work and such Derivative Works in Source or 70 | Object form. 71 | 72 | 3. Grant of Patent License. Subject to the terms and conditions of this 73 | License, each Contributor hereby grants to You a perpetual, worldwide, 74 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in 75 | this section) patent license to make, have made, use, offer to sell, sell, 76 | import, and otherwise transfer the Work, where such license applies only to 77 | those patent claims licensable by such Contributor that are necessarily 78 | infringed by their Contribution(s) alone or by combination of their 79 | Contribution(s) with the Work to which such Contribution(s) was submitted. 80 | If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this 84 | License for that Work shall terminate as of the date such litigation is 85 | filed. 86 | 87 | 4. Redistribution. You may reproduce and distribute copies of the Work or 88 | Derivative Works thereof in any medium, with or without modifications, and 89 | in Source or Object form, provided that You meet the following conditions: 90 | 91 | a. You must give any other recipients of the Work or Derivative Works a 92 | copy of this License; and 93 | 94 | b. You must cause any modified files to carry prominent notices stating 95 | that You changed the files; and 96 | 97 | c. You must retain, in the Source form of any Derivative Works that You 98 | distribute, all copyright, patent, trademark, and attribution notices 99 | from the Source form of the Work, excluding those notices that do not 100 | pertain to any part of the Derivative Works; and 101 | 102 | d. If the Work includes a "NOTICE" text file as part of its distribution, 103 | then any Derivative Works that You distribute must include a readable 104 | copy of the attribution notices contained within such NOTICE file, 105 | excluding those notices that do not pertain to any part of the Derivative 106 | Works, in at least one of the following places: within a NOTICE text file 107 | distributed as part of the Derivative Works; within the Source form or 108 | documentation, if provided along with the Derivative Works; or, within a 109 | display generated by the Derivative Works, if and wherever such 110 | third-party notices normally appear. The contents of the NOTICE file are 111 | for informational purposes only and do not modify the License. You may 112 | add Your own attribution notices within Derivative Works that You 113 | distribute, alongside or as an addendum to the NOTICE text from the Work, 114 | provided that such additional attribution notices cannot be construed as 115 | modifying the License. 116 | 117 | You may add Your own copyright statement to Your modifications and may 118 | provide additional or different license terms and conditions for use, 119 | reproduction, or distribution of Your modifications, or for any such 120 | Derivative Works as a whole, provided Your use, reproduction, and 121 | distribution of the Work otherwise complies with the conditions stated in 122 | this License. 123 | 124 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 125 | Contribution intentionally submitted for inclusion in the Work by You to the 126 | Licensor shall be under the terms and conditions of this License, without 127 | any additional terms or conditions. Notwithstanding the above, nothing 128 | herein shall supersede or modify the terms of any separate license agreement 129 | you may have executed with Licensor regarding such Contributions. 130 | 131 | 6. Trademarks. This License does not grant permission to use the trade names, 132 | trademarks, service marks, or product names of the Licensor, except as 133 | required for reasonable and customary use in describing the origin of the 134 | Work and reproducing the content of the NOTICE file. 135 | 136 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 137 | writing, Licensor provides the Work (and each Contributor provides its 138 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 139 | KIND, either express or implied, including, without limitation, any 140 | warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or 141 | FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining 142 | the appropriateness of using or redistributing the Work and assume any risks 143 | associated with Your exercise of permissions under this License. 144 | 145 | 8. Limitation of Liability. In no event and under no legal theory, whether in 146 | tort (including negligence), contract, or otherwise, unless required by 147 | applicable law (such as deliberate and grossly negligent acts) or agreed to 148 | in writing, shall any Contributor be liable to You for damages, including 149 | any direct, indirect, special, incidental, or consequential damages of any 150 | character arising as a result of this License or out of the use or inability 151 | to use the Work (including but not limited to damages for loss of goodwill, 152 | work stoppage, computer failure or malfunction, or any and all other 153 | commercial damages or losses), even if such Contributor has been advised of 154 | the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or 157 | Derivative Works thereof, You may choose to offer, and charge a fee for, 158 | acceptance of support, warranty, indemnity, or other liability obligations 159 | and/or rights consistent with this License. However, in accepting such 160 | obligations, You may act only on Your own behalf and on Your sole 161 | responsibility, not on behalf of any other Contributor, and only if You 162 | agree to indemnify, defend, and hold each Contributor harmless for any 163 | liability incurred by, or claims asserted against, such Contributor by 164 | reason of your accepting any such warranty or additional liability. 165 | 166 | END OF TERMS AND CONDITIONS 167 | 168 | LLVM EXCEPTION TO THE APACHE 2.0 LICENSE 169 | 170 | As an exception, if, as a result of your compiling your source code, portions 171 | of this Software are embedded into an Object form of such source code, you may 172 | redistribute such embedded portions in such Object form without complying with 173 | the conditions of Sections 4(a), 4(b) and 4(d) of the License. 174 | 175 | In addition, if you combine or link compiled forms of this Software with 176 | software that is licensed under the GPLv2 ("Combined Software") and if a court 177 | of competent jurisdiction determines that the patent provision (Section 3), the 178 | indemnity provision (Section 9) or other Section of the License conflicts with 179 | the conditions of the GPLv2, you may retroactively and prospectively choose to 180 | deem waived or otherwise exclude such Section(s) of the License, but only in 181 | their entirety and only with respect to the Combined Software. 182 | 183 | END OF LLVM EXCEPTION 184 | 185 | APPENDIX: How to apply the Apache License to your work. 186 | 187 | To apply the Apache License to your work, attach the following boilerplate 188 | notice, with the fields enclosed by brackets "[]" replaced with your own 189 | identifying information. (Don't include the brackets!) The text should be 190 | enclosed in the appropriate comment syntax for the file format. We also 191 | recommend that a file or class name and description of purpose be included on 192 | the same "printed page" as the copyright notice for easier identification 193 | within third-party archives. 194 | 195 | Copyright 2021 Rocklabs 196 | 197 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 198 | this file except in compliance with the License. You may obtain a copy of the 199 | License at 200 | 201 | http://www.apache.org/licenses/LICENSE-2.0 202 | 203 | Unless required by applicable law or agreed to in writing, software distributed 204 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 205 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 206 | specific language governing permissions and limitations under the License. 207 | 208 | END OF APPENDIX 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Token standard is essential for the Internet Computer ecosystem, especially for the decentralized finance(DeFi) system. We implemented an ERC-20 style token standard in both Motoko and Rust, the standard is named DIP20. 4 | 5 | You can find the interface descriptions in the [specification file](./spec.md). 6 | 7 | [This branch](https://github.com/dfinance-tech/ic-token/tree/templates) contains code of several other token canister templates. 8 | 9 | 10 | ## Development 11 | 12 | You need the latest DFINITY Canister SDK to be able to build and deploy a token canister: 13 | 14 | ```shell 15 | sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)" 16 | ``` 17 | 18 | Navigate to a the sub directory and start a local development network: 19 | 20 | ```shell 21 | cd motoko 22 | dfx start --background 23 | ``` 24 | 25 | Create canisters: 26 | 27 | ```shell 28 | dfx canister create --all 29 | ``` 30 | 31 | Install code for token canister: 32 | 33 | ``` 34 | dfx build 35 | 36 | dfx canister install token --argument="(\"\", \"\", \"\", , , , )" 37 | ``` 38 | e.g.: 39 | ``` 40 | dfx canister install token --argument="(\"data:image/jpeg;base64,...\", \"DFinance Coin\", \"DFC\", 8, 10000000000000000, principal \"4qehi-lqyo6-afz4c-hwqwo-lubfi-4evgk-5vrn5-rldx2-lheha-xs7a4-gae\", 10000)" 41 | ``` 42 | 43 | Refer to `demo.sh` in the corresponding sub directory for more details. 44 | 45 | 46 | 47 | ## Contributing 48 | 49 | We'd like to collaborate with the community to provide better token standard implementation for the developers on the IC, if you have some ideas you'd like to discuss, submit an issue, if you want to improve the code or you made a different implementation, make a pull request! 50 | 51 | -------------------------------------------------------------------------------- /motoko/demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set -e 4 | 5 | # clear 6 | dfx stop 7 | rm -rf .dfx 8 | 9 | ALICE_HOME=$(mktemp -d) 10 | BOB_HOME=$(mktemp -d) 11 | DAN_HOME=$(mktemp -d) 12 | FEE_HOME=$(mktemp -d) 13 | HOME=$ALICE_HOME 14 | 15 | ALICE_PUBLIC_KEY="principal \"$( \ 16 | HOME=$ALICE_HOME dfx identity get-principal 17 | )\"" 18 | BOB_PUBLIC_KEY="principal \"$( \ 19 | HOME=$BOB_HOME dfx identity get-principal 20 | )\"" 21 | DAN_PUBLIC_KEY="principal \"$( \ 22 | HOME=$DAN_HOME dfx identity get-principal 23 | )\"" 24 | FEE_PUBLIC_KEY="principal \"$( \ 25 | HOME=$FEE_HOME dfx identity get-principal 26 | )\"" 27 | 28 | echo Alice id = $ALICE_PUBLIC_KEY 29 | echo Bob id = $BOB_PUBLIC_KEY 30 | echo Dan id = $DAN_PUBLIC_KEY 31 | echo Fee id = $FEE_PUBLIC_KEY 32 | 33 | dfx start --clean --background 34 | dfx canister create --all 35 | dfx build 36 | 37 | TOKENID=$(dfx canister id token) 38 | TOKENID="principal \"$TOKENID\"" 39 | 40 | echo Token id: $TOKENID 41 | 42 | echo 43 | echo == Install token canister 44 | echo 45 | 46 | HOME=$ALICE_HOME 47 | eval dfx canister install token --argument="'(\"Test Token Logo\", \"Test Token Name\", \"Test Token Symbol\", 3, 1000000, $ALICE_PUBLIC_KEY, 0)'" 48 | 49 | echo 50 | echo == Initial setting for token canister 51 | echo 52 | 53 | eval dfx canister call token setFeeTo "'($FEE_PUBLIC_KEY)'" 54 | eval dfx canister call token setFee "'(100)'" 55 | 56 | echo 57 | echo == Initial token balances for Alice and Bob, Dan, FeeTo 58 | echo 59 | 60 | echo Alice = $( \ 61 | eval dfx canister call token balanceOf "'($ALICE_PUBLIC_KEY)'" \ 62 | ) 63 | echo Bob = $( \ 64 | eval dfx canister call token balanceOf "'($BOB_PUBLIC_KEY)'" \ 65 | ) 66 | echo Dan = $( \ 67 | eval dfx canister call token balanceOf "'($DAN_PUBLIC_KEY)'" \ 68 | ) 69 | echo FeeTo = $( \ 70 | eval dfx canister call token balanceOf "'($FEE_PUBLIC_KEY)'" \ 71 | ) 72 | 73 | echo 74 | echo == Transfer 0 tokens from Alice to Bob, should Return false, as value is smaller than fee. 75 | echo 76 | 77 | eval dfx canister call token transfer "'($BOB_PUBLIC_KEY, 0)'" 78 | 79 | echo 80 | echo == Transfer 0 tokens from Alice to Alice, should Return false, as value is smaller than fee. 81 | echo 82 | 83 | eval dfx canister call token transfer "'($ALICE_PUBLIC_KEY, 0)'" 84 | 85 | echo 86 | echo == Transfer 0.1 tokens from Alice to Bob, should success, revieve 0, as value = fee. 87 | echo 88 | 89 | eval dfx canister call token transfer "'($BOB_PUBLIC_KEY, 100)'" 90 | 91 | echo 92 | echo == Transfer 0.1 tokens from Alice to Alice, should success, revieve 0, as value = fee. 93 | echo 94 | 95 | eval dfx canister call token transfer "'($ALICE_PUBLIC_KEY, 100)'" 96 | 97 | echo 98 | echo == Transfer 100 tokens from Alice to Alice, should success. 99 | echo 100 | 101 | eval dfx canister call token transfer "'($ALICE_PUBLIC_KEY, 100_000)'" 102 | 103 | echo 104 | echo == Transfer 2000 tokens from Alice to Alice, should Return false, as no enough balance. 105 | echo 106 | 107 | eval dfx canister call token transfer "'($ALICE_PUBLIC_KEY, 2_000_000)'" 108 | 109 | echo 110 | echo == Transfer 0 tokens from Bob to Bob, should Return false, as value is smaller than fee. 111 | echo 112 | 113 | HOME=$BOB_HOME 114 | eval dfx canister call token transfer "'($BOB_PUBLIC_KEY, 0)'" 115 | 116 | echo 117 | echo == Transfer 42 tokens from Alice to Bob, should success. 118 | echo 119 | 120 | HOME=$ALICE_HOME 121 | eval dfx canister call token transfer "'($BOB_PUBLIC_KEY, 42_000)'" 122 | 123 | echo 124 | echo == Alice grants Dan permission to spend 1 of her tokens, should success. 125 | echo 126 | 127 | eval dfx canister call token approve "'($DAN_PUBLIC_KEY, 1_000)'" 128 | 129 | echo 130 | echo == Alice grants Dan permission to spend 0 of her tokens, should success. 131 | echo 132 | 133 | eval dfx canister call token approve "'($DAN_PUBLIC_KEY, 0)'" 134 | 135 | echo 136 | echo == Bob grants Dan permission to spend 1 of her tokens, should success. 137 | echo 138 | 139 | HOME=$BOB_HOME 140 | eval dfx canister call token approve "'($DAN_PUBLIC_KEY, 1_000)'" 141 | 142 | echo 143 | echo == Dan transfer 1 token from Bob to Alice, should success. 144 | echo 145 | 146 | HOME=$DAN_HOME 147 | eval dfx canister call token transferFrom "'($BOB_PUBLIC_KEY, $ALICE_PUBLIC_KEY, 1_000)'" 148 | 149 | 150 | echo 151 | echo == Transfer 40.9 tokens from Bob to Alice, should success. 152 | echo 153 | 154 | HOME=$BOB_HOME 155 | eval dfx canister call token transfer "'($ALICE_PUBLIC_KEY, 40_900)'" 156 | 157 | echo 158 | echo == token balances for Alice, Bob, Dan and FeeTo. 159 | echo 160 | 161 | echo Alice = $( \ 162 | eval dfx canister call token balanceOf "'($ALICE_PUBLIC_KEY)'" \ 163 | ) 164 | echo Bob = $( \ 165 | eval dfx canister call token balanceOf "'($BOB_PUBLIC_KEY)'" \ 166 | ) 167 | echo Dan = $( \ 168 | eval dfx canister call token balanceOf "'($DAN_PUBLIC_KEY)'" \ 169 | ) 170 | echo FeeTo = $( \ 171 | eval dfx canister call token balanceOf "'($FEE_PUBLIC_KEY)'" \ 172 | ) 173 | 174 | echo 175 | echo == Alice grants Dan permission to spend 50 of her tokens, should success. 176 | echo 177 | 178 | HOME=$ALICE_HOME 179 | eval dfx canister call token approve "'($DAN_PUBLIC_KEY, 50_000)'" 180 | 181 | echo 182 | echo == Alices allowances 183 | echo 184 | 185 | echo Alices allowance for Dan = $( \ 186 | eval dfx canister call token allowance "'($ALICE_PUBLIC_KEY, $DAN_PUBLIC_KEY)'" \ 187 | ) 188 | echo Alices allowance for Bob = $( \ 189 | eval dfx canister call token allowance "'($ALICE_PUBLIC_KEY, $BOB_PUBLIC_KEY)'" \ 190 | ) 191 | 192 | echo 193 | echo == Dan transfers 40 tokens from Alice to Bob, should success. 194 | echo 195 | 196 | HOME=$DAN_HOME 197 | eval dfx canister call token transferFrom "'($ALICE_PUBLIC_KEY, $BOB_PUBLIC_KEY, 40_000)'" 198 | 199 | echo 200 | echo == Alice transfer 1 tokens To Dan 201 | echo 202 | 203 | HOME=$ALICE_HOME 204 | eval dfx canister call token transfer "'($DAN_PUBLIC_KEY, 1_000)'" 205 | 206 | echo 207 | echo == Dan transfers 40 tokens from Alice to Bob, should Return false, as allowance remain 10, smaller than 40. 208 | echo 209 | 210 | HOME=$DAN_HOME 211 | eval dfx canister call token transferFrom "'($ALICE_PUBLIC_KEY, $BOB_PUBLIC_KEY, 40_000)'" 212 | 213 | echo 214 | echo == Token balance for Alice and Bob and Dan 215 | echo 216 | 217 | echo Alice = $( \ 218 | eval dfx canister call token balanceOf "'($ALICE_PUBLIC_KEY)'" \ 219 | ) 220 | echo Bob = $( \ 221 | eval dfx canister call token balanceOf "'($BOB_PUBLIC_KEY)'" \ 222 | ) 223 | echo Dan = $( \ 224 | eval dfx canister call token balanceOf "'($DAN_PUBLIC_KEY)'" \ 225 | ) 226 | echo Fee = $( \ 227 | eval dfx canister call token balanceOf "'($FEE_PUBLIC_KEY)'" \ 228 | ) 229 | 230 | echo 231 | echo == Alice allowances 232 | echo 233 | 234 | echo Alices allowance for Bob = $( \ 235 | eval dfx canister call token allowance "'($ALICE_PUBLIC_KEY, $BOB_PUBLIC_KEY)'" \ 236 | ) 237 | echo Alices allowance for Dan = $( \ 238 | eval dfx canister call token allowance "'($ALICE_PUBLIC_KEY, $DAN_PUBLIC_KEY)'" \ 239 | ) 240 | 241 | 242 | echo 243 | echo == Alice grants Bob permission to spend 100 of her tokens 244 | echo 245 | 246 | HOME=$ALICE_HOME 247 | eval dfx canister call token approve "'($BOB_PUBLIC_KEY, 100_000)'" 248 | 249 | echo 250 | echo == Alice allowances 251 | echo 252 | 253 | echo Alices allowance for Bob = $( \ 254 | eval dfx canister call token allowance "'($ALICE_PUBLIC_KEY, $BOB_PUBLIC_KEY)'" \ 255 | ) 256 | echo Alices allowance for Dan = $( \ 257 | eval dfx canister call token allowance "'($ALICE_PUBLIC_KEY, $DAN_PUBLIC_KEY)'" \ 258 | ) 259 | 260 | echo 261 | echo == Bob transfers 99 tokens from Alice to Dan 262 | echo 263 | 264 | HOME=$BOB_HOME 265 | eval dfx canister call token transferFrom "'($ALICE_PUBLIC_KEY, $DAN_PUBLIC_KEY, 99_000)'" 266 | 267 | echo 268 | echo == Balances 269 | echo 270 | 271 | echo Alice = $( \ 272 | eval dfx canister call token balanceOf "'($ALICE_PUBLIC_KEY)'" \ 273 | ) 274 | echo Bob = $( \ 275 | eval dfx canister call token balanceOf "'($BOB_PUBLIC_KEY)'" \ 276 | ) 277 | echo Dan = $( \ 278 | eval dfx canister call token balanceOf "'($DAN_PUBLIC_KEY)'" \ 279 | ) 280 | echo Fee = $( \ 281 | eval dfx canister call token balanceOf "'($FEE_PUBLIC_KEY)'" \ 282 | ) 283 | 284 | echo 285 | echo == Alice allowances 286 | echo 287 | 288 | echo Alices allowance for Bob = $( eval dfx canister call token allowance "'($ALICE_PUBLIC_KEY, $BOB_PUBLIC_KEY)'" ) 289 | echo Alices allowance for Dan = $( eval dfx canister call token allowance "'($ALICE_PUBLIC_KEY, $DAN_PUBLIC_KEY)'" ) 290 | 291 | echo 292 | echo == Dan grants Bob permission to spend 100 of this tokens, should success. 293 | echo 294 | 295 | HOME=$DAN_HOME 296 | eval dfx canister call token approve "'($BOB_PUBLIC_KEY, 100_000)'" 297 | 298 | echo 299 | echo == Dan grants Bob permission to spend 50 of this tokens 300 | echo 301 | 302 | eval dfx canister call token approve "'($BOB_PUBLIC_KEY, 50_000)'" 303 | 304 | echo 305 | echo == Dan allowances 306 | echo 307 | 308 | echo Dan allowance for Bob = $( \ 309 | eval dfx canister call token allowance "'($DAN_PUBLIC_KEY, $BOB_PUBLIC_KEY)'" \ 310 | ) 311 | echo Dan allowance for Alice = $( \ 312 | eval dfx canister call token allowance "'($DAN_PUBLIC_KEY, $ALICE_PUBLIC_KEY)'" \ 313 | ) 314 | 315 | echo 316 | echo == Dan change Bobs permission to spend 40 of this tokens instead of 50 317 | echo 318 | 319 | eval dfx canister call token approve "'($BOB_PUBLIC_KEY, 40_000)'" 320 | 321 | echo 322 | echo == Dan allowances 323 | echo 324 | 325 | echo Dan allowance for Bob = $( \ 326 | eval dfx canister call token allowance "'($DAN_PUBLIC_KEY, $BOB_PUBLIC_KEY)'" \ 327 | ) 328 | echo Dan allowance for Alice = $( \ 329 | eval dfx canister call token allowance "'($DAN_PUBLIC_KEY, $ALICE_PUBLIC_KEY)'" \ 330 | ) 331 | 332 | echo 333 | echo == logo 334 | echo 335 | eval dfx canister call token logo 336 | 337 | echo 338 | echo == name 339 | echo 340 | eval dfx canister call token name 341 | 342 | echo 343 | echo == symbol 344 | echo 345 | eval dfx canister call token symbol 346 | 347 | echo 348 | echo == decimals 349 | echo 350 | eval dfx canister call token decimals 351 | 352 | echo 353 | echo == totalSupply 354 | echo 355 | eval dfx canister call token totalSupply 356 | 357 | echo 358 | echo == getMetadata 359 | echo 360 | eval dfx canister call token getMetadata 361 | 362 | echo 363 | echo == historySize 364 | echo 365 | eval dfx canister call token historySize 366 | 367 | echo 368 | echo == getTransaction 369 | echo 370 | eval dfx canister call token getTransaction "'(1)'" 371 | 372 | echo 373 | echo == getTransactions 374 | echo 375 | eval dfx canister call token getTransactions "'(0,100)'" 376 | 377 | echo 378 | echo == getUserTransactionAmount 379 | echo 380 | eval dfx canister call token getUserTransactionAmount "'($ALICE_PUBLIC_KEY)'" 381 | 382 | echo 383 | echo == getUserTransactions 384 | echo 385 | eval dfx canister call token getUserTransactions "'($ALICE_PUBLIC_KEY, 0, 1000)'" 386 | 387 | echo 388 | echo == getTokenInfo 389 | echo 390 | eval dfx canister call token getTokenInfo 391 | 392 | echo 393 | echo == getHolders 394 | echo 395 | eval dfx canister call token getHolders "'(0,100)'" 396 | 397 | echo 398 | echo == getAllowanceSize 399 | echo 400 | eval dfx canister call token getAllowanceSize 401 | 402 | echo 403 | echo == getUserApprovals 404 | echo 405 | eval dfx canister call token getUserApprovals "'($ALICE_PUBLIC_KEY)'" 406 | 407 | echo 408 | echo == get alice getUserTransactions 409 | echo 410 | eval dfx canister call token getUserTransactions "'($ALICE_PUBLIC_KEY, 0, 1000)'" 411 | 412 | echo 413 | echo == get bob History 414 | echo 415 | eval dfx canister call token getUserTransactions "'($BOB_PUBLIC_KEY, 0, 1000)'" 416 | 417 | echo 418 | echo == get dan History 419 | echo 420 | eval dfx canister call token getUserTransactions "'($DAN_PUBLIC_KEY, 0, 1000)'" 421 | 422 | echo 423 | echo == get fee History 424 | echo 425 | eval dfx canister call token getUserTransactions "'($FEE_PUBLIC_KEY, 0, 1000)'" 426 | 427 | 428 | echo 429 | echo == Upgrade token 430 | echo 431 | HOME=$ALICE_HOME 432 | eval dfx canister install token --argument="'(\"test\", \"Test Token\", \"TT\", 2, 100, $ALICE_PUBLIC_KEY)'" -m=upgrade 433 | 434 | echo 435 | echo == all History 436 | echo 437 | eval dfx canister call token getTransactions "'(0, 1000)'" 438 | 439 | echo 440 | echo == getTokenInfo 441 | echo 442 | dfx canister call token getTokenInfo 443 | 444 | echo 445 | echo == get alice History 446 | echo 447 | eval dfx canister call token getUserTransactions "'($ALICE_PUBLIC_KEY, 0, 1000)'" 448 | 449 | dfx stop 450 | -------------------------------------------------------------------------------- /motoko/dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "token": { 4 | "main": "src/token.mo" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /motoko/src/token.mo: -------------------------------------------------------------------------------- 1 | /** 2 | * Module : token.mo 3 | * Copyright : 2021 Rocklabs 4 | * License : Apache 2.0 with LLVM Exception 5 | * Maintainer : Rocklabs 6 | * Stability : Experimental 7 | */ 8 | 9 | import HashMap "mo:base/HashMap"; 10 | import Principal "mo:base/Principal"; 11 | import Types "./types"; 12 | import Time "mo:base/Time"; 13 | import Iter "mo:base/Iter"; 14 | import Array "mo:base/Array"; 15 | import Option "mo:base/Option"; 16 | import Order "mo:base/Order"; 17 | import Nat "mo:base/Nat"; 18 | import Result "mo:base/Result"; 19 | import ExperimentalCycles "mo:base/ExperimentalCycles"; 20 | 21 | shared(msg) actor class Token( 22 | _logo: Text, 23 | _name: Text, 24 | _symbol: Text, 25 | _decimals: Nat8, 26 | _totalSupply: Nat, 27 | _owner: Principal, 28 | _fee: Nat 29 | ) { 30 | type Operation = Types.Operation; 31 | type TransactionStatus = Types.TransactionStatus; 32 | type TxRecord = Types.TxRecord; 33 | type Metadata = { 34 | logo : Text; 35 | name : Text; 36 | symbol : Text; 37 | decimals : Nat8; 38 | totalSupply : Nat; 39 | owner : Principal; 40 | fee : Nat; 41 | }; 42 | // returns tx index or error msg 43 | type TxReceipt = Result.Result; 48 | 49 | private stable var owner_ : Principal = _owner; 50 | private stable var logo_ : Text = _logo; 51 | private stable var name_ : Text = _name; 52 | private stable var decimals_ : Nat8 = _decimals; 53 | private stable var symbol_ : Text = _symbol; 54 | private stable var totalSupply_ : Nat = _totalSupply; 55 | private stable var blackhole : Principal = Principal.fromText("aaaaa-aa"); 56 | private stable var feeTo : Principal = owner_; 57 | private stable var fee : Nat = _fee; 58 | private stable var balanceEntries : [(Principal, Nat)] = []; 59 | private stable var allowanceEntries : [(Principal, [(Principal, Nat)])] = []; 60 | private var balances = HashMap.HashMap(1, Principal.equal, Principal.hash); 61 | private var allowances = HashMap.HashMap>(1, Principal.equal, Principal.hash); 62 | balances.put(owner_, totalSupply_); 63 | private stable let genesis : TxRecord = { 64 | caller = ?owner_; 65 | op = #mint; 66 | index = 0; 67 | from = blackhole; 68 | to = owner_; 69 | amount = totalSupply_; 70 | fee = 0; 71 | timestamp = Time.now(); 72 | status = #succeeded; 73 | }; 74 | private stable var ops : [TxRecord] = [genesis]; 75 | 76 | private func addRecord( 77 | caller: ?Principal, op: Operation, from: Principal, to: Principal, amount: Nat, 78 | fee: Nat, timestamp: Time.Time, status: TransactionStatus 79 | ): Nat { 80 | let index = ops.size(); 81 | let o : TxRecord = { 82 | caller = caller; 83 | op = op; 84 | index = index; 85 | from = from; 86 | to = to; 87 | amount = amount; 88 | fee = fee; 89 | timestamp = timestamp; 90 | status = status; 91 | }; 92 | ops := Array.append(ops, [o]); 93 | return index; 94 | }; 95 | 96 | private func _chargeFee(from: Principal, fee: Nat) { 97 | if(fee > 0) { 98 | _transfer(from, feeTo, fee); 99 | }; 100 | }; 101 | 102 | private func _transfer(from: Principal, to: Principal, value: Nat) { 103 | let from_balance = _balanceOf(from); 104 | let from_balance_new : Nat = from_balance - value; 105 | if (from_balance_new != 0) { balances.put(from, from_balance_new); } 106 | else { balances.delete(from); }; 107 | 108 | let to_balance = _balanceOf(to); 109 | let to_balance_new : Nat = to_balance + value; 110 | if (to_balance_new != 0) { balances.put(to, to_balance_new); }; 111 | }; 112 | 113 | private func _balanceOf(who: Principal) : Nat { 114 | switch (balances.get(who)) { 115 | case (?balance) { return balance; }; 116 | case (_) { return 0; }; 117 | } 118 | }; 119 | 120 | private func _allowance(owner: Principal, spender: Principal) : Nat { 121 | switch(allowances.get(owner)) { 122 | case (?allowance_owner) { 123 | switch(allowance_owner.get(spender)) { 124 | case (?allowance) { return allowance; }; 125 | case (_) { return 0; }; 126 | } 127 | }; 128 | case (_) { return 0; }; 129 | } 130 | }; 131 | 132 | /* 133 | * Core interfaces: 134 | * update calls: 135 | * transfer/transferFrom/approve 136 | * query calls: 137 | * logo/name/symbol/decimal/totalSupply/balanceOf/allowance/getMetadata 138 | * historySize/getTransaction/getTransactions 139 | */ 140 | 141 | /// Transfers value amount of tokens to Principal to. 142 | public shared(msg) func transfer(to: Principal, value: Nat) : async TxReceipt { 143 | if (_balanceOf(msg.caller) < value + fee) { return #err(#InsufficientBalance); }; 144 | _chargeFee(msg.caller, fee); 145 | _transfer(msg.caller, to, value); 146 | let txid = addRecord(null, #transfer, msg.caller, to, value, fee, Time.now(), #succeeded); 147 | return #ok(txid); 148 | }; 149 | 150 | /// Transfers value amount of tokens from Principal from to Principal to. 151 | public shared(msg) func transferFrom(from: Principal, to: Principal, value: Nat) : async TxReceipt { 152 | if (_balanceOf(from) < value + fee) { return #err(#InsufficientBalance); }; 153 | let allowed : Nat = _allowance(from, msg.caller); 154 | if (allowed < value + fee) { return #err(#InsufficientAllowance); }; 155 | _chargeFee(from, fee); 156 | _transfer(from, to, value); 157 | let allowed_new : Nat = allowed - value - fee; 158 | if (allowed_new != 0) { 159 | let allowance_from = Types.unwrap(allowances.get(from)); 160 | allowance_from.put(msg.caller, allowed_new); 161 | allowances.put(from, allowance_from); 162 | } else { 163 | if (allowed != 0) { 164 | let allowance_from = Types.unwrap(allowances.get(from)); 165 | allowance_from.delete(msg.caller); 166 | if (allowance_from.size() == 0) { allowances.delete(from); } 167 | else { allowances.put(from, allowance_from); }; 168 | }; 169 | }; 170 | let txid = addRecord(?msg.caller, #transferFrom, from, to, value, fee, Time.now(), #succeeded); 171 | return #ok(txid); 172 | }; 173 | 174 | /// Allows spender to withdraw from your account multiple times, up to the value amount. 175 | /// If this function is called again it overwrites the current allowance with value. 176 | public shared(msg) func approve(spender: Principal, value: Nat) : async TxReceipt { 177 | if(_balanceOf(msg.caller) < fee) { return #err(#InsufficientBalance); }; 178 | _chargeFee(msg.caller, fee); 179 | let v = value + fee; 180 | if (value == 0 and Option.isSome(allowances.get(msg.caller))) { 181 | let allowance_caller = Types.unwrap(allowances.get(msg.caller)); 182 | allowance_caller.delete(spender); 183 | if (allowance_caller.size() == 0) { allowances.delete(msg.caller); } 184 | else { allowances.put(msg.caller, allowance_caller); }; 185 | } else if (value != 0 and Option.isNull(allowances.get(msg.caller))) { 186 | var temp = HashMap.HashMap(1, Principal.equal, Principal.hash); 187 | temp.put(spender, v); 188 | allowances.put(msg.caller, temp); 189 | } else if (value != 0 and Option.isSome(allowances.get(msg.caller))) { 190 | let allowance_caller = Types.unwrap(allowances.get(msg.caller)); 191 | allowance_caller.put(spender, v); 192 | allowances.put(msg.caller, allowance_caller); 193 | }; 194 | let txid = addRecord(null, #approve, msg.caller, spender, v, fee, Time.now(), #succeeded); 195 | return #ok(txid); 196 | }; 197 | 198 | public shared(msg) func mint(to: Principal, amount: Nat): async TxReceipt { 199 | if(msg.caller != owner_) { 200 | return #err(#Unauthorized); 201 | }; 202 | let to_balance = _balanceOf(to); 203 | totalSupply_ += amount; 204 | balances.put(to, to_balance + amount); 205 | let txid = addRecord(?msg.caller, #mint, blackhole, to, amount, 0, Time.now(), #succeeded); 206 | return #ok(txid); 207 | }; 208 | 209 | public shared(msg) func burn(amount: Nat): async TxReceipt { 210 | let from_balance = _balanceOf(msg.caller); 211 | if(from_balance < amount) { 212 | return #err(#InsufficientBalance); 213 | }; 214 | totalSupply_ -= amount; 215 | balances.put(msg.caller, from_balance - amount); 216 | let txid = addRecord(?msg.caller, #burn, msg.caller, blackhole, amount, 0, Time.now(), #succeeded); 217 | return #ok(txid); 218 | }; 219 | 220 | public query func logo() : async Text { 221 | return logo_; 222 | }; 223 | 224 | public query func name() : async Text { 225 | return name_; 226 | }; 227 | 228 | public query func symbol() : async Text { 229 | return symbol_; 230 | }; 231 | 232 | public query func decimals() : async Nat8 { 233 | return decimals_; 234 | }; 235 | 236 | public query func totalSupply() : async Nat { 237 | return totalSupply_; 238 | }; 239 | 240 | public query func getTokenFee() : async Nat { 241 | return fee; 242 | }; 243 | 244 | public query func balanceOf(who: Principal) : async Nat { 245 | return _balanceOf(who); 246 | }; 247 | 248 | public query func allowance(owner: Principal, spender: Principal) : async Nat { 249 | return _allowance(owner, spender); 250 | }; 251 | 252 | public query func getMetadata() : async Metadata { 253 | return { 254 | logo = logo_; 255 | name = name_; 256 | symbol = symbol_; 257 | decimals = decimals_; 258 | totalSupply = totalSupply_; 259 | owner = owner_; 260 | fee = fee; 261 | }; 262 | }; 263 | 264 | /// Get transaction history size 265 | public query func historySize() : async Nat { 266 | return ops.size(); 267 | }; 268 | 269 | /// Get transaction by index. 270 | public query func getTransaction(index: Nat) : async TxRecord { 271 | return ops[index]; 272 | }; 273 | 274 | /// Get history 275 | public query func getTransactions(start: Nat, limit: Nat) : async [TxRecord] { 276 | var ret: [TxRecord] = []; 277 | var i = start; 278 | while(i < start + limit and i < ops.size()) { 279 | ret := Array.append(ret, [ops[i]]); 280 | i += 1; 281 | }; 282 | return ret; 283 | }; 284 | 285 | /* 286 | * Optional interfaces: 287 | * setLogo/setFee/setFeeTo/setOwner 288 | * getUserTransactionsAmount/getUserTransactions 289 | * getTokenInfo/getHolders/getUserApprovals 290 | */ 291 | public shared(msg) func setLogo(logo: Text) { 292 | assert(msg.caller == owner_); 293 | logo_ := logo; 294 | }; 295 | 296 | public shared(msg) func setFeeTo(to: Principal) { 297 | assert(msg.caller == owner_); 298 | feeTo := to; 299 | }; 300 | 301 | public shared(msg) func setFee(_fee: Nat) { 302 | assert(msg.caller == owner_); 303 | fee := _fee; 304 | }; 305 | 306 | public shared(msg) func setOwner(_owner: Principal) { 307 | assert(msg.caller == owner_); 308 | owner_ := _owner; 309 | }; 310 | 311 | public query func getUserTransactionAmount(a: Principal) : async Nat { 312 | var res: Nat = 0; 313 | for (i in ops.vals()) { 314 | if (i.caller == ?a or i.from == a or i.to == a) { 315 | res += 1; 316 | }; 317 | }; 318 | return res; 319 | }; 320 | 321 | public query func getUserTransactions(a: Principal, start: Nat, limit: Nat) : async [TxRecord] { 322 | var res: [TxRecord] = []; 323 | var index: Nat = 0; 324 | for (i in ops.vals()) { 325 | if (i.caller == ?a or i.from == a or i.to == a) { 326 | if(index >= start and index < start + limit) { 327 | res := Array.append(res, [i]); 328 | }; 329 | index += 1; 330 | }; 331 | }; 332 | return res; 333 | }; 334 | 335 | public type TokenInfo = { 336 | metadata: Metadata; 337 | feeTo: Principal; 338 | // status info 339 | historySize: Nat; 340 | deployTime: Time.Time; 341 | holderNumber: Nat; 342 | cycles: Nat; 343 | }; 344 | public query func getTokenInfo(): async TokenInfo { 345 | { 346 | metadata = { 347 | logo = logo_; 348 | name = name_; 349 | symbol = symbol_; 350 | decimals = decimals_; 351 | totalSupply = totalSupply_; 352 | owner = owner_; 353 | fee = fee; 354 | }; 355 | feeTo = feeTo; 356 | historySize = ops.size(); 357 | deployTime = genesis.timestamp; 358 | holderNumber = balances.size(); 359 | cycles = ExperimentalCycles.balance(); 360 | } 361 | }; 362 | 363 | public query func getHolders(start: Nat, limit: Nat) : async [(Principal, Nat)] { 364 | let temp = Iter.toArray(balances.entries()); 365 | func order (a: (Principal, Nat), b: (Principal, Nat)) : Order.Order { 366 | return Nat.compare(b.1, a.1); 367 | }; 368 | let sorted = Array.sort(temp, order); 369 | let limit_: Nat = if(start + limit > temp.size()) { 370 | temp.size() - start 371 | } else { 372 | limit 373 | }; 374 | let res = Array.init<(Principal, Nat)>(limit_, (owner_, 0)); 375 | for (i in Iter.range(0, limit_ - 1)) { 376 | res[i] := sorted[i+start]; 377 | }; 378 | return Array.freeze(res); 379 | }; 380 | 381 | public query func getAllowanceSize() : async Nat { 382 | var size : Nat = 0; 383 | for ((k, v) in allowances.entries()) { 384 | size += v.size(); 385 | }; 386 | return size; 387 | }; 388 | 389 | public query func getUserApprovals(who : Principal) : async [(Principal, Nat)] { 390 | switch (allowances.get(who)) { 391 | case (?allowance_who) { 392 | return Iter.toArray(allowance_who.entries()); 393 | }; 394 | case (_) { 395 | return []; 396 | }; 397 | } 398 | }; 399 | 400 | /* 401 | * upgrade functions 402 | */ 403 | system func preupgrade() { 404 | balanceEntries := Iter.toArray(balances.entries()); 405 | var size : Nat = allowances.size(); 406 | var temp : [var (Principal, [(Principal, Nat)])] = Array.init<(Principal, [(Principal, Nat)])>(size, (owner_, [])); 407 | size := 0; 408 | for ((k, v) in allowances.entries()) { 409 | temp[size] := (k, Iter.toArray(v.entries())); 410 | size += 1; 411 | }; 412 | allowanceEntries := Array.freeze(temp); 413 | }; 414 | 415 | system func postupgrade() { 416 | balances := HashMap.fromIter(balanceEntries.vals(), 1, Principal.equal, Principal.hash); 417 | balanceEntries := []; 418 | for ((k, v) in allowanceEntries.vals()) { 419 | let allowed_temp = HashMap.fromIter(v.vals(), 1, Principal.equal, Principal.hash); 420 | allowances.put(k, allowed_temp); 421 | }; 422 | allowanceEntries := []; 423 | }; 424 | }; 425 | -------------------------------------------------------------------------------- /motoko/src/types.mo: -------------------------------------------------------------------------------- 1 | /** 2 | * Module : types.mo 3 | * Copyright : 2021 Rocklabs 4 | * License : Apache 2.0 with LLVM Exception 5 | * Maintainer : Rocklabs 6 | * Stability : Experimental 7 | */ 8 | 9 | import Time "mo:base/Time"; 10 | import P "mo:base/Prelude"; 11 | 12 | module { 13 | /// Update call operations 14 | public type Operation = { 15 | #mint; 16 | #burn; 17 | #transfer; 18 | #transferFrom; 19 | #approve; 20 | }; 21 | public type TransactionStatus = { 22 | #succeeded; 23 | #inprogress; 24 | #failed; 25 | }; 26 | /// Update call operation record fields 27 | public type TxRecord = { 28 | caller: ?Principal; 29 | op: Operation; 30 | index: Nat; 31 | from: Principal; 32 | to: Principal; 33 | amount: Nat; 34 | fee: Nat; 35 | timestamp: Time.Time; 36 | status: TransactionStatus; 37 | }; 38 | 39 | public func unwrap(x : ?T) : T = 40 | switch x { 41 | case null { P.unreachable() }; 42 | case (?x_) { x_ }; 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.44" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" 19 | 20 | [[package]] 21 | name = "arrayvec" 22 | version = "0.5.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 25 | 26 | [[package]] 27 | name = "ascii-canvas" 28 | version = "3.0.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" 31 | dependencies = [ 32 | "term", 33 | ] 34 | 35 | [[package]] 36 | name = "assert-panic" 37 | version = "1.0.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "763b2b82aee23fe46c14c792470080c26538396e9ea589f548298f26b22d7f41" 40 | 41 | [[package]] 42 | name = "async-attributes" 43 | version = "1.1.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" 46 | dependencies = [ 47 | "quote", 48 | "syn", 49 | ] 50 | 51 | [[package]] 52 | name = "async-channel" 53 | version = "1.6.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" 56 | dependencies = [ 57 | "concurrent-queue", 58 | "event-listener", 59 | "futures-core", 60 | ] 61 | 62 | [[package]] 63 | name = "async-executor" 64 | version = "1.4.1" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" 67 | dependencies = [ 68 | "async-task", 69 | "concurrent-queue", 70 | "fastrand", 71 | "futures-lite", 72 | "once_cell", 73 | "slab", 74 | ] 75 | 76 | [[package]] 77 | name = "async-global-executor" 78 | version = "2.0.2" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" 81 | dependencies = [ 82 | "async-channel", 83 | "async-executor", 84 | "async-io", 85 | "async-mutex", 86 | "blocking", 87 | "futures-lite", 88 | "num_cpus", 89 | "once_cell", 90 | ] 91 | 92 | [[package]] 93 | name = "async-io" 94 | version = "1.6.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" 97 | dependencies = [ 98 | "concurrent-queue", 99 | "futures-lite", 100 | "libc", 101 | "log", 102 | "once_cell", 103 | "parking", 104 | "polling", 105 | "slab", 106 | "socket2", 107 | "waker-fn", 108 | "winapi", 109 | ] 110 | 111 | [[package]] 112 | name = "async-lock" 113 | version = "2.4.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" 116 | dependencies = [ 117 | "event-listener", 118 | ] 119 | 120 | [[package]] 121 | name = "async-mutex" 122 | version = "1.4.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" 125 | dependencies = [ 126 | "event-listener", 127 | ] 128 | 129 | [[package]] 130 | name = "async-std" 131 | version = "1.10.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" 134 | dependencies = [ 135 | "async-attributes", 136 | "async-channel", 137 | "async-global-executor", 138 | "async-io", 139 | "async-lock", 140 | "crossbeam-utils", 141 | "futures-channel", 142 | "futures-core", 143 | "futures-io", 144 | "futures-lite", 145 | "gloo-timers", 146 | "kv-log-macro", 147 | "log", 148 | "memchr", 149 | "num_cpus", 150 | "once_cell", 151 | "pin-project-lite", 152 | "pin-utils", 153 | "slab", 154 | "wasm-bindgen-futures", 155 | ] 156 | 157 | [[package]] 158 | name = "async-task" 159 | version = "4.0.3" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" 162 | 163 | [[package]] 164 | name = "atomic-waker" 165 | version = "1.0.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" 168 | 169 | [[package]] 170 | name = "atty" 171 | version = "0.2.14" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 174 | dependencies = [ 175 | "hermit-abi", 176 | "libc", 177 | "winapi", 178 | ] 179 | 180 | [[package]] 181 | name = "autocfg" 182 | version = "1.0.1" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 185 | 186 | [[package]] 187 | name = "base32" 188 | version = "0.4.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" 191 | 192 | [[package]] 193 | name = "beef" 194 | version = "0.5.1" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" 197 | 198 | [[package]] 199 | name = "binread" 200 | version = "2.2.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f" 203 | dependencies = [ 204 | "binread_derive", 205 | "lazy_static", 206 | "rustversion", 207 | ] 208 | 209 | [[package]] 210 | name = "binread_derive" 211 | version = "2.1.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" 214 | dependencies = [ 215 | "either", 216 | "proc-macro2", 217 | "quote", 218 | "syn", 219 | ] 220 | 221 | [[package]] 222 | name = "bit-set" 223 | version = "0.5.2" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" 226 | dependencies = [ 227 | "bit-vec", 228 | ] 229 | 230 | [[package]] 231 | name = "bit-vec" 232 | version = "0.6.3" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 235 | 236 | [[package]] 237 | name = "bitflags" 238 | version = "1.3.2" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 241 | 242 | [[package]] 243 | name = "block-buffer" 244 | version = "0.9.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 247 | dependencies = [ 248 | "generic-array", 249 | ] 250 | 251 | [[package]] 252 | name = "blocking" 253 | version = "1.0.2" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" 256 | dependencies = [ 257 | "async-channel", 258 | "async-task", 259 | "atomic-waker", 260 | "fastrand", 261 | "futures-lite", 262 | "once_cell", 263 | ] 264 | 265 | [[package]] 266 | name = "bumpalo" 267 | version = "3.7.1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" 270 | 271 | [[package]] 272 | name = "byteorder" 273 | version = "1.4.3" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 276 | 277 | [[package]] 278 | name = "cache-padded" 279 | version = "1.1.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" 282 | 283 | [[package]] 284 | name = "candid" 285 | version = "0.7.8" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "1d7577605c33073dcafc17a5ed6373aa0cb7005e7d4e4b7cd40ca01cb2385533" 288 | dependencies = [ 289 | "anyhow", 290 | "binread", 291 | "byteorder", 292 | "candid_derive", 293 | "codespan-reporting", 294 | "hex", 295 | "ic-types", 296 | "lalrpop", 297 | "lalrpop-util", 298 | "leb128", 299 | "logos", 300 | "num-bigint", 301 | "num-traits", 302 | "num_enum", 303 | "paste", 304 | "pretty", 305 | "serde", 306 | "serde_bytes", 307 | "thiserror", 308 | ] 309 | 310 | [[package]] 311 | name = "candid_derive" 312 | version = "0.4.5" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "2e02c03c4d547674a3f3f3109538fb49871fbe636216daa019f06a62faca9061" 315 | dependencies = [ 316 | "lazy_static", 317 | "proc-macro2", 318 | "quote", 319 | "syn", 320 | ] 321 | 322 | [[package]] 323 | name = "cc" 324 | version = "1.0.71" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" 327 | 328 | [[package]] 329 | name = "cfg-if" 330 | version = "1.0.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 333 | 334 | [[package]] 335 | name = "codespan-reporting" 336 | version = "0.11.1" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 339 | dependencies = [ 340 | "termcolor", 341 | "unicode-width", 342 | ] 343 | 344 | [[package]] 345 | name = "concurrent-queue" 346 | version = "1.2.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" 349 | dependencies = [ 350 | "cache-padded", 351 | ] 352 | 353 | [[package]] 354 | name = "cpufeatures" 355 | version = "0.2.1" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 358 | dependencies = [ 359 | "libc", 360 | ] 361 | 362 | [[package]] 363 | name = "crc32fast" 364 | version = "1.2.1" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 367 | dependencies = [ 368 | "cfg-if", 369 | ] 370 | 371 | [[package]] 372 | name = "crossbeam-utils" 373 | version = "0.8.5" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" 376 | dependencies = [ 377 | "cfg-if", 378 | "lazy_static", 379 | ] 380 | 381 | [[package]] 382 | name = "crunchy" 383 | version = "0.2.2" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 386 | 387 | [[package]] 388 | name = "ctor" 389 | version = "0.1.21" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" 392 | dependencies = [ 393 | "quote", 394 | "syn", 395 | ] 396 | 397 | [[package]] 398 | name = "derivative" 399 | version = "2.2.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 402 | dependencies = [ 403 | "proc-macro2", 404 | "quote", 405 | "syn", 406 | ] 407 | 408 | [[package]] 409 | name = "diff" 410 | version = "0.1.12" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" 413 | 414 | [[package]] 415 | name = "digest" 416 | version = "0.9.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 419 | dependencies = [ 420 | "generic-array", 421 | ] 422 | 423 | [[package]] 424 | name = "dirs-next" 425 | version = "2.0.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 428 | dependencies = [ 429 | "cfg-if", 430 | "dirs-sys-next", 431 | ] 432 | 433 | [[package]] 434 | name = "dirs-sys-next" 435 | version = "0.1.2" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 438 | dependencies = [ 439 | "libc", 440 | "redox_users", 441 | "winapi", 442 | ] 443 | 444 | [[package]] 445 | name = "either" 446 | version = "1.6.1" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 449 | 450 | [[package]] 451 | name = "ena" 452 | version = "0.14.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" 455 | dependencies = [ 456 | "log", 457 | ] 458 | 459 | [[package]] 460 | name = "event-listener" 461 | version = "2.5.1" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" 464 | 465 | [[package]] 466 | name = "fastrand" 467 | version = "1.5.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" 470 | dependencies = [ 471 | "instant", 472 | ] 473 | 474 | [[package]] 475 | name = "fixedbitset" 476 | version = "0.2.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" 479 | 480 | [[package]] 481 | name = "fnv" 482 | version = "1.0.7" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 485 | 486 | [[package]] 487 | name = "futures" 488 | version = "0.3.17" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" 491 | dependencies = [ 492 | "futures-channel", 493 | "futures-core", 494 | "futures-executor", 495 | "futures-io", 496 | "futures-sink", 497 | "futures-task", 498 | "futures-util", 499 | ] 500 | 501 | [[package]] 502 | name = "futures-channel" 503 | version = "0.3.17" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" 506 | dependencies = [ 507 | "futures-core", 508 | "futures-sink", 509 | ] 510 | 511 | [[package]] 512 | name = "futures-core" 513 | version = "0.3.17" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" 516 | 517 | [[package]] 518 | name = "futures-executor" 519 | version = "0.3.17" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" 522 | dependencies = [ 523 | "futures-core", 524 | "futures-task", 525 | "futures-util", 526 | ] 527 | 528 | [[package]] 529 | name = "futures-io" 530 | version = "0.3.17" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" 533 | 534 | [[package]] 535 | name = "futures-lite" 536 | version = "1.12.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" 539 | dependencies = [ 540 | "fastrand", 541 | "futures-core", 542 | "futures-io", 543 | "memchr", 544 | "parking", 545 | "pin-project-lite", 546 | "waker-fn", 547 | ] 548 | 549 | [[package]] 550 | name = "futures-macro" 551 | version = "0.3.17" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" 554 | dependencies = [ 555 | "autocfg", 556 | "proc-macro-hack", 557 | "proc-macro2", 558 | "quote", 559 | "syn", 560 | ] 561 | 562 | [[package]] 563 | name = "futures-sink" 564 | version = "0.3.17" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" 567 | 568 | [[package]] 569 | name = "futures-task" 570 | version = "0.3.17" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" 573 | 574 | [[package]] 575 | name = "futures-util" 576 | version = "0.3.17" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" 579 | dependencies = [ 580 | "autocfg", 581 | "futures-channel", 582 | "futures-core", 583 | "futures-io", 584 | "futures-macro", 585 | "futures-sink", 586 | "futures-task", 587 | "memchr", 588 | "pin-project-lite", 589 | "pin-utils", 590 | "proc-macro-hack", 591 | "proc-macro-nested", 592 | "slab", 593 | ] 594 | 595 | [[package]] 596 | name = "generic-array" 597 | version = "0.14.4" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 600 | dependencies = [ 601 | "typenum", 602 | "version_check", 603 | ] 604 | 605 | [[package]] 606 | name = "getrandom" 607 | version = "0.2.3" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 610 | dependencies = [ 611 | "cfg-if", 612 | "libc", 613 | "wasi", 614 | ] 615 | 616 | [[package]] 617 | name = "gloo-timers" 618 | version = "0.2.1" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" 621 | dependencies = [ 622 | "futures-channel", 623 | "futures-core", 624 | "js-sys", 625 | "wasm-bindgen", 626 | "web-sys", 627 | ] 628 | 629 | [[package]] 630 | name = "hashbrown" 631 | version = "0.11.2" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 634 | 635 | [[package]] 636 | name = "hermit-abi" 637 | version = "0.1.19" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 640 | dependencies = [ 641 | "libc", 642 | ] 643 | 644 | [[package]] 645 | name = "hex" 646 | version = "0.4.3" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 649 | 650 | [[package]] 651 | name = "ic-cdk" 652 | version = "0.3.2" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "0584eb389b9182ee3be53a44f0123d478426a7ef4fcc2d98c2e3716f654e1c92" 655 | dependencies = [ 656 | "candid", 657 | "cfg-if", 658 | "serde", 659 | ] 660 | 661 | [[package]] 662 | name = "ic-cdk-macros" 663 | version = "0.3.2" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "0af2789a609a78e5e18a01792c9ee1237d45b4a86824ad7ccde80edf279ae230" 666 | dependencies = [ 667 | "candid", 668 | "ic-cdk", 669 | "proc-macro2", 670 | "quote", 671 | "serde", 672 | "serde_tokenstream", 673 | "syn", 674 | ] 675 | 676 | [[package]] 677 | name = "ic-kit" 678 | version = "0.4.3" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "2a0d4a05326865a582add1d6bcdec2b5d2af0415a12f6ef2431f3e93e52c347a" 681 | dependencies = [ 682 | "async-std", 683 | "futures", 684 | "ic-cdk", 685 | "ic-cdk-macros", 686 | "serde", 687 | "serde_bytes", 688 | ] 689 | 690 | [[package]] 691 | name = "ic-types" 692 | version = "0.2.2" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "b2c021c11ae1d716f45d783f5764f418a11f12aea1fdc4fc8a2b2242e0dae708" 695 | dependencies = [ 696 | "base32", 697 | "crc32fast", 698 | "hex", 699 | "serde", 700 | "serde_bytes", 701 | "sha2", 702 | "thiserror", 703 | ] 704 | 705 | [[package]] 706 | name = "indexmap" 707 | version = "1.7.0" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 710 | dependencies = [ 711 | "autocfg", 712 | "hashbrown", 713 | ] 714 | 715 | [[package]] 716 | name = "instant" 717 | version = "0.1.12" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 720 | dependencies = [ 721 | "cfg-if", 722 | ] 723 | 724 | [[package]] 725 | name = "itertools" 726 | version = "0.10.1" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" 729 | dependencies = [ 730 | "either", 731 | ] 732 | 733 | [[package]] 734 | name = "js-sys" 735 | version = "0.3.55" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 738 | dependencies = [ 739 | "wasm-bindgen", 740 | ] 741 | 742 | [[package]] 743 | name = "kv-log-macro" 744 | version = "1.0.7" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 747 | dependencies = [ 748 | "log", 749 | ] 750 | 751 | [[package]] 752 | name = "lalrpop" 753 | version = "0.19.6" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988" 756 | dependencies = [ 757 | "ascii-canvas", 758 | "atty", 759 | "bit-set", 760 | "diff", 761 | "ena", 762 | "itertools", 763 | "lalrpop-util", 764 | "petgraph", 765 | "pico-args", 766 | "regex", 767 | "regex-syntax", 768 | "string_cache", 769 | "term", 770 | "tiny-keccak", 771 | "unicode-xid", 772 | ] 773 | 774 | [[package]] 775 | name = "lalrpop-util" 776 | version = "0.19.6" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278" 779 | dependencies = [ 780 | "regex", 781 | ] 782 | 783 | [[package]] 784 | name = "lazy_static" 785 | version = "1.4.0" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 788 | 789 | [[package]] 790 | name = "leb128" 791 | version = "0.2.4" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" 794 | 795 | [[package]] 796 | name = "libc" 797 | version = "0.2.101" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" 800 | 801 | [[package]] 802 | name = "log" 803 | version = "0.4.14" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 806 | dependencies = [ 807 | "cfg-if", 808 | "value-bag", 809 | ] 810 | 811 | [[package]] 812 | name = "logos" 813 | version = "0.12.0" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "427e2abca5be13136da9afdbf874e6b34ad9001dd70f2b103b083a85daa7b345" 816 | dependencies = [ 817 | "logos-derive", 818 | ] 819 | 820 | [[package]] 821 | name = "logos-derive" 822 | version = "0.12.0" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d" 825 | dependencies = [ 826 | "beef", 827 | "fnv", 828 | "proc-macro2", 829 | "quote", 830 | "regex-syntax", 831 | "syn", 832 | "utf8-ranges", 833 | ] 834 | 835 | [[package]] 836 | name = "memchr" 837 | version = "2.4.1" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 840 | 841 | [[package]] 842 | name = "new_debug_unreachable" 843 | version = "1.0.4" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 846 | 847 | [[package]] 848 | name = "num-bigint" 849 | version = "0.4.2" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535" 852 | dependencies = [ 853 | "autocfg", 854 | "num-integer", 855 | "num-traits", 856 | ] 857 | 858 | [[package]] 859 | name = "num-integer" 860 | version = "0.1.44" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 863 | dependencies = [ 864 | "autocfg", 865 | "num-traits", 866 | ] 867 | 868 | [[package]] 869 | name = "num-traits" 870 | version = "0.2.14" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 873 | dependencies = [ 874 | "autocfg", 875 | ] 876 | 877 | [[package]] 878 | name = "num_cpus" 879 | version = "1.13.0" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 882 | dependencies = [ 883 | "hermit-abi", 884 | "libc", 885 | ] 886 | 887 | [[package]] 888 | name = "num_enum" 889 | version = "0.5.4" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" 892 | dependencies = [ 893 | "derivative", 894 | "num_enum_derive", 895 | ] 896 | 897 | [[package]] 898 | name = "num_enum_derive" 899 | version = "0.5.4" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" 902 | dependencies = [ 903 | "proc-macro-crate", 904 | "proc-macro2", 905 | "quote", 906 | "syn", 907 | ] 908 | 909 | [[package]] 910 | name = "once_cell" 911 | version = "1.8.0" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 914 | 915 | [[package]] 916 | name = "opaque-debug" 917 | version = "0.3.0" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 920 | 921 | [[package]] 922 | name = "parking" 923 | version = "2.0.0" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" 926 | 927 | [[package]] 928 | name = "paste" 929 | version = "1.0.5" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" 932 | 933 | [[package]] 934 | name = "petgraph" 935 | version = "0.5.1" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" 938 | dependencies = [ 939 | "fixedbitset", 940 | "indexmap", 941 | ] 942 | 943 | [[package]] 944 | name = "phf_shared" 945 | version = "0.8.0" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 948 | dependencies = [ 949 | "siphasher", 950 | ] 951 | 952 | [[package]] 953 | name = "pico-args" 954 | version = "0.4.2" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" 957 | 958 | [[package]] 959 | name = "pin-project-lite" 960 | version = "0.2.7" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" 963 | 964 | [[package]] 965 | name = "pin-utils" 966 | version = "0.1.0" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 969 | 970 | [[package]] 971 | name = "polling" 972 | version = "2.1.0" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "92341d779fa34ea8437ef4d82d440d5e1ce3f3ff7f824aa64424cd481f9a1f25" 975 | dependencies = [ 976 | "cfg-if", 977 | "libc", 978 | "log", 979 | "wepoll-ffi", 980 | "winapi", 981 | ] 982 | 983 | [[package]] 984 | name = "precomputed-hash" 985 | version = "0.1.1" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 988 | 989 | [[package]] 990 | name = "pretty" 991 | version = "0.10.0" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "ad9940b913ee56ddd94aec2d3cd179dd47068236f42a1a6415ccf9d880ce2a61" 994 | dependencies = [ 995 | "arrayvec", 996 | "typed-arena", 997 | ] 998 | 999 | [[package]] 1000 | name = "proc-macro-crate" 1001 | version = "1.0.0" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" 1004 | dependencies = [ 1005 | "thiserror", 1006 | "toml", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "proc-macro-hack" 1011 | version = "0.5.19" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 1014 | 1015 | [[package]] 1016 | name = "proc-macro-nested" 1017 | version = "0.1.7" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" 1020 | 1021 | [[package]] 1022 | name = "proc-macro2" 1023 | version = "1.0.29" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 1026 | dependencies = [ 1027 | "unicode-xid", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "quote" 1032 | version = "1.0.9" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 1035 | dependencies = [ 1036 | "proc-macro2", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "redox_syscall" 1041 | version = "0.2.10" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 1044 | dependencies = [ 1045 | "bitflags", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "redox_users" 1050 | version = "0.4.0" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" 1053 | dependencies = [ 1054 | "getrandom", 1055 | "redox_syscall", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "regex" 1060 | version = "1.5.4" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 1063 | dependencies = [ 1064 | "aho-corasick", 1065 | "memchr", 1066 | "regex-syntax", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "regex-syntax" 1071 | version = "0.6.25" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 1074 | 1075 | [[package]] 1076 | name = "rustversion" 1077 | version = "1.0.5" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" 1080 | 1081 | [[package]] 1082 | name = "serde" 1083 | version = "1.0.130" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 1086 | dependencies = [ 1087 | "serde_derive", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "serde_bytes" 1092 | version = "0.11.5" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" 1095 | dependencies = [ 1096 | "serde", 1097 | ] 1098 | 1099 | [[package]] 1100 | name = "serde_derive" 1101 | version = "1.0.130" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 1104 | dependencies = [ 1105 | "proc-macro2", 1106 | "quote", 1107 | "syn", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "serde_tokenstream" 1112 | version = "0.1.2" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "0c3ce95257fba42a656f558db28d56a9fac5aa6e4f29c5ef607f32f524fab0ab" 1115 | dependencies = [ 1116 | "proc-macro2", 1117 | "serde", 1118 | "syn", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "sha2" 1123 | version = "0.9.8" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" 1126 | dependencies = [ 1127 | "block-buffer", 1128 | "cfg-if", 1129 | "cpufeatures", 1130 | "digest", 1131 | "opaque-debug", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "siphasher" 1136 | version = "0.3.7" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" 1139 | 1140 | [[package]] 1141 | name = "slab" 1142 | version = "0.4.5" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 1145 | 1146 | [[package]] 1147 | name = "socket2" 1148 | version = "0.4.2" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" 1151 | dependencies = [ 1152 | "libc", 1153 | "winapi", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "string_cache" 1158 | version = "0.8.1" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" 1161 | dependencies = [ 1162 | "lazy_static", 1163 | "new_debug_unreachable", 1164 | "phf_shared", 1165 | "precomputed-hash", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "syn" 1170 | version = "1.0.76" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" 1173 | dependencies = [ 1174 | "proc-macro2", 1175 | "quote", 1176 | "unicode-xid", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "term" 1181 | version = "0.7.0" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 1184 | dependencies = [ 1185 | "dirs-next", 1186 | "rustversion", 1187 | "winapi", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "termcolor" 1192 | version = "1.1.2" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 1195 | dependencies = [ 1196 | "winapi-util", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "thiserror" 1201 | version = "1.0.29" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" 1204 | dependencies = [ 1205 | "thiserror-impl", 1206 | ] 1207 | 1208 | [[package]] 1209 | name = "thiserror-impl" 1210 | version = "1.0.29" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" 1213 | dependencies = [ 1214 | "proc-macro2", 1215 | "quote", 1216 | "syn", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "tiny-keccak" 1221 | version = "2.0.2" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 1224 | dependencies = [ 1225 | "crunchy", 1226 | ] 1227 | 1228 | [[package]] 1229 | name = "token" 1230 | version = "0.1.0" 1231 | dependencies = [ 1232 | "assert-panic", 1233 | "async-std", 1234 | "candid", 1235 | "ic-cdk", 1236 | "ic-cdk-macros", 1237 | "ic-kit", 1238 | "serde", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "toml" 1243 | version = "0.5.8" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 1246 | dependencies = [ 1247 | "serde", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "typed-arena" 1252 | version = "2.0.1" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" 1255 | 1256 | [[package]] 1257 | name = "typenum" 1258 | version = "1.14.0" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 1261 | 1262 | [[package]] 1263 | name = "unicode-width" 1264 | version = "0.1.8" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 1267 | 1268 | [[package]] 1269 | name = "unicode-xid" 1270 | version = "0.2.2" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1273 | 1274 | [[package]] 1275 | name = "utf8-ranges" 1276 | version = "1.0.4" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" 1279 | 1280 | [[package]] 1281 | name = "value-bag" 1282 | version = "1.0.0-alpha.7" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "dd320e1520f94261153e96f7534476ad869c14022aee1e59af7c778075d840ae" 1285 | dependencies = [ 1286 | "ctor", 1287 | "version_check", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "version_check" 1292 | version = "0.9.3" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1295 | 1296 | [[package]] 1297 | name = "waker-fn" 1298 | version = "1.1.0" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" 1301 | 1302 | [[package]] 1303 | name = "wasi" 1304 | version = "0.10.2+wasi-snapshot-preview1" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1307 | 1308 | [[package]] 1309 | name = "wasm-bindgen" 1310 | version = "0.2.78" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 1313 | dependencies = [ 1314 | "cfg-if", 1315 | "wasm-bindgen-macro", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "wasm-bindgen-backend" 1320 | version = "0.2.78" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 1323 | dependencies = [ 1324 | "bumpalo", 1325 | "lazy_static", 1326 | "log", 1327 | "proc-macro2", 1328 | "quote", 1329 | "syn", 1330 | "wasm-bindgen-shared", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "wasm-bindgen-futures" 1335 | version = "0.4.28" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" 1338 | dependencies = [ 1339 | "cfg-if", 1340 | "js-sys", 1341 | "wasm-bindgen", 1342 | "web-sys", 1343 | ] 1344 | 1345 | [[package]] 1346 | name = "wasm-bindgen-macro" 1347 | version = "0.2.78" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 1350 | dependencies = [ 1351 | "quote", 1352 | "wasm-bindgen-macro-support", 1353 | ] 1354 | 1355 | [[package]] 1356 | name = "wasm-bindgen-macro-support" 1357 | version = "0.2.78" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 1360 | dependencies = [ 1361 | "proc-macro2", 1362 | "quote", 1363 | "syn", 1364 | "wasm-bindgen-backend", 1365 | "wasm-bindgen-shared", 1366 | ] 1367 | 1368 | [[package]] 1369 | name = "wasm-bindgen-shared" 1370 | version = "0.2.78" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 1373 | 1374 | [[package]] 1375 | name = "web-sys" 1376 | version = "0.3.55" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" 1379 | dependencies = [ 1380 | "js-sys", 1381 | "wasm-bindgen", 1382 | ] 1383 | 1384 | [[package]] 1385 | name = "wepoll-ffi" 1386 | version = "0.1.2" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" 1389 | dependencies = [ 1390 | "cc", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "winapi" 1395 | version = "0.3.9" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1398 | dependencies = [ 1399 | "winapi-i686-pc-windows-gnu", 1400 | "winapi-x86_64-pc-windows-gnu", 1401 | ] 1402 | 1403 | [[package]] 1404 | name = "winapi-i686-pc-windows-gnu" 1405 | version = "0.4.0" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1408 | 1409 | [[package]] 1410 | name = "winapi-util" 1411 | version = "0.1.5" 1412 | source = "registry+https://github.com/rust-lang/crates.io-index" 1413 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1414 | dependencies = [ 1415 | "winapi", 1416 | ] 1417 | 1418 | [[package]] 1419 | name = "winapi-x86_64-pc-windows-gnu" 1420 | version = "0.4.0" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1423 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "token" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | ic-cdk-macros = "0.3" 10 | candid = "0.7.4" 11 | serde = "1.0" 12 | ic-kit = "0.4.3" 13 | ic-cdk = "0.3.1" 14 | assert-panic = "1.0.1" 15 | 16 | [target.'cfg(not(target_family = "wasm"))'.dependencies] 17 | async-std = { version="1.10.0", features = ["attributes"] } -------------------------------------------------------------------------------- /rust/deploy.sh: -------------------------------------------------------------------------------- 1 | sudo dfx canister --no-wallet create --all 2 | cargo run > token.did 3 | ic-cdk-optimizer target/wasm32-unknown-unknown/release/token.wasm -o target/wasm32-unknown-unknown/release/opt.wasm 4 | sudo dfx build token 5 | OWNER="principal \"$( \ 6 | dfx identity get-principal 7 | )\"" 8 | sudo dfx canister --no-wallet install token --argument "(\"test logo\", \"test token\", \"TT\", 8:nat8, 100000000:nat64, $OWNER, 0)" -m=reinstall 9 | -------------------------------------------------------------------------------- /rust/dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "token": { 4 | "build": "cargo build --target wasm32-unknown-unknown --release", 5 | "candid": "token.did", 6 | "wasm": "target/wasm32-unknown-unknown/release/opt.wasm", 7 | "type": "custom" 8 | } 9 | }, 10 | "defaults": { 11 | "build": { 12 | "packtool": "" 13 | } 14 | }, 15 | "networks": { 16 | "local": { 17 | "bind": "127.0.0.1:8000", 18 | "type": "ephemeral" 19 | } 20 | }, 21 | "version": 1 22 | } 23 | -------------------------------------------------------------------------------- /rust/rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly -------------------------------------------------------------------------------- /rust/src/main.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Module : main.rs 3 | * Copyright : 2021 Rocklabs 4 | * License : Apache 2.0 with LLVM Exception 5 | * Maintainer : Rocklabs 6 | * Stability : Experimental 7 | */ 8 | use candid::{candid_method, CandidType, Deserialize, types::number::Nat}; 9 | use ic_kit::{ic , Principal}; 10 | use ic_cdk_macros::*; 11 | use std::collections::HashMap; 12 | use std::iter::FromIterator; 13 | use std::string::String; 14 | 15 | #[derive(Deserialize, CandidType, Clone, Debug)] 16 | struct Metadata { 17 | logo: String, 18 | name: String, 19 | symbol: String, 20 | decimals: u8, 21 | total_supply: Nat, 22 | owner: Principal, 23 | fee: Nat, 24 | fee_to: Principal, 25 | } 26 | 27 | #[derive(Deserialize, CandidType, Clone, Debug)] 28 | struct TokenInfo { 29 | metadata: Metadata, 30 | fee_to: Principal, 31 | // status info 32 | history_size: usize, 33 | deploy_time: u64, 34 | holder_number: usize, 35 | cycles: u64, 36 | } 37 | 38 | impl Default for Metadata { 39 | fn default() -> Self { 40 | Metadata { 41 | logo: "".to_string(), 42 | name: "".to_string(), 43 | symbol: "".to_string(), 44 | decimals: 0u8, 45 | total_supply: Nat::from(0), 46 | owner: Principal::anonymous(), 47 | fee: Nat::from(0), 48 | fee_to: Principal::anonymous(), 49 | } 50 | } 51 | } 52 | 53 | type Balances = HashMap; 54 | type Allowances = HashMap>; 55 | type Ops = Vec; 56 | 57 | #[derive(Deserialize, CandidType)] 58 | struct UpgradePayload { 59 | metadata: Metadata, 60 | balance: Vec<(Principal, Nat)>, 61 | allow: Vec<(Principal, Vec<(Principal, Nat)>)>, 62 | } 63 | 64 | #[derive(CandidType, Clone, Copy, Debug, PartialEq)] 65 | enum Operation { 66 | Mint, 67 | Burn, 68 | Transfer, 69 | TransferFrom, 70 | Approve, 71 | } 72 | 73 | #[derive(CandidType, Clone, Copy, Debug, PartialEq)] 74 | enum TransactionStatus { 75 | Succeeded, 76 | Inprogress, 77 | Failed, 78 | } 79 | 80 | #[derive(CandidType, Clone, Debug)] 81 | struct OpRecord { 82 | caller: Option, 83 | op: Operation, 84 | index: usize, 85 | from: Principal, 86 | to: Principal, 87 | amount: Nat, 88 | fee: Nat, 89 | timestamp: u64, 90 | status: TransactionStatus, 91 | } 92 | 93 | #[derive(CandidType, Debug, PartialEq)] 94 | enum TxError { 95 | InsufficientBalance, 96 | InsufficientAllowance, 97 | Unauthorized, 98 | } 99 | type TxReceipt = Result; 100 | 101 | fn add_record( 102 | caller: Option, 103 | op: Operation, 104 | from: Principal, 105 | to: Principal, 106 | amount: Nat, 107 | fee: Nat, 108 | timestamp: u64, 109 | status: TransactionStatus, 110 | ) -> usize { 111 | let ops = ic::get_mut::(); 112 | let index = ops.len(); 113 | ops.push(OpRecord { 114 | caller, 115 | op, 116 | index, 117 | from, 118 | to, 119 | amount, 120 | fee, 121 | timestamp, 122 | status, 123 | }); 124 | index 125 | } 126 | 127 | #[init] 128 | #[candid_method(init)] 129 | fn init( 130 | logo: String, 131 | name: String, 132 | symbol: String, 133 | decimals: u8, 134 | total_supply: Nat, 135 | owner: Principal, 136 | fee: Nat, 137 | ) { 138 | let metadata = ic::get_mut::(); 139 | metadata.logo = logo; 140 | metadata.name = name; 141 | metadata.symbol = symbol; 142 | metadata.decimals = decimals; 143 | metadata.total_supply = total_supply.clone(); 144 | metadata.owner = owner; 145 | metadata.fee = fee; 146 | let balances = ic::get_mut::(); 147 | balances.insert(owner, total_supply.clone()); 148 | let _ = add_record( 149 | Some(owner), 150 | Operation::Mint, 151 | Principal::from_text("aaaaa-aa").unwrap(), 152 | owner, 153 | total_supply, 154 | Nat::from(0), 155 | ic::time(), 156 | TransactionStatus::Succeeded, 157 | ); 158 | } 159 | 160 | fn _transfer(from: Principal, to: Principal, value: Nat) { 161 | let balances = ic::get_mut::(); 162 | let from_balance = balance_of(from); 163 | let from_balance_new = from_balance - value.clone(); 164 | if from_balance_new != 0 { 165 | balances.insert(from, from_balance_new); 166 | } else { 167 | balances.remove(&from); 168 | } 169 | let to_balance = balance_of(to); 170 | let to_balance_new = to_balance + value; 171 | if to_balance_new != 0 { 172 | balances.insert(to, to_balance_new); 173 | } 174 | } 175 | 176 | fn _charge_fee(user: Principal, fee_to: Principal, fee: Nat) { 177 | let metadata = ic::get::(); 178 | if metadata.fee > Nat::from(0) { 179 | _transfer(user, fee_to, fee); 180 | } 181 | } 182 | 183 | #[update(name = "transfer")] 184 | #[candid_method(update)] 185 | fn transfer(to: Principal, value: Nat) -> TxReceipt { 186 | let from = ic::caller(); 187 | let metadata = ic::get::(); 188 | if balance_of(from) < value.clone() + metadata.fee.clone() { 189 | return Err(TxError::InsufficientBalance); 190 | } 191 | _charge_fee(from, metadata.fee_to, metadata.fee.clone()); 192 | _transfer(from, to, value.clone()); 193 | let txid = add_record( 194 | None, 195 | Operation::Transfer, 196 | from, 197 | to, 198 | value, 199 | metadata.fee.clone(), 200 | ic::time(), 201 | TransactionStatus::Succeeded, 202 | ); 203 | Ok(txid) 204 | } 205 | 206 | #[update(name = "transferFrom")] 207 | #[candid_method(update, rename = "transferFrom")] 208 | fn transfer_from(from: Principal, to: Principal, value: Nat) -> TxReceipt { 209 | let owner = ic::caller(); 210 | let from_allowance = allowance(from, owner); 211 | let metadata = ic::get::(); 212 | if from_allowance < value.clone() + metadata.fee.clone() { 213 | return Err(TxError::InsufficientAllowance); 214 | } 215 | let from_balance = balance_of(from); 216 | if from_balance < value.clone() + metadata.fee.clone() { 217 | return Err(TxError::InsufficientBalance); 218 | } 219 | _charge_fee(from, metadata.fee_to, metadata.fee.clone()); 220 | _transfer(from, to, value.clone()); 221 | let allowances = ic::get_mut::(); 222 | match allowances.get(&from) { 223 | Some(inner) => { 224 | let result = inner.get(&owner).unwrap().clone(); 225 | let mut temp = inner.clone(); 226 | if result.clone() - value.clone() - metadata.fee.clone() != 0 { 227 | temp.insert(owner, result - value.clone() - metadata.fee.clone()); 228 | allowances.insert(from, temp); 229 | } else { 230 | temp.remove(&owner); 231 | if temp.len() == 0 { 232 | allowances.remove(&from); 233 | } else { 234 | allowances.insert(from, temp); 235 | } 236 | } 237 | } 238 | None => { 239 | assert!(false); 240 | } 241 | } 242 | let txid = add_record( 243 | Some(owner), 244 | Operation::TransferFrom, 245 | from, 246 | to, 247 | value, 248 | metadata.fee.clone(), 249 | ic::time(), 250 | TransactionStatus::Succeeded, 251 | ); 252 | Ok(txid) 253 | } 254 | 255 | #[update(name = "approve")] 256 | #[candid_method(update)] 257 | fn approve(spender: Principal, value: Nat) -> TxReceipt { 258 | let owner = ic::caller(); 259 | let metadata = ic::get::(); 260 | if balance_of(owner) < metadata.fee.clone() { 261 | return Err(TxError::InsufficientBalance); 262 | } 263 | _charge_fee(owner, metadata.fee_to, metadata.fee.clone()); 264 | let v = value.clone() + metadata.fee.clone(); 265 | let allowances = ic::get_mut::(); 266 | match allowances.get(&owner) { 267 | Some(inner) => { 268 | let mut temp = inner.clone(); 269 | if v != 0 { 270 | temp.insert(spender, v.clone()); 271 | allowances.insert(owner, temp); 272 | } else { 273 | temp.remove(&spender); 274 | if temp.len() == 0 { 275 | allowances.remove(&owner); 276 | } else { 277 | allowances.insert(owner, temp); 278 | } 279 | } 280 | } 281 | None => { 282 | if v != 0 { 283 | let mut inner = HashMap::new(); 284 | inner.insert(spender, v.clone()); 285 | let allowances = ic::get_mut::(); 286 | allowances.insert(owner, inner); 287 | } 288 | } 289 | } 290 | let txid = add_record( 291 | None, 292 | Operation::Approve, 293 | owner, 294 | spender, 295 | v, 296 | metadata.fee.clone(), 297 | ic::time(), 298 | TransactionStatus::Succeeded, 299 | ); 300 | Ok(txid) 301 | } 302 | 303 | #[update(name = "mint")] 304 | #[candid_method(update, rename = "mint")] 305 | fn mint(to: Principal, amount: Nat) -> TxReceipt { 306 | let caller = ic::caller(); 307 | let metadata = ic::get_mut::(); 308 | if caller != metadata.owner { 309 | return Err(TxError::Unauthorized); 310 | } 311 | let to_balance = balance_of(to); 312 | let balances = ic::get_mut::(); 313 | balances.insert(to, to_balance + amount.clone()); 314 | metadata.total_supply += amount.clone(); 315 | 316 | let txid = add_record( 317 | Some(caller), 318 | Operation::Mint, 319 | Principal::from_text("aaaaa-aa").unwrap(), 320 | to, 321 | amount, 322 | Nat::from(0), 323 | ic::time(), 324 | TransactionStatus::Succeeded, 325 | ); 326 | Ok(txid) 327 | } 328 | 329 | #[update(name = "burn")] 330 | #[candid_method(update, rename = "burn")] 331 | fn burn(amount: Nat) -> TxReceipt { 332 | let caller = ic::caller(); 333 | let metadata = ic::get_mut::(); 334 | let caller_balance = balance_of(caller); 335 | if caller_balance < amount.clone() { 336 | return Err(TxError::InsufficientBalance); 337 | } 338 | let balances = ic::get_mut::(); 339 | balances.insert(caller, caller_balance - amount.clone()); 340 | metadata.total_supply -= amount.clone(); 341 | let txid = add_record( 342 | Some(caller), 343 | Operation::Burn, 344 | caller, 345 | Principal::from_text("aaaaa-aa").unwrap(), 346 | amount, 347 | Nat::from(0), 348 | ic::time(), 349 | TransactionStatus::Succeeded, 350 | ); 351 | Ok(txid) 352 | } 353 | 354 | #[update(name = "setLogo")] 355 | #[candid_method(update, rename = "setLogo")] 356 | fn set_logo(logo: String) { 357 | let metadata = ic::get_mut::(); 358 | assert_eq!(ic::caller(), metadata.owner); 359 | metadata.logo = logo; 360 | } 361 | 362 | #[update(name = "setFee")] 363 | #[candid_method(update, rename = "setFee")] 364 | fn set_fee(fee: Nat) { 365 | let metadata = ic::get_mut::(); 366 | assert_eq!(ic::caller(), metadata.owner); 367 | metadata.fee = fee; 368 | } 369 | 370 | #[update(name = "setFeeTo")] 371 | #[candid_method(update, rename = "setFeeTo")] 372 | fn set_fee_to(fee_to: Principal) { 373 | let metadata = ic::get_mut::(); 374 | assert_eq!(ic::caller(), metadata.owner); 375 | metadata.fee_to = fee_to; 376 | } 377 | 378 | #[update(name = "setOwner")] 379 | #[candid_method(update, rename = "setOwner")] 380 | fn set_owner(owner: Principal) { 381 | let metadata = ic::get_mut::(); 382 | assert_eq!(ic::caller(), metadata.owner); 383 | metadata.owner = owner; 384 | } 385 | 386 | #[query(name = "balanceOf")] 387 | #[candid_method(query, rename = "balanceOf")] 388 | fn balance_of(id: Principal) -> Nat { 389 | let balances = ic::get::(); 390 | match balances.get(&id) { 391 | Some(balance) => balance.clone(), 392 | None => Nat::from(0), 393 | } 394 | } 395 | 396 | #[query(name = "allowance")] 397 | #[candid_method(query)] 398 | fn allowance(owner: Principal, spender: Principal) -> Nat { 399 | let allowances = ic::get::(); 400 | match allowances.get(&owner) { 401 | Some(inner) => match inner.get(&spender) { 402 | Some(value) => value.clone(), 403 | None => Nat::from(0), 404 | }, 405 | None => Nat::from(0), 406 | } 407 | } 408 | 409 | #[query(name = "getLogo")] 410 | #[candid_method(query, rename = "getLogo")] 411 | fn get_logo() -> String { 412 | let metadata = ic::get::(); 413 | metadata.logo.clone() 414 | } 415 | 416 | #[query(name = "name")] 417 | #[candid_method(query)] 418 | fn name() -> String { 419 | let metadata = ic::get::(); 420 | metadata.name.clone() 421 | } 422 | 423 | #[query(name = "symbol")] 424 | #[candid_method(query)] 425 | fn symbol() -> String { 426 | let metadata = ic::get::(); 427 | metadata.symbol.clone() 428 | } 429 | 430 | #[query(name = "decimals")] 431 | #[candid_method(query)] 432 | fn decimals() -> u8 { 433 | let metadata = ic::get::(); 434 | metadata.decimals 435 | } 436 | 437 | #[query(name = "totalSupply")] 438 | #[candid_method(query, rename = "totalSupply")] 439 | fn total_supply() -> Nat { 440 | let metadata = ic::get::(); 441 | metadata.total_supply.clone() 442 | } 443 | 444 | #[query(name = "owner")] 445 | #[candid_method(query)] 446 | fn owner() -> Principal { 447 | let metadata = ic::get::(); 448 | metadata.owner 449 | } 450 | 451 | #[query(name = "getMetadta")] 452 | #[candid_method(query, rename = "getMetadta")] 453 | fn get_metadata() -> Metadata { 454 | ic::get::().clone() 455 | } 456 | 457 | #[query(name = "historySize")] 458 | #[candid_method(query, rename = "historySize")] 459 | fn history_size() -> usize { 460 | let ops = ic::get::(); 461 | ops.len() 462 | } 463 | 464 | #[query(name = "getTransaction")] 465 | #[candid_method(query, rename = "getTransaction")] 466 | fn get_transaction(index: usize) -> OpRecord { 467 | let ops = ic::get::(); 468 | ops[index].clone() 469 | } 470 | 471 | #[query(name = "getTransactions")] 472 | #[candid_method(query, rename = "getTransactions")] 473 | fn get_transactions(start: usize, limit: usize) -> Vec { 474 | let mut ret: Vec = Vec::new(); 475 | let ops = ic::get::(); 476 | let mut i = start; 477 | while i < start + limit && i < ops.len() { 478 | ret.push(ops[i].clone()); 479 | i += 1; 480 | } 481 | ret 482 | } 483 | 484 | #[query(name = "getUserTransactionAmount")] 485 | #[candid_method(query, rename = "getUserTransactionAmount")] 486 | fn get_user_transaction_amount(a: Principal) -> usize { 487 | let mut res = 0; 488 | let ops = ic::get::(); 489 | for i in ops.clone() { 490 | if i.caller == Some(a) || i.from == a || i.to == a { 491 | res += 1; 492 | } 493 | } 494 | res 495 | } 496 | 497 | #[query(name = "getUserTransactions")] 498 | #[candid_method(query, rename = "getUserTransactions")] 499 | fn get_user_transactions(a: Principal, start: usize, limit: usize) -> Vec { 500 | let ops = ic::get::(); 501 | let mut res: Vec = Vec::new(); 502 | let mut index: usize = 0; 503 | for i in ops.clone() { 504 | if i.caller == Some(a) || i.from == a || i.to == a { 505 | if index >= start && index < start + limit { 506 | res.push(i); 507 | } 508 | index += 1; 509 | } 510 | } 511 | res 512 | } 513 | 514 | #[query(name = "getTokenInfo")] 515 | #[candid_method(query, rename = "getTokenInfo")] 516 | fn get_token_info() -> TokenInfo { 517 | let metadata = ic::get::().clone(); 518 | let ops = ic::get::(); 519 | let balance = ic::get::(); 520 | 521 | return TokenInfo { 522 | metadata: metadata.clone(), 523 | fee_to: metadata.fee_to, 524 | history_size: ops.len(), 525 | deploy_time: ops[0].timestamp, 526 | holder_number: balance.len(), 527 | cycles: ic::balance(), 528 | }; 529 | } 530 | 531 | #[query(name = "getHolders")] 532 | #[candid_method(query, rename = "getHolders")] 533 | fn get_holders(start: usize, limit: usize) -> Vec<(Principal, Nat)> { 534 | let mut balance = Vec::new(); 535 | for (k, v) in ic::get::().clone() { 536 | balance.push((k, v.clone())); 537 | } 538 | balance.sort_by(|a, b| b.1.cmp(&a.1)); 539 | let limit: usize = if start + limit > balance.len() { 540 | balance.len() - start 541 | } else { 542 | limit 543 | }; 544 | balance[start..start + limit].to_vec() 545 | } 546 | 547 | #[query(name = "getAllowanceSize")] 548 | #[candid_method(query, rename = "getAllowanceSize")] 549 | fn get_allowance_size() -> usize { 550 | let mut size = 0; 551 | let allowances = ic::get::(); 552 | for (_, v) in allowances.iter() { 553 | size += v.len(); 554 | } 555 | size 556 | } 557 | 558 | #[query(name = "getUserApprovals")] 559 | #[candid_method(query, rename = "getUserApprovals")] 560 | fn get_user_approvals(who: Principal) -> Vec<(Principal, Nat)> { 561 | let allowances = ic::get::(); 562 | match allowances.get(&who) { 563 | Some(allow) => return Vec::from_iter(allow.clone().into_iter()), 564 | None => return Vec::new(), 565 | } 566 | } 567 | 568 | #[cfg(any(target_arch = "wasm32", test))] 569 | fn main() {} 570 | 571 | #[cfg(not(any(target_arch = "wasm32", test)))] 572 | fn main() { 573 | candid::export_service!(); 574 | std::print!("{}", __export_service()); 575 | } 576 | 577 | // TODO: fix upgrade functions 578 | #[pre_upgrade] 579 | fn pre_upgrade() { 580 | let metadata = ic::get::().clone(); 581 | let mut balance = Vec::new(); 582 | // let mut allow: Vec<(Principal, Vec<(Principal, Nat)>)> = Vec::new(); 583 | let mut allow = Vec::new(); 584 | for (k, v) in ic::get::().clone() { 585 | balance.push((k, v)); 586 | } 587 | for (k, v) in ic::get::().iter() { 588 | let mut item = Vec::new(); 589 | for (a, b) in v.clone() { 590 | item.push((a, b)); 591 | } 592 | allow.push((*k, item)); 593 | } 594 | let up = UpgradePayload { 595 | metadata, 596 | balance, 597 | allow, 598 | }; 599 | ic::stable_store((up,)).unwrap(); 600 | } 601 | 602 | #[post_upgrade] 603 | fn post_upgrade() { 604 | // There can only be one value in stable memory, currently. otherwise, lifetime error. 605 | // https://docs.rs/ic-cdk/0.3.0/ic_cdk/storage/fn.stable_restore.html 606 | let (down,): (UpgradePayload,) = ic::stable_restore().unwrap(); 607 | let metadata = ic::get_mut::(); 608 | *metadata = down.metadata; 609 | for (k, v) in down.balance { 610 | ic::get_mut::().insert(k, v); 611 | } 612 | for (k, v) in down.allow { 613 | let mut inner = HashMap::new(); 614 | for (a, b) in v { 615 | inner.insert(a, b); 616 | } 617 | ic::get_mut::().insert(k, inner); 618 | } 619 | } 620 | 621 | #[cfg(test)] 622 | mod tests { 623 | use super::*; 624 | use ic_kit::{mock_principals::{alice, bob, john}, MockContext}; 625 | use assert_panic::assert_panic; 626 | 627 | fn initialize_tests() { 628 | init( 629 | String::from("logo"), 630 | String::from("token"), 631 | String::from("TOKEN"), 632 | 2, 633 | 1_000, 634 | alice(), 635 | 1, 636 | ); 637 | } 638 | 639 | #[test] 640 | fn functionality_test() { 641 | MockContext::new() 642 | .with_balance(100_000) 643 | .with_caller(alice()) 644 | .inject(); 645 | 646 | initialize_tests(); 647 | 648 | // initialization tests 649 | assert_eq!(balance_of(alice()), 1_000, "balanceOf did not return the correct value"); 650 | assert_eq!(total_supply(), 1_000, "totalSupply did not return the correct value"); 651 | assert_eq!(symbol(), String::from("TOKEN"), "symbol did not return the correct value"); 652 | assert_eq!(owner(), alice(), "owner did not return the correct value"); 653 | assert_eq!(name(), String::from("token"), "name did not return the correct value"); 654 | assert_eq!(get_logo(), String::from("logo"), "getLogo did not return the correct value"); 655 | assert_eq!(decimals(), 2, "decimals did not return the correct value"); 656 | assert_eq!(get_holders(0, 10).len(), 1, "get_holders returned the correct amount of holders after initialization"); 657 | assert_eq!(get_transaction(0).op, Operation::Mint, "get_transaction returnded a Mint operation"); 658 | 659 | let token_info = get_token_info(); 660 | assert_eq!(token_info.fee_to, Principal::anonymous(), "tokenInfo.fee_to did not return the correct value"); 661 | assert_eq!(token_info.history_size, 1, "tokenInfo.history_size did not return the correct value"); 662 | assert!(token_info.deploy_time > 0, "tokenInfo.deploy_time did not return the correct value"); 663 | assert_eq!(token_info.holder_number, 1, "tokenInfo.holder_number did not return the correct value"); 664 | assert_eq!(token_info.cycles, 100_000, "tokenInfo.cycles did not return the correct value"); 665 | 666 | let metadata = get_metadata(); 667 | assert_eq!(metadata.total_supply, 1_000, "metadata.total_supply did not return the correct value"); 668 | assert_eq!(metadata.symbol, String::from("TOKEN"), "metadata.symbol did not return the correct value"); 669 | // assert_eq!(metadata.owner, alice(), "metadata.owner did not return the correct value"); 670 | assert_eq!(metadata.name, String::from("token"), "metadata.name did not return the correct value"); 671 | assert_eq!(metadata.logo, String::from("logo"), "metadata.logo did not return the correct value"); 672 | assert_eq!(metadata.decimals, 2, "metadata.decimals did not return the correct value"); 673 | assert_eq!(metadata.fee, 1, "metadata.fee did not return the correct value"); 674 | assert_eq!(metadata.fee_to, Principal::anonymous(), "metadata.fee_to did not return the correct value"); 675 | 676 | // set fee test 677 | set_fee(2); 678 | assert_eq!(2, get_metadata().fee ,"Failed to update the fee_to"); 679 | 680 | // set fee_to test 681 | set_fee_to(john()); 682 | assert_eq!(john(), get_metadata().fee_to, "Failed to set fee"); 683 | set_fee_to(Principal::anonymous()); 684 | 685 | // set logo 686 | set_logo(String::from("new_logo")); 687 | assert_eq!("new_logo", get_logo()); 688 | 689 | // test transfers 690 | let transfer_alice_balance_expected = balance_of(alice()) - 10 - get_metadata().fee; 691 | let transfer_bob_balance_expected = balance_of(bob()) + 10; 692 | let transfer_john_balance_expected = balance_of(john()); 693 | let transfer_transaction_amount_expected = get_transactions(0, 10).len() + 1; 694 | let transfer_user_transaction_amount_expected = get_user_transaction_amount(alice()) + 1; 695 | transfer(bob(), 10).map_err(|err| println!("{:?}", err)).ok(); 696 | 697 | assert_eq!(balance_of(alice()), transfer_alice_balance_expected, "Transfer did not transfer the expected amount to Alice"); 698 | assert_eq!(balance_of(bob()), transfer_bob_balance_expected, "Transfer did not transfer the expected amount to Bob"); 699 | assert_eq!(balance_of(john()), transfer_john_balance_expected, "Transfer did not transfer the expected amount to John"); 700 | assert_eq!(get_transactions(0, 10).len(), transfer_transaction_amount_expected, "transfer operation did not produce a transaction"); 701 | assert_eq!(get_user_transaction_amount(alice()), transfer_user_transaction_amount_expected, "get_user_transaction_amount returned the wrong value after a transfer"); 702 | assert_eq!(get_user_transactions(alice(), 0, 10).len(), transfer_user_transaction_amount_expected, "get_user_transactions returned the wrong value after a transfer"); 703 | assert_eq!(get_holders(0, 10).len(), 3, "get_holders returned the correct amount of holders after transfer"); 704 | assert_eq!(get_transaction(1).op, Operation::Transfer, "get_transaction returnded a Transfer operation"); 705 | 706 | // test allowances 707 | approve(bob(), 100).map_err(|err| println!("{:?}", err)).ok(); 708 | assert_eq!(allowance(alice(), bob()), 100 + get_metadata().fee, "Approve did not give the correct allowance"); 709 | assert_eq!(get_allowance_size(), 1, "getAllowanceSize returns the correct value"); 710 | assert_eq!(get_user_approvals(alice()).len(), 1, "getUserApprovals not returning the correct value"); 711 | 712 | // test transfer_from 713 | // inserting an allowance of Alice for Bob's balance to test transfer_from 714 | let allowances = ic::get_mut::(); 715 | let mut inner = HashMap::new(); 716 | inner.insert(alice(), 5 + get_metadata().fee); 717 | allowances.insert(bob(), inner); 718 | 719 | let transfer_from_alice_balance_expected = balance_of(alice()); 720 | let transfer_from_bob_balance_expected = balance_of(bob()) - 5 - get_metadata().fee; 721 | let transfer_from_john_balance_expected = balance_of(john()) + 5; 722 | let transfer_from_transaction_amount_expected = get_transactions(0, 10).len() + 1; 723 | 724 | transfer_from(bob(), john(), 5).map_err(|err| println!("{:?}", err)).ok(); 725 | 726 | assert_eq!(balance_of(alice()), transfer_from_alice_balance_expected, "transfer_from transferred the correct value for alice"); 727 | assert_eq!(balance_of(bob()), transfer_from_bob_balance_expected, "transfer_from transferred the correct value for bob"); 728 | assert_eq!(balance_of(john()), transfer_from_john_balance_expected, "transfer_from transferred the correct value for john"); 729 | assert_eq!(allowance(bob(), alice()), 0, "allowance has not been spent"); 730 | assert_eq!(get_transactions(0, 10).len(), transfer_from_transaction_amount_expected, "transfer_from operation did not produce a transaction"); 731 | 732 | // Transferring more than the balance 733 | assert_eq!(transfer(alice(), 1_000_000), Err(TxError::InsufficientBalance) , "alice was able to transfer more than is allowed"); 734 | // Transferring more than the balance 735 | assert_eq!(transfer_from(bob(), john(), 1_000_000), Err(TxError::InsufficientAllowance) , "alice was able to transfer more than is allowed"); 736 | 737 | //set owner test 738 | set_owner(bob()); 739 | assert_eq!(bob(), owner(), "Failed to set new owner"); 740 | } 741 | 742 | #[test] 743 | fn permission_tests() { 744 | MockContext::new() 745 | .with_balance(100_000) 746 | .with_caller(bob()) 747 | .inject(); 748 | 749 | initialize_tests(); 750 | 751 | assert_panic!(set_logo(String::from("forbidden"))); 752 | assert_panic!(set_fee(123)); 753 | assert_panic!(set_fee_to(john())); 754 | assert_panic!(set_owner(bob())); 755 | } 756 | } 757 | -------------------------------------------------------------------------------- /rust/token.did: -------------------------------------------------------------------------------- 1 | type Metadata = record { 2 | fee : nat; 3 | decimals : nat8; 4 | fee_to : principal; 5 | owner : principal; 6 | logo : text; 7 | name : text; 8 | total_supply : nat; 9 | symbol : text; 10 | }; 11 | type OpRecord = record { 12 | op : Operation; 13 | to : principal; 14 | fee : nat; 15 | status : TransactionStatus; 16 | from : principal; 17 | timestamp : nat64; 18 | caller : opt principal; 19 | index : nat64; 20 | amount : nat; 21 | }; 22 | type Operation = variant { Approve; Burn; Mint; Transfer; TransferFrom }; 23 | type Result = variant { Ok : nat64; Err : TxError }; 24 | type TokenInfo = record { 25 | deploy_time : nat64; 26 | holder_number : nat64; 27 | fee_to : principal; 28 | history_size : nat64; 29 | metadata : Metadata; 30 | cycles : nat64; 31 | }; 32 | type TransactionStatus = variant { Failed; Succeeded; Inprogress }; 33 | type TxError = variant { 34 | InsufficientAllowance; 35 | InsufficientBalance; 36 | Unauthorized; 37 | }; 38 | service : (text, text, text, nat8, nat, principal, nat) -> { 39 | allowance : (principal, principal) -> (nat) query; 40 | approve : (principal, nat) -> (Result); 41 | balanceOf : (principal) -> (nat) query; 42 | burn : (nat) -> (Result); 43 | decimals : () -> (nat8) query; 44 | getAllowanceSize : () -> (nat64) query; 45 | getHolders : (nat64, nat64) -> (vec record { principal; nat }) query; 46 | getLogo : () -> (text) query; 47 | getMetadta : () -> (Metadata) query; 48 | getTokenInfo : () -> (TokenInfo) query; 49 | getTransaction : (nat64) -> (OpRecord) query; 50 | getTransactions : (nat64, nat64) -> (vec OpRecord) query; 51 | getUserApprovals : (principal) -> (vec record { principal; nat }) query; 52 | getUserTransactionAmount : (principal) -> (nat64) query; 53 | getUserTransactions : (principal, nat64, nat64) -> (vec OpRecord) query; 54 | historySize : () -> (nat64) query; 55 | mint : (principal, nat) -> (Result); 56 | name : () -> (text) query; 57 | owner : () -> (principal) query; 58 | setFee : (nat) -> (); 59 | setFeeTo : (principal) -> (); 60 | setLogo : (text) -> (); 61 | setOwner : (principal) -> (); 62 | symbol : () -> (text) query; 63 | totalSupply : () -> (nat) query; 64 | transfer : (principal, nat) -> (Result); 65 | transferFrom : (principal, principal, nat) -> (Result); 66 | } -------------------------------------------------------------------------------- /spec.md: -------------------------------------------------------------------------------- 1 | # Token Standard Spec 2 | 3 | 4 | 5 | A fungible token standard for the DFINITY Internet Computer. 6 | 7 | ## Abstract 8 | 9 | A standard token interface is a basic building block for many applications on the Internet Computer, such as wallets and decentralized exchanges, in this specification we propose a standard token interface for fungible tokens on the IC. This standard provides basic functionality to transfer tokens, allow tokens to be approved so they can be spent by a third-party, it also provides interfaces to query history transactions. 10 | 11 | ## Specification 12 | 13 | ### 1. Data Structures 14 | 15 | 1. Metadata: basic token information 16 | 17 | ```js 18 | type Metadata = { 19 | logo : Text; // base64 encoded logo or logo url 20 | name : Text; // token name 21 | symbol : Text; // token symbol 22 | decimals : Nat8; // token decimal 23 | totalSupply : Nat; // token total supply 24 | owner : Principal; // token owner 25 | fee : Nat; // fee for update calls 26 | } 27 | ``` 28 | 29 | 2. TxReceipt: receipt for update calls, contains the transaction index or an error message 30 | 31 | ```js 32 | type TxReceipt = Result.Result; 36 | 37 | when the Transaction status is #failed, an error should be returned instead of a transaction id 38 | 39 | 3. TxRecord: history transaction record 40 | 41 | ```js 42 | public type Operation = { 43 | #approve; 44 | #mint; 45 | #transfer; 46 | #transferFrom; 47 | }; 48 | public type TransactionStatus = { 49 | #succeeded; 50 | #failed; 51 | }; 52 | public type TxRecord = { 53 | caller: ?Principal; 54 | op: Operation; // operation type 55 | index: Nat; // transaction index 56 | from: Principal; 57 | to: Principal; 58 | amount: Nat; 59 | fee: Nat; 60 | timestamp: Time.Time; 61 | status: TransactionStatus; 62 | }; 63 | ``` 64 | 65 | `caller` in TxRecord is optional and only need to be non-empty for `transferFrom` calls 66 | 67 | ### 2. Basic Interfaces 68 | 69 | #### Update calls 70 | 71 | The update calls described in this section might choose to charge `fee` amount of tokens to prevent DDoS attack, this is necessary because of the reverse gas model of the IC. 72 | All update functions are allowed to trap, instead of returning an error in order to take advantage of the canisters automatic, atomic state rollback. 73 | 74 | Please keep in mind that as of now the canisters' stable memory is limited to 8GB. This forces token implementations to come up with their own scalable transaction storage implementation that offloads the data to separate canisters. An other limitation of the Dfinity blockchain is that currently inter-canister query calls are not supported. These limitations together mean that getTransaction and getTransactions functions temporarily have to be update functions. 75 | 76 | ##### transfer 77 | 78 | Transfers `value` amount of tokens to user `to`, returns a `TxReceipt` which contains the transaction index or an error message. 79 | 80 | ```javascript 81 | public shared(msg) func transfer(to: Principal, value: Nat) : async TxReceipt 82 | ``` 83 | 84 | ##### transferFrom 85 | 86 | Transfers `value` amount of tokens from user `from` to user `to`, this method allows canster smart contracts to transfer tokens on your behalf, it returns a `TxReceipt` which contains the transaction index or an error message. 87 | 88 | ```js 89 | public shared(msg) func transferFrom(from: Principal, to: Principal, value: Nat) : async TxReceipt 90 | ``` 91 | 92 | ##### approve 93 | 94 | Allows `spender` to withdraw tokens from your account, up to the `value` amount. If it is called again it overwrites the current allowance with `value`. There is no upper limit for `value`. 95 | 96 | ```js 97 | public shared(msg) func approve(spender: Principal, value: Nat) : async TxReceipt 98 | ``` 99 | 100 | ##### getTransaction 101 | 102 | Returns transaction detail of the transaction identified by `index`. If the `index` is out of range, the execution traps. Transactions are indexed from zero. 103 | 104 | ```js 105 | public query func getTransaction(index: Nat) : async TxRecord 106 | ``` 107 | 108 | ##### getTransactions 109 | 110 | Returns an array of transaction records in the range `[start, start + limit)`. To fend off DoS attacks, this function is allowed to trap, if limit is greater than the limit allowed by the token. This function is also allowed to trap if `start + limit > historySize()` 111 | 112 | ```js 113 | public query func getTransactions(start: Nat, limit: Nat) : async [TxRecord] 114 | ``` 115 | 116 | #### Query calls 117 | 118 | ##### logo 119 | 120 | Returns the logo of the token. 121 | 122 | ```js 123 | public query func logo() : async Text 124 | ``` 125 | 126 | ##### name 127 | 128 | Returns the name of the token. 129 | 130 | ```js 131 | public query func name() : async Text 132 | ``` 133 | 134 | ##### symbol 135 | 136 | Returns the symbol of the token. 137 | 138 | ```js 139 | public query func symbol() : async Text 140 | ``` 141 | 142 | ##### decimals 143 | 144 | Returns the decimals of the token. 145 | 146 | ```js 147 | public query func decimals() : async Nat8 148 | ``` 149 | 150 | ##### totalSupply 151 | 152 | Returns the total supply of the token. 153 | 154 | ```js 155 | public query func totalSupply() : async Nat 156 | ``` 157 | 158 | ##### balanceOf 159 | 160 | Returns the balance of user `who`. 161 | 162 | ```js 163 | public query func balanceOf(who: Principal) : async Nat 164 | ``` 165 | 166 | ##### allowance 167 | 168 | Returns the amount which `spender` is still allowed to withdraw from `owner`. 169 | 170 | ```js 171 | public query func allowance(owner: Principal, spender: Principal) : async Nat 172 | ``` 173 | 174 | ##### getMetadata 175 | 176 | Returns the metadata of the token. 177 | 178 | ```js 179 | public query func getMetadata() : async Metadata 180 | ``` 181 | 182 | ##### historySize 183 | 184 | Returns the history size. 185 | 186 | ```js 187 | public query func historySize() : async Nat 188 | ``` 189 | 190 | 191 | ### 3. Optional interfaces 192 | 193 | #### Update calls 194 | 195 | The following update calls should be authorized, only the `owner` of the token canister can call these functions. 196 | 197 | ##### mint 198 | 199 | Mint `value` number of new tokens to user `to`, this will increase the token total supply, only `owner` is allowed to mint new tokens. 200 | 201 | ```js 202 | public shared(msg) func mint(to: Principal, value: Nat): async TxReceipt 203 | ``` 204 | 205 | ##### burn 206 | 207 | Burn `value` number of new tokens from user `from`, this will decrease the token total supply, only `owner` or the user `from` him/herself can perform this operation. 208 | 209 | ```js 210 | public shared(msg) func burn(from: Principal, value: Nat): async TxReceipt 211 | ``` 212 | 213 | `aaaaa-aa` is the IC management canister id, it's not a real canister, just an abstraction of system level management functions, it can be used as blackhole address. 214 | 215 | ##### setLogo 216 | 217 | Change the logo of the token, no return value needed. The `logo` can either be a base64 encoded text of the logo picture or an URL pointing to the logo picture. 218 | 219 | ```js 220 | public shared(msg) func setLogo(logo: Text) 221 | ``` 222 | 223 | ##### setFee 224 | 225 | Set fee to `newFee` for update calls(`approve`, `transfer`, `transferFrom`), no return value needed. 226 | 227 | ```javascript 228 | public shared(msg) func setFee(newFee: Nat) 229 | ``` 230 | 231 | ##### setFeeTo 232 | 233 | Set fee receiver to `newFeeTo` , no return value needed. 234 | 235 | ```javascript 236 | public shared(msg) func setFeeTo(newFeeTo: Principal) 237 | ``` 238 | 239 | ##### setOwner 240 | 241 | Set the owner of the token to `newOwner`, no return value needed. 242 | 243 | ```javascript 244 | public shared(msg) func setOwner(newOwner: Principal) 245 | ``` 246 | 247 | #### Query calls 248 | 249 | ##### getUserTransactions 250 | 251 | Returns an array of transaction records in range `[start, start + limit)` related to user `who` . Unlike `getTransactions` 252 | function, the range [start, start + limit) for getUserTransactions is not the global range of all transactions. 253 | The range [start, start + limit) here pertains to the transactions of user `who`. 254 | Implementations are allowed to return less TxRecords than requested to fend off DoS attacks. 255 | 256 | ```js 257 | public query func getUserTransactions(who: Principal, start: Nat, limit: Nat) : async [TxRecord] 258 | ``` 259 | 260 | ##### getUserTransactionAmount 261 | 262 | Returns total number of transactions related to the user `who`. 263 | 264 | ```js 265 | public query func getUserTransactionAmount(who: Principal) : async Nat 266 | ``` 267 | 268 | ### 4. Change log 269 | --------------------------------------------------------------------------------