├── .gitignore ├── .gitmodules ├── Dockerfile ├── LICENSE ├── LICENSE-APACHEv2 ├── LICENSE-MIT ├── Makefile ├── README.md ├── build.sh ├── docs ├── basics.md └── installing.md ├── examples ├── balances.nim ├── default_func.nim ├── ee │ ├── block_echo.nim │ ├── helloworld.nim │ └── panicoverride.nim ├── erc20.nim ├── hello.nim ├── king_of_the_hill.nim ├── map.nim ├── panicoverride.nim ├── payable.nim ├── registry.nim ├── substrate │ ├── hello_world.nim │ ├── malloc.c │ ├── panicoverride.nim │ └── setter.nim ├── wrc20.nim └── wrc202.nim ├── include └── string.h ├── nimplay.nim ├── nimplay ├── builtin_keywords.nim ├── ee_runtime.nim ├── ewasm_runtime.nim ├── function_signature.nim ├── logging.nim ├── nimplay_macros.nim ├── storage.nim ├── substrate_runtime.nim ├── types.nim └── utils.nim ├── nimplay0_1.nim ├── tests ├── ee │ ├── test.sh │ ├── test_block_echo.yml │ └── test_helloworld.yml └── substrate │ ├── package.json │ ├── test.sh │ ├── tests │ ├── consts.ts │ ├── contract-nimplay.spec.ts │ └── utils.ts │ ├── tsconfig.js │ ├── tsconfig.json │ └── utils.ts └── tools ├── abi_gen.nim ├── deploy.py ├── eth_postprocess.sh ├── get_wabt.sh ├── k256_sig.nim ├── nim.cfg ├── nimplayc └── substrate_postprocess.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | tools/wabt/* 3 | tools/abi_gen 4 | examples/*.wasm 5 | .priv_key_hex 6 | *.wasm 7 | tests/ee/ewasm-scout/ 8 | tests/substrate/node_modules/ 9 | 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/nim-stew"] 2 | path = vendor/nim-stew 3 | url = https://github.com/status-im/nim-stew.git 4 | [submodule "vendor/stint"] 5 | path = vendor/stint 6 | url = https://github.com/status-im/nim-stint.git 7 | [submodule "vendor/nimcrypto"] 8 | path = vendor/nimcrypto 9 | url = https://github.com/cheatfate/nimcrypto.git 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | ADD . /nimplay/ 4 | WORKDIR /nimplay/ 5 | 6 | RUN apt-get update && \ 7 | apt-get install -y software-properties-common wget curl gcc g++ git cmake && \ 8 | wget https://nim-lang.org/download/nim-0.20.2-linux_x64.tar.xz && \ 9 | tar xf nim-0.20.2-linux_x64.tar.xz && \ 10 | cd nim-0.20.2 && \ 11 | ./install.sh /usr/bin && \ 12 | cd .. && \ 13 | rm -rf nim-0.20.2 && \ 14 | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ 15 | apt-add-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic main" && \ 16 | apt-get install -y clang-8 lld-8 libc6-dev-i386 && \ 17 | rm -rf wabt && \ 18 | git clone --recursive https://github.com/WebAssembly/wabt wabt && \ 19 | mkdir -p wabt/build && \ 20 | cd wabt/build && \ 21 | cmake ".." && \ 22 | cmake --build "." && \ 23 | make install && \ 24 | cd ../../ && \ 25 | rm -rf wabt && \ 26 | nim c -d:release -p:/nimplay/vendor/nimcrypto -p:/nimplay/vendor/stint -p:/nimplay/vendor/nim-stew/ --out:/usr/bin/k256_sig tools/k256_sig.nim && \ 27 | nim c -d:release -p:/nimplay/vendor/nimcrypto -p:/nimplay/vendor/stint -p:/nimplay/vendor/nim-stew/ --out:/usr/bin/abi_gen tools/abi_gen.nim && \ 28 | cp tools/nimplayc /usr/bin/nimplayc && \ 29 | update-alternatives --install /usr/bin/clang clang /usr/bin/clang-8 1000 && \ 30 | update-alternatives --install /usr/bin/wasm-ld wasm-ld /usr/bin/wasm-ld-8 1000 && \ 31 | apt-get remove -y cmake curl wget gcc software-properties-common && \ 32 | apt-get auto-remove -y && \ 33 | rm -rf /var/lib/apt/lists/* 34 | 35 | ENTRYPOINT ["/usr/bin/nim"] 36 | 37 | 38 | # apt-get install libc6-dev-i386 39 | # apt-get install lld-8 40 | # update-alternatives --install /usr/bin/wasm-ld wasm-ld /usr/bin/wasm-ld-8 1000 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Status 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE-APACHEv2: -------------------------------------------------------------------------------- 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, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising 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 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Status Research & Development GmbH 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Status Research & Development GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | user_id :=$(shell id -u $(shell whoami)) 2 | pwd=$(shell pwd) 3 | POSTPROCESS=tools/eth_postprocess.sh 4 | PATH_PARAMS=-p:/code/vendor/nimcrypto -p:/code/vendor/stint -p:/code/vendor/nim-stew/ 5 | # Use NLVM 6 | DOCKER_NLVM=docker run -e HOME='/tmp/' --user $(user_id):$(user_id) -w /code/ -v $(pwd):/code/ jacqueswww/nlvm 7 | DOCKER_NLVM_C=$(DOCKER_NLVM) $(PATH_PARAMS) c 8 | NLVM_WAMS32_FLAGS= --nlvm.target=wasm32 --gc:none -l:--no-entry -l:--allow-undefined -d:clang -d:release 9 | DOCKER_NLVM_C=$(DOCKER_NLVM) $(PATH_PARAMS) $(NLVM_WAMS32_FLAGS) c 10 | # Use nim + clang 11 | DOCKER_NIM_CLANG=docker run -e HOME='/tmp/' --user $(user_id):$(user_id) -w /code/ -v $(pwd):/code/ --entrypoint="/usr/bin/nim" jacqueswww/nimclang --verbosity:2 12 | CLANG_OPTIONS_LINKER=-nostdlib -Wl,--no-entry,--allow-undefined,--strip-all,--export-dynamic 13 | 14 | # Ewasm 15 | DOCKER_NIM_CLANG_C=$(DOCKER_NIM_CLANG) --cc:clang $(PATH_PARAMS) c 16 | DOCKER_NIM_CLANG_PASS_FLAGS_EWASM = --passC:"--target=wasm32-unknown-unknown-wasm" \ 17 | --passL:"--target=wasm32-unknown-unknown-wasm" --passC:"-I./include" --clang.options.linker:"$(CLANG_OPTIONS_LINKER)" 18 | DOCKER_NIM_CLANG_FLAGS_EWASM=$(DOCKER_NIM_CLANG_PASS_FLAGS_EWASM) --os:standalone --cpu:i386 --cc:clang --gc:none --nomain -d:release 19 | DOCKER_NIM_CLANG_EWASM_C=$(DOCKER_NIM_CLANG) $(DOCKER_NIM_CLANG_FLAGS_EWASM) $(PATH_PARAMS) c 20 | 21 | # Substrate 22 | DOCKER_NIM_CLANG_PASS_FLAGS_SUBSTRATE = --passC:"--target=wasm32-unknown-unknown-wasm" \ 23 | --passL:"--target=wasm32-unknown-unknown-wasm" --passC:"-I./include" \ 24 | --clang.options.linker:"$(CLANG_OPTIONS_LINKER),--import-memory,--max-memory=131072" 25 | DOCKER_NIM_CLANG_FLAGS_SUBSTRATE=$(DOCKER_NIM_CLANG_PASS_FLAGS_SUBSTRATE) --os:standalone --cpu:i386 --cc:clang --gc:none --nomain -d:release 26 | SUBSTRATE_NIMC=$(DOCKER_NIM_CLANG) $(DOCKER_NIM_CLANG_FLAGS_SUBSTRATE) $(PATH_PARAMS) c 27 | 28 | ifdef USE_NLVM 29 | NIMC=$(DOCKER_NLVM_C) 30 | EWASM_NIMC=$(DOCKER_NLVM_C) 31 | else 32 | NIMC=$(DOCKER_NIM_CLANG_C) 33 | EWASM_NIMC=$(DOCKER_NIM_CLANG_EWASM_C) 34 | endif 35 | 36 | .PHONY: all 37 | all: tools examples 38 | 39 | .PHONY: get-nlvm-docker 40 | get-nlvm-docker: 41 | docker pull docker.io/jacqueswww/nlvm 42 | 43 | .PHONY: get-nimclang-docker 44 | get-nimclang-docker: 45 | docker pull docker.io/jacqueswww/nimclang 46 | 47 | .PHONY: get-wabt 48 | get-wabt: 49 | rm -rf tools/wabt 50 | ./tools/get_wabt.sh 51 | mv wabt tools/ 52 | 53 | .PHONY: tools 54 | tools: 55 | $(NIMC) -d:release --out:tools/k256_sig tools/k256_sig.nim 56 | $(NIMC) -d:release --out:tools/abi_gen tools/abi_gen.nim 57 | 58 | .PHONY: clean 59 | clean: 60 | rm -f *.wasm *.ll *.wat 61 | 62 | .PHONY: get-nlvm examples 63 | get-nlvm-appimage: 64 | curl -L https://github.com/arnetheduck/nlvm/releases/download/continuous/nlvm-x86_64.AppImage -o tools/nlvm 65 | chmod +x tools/nlvm 66 | 67 | .PHONY: vendors 68 | vendors: 69 | cd vendors 70 | git submodule update --init 71 | 72 | .PHONY: ewasm_king_of_the_hill 73 | ewasm_king_of_the_hill: 74 | $(EWASM_NIMC) --out:examples/king_of_the_hill.wasm examples/king_of_the_hill.nim 75 | $(POSTPROCESS) examples/king_of_the_hill.wasm 76 | 77 | .PHONY: examples 78 | ewasm-examples: ewasm_king_of_the_hill 79 | $(EWASM_NIMC) --out:examples/registry.wasm examples/registry.nim 80 | $(POSTPROCESS) examples/registry.wasm 81 | $(EWASM_NIMC) --out:examples/balances.wasm examples/balances.nim 82 | $(POSTPROCESS) examples/balances.wasm 83 | $(EWASM_NIMC) --out:examples/erc20.wasm examples/erc20.nim 84 | $(POSTPROCESS) examples/erc20.wasm 85 | $(EWASM_NIMC) --out:examples/default_func.wasm examples/default_func.nim 86 | $(POSTPROCESS) examples/default_func.wasm 87 | 88 | .PHONY: ee-examples 89 | ee-examples: 90 | $(EWASM_NIMC) --out:examples/ee/helloworld.wasm examples/ee/helloworld.nim 91 | $(EWASM_NIMC) --out:examples/ee/block_echo.wasm examples/ee/block_echo.nim 92 | 93 | .PHONY: test-ee 94 | test-ee: ee-examples 95 | cd tests/ee/; \ 96 | ./test.sh 97 | 98 | SUBSTRATE_POSTPROCESS=tools/substrate_postprocess.sh 99 | 100 | .PHONY: substrate-examples 101 | substrate-examples: 102 | $(SUBSTRATE_NIMC) --out:examples/substrate/hello_world.wasm examples/substrate/hello_world.nim 103 | $(SUBSTRATE_POSTPROCESS) examples/substrate/hello_world.wasm 104 | $(SUBSTRATE_NIMC) --out:examples/substrate/setter.wasm examples/substrate/setter.nim 105 | $(SUBSTRATE_POSTPROCESS) examples/substrate/setter.wasm 106 | 107 | .PHONY: test-substrate 108 | test-substrate: substrate-examples 109 | cd tests/substrate; \ 110 | SUBSTRATE_PATH="${HOME}/.cargo/bin/substrate" ./test.sh 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NimPlay 2 | 3 | [![Join the chat at https://gitter.im/status-im/nimplay](https://badges.gitter.im/status-im/nimplay.svg)](https://gitter.im/status-im/nimplay?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Disclaimer: WIP 6 | 7 | Nimplay is Domain Specific Language for writing smart contracts in Ethereum, using the Nim macro system. 8 | 9 | ## Language Principles 10 | 11 | *to be decided ;)* 12 | 13 | ## Docs 14 | 15 | [Installation Docs](docs/installing.md) to get nimplay up and running. 16 | [Language Basics](docs/basics.md) to get coding with nimplay. 17 | 18 | ## License 19 | 20 | Licensed and distributed under either of 21 | 22 | * MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT 23 | 24 | or 25 | 26 | * Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0) 27 | 28 | at your option. This file may not be copied, modified, or distributed except according to those terms. 29 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export WASM_LLVM_BIN="./llvm-wasm/bin/" 4 | 5 | function print_contract () { 6 | wasm_file=$1 7 | echo "${wasm_file} eWASM bytecode:" 8 | echo "0x"`xxd -p $wasm_file | tr -d '\n'` 9 | } 10 | 11 | nimble examples 12 | 13 | if [ $? -eq 0 ]; then 14 | print_contract examples/hello.wasm 15 | fi 16 | -------------------------------------------------------------------------------- /docs/basics.md: -------------------------------------------------------------------------------- 1 | # Contract Interface 2 | 3 | To setup a simple contract one makes use of the `contract` block macro. 4 | 5 | ```nim 6 | contract("ContractName"): 7 | 8 | proc addition*(a: uint256, b: uint256): string = 9 | return a + b 10 | 11 | ``` 12 | 13 | The string paramater given to the `contract` block macro can be any unique string. 14 | 15 | # Function Visibility 16 | 17 | All nimplay functions need to be annotated as either private or public, all functions marked with 18 | and asterisk `*` are public and can be called from a transaction or another contract. 19 | If a contract is not marked as public, it will be unreachable from outside the contract. 20 | 21 | # Payable 22 | 23 | To mark a function open to receive funds, the `payable` function pragma is used. 24 | 25 | ```nim 26 | contract("Main"): 27 | 28 | proc pay_me() {.payable.}: uint256 = 29 | 1.stuint(256) 30 | ``` 31 | 32 | # Types 33 | 34 | Type Name | Nim Type | Runtime Size (B) | ABIv1 Size (B) 35 | ----------|------------- |------------------|--------------- 36 | uint256 | StUint(256) | 32 | 32 37 | uint128 | StUint(128) | 16 | 32 38 | address | array[20,byte]| 20 | 32 39 | bytes32 | array[32,byte | 32 | 32 40 | 41 | 42 | # Builtin Variables 43 | 44 | To easily access the current transactions state as well block specific states the following builtin variables are exposed. 45 | 46 | Variable Name | Type | Contents 47 | --------------|--------- |---------- 48 | msg.sender | address | Current address making CALL to contract 49 | msg.value | wei_value (uint128)| Ether value in wei 50 | 51 | 52 | # Logging 53 | 54 | To let the world know an event occured from a contract, Ethereum uses events. In Nimplay this can 55 | be achieved using the `{.event.}` with function without a body of code. 56 | To emit an event the `log` base keyword is used. 57 | 58 | ```nim 59 | contract("Main"): 60 | # Events 61 | proc nameGiven(name: bytes32, address: uint128) {.event.} 62 | 63 | proc setName*(name: bytes32): 64 | self.name = name 65 | log.nameGiven(name, msg.sender) 66 | ``` 67 | 68 | To indicate what parameters have to be indexed, for easy retrieval later use `{.indexed.}`, a maximum of 3 indexed topics (or parameters) are allowed. 69 | 70 | ```nim 71 | contract("Main"): 72 | # Events 73 | proc nameGiven(name: bytes32, address {.indexed.}: uint128) {.event.} 74 | 75 | ``` 76 | 77 | # Mappings 78 | 79 | Nimplay uses `StorageTable` type to indicated a mapping. 80 | 81 | ```nim 82 | contract("Registry"): 83 | 84 | names: StorageTable[bytes32, bytes32] 85 | 86 | proc set_name*(k: bytes32, v: bytes32) {.self.} = 87 | self.names[k] = v 88 | 89 | proc get_name*(k: bytes32): bytes32 {.self.} = 90 | self.names[k] 91 | ``` 92 | 93 | # Default / Fallback Function 94 | 95 | To define a Fallback function, add function to your contract named `default`. 96 | 97 | ```nim 98 | 99 | contract("Default"): 100 | proc default*() = 101 | rever(nil, 0) 102 | # execute on all transactions 103 | ``` 104 | 105 | If no Fallback is declared the fallback function causes a `revert()`. 106 | 107 | 108 | -------------------------------------------------------------------------------- /docs/installing.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | The NimPlay installation is currently managed through a simple [Makefile](https://github.com/status-im/nimplay/blob/master/Makefile). 4 | To use it, you shouldn't need anything else besides _BASH_, _GNU Make_, _CMake_ and _Python_. 5 | 6 | ## Obtaining Nim + Clang Docker 7 | 8 | ``` 9 | make get-nimclang-docker 10 | ``` 11 | 12 | ## Obtaining NLVM docker 13 | 14 | To use NLVM instead of Nim + Clang use 15 | 16 | ``` 17 | make get-nlvm-docker 18 | git submodule update --init 19 | make get-wabt 20 | make USE_NLVM=1 tools 21 | ``` 22 | 23 | ### Using NLVM docker 24 | 25 | ``` 26 | make USE_NLVM=1 examples 27 | ``` 28 | 29 | ## Using Nim + Clang Docker 30 | 31 | ``` 32 | make get-nimclang-docker 33 | git submodule update --init 34 | ``` 35 | Note: nimclang docker image contains tools & wabt. 36 | 37 | ## Building the examples 38 | 39 | This repo includes a number of examples such as the [King of the Hill](https://github.com/status-im/nimplay/blob/master/examples/king_of_the_hill.nim) contract. To build them, use the following commands: 40 | 41 | ``` 42 | make examples 43 | ``` 44 | or 45 | ``` 46 | make USE_NLVM=1 examples 47 | ``` 48 | 49 | ## Deploying to EWASM Testnet 50 | 51 | Nimplay ships with a deploy python script (requires web3py installed). This script key can be used to deploy a contract 52 | 53 | Place 32 byte private key, hex encoded in `.priv_key_hex` 54 | ``` 55 | cat > .priv_key_hex 56 | 0x0000000000000000000000000000000000000000000000000000000000000000 57 | ``` 58 | 59 | To deploy a contract use: 60 | ``` 61 | ./tools/deploy.py examples/king_of_the_hill.wasm 62 | ``` 63 | 64 | The following enviroment variables are consumed by the deploy.py script 65 | 66 | | Varname |Default (unset) | 67 | |--- |--- | 68 | | PRIVATE_KEY_FILE| .priv_key_hex | 69 | | RPC_URL | http://ewasm.ethereum.org:8545| 70 | | WAT2WASM | ./tools/wabt/bin/wat2wasm | 71 | | WASM2WAT | ./tools/wabt/bin/wasm2wat | 72 | 73 | ### ModuleNotFoundError: No module named 'web3' 74 | 75 | This means web3py is not installed, please install using pip: 76 | 77 | ``` 78 | pip install --user web3 79 | ``` 80 | 81 | Also see how to make a [virtualenv](https://www.liquidweb.com/kb/creating-virtual-environment-ubuntu-16-04/) if you want your packages isolated. 82 | -------------------------------------------------------------------------------- /examples/balances.nim: -------------------------------------------------------------------------------- 1 | import ../nimplay0_1 2 | 3 | 4 | contract("Depository"): 5 | 6 | balances: StorageTable[address, bytes32, wei_value] 7 | 8 | proc deposit*(k: bytes32) {.self,msg,payable.} = 9 | self.balances[msg.sender][k] = msg.value 10 | 11 | proc get_balance*(k: bytes32): wei_value {.self,msg.} = 12 | self.balances[msg.sender][k] 13 | 14 | # proc withdraw*(amount: wei_value) = 15 | # var 16 | # balance = self.balances[msg.sender][k] 17 | 18 | # if amount <= balance: 19 | # self.balances[msg.sender][k] = 20 | # call( 21 | # getGasLeft(), 22 | # addr msg.sender 23 | # amount, 24 | # ) 25 | 26 | 27 | # proc call*(gas: int64, addressOffset, valueOffset, dataOffset: pointer, 28 | # dataLength: int32, resultOffset: pointer, 29 | # resultLength: int32): int32 30 | -------------------------------------------------------------------------------- /examples/default_func.nim: -------------------------------------------------------------------------------- 1 | import ../nimplay0_1 2 | 3 | 4 | contract("Default"): 5 | 6 | proc default*() = 7 | discard 8 | -------------------------------------------------------------------------------- /examples/ee/block_echo.nim: -------------------------------------------------------------------------------- 1 | import ../../nimplay/ee_runtime 2 | 3 | {.compile: "malloc.c".} 4 | proc malloc(n: int): pointer {.importc.} 5 | 6 | 7 | proc copy_into_ba(to_ba: var auto, offset: int, from_ba: auto) = 8 | for i, x in from_ba: 9 | if offset + i > sizeof(to_ba) - 1: 10 | break 11 | to_ba[offset + i] = x 12 | 13 | 14 | proc main() {.exportwasm.} = 15 | var 16 | pre_state_root {.noinit.}: array[32, byte] 17 | post_state_root {.noinit.}: array[32, byte] 18 | eth2_loadPreStateRoot(addr pre_state_root) 19 | var 20 | block_data_size = eth2_blockDataSize() 21 | block_data = malloc(block_data_size.int) 22 | eth2_blockDataCopy(block_data, 0, block_data_size) 23 | copyMem(addr post_state_root, block_data, 32) 24 | eth2_savePostStateRoot(addr post_state_root[0]) 25 | -------------------------------------------------------------------------------- /examples/ee/helloworld.nim: -------------------------------------------------------------------------------- 1 | import ../../nimplay/ee_runtime 2 | 3 | 4 | proc main() {.exportwasm.} = 5 | var 6 | pre_state_root {.noinit.}: array[32, byte] 7 | post_state_root {.noinit.}: array[32, byte] 8 | eth2_loadPreStateRoot(addr pre_state_root) 9 | debug_log("hello world!") 10 | debug_print32(42) 11 | debug_print64(99'u64) 12 | 13 | post_state_root = pre_state_root # no changes 14 | post_state_root[30] = 0x11'u8 15 | post_state_Root[31] = 0x22'u8 16 | eth2_savePostStateRoot(addr post_state_root) 17 | -------------------------------------------------------------------------------- /examples/ee/panicoverride.nim: -------------------------------------------------------------------------------- 1 | {.push stack_trace: off, profiler:off.} 2 | proc debug_printMemHex*(offset: pointer, length: uint32) {.noreturn, cdecl, importc.} 3 | proc rawoutput(s: string) = 4 | debug_printMemHex(cstring(s), s.len.uint32) 5 | proc panic(s: string) = rawoutput(s) 6 | {.pop.} 7 | -------------------------------------------------------------------------------- /examples/erc20.nim: -------------------------------------------------------------------------------- 1 | import ../nimplay0_1 2 | 3 | 4 | contract("NimCoin"): 5 | # Globals 6 | var 7 | name*: bytes32 8 | symbol*: bytes32 9 | decimals*: uint256 10 | total_supply: uint256 11 | minter*: address 12 | initialised: bool 13 | 14 | # Maps 15 | balanceOf: StorageTable[address, uint256] 16 | allowances: StorageTable[address, address, uint256] 17 | 18 | initialised: bool 19 | 20 | # Events 21 | proc Transfer(ffrom: address, to: address, value: uint256) {.event.} 22 | proc Approval(owner: address, spender: address, value: uint256) {.event.} 23 | 24 | proc init*(nname: bytes32, ssymbol: bytes32, ddecimals: uint256, ttotal_supply: uint256) {.self,msg,log.} = 25 | if not self.initialised: 26 | return 27 | 28 | self.total_supply = ttotal_supply 29 | self.symbol = ssymbol 30 | self.decimals = ddecimals 31 | self.name = nname 32 | self.minter = msg.sender 33 | self.initialised = true 34 | log.Transfer(ZERO_ADDRESS, msg.sender, ttotal_supply) 35 | 36 | proc totalSupply*(): uint256 {.self.} = 37 | self.total_supply 38 | 39 | proc allowance(owner : address, spender : address): uint256 {.self.} = 40 | # @dev Function to check the amount of tokens that an owner allowed to a spender. 41 | # @param owner The address which owns the funds. 42 | # @param spender The address which will spend the funds. 43 | # @return An uint256 specifying the amount of tokens still available for the spender. 44 | self.allowances[owner][spender] 45 | 46 | proc transfer*(to: address, value: uint256): bool {.self,msg,log.} = 47 | # @param to The address to transfer to. 48 | # @param value The amount to be transferred. 49 | self.balanceOf[msg.sender] -= value 50 | self.balanceOf[to] += value 51 | log.Transfer(msg.sender, to, value) 52 | return true 53 | 54 | proc transferFrom(ffrom: address, to: address, value: uint256): bool {.self,msg,log.} = 55 | # @dev Transfer tokens ffrom one address to another. 56 | # Note that while this function emits a Transfer event, this is not required as per the specification, 57 | # and other compliant implementations may not emit the event. 58 | # @param ffrom address The address which you want to send tokens ffrom 59 | # @param to address The address which you want to transfer to 60 | # @param value uint256 the amount of tokens to be transferred 61 | self.balanceOf[ffrom] -= value 62 | self.balanceOf[to] += value 63 | self.allowances[ffrom][msg.sender] -= value 64 | log.Transfer(ffrom, to, value) 65 | return true 66 | 67 | proc approve(spender : address, value : uint256): bool {.self,msg,log.} = 68 | # @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 69 | # Beware that changing an allowance with this method brings the risk that someone may use both the old 70 | # and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 71 | # race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 72 | # https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 73 | # @param spender The address which will spend the funds. 74 | # @param value The amount of tokens to be spent. 75 | self.allowances[msg.sender][spender] =value 76 | log.Approval(msg.sender, spender, value) 77 | return true 78 | 79 | proc mint(to: address, value: uint256) {.self,msg,log.} = 80 | # @dev Mint an amount of the token and assigns it to an account. 81 | # This encapsulates the modification of balances such that the 82 | # proper events are emitted. 83 | # @param to The account that will receive the created tokens. 84 | # @param value The amount that will be created. 85 | 86 | assert msg.sender == self.minter 87 | assert to != ZERO_ADDRESS 88 | self.total_supply += value 89 | self.balanceOf[to] += value 90 | log.Transfer(ZERO_ADDRESS, to, value) 91 | -------------------------------------------------------------------------------- /examples/hello.nim: -------------------------------------------------------------------------------- 1 | import ../eth_contracts 2 | import endians 3 | import stint 4 | 5 | 6 | # proc main() {.exportwasm.} = 7 | # proc test() = 8 | # return 1 9 | # var res = 1234.stuint(256).toByteArrayBE 10 | # finish(addr res, sizeof(res).int32) 11 | 12 | proc main() {.exportwasm.} = 13 | var selector: uint32 14 | callDataCopy(selector, 0) 15 | bigEndian32(addr selector, addr selector) 16 | case selector 17 | of 0xb0f0c96a'u32: 18 | var a: uint32 = 33333 19 | finish(addr a, sizeof(a).int32) 20 | else: 21 | finish(addr selector, sizeof(selector).int32) 22 | 23 | # var hello_param_0: array[32, byte] 24 | # callDataCopy(addr hello_param_0, 0, 32) 25 | # finish(addr hello_param_0, 32) 26 | # proc test(): int = 27 | # return 1 28 | # var res = 1234.stuint(256).toByteArrayBE 29 | # finish(addr res, sizeof(res).int32) 30 | -------------------------------------------------------------------------------- /examples/king_of_the_hill.nim: -------------------------------------------------------------------------------- 1 | import ../nimplay0_1 2 | 3 | 4 | contract("KingOfTheHill"): 5 | 6 | # Globals 7 | var 8 | king_name*: bytes32 9 | king_addr*: address 10 | king_value*: wei_value 11 | 12 | # Events 13 | proc BecameKing(name: bytes32, value: uint128) {.event.} 14 | 15 | # Methods 16 | proc becomeKing*(name: bytes32) {.payable,self,msg,log.} = 17 | if msg.value > self.king_value: 18 | self.king_name = name 19 | self.king_addr = msg.sender 20 | self.king_value = msg.value 21 | log.BecameKing(name, msg.value) 22 | -------------------------------------------------------------------------------- /examples/map.nim: -------------------------------------------------------------------------------- 1 | import ../nimplay0_1 2 | 3 | # proc concat[I1, I2: static[int]; T](a: array[I1, T], b: array[I2, T]): array[I1 + I2, T] = 4 | # result[0..a.high] = a 5 | # result[a.len..result.high] = b 6 | 7 | # proc exit_message(s: string) = 8 | # revert(cstring(s), s.len.int32) 9 | 10 | # # balances: StorageTable[address, wei_value] 11 | # proc setTableValue[N](table_id: int32, key: array[N, byte], value: var bytes32) {.inline.} = 12 | # type CombinedKey {.packed.} = object 13 | # table_id: int32 14 | # key: array[N, byte] 15 | 16 | # var 17 | # sha256_address: array[20, byte] 18 | # combined_key: CombinedKey 19 | # hashed_key: bytes32 20 | 21 | # sha256_address[19] = 2'u8 22 | # combined_key.table_id = table_id 23 | # combined_key.key = key 24 | 25 | # var res = call( 26 | # getGasLeft(), # gas 27 | # addr sha256_address, # addressOffset (ptr) 28 | # nil, # valueOffset (ptr) 29 | # addr combined_key, # dataOffset (ptr) 30 | # sizeof(combined_key).int32, # dataLength 31 | # ) 32 | # if res == 1: # call failed 33 | # exit_message("Could not call sha256 in setTableValue") 34 | # # raise newException(Exception, "Could not call sha256 in setTableValue") 35 | # if getReturnDataSize() != 32.int32: 36 | # exit_message("Could not call sha256, Incorrect return size") 37 | 38 | # returnDataCopy(addr hashed_key, 0.int32, 32.int32) 39 | # storageStore(hashed_key, addr value) 40 | 41 | 42 | contract("MapStorage"): 43 | 44 | proc test*() = 45 | var 46 | key: array[5, byte] = [0x68'u8, 0x65'u8, 0x6c'u8, 0x6c'u8, 0x6f'u8] 47 | 48 | val: bytes32 49 | val[31] = 0x75'u8 50 | setTableValue(0.int32, key, val) 51 | -------------------------------------------------------------------------------- /examples/panicoverride.nim: -------------------------------------------------------------------------------- 1 | {.push stack_trace: off, profiler:off.} 2 | proc revert(dataOffset: pointer; length: int32) {.noreturn, cdecl, importc.} 3 | proc rawoutput(s: string) = 4 | revert(cstring(s), s.len.int32) 5 | proc panic(s: string) = rawoutput(s) 6 | {.pop.} 7 | 8 | 9 | # Try LLVm builtin unreachable: 10 | # void myabort(void) __attribute__((noreturn)); 11 | # void myabort(void) { 12 | # asm("int3"); 13 | # __builtin_unreachable(); 14 | # } 15 | -------------------------------------------------------------------------------- /examples/payable.nim: -------------------------------------------------------------------------------- 1 | import ../nimplay/nimplay_macros 2 | import ../nimplay/types 3 | import ../nimplay/ewasm_eei 4 | import stint 5 | 6 | 7 | contract("PayAssert"): 8 | 9 | proc plus_two*(a: uint256): uint256 {.payable.} = 10 | a + 2 11 | 12 | proc plus_one*(a: uint256): uint256 = 13 | a + 1 14 | -------------------------------------------------------------------------------- /examples/registry.nim: -------------------------------------------------------------------------------- 1 | import ../nimplay0_1 2 | 3 | 4 | contract("Registry"): 5 | 6 | names: StorageTable[bytes32, bytes32] 7 | 8 | proc set_name*(k: bytes32, v: bytes32) {.self.} = 9 | self.names[k] = v 10 | 11 | proc get_name*(k: bytes32): bytes32 {.self.} = 12 | self.names[k] 13 | -------------------------------------------------------------------------------- /examples/substrate/hello_world.nim: -------------------------------------------------------------------------------- 1 | import ../../nimplay/substrate_runtime 2 | 3 | 4 | # Init function. 5 | proc deploy(): uint32 {.exportwasm.} = 6 | 0 7 | 8 | # Main function. 9 | proc call(): uint32 {.exportwasm.} = 10 | var 11 | s = cstring("Hello world!") 12 | ext_println(s, s.len.int32) 13 | ext_scratch_write(s, s.len.int32) 14 | 0 # return 15 | -------------------------------------------------------------------------------- /examples/substrate/malloc.c: -------------------------------------------------------------------------------- 1 | extern unsigned char __heap_base; 2 | 3 | unsigned int bump_pointer = &__heap_base; 4 | 5 | void* malloc(int n) { 6 | unsigned int r = bump_pointer ; 7 | bump_pointer += n; 8 | return (void *)r; 9 | } 10 | 11 | void free(void* p) { 12 | // lol 13 | } 14 | -------------------------------------------------------------------------------- /examples/substrate/panicoverride.nim: -------------------------------------------------------------------------------- 1 | {.push stack_trace: off, profiler:off.} 2 | proc ext_println*(offset: pointer, length: uint32) {.noreturn, cdecl, importc.} 3 | proc rawoutput(s: string) = 4 | ext_println(cstring(s), s.len.uint32) 5 | proc panic(s: string) = rawoutput(s) 6 | {.pop.} 7 | -------------------------------------------------------------------------------- /examples/substrate/setter.nim: -------------------------------------------------------------------------------- 1 | import ../../nimplay/substrate_runtime 2 | 3 | {.compile: "malloc.c".} 4 | proc malloc(n: int): pointer {.importc.} 5 | 6 | type 7 | Action = enum 8 | Set = 0'u8, Get = 1'u8, SelfEvict = 2'u8 9 | 10 | # Init function. 11 | proc deploy(): uint32 {.exportwasm.} = 12 | 0 13 | 14 | proc get_scratch(): (pointer, int32) = 15 | var 16 | scratch_size = ext_scratch_size() 17 | mem_ptr = malloc(scratch_size.int) 18 | ext_scratch_read(mem_ptr, 0, scratch_size) 19 | (mem_ptr, scratch_size) 20 | 21 | proc print(s: cstring) = 22 | ext_println(s, s.len.int32) 23 | 24 | 25 | proc incr_pointer(oldp: pointer): pointer = 26 | var newp = cast[pointer](cast[uint](oldp) + 1u) 27 | newp 28 | 29 | 30 | proc set_val_in_store(scratch_ptr: pointer, scratch_size: int32) = 31 | print("Set".cstring) 32 | var 33 | key: array[32, byte] = [ 34 | 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 35 | 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 36 | 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 2'u8, 37 | 2'u8, 2'u8, 2'u8, 2'u8, 2'u8 38 | ] 39 | offset_ptr = incr_pointer(scratch_ptr) 40 | 41 | ext_set_storage(addr key, 1.int32 , offset_ptr, scratch_size - 1) 42 | 43 | # Main function. 44 | proc call(): uint32 {.exportwasm.} = 45 | var 46 | selector: uint8 47 | (scratch_ptr, scratch_size) = get_scratch() 48 | 49 | copyMem(addr selector, scratch_ptr, 1) 50 | 51 | case selector 52 | of Action.Set.ord: 53 | set_val_in_store(scratch_ptr, scratch_size) 54 | of Action.Get.ord: 55 | print("Get: Todo".cstring) 56 | of Action.SelfEvict.ord: 57 | print("SelfEvict: Todo".cstring) 58 | else: 59 | print("Unknown action passed".cstring) 60 | 61 | 0 # return 62 | 63 | # sys::ext_set_storage( 64 | # key.as_ptr() as u32, 65 | # 1, 66 | # value.as_ptr() as u32, 67 | # value.len() as u32, 68 | # ) 69 | 70 | 71 | # block_data = malloc(block_data_size.int) 72 | # eth2_blockDataCopy(block_data, 0, block_data_size) 73 | # copyMem(addr post_state_root, block_data, 32) 74 | -------------------------------------------------------------------------------- /examples/wrc20.nim: -------------------------------------------------------------------------------- 1 | ## ewasm “WRC20” token contract coding challenge 2 | ## https://gist.github.com/axic/16158c5c88fbc7b1d09dfa8c658bc363 3 | 4 | import ../nimplay/ewasm_eei, endians 5 | 6 | proc do_balance() = 7 | if getCallDataSize() != 24: 8 | revert(nil, 0) 9 | 10 | var address{.noinit.}: array[20, byte] 11 | callDataCopy(address, 4) 12 | 13 | var balance{.noinit.}: array[32, byte] 14 | storageLoad(address, addr balance) 15 | finish(addr balance, sizeof(balance).int32) 16 | 17 | proc do_transfer() = 18 | if getCallDataSize() != 32: 19 | revert(nil, 0) 20 | 21 | var sender: array[20, byte] 22 | getCaller(addr sender) 23 | var recipient: array[20, byte] 24 | callDataCopy(recipient, 4) 25 | var value: array[8, byte] 26 | callDataCopy(value, 24) 27 | 28 | var senderBalance: array[32, byte] 29 | storageLoad(sender, addr senderBalance) 30 | var recipientBalance: array[32, byte] 31 | storageLoad(recipient, addr recipientBalance) 32 | 33 | var sb, rb, v: uint64 34 | 35 | bigEndian64(addr v, addr value) 36 | bigEndian64(addr sb, addr senderBalance[32 - 8]) 37 | 38 | if sb < v: 39 | revert(nil, 0) 40 | 41 | bigEndian64(addr rb, addr recipientBalance[32 - 8]) 42 | 43 | sb -= v 44 | rb += v # TODO there's an overflow possible here.. 45 | 46 | bigEndian64(addr senderBalance[32 - 8], addr sb) 47 | bigEndian64(addr recipientBalance[32 - 8], addr rb) 48 | 49 | storageStore(sender, addr senderBalance) 50 | storageStore(recipient, addr recipientBalance) 51 | 52 | proc main() {.exportwasm.} = 53 | if getCallDataSize() < 4: 54 | revert(nil, 0) 55 | var selector: uint32 56 | callDataCopy(selector, 0) 57 | case selector 58 | of 0x9993021a'u32: 59 | do_balance() 60 | of 0x5d359fbd'u32: 61 | do_transfer() 62 | else: 63 | revert(nil, 0) 64 | -------------------------------------------------------------------------------- /examples/wrc202.nim: -------------------------------------------------------------------------------- 1 | import ../nimplay/ewasm_eei 2 | import ../nimplay/nimplay_macros 3 | import ../nimplay/types 4 | 5 | # import endians 6 | # import macros 7 | import stint 8 | 9 | # expandMacros: 10 | contract("MyContract"): 11 | var 12 | a: uint256 13 | owner: address 14 | name: bytes32 15 | 16 | # proc default*() {.payable.} 17 | # ... 18 | 19 | # proc publicGetSender(slot_number: uint256) = 20 | # discard 21 | # dumpAstGen: 22 | # tmp_func_get_storage_owner() 23 | # proc tmp_func_get_storage_owner(): address = 24 | # var 25 | # tmp: array[32, byte] 26 | # position: array[32, byte] 27 | # slot_number: uint32 = 1 28 | # position[0..3] = cast[array[4, byte]](slot_number) 29 | # storageLoad(position, addr tmp) 30 | # var output: address 31 | # output[0..20] = tmp[0..20] 32 | # return output 33 | 34 | # proc get_value*(): uint128 {.payable.} = 35 | # var ba: array[16, byte] 36 | # getCallValue(addr ba) 37 | # var val: Stuint[128] 38 | # {.pragma: restrict, codegenDecl: "$# __restrict $#".} 39 | # let r_ptr {.restrict.} = cast[ptr array[128, byte]](addr val) 40 | # for i, b in ba: 41 | # r_ptr[i] = b 42 | # return val 43 | 44 | # proc ret_bytes32*(in_a: bytes32): bytes32 = 45 | # return in_a 46 | 47 | proc set_bytes32*(in_a: bytes32) {.self.} = 48 | self.name = in_a 49 | 50 | proc get_bytes32*(): bytes32 {.self.} = 51 | self.name 52 | 53 | # proc get_value*(): uint128 {.payable.} = 54 | # return msg.value 55 | 56 | # var b: array[16, byte] 57 | # var N = 16 58 | # for i in 0 ..< N: 59 | # b[N-1 - i] = a[i] 60 | # return Uint128.fromBytesBE(b) 61 | 62 | # proc test_out*(aaa: uint128): uint128 = 63 | # return 1222233344.stuint(128) 64 | 65 | # proc test_out_256*(): uint256 = 66 | # return 1222233344.stuint(256) 67 | 68 | # proc get_storage*(): uint256 = 69 | # return self.a 70 | 71 | # proc set_storage*(in_a: uint256) = 72 | # self.a = in_a 73 | 74 | # proc set_storage*(a: uint256) = 75 | # discard 76 | 77 | # proc set_storage*() = 78 | # var 79 | # pos = 0.stuint(32).toByteArrayBE 80 | # value = 999999999999999.stuint(256).toByteArrayBE 81 | # storageStore(pos, addr value) 82 | 83 | # return msg.sender 84 | 85 | # proc tmp_func_get_storage_owner(slot_number: uint256): address = 86 | # var tmp: array[32, byte] 87 | # var position = slot_number.toByteArrayBE 88 | # storageLoad(position, addr tmp) 89 | # var output: address 90 | # output[0..19] = tmp[12..31] 91 | # return output 92 | 93 | # proc addition(in_a: uint256, in_b: uint256): uint256 = 94 | # return in_a + in_b 95 | 96 | # proc get*(): uint256 = 97 | # var tmp: array[32, byte] 98 | # var pos = 0.stuint(32).toByteArrayBE 99 | # storageLoad(pos, addr tmp) 100 | # return Uint256.fromBytesBE(tmp) 101 | 102 | # proc set*() = 103 | # var tmp = 556677.stuint(256).toByteArrayBE 104 | # var pos = 0.stuint(32).toByteArrayBE 105 | # storageStore(pos, addr tmp) 106 | 107 | # proc get_storage*(slot_number: uint256): uint256 = 108 | # var tmp: uint256 109 | # var pos = cast[bytes32](slot_number) 110 | # storageLoad(pos, addr tmp) 111 | # return tmp 112 | 113 | # proc set_storage*(slot_number: uint256, value: uint256) = 114 | # var tmp: array[32, byte] = value.toByteArrayBE 115 | # var pos = cast[bytes32](slot_number) 116 | # storageStore(pos, addr tmp) 117 | 118 | # proc setOwner(in_owner: address) = 119 | # self.owner = in_owner 120 | 121 | # proc getOwner(): address = 122 | # return self.owner 123 | 124 | # proc get_sender222(): address = 125 | # if true: 126 | # return msg.sender 127 | 128 | # proc get_sender(): address = 129 | # if true: 130 | # return msg.sender 131 | 132 | # getCaller(addr tmp_addr) 133 | 134 | # blacklist storageStore 135 | # proc hello333(a: uint256): uint256 = 136 | # discard 137 | 138 | # proc hello(a: uint256): uint256 = 139 | 140 | # dumpAstGen: 141 | # let c: uint256 = Uint256.fromBytesBE(b) 142 | # case a: 143 | # else: 144 | # discard 145 | # var a = hello(1.uint256) 146 | # var b: array[32, byte] 147 | # callDataCopy(addr b, 4, 32) 148 | # var c: uint256 = Uint256.fromBytesBE(b) 149 | 150 | # bigEndian32(addr selector, addr selector) 151 | 152 | # hello(a) 153 | # var selector: uint32 154 | # callDataCopy(selector, 0) 155 | # case selector 156 | # of 0x9993021a'u32: 157 | # discard 158 | # else: 159 | # revert(nil, 0) 160 | 161 | # dumpAstGen: 162 | # var res {.noinit.}: uint256 163 | # dumpAstGen: 164 | # var res = hello(a) 165 | # var res_a = res.toByteArrayBE 166 | # finish(addr res_a, 256) 167 | # finish(nil, 0) 168 | # dumpAstGen: 169 | # bigEndian32(addr selector, addr selector) 170 | # return (123).stuint(256) 171 | 172 | # proc AllWorksToken() {.discardable.} = 173 | # var b: array[32, byte] = (77877).stuint(256).toByteArrayBE() 174 | # finish(addr b, 32) 175 | 176 | # proc world(a: uint256, b: uint256): uint256 = 177 | # return a + b 178 | 179 | # proc do_nothing(a: uint256, b: uint256) = 180 | # discard 181 | 182 | # func addition(a: uint256, b: uint256): uint256 = 183 | # return a + b 184 | 185 | # proc hellonone() = 186 | # discard 187 | 188 | # proc addition(a: uint256, b: uint256): uint256 = 189 | # return a + b 190 | 191 | # contract("MyContract"): 192 | # lib: from("lib/...") 193 | 194 | # proc hello(): uint256 = 195 | # lib.sha500() 196 | 197 | 198 | 199 | # library A: 200 | 201 | # # public init() 202 | # # owner 203 | # # selfdestruct() 204 | # # special_func(aaa) 205 | 206 | 207 | # contract B(A): 208 | # $$$$ 209 | # $$$$ 210 | # $$$$ 211 | # $$$$ 212 | # $$$$ 213 | -------------------------------------------------------------------------------- /include/string.h: -------------------------------------------------------------------------------- 1 | /* 2 | This is a stub file to suppress compilation errors, when nim calls out to 3 | memcpy, memset and memcpy. Currently we rely on clang optimizing these calls 4 | away, but in future we will likely have to come up with something better/reliable 5 | */ 6 | -------------------------------------------------------------------------------- /nimplay.nim: -------------------------------------------------------------------------------- 1 | import nimplay/nimplay_macros 2 | 3 | -------------------------------------------------------------------------------- /nimplay/builtin_keywords.nim: -------------------------------------------------------------------------------- 1 | import 2 | macros, strformat, tables, 3 | strutils, sequtils, algorithm 4 | 5 | import 6 | ./types, ./utils, ./storage, 7 | ./logging 8 | 9 | 10 | proc extract_keys(base_node: NimNode, keys: var seq[NimNode]) = 11 | for x in base_node: 12 | if x.kind == nnkBracketExpr: 13 | extract_keys(x, keys) 14 | else: 15 | keys.add(x) 16 | 17 | 18 | proc check_global_exists(global_ctx: GlobalContext, var_name: string, node: NimNode) = 19 | if not (var_name in global_ctx.global_variables): 20 | raiseParserError( 21 | fmt"Invalid global variable {var_name}, has not been defined.", 22 | node 23 | ) 24 | 25 | 26 | proc is_dot_variable(node: NimNode): bool = 27 | if node.kind == nnkDotExpr: 28 | var correct_types = (node[0].kind, node[1].kind) == (nnkIdent, nnkIdent) 29 | var correct_length = node.len == 2 30 | if correct_types and correct_length: 31 | return true 32 | return false 33 | 34 | 35 | proc is_message_sender(node: NimNode): bool = 36 | if is_dot_variable(node) and node[0].strVal == "msg" and node[1].strVal == "sender": 37 | return true 38 | return false 39 | 40 | 41 | proc is_message_value(node: NimNode): bool = 42 | if is_dot_variable(node) and node[0].strVal == "msg" and node[1].strVal == "value": 43 | return true 44 | return false 45 | 46 | 47 | proc has_self(node: NimNode, global_ctx: GlobalContext): bool = 48 | if is_dot_variable(node) and node[0].strVal == "self": 49 | global_ctx.check_global_exists(node[1].strVal, node) 50 | if node[1].strVal in global_ctx.global_variables: 51 | return true 52 | return false 53 | 54 | 55 | proc is_log_statement(node: NimNode, global_ctx: GlobalContext): bool = 56 | if node.kind == nnkCall: 57 | if is_dot_variable(node[0]) and strVal(node[0][0]) == "log": 58 | var 59 | func_name = strVal(node[0][1]) 60 | if func_name in global_ctx.events: 61 | for func_name in global_ctx.events.keys(): 62 | return true 63 | else: 64 | raiseParserError( 65 | fmt"Invalid log statement {func_name}, event was not defined.", 66 | node 67 | ) 68 | 69 | 70 | proc has_self_assignment(node: NimNode): bool = 71 | if node.kind == nnkAsgn: 72 | if is_dot_variable(node[0]) and node[0][0].strVal == "self": 73 | return true 74 | return false 75 | 76 | 77 | proc has_self_storage_table_set(node: NimNode, global_ctx: GlobalContext): (bool, NimNode) = 78 | var n: NimNode 79 | if node.kind == nnkAsgn and node[0].kind == nnkBracketExpr: 80 | var keys: seq[NimNode] 81 | extract_keys(node[0], keys) 82 | if keys.len > 0 and is_dot_variable(keys[0]) and strVal(keys[0][0]) == "self": 83 | return (true, keys[0]) 84 | return (false, n) 85 | 86 | 87 | proc has_self_storage_table_get(node: NimNode, global_ctx: GlobalContext): (bool, NimNode) = 88 | var n: NimNode 89 | if node.kind == nnkBracketExpr: 90 | var keys: seq[NimNode] 91 | extract_keys(node, keys) 92 | if keys.len > 0 and is_dot_variable(keys[0]) and strVal(keys[0][0]) == "self": 93 | return (true, keys[0]) 94 | return (false, node) 95 | 96 | 97 | proc has_infix_storage_table(node: NimNode): (bool, NimNode) = 98 | var n: NimNode 99 | if node.kind == nnkInfix and node[1].kind == nnkBracketExpr: 100 | var keys: seq[NimNode] 101 | extract_keys(node[1], keys) 102 | if keys.len > 0 and is_dot_variable(keys[0]) and strVal(keys[0][0]) == "self": 103 | return (true, node) 104 | return (false, node) 105 | 106 | 107 | proc has_infix_self_storage(node: NimNode): (bool, NimNode) = 108 | if node.kind == nnkInfix: 109 | if is_dot_variable(node[1]) and node[1][0].strVal == "self": 110 | return (true, node) 111 | return (false, node) 112 | 113 | 114 | proc is_keyword(node: NimNode, global_ctx: GlobalContext): (bool, string) = 115 | var 116 | (valid, sub_node) = has_infix_self_storage(node) 117 | if valid: 118 | return (true, "infix_set_self." & strVal(sub_node[0])[0] & "." & strVal(sub_node[1][1])) 119 | (valid, sub_node) = has_infix_storage_table(node) 120 | if valid: 121 | var keys: seq[NimNode] 122 | extract_keys(node[1], keys) 123 | return (true, "infix_set_table." & strVal(sub_node[0])[0] & "." & strVal(keys[0][1])) 124 | (valid, sub_node) = has_self_storage_table_set(node, global_ctx) 125 | if valid: 126 | return (true, "set_table_self." & strVal(sub_node[1])) 127 | (valid, sub_node) = has_self_storage_table_get(node, global_ctx) 128 | if valid: 129 | return (true, "get_table_self." & strVal(sub_node[1])) 130 | if is_message_sender(node): 131 | return (true, "msg.sender") 132 | elif is_message_value(node): 133 | return (true, "msg.value") 134 | elif has_self_assignment(node): 135 | return (true, "set_self." & strVal(node[0][1])) 136 | elif has_self(node, global_ctx): 137 | return (true, "self." & strVal(node[1])) 138 | elif is_log_statement(node, global_ctx): 139 | return (true, "log." & strVal(node[0][1])) 140 | else: 141 | return (false, "") 142 | 143 | 144 | proc find_builtin_keywords(func_body: NimNode, used_keywords: var seq[string], global_ctx: GlobalContext) = 145 | 146 | for child in func_body: 147 | let (is_kw, kw_key_name) = is_keyword(child, global_ctx) 148 | 149 | var 150 | setter_kw = false 151 | exemption_list = @[ 152 | "set_" & kw_key_name, 153 | "set_table_" & kw_key_name, 154 | "get_table_" & kw_key_name, 155 | ] 156 | 157 | for proposed_setter_kw in exemption_list: 158 | if proposed_setter_kw in used_keywords: 159 | setter_kw = true 160 | if is_kw and not setter_kw: # Should be added to globals list. 161 | used_keywords.add(kw_key_name) 162 | # For infix make sure we have getter & setter functions. 163 | if is_kw and kw_key_name.startsWith("infix_set_table"): 164 | var var_name = kw_key_name.split('.')[^1] 165 | used_keywords.add("set_table_self." & var_name) 166 | used_keywords.add("get_table_self." & var_name) 167 | elif is_kw and kw_key_name.startsWith("infix_set_self"): 168 | var var_name = kw_key_name.split('.')[^1] 169 | used_keywords.add("set_self." & var_name) 170 | used_keywords.add("self." & var_name) 171 | find_builtin_keywords(child, used_keywords, global_ctx) 172 | 173 | 174 | proc generate_defines(keywords: seq[string], global_ctx: GlobalContext): (NimNode, Table[string, string]) = 175 | # Allocate keywords that do not alter their value 176 | # during execution of a function e.g. msg.sender, msg.value etc. 177 | var stmts = newStmtList() 178 | var tmp_vars = initTable[string, string]() 179 | if "msg.sender" in keywords: 180 | 181 | var 182 | tmp_var_name = "msg_sender_tmp_variable_alloc" 183 | tmp_var_node = newIdentNode(tmp_var_name) 184 | 185 | var s = quote do: 186 | var `tmp_var_node` {.noinit.}: address 187 | getCaller(addr `tmp_var_node`) 188 | 189 | stmts.add(s) 190 | tmp_vars["msg.sender"] = tmp_var_name 191 | 192 | if "msg.value" in keywords: 193 | var tmp_func_name = fmt"msg_value_func" 194 | stmts.add(parseStmt("proc " & tmp_func_name & """(): uint128 = 195 | var ba {.noinit.}: array[16, byte] 196 | getCallValue(addr ba) 197 | var val {.noinit.}: Stuint[128] 198 | {.pragma: restrict, codegenDecl: "$# __restrict $#".} 199 | let r_ptr {.restrict.} = cast[ptr array[128, byte]](addr val) 200 | for i, b in ba: 201 | r_ptr[i] = b 202 | return val 203 | """)) 204 | tmp_vars["msg.value"] = tmp_func_name 205 | 206 | for kw in keywords: 207 | if kw.startsWith("log."): 208 | var (new_proc, new_proc_name) = generate_log_func(kw, global_ctx) 209 | tmp_vars[kw] = new_proc_name 210 | stmts.add(new_proc) 211 | elif kw.startsWith("set_self."): 212 | var (new_proc, new_proc_name) = generate_storage_set_func(kw, global_ctx) 213 | tmp_vars[kw] = new_proc_name 214 | stmts.add(new_proc) 215 | elif kw.startsWith("set_table_self."): 216 | var (new_proc, new_proc_name) = generate_storage_table_set_func(kw, global_ctx) 217 | tmp_vars[kw] = new_proc_name 218 | stmts.add(new_proc) 219 | elif kw.startsWith("get_table_self."): 220 | var (new_proc, new_proc_name) = generate_storage_table_get_func(kw, global_ctx) 221 | tmp_vars[kw] = new_proc_name 222 | stmts.add(new_proc) 223 | elif kw.startsWith("self."): 224 | var (new_proc, new_proc_name) = generate_storage_get_func(kw, global_ctx) 225 | tmp_vars[kw] = new_proc_name 226 | stmts.add(new_proc) 227 | return (stmts, tmp_vars) 228 | 229 | 230 | proc check_keyword_defines(keywords_used: seq[string], local_ctx: LocalContext) = 231 | for keyword in keywords_used: 232 | if "infix_" in keyword: 233 | continue 234 | var base = keyword.replace("set_", "").replace("get_", "").replace("table_", "") 235 | if "." in base: 236 | base = base.split(".")[0] 237 | if not (base in local_ctx.sig.pragma_base_keywords): 238 | raiseParserError( 239 | fmt"Base Keyword {{.{base}.}} needs to be placed in the pragma of function '{local_ctx.sig.name}'.", 240 | local_ctx.sig.line_info 241 | ) 242 | 243 | 244 | proc get_keyword_defines*(proc_def: NimNode, global_ctx: GlobalContext, local_ctx: LocalContext): (NimNode, Table[string, string]) = 245 | var 246 | keywords_used: seq[string] 247 | find_builtin_keywords(proc_def, keywords_used, global_ctx) 248 | keywords_used = deduplicate(keywords_used) 249 | check_keyword_defines(keywords_used, local_ctx) 250 | let (global_define_stmts, global_keyword_map) = generate_defines(keywords_used, global_ctx) 251 | return (global_define_stmts, global_keyword_map) 252 | 253 | 254 | proc get_next_storage_node(kw_key_name: string, global_keyword_map: Table[string, string], current_node: NimNode): NimNode = 255 | var 256 | keys: seq[NimNode] 257 | 258 | if kw_key_name.startsWith("self."): 259 | return nnkCall.newTree( 260 | newIdentNode(global_keyword_map[kw_key_name]) 261 | ) 262 | elif kw_key_name.startsWith("set_self."): 263 | return nnkCall.newTree( 264 | newIdentNode(global_keyword_map[kw_key_name]), 265 | current_node[1] 266 | ) 267 | elif kw_key_name.startsWith("get_table_self."): 268 | var 269 | call_func = nnkCall.newTree( 270 | newIdentNode(global_keyword_map[kw_key_name]), 271 | ) 272 | extract_keys(current_node, keys) 273 | for param in keys[1..keys.len - 1]: 274 | call_func.add(param) 275 | return call_func 276 | elif kw_key_name.startsWith("set_table_self."): 277 | var 278 | call_func = nnkCall.newTree( 279 | newIdentNode(global_keyword_map[kw_key_name]), 280 | ) 281 | extract_keys(current_node[0], keys) 282 | for param in keys[1..^1]: 283 | call_func.add(param) 284 | call_func.add(current_node[1]) # val param 285 | return call_func 286 | elif kw_key_name.startsWith("infix"): 287 | # infix_set_table.balanceOf 288 | var 289 | set_call_func, get_call_func: NimNode 290 | tmp = kw_key_name.split(".") 291 | (infix_operator, var_name) = (tmp[1], tmp[2]) 292 | 293 | if kw_key_name.startsWith("infix_set_table"): 294 | set_call_func = nnkCall.newTree( 295 | newIdentNode(global_keyword_map["set_table_self." & var_name]), 296 | ) 297 | get_call_func = nnkCall.newTree( 298 | newIdentNode(global_keyword_map["get_table_self." & var_name]), 299 | ) 300 | # echo treeRepr(current_node) 301 | extract_keys(current_node[1], keys) 302 | # Get value. 303 | for param in keys[1..keys.len - 1]: 304 | get_call_func.add(param) 305 | set_call_func.add(param) 306 | 307 | elif kw_key_name.startsWith("infix_set_self"): 308 | set_call_func = nnkCall.newTree( 309 | newIdentNode(global_keyword_map["set_self." & var_name]), 310 | ) 311 | get_call_func = nnkCall.newTree( 312 | newIdentNode(global_keyword_map["self." & var_name]), 313 | ) 314 | else: 315 | raise newException(ParserError, "Unknown infix operation") 316 | 317 | var 318 | op_get = newNimNode(nnkInfix) 319 | op_get.add(newIdentNode(infix_operator)) 320 | op_get.add(get_call_func) 321 | op_get.add(current_node[2]) 322 | set_call_func.add(op_get) 323 | 324 | return set_call_func 325 | 326 | else: 327 | raise newException(ParserError, "Unknown global self keyword") 328 | 329 | 330 | proc replace_keywords*(ast_node: NimNode, global_keyword_map: Table[string, string], global_ctx: GlobalContext): NimNode = 331 | var 332 | TMP_VAR_KEYWORDS = @["msg.sender"] 333 | TMP_FUNC_KEYWORDS = @["msg.value"] 334 | 335 | var res_node = copyNimNode(ast_node) 336 | for child in ast_node: 337 | var next: NimNode 338 | let (is_kw, kw_key_name) = is_keyword(child, global_ctx) 339 | if is_kw: 340 | if kw_key_name.startsWith("log."): 341 | next = generate_next_call_log_node(kw_key_name, global_keyword_map, child) 342 | elif "self." in kw_key_name or kw_key_name.startsWith("infix_set_table"): 343 | next = get_next_storage_node(kw_key_name, global_keyword_map, child) 344 | elif kw_key_name in TMP_FUNC_KEYWORDS: 345 | next = nnkCall.newTree( 346 | newIdentNode(global_keyword_map[kw_key_name]) 347 | ) 348 | elif kw_key_name in TMP_VAR_KEYWORDS: 349 | next = newIdentNode(global_keyword_map[kw_key_name]) 350 | else: 351 | raise newException(ParserError, "No replacement specified for " & kw_key_name & " keyword") 352 | else: 353 | next = child 354 | res_node.add(replace_keywords(next, global_keyword_map, global_ctx)) 355 | return res_node 356 | -------------------------------------------------------------------------------- /nimplay/ee_runtime.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | 3 | {.push cdecl, importc.} 4 | proc eth2_loadPreStateRoot*(memoryOffset: pointer) 5 | proc eth2_blockDataSize*(): uint32 6 | proc eth2_blockDataCopy*(outputOfset: pointer, offset: uint32, length: uint32) 7 | proc eth2_savePostStateRoot*(memoryOffset: pointer) 8 | proc eth2_pushNewDeposit*(memoryOffset: pointer, length: uint32) 9 | proc debug_print32*(value: uint32) 10 | proc debug_print64*(value: uint64) 11 | proc debug_printMem*(offset: pointer, length: uint32) 12 | proc debug_printMemHex*(offset: pointer, length: uint32) 13 | proc debug_printStorage*(pathOffset: pointer) 14 | proc debug_printStorageHex*(pathOffset: pointer) 15 | 16 | {.pop.} 17 | 18 | 19 | proc debug_log*(s: string) = 20 | debug_printMem(cstring(s), s.len.uint32) 21 | 22 | 23 | macro exportwasm*(p: untyped): untyped = 24 | expectKind(p, nnkProcDef) 25 | result = p 26 | result.addPragma(newIdentNode("exportc")) 27 | result.addPragma( 28 | newColonExpr( 29 | newIdentNode("codegenDecl"), newLit("__attribute__ ((visibility (\"default\"))) $# $#$#") 30 | ) 31 | ) 32 | 33 | 34 | # eth2::loadPreStateRoot(memoryOffset: u32ptr) 35 | # The current pre_state_root (256-bit value) is loaded from the memory offset pointed at 36 | # eth2::blockDataSize() -> u32 37 | # Returns the size of the block.data 38 | # eth2::blockDataCopy(memoryOffset: u32ptr, offset: u32, length: u32) 39 | # Copies length bytes from block.data + offset to the memory offset 40 | # eth2::savePostStateRoot(memoryOffset: u32ptr) 41 | # The post_state_root (256-bit value) is set to the content of the memory offset pointed at 42 | # eth2::pushNewDeposit(memoryOffset: u32ptr, length: u32) 43 | # This expects a Deposit 2 data structure to be stored at the memory offset (SSZ serialised?). It will be appended to the deposit list. 44 | 45 | # (type (;0;) (func (param i32))) 46 | # (type (;1;) (func (param i32 i32))) 47 | # (type (;2;) (func (param i64))) 48 | # (type (;3;) (func (result i32))) 49 | # (type (;4;) (func)) 50 | # (type (;5;) (func (param i32 i32 i32) (result i32))) 51 | # (import "env" "eth2_loadPreStateRoot" (func $eth2_loadPreStateRoot (type 0))) 52 | # (import "env" "debug_printMem" (func $debug_printMem (type 1))) 53 | # (import "env" "debug_print32" (func $debug_print32 (type 0))) 54 | # (import "env" "debug_print64" (func $debug_print64 (type 2))) 55 | # (import "env" "debug_printMemHex" (func $debug_printMemHex (type 1))) 56 | # (import "env" "eth2_blockDataSize" (func $eth2_blockDataSize (type 3))) 57 | # (import "env" "eth2_pushNewDeposit" (func $eth2_pushNewDeposit (type 1))) 58 | # (import "env" "eth2_savePostStateRoot" (func $eth2_savePostStateRoot (type 0))) 59 | 60 | # pub fn eth2_loadPreStateRoot(offset: *const u32); 61 | # pub fn eth2_blockDataSize() -> u32; 62 | # pub fn eth2_blockDataCopy(outputOfset: *const u32, offset: u32, length: u32); 63 | # pub fn eth2_savePostStateRoot(offset: *const u32); 64 | # pub fn eth2_pushNewDeposit(offset: *const u32, length: u32); 65 | 66 | # pub fn debug_print32(value: u32); 67 | # pub fn debug_print64(value: u64); 68 | # pub fn debug_printMem(offset: *const u32, len: uint32) 69 | # pub fn debug_printMemHex(offset: *const u32, len: u32); 70 | # pub fn debug_printStorage(pathOffset: *const u32); 71 | # pub fn debug_printStorageHex(pathOffset: *const u32); 72 | -------------------------------------------------------------------------------- /nimplay/ewasm_runtime.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | 3 | import utils 4 | 5 | {.push cdecl, importc.} 6 | 7 | proc useGas*(amount: int64) 8 | ## Subtracts an amount to the gas counter 9 | ## 10 | ## Parameters: 11 | ## `amount` - the amount to subtract to the gas counter 12 | 13 | proc getAddress*(resultOffset: pointer) 14 | ## Gets address of currently executing account and stores it in memory at 15 | ## the given offset. 16 | ## 17 | ## Parameters: 18 | ## `resultOffset` the memory offset at which the address is to be stored (`address`) 19 | 20 | proc getExternalBalance*(addressOffset, resultOffset: pointer) 21 | ## Gets balance of the given account and loads it into memory at the given offset. 22 | ## 23 | ## Parameters: 24 | ## `addressOffset` the memory offset to load the address from (`address`) 25 | # `resultOffset` the memory offset to load the balance into (`u128`) 26 | 27 | proc getBlockHash*(number: int64, resultOffset: pointer) 28 | ## Gets the hash of one of the 256 most recent complete blocks. 29 | ## 30 | ## Parameters: 31 | ## `number` which block to load 32 | ## `resultOffset` the memory offset to load the hash into (`u256`) 33 | 34 | proc call*(gas: int64, addressOffset, valueOffset, dataOffset: pointer, 35 | dataLength: int32): int32 36 | ## Sends a message with arbitrary data to a given address path 37 | ## 38 | ## Parameters: 39 | ## `gas` **i64** the gas limit 40 | ## `addressOffset` the memory offset to load the address from (`address`) 41 | ## `valueOffset` the memory offset to load the value from (`u128`) 42 | ## `dataOffset` the memory offset to load data from (`bytes`) 43 | ## `dataLength` the length of data 44 | ## Returns: 45 | ## 0 on success, 1 on failure and 2 on `revert` 46 | proc call*(gas: int64, addressOffset, valueOffset, dataOffset: pointer, 47 | dataLength: int32, resultOffset: pointer, 48 | resultLength: int32): int32 49 | 50 | proc callDataCopy*(resultOffset: pointer, dataOffset, length: int32) 51 | ## Copies the input data in current environment to memory. This pertains to 52 | ## the input data passed with the message call instruction or transaction. 53 | ## 54 | ## Parameters: 55 | ## `resultOffset` the memory offset to load data into (`bytes`) 56 | ## `dataOffset` the offset in the input data 57 | ## `length` the length of data to copy 58 | 59 | proc getCallDataSize*(): int32 60 | ## Get size of input data in current environment. This pertains to the input 61 | ## data passed with the message call instruction or transaction. 62 | ## 63 | ## Returns: call data size 64 | 65 | proc callCode*(gas: int64, addressOffset, valueOffset, dataOffset: pointer, 66 | dataLength: int32): int32 67 | ## Message-call into this account with an alternative account's code. 68 | ## 69 | ## Parameters: 70 | ## `gas` the gas limit 71 | ## `addressOffset` the memory offset to load the address from (`address`) 72 | ## `valueOffset` the memory offset to load the value from (`u128`) 73 | ## `dataOffset` the memory offset to load data from (`bytes`) 74 | ## `dataLength` the length of data 75 | ## 76 | ## Returns: 0 on success, 1 on failure and 2 on `revert` 77 | 78 | proc callDelegate*(gas: int64, addressOffset, dataOffset: pointer, 79 | dataLength: int32) 80 | ## Message-call into this account with an alternative account’s code, but 81 | ## persisting the current values for sender and value. 82 | ## 83 | ## Parameters: 84 | ## `gas` the gas limit 85 | ## `addressOffset` the memory offset to load the address from (`address`) 86 | ## `dataOffset` the memory offset to load data from (`bytes`) 87 | ## `dataLength` the length of data 88 | ## 89 | ## Returns: 0 on success, 1 on failure and 2 on `revert` 90 | 91 | proc callStatic*(gas: int64, addressOffset, dataOffset: pointer, 92 | dataLength: int32) 93 | ## Sends a message with arbitrary data to a given address path, but disallow 94 | ## state modifications. This includes `log`, `create`, `selfdestruct` and `call` 95 | ## with a non-zero value. 96 | ## 97 | ## Parameters: 98 | ## `gas` the gas limit 99 | ## `addressOffset` the memory offset to load the address from (`address`) 100 | ## `dataOffset` the memory offset to load data from (`bytes`) 101 | ## `dataLength` the length of data 102 | ## 103 | ## Returns: 0 on success, 1 on failure and 2 on `revert` 104 | 105 | proc storageStore*(pathOffset, valueOffset: pointer) 106 | ## Store 256-bit a value in memory to persistent storage 107 | ## 108 | ## Parameters: 109 | ## `pathOffset` the memory offset to load the path from (`u256`) 110 | ## `valueOffset` the memory offset to load the value from (`u256`) 111 | 112 | proc storageLoad*(pathOffset, valueOffset: pointer) 113 | ## Loads a 256-bit a value to memory from persistent storage 114 | ## 115 | ## Parameters: 116 | ## `pathOffset` the memory offset to load the path from (`u256`) 117 | ## `resultOffset` the memory offset to store the result at (`u256`) 118 | 119 | proc getCaller*(resultOffset: pointer) 120 | ## Gets caller address and loads it into memory at the given offset. This is 121 | ## the address of the account that is directly responsible for this execution. 122 | ## 123 | ## Parameters: 124 | ## `resultOffset` the memory offset to load the address into (`address`) 125 | 126 | proc getCallValue*(resultOffset: pointer) 127 | ## Gets the deposited value by the instruction/transaction responsible for 128 | ## this execution and loads it into memory at the given location. 129 | ## 130 | ## Parameters: 131 | ## `resultOffset` the memory offset to load the value into (`u128`) 132 | 133 | proc codeCopy*(resultOffset: pointer, codeOffset, length: int32) 134 | ## Copies the code running in current environment to memory. 135 | ## 136 | ## Parameters: 137 | ## `resultOffset` the memory offset to load the result into (`bytes`) 138 | ## `codeOffset` the offset within the code 139 | ## `length` the length of code to copy 140 | 141 | proc getCodeSize*(): int32 142 | ## Gets the size of code running in current environment. 143 | 144 | proc getBlockCoinbase*(resultOffset: pointer) 145 | ## Gets the block’s beneficiary address and loads into memory. 146 | ## 147 | ## Parameters: 148 | ## `resultOffset` the memory offset to load the coinbase address into (`address`) 149 | 150 | proc create*(valueOffset, dataOffset: pointer, length: int32, resultOffset: pointer): int32 151 | ## Creates a new contract with a given value. 152 | ## 153 | ## Parameters: 154 | ## `valueOffset` the memory offset to load the value from (`u128`) 155 | ## `dataOffset` the memory offset to load the code for the new contract from (`bytes`) 156 | ## `length` the data length 157 | ## `resultOffset` the memory offset to write the new contract address to (`address`) 158 | ## 159 | ## Note: `create` will clear the return buffer in case of success or may fill 160 | ## it with data coming from `revert`. 161 | ## 162 | ## Returns: 0 on success, 1 on failure and 2 on `revert` 163 | 164 | proc getBlockDifficulty*(resultOffset: pointer) 165 | ## Get the block’s difficulty. 166 | ## 167 | ## Parameters: 168 | ## `resultOffset` the memory offset to load the difficulty into (`u256`) 169 | 170 | proc externalCodeCopy*(addressOffset, resultOffset: pointer, codeOffset, length: int32) 171 | ## Copies the code of an account to memory. 172 | ## 173 | ## Parameters: 174 | ## `addressOffset` the memory offset to load the address from (`address`) 175 | ## `resultOffset` the memory offset to load the result into (`bytes`) 176 | ## `codeOffset` the offset within the code 177 | ## `length` the length of code to copy 178 | 179 | proc getExternalCodeSize*(addressOffset: pointer): int32 180 | ## Get size of an account’s code. 181 | ## 182 | ## Parameters: 183 | ## `addressOffset` the memory offset to load the address from (`address`) 184 | 185 | proc getGasLeft*(): int64 186 | ## Returns the current gasCounter 187 | 188 | proc getBlockGasLimit*(): int64 189 | ## Get the block’s gas limit. 190 | 191 | proc getTxGasPrice*(valueOffset: pointer) 192 | ## Gets price of gas in current environment. 193 | ## 194 | ## Parameters: 195 | ## `valueOffset` the memory offset to write the value to (`u128`) 196 | 197 | proc log*(dataOffset: pointer, length, numberOfTopics: int32, 198 | topic1, topic2, topic3, topic4: pointer) 199 | ## Creates a new log in the current environment 200 | ## 201 | ## Parameters: 202 | ## `dataOffset` the memory offset to load data from (`bytes`) 203 | ## `length` the data length 204 | ## `numberOfTopics` the number of topics following (0 to 4) 205 | ## `topic1` the memory offset to load topic1 from (`u256`) 206 | ## `topic2` the memory offset to load topic2 from (`u256`) 207 | ## `topic3` the memory offset to load topic3 from (`u256`) 208 | ## `topic4` the memory offset to load topic4 from (`u256`) 209 | 210 | proc getBlockNumber*(): int64 211 | ## Get the block’s number. 212 | 213 | proc getTxOrigin*(resultOffset: pointer) 214 | ## Gets the execution's origination address and loads it into memory at the 215 | ## given offset. This is the sender of original transaction; it is never an 216 | ## account with non-empty associated code. 217 | ## 218 | ## Parameters: 219 | ## `resultOffset` the memory offset to load the origin address from (`address`) 220 | 221 | proc finish*(dataOffset: pointer, length: int32) {.noreturn.} 222 | ## Set the returning output data for the execution. This will cause a trap and 223 | ## the execution will be aborted immediately. 224 | ## 225 | ## Note: multiple invocations will overwrite the previous data. 226 | ## 227 | ## Parameters: 228 | ## `dataOffset` the memory offset of the output data (`bytes`) 229 | ## `length` the length of the output data 230 | 231 | proc revert*(dataOffset: pointer, length: int32) {.noreturn.} 232 | ## Set the returning output data for the execution. This will cause a trap and 233 | ## the execution will be aborted immediately. 234 | ## 235 | ## Note: multiple invocations will overwrite the previous data. 236 | ## 237 | ## Parameters: 238 | ## `dataOffset` the memory offset of the output data (`bytes`) 239 | ## `length` the length of the output data 240 | 241 | proc getReturnDataSize*(): int32 242 | ## Get size of current return data buffer to memory. This contains the return 243 | ## data from the last executed `call`, `callCode`, `callDelegate`, `callStatic` 244 | ## or `create`. 245 | ## 246 | ## Note: `create` only fills the return data buffer in case of a failure. 247 | 248 | proc returnDataCopy*(resultOffset: pointer, dataOffset, length: int32) 249 | ## Copies the current return data buffer to memory. This contains the return data 250 | ## from last executed `call`, `callCode`, `callDelegate`, `callStatic` or `create`. 251 | ## 252 | ## Note: `create` only fills the return data buffer in case of a failure. 253 | ## 254 | ## Parameters: 255 | ## `resultOffset` the memory offset to load data into (`bytes`) 256 | ## `dataOffset` the offset in the return data 257 | ## `length` the length of data to copy 258 | 259 | proc selfDestruct*(addressOffset: pointer) 260 | ## Mark account for later deletion and give the remaining balance to the specified 261 | ## beneficiary address. This will cause a trap and the execution will be aborted 262 | ## immediately. 263 | ## 264 | ## Parameters: 265 | ## `addressOffset` the memory offset to load the address from (`address`) 266 | 267 | proc getBlockTimestamp*(): int64 268 | ## Get the block’s timestamp. 269 | 270 | {.pop.} 271 | 272 | proc callDataCopy*[T](res: var T, offset: int) {.inline.} = 273 | callDataCopy(addr res, offset.int32, sizeof(res).int32) 274 | 275 | proc storageLoad*[N](path: array[N, byte], res: pointer) {.inline.} = 276 | when path.len < 32: 277 | type PaddedPath {.packed.} = object 278 | padding: array[32 - path.len, byte] 279 | p: array[N, byte] 280 | var p: PaddedPath 281 | p.p = path 282 | storageLoad(addr p, res) 283 | else: 284 | storageLoad(unsafeAddr path[0], res) 285 | 286 | proc storageStore*[N](path: array[N, byte], res: pointer) {.inline.} = 287 | when path.len < 32: 288 | type PaddedPath {.packed.} = object 289 | padding: array[32 - path.len, byte] 290 | p: array[N, byte] 291 | var p: PaddedPath 292 | p.p = path 293 | storageStore(addr p, res) 294 | else: 295 | storageStore(unsafeAddr path[0], res) 296 | 297 | macro exportwasm*(p: untyped): untyped = 298 | expectKind(p, nnkProcDef) 299 | result = p 300 | result.addPragma(newIdentNode("exportc")) 301 | result.addPragma( 302 | newColonExpr( 303 | newIdentNode("codegenDecl"), newLit("__attribute__ ((visibility (\"default\"))) $# $#$#") 304 | ) 305 | ) 306 | -------------------------------------------------------------------------------- /nimplay/function_signature.nim: -------------------------------------------------------------------------------- 1 | import macros, strutils, stint, strformat 2 | 3 | import ./utils, ./types 4 | 5 | 6 | template generate_method_sig*(func_sig: untyped, v2_sig: bool = false): string = 7 | var inputs: seq[string] 8 | for input in func_sig.inputs: 9 | inputs.add(input.var_type) 10 | var method_str = func_sig.name & "(" & join(inputs, ",") & ")" 11 | if v2_sig and len(func_sig.outputs) > 0: 12 | var outputs: seq[string] 13 | for output in func_sig.outputs: 14 | outputs.add(output.var_type) 15 | method_str = method_str & ":(" & join(outputs, ",") & ")" 16 | method_str 17 | 18 | 19 | proc getKHash*(inp: string): string {.compileTime.} = 20 | when defined(osk256_sig): 21 | let exec_string = "/usr/bin/k256_sig \"" & inp & "\"" 22 | else: 23 | let exec_string = "../tools/k256_sig \"" & inp & "\"" 24 | let outp_shell = staticExec(exec_string) 25 | return outp_shell 26 | 27 | 28 | proc generate_method_id*(func_sig: FunctionSignature): string = 29 | # var method_str = generate_method_sig(func_sig) 30 | # echo method_str 31 | # return keccak_256.digest(method_str).data 32 | return getKHash(generate_method_sig(func_sig))[0..7] 33 | 34 | 35 | proc generate_function_signature*(proc_def: NimNode, global_ctx: GlobalContext): FunctionSignature = 36 | expectKind(proc_def, nnkProcDef) 37 | 38 | var func_sig = FunctionSignature() 39 | for child in proc_def: 40 | case child.kind: 41 | of nnkIdent: 42 | func_sig.name = strVal(child) 43 | func_sig.is_private = true 44 | of nnkPostfix: 45 | func_sig.name = strVal(child[1]) 46 | func_sig.is_private = false 47 | of nnkFormalParams: 48 | for param in child: 49 | case param.kind 50 | of nnkIdent: 51 | func_sig.outputs.add( 52 | VariableType( 53 | name: "out1", 54 | var_type: strVal(param) 55 | ) 56 | ) 57 | of nnkIdentDefs: 58 | check_valid_variable_name(param[0], global_ctx) 59 | func_sig.inputs.add( 60 | VariableType( 61 | name: strVal(param[0]), 62 | var_type: strVal(param[1]) 63 | ) 64 | ) 65 | of nnkEmpty: 66 | discard 67 | else: 68 | raise newException(Exception, "unknown param type" & treeRepr(child)) 69 | of nnkPragma: 70 | for pragma_child in child: 71 | if pragma_child.kind == nnkIdent: 72 | var pragma_name = strVal(pragma_child) 73 | # Add pragma to list of pragmas 74 | if not (pragma_name in ALLOWED_PRAGMAS): 75 | raiseParserError( 76 | fmt"Unsupported pragma: {pragma_name}, must be one of " & ALLOWED_PRAGMAS.join(","), 77 | child 78 | ) 79 | func_sig.pragma_base_keywords.add(pragma_name) 80 | if pragma_name == "payable": 81 | func_sig.payable = true 82 | else: 83 | discard 84 | # raise newException(Exception, "unknown func type" & treeRepr(child)) 85 | 86 | func_sig.method_sig = generate_method_sig(func_sig) 87 | func_sig.method_id = generate_method_id(func_sig) 88 | func_sig.line_info = lineInfoObj(proc_def) 89 | 90 | return func_sig 91 | 92 | 93 | proc strip_pragmas*(proc_def: NimNode): NimNode = 94 | var new_proc_def = copyNimNode(proc_def) 95 | for c in proc_def: 96 | if c.kind != nnkPragma: 97 | new_proc_def.add(copyNimTree(c)) 98 | else: 99 | new_proc_def.add(newEmptyNode()) 100 | return new_proc_def 101 | 102 | -------------------------------------------------------------------------------- /nimplay/logging.nim: -------------------------------------------------------------------------------- 1 | import 2 | macros, strutils, strformat, tables 3 | 4 | import 5 | ./types, ./utils, ./function_signature 6 | 7 | 8 | proc get_buffer_sizes(event_sig: EventSignature): (int, int) = 9 | var 10 | data_total_size = 0 11 | indexed_total_size = 32 # log signature always place in topic 1 12 | for event in event_sig.inputs: 13 | if event.indexed: 14 | indexed_total_size += get_byte_size_of(event.var_type) 15 | else: 16 | data_total_size += get_byte_size_of(event.var_type) 17 | (data_total_size, indexed_total_size) # return 18 | 19 | 20 | proc get_converter_ident(param_name, param_type: string): (int, string) = 21 | case param_type: 22 | of "uint256": 23 | (0, param_name & ".toByteArrayBE") 24 | of "uint128": 25 | (16, param_name & ".toByteArrayBE") 26 | of "address": 27 | (32 - 20, param_name) 28 | else: 29 | (0, param_name) 30 | 31 | 32 | proc get_indexed_param_locations(event_sig: EventSignature): seq[int] = 33 | var 34 | locations: seq[int] 35 | i = 0 36 | for event_param in event_sig.inputs: 37 | if event_param.indexed: 38 | locations.add(event_param.param_position) 39 | i += 1 40 | locations # return 41 | 42 | 43 | proc get_param_copiers(event_sig: EventSignature, buffer_name: string, indexed: bool, start_position: int = 0): (NimNode, Table[string, int]) = 44 | var 45 | copy_stmts = newStmtList() 46 | current_abi_offset = start_position 47 | position_map: Table[string, int] 48 | for event_param in event_sig.inputs: 49 | if event_param.indexed == indexed: 50 | var (type_offset, converted_param_name) = get_converter_ident(event_param.name, event_param.var_type) 51 | var abi_bytearr_offset = current_abi_offset + type_offset 52 | position_map[event_param.name] = current_abi_offset 53 | copy_stmts.add( 54 | parseStmt( 55 | fmt""" 56 | copy_into_ba({buffer_name}, {abi_bytearr_offset}, {converted_param_name}) 57 | """) 58 | ) 59 | current_abi_offset += get_byte_size_of(event_param.var_type) 60 | (copy_stmts, position_map) 61 | 62 | 63 | proc get_new_proc_def(event_sig: EventSignature): NimNode = 64 | # copyNimTree(func_def) 65 | var 66 | param_list: seq[string] 67 | for param in event_sig.inputs: 68 | param_list.add(fmt"{param.name}:{param.var_type}") 69 | var 70 | param_str = param_list.join(";") 71 | new_proc = parseExpr( 72 | fmt""" 73 | proc {event_sig.name} ({param_str}) = 74 | discard 75 | """ 76 | ) 77 | new_proc 78 | 79 | 80 | proc generate_log_func*(log_keyword: string, global_ctx: GlobalContext): (NimNode, string) = 81 | # To make a log statement we allocate a chunk of data memory. 82 | # This buffer contains: 83 | # 0-32 contains the 32 byte keccak, this also becomes topic 1 (indexed). 84 | # 32-N contains the rest of the data portion, the data portion is not indexed. 85 | var 86 | event_name = log_keyword.split(".")[1] 87 | new_proc_name = fmt"log_{event_name}" 88 | event_sig = global_ctx.events[event_name] 89 | func_def = event_sig.definition 90 | event_func_def = get_new_proc_def(event_sig) 91 | (data_total_size, indexed_total_size) = get_buffer_sizes(event_sig) 92 | event_id_int_arr = hexstr_to_intarray[32](getKHash(generate_method_sig(event_sig))) 93 | data_buffer_name = "data_output_buffer" 94 | indexed_buffer_name = "indexed_output_buffer" 95 | indexed_param_locations = get_indexed_param_locations(event_sig) 96 | 97 | # Create function definition. 98 | 99 | event_func_def[0] = newIdentNode(new_proc_name) 100 | event_func_def[4] = newEmptyNode() 101 | # Create data section buffer. 102 | event_func_def[6] = parseStmt( 103 | fmt""" 104 | var 105 | {data_buffer_name} {{.noinit.}}: array[{data_total_size}, byte] 106 | {indexed_buffer_name} {{.noinit.}}: array[{indexed_total_size}, byte] 107 | """ 108 | ) 109 | 110 | var 111 | event_sig_int_arr = nnkBracket.newTree() 112 | for x in event_id_int_arr: 113 | event_sig_int_arr.add(parseExpr(intToStr(x) & "'u8")) 114 | event_func_def[6].add( 115 | newCommentStmtNode("Set event signature") 116 | ) 117 | event_func_def[6].add( 118 | nnkAsgn.newTree( 119 | nnkBracketExpr.newTree( 120 | newIdentNode(indexed_buffer_name), 121 | nnkInfix.newTree( 122 | newIdentNode(".."), 123 | newLit(0), 124 | newLit(31) 125 | ) 126 | ), 127 | event_sig_int_arr 128 | ) 129 | ) 130 | 131 | let 132 | # Copy data into memory 133 | (data_copiers, _) = get_param_copiers( 134 | event_sig, 135 | data_buffer_name, 136 | indexed=false 137 | ) 138 | # Copy indexed data into memory 139 | (indexed_copiers, indexed_position_maps) = get_param_copiers( 140 | event_sig, 141 | indexed_buffer_name, 142 | indexed=true, 143 | start_position=32 144 | ) 145 | event_func_def[6].add( 146 | data_copiers 147 | ) 148 | event_func_def[6].add( 149 | indexed_copiers 150 | ) 151 | # Create topic parameters (1-3, after indexed function sig). 152 | var 153 | topic_list: seq[string] 154 | topic_count = 0 155 | for i, param in event_sig.inputs: 156 | if param.indexed: 157 | topic_list.add(fmt"addr {indexed_buffer_name}[{intToStr(indexed_position_maps[param.name])}]") 158 | topic_count += 1 159 | # Add nils 160 | for _ in 1..(3 - len(topic_list)): 161 | topic_list.add("nil") 162 | # Call Log 163 | event_func_def[6].add( 164 | parseStmt( 165 | fmt""" 166 | log(addr {data_buffer_name}[0], {data_total_size}.int32, {1 + topic_count}.int32, addr {indexed_buffer_name}[0], {topic_list.join(",")}) 167 | """ 168 | ) 169 | ) 170 | return (event_func_def, new_proc_name) 171 | 172 | 173 | proc generate_next_call_log_node*(kw_key_name: string, global_keyword_map: Table[string, string], current_node: NimNode): NimNode = 174 | if kw_key_name.startsWith("log."): 175 | let event_func_name = kw_key_name.split(".")[1] 176 | var new_call = copyNimTree(current_node) 177 | new_call[0] = newIdentNode(global_keyword_map[kw_key_name]) 178 | return new_call 179 | else: 180 | raise newException(ParserError, fmt"Unknown '{kw_key_name}' log keyword supplied") 181 | -------------------------------------------------------------------------------- /nimplay/nimplay_macros.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | import stint 3 | import system 4 | import strformat 5 | import tables 6 | import endians 7 | import sequtils 8 | import strutils 9 | 10 | import ./function_signature, ./builtin_keywords, ./types, ./utils 11 | 12 | 13 | proc get_func_name(proc_def: NimNode): string = 14 | var func_name = "" 15 | for child in proc_def: 16 | if child.kind == nnkPostfix: 17 | func_name = strVal(child[1]) 18 | break 19 | if child.kind == nnkIdent: 20 | func_name = strVal(child) 21 | break 22 | return func_name 23 | 24 | 25 | proc get_local_input_type_conversion(tmp_var_name, tmp_var_converted_name, var_type: string): (NimNode, NimNode) = 26 | case var_type 27 | of "uint256", "uint128", "wei_value": 28 | var base_stint_type = "Uint256" 29 | if var_type in @["uint128", "wei_value"]: 30 | base_stint_type = "UInt128" 31 | var convert_node = nnkLetSection.newTree( 32 | nnkIdentDefs.newTree( 33 | newIdentNode(tmp_var_converted_name), 34 | newIdentNode(var_type), 35 | nnkCall.newTree( 36 | nnkDotExpr.newTree( 37 | newIdentNode(base_stint_type), 38 | newIdentNode("fromBytesBE") 39 | ), 40 | newIdentNode(tmp_var_name) 41 | ) 42 | ) 43 | ) 44 | var ident_node = newIdentNode(tmp_var_converted_name) 45 | return (ident_node, convert_node) 46 | of "bytes32": 47 | return (newIdentNode(tmp_var_name), newEmptyNode()) 48 | of "address": 49 | var convert_node = parseStmt(unindent(fmt""" 50 | var {tmp_var_converted_name}: address 51 | copy_into_ba({tmp_var_converted_name}, 12, {tmp_var_name}) 52 | """ 53 | )) 54 | return (newIdentNode(tmp_var_converted_name), convert_node) 55 | else: 56 | raise newException(ParserError, fmt"get_local_input_type_conversion: Unknown '{var_type}' type supplied!") 57 | 58 | 59 | proc get_local_output_type_conversion(tmp_result_name, tmp_result_converted_name, var_type: string): (NimNode, NimNode) = 60 | case var_type 61 | of "bool": 62 | var ident_node = newIdentNode(tmp_result_converted_name) 63 | var conversion_node = parseStmt(unindent(fmt""" 64 | var {tmp_result_converted_name}: array[32, byte] 65 | if {tmp_result_name}: {tmp_result_converted_name}[31] = 1 66 | """)) 67 | return (ident_node, conversion_node) 68 | of "uint256": 69 | var ident_node = newIdentNode(tmp_result_converted_name) 70 | var conversion_node = nnkVarSection.newTree( 71 | nnkIdentDefs.newTree( 72 | newIdentNode(tmp_result_converted_name), 73 | newEmptyNode(), 74 | nnkDotExpr.newTree( 75 | newIdentNode(tmp_result_name), 76 | newIdentNode("toByteArrayBE") 77 | ) 78 | ) 79 | ) 80 | return (ident_node, conversion_node) 81 | of "uint128", "wei_value": 82 | var ident_node = newIdentNode(tmp_result_converted_name) 83 | var conversion_node = parseStmt(unindent(fmt""" 84 | var {tmp_result_converted_name} {{.noinit.}}: array[32, byte] 85 | {tmp_result_converted_name}[16..31] = toByteArrayBE({tmp_result_name}) 86 | """)) 87 | return (ident_node, conversion_node) 88 | of "address": 89 | var ident_node = newIdentNode(tmp_result_converted_name) 90 | var conversion_node = nnkStmtList.newTree( 91 | nnkVarSection.newTree( # var a: array[32, byte] 92 | nnkIdentDefs.newTree( 93 | newIdentNode(tmp_result_converted_name), 94 | nnkBracketExpr.newTree( 95 | newIdentNode("array"), 96 | newLit(32), 97 | newIdentNode("byte"), 98 | ), 99 | newEmptyNode() 100 | ) 101 | ), 102 | nnkAsgn.newTree( # a[11..31] = tmp_addr 103 | nnkBracketExpr.newTree( 104 | newIdentNode(tmp_result_converted_name), 105 | nnkInfix.newTree( 106 | newIdentNode(".."), 107 | newLit(12), 108 | newLit(31) 109 | ) 110 | ), 111 | newIdentNode(tmp_result_name) 112 | ) 113 | ) 114 | return (ident_node, conversion_node) 115 | # return (newIdentNode(tmp_result_name), newEmptyNode()) 116 | of "bytes32": 117 | return (newIdentNode(tmp_result_name), newEmptyNode()) 118 | else: 119 | raise newException(ParserError, fmt"Unknown '{var_type}' type supplied!") 120 | 121 | 122 | proc generate_context(proc_def: NimNode, global_ctx: GlobalContext): LocalContext = 123 | var ctx = LocalContext() 124 | ctx.name = get_func_name(proc_def) 125 | ctx.sig = generate_function_signature(proc_def, global_ctx) 126 | (ctx.keyword_define_stmts, ctx.global_keyword_map) = get_keyword_defines(proc_def, global_ctx, ctx) 127 | return ctx 128 | 129 | proc handle_global_storage_tables(table_def: NimNode, global_ctx :var GlobalContext, slot_number: var int) = 130 | expectKind(table_def, nnkCall) 131 | var 132 | key_types: seq[string] 133 | var_name = strVal(table_def[0]) 134 | var_types = table_def[1][0] 135 | 136 | for x in var_types[1..var_types.len - 1]: 137 | key_types.add(strVal(x)) 138 | 139 | var var_struct = VariableType( 140 | name: var_name, 141 | var_type: "StorageTable", 142 | key_types: key_types, 143 | slot: slot_number 144 | ) 145 | global_ctx.global_variables[var_name] = var_struct 146 | inc(slot_number) 147 | 148 | 149 | proc handle_global_defines(var_section: NimNode, global_ctx :var GlobalContext, slot_number: var int) = 150 | 151 | for child in var_section: 152 | case child.kind 153 | of nnkIdentDefs: 154 | var create_getter = false 155 | if (child[0].kind, child[1].kind) == (nnkPostfix, nnkIdent) and strVal(child[0][0]) == "*": 156 | # create getter. 157 | create_getter = true 158 | elif (child[0].kind, child[1].kind) != (nnkIdent, nnkIdent): 159 | raiseParserError( 160 | "Global variables need to be defined as 'var_name: var_type'", 161 | child 162 | ) 163 | 164 | var 165 | var_node = if create_getter: child[0][1] else: child[0] 166 | var_name = strVal(var_node) 167 | var_type = strVal(child[1]) 168 | 169 | check_valid_variable_name(var_node, global_ctx) 170 | # echo var_name & " -> " & var_type 171 | if var_name in global_ctx.global_variables: 172 | raiseParserError( 173 | fmt"Global variable '{var_name}' has already been defined", 174 | child 175 | ) 176 | var var_struct = VariableType( 177 | name: var_name, 178 | var_type: var_type, 179 | slot: slot_number 180 | ) 181 | if create_getter: 182 | global_ctx.getter_funcs.add(var_struct) 183 | global_ctx.global_variables[var_name] = var_struct 184 | inc(slot_number) 185 | else: 186 | raiseParserError( 187 | fmt"Unsupported statement in global var section", 188 | child 189 | ) 190 | 191 | 192 | proc handle_event_defines(event_def: NimNode, global_ctx: var GlobalContext) = 193 | expectKind(event_def, nnkProcDef) 194 | if event_def[6].kind != nnkEmpty: 195 | raiseParserError("Event definition expect no function bodies", event_def) 196 | if not (event_def[4].kind == nnkPragma and strVal(event_def[4][0]) == "event"): 197 | raiseParserError("Events require event pragma e.g. 'proc EventName(a: uint256) {.event.}'", event_def) 198 | 199 | var event_name = strVal(event_def[0]) 200 | if event_name in global_ctx.events: 201 | raiseParserError(fmt"Event '{event_name}' has already been defined.", event_def) 202 | var 203 | event_sig = EventSignature( 204 | name: event_name, 205 | definition: event_def 206 | ) 207 | param_position = 0 208 | 209 | for child in event_def: 210 | if child.kind == nnkFormalParams: 211 | var params = child 212 | for param in params: 213 | if param.kind == nnkIdentDefs: 214 | if param[0].kind == nnkPragmaExpr and param[0][1].kind == nnkPragma: 215 | var 216 | pragma_expr = param[0][1][0] 217 | if pragma_expr.kind != nnkIdent or strVal(pragma_expr) != "indexed": 218 | raiseParserError("Unsupported pragma", pragma_expr) 219 | event_sig.inputs.add(EventType( 220 | name: strVal(param[0][0]), 221 | var_type: strVal(param[1]), 222 | indexed: true, 223 | param_position: param_position 224 | )) 225 | elif param[0].kind == nnkIdent: 226 | check_valid_variable_name(param[0], global_ctx) 227 | var param_name = strVal(param[0]) 228 | event_sig.inputs.add(EventType( 229 | name: param_name, 230 | var_type: strVal(param[1]), 231 | indexed: false, 232 | param_position: param_position 233 | )) 234 | else: 235 | raiseParserError("Unsupported event parameter type", params) 236 | param_position += 1 237 | if len(event_sig.inputs) == 0: 238 | raiseParserError("Event requires parameters", event_def) 239 | if filter(event_sig.inputs, proc(x: EventType): bool = x.indexed).len > 3: 240 | raiseParserError("Can only have 3 indexed parameters", event_def) 241 | global_ctx.events[event_name] = event_sig 242 | 243 | 244 | template get_util_functions() {.dirty.} = 245 | proc copy_into_ba(to_ba: var auto, offset: int, from_ba: auto) = 246 | for i, x in from_ba: 247 | if offset + i > sizeof(to_ba) - 1: 248 | break 249 | to_ba[offset + i] = x 250 | 251 | func to_bytes32(a: auto): bytes32 = 252 | var 253 | tmp: bytes32 254 | copy_into_ba(tmp, 0, a.toBytesLE) 255 | tmp 256 | 257 | func to_uint128(a: bytes32): uint128 = 258 | var 259 | tmp_ba {.noinit.}: array[32, byte] 260 | tmp: uint128 261 | copy_into_ba(tmp_ba, 0, a) 262 | tmp = fromBytes(Uint128, tmp_ba) 263 | tmp 264 | 265 | func to_uint256(a: bytes32): uint256 = 266 | var 267 | tmp_ba {.noinit.}: array[32, byte] 268 | tmp: uint256 269 | copy_into_ba(tmp_ba, 0, a) 270 | tmp = fromBytes(Uint256, tmp_ba) 271 | tmp 272 | 273 | proc assertNotPayable() = 274 | var b {.noinit.}: array[16, byte] 275 | getCallValue(addr b) 276 | if Uint128.fromBytesBE(b) > 0.stuint(128): 277 | revert(nil, 0) 278 | 279 | proc exitMessage(s: string) = 280 | revert(cstring(s), s.len.int32) 281 | 282 | proc concat[I1, I2: static[int]; T](a: array[I1, T], b: array[I2, T]): array[I1 + I2, T] = 283 | result[0..a.high] = a 284 | result[a.len..result.high] = b 285 | 286 | proc getHashedKey[N](table_id: int32, key: array[N, byte]): bytes32 = 287 | type CombinedKey {.packed.} = object 288 | table_id: int32 289 | key: array[N, byte] 290 | var 291 | hashed_key {.noinit.}: bytes32 292 | combined_key: CombinedKey 293 | sha256_address: array[20, byte] 294 | 295 | sha256_address[19] = 2'u8 296 | combined_key.table_id = table_id 297 | combined_key.key = key 298 | 299 | var res = call( 300 | getGasLeft(), # gas 301 | addr sha256_address, # addressOffset (ptr) 302 | nil, # valueOffset (ptr) 303 | addr combined_key, # dataOffset (ptr) 304 | sizeof(combined_key).int32, # dataLength 305 | ) 306 | if res == 1: # call failed 307 | exitMessage("Could not call sha256 in setTableValue") 308 | # raise newException(Exception, "Could not call sha256 in setTableValue") 309 | if getReturnDataSize() != 32.int32: 310 | exitMessage("Could not call sha256, Incorrect return size") 311 | returnDataCopy(addr hashed_key, 0.int32, 32.int32) 312 | 313 | hashed_key # return 314 | 315 | proc setTableValue[N](table_id: int32, key: array[N, byte], value: var bytes32) = 316 | var hashed_key = getHashedKey(table_id, key) 317 | storageStore(hashed_key, addr value) 318 | 319 | proc getTableValue[N](table_id: int32, key: array[N, byte], ): bytes32 = 320 | var 321 | value: bytes32 322 | hashed_key: bytes32 = getHashedKey(table_id, key) 323 | storageLoad(hashed_key, addr value) 324 | value # return 325 | 326 | 327 | proc get_getter_func(var_struct: VariableType): NimNode = 328 | let 329 | proc_name_node = newIdentNode(var_struct.name) 330 | proc_return_type_node = newIdentNode(var_struct.var_type) 331 | storage_name_node = newIdentNode(var_struct.name) 332 | quote do: 333 | proc `proc_name_node`*():`proc_return_type_node` {.self.} = ## generated getter 334 | self.`storage_name_node` 335 | 336 | 337 | proc check_default_func(sig: FunctionSignature, global_ctx: GlobalContext, child: NimNode) = 338 | ## Check default function is correctly defined. 339 | if global_ctx.has_default_func: 340 | raiseParserError("Only one default function may be defined per Contract.", child) 341 | if sig.is_private: 342 | raiseParserError("Default function has to be public and payable.", child) 343 | if sig.inputs.len > 0 or sig.outputs.len > 0: 344 | raiseParserError("Default function can not have arguments nor return a value.", child) 345 | 346 | 347 | proc handle_contract_interface(in_stmts: NimNode): NimNode = 348 | var 349 | main_out_stmts = newStmtList() 350 | function_signatures = newSeq[FunctionSignature]() 351 | global_ctx = GlobalContext() 352 | 353 | main_out_stmts.add(getAst(get_util_functions())) 354 | # var util_funcs = get_util_functions() 355 | # discard util_funcs 356 | var slot_number = 0 357 | for child in in_stmts: 358 | if child.kind == nnkCall and child[1].kind == nnkStmtList and child[1][0].kind == nnkBracketExpr: 359 | if strVal(child[1][0][0]) == "StorageTable": 360 | handle_global_storage_tables(child, global_ctx, slot_number) 361 | else: 362 | raiseParserError("Unsupported global definition.", child) 363 | if child.kind == nnkVarSection: 364 | handle_global_defines(child, global_ctx, slot_number) 365 | 366 | # Inject getter functions if needed. 367 | if global_ctx.getter_funcs.len > 0: 368 | for var_struct in global_ctx.getter_funcs: 369 | in_stmts.add(get_getter_func(var_struct)) 370 | 371 | for child in in_stmts: 372 | case child.kind: 373 | of nnkProcDef: 374 | if child[6].kind == nnkEmpty: # Event definition. 375 | handle_event_defines(child, global_ctx) 376 | continue 377 | var ctx = generate_context(child, global_ctx) 378 | if ctx.sig.name == "default": 379 | check_default_func(ctx.sig, global_ctx, child) 380 | global_ctx.has_default_func = true 381 | function_signatures.add(ctx.sig) 382 | var new_proc_def = strip_pragmas(child) 383 | new_proc_def = replace_keywords( 384 | ast_node=new_proc_def, 385 | global_keyword_map=ctx.global_keyword_map, 386 | global_ctx=global_ctx 387 | ) 388 | # Insert global defines. 389 | new_proc_def[6].insert(0, ctx.keyword_define_stmts) 390 | main_out_stmts.add(new_proc_def) 391 | else: 392 | discard 393 | 394 | if filter(function_signatures, proc(x: FunctionSignature): bool = not x.is_private).len == 0: 395 | raise newException( 396 | ParserError, 397 | "No public functions have been defined, use * postfix to annotate public functions. e.g. proc myfunc*(a: uint256)" 398 | ) 399 | 400 | # Build Main Entrypoint. 401 | var out_stmts = newStmtList() 402 | out_stmts.add( 403 | nnkVarSection.newTree( # var selector: uint32 404 | nnkIdentDefs.newTree( 405 | newIdentNode("selector"), 406 | newIdentNode("uint32"), 407 | newEmptyNode() 408 | ) 409 | ), 410 | nnkCall.newTree( # callDataCopy(selector, 0) 411 | newIdentNode("callDataCopy"), 412 | newIdentNode("selector"), 413 | newLit(0) 414 | ), 415 | ) 416 | 417 | # Convert selector. 418 | out_stmts.add( 419 | nnkStmtList.newTree( 420 | newCall( 421 | bindSym"bigEndian32", 422 | nnkCommand.newTree( 423 | newIdentNode("addr"), 424 | newIdentNode("selector") 425 | ), 426 | nnkCommand.newTree( 427 | newIdentNode("addr"), 428 | newIdentNode("selector") 429 | ) 430 | ) 431 | ) 432 | ) 433 | 434 | var selector_CaseStmt = nnkCaseStmt.newTree( 435 | newIdentNode("selector") 436 | ) 437 | 438 | # Build function selector. 439 | for func_sig in function_signatures: 440 | if func_sig.is_private: 441 | continue 442 | if func_sig.name == "default" : # default function should have a selector. 443 | continue 444 | echo "Building " & func_sig.method_sig 445 | var call_and_copy_block = nnkStmtList.newTree() 446 | var call_to_func = nnkCall.newTree( 447 | newIdentNode(func_sig.name) 448 | ) 449 | var start_offset = 4 450 | 451 | if not func_sig.payable: 452 | call_and_copy_block.add(parseStmt("assertNotPayable()")) 453 | 454 | for idx, param in func_sig.inputs: 455 | var 456 | static_param_size = get_byte_size_of(param.var_type) 457 | tmp_var_name = fmt"{func_sig.name}_param_{idx}" 458 | tmp_var_converted_name = fmt"{func_sig.name}_param_{idx}_converted" 459 | # var : 460 | # callDataCopy(addr , , ) 461 | call_and_copy_block.add( 462 | parseStmt(unindent(fmt""" 463 | var {tmp_var_name} {{.noinit.}}: array[{static_param_size}, byte] 464 | callDataCopy(addr {tmp_var_name}, {start_offset}, {static_param_size}) 465 | """) 466 | ) 467 | ) 468 | 469 | # Get conversion code if necessary. 470 | let (ident_node, convert_node) = get_local_input_type_conversion( 471 | tmp_var_name, 472 | tmp_var_converted_name, 473 | param.var_type 474 | ) 475 | if not (ident_node.kind == nnkEmpty): 476 | if not (convert_node.kind == nnkEmpty): 477 | call_and_copy_block.add(convert_node) 478 | call_to_func.add(ident_node) 479 | start_offset += static_param_size 480 | 481 | # Handle returned data from function. 482 | if len(func_sig.outputs) == 0: 483 | # Add final function call. 484 | call_and_copy_block.add(call_to_func) 485 | call_and_copy_block.add( 486 | nnkStmtList.newTree( 487 | nnkCall.newTree( 488 | newIdentNode("finish"), 489 | newNilLit(), 490 | newLit(0) 491 | ) 492 | ) 493 | ) 494 | elif len(func_sig.outputs) == 1: 495 | var assign_result_block = nnkAsgn.newTree() 496 | var param = func_sig.outputs[0] 497 | var idx = 0 498 | # create placeholder variables 499 | var tmp_result_name = fmt"{func_sig.name}_result_{idx}" 500 | var tmp_result_converted_name = tmp_result_name & "_arr" 501 | call_and_copy_block.add( 502 | nnkVarSection.newTree( 503 | nnkIdentDefs.newTree( 504 | nnkPragmaExpr.newTree( 505 | newIdentNode(tmp_result_name), 506 | nnkPragma.newTree( 507 | newIdentNode("noinit") 508 | ) 509 | ), 510 | newIdentNode(param.var_type), 511 | newEmptyNode() 512 | ) 513 | ) 514 | ) 515 | assign_result_block.add(newIdentNode(tmp_result_name)) 516 | assign_result_block.add(call_to_func) 517 | 518 | call_and_copy_block.add(assign_result_block) 519 | let (tmp_conversion_ident_node, conversion_node) = get_local_output_type_conversion( 520 | tmp_result_name, 521 | tmp_result_converted_name, 522 | param.var_type 523 | ) 524 | 525 | call_and_copy_block.add(conversion_node) 526 | 527 | call_and_copy_block.add( 528 | nnkCall.newTree( 529 | newIdentNode("finish"), 530 | nnkCommand.newTree( 531 | newIdentNode("addr"), 532 | tmp_conversion_ident_node 533 | ), 534 | newLit(get_byte_size_of(param.var_type)) 535 | ) 536 | ) 537 | else: 538 | raiseParserError( 539 | "Can only handle functions with a single variable output ATM.", 540 | func_sig.line_info 541 | ) 542 | 543 | selector_CaseStmt.add( 544 | nnkOfBranch.newTree( # of 0x<>'u32: 545 | parseExpr( "0x" & func_sig.method_id & "'u32"), 546 | call_and_copy_block 547 | ) 548 | ) 549 | 550 | # Add default function. 551 | var default_func_node: NimNode 552 | if global_ctx.has_default_func: 553 | var 554 | default_fsig = filter( 555 | function_signatures, proc(x: FunctionSignature): bool = x.name == "default" 556 | )[0] 557 | assert_payable_str = if default_fsig.payable: "assertNotPayable()" else: "" 558 | default_func_node = parseStmt(unindent(fmt""" 559 | {assert_payable_str} 560 | default() 561 | finish(nil, 0) 562 | """)) 563 | else: 564 | default_func_node = parseStmt(unindent(""" 565 | discard 566 | """)) 567 | echo treeRepr(default_func_node) 568 | # Add default to function table. 569 | selector_CaseStmt.add( 570 | nnkElse.newTree( 571 | default_func_node 572 | ) 573 | ) 574 | out_stmts.add(selector_CaseStmt) 575 | out_stmts.add(nnkCall.newTree( 576 | newIdentNode("revert"), 577 | newNilLit(), 578 | newLit(0) 579 | ) 580 | ) 581 | 582 | # Build Main Func 583 | # proc main() {.exportwasm.} = 584 | # if getCallDataSize() < 4: 585 | # revert(nil, 0) 586 | 587 | var main_func = nnkStmtList.newTree( 588 | nnkProcDef.newTree( 589 | newIdentNode("main"), 590 | newEmptyNode(), 591 | newEmptyNode(), 592 | nnkFormalParams.newTree( 593 | newEmptyNode() 594 | ), 595 | nnkPragma.newTree( 596 | newIdentNode("exportwasm") 597 | ), 598 | newEmptyNode(), 599 | out_stmts, 600 | ) 601 | ) 602 | main_out_stmts.add(main_func) 603 | # echo out_stmts 604 | 605 | # build selector: 606 | # keccak256("balance(address):(uint64)")[0, 4] 607 | 608 | return main_out_stmts 609 | 610 | 611 | macro contract*(contract_name: string, proc_def: untyped): untyped = 612 | echo contract_name 613 | # echo "Before:" 614 | # echo treeRepr(proc_def) 615 | expectKind(proc_def, nnkStmtList) 616 | var stmtlist = handle_contract_interface(proc_def) 617 | echo "Final Contract Code:" 618 | echo repr(stmtlist) 619 | return stmtlist 620 | -------------------------------------------------------------------------------- /nimplay/storage.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | import strformat 3 | import strutils 4 | import tables 5 | 6 | import ./types, ./utils 7 | 8 | 9 | proc generate_storage_get_func*(storage_keyword: string, global_ctx: GlobalContext): (NimNode, string) = 10 | var 11 | global_var_name = storage_keyword.split(".")[1] 12 | new_proc_name = fmt"get_{global_var_name}_from_storage" 13 | var_info = global_ctx.global_variables[global_var_name] 14 | slot_number = var_info.slot 15 | 16 | if var_info.var_type == "uint256": 17 | var new_proc = parseStmt(fmt""" 18 | proc {new_proc_name}(): uint256 = 19 | var 20 | tmp {{.noinit.}}: array[32, byte] 21 | pos = {$slot_number}.stuint(32).toByteArrayBE 22 | storageLoad(pos, addr tmp) 23 | return Uint256.fromBytesBE(tmp) 24 | """) 25 | return (new_proc, new_proc_name) 26 | elif var_info.var_type == "uint128" or var_info.var_type == "wei_value": 27 | var new_proc = parseStmt(fmt""" 28 | proc {new_proc_name}(): {var_info.var_type} = 29 | var 30 | tmp {{.noinit.}}: array[32, byte] 31 | tmp_ba {{.noinit.}}: array[16, byte] 32 | pos = {$slot_number}.stuint(32).toByteArrayBE 33 | storageLoad(pos, addr tmp) 34 | for i in 0..15: 35 | tmp_ba[i] = tmp[i] 36 | return Uint128.fromBytesBE(tmp_ba) 37 | """) 38 | return (new_proc, new_proc_name) 39 | elif var_info.var_type == "address": 40 | var new_proc = parseStmt(fmt""" 41 | proc {new_proc_name}(): address = 42 | var 43 | tmp {{.noinit.}}: array[32, byte] 44 | pos = {$slot_number}.stuint(32).toByteArrayBE 45 | storageLoad(pos, addr tmp) 46 | var out_var: address 47 | # out_var[0..19] = tmp[12..31] 48 | for i, b in tmp: 49 | if i >= 12: 50 | out_var[i - 12] = b 51 | return out_var 52 | """) 53 | return (new_proc, new_proc_name) 54 | elif var_info.var_type == "bytes32": 55 | var new_proc = parseStmt(fmt""" 56 | proc {new_proc_name}(): bytes32 = 57 | var 58 | tmp {{.noinit.}}: bytes32 59 | pos = {$slot_number}.stuint(32).toByteArrayBE 60 | storageLoad(pos, addr tmp) 61 | return tmp 62 | """) 63 | return (new_proc, new_proc_name) 64 | elif var_info.var_type == "bool": 65 | var new_proc = parseStmt(fmt""" 66 | proc {new_proc_name}(): bool = 67 | var 68 | tmp {{.noinit.}}: bytes32 69 | pos = {$slot_number}.stuint(32).toByteArrayBE 70 | storageLoad(pos, addr tmp) 71 | if tmp[31] == 1: 72 | return true 73 | else: 74 | return false 75 | """) 76 | return (new_proc, new_proc_name) 77 | else: 78 | raise newException(ParserError, var_info.var_type & " storage is not supported at the moment.") 79 | 80 | 81 | proc generate_storage_set_func*(storage_keyword: string, global_ctx: GlobalContext): (NimNode, string) = 82 | var 83 | global_var_name = storage_keyword.split(".")[1] 84 | new_proc_name = fmt"set_{global_var_name}_in_storage" 85 | var_info = global_ctx.global_variables[global_var_name] 86 | slot_number = var_info.slot 87 | 88 | if var_info.var_type == "uint256": 89 | var new_proc = parseStmt(fmt""" 90 | proc {new_proc_name}(value:uint256) = 91 | var 92 | tmp {{.noinit.}}: array[32, byte] = value.toByteArrayBE 93 | pos = {$slot_number}.stuint(32).toByteArrayBE 94 | storageStore(pos, addr tmp) 95 | """) 96 | return (new_proc, new_proc_name) 97 | elif var_info.var_type == "uint128" or var_info.var_type == "wei_value": 98 | var new_proc = parseStmt(fmt""" 99 | proc {new_proc_name}(value: {var_info.var_type}) = 100 | var 101 | tmp {{.noinit.}}: array[32, byte] 102 | pos = {$slot_number}.stuint(32).toByteArrayBE 103 | tmp_ba = value.toByteArrayBE 104 | for i in 0..15: 105 | tmp[i] = tmp_ba[i] 106 | storageStore(pos, addr tmp) 107 | """) 108 | return (new_proc, new_proc_name) 109 | elif var_info.var_type == "address": 110 | var new_proc = parseStmt(fmt""" 111 | proc {new_proc_name}(value: address) = 112 | var 113 | tmp {{.noinit.}}: array[32, byte] 114 | pos = {$slot_number}.stuint(32).toByteArrayBE 115 | for i, b in value: 116 | tmp[12 + i] = b 117 | storageStore(pos, addr tmp) 118 | """) 119 | return (new_proc, new_proc_name) 120 | elif var_info.var_type == "bytes32": 121 | var new_proc = parseStmt(fmt""" 122 | proc {new_proc_name}(value: bytes32) = 123 | var pos = {$slot_number}.stuint(32).toByteArrayBE 124 | storageStore(pos, unsafeAddr value) 125 | """) 126 | return (new_proc, new_proc_name) 127 | elif var_info.var_type == "bool": 128 | var new_proc = parseStmt(fmt""" 129 | proc {new_proc_name}(value: bool) = 130 | var 131 | pos = {$slot_number}.stuint(32).toByteArrayBE 132 | v: bytes32 133 | if value: 134 | v[31] = 1u8 135 | storageStore(pos, unsafeAddr value) 136 | """) 137 | return (new_proc, new_proc_name) 138 | else: 139 | raise newException(ParserError, var_info.var_type & " storage is not supported at the moment.") 140 | 141 | 142 | proc get_combined_key_info(var_info: VariableType): (string, seq[string], string, int) = 143 | var 144 | value_conversion = "" 145 | key_param_arr: seq[string] 146 | key_copy_block: string 147 | total_key_size = 0 148 | key_count = 0 149 | 150 | for key_type in var_info.key_types[0..^2]: 151 | var current_key_name = fmt"key{key_count}" 152 | key_param_arr.add( 153 | fmt"{current_key_name}: " & key_type 154 | ) 155 | key_copy_block.add( 156 | fmt" copy_into_ba(combined_key, {total_key_size}, {current_key_name})" & '\n' 157 | ) 158 | inc(key_count) 159 | total_key_size += get_memory_byte_size_of(key_type) 160 | 161 | (value_conversion, key_param_arr, key_copy_block, total_key_size) 162 | 163 | 164 | proc generate_storage_table_set_func*(storage_keyword: string, global_ctx: GlobalContext): (NimNode, string) = 165 | var 166 | global_var_name = storage_keyword.split(".")[1] 167 | var_info = global_ctx.global_variables[global_var_name] 168 | new_proc_name = fmt"set_{global_var_name}_in_storage_table" 169 | value_type = var_info.key_types[^1] 170 | table_id = var_info.slot 171 | (value_conversion, key_param_arr, key_copy_block, total_key_size) = get_combined_key_info(var_info) 172 | 173 | if value_type == "bytes32": 174 | value_conversion = "tmp_val = val" 175 | elif value_type in @["wei_value", "uint128"]: 176 | value_conversion = "tmp_val = val.to_bytes32" 177 | elif value_type == "uint256": 178 | value_conversion = "tmp_val = val.to_bytes32" 179 | elif value_type == "bool": 180 | value_conversion = "if val: tmp_val[31] = 1" 181 | else: 182 | raise newException(ParserError, "Unsupported '{value_type}' value type.") 183 | 184 | var s = fmt""" 185 | proc {new_proc_name}({key_param_arr.join(",")}, val: {value_type}) = 186 | var 187 | {value_conversion} 188 | combined_key {{.noinit.}}: array[{total_key_size}, byte] 189 | {key_copy_block} 190 | setTableValue({table_id}.int32, combined_key, tmp_val) 191 | """ 192 | echo s 193 | var new_proc = parseStmt(s) 194 | return (new_proc, new_proc_name) 195 | 196 | 197 | proc generate_storage_table_get_func*(storage_keyword: string, global_ctx: GlobalContext): (NimNode, string) = 198 | var 199 | global_var_name = storage_keyword.split(".")[1] 200 | new_proc_name = fmt"get_{global_var_name}_in_storage_table" 201 | var_info = global_ctx.global_variables[global_var_name] 202 | value_type = var_info.key_types[^1] 203 | table_id = var_info.slot 204 | (value_conversion, key_param_arr, key_copy_block, total_key_size) = get_combined_key_info(var_info) 205 | echo value_type 206 | 207 | if value_type == "bytes32": 208 | value_conversion = "tmp_val" 209 | elif value_type == "bool": 210 | value_conversion = "tmp_val[31] == 1" 211 | elif value_type in @["wei_value", "uint128"]: 212 | value_conversion = "tmp_val.to_uint128" 213 | elif value_type == "uint256": 214 | value_conversion = "tmp_val.to_uint256" 215 | else: 216 | raise newException(ParserError, "Unsupported StorageTable '{value_type}' value type.") 217 | 218 | var new_proc = parseStmt(fmt""" 219 | proc {new_proc_name}({key_param_arr.join(",")}): {value_type} = 220 | var 221 | tmp_val: bytes32 222 | combined_key {{.noinit.}}: array[{total_key_size}, byte] 223 | {key_copy_block} 224 | tmp_val = getTableValue({table_id}.int32, combined_key) 225 | {value_conversion} 226 | """) 227 | return (new_proc, new_proc_name) 228 | -------------------------------------------------------------------------------- /nimplay/substrate_runtime.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | 3 | {.push cdecl, importc.} 4 | 5 | proc ext_address*() 6 | proc ext_block_number*() 7 | proc ext_gas_left*() 8 | proc ext_gas_price*() 9 | proc ext_get_storage*(key_ptr: pointer): int32 10 | proc ext_now*() 11 | proc ext_println*(str_ptr: pointer, str_len: int32) # experimental; will be removed. 12 | proc ext_random_seed*() 13 | proc ext_scratch_read*(dest_ptr: pointer, offset: int32, len: int32) 14 | proc ext_scratch_size*(): int32 15 | proc ext_scratch_write*(src_ptr: pointer, len: int32) 16 | proc ext_set_rent_allowance*(value_ptr: pointer, value_len: int32) 17 | proc ext_set_storage*(key_ptr: pointer, value_non_null: int32, value_ptr: pointer, value_len: int32) 18 | proc ext_value_transferred*() 19 | 20 | {.pop.} 21 | 22 | macro exportwasm*(p: untyped): untyped = 23 | expectKind(p, nnkProcDef) 24 | result = p 25 | result.addPragma(newIdentNode("exportc")) 26 | result.addPragma( 27 | newColonExpr( 28 | newIdentNode("codegenDecl"), newLit("__attribute__ ((visibility (\"default\"))) $# $#$#") 29 | ) 30 | ) 31 | -------------------------------------------------------------------------------- /nimplay/types.nim: -------------------------------------------------------------------------------- 1 | import stint 2 | import tables 3 | import macros 4 | import sequtils 5 | 6 | # Nimplay types. 7 | 8 | type 9 | uint256* = StUint[256] 10 | int128* = StInt[128] 11 | uint128* = StUint[128] 12 | address* = array[20, byte] 13 | bytes32* = array[32, byte] 14 | wei_value* = uint128 15 | 16 | # Nimplay structs. 17 | 18 | type 19 | VariableType* = object 20 | name*: string 21 | var_type*: string 22 | key_types*: seq[string] 23 | slot*: int64 24 | 25 | type 26 | EventType* = object 27 | name*: string 28 | var_type*: string 29 | indexed*: bool 30 | param_position*: int 31 | 32 | type 33 | FunctionSignature* = object 34 | name*: string 35 | inputs*: seq[VariableType] 36 | outputs*: seq[VariableType] 37 | constant*: bool 38 | payable*: bool 39 | method_id*: string 40 | method_sig*: string 41 | is_private*: bool 42 | line_info*: LineInfo 43 | pragma_base_keywords*: seq[string] # list of pragmas 44 | 45 | type 46 | EventSignature* = object 47 | name*: string 48 | inputs*: seq[EventType] 49 | outputs*: seq[EventType] 50 | definition*: NimNode 51 | 52 | type 53 | LocalContext* = object 54 | # Name of the function. 55 | name*: string 56 | # Function signature, used for function selection and ABI encoding / decoding. 57 | sig*: FunctionSignature 58 | # Global temp variables create at beginning of the function. 59 | keyword_define_stmts*: NimNode 60 | # Map of temp variables that have to be replaced by. 61 | global_keyword_map*: Table[string, string] 62 | 63 | type 64 | GlobalContext* = object 65 | global_variables*: Table[string, VariableType] 66 | events*: Table[string, EventSignature] 67 | getter_funcs*: seq[VariableType] 68 | has_default_func*: bool 69 | 70 | # Exceptions. 71 | 72 | type ParserError* = object of Exception 73 | 74 | # Nimplay constants. 75 | 76 | let 77 | KEYWORDS* {.compileTime.} = @["contract", "self", "log"] 78 | TYPE_NAMES* {.compileTime.} = @["address", "uint256", "bytes32", "int128", "uint128"] 79 | ALL_KEYWORDS* {.compileTime.} = concat(TYPE_NAMES, KEYWORDS) 80 | ALLOWED_PRAGMAS* {.compileTime.} = @["payable", "event", "self", "msg", "log"] 81 | 82 | # Constants 83 | 84 | let ZERO_ADDRESS*: address = [ 85 | 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 86 | 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8 87 | ] 88 | -------------------------------------------------------------------------------- /nimplay/utils.nim: -------------------------------------------------------------------------------- 1 | import 2 | macros, strformat, strutils, 3 | tables, math, macros 4 | 5 | import 6 | ./types 7 | 8 | 9 | proc raiseParserError*(in_msg: string, line_info: LineInfo) = 10 | var out_msg = "\n\n" & line_info.filename & "\n" 11 | var line_no = 1 12 | var line_no_prefix = $line_no & ":" 13 | var file_str = staticRead(line_info.filename) 14 | for line in file_str.splitLines(): 15 | if line_no == line_info.line: 16 | line_no_prefix = $line_no & ":" 17 | out_msg &= line_no_prefix 18 | out_msg &= line & "\n" 19 | out_msg &= "-".repeat(line_info.column + line_no_prefix.len) & "^\n" 20 | break 21 | inc(line_no) 22 | out_msg &= "PlayParserError: " & in_msg & "\n\n" 23 | raise newException(ParserError, out_msg) 24 | 25 | 26 | proc raiseParserError*(msg: string, node: NimNode) = 27 | let line_info = lineInfoObj(node) 28 | raiseParserError(msg, line_info) 29 | 30 | 31 | proc valid_var_chars(var_name: string): bool = 32 | var res = false 33 | for c in var_name: 34 | if c in {'a'..'z'}: 35 | res = true 36 | elif c in {'A'..'Z'}: 37 | res = true 38 | elif c == '_': 39 | res = true 40 | elif c in {'0'..'9'}: 41 | res = true 42 | else: 43 | res = false 44 | break 45 | return res 46 | 47 | 48 | proc check_valid_variable_name*(node: NimNode, global_ctx: GlobalContext) = 49 | expectKind(node, nnkIdent) 50 | var 51 | name = strVal(node) 52 | err_msg = "" 53 | 54 | if global_ctx.global_variables.hasKey(name): 55 | err_msg = "Variable name is same as a global variable, please chose another name." 56 | elif name in ALL_KEYWORDS: 57 | err_msg = "Variable name to similar to a keyword." 58 | elif not valid_var_chars(name): # prevent homograph attack. 59 | err_msg = "Invalid variable name, only alphanumeric characters with underscore are supported." 60 | if err_msg != "": 61 | raiseParserError(err_msg, node) 62 | 63 | 64 | proc get_byte_size_of*(type_str: string): int = 65 | let BASE32_TYPES_NAMES: array = [ 66 | "bool", 67 | "uint256", 68 | "uint128", 69 | "int128", 70 | "address", 71 | "bytes32", 72 | "wei_value" 73 | ] 74 | if type_str in BASE32_TYPES_NAMES: 75 | return 32 76 | else: 77 | raise newException(ParserError, fmt"Unknown '{type_str}' type supplied!") 78 | 79 | 80 | proc get_memory_byte_size_of*(type_str: string): int = 81 | if type_str in @["uint256", "bytes32"]: 82 | return 32 83 | elif type_str in @["address"]: 84 | return 20 85 | elif type_str in @["uint128", "int128", "wei_value"]: 86 | return 16 87 | else: 88 | raise newException(ParserError, fmt"Unknown '{type_str}' type supplied!") 89 | 90 | 91 | func get_bit_size_of*(type_str: string): int = 92 | get_byte_size_of(type_str) * 8 93 | 94 | 95 | proc hexstr_tointarray*[N: static[int]](in_str: string): array[N, int]= 96 | var out_arr: array[N, int] 97 | for i in countup(0, in_str.len - 2, 2): 98 | out_arr[floorDiv(i, 2)] = fromHex[int](in_str[i..i+1]) 99 | out_arr 100 | -------------------------------------------------------------------------------- /nimplay0_1.nim: -------------------------------------------------------------------------------- 1 | import nimplay/nimplay_macros 2 | import nimplay/types 3 | import nimplay/ewasm_runtime 4 | import nimcrypto/[sha, ripemd, hash, blake2] 5 | import stint 6 | 7 | export nimplay_macros 8 | export types 9 | export ewasm_runtime 10 | export stint 11 | export hash 12 | export sha 13 | export ripemd 14 | export blake2 15 | -------------------------------------------------------------------------------- /tests/ee/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}" 3 | RUST_VERSION="nightly" 4 | REPO_DIR="ewasm-scout" 5 | REPO_URL="https://github.com/ewasm/scout.git" 6 | SCOUT_TEST_FILES=./*.yml 7 | 8 | 9 | if [[ -e $REPO_DIR ]]; then 10 | rm -v $REPO_DIR/*.yml 11 | cp $SCOUT_TEST_FILES $REPO_DIR/ 12 | cd $REPO_DIR 13 | git pull 14 | else 15 | git clone $REPO_URL $REPO_DIR 16 | rm -v $REPO_DIR/*.yml 17 | cp $SCOUT_TEST_FILES $REPO_DIR/ 18 | cd $REPO_DIR 19 | fi 20 | 21 | 22 | if [[ ! -e $CARGO_HOME ]]; then 23 | echo "Fetching rustup" 24 | curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain $RUST_VERSION 25 | fi 26 | 27 | rustup target add wasm32-unknown-unknown 28 | rustup component add rustfmt 29 | rustup update 30 | cargo install chisel 31 | # make build 32 | cargo build --release 33 | # make test 34 | # target/release/phase2-scout $SCOUT_TEST_FILE 35 | 36 | for testfile in $SCOUT_TEST_FILES 37 | do 38 | echo $testfile 39 | RUST_LOG=debug target/release/phase2-scout $testfile || exit 40 | break 41 | done 42 | -------------------------------------------------------------------------------- /tests/ee/test_block_echo.yml: -------------------------------------------------------------------------------- 1 | 2 | beacon_state: 3 | execution_scripts: 4 | - ../../../examples/ee/block_echo.wasm 5 | shard_pre_state: 6 | exec_env_states: 7 | - "0000000000000000000000000000000000000000000000000000000000000000" 8 | shard_blocks: 9 | - env: 0 10 | data: "fafbfcfdfef0f0f0f0f0ffffffffffffffffffffffffffffffffe6e5e4e3e2e1" 11 | shard_post_state: 12 | exec_env_states: 13 | - "fafbfcfdfef0f0f0f0f0ffffffffffffffffffffffffffffffffe6e5e4e3e2e1" 14 | -------------------------------------------------------------------------------- /tests/ee/test_helloworld.yml: -------------------------------------------------------------------------------- 1 | 2 | beacon_state: 3 | execution_scripts: 4 | - ../../../examples/ee/helloworld.wasm 5 | shard_pre_state: 6 | exec_env_states: 7 | - "0000000000000000000000000000000000000000000000000000000000000000" 8 | shard_blocks: 9 | - env: 0 10 | data: "" 11 | - env: 0 12 | data: "" 13 | shard_post_state: 14 | exec_env_states: 15 | - "0000000000000000000000000000000000000000000000000000000000001122" 16 | -------------------------------------------------------------------------------- /tests/substrate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "srml-contract-waterfall-nimplay", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Sergey Shulepov ", 6 | "contributors": [ 7 | "Stefanie Doll " 8 | ], 9 | "license": "MIT", 10 | "dependencies": { 11 | "@polkadot/api": "^0.97.0-beta.39", 12 | "@polkadot/api-contract": "^0.97.0-beta.39", 13 | "blakejs": "^1.1.0", 14 | "typescript": "^3.6.4" 15 | }, 16 | "devDependencies": { 17 | "@types/jest": "^24.0.18", 18 | "@types/node": "^10.12.18", 19 | "@typescript-eslint/eslint-plugin": "^2.6.0", 20 | "@typescript-eslint/parser": "^2.6.0", 21 | "bn.js": "^5.0.0", 22 | "eslint": "^6.6.0", 23 | "jest": "^24.9.0", 24 | "ts-jest": "^24.1.0", 25 | "ts-loader": "^5.3.2" 26 | }, 27 | "jest": { 28 | "preset": "ts-jest/presets/js-with-ts" 29 | }, 30 | "scripts": { 31 | "test": "NODE_ENV=abc jest" 32 | } 33 | } -------------------------------------------------------------------------------- /tests/substrate/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$SUBSTRATE_PATH" ]; then 4 | echo "Please specify the path to substrate in the SUBSTRATE_PATH environment variable" 5 | exit 1 6 | fi 7 | 8 | if [ ! -f "$SUBSTRATE_PATH" ]; then 9 | echo "$SUBSTRATE_PATH doesn't exist" 10 | exit 2 11 | fi 12 | 13 | # Purge dev chain and then spin up the substrate node in background 14 | $SUBSTRATE_PATH purge-chain --dev -y 15 | $SUBSTRATE_PATH --dev -l debug & 16 | SUBSTRATE_PID=$! 17 | 18 | # # Execute tests 19 | yarn && yarn test --verbose 20 | 21 | # # Kill the spawned substrate node 22 | kill -9 $SUBSTRATE_PID 23 | -------------------------------------------------------------------------------- /tests/substrate/tests/consts.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | 3 | export const WSURL = "ws://127.0.0.1:9944"; 4 | export const DOT: BN = new BN("1000000000000000"); 5 | export const CREATION_FEE: BN = DOT.muln(200); 6 | export const GAS_REQUIRED = 50000; 7 | export const ALICE = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"; 8 | export const BOB = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"; 9 | 10 | -------------------------------------------------------------------------------- /tests/substrate/tests/contract-nimplay.spec.ts: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/paritytech/srml-contracts-waterfall/ 2 | 3 | import { ApiPromise, SubmittableResult, WsProvider } from "@polkadot/api"; 4 | import { Abi } from '@polkadot/api-contract'; 5 | import testKeyring from "@polkadot/keyring/testing"; 6 | import { u8aToHex } from "@polkadot/util"; 7 | import { randomAsU8a } from "@polkadot/util-crypto"; 8 | import { KeyringPair } from "@polkadot/keyring/types"; 9 | import { Option } from "@polkadot/types"; 10 | import { Address, ContractInfo, Hash } from "@polkadot/types/interfaces"; 11 | 12 | import { ALICE, CREATION_FEE, WSURL } from "./consts"; 13 | import { 14 | callContract, 15 | instantiate, 16 | getContractStorage, 17 | putCode 18 | } from "./utils"; 19 | 20 | // This is a test account that is going to be created and funded each test. 21 | const keyring = testKeyring({ type: "sr25519" }); 22 | const alicePair = keyring.getPair(ALICE); 23 | let testAccount: KeyringPair; 24 | let api: ApiPromise; 25 | 26 | beforeAll((): void => { 27 | jest.setTimeout(30000); 28 | }); 29 | 30 | beforeEach( 31 | async (done): Promise<() => void> => { 32 | api = await ApiPromise.create({ provider: new WsProvider(WSURL) }); 33 | testAccount = keyring.addFromSeed(randomAsU8a(32)); 34 | 35 | return api.tx.balances 36 | .transfer(testAccount.address, CREATION_FEE.muln(3)) 37 | .signAndSend(alicePair, (result: SubmittableResult): void => { 38 | if ( 39 | result.status.isFinalized && 40 | result.findRecord("system", "ExtrinsicSuccess") 41 | ) { 42 | console.log("New test account has been created."); 43 | done(); 44 | } 45 | }); 46 | } 47 | ); 48 | 49 | describe("Nimplay Hellow World", () => { 50 | test("Can deploy and execute", async (done): Promise => { 51 | const codeHash = await putCode( 52 | api, 53 | testAccount, 54 | "../../../examples/substrate/hello_world.wasm" 55 | ); 56 | expect(codeHash).toBeDefined(); 57 | const address: Address = await instantiate( 58 | api, 59 | testAccount, 60 | codeHash, 61 | "0x00", 62 | CREATION_FEE 63 | ); 64 | expect(address).toBeDefined(); 65 | await callContract(api, testAccount, address, "0x00"); 66 | done(); 67 | }); 68 | }); 69 | 70 | describe("Nimplay Storage Setter", () => { 71 | test("Setter: Can deploy and execute", async (done): Promise => { 72 | // See https://github.com/paritytech/srml-contracts-waterfall/issues/6 for info about 73 | // how to get the STORAGE_KEY of an instantiated contract 74 | 75 | const STORAGE_KEY = (new Uint8Array(32)).fill(2); 76 | // Deploy contract code on chain and retrieve the code hash 77 | const codeHash = await putCode( 78 | api, 79 | testAccount, 80 | "../../../examples/substrate/setter.wasm" 81 | ); 82 | expect(codeHash).toBeDefined(); 83 | 84 | // Instantiate a new contract instance and retrieve the contracts address 85 | const address: Address = await instantiate( 86 | api, 87 | testAccount, 88 | codeHash, 89 | "0x00", 90 | CREATION_FEE 91 | ); 92 | expect(address).toBeDefined(); 93 | 94 | const initialValue: Uint8Array = await getContractStorage( 95 | api, 96 | address, 97 | STORAGE_KEY 98 | ); 99 | expect(initialValue).toBeDefined(); 100 | expect(initialValue.toString()).toEqual(""); 101 | 102 | await callContract(api, testAccount, address, "0x00"); 103 | var val_hex = "03".repeat(32); 104 | // "0x00" indicates calling "Set" Action 105 | await callContract(api, testAccount, address, "0x00" + val_hex); 106 | const newValue = await getContractStorage(api, address, STORAGE_KEY); 107 | expect(newValue.toString()).toEqual("0x" + val_hex); 108 | 109 | done(); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /tests/substrate/tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise, SubmittableResult } from "@polkadot/api"; 2 | import { KeyringPair } from "@polkadot/keyring/types"; 3 | import { Option, StorageData } from "@polkadot/types"; 4 | import { Address, ContractInfo, Hash } from "@polkadot/types/interfaces"; 5 | import BN from "bn.js"; 6 | import fs from "fs"; 7 | import path from "path"; 8 | const blake = require('blakejs') 9 | 10 | import { GAS_REQUIRED } from "./consts"; 11 | 12 | export async function sendAndReturnFinalized(signer: KeyringPair, tx: any) { 13 | return new Promise(function(resolve, reject) { 14 | tx.signAndSend(signer, (result: SubmittableResult) => { 15 | if (result.status.isFinalized) { 16 | // Return result of the submittable extrinsic after the transfer is finalized 17 | resolve(result as SubmittableResult); 18 | } 19 | if ( 20 | result.status.isDropped || 21 | result.status.isInvalid || 22 | result.status.isUsurped 23 | ) { 24 | reject(result as SubmittableResult); 25 | console.error("ERROR: Transaction could not be finalized."); 26 | } 27 | }); 28 | }); 29 | } 30 | 31 | export async function putCode( 32 | api: ApiPromise, 33 | signer: KeyringPair, 34 | fileName: string, 35 | gasRequired: number = GAS_REQUIRED 36 | ): Promise { 37 | const wasmCode = fs 38 | .readFileSync(path.join(__dirname, fileName)) 39 | .toString("hex"); 40 | const tx = api.tx.contracts.putCode(gasRequired, `0x${wasmCode}`); 41 | const result: any = await sendAndReturnFinalized(signer, tx); 42 | const record = result.findRecord("contracts", "CodeStored"); 43 | 44 | if (!record) { 45 | console.error("ERROR: No code stored after executing putCode()"); 46 | } 47 | // Return code hash. 48 | return record.event.data[0]; 49 | } 50 | 51 | export async function instantiate( 52 | api: ApiPromise, 53 | signer: KeyringPair, 54 | codeHash: Hash, 55 | inputData: any, 56 | endowment: BN, 57 | gasRequired: number = GAS_REQUIRED 58 | ): Promise
{ 59 | const tx = api.tx.contracts.instantiate( 60 | endowment, 61 | gasRequired, 62 | codeHash, 63 | inputData 64 | ); 65 | const result: any = await sendAndReturnFinalized(signer, tx); 66 | const record = result.findRecord("contracts", "Instantiated"); 67 | 68 | if (!record) { 69 | console.error("ERROR: No new instantiated contract"); 70 | } 71 | // Return the address of instantiated contract. 72 | return record.event.data[1]; 73 | } 74 | 75 | export async function callContract( 76 | api: ApiPromise, 77 | signer: KeyringPair, 78 | contractAddress: Address, 79 | inputData: any, 80 | gasRequired: number = GAS_REQUIRED, 81 | endowment: number = 0 82 | ): Promise { 83 | const tx = api.tx.contracts.call( 84 | contractAddress, 85 | endowment, 86 | gasRequired, 87 | inputData 88 | ); 89 | await sendAndReturnFinalized(signer, tx); 90 | } 91 | 92 | export async function getContractStorage( 93 | api: ApiPromise, 94 | contractAddress: Address, 95 | storageKey: Uint8Array 96 | ): Promise { 97 | const contractInfo = await api.query.contracts.contractInfoOf( 98 | contractAddress 99 | ); 100 | // Return the value of the contracts storage 101 | const storageKeyBlake2b = blake.blake2bHex(storageKey, null, 32); 102 | return await api.rpc.state.getChildStorage( 103 | (contractInfo as Option).unwrap().asAlive.trieId, 104 | '0x' + storageKeyBlake2b 105 | ); 106 | } 107 | 108 | -------------------------------------------------------------------------------- /tests/substrate/tsconfig.js: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "module": "commonjs", 6 | "lib": ["dom", "es2018"], 7 | "resolveJsonModule": true, 8 | "strict": true, 9 | "noImplicitAny": true, 10 | "strictNullChecks": true, 11 | "strictFunctionTypes": true, 12 | "strictBindCallApply": true, 13 | "strictPropertyInitialization": true, 14 | "noImplicitThis": true, 15 | "alwaysStrict": true, 16 | "allowJs": true, 17 | "esModuleInterop": true 18 | }, 19 | "exclude": [ 20 | "contracts/rust", 21 | "lib", 22 | "node_modules", 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /tests/substrate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "module": "commonjs", 6 | "lib": ["dom", "es2018"], 7 | "resolveJsonModule": true, 8 | "strict": true, 9 | "noImplicitAny": true, 10 | "strictNullChecks": true, 11 | "strictFunctionTypes": true, 12 | "strictBindCallApply": true, 13 | "strictPropertyInitialization": true, 14 | "noImplicitThis": true, 15 | "alwaysStrict": true, 16 | "allowJs": true, 17 | "esModuleInterop": true 18 | }, 19 | "exclude": [ 20 | "contracts/rust", 21 | "lib", 22 | "node_modules", 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /tests/substrate/utils.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | 3 | export const WSURL = "ws://127.0.0.1:9944"; 4 | export const DOT: BN = new BN("1000000000000000"); 5 | export const CREATION_FEE: BN = DOT.muln(200); 6 | export const GAS_REQUIRED = 50000; 7 | export const ALICE = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"; 8 | export const BOB = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"; 9 | 10 | -------------------------------------------------------------------------------- /tools/abi_gen.nim: -------------------------------------------------------------------------------- 1 | import compiler / [ast, vmdef, vm, nimeval, options, parser, idents, condsyms, 2 | nimconf, extccomp, astalgo, llstream, pathutils] 3 | import json 4 | import os 5 | 6 | 7 | import 8 | ../nimplay/types 9 | 10 | 11 | type 12 | VariableType* = object 13 | name: string 14 | var_type: string 15 | 16 | type 17 | EventType* = object 18 | name: string 19 | var_type: string 20 | indexed: bool 21 | 22 | type 23 | FunctionSignature* = object 24 | name: string 25 | inputs: seq[VariableType] 26 | outputs: seq[VariableType] 27 | constant: bool 28 | payable: bool 29 | 30 | type 31 | EventSignature* = object 32 | name: string 33 | inputs: seq[EventType] 34 | indexed: bool 35 | 36 | 37 | proc get_abi_type(nimplay_type_str: string): string = 38 | if nimplay_type_str in @["uint256", "bytes32", "int128", "uint128", "address"]: 39 | nimplay_type_str 40 | elif nimplay_type_str == "wei_value": 41 | "uint128" 42 | else: 43 | raise newException(Exception, "Unknown type: " & nimplay_type_str) 44 | 45 | 46 | proc string_to_ast*(s: string): PNode {.raises: [Exception] .} = 47 | var conf = newConfigRef() 48 | var cache = newIdentCache() 49 | condsyms.initDefines(conf.symbols) 50 | conf.projectName = "stdinfile" 51 | conf.projectFull = "stdinfile".AbsoluteFile 52 | conf.projectPath = canonicalizePath(conf, getCurrentDir().AbsoluteFile).AbsoluteDir 53 | conf.projectIsStdin = true 54 | loadConfigs(DefaultConfig, cache, conf) 55 | extccomp.initVars(conf) 56 | var node = parseString(s, cache, conf) 57 | if len(node.sons) > 1: 58 | result = node 59 | else: 60 | result = node.sons[0] 61 | 62 | 63 | proc generate_function_signature(proc_def: PNode): FunctionSignature = 64 | doAssert(proc_def.kind == nkFuncDef or proc_def.kind == nkProcDef) 65 | var 66 | func_sig = FunctionSignature() 67 | 68 | for child in proc_def.sons: 69 | case child.kind: 70 | of nkPostfix: 71 | func_sig.name = child[1].ident.s 72 | of nkFormalParams: 73 | for param in child: 74 | case param.kind 75 | of nkIdent: 76 | func_sig.outputs.add( 77 | VariableType( 78 | name: "out1", 79 | var_type: get_abi_type(param.ident.s) 80 | ) 81 | ) 82 | of nkIdentDefs: 83 | func_sig.inputs.add( 84 | VariableType( 85 | name: param.sons[0].ident.s, 86 | var_type: get_abi_type(param.sons[1].ident.s) 87 | ) 88 | ) 89 | of nkEmpty: 90 | discard 91 | else: 92 | raise newException(Exception, "unknown param type") 93 | of nkPragma: 94 | for pragma_child in child: 95 | if pragma_child.kind == nkIdent and pragma_child.ident.s == "payable": 96 | func_sig.payable = true 97 | else: 98 | discard 99 | return func_sig 100 | 101 | 102 | proc generate_event_signature(proc_def: PNode): EventSignature = 103 | doAssert(proc_def.kind == nkProcDef) 104 | var 105 | event_sig = EventSignature() 106 | for child in proc_def.sons: 107 | case child.kind: 108 | of nkIdent: 109 | event_sig.name = child.ident.s 110 | of nkFormalParams: 111 | for param in child: 112 | if param.kind == nkIdentDefs: 113 | var 114 | ev: EventType 115 | if param[0].kind == nkPragmaExpr and param[0][1].kind == nkPragma: 116 | ev = EventType( 117 | name: param.sons[0].sons[0].ident.s, 118 | var_type: param.sons[1].ident.s, 119 | indexed: true 120 | ) 121 | else: 122 | ev = EventType( 123 | name: param.sons[0].ident.s, 124 | var_type: param.sons[1].ident.s, 125 | indexed: false 126 | ) 127 | event_sig.inputs.add(ev) 128 | else: 129 | discard 130 | 131 | event_sig # return 132 | 133 | 134 | proc generateSignatures(stmts: PNode): (seq[FunctionSignature], seq[EventSignature]) = 135 | var 136 | public_functions: seq[FunctionSignature] 137 | events: seq[EventSignature] 138 | for child in stmts: 139 | if child.kind == nkCall and len(child.sons) > 2: 140 | var first_son = child.sons[0] 141 | if first_son.kind == nkIdent and first_son.ident.s == "contract": 142 | var main_block = child 143 | for b in main_block.sons: 144 | if b.kind == nkStmtList: 145 | for s in b.sons: 146 | if s.kind == nkVarSection: # handle getters. 147 | for var_def in s.sons: 148 | if var_def.kind == nkIdentDefs: 149 | var 150 | potential_postfix = var_def.sons[0] 151 | if potential_postfix.kind == nkPostfix and potential_postfix.sons[0].ident.s == "*": 152 | var 153 | func_name = potential_postfix.sons[1].ident.s 154 | type_name = var_def.sons[1].ident.s 155 | sig = FunctionSignature(name: func_name) 156 | sig.outputs.add( 157 | VariableType(name: "out1", var_type: get_abi_type(type_name)) 158 | ) 159 | public_functions.add( 160 | sig 161 | ) 162 | elif s.kind == nkFuncDef or s.kind == nkProcDef: 163 | # only get public functions. 164 | if s[0].kind == nkPostfix and s[0][0].ident.s == "*": 165 | public_functions.add( 166 | generate_function_signature(s) 167 | ) 168 | # handle event signature 169 | elif s[6].kind == nkEmpty: 170 | events.add( 171 | generate_event_signature(s) 172 | ) 173 | return (public_functions, events) 174 | 175 | 176 | proc generateJSONABI(funcs: seq[FunctionSignature], events: seq[EventSignature]): string = 177 | var jsonObject = %* [] 178 | 179 | proc getVarTypes(var_types: seq[VariableType]): JsonNode = 180 | var types = %* [] 181 | for t in var_types: 182 | types.add(%* { 183 | "name": t.name, 184 | "type": t.var_type, 185 | }) 186 | types # return 187 | 188 | 189 | func getFunc(f: FunctionSignature): JsonNode = 190 | %* { 191 | "name": f.name, 192 | "inputs": getVarTypes(f.inputs), 193 | "outputs": getVarTypes(f.outputs), 194 | "constant": f.constant, 195 | "payable": f.payable, 196 | "type": "function" 197 | } 198 | 199 | proc getEventTypes(var_types: seq[EventType]): JsonNode = 200 | var types = %* [] 201 | for t in var_types: 202 | types.add(%* { 203 | "name": t.name, 204 | "type": t.var_type, 205 | "indexed": t.indexed 206 | }) 207 | types 208 | 209 | func getEvent(e: EventSignature): JsonNode = 210 | %* { 211 | "inputs": getEventTypes(e.inputs), 212 | "name": e.name, 213 | "type": "event", 214 | "anonymous": false 215 | } 216 | 217 | for fn in funcs: 218 | jsonObject.add(getFunc(fn)) 219 | 220 | for event in events: 221 | jsonObject.add(getEvent(event)) 222 | 223 | $jsonObject # return 224 | 225 | 226 | proc main() = 227 | if paramCount() != 1: 228 | echo("Requires .nim file with contract() block macro") 229 | quit() 230 | if not existsFile(commandLineParams()[0]): 231 | echo("Requires .nim file with contract() block macro") 232 | quit() 233 | 234 | var nimFile = commandLineParams()[0] 235 | let node = string_to_ast(readFile(nimFile)) 236 | 237 | if node.kind == nkStmtList: 238 | let (functions, events) = generateSignatures(node) 239 | echo(generateJSONABI(functions, events)) 240 | else: 241 | raise newException(Exception, "Expected nkStmtList") 242 | 243 | 244 | when is_main_module: 245 | main() 246 | -------------------------------------------------------------------------------- /tools/deploy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Deployment script for NimPlay contracts 3 | # requirements: 4 | import os 5 | import sys 6 | 7 | import argparse 8 | import json 9 | import web3 10 | import textwrap 11 | import subprocess 12 | import tempfile 13 | 14 | from web3 import Web3 15 | from web3.providers import HTTPProvider 16 | 17 | PRIVATE_KEY = os.environ.get('PRIVATE_KEY') 18 | PRIVATE_KEY_FILE = os.environ.get('PRIVATE_KEY_FILE', '.priv_key_hex') 19 | RPC_URL = os.environ.get('RPC_URL', 'http://ewasm.ethereum.org:8545') 20 | WAT2WASM = os.environ.get('WAT2WASM', './tools/wabt/bin/wat2wasm') 21 | WASM2WAT = os.environ.get('WASM2WAT', './tools/wabt/bin/wasm2wat') 22 | 23 | 24 | if not PRIVATE_KEY and PRIVATE_KEY_FILE: 25 | PRIVATE_KEY = open(PRIVATE_KEY_FILE, 'r').read() 26 | 27 | if not PRIVATE_KEY: 28 | print('Private Key required, user either PRIVATE_KEY or PRIVATE_KEY_FILE') 29 | sys.exit(1) 30 | 31 | 32 | def watb_util(binary, code): 33 | with tempfile.NamedTemporaryFile() as in_file, tempfile.NamedTemporaryFile() as out_file: 34 | outb = code.encode() if isinstance(code, str) else code 35 | in_file.write(outb) 36 | in_file.flush() 37 | completed_process = subprocess.run([binary, in_file.name, '-o', out_file.name]) 38 | out_file.seek(0) 39 | b = out_file.read() 40 | return b 41 | 42 | 43 | def wasm2wat(code): 44 | return watb_util(WASM2WAT, code) 45 | 46 | 47 | def wat2wasm(code): 48 | return watb_util(WAT2WASM, code) 49 | 50 | 51 | def get_abi(fpath): 52 | cmdline = ['./tools/abi_gen', fpath.replace('wasm', 'nim')] 53 | completed_process = subprocess.run( 54 | cmdline, 55 | stdout=subprocess.PIPE, 56 | ) 57 | if completed_process.returncode != 0: 58 | print(completed_process.stdout) 59 | print(completed_process.stderr) 60 | raise Exception('Could not get ABI') 61 | return json.loads(completed_process.stdout) 62 | 63 | 64 | def create_deploy_bytecode(fpath): 65 | with open(fpath, 'rb') as f: 66 | wasmb = f.read() 67 | hexb = Web3.toHex(wasmb)[2:] 68 | total_len = len(hexb) // 2 69 | escaped_hexb = "\\" + "\\".join(hexb[x:x+2] for x in range(0, len(hexb), 2)) 70 | code = textwrap.dedent(f""" 71 | (module 72 | (type (;0;) (func (param i32 i32))) 73 | (type (;1;) (func)) 74 | (import "ethereum" "finish" (func (;0;) (type 0))) 75 | (func (;1;) (type 1) 76 | i32.const 0 77 | i32.const {total_len} 78 | call 0) 79 | (memory (;0;) 100) 80 | (export "memory" (memory 0)) 81 | (export "main" (func 1)) 82 | (data (;0;) (i32.const 0) "{escaped_hexb}")) 83 | """) 84 | print(code) 85 | deploy_wasmb = wat2wasm(code) 86 | abi = get_abi(fpath) 87 | return abi, Web3.toHex(deploy_wasmb) 88 | 89 | 90 | def main(contract_file, get_shell=False): 91 | abi, bytecode = create_deploy_bytecode(contract_file) 92 | 93 | w3 = Web3(HTTPProvider(RPC_URL)) 94 | acct = w3.eth.account.privateKeyToAccount(PRIVATE_KEY) 95 | contract_ = w3.eth.contract( 96 | abi=abi, 97 | bytecode=bytecode 98 | ) 99 | 100 | construct_txn = contract_.constructor().buildTransaction({ 101 | 'from': acct.address, 102 | 'nonce': w3.eth.getTransactionCount(acct.address), 103 | # 'gas': 8 * 10**6, 104 | 'gasPrice': w3.toWei('1', 'wei')} 105 | ) 106 | 107 | # Deploy 108 | print(f"Account address: {acct.address}") 109 | receipt = None 110 | signed = acct.signTransaction(construct_txn) 111 | tx_hash = w3.eth.sendRawTransaction(signed.rawTransaction) 112 | print(f'Deployed Transaction: {tx_hash.hex()}') 113 | w3.eth.waitForTransactionReceipt(tx_hash) 114 | print('Mined transaction') 115 | receipt = w3.eth.waitForTransactionReceipt(tx_hash) 116 | print(f'Contract Address: {receipt["contractAddress"]}') 117 | print(f"Success?: {receipt['status'] == 1}") 118 | print(receipt) 119 | 120 | if get_shell: 121 | contract_ = w3.eth.contract( 122 | abi=abi, 123 | address=receipt["contractAddress"] 124 | ) 125 | print('Contract located on contract_ variable.') 126 | import pdb; pdb.set_trace() 127 | 128 | 129 | if __name__ == '__main__': 130 | parser = argparse.ArgumentParser() 131 | parser.add_argument("contract_file") 132 | parser.add_argument("--get-shell", help="Break to pdb debugger right after deploying.", action="store_true") 133 | args = parser.parse_args() 134 | main(args.contract_file, args.get_shell) 135 | -------------------------------------------------------------------------------- /tools/eth_postprocess.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WASM_FILE=$1 4 | 5 | set -ex 6 | 7 | #wasm2wat="tools/wabt/build/wasm2wat" 8 | #wat2wasm="tools/wabt/build/wat2wasm" 9 | 10 | wasm2wat="docker run --entrypoint=wasm2wat -w /code/ -v $(pwd):/code/ jacqueswww/nimclang " 11 | wat2wasm="docker run --entrypoint=wat2wasm -w /code/ -v $(pwd):/code/ jacqueswww/nimclang " 12 | 13 | # Replace "env" with "ethereum" 14 | $wasm2wat "$WASM_FILE" | 15 | sed 's/(import "env" /(import "ethereum" /g' | 16 | sed '/(export.*memory\|main.*/! s/(export.*//g' > ./wasm.tmp 17 | 18 | $wat2wasm -o "$WASM_FILE" ./wasm.tmp 19 | 20 | rm ./wasm.tmp 21 | -------------------------------------------------------------------------------- /tools/get_wabt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf wabt 4 | git clone --recursive https://github.com/WebAssembly/wabt wabt 5 | mkdir -p wabt/build 6 | cd wabt/build 7 | cmake ".." 8 | cmake --build "." 9 | -------------------------------------------------------------------------------- /tools/k256_sig.nim: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import nimcrypto 4 | 5 | 6 | 7 | proc main() = 8 | if paramCount() != 1: 9 | echo("Requires single parameter to be hashed") 10 | quit() 11 | 12 | echo(keccak256.digest(commandLineParams()[0])) 13 | 14 | 15 | when is_main_module: 16 | main() 17 | -------------------------------------------------------------------------------- /tools/nim.cfg: -------------------------------------------------------------------------------- 1 | --path:"$lib/packages/docutils" 2 | --path:"$nim/" 3 | -d:nimcore 4 | -d:nimOldCaseObjects 5 | -------------------------------------------------------------------------------- /tools/nimplayc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # For use inside Nimplay Docker image. 3 | NIM_FILE=$1 4 | if [[ $# -eq 2 ]]; 5 | then 6 | WASM_FILE="$2" 7 | else 8 | WASM_FILE="${NIM_FILE%.*}.wasm" 9 | fi 10 | 11 | PANIC_PATH="$(dirname ${NIM_FILE})" 12 | 13 | PATH_PARAMS="-p:/nimplay/ -p:/nimplay/vendor/nimcrypto -p:/nimplay/vendor/stint -p:/nimplay/vendor/nim-stew/" 14 | NIM_CLANG_C_FLAGS="--passC:\""--target=wasm32-unknown-unknown-wasm\"" --passC:\""-I./include\""" 15 | NIM_CLANG_LINKER_FLAGS="--clang.options.linker:\"-nostdlib -Wl,--no-entry,--allow-undefined,--strip-all,--export-dynamic\" --passL:\"--target=wasm32-unknown-unknown-wasm\"" 16 | NIM_CLANG_ALL_FLAGS="${NIM_CLANG_C_FLAGS} ${NIM_CLANG_LINKER_FLAGS} --os:standalone --cpu:i386 --cc:clang --gc:none --nomain -d:release" 17 | NIM_CLANG_WASM32_C="nim c -d:osk256_sig ${PATH_PARAMS} ${NIM_CLANG_ALL_FLAGS}" 18 | FULL_CMD="${NIM_CLANG_WASM32_C} --out:${WASM_FILE} ${NIM_FILE}" 19 | 20 | 21 | cp /nimplay/examples/panicoverride.nim $PANIC_PATH/ 22 | echo $FULL_CMD 23 | eval $FULL_CMD 24 | if [ $? -ne 0 ]; then 25 | rm $PANIC_PATH/panicoverride.nim 26 | exit 1 27 | fi 28 | 29 | 30 | # Postprocess produced wasm file 31 | wasm2wat="wasm2wat" 32 | wat2wasm="wat2wasm" 33 | 34 | # Replace "env" with "ethereum" 35 | $wasm2wat "$WASM_FILE" | 36 | sed 's/(import "env" /(import "ethereum" /g' | 37 | sed '/(export.*memory\|main.*/! s/(export.*//g' > ./wasm.tmp 38 | 39 | $wat2wasm -o "$WASM_FILE" ./wasm.tmp 40 | 41 | rm ./wasm.tmp 42 | rm $PANIC_PATH/panicoverride.nim 43 | -------------------------------------------------------------------------------- /tools/substrate_postprocess.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WASM_FILE=$1 4 | 5 | set -ex 6 | 7 | #wasm2wat="tools/wabt/build/wasm2wat" 8 | #wat2wasm="tools/wabt/build/wat2wasm" 9 | 10 | wasm2wat="docker run --entrypoint=wasm2wat -w /code/ -v $(pwd):/code/ jacqueswww/nimclang " 11 | wat2wasm="docker run --entrypoint=wat2wasm -w /code/ -v $(pwd):/code/ jacqueswww/nimclang " 12 | 13 | # Replace "env" with "ethereum" 14 | $wasm2wat "$WASM_FILE" | 15 | sed '/(export.*deploy\|call.*/! s/(export.*//g' > ./wasm.tmp 16 | 17 | $wat2wasm -o "$WASM_FILE" ./wasm.tmp 18 | 19 | rm ./wasm.tmp 20 | --------------------------------------------------------------------------------