├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── Doxyfile ├── LICENSE ├── README.md ├── SECURITY.md ├── azure-pipelines-gh-pages.yml ├── azure-pipelines.yml ├── cmake ├── evm4ccf.app.cmake └── evm4ccf.tests.cmake ├── include ├── ethereum_transaction.h ├── rpc_types.h └── rpc_types_serialization.inl ├── samples ├── erc20_deploy_and_transfer.py ├── provider.py └── utils.py ├── sphinx ├── Makefile ├── requirements.txt └── source │ ├── _static │ └── .gitkeep │ ├── _templates │ └── .gitkeep │ ├── api.rst │ ├── conf.py │ ├── demos.rst │ ├── implementation.rst │ ├── index.rst │ ├── introduction.rst │ └── rpcinterface.rst ├── src └── app │ ├── account_proxy.h │ ├── ethereum_state.h │ ├── evm_for_ccf.cpp │ ├── msgpacktypes.h │ ├── nljsontypes.h │ ├── oe_sign.conf │ └── tables.h └── tests ├── contracts ├── Ballot.sol ├── Ballot_combined.json ├── Call1.sol ├── Call1_combined.json ├── Call2.sol ├── Call2_combined.json ├── ERC20.sol ├── ERC20_combined.json ├── Events.sol ├── Events_combined.json ├── SimpleStore.sol ├── SimpleStore_combined.json └── compile.sh ├── erc20_transfers_scenario.json ├── eth_call.cpp ├── eth_sendTransaction.cpp ├── eth_signature.cpp ├── event_logs.cpp ├── kv_conversions.cpp ├── requirements.txt ├── shared.cpp ├── shared.h ├── signed_transactions.cpp └── tests.sh /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: AlwaysBreak 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: DontAlign 9 | AlignOperands: false 10 | AlignTrailingComments: false 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: true 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterObjCDeclaration: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | AfterExternBlock: true 33 | BeforeCatch: true 34 | BeforeElse: true 35 | IndentBraces: false 36 | SplitEmptyFunction: false 37 | SplitEmptyRecord: false 38 | SplitEmptyNamespace: false 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Custom 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: false 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: AfterColon 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 80 48 | CommentPragmas: '^ IWYU pragma:' 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 51 | ConstructorInitializerIndentWidth: 2 52 | ContinuationIndentWidth: 2 53 | Cpp11BracedListStyle: true 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: true 58 | ForEachMacros: 59 | - Q_FOREACH 60 | IncludeBlocks: Regroup 61 | IncludeCategories: 62 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 63 | Priority: 2 64 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 65 | Priority: 3 66 | - Regex: '.*' 67 | Priority: 1 68 | IncludeIsMainRegex: '(Test)?$' 69 | IndentCaseLabels: true 70 | IndentPPDirectives: AfterHash 71 | IndentWidth: 2 72 | IndentWrappedFunctionNames: false 73 | JavaScriptQuotes: Leave 74 | JavaScriptWrapImports: true 75 | KeepEmptyLinesAtTheStartOfBlocks: false 76 | MacroBlockBegin: '' 77 | MacroBlockEnd: '' 78 | MaxEmptyLinesToKeep: 1 79 | NamespaceIndentation: All 80 | ObjCBlockIndentWidth: 2 81 | ObjCSpaceAfterProperty: false 82 | ObjCSpaceBeforeProtocolList: true 83 | PenaltyBreakAssignment: 2 84 | PenaltyBreakBeforeFirstCallParameter: 19 85 | PenaltyBreakComment: 300 86 | PenaltyBreakFirstLessLess: 120 87 | PenaltyBreakString: 1000 88 | PenaltyExcessCharacter: 1000000 89 | PenaltyReturnTypeOnItsOwnLine: 600 90 | PointerAlignment: Left 91 | ReflowComments: true 92 | SortIncludes: true 93 | SortUsingDeclarations: true 94 | SpaceAfterCStyleCast: false 95 | SpaceAfterTemplateKeyword: true 96 | SpaceBeforeAssignmentOperators: true 97 | SpaceBeforeParens: ControlStatements 98 | SpaceInEmptyParentheses: false 99 | SpacesBeforeTrailingComments: 1 100 | SpacesInAngles: false 101 | SpacesInContainerLiterals: false 102 | SpacesInCStyleCastParentheses: false 103 | SpacesInParentheses: false 104 | SpacesInSquareBrackets: false 105 | Standard: Cpp11 106 | TabWidth: 2 107 | UseTab: Never 108 | ... 109 | 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | debug*/ 3 | release*/ 4 | doxygen/ 5 | 6 | .vscode/ 7 | 8 | tests/env/ 9 | sphinx/env/ 10 | 11 | __pycache__/ 12 | 13 | getting_started/*.retry 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "CCF"] 2 | path = CCF 3 | url = https://github.com/Microsoft/CCF.git 4 | [submodule "eEVM"] 5 | path = eEVM 6 | url = https://github.com/Microsoft/eEVM.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | 3 | add_definitions(-DOE_API_VERSION=2) 4 | 5 | # Paths to dependencies - currently explicit 6 | set(CCF_DIR ${CMAKE_CURRENT_SOURCE_DIR}/CCF) 7 | set(EVM_DIR ${CMAKE_CURRENT_SOURCE_DIR}/eEVM) 8 | 9 | include(${CCF_DIR}/cmake/preproject.cmake) 10 | 11 | project(evm_for_ccf C CXX) 12 | 13 | include(${CCF_DIR}/cmake/common.cmake) 14 | 15 | # TODO: This should not be duplicated here. Ideally it would be exposed independently 16 | # as an optional target, but this requires significant CMake refactoring. When member 17 | # commands are signed from Python in the CCF test infrastructure, this can be removed 18 | # MemberClient executable 19 | add_executable(memberclient ${CCF_DIR}/src/clients/memberclient.cpp) 20 | use_client_mbedtls(memberclient) 21 | target_link_libraries(memberclient PRIVATE 22 | ${CMAKE_THREAD_LIBS_INIT} 23 | ccfcrypto.host 24 | secp256k1.host 25 | http_parser.host 26 | ) 27 | 28 | add_subdirectory(${EVM_DIR}/3rdparty) 29 | 30 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/tests.sh ${CMAKE_CURRENT_BINARY_DIR}/tests.sh COPYONLY) 31 | 32 | option(RECORD_TRACE "Record a detailed trace of EVM execution when transaction fails" OFF) 33 | if(RECORD_TRACE) 34 | add_definitions(-DRECORD_TRACE) 35 | endif(RECORD_TRACE) 36 | 37 | include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/evm4ccf.app.cmake) 38 | 39 | option(BUILD_TESTS "Build tests" ON) 40 | option(BUILD_SAMPLES "Build samples" ON) 41 | 42 | if(BUILD_TESTS) 43 | enable_testing() 44 | include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/evm4ccf.tests.cmake) 45 | endif() 46 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ⚠️ 2 | #### _This repository is based on a very old version of CCF, and is no longer being updated. For up-to-date sample apps for CCF, see the main CCF repo: https://github.com/Microsoft/CCF_ 3 | ## ⚠️ 4 | 5 | # EVM for CCF 6 | 7 | This repository contains a sample application for the Confidential Consortium Framework ([CCF](https://github.com/Microsoft/CCF)) running an Ethereum Virtual Machine ([EVM](https://github.com/Microsoft/eEVM/)). This demonstrates how to build CCF from an external project, while also showcasing CCF's deterministic commits, dynamic confidentiality, and high performance. 8 | 9 | The app exposes API endpoints based on the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specification (eg - `eth_sendRawTransaction`, `eth_getTransactionReceipt`), so some standard Ethereum tooling can be reused by merely modifying the transport layer to communicate with CCF. 10 | 11 | ## Contents 12 | 13 | | File/folder | Description | 14 | |-------------------|--------------------------------------------| 15 | | `src` | Source code for the EVM4CCF app | 16 | | `tests` | Unit tests for the app's key functionality | 17 | | `samples` | End-to-end tests, driving an EVM4CCF instance with standard web3.py tools| 18 | 19 | ## Prerequisites 20 | 21 | This sample requires an SGX-enabled VM with CCF's dependencies. Installation of these requirements is described in [CCF's documentation](https://microsoft.github.io/CCF/quickstart/requirements.html#environment-setup). 22 | 23 | ## Setup 24 | 25 | ``` 26 | git clone --recurse-submodules https://github.com/microsoft/EVM-for-CCF.git 27 | cd EVM-for-CCF 28 | mkdir build 29 | cd build 30 | cmake .. -GNinja 31 | ninja 32 | ``` 33 | 34 | ## Running the sample 35 | 36 | To run the full test suite: 37 | 38 | ``` 39 | cd build 40 | ./tests.sh -VV 41 | ``` 42 | 43 | To launch a local instance for manual testing: 44 | 45 | ``` 46 | cd build 47 | ./tests.sh -N 48 | source env/bin/activate 49 | export PYTHONPATH=../CCF/tests 50 | python ../CCF/tests/start_network.py -g ../CCF/src/runtime_config/gov.lua -p libevm4ccf 51 | 52 | ... 53 | Started CCF network with the following nodes: 54 | Node [ 0] = 127.163.125.22:40718 55 | Node [ 1] = 127.40.220.213:32917 56 | Node [ 2] = 127.42.144.73:40275 57 | ``` 58 | 59 | User transactions can then be submitted as described in the [CCF documentation](https://microsoft.github.io/CCF/users/issue_commands.html), or via [web3.py](https://web3py.readthedocs.io/) with the `CCFProvider` class defined in `samples/provider.py`. 60 | 61 | ## Key concepts 62 | 63 | CCF is a framework for building fault-tolerant, high-performance, fully-confidential distributed services, hosting a user-defined application. In this case the user-defined application is an interpreter for Ethereum bytecode, executing smart contracts entirely inside a [TEE](https://en.wikipedia.org/wiki/Trusted_execution_environment). 64 | 65 | This service looks in many ways like a traditional Ethereum node, but has some fundamental differences: 66 | - Consensus is deterministic rather than probabilistic. Since we trust the executing node, we do not need to re-execute on every node or wait for multiple block commits. There is a single transaction history, with no forks. 67 | - There are no local nodes. Users do not run their own node, trusting it with key access and potentially private state. Instead all nodes run inside enclaves, maintaining privacy and guaranteeing execution integrity, regardless of where those enclaves are actually hosted. 68 | - State is confidential, and that confidentiality is entirely controlled by smart contract logic. The app does not produce a public log of all transactions, and it does not reveal the resulting state to all users. The only access to state is by calling methods on smart contracts, where arbitrarily complex and dynamic restrictions can be applied. 69 | 70 | ## Contributing 71 | 72 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 73 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 74 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 75 | 76 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 77 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 78 | provided by the bot. You will only need to do this once across all repos using our CLA. 79 | 80 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 81 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 82 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 83 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /azure-pipelines-gh-pages.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | jobs: 5 | - job: build_and_publish_docs 6 | container: ccfciteam/ccf-ci-18.04-oe-0.7.0-sgx:latest 7 | pool: Ubuntu-1804-D8s_v3 8 | 9 | steps: 10 | - checkout: self 11 | clean: true 12 | submodules: true 13 | 14 | - script: doxygen 15 | displayName: Doxgen 16 | 17 | - script: | 18 | python3.7 -m venv env 19 | source env/bin/activate 20 | pip install wheel 21 | pip install -U -r requirements.txt 22 | make html 23 | displayName: Sphinx 24 | workingDirectory: sphinx 25 | 26 | - script: | 27 | git init 28 | git config --local user.name "Azure Pipelines" 29 | git config --local user.email "azuredevops@microsoft.com" 30 | git add . 31 | git commit -m "[ci skip] commit generated documentation" 32 | displayName: 'Commit pages' 33 | workingDirectory: sphinx/build/html 34 | 35 | - task: DownloadSecureFile@1 36 | inputs: 37 | secureFile: evm4ccf_ghpages 38 | displayName: 'Get the deploy key' 39 | 40 | - script: | 41 | mv $DOWNLOADSECUREFILE_SECUREFILEPATH deploy_key 42 | chmod 600 deploy_key 43 | mkdir ~/.ssh 44 | chmod 700 ~/.ssh 45 | ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts 46 | git remote add origin git@github.com:microsoft/EVM-for-CCF.git 47 | GIT_SSH_COMMAND="ssh -i deploy_key" git push -f origin HEAD:gh-pages 48 | displayName: 'Publish GitHub Pages' 49 | condition: | 50 | and(not(eq(variables['Build.Reason'], 'PullRequest')), 51 | eq(variables['Build.SourceBranch'], 'refs/heads/master')) 52 | workingDirectory: sphinx/build/html 53 | 54 | - script: rm deploy_key || true 55 | displayName: 'Make sure key is removed' 56 | workingDirectory: sphinx/build/html 57 | condition: succeededOrFailed() 58 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # https://aka.ms/yaml 2 | 3 | trigger: 4 | batch: true 5 | branches: 6 | include: 7 | - "master" 8 | paths: 9 | include: 10 | - '*' 11 | exclude: 12 | - '*.md' 13 | - 'sphinx/*' 14 | 15 | pr: 16 | autoCancel: true 17 | branches: 18 | include: 19 | - master 20 | paths: 21 | include: 22 | - '*' 23 | exclude: 24 | - '*.md' 25 | - 'sphinx/*' 26 | 27 | jobs: 28 | - job: build_and_test_sgx 29 | displayName: Build and Test SGX 30 | pool: Ubuntu-1804-DC4s 31 | container: 32 | image: ccfciteam/ccf-ci-18.04-oe-0.7.0-sgx:latest 33 | options: --publish-all --device /dev/sgx:/dev/sgx 34 | 35 | steps: 36 | - checkout: self 37 | clean: true 38 | submodules: true 39 | 40 | - script: mkdir build 41 | displayName: 'Create build directory' 42 | 43 | - script: cmake .. -GNinja -L 44 | displayName: CMake 45 | workingDirectory: build 46 | 47 | - script: ninja 48 | displayName: Build 49 | workingDirectory: build 50 | 51 | - script: ./tests.sh -VV 52 | displayName: Tests 53 | workingDirectory: build 54 | 55 | - job: build_and_test_nosgx 56 | displayName: Build and Test NoSGX 57 | pool: Ubuntu-1804-D8s_v3 58 | container: ccfciteam/ccf-ci-18.04-oe-0.7.0-sgx:latest 59 | 60 | steps: 61 | - checkout: self 62 | clean: true 63 | submodules: true 64 | 65 | - script: mkdir build 66 | displayName: 'Create build directory' 67 | 68 | - script: cmake .. -GNinja -DTARGET=virtual -L 69 | displayName: CMake (virtual) 70 | workingDirectory: build 71 | 72 | - script: ninja 73 | displayName: Build 74 | workingDirectory: build 75 | 76 | - script: ./tests.sh -VV 77 | displayName: Tests 78 | workingDirectory: build 79 | -------------------------------------------------------------------------------- /cmake/evm4ccf.app.cmake: -------------------------------------------------------------------------------- 1 | # This is not standalone - it expects to be included with options and variables configured 2 | 3 | # Build Keccak library for use inside the enclave 4 | file(GLOB KECCAK_SOURCES 5 | ${EVM_DIR}/3rdparty/keccak/*.c 6 | ) 7 | enable_language(ASM) 8 | add_enclave_library_c(keccak_enclave "${KECCAK_SOURCES}") 9 | 10 | 11 | # Build eEVM library for use inside the enclave 12 | set(EVM_CPP_FILES 13 | ${EVM_DIR}/src/disassembler.cpp 14 | ${EVM_DIR}/src/stack.cpp 15 | ${EVM_DIR}/src/transaction.cpp 16 | ${EVM_DIR}/src/util.cpp 17 | ${EVM_DIR}/src/processor.cpp) 18 | add_library(enclave_evm STATIC 19 | ${EVM_CPP_FILES}) 20 | target_compile_definitions(enclave_evm PRIVATE 21 | INSIDE_ENCLAVE 22 | _LIBCPP_HAS_THREAD_API_PTHREAD) 23 | # Not setting -nostdinc in order to pick up compiler specific xmmintrin.h. 24 | target_compile_options(enclave_evm PRIVATE 25 | -nostdinc++ 26 | -U__linux__) 27 | target_include_directories(enclave_evm SYSTEM PRIVATE 28 | ${OE_LIBCXX_INCLUDE_DIR} 29 | ${OE_LIBC_INCLUDE_DIR} 30 | ${EVM_DIR}/include 31 | ${EVM_DIR}/3rdparty) 32 | target_link_libraries(enclave_evm PRIVATE 33 | intx::intx) 34 | set_property(TARGET enclave_evm PROPERTY POSITION_INDEPENDENT_CODE ON) 35 | 36 | 37 | # Build app 38 | add_enclave_lib(evm4ccf 39 | ${CMAKE_CURRENT_LIST_DIR}/../src/app/oe_sign.conf 40 | ${CCF_DIR}/src/apps/sample_key.pem 41 | SRCS 42 | ${CMAKE_CURRENT_LIST_DIR}/../src/app/evm_for_ccf.cpp 43 | ${EVM_CPP_FILES} 44 | INCLUDE_DIRS 45 | ${CMAKE_CURRENT_LIST_DIR}/../include 46 | ${EVM_DIR}/include 47 | LINK_LIBS 48 | keccak_enclave 49 | intx::intx 50 | ) 51 | -------------------------------------------------------------------------------- /cmake/evm4ccf.tests.cmake: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | if(BUILD_TESTS) 4 | set(TESTS_DIR ${CMAKE_CURRENT_LIST_DIR}/../tests) 5 | set(SAMPLES_DIR ${CMAKE_CURRENT_LIST_DIR}/../samples) 6 | 7 | # Standalone test of app frontend 8 | add_unit_test(app_test 9 | ${TESTS_DIR}/shared.cpp 10 | ${TESTS_DIR}/kv_conversions.cpp 11 | ${TESTS_DIR}/eth_call.cpp 12 | ${TESTS_DIR}/eth_sendTransaction.cpp 13 | ${TESTS_DIR}/signed_transactions.cpp 14 | ${TESTS_DIR}/event_logs.cpp 15 | ${CMAKE_CURRENT_LIST_DIR}/../src/app/evm_for_ccf.cpp 16 | ${EVM_CPP_FILES} 17 | ) 18 | target_include_directories(app_test PRIVATE 19 | ${CMAKE_CURRENT_LIST_DIR}/../include 20 | ${EVM_DIR}/include 21 | ) 22 | target_link_libraries(app_test PRIVATE 23 | keccak_enclave 24 | secp256k1.host 25 | intx::intx 26 | ) 27 | 28 | set(ENV_CONTRACTS_DIR "CONTRACTS_DIR=${TESTS_DIR}/contracts") 29 | 30 | # Make compiled contracts available to app_test 31 | set_tests_properties( 32 | app_test 33 | PROPERTIES 34 | ENVIRONMENT "${ENV_CONTRACTS_DIR}" 35 | ) 36 | 37 | # Add an end-to-end test using CCF's generic Python scenario client 38 | add_e2e_test( 39 | NAME erc20_scenario 40 | PYTHON_SCRIPT ${CCF_DIR}/tests/e2e_scenarios.py 41 | ADDITIONAL_ARGS 42 | --scenario ${TESTS_DIR}/erc20_transfers_scenario.json 43 | ) 44 | 45 | # Add an end-to-end test using a web3.py-based client 46 | add_e2e_test( 47 | NAME erc20_python 48 | PYTHON_SCRIPT ${SAMPLES_DIR}/erc20_deploy_and_transfer.py 49 | ) 50 | 51 | # Make python test client framework importable for all end-to-end tests 52 | set_tests_properties( 53 | erc20_scenario 54 | erc20_python 55 | PROPERTIES 56 | ENVIRONMENT "${ENV_CONTRACTS_DIR};PYTHONPATH=${CCF_DIR}/tests:$ENV{PYTHONPATH}" 57 | ) 58 | endif() 59 | -------------------------------------------------------------------------------- /include/ethereum_transaction.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #pragma once 4 | 5 | #include "rpc_types.h" 6 | 7 | // CCF 8 | #include "tls/keypair.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace evm4ccf 14 | { 15 | struct ChainIDs 16 | { 17 | static constexpr size_t pre_eip_155 = 0; 18 | static constexpr size_t ethereum_mainnet = 1; 19 | static constexpr size_t expanse_mainnet = 2; 20 | static constexpr size_t ropsten = 3; 21 | static constexpr size_t rinkeby = 4; 22 | static constexpr size_t goerli = 5; 23 | static constexpr size_t kovan = 42; 24 | static constexpr size_t geth_private_default = 1337; 25 | }; 26 | 27 | // TODO: This may become constexpr, determined at compile time. For now it 28 | // is malleable. 29 | static size_t current_chain_id = ChainIDs::pre_eip_155; 30 | 31 | static constexpr size_t pre_155_v_start = 27; 32 | static constexpr size_t post_155_v_start = 35; 33 | 34 | inline bool is_pre_eip_155(size_t v) 35 | { 36 | return v == 27 || v == 28; 37 | } 38 | 39 | inline size_t to_ethereum_recovery_id(size_t rec_id) 40 | { 41 | if (rec_id > 3) 42 | { 43 | throw std::logic_error(fmt::format( 44 | "ECDSA recovery values should be between 0 and 3, {} is invalid", 45 | rec_id)); 46 | } 47 | 48 | if (rec_id > 1) 49 | { 50 | throw std::logic_error(fmt::format( 51 | "Ethereum only accepts finite curve coordinates, {} represents an " 52 | "infinite value", 53 | rec_id)); 54 | } 55 | 56 | if (current_chain_id == ChainIDs::pre_eip_155) 57 | { 58 | return rec_id + pre_155_v_start; 59 | } 60 | 61 | return rec_id + current_chain_id * 2 + post_155_v_start; 62 | } 63 | 64 | inline size_t from_ethereum_recovery_id(size_t v) 65 | { 66 | if (is_pre_eip_155(v)) 67 | { 68 | return v - pre_155_v_start; 69 | } 70 | 71 | constexpr auto min_valid_v = 37u; 72 | if (v < min_valid_v) 73 | { 74 | throw std::logic_error(fmt::format( 75 | "Expected v to encode a valid chain ID (must be at least {}), but is " 76 | "{}", 77 | min_valid_v, 78 | v)); 79 | } 80 | 81 | const size_t rec_id = (v - post_155_v_start) % 2; 82 | 83 | const size_t chain_id = ((v - rec_id) - post_155_v_start) / 2; 84 | if (chain_id != current_chain_id) 85 | { 86 | throw std::logic_error(fmt::format( 87 | "Parsed chain ID {} (from v {}), expected to find current chain ID {}", 88 | chain_id, 89 | v, 90 | current_chain_id)); 91 | } 92 | 93 | return rec_id; 94 | } 95 | 96 | inline eevm::rlp::ByteString encode_optional_address( 97 | const std::optional& address) 98 | { 99 | // The encoding of addresses must be either a fixed-length 20-bytes, or 100 | // the empty list for the null in contract-creation. If treated as a 101 | // number, any leading 0s would be stripped. 102 | eevm::rlp::ByteString encoded; 103 | if (address.has_value()) 104 | { 105 | constexpr size_t address_length = 20; 106 | uint8_t address_bytes[address_length] = {}; 107 | intx::be::trunc(address_bytes, *address); 108 | encoded.insert( 109 | encoded.end(), std::begin(address_bytes), std::end(address_bytes)); 110 | } 111 | return encoded; 112 | } 113 | 114 | inline eevm::Address get_address_from_public_key_asn1( 115 | const std::vector& asn1) 116 | { 117 | // Check the bytes are prefixed with the ASN.1 type tag we expect, 118 | // then return raw bytes without type tag prefix. 119 | if (asn1[0] != MBEDTLS_ASN1_OCTET_STRING) 120 | { 121 | throw std::logic_error(fmt::format( 122 | "Expected ASN.1 key to begin with {}, not {}", 123 | MBEDTLS_ASN1_OCTET_STRING, 124 | asn1[0])); 125 | } 126 | 127 | const std::vector bytes(asn1.begin() + 1, asn1.end()); 128 | const auto hashed = eevm::keccak_256(bytes); 129 | 130 | // Address is the last 20 bytes of 32-byte hash, so skip first 12 131 | return eevm::from_big_endian(hashed.data() + 12, 20u); 132 | } 133 | 134 | struct EthereumTransaction 135 | { 136 | protected: 137 | EthereumTransaction() {} 138 | 139 | public: 140 | size_t nonce; 141 | uint256_t gas_price; 142 | uint256_t gas; 143 | eevm::rlp::ByteString to; 144 | uint256_t value; 145 | eevm::rlp::ByteString data; 146 | 147 | EthereumTransaction(size_t nonce_, const rpcparams::MessageCall& tc) 148 | { 149 | nonce = nonce_; 150 | gas_price = tc.gas_price; 151 | gas = tc.gas; 152 | to = encode_optional_address(tc.to); 153 | value = tc.value; 154 | data = eevm::to_bytes(tc.data); 155 | } 156 | 157 | EthereumTransaction(const eevm::rlp::ByteString& encoded) 158 | { 159 | auto tup = eevm::rlp::decode< 160 | size_t, 161 | uint256_t, 162 | uint256_t, 163 | eevm::rlp::ByteString, 164 | uint256_t, 165 | eevm::rlp::ByteString>(encoded); 166 | 167 | nonce = std::get<0>(tup); 168 | gas_price = std::get<1>(tup); 169 | gas = std::get<2>(tup); 170 | to = std::get<3>(tup); 171 | value = std::get<4>(tup); 172 | data = std::get<5>(tup); 173 | } 174 | 175 | eevm::rlp::ByteString encode() const 176 | { 177 | return eevm::rlp::encode(nonce, gas_price, gas, to, value, data); 178 | } 179 | 180 | virtual eevm::KeccakHash to_be_signed() const 181 | { 182 | return eevm::keccak_256(encode()); 183 | } 184 | 185 | virtual void to_transaction_call(rpcparams::MessageCall& tc) const 186 | { 187 | tc.gas_price = gas_price; 188 | tc.gas = gas; 189 | if (to.empty()) 190 | { 191 | tc.to = std::nullopt; 192 | } 193 | else 194 | { 195 | tc.to = eevm::from_big_endian(to.data(), to.size()); 196 | } 197 | tc.value = value; 198 | tc.data = eevm::to_hex_string(data); 199 | } 200 | }; 201 | 202 | struct EthereumTransactionWithSignature : public EthereumTransaction 203 | { 204 | // In tls::RecoverableSignature, r and s are combined in a single fixed-size 205 | // array. The first 32 bytes contain r, the second 32 contain s. 206 | static constexpr size_t r_fixed_length = 32u; 207 | 208 | using PointCoord = uint256_t; 209 | uint8_t v; 210 | PointCoord r; 211 | PointCoord s; 212 | 213 | EthereumTransactionWithSignature( 214 | const EthereumTransaction& tx, 215 | uint8_t v_, 216 | const PointCoord& r_, 217 | const PointCoord& s_) : 218 | EthereumTransaction(tx) 219 | { 220 | v = v_; 221 | r = r_; 222 | s = s_; 223 | } 224 | 225 | EthereumTransactionWithSignature( 226 | const EthereumTransaction& tx, const tls::RecoverableSignature& sig) : 227 | EthereumTransaction(tx) 228 | { 229 | v = to_ethereum_recovery_id(sig.recovery_id); 230 | 231 | const auto s_data = sig.raw.begin() + r_fixed_length; 232 | r = eevm::from_big_endian(sig.raw.data(), r_fixed_length); 233 | s = eevm::from_big_endian(s_data, r_fixed_length); 234 | } 235 | 236 | EthereumTransactionWithSignature(const eevm::rlp::ByteString& encoded) 237 | { 238 | auto tup = eevm::rlp::decode< 239 | size_t, 240 | uint256_t, 241 | uint256_t, 242 | eevm::rlp::ByteString, 243 | uint256_t, 244 | eevm::rlp::ByteString, 245 | uint8_t, 246 | PointCoord, 247 | PointCoord>(encoded); 248 | 249 | nonce = std::get<0>(tup); 250 | gas_price = std::get<1>(tup); 251 | gas = std::get<2>(tup); 252 | to = std::get<3>(tup); 253 | value = std::get<4>(tup); 254 | data = std::get<5>(tup); 255 | v = std::get<6>(tup); 256 | r = std::get<7>(tup); 257 | s = std::get<8>(tup); 258 | } 259 | 260 | eevm::rlp::ByteString encode() const 261 | { 262 | return eevm::rlp::encode(nonce, gas_price, gas, to, value, data, v, r, s); 263 | } 264 | 265 | void to_recoverable_signature(tls::RecoverableSignature& sig) const 266 | { 267 | sig.recovery_id = from_ethereum_recovery_id(v); 268 | 269 | const auto s_begin = sig.raw.data() + r_fixed_length; 270 | eevm::to_big_endian(r, sig.raw.data()); 271 | eevm::to_big_endian(s, s_begin); 272 | } 273 | 274 | eevm::KeccakHash to_be_signed() const override 275 | { 276 | if (is_pre_eip_155(v)) 277 | { 278 | return EthereumTransaction::to_be_signed(); 279 | } 280 | 281 | // EIP-155 adds (CHAIN_ID, 0, 0) to the data which is hashed, but _only_ 282 | // for signing/recovering. The canonical transaction hash (produced by 283 | // encode(), used as transaction ID) is unaffected 284 | return eevm::keccak_256(eevm::rlp::encode( 285 | nonce, gas_price, gas, to, value, data, current_chain_id, 0, 0)); 286 | } 287 | 288 | void to_transaction_call(rpcparams::MessageCall& tc) const override 289 | { 290 | EthereumTransaction::to_transaction_call(tc); 291 | 292 | tls::RecoverableSignature rs; 293 | to_recoverable_signature(rs); 294 | const auto tbs = to_be_signed(); 295 | auto pubk = 296 | tls::PublicKey_k1Bitcoin::recover_key(rs, {tbs.data(), tbs.size()}); 297 | tc.from = get_address_from_public_key_asn1(pubk.public_key_asn1()); 298 | } 299 | }; 300 | 301 | inline EthereumTransactionWithSignature sign_transaction( 302 | tls::KeyPair_k1Bitcoin& kp, const EthereumTransaction& tx) 303 | { 304 | const auto tbs = tx.to_be_signed(); 305 | const auto signature = kp.sign_recoverable_hashed({tbs.data(), tbs.size()}); 306 | return EthereumTransactionWithSignature(tx, signature); 307 | } 308 | } // namespace evm4ccf -------------------------------------------------------------------------------- /include/rpc_types.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // STL 14 | #include 15 | #include 16 | 17 | namespace evm4ccf 18 | { 19 | using Balance = uint256_t; 20 | 21 | using BlockID = std::string; 22 | constexpr auto DefaultBlockID = "latest"; 23 | 24 | // Pass around hex-encoded strings that we can manipulate as long as possible, 25 | // only convert to actual byte arrays when needed 26 | using ByteData = std::string; 27 | 28 | using EthHash = uint256_t; 29 | using TxHash = EthHash; 30 | using BlockHash = EthHash; 31 | 32 | using ContractParticipants = std::set; 33 | 34 | // TODO(eddy|#refactoring): Reconcile this with eevm::Block 35 | struct BlockHeader 36 | { 37 | uint64_t number = {}; 38 | uint64_t difficulty = {}; 39 | uint64_t gas_limit = {}; 40 | uint64_t gas_used = {}; 41 | uint64_t timestamp = {}; 42 | eevm::Address miner = {}; 43 | BlockHash block_hash = {}; 44 | }; 45 | 46 | inline bool operator==(const BlockHeader& l, const BlockHeader& r) 47 | { 48 | return l.number == r.number && l.difficulty == r.difficulty && 49 | l.gas_limit == r.gas_limit && l.gas_used == r.gas_used && 50 | l.timestamp == r.timestamp && l.miner == r.miner && 51 | l.block_hash == r.block_hash; 52 | } 53 | 54 | namespace rpcparams 55 | { 56 | struct MessageCall 57 | { 58 | eevm::Address from = {}; 59 | std::optional to = std::nullopt; 60 | uint256_t gas = 90000; 61 | uint256_t gas_price = 0; 62 | uint256_t value = 0; 63 | ByteData data = {}; 64 | std::optional private_for = std::nullopt; 65 | }; 66 | 67 | struct AddressWithBlock 68 | { 69 | eevm::Address address = {}; 70 | BlockID block_id = DefaultBlockID; 71 | }; 72 | 73 | struct Call 74 | { 75 | MessageCall call_data = {}; 76 | BlockID block_id = DefaultBlockID; 77 | }; 78 | 79 | struct GetTransactionCount 80 | { 81 | eevm::Address address = {}; 82 | BlockID block_id = DefaultBlockID; 83 | }; 84 | 85 | struct GetTransactionReceipt 86 | { 87 | TxHash tx_hash = {}; 88 | }; 89 | 90 | struct SendRawTransaction 91 | { 92 | ByteData raw_transaction = {}; 93 | }; 94 | 95 | struct SendTransaction 96 | { 97 | MessageCall call_data = {}; 98 | }; 99 | } // namespace rpcparams 100 | 101 | namespace rpcresults 102 | { 103 | struct TxReceipt 104 | { 105 | TxHash transaction_hash = {}; 106 | uint256_t transaction_index = {}; 107 | BlockHash block_hash = {}; 108 | uint256_t block_number = {}; 109 | eevm::Address from = {}; 110 | std::optional to = std::nullopt; 111 | uint256_t cumulative_gas_used = {}; 112 | uint256_t gas_used = {}; 113 | std::optional contract_address = std::nullopt; 114 | std::vector logs = {}; 115 | // logs_bloom could be bitset for interaction, but is currently ignored 116 | std::array logs_bloom = {}; 117 | uint256_t status = {}; 118 | }; 119 | 120 | // "A transaction receipt object, or null when no receipt was found" 121 | using ReceiptResponse = std::optional; 122 | } // namespace rpcresults 123 | 124 | template 125 | struct RpcBuilder 126 | { 127 | using Tag = TTag; 128 | using Params = TParams; 129 | using Result = TResult; 130 | 131 | using In = jsonrpc::ProcedureCall; 132 | using Out = jsonrpc::Response; 133 | 134 | static constexpr auto name = Tag::name; 135 | 136 | static In make(jsonrpc::SeqNo n = 0) 137 | { 138 | In in; 139 | in.id = n; 140 | in.method = TTag::name; 141 | return in; 142 | } 143 | }; 144 | 145 | // Ethereum JSON-RPC 146 | namespace ethrpc 147 | { 148 | struct BlockNumberTag 149 | { 150 | static constexpr auto name = "eth_blockNumber"; 151 | }; 152 | using BlockNumber = RpcBuilder; 153 | 154 | struct CallTag 155 | { 156 | static constexpr auto name = "eth_call"; 157 | }; 158 | using Call = RpcBuilder; 159 | 160 | struct GetAccountsTag 161 | { 162 | static constexpr auto name = "eth_accounts"; 163 | }; 164 | using GetAccounts = 165 | RpcBuilder>; 166 | 167 | struct GetBalanceTag 168 | { 169 | static constexpr auto name = "eth_getBalance"; 170 | }; 171 | using GetBalance = 172 | RpcBuilder; 173 | 174 | struct GetCodeTag 175 | { 176 | static constexpr auto name = "eth_getCode"; 177 | }; 178 | using GetCode = 179 | RpcBuilder; 180 | 181 | struct GetTransactionCountTag 182 | { 183 | static constexpr auto name = "eth_getTransactionCount"; 184 | }; 185 | using GetTransactionCount = RpcBuilder< 186 | GetTransactionCountTag, 187 | rpcparams::GetTransactionCount, 188 | size_t>; 189 | 190 | struct GetTransactionReceiptTag 191 | { 192 | static constexpr auto name = "eth_getTransactionReceipt"; 193 | }; 194 | using GetTransactionReceipt = RpcBuilder< 195 | GetTransactionReceiptTag, 196 | rpcparams::GetTransactionReceipt, 197 | rpcresults::ReceiptResponse>; 198 | 199 | struct SendRawTransactionTag 200 | { 201 | static constexpr auto name = "eth_sendRawTransaction"; 202 | }; 203 | using SendRawTransaction = 204 | RpcBuilder; 205 | 206 | struct SendTransactionTag 207 | { 208 | static constexpr auto name = "eth_sendTransaction"; 209 | }; 210 | using SendTransaction = 211 | RpcBuilder; 212 | } // namespace ethrpc 213 | } // namespace evm4ccf 214 | 215 | #include "rpc_types_serialization.inl" 216 | -------------------------------------------------------------------------------- /include/rpc_types_serialization.inl: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace evm4ccf 8 | { 9 | template 10 | inline void array_from_hex_string( 11 | std::array& a, const std::string& s) 12 | { 13 | const auto stripped = eevm::strip(s); 14 | 15 | if (stripped.size() != N * 2) 16 | { 17 | throw std::logic_error( 18 | fmt::format("Expected {} characters, got {}", N * 2, stripped.size())); 19 | } 20 | 21 | for (auto i = 0; i < N; i++) 22 | { 23 | a[i] = static_cast( 24 | strtoul(stripped.substr(i * 2, 2).c_str(), nullptr, 16)); 25 | } 26 | } 27 | 28 | template 29 | inline void from_optional_hex_str( 30 | const nlohmann::json& j, const std::string& s, T& v) 31 | { 32 | const auto it = j.find(s); 33 | if ( 34 | it == j.end() || it->is_null() || 35 | (it->is_string() && it->get().empty())) 36 | { 37 | // Don't change v from default 38 | return; 39 | } 40 | else 41 | { 42 | v = eevm::to_uint256(*it); 43 | } 44 | } 45 | 46 | inline void require_object(const nlohmann::json& j) 47 | { 48 | if (!j.is_object()) 49 | { 50 | throw std::invalid_argument( 51 | fmt::format("Expected object, got: {}", j.dump())); 52 | } 53 | } 54 | 55 | inline void require_array(const nlohmann::json& j) 56 | { 57 | if (!j.is_array()) 58 | { 59 | throw std::invalid_argument( 60 | fmt::format("Expected array, got: {}", j.dump())); 61 | } 62 | } 63 | 64 | // 65 | inline void to_json(nlohmann::json& j, const BlockHeader& s) 66 | { 67 | j = nlohmann::json::object(); 68 | 69 | j["number"] = eevm::to_hex_string(s.number); 70 | j["difficulty"] = eevm::to_hex_string(s.difficulty); 71 | j["gasLimit"] = eevm::to_hex_string(s.gas_limit); 72 | j["gasUsed"] = eevm::to_hex_string(s.gas_used); 73 | j["timestamp"] = eevm::to_hex_string(s.timestamp); 74 | j["miner"] = eevm::to_checksum_address(s.miner); 75 | j["hash"] = eevm::to_hex_string(s.block_hash); 76 | } 77 | 78 | inline void from_json(const nlohmann::json& j, BlockHeader& s) 79 | { 80 | require_object(j); 81 | 82 | s.number = eevm::to_uint64(j["number"]); 83 | s.difficulty = eevm::to_uint64(j["difficulty"]); 84 | s.gas_limit = eevm::to_uint64(j["gasLimit"]); 85 | s.gas_used = eevm::to_uint64(j["gasUsed"]); 86 | s.timestamp = eevm::to_uint64(j["timestamp"]); 87 | s.miner = eevm::to_uint256(j["miner"]); 88 | s.block_hash = eevm::to_uint256(j["hash"]); 89 | } 90 | 91 | namespace rpcparams 92 | { 93 | // 94 | inline void to_json(nlohmann::json& j, const MessageCall& s) 95 | { 96 | j = nlohmann::json::object(); 97 | 98 | j["from"] = eevm::to_checksum_address(s.from); 99 | 100 | if (s.to.has_value()) 101 | { 102 | j["to"] = eevm::to_checksum_address(s.to.value()); 103 | } 104 | else 105 | { 106 | j["to"] = nullptr; 107 | } 108 | 109 | j["gas"] = eevm::to_hex_string(s.gas); 110 | j["gasPrice"] = eevm::to_hex_string(s.gas_price); 111 | j["value"] = eevm::to_hex_string(s.value); 112 | j["data"] = s.data; 113 | 114 | if (s.private_for.has_value()) 115 | { 116 | auto j_for = nlohmann::json::array(); 117 | for (const auto& a : s.private_for.value()) 118 | { 119 | j_for.push_back(eevm::to_checksum_address(a)); 120 | } 121 | j["privateFor"] = j_for; 122 | } 123 | } 124 | 125 | inline void from_json(const nlohmann::json& j, MessageCall& s) 126 | { 127 | require_object(j); 128 | 129 | s.from = eevm::to_uint256(j["from"]); 130 | from_optional_hex_str(j, "to", s.to); 131 | from_optional_hex_str(j, "gas", s.gas); 132 | from_optional_hex_str(j, "gasPrice", s.gas_price); 133 | from_optional_hex_str(j, "value", s.value); 134 | 135 | // Transactions in blocks use "input" rather than "data". To parse both 136 | // formats, check for either key 137 | const auto data_it = j.find("data"); 138 | const auto input_it = j.find("input"); 139 | if (data_it != j.end()) 140 | { 141 | s.data = *data_it; 142 | } 143 | else if (input_it != j.end()) 144 | { 145 | s.data = *input_it; 146 | } 147 | 148 | const auto private_for_it = j.find("privateFor"); 149 | if (private_for_it != j.end()) 150 | { 151 | s.private_for = ContractParticipants(); 152 | for (const auto& a : *private_for_it) 153 | { 154 | s.private_for->insert(eevm::to_uint256(a)); 155 | } 156 | } 157 | } 158 | 159 | // 160 | inline void to_json(nlohmann::json& j, const AddressWithBlock& s) 161 | { 162 | j = nlohmann::json::array(); 163 | j.push_back(eevm::to_checksum_address(s.address)); 164 | j.push_back(s.block_id); 165 | } 166 | 167 | inline void from_json(const nlohmann::json& j, AddressWithBlock& s) 168 | { 169 | require_array(j); 170 | s.address = eevm::to_uint256(j[0]); 171 | s.block_id = j[1]; 172 | } 173 | 174 | // 175 | inline void to_json(nlohmann::json& j, const Call& s) 176 | { 177 | j = nlohmann::json::array(); 178 | j.push_back(s.call_data); 179 | j.push_back(s.block_id); 180 | } 181 | 182 | inline void from_json(const nlohmann::json& j, Call& s) 183 | { 184 | require_array(j); 185 | s.call_data = j[0]; 186 | s.block_id = j[1]; 187 | } 188 | 189 | // 190 | inline void to_json(nlohmann::json& j, const GetTransactionCount& s) 191 | { 192 | j = nlohmann::json::array(); 193 | j.push_back(eevm::to_checksum_address(s.address)); 194 | j.push_back(s.block_id); 195 | } 196 | 197 | inline void from_json(const nlohmann::json& j, GetTransactionCount& s) 198 | { 199 | require_array(j); 200 | s.address = eevm::to_uint256(j[0]); 201 | s.block_id = j[1]; 202 | } 203 | 204 | // 205 | inline void to_json(nlohmann::json& j, const GetTransactionReceipt& s) 206 | { 207 | j = nlohmann::json::array(); 208 | j.push_back(eevm::to_hex_string(s.tx_hash)); 209 | } 210 | 211 | inline void from_json(const nlohmann::json& j, GetTransactionReceipt& s) 212 | { 213 | require_array(j); 214 | s.tx_hash = eevm::to_uint256(j[0]); 215 | } 216 | 217 | // 218 | inline void to_json(nlohmann::json& j, const SendTransaction& s) 219 | { 220 | j = nlohmann::json::array(); 221 | j.push_back(s.call_data); 222 | } 223 | 224 | inline void from_json(const nlohmann::json& j, SendTransaction& s) 225 | { 226 | require_array(j); 227 | s.call_data = j[0]; 228 | } 229 | 230 | // 231 | inline void to_json(nlohmann::json& j, const SendRawTransaction& s) 232 | { 233 | j = nlohmann::json::array(); 234 | j.push_back(s.raw_transaction); 235 | } 236 | 237 | inline void from_json(const nlohmann::json& j, SendRawTransaction& s) 238 | { 239 | require_array(j); 240 | s.raw_transaction = j[0]; 241 | } 242 | } // namespace rpcparams 243 | 244 | namespace rpcresults 245 | { 246 | // 247 | inline void to_json(nlohmann::json& j, const ReceiptResponse& s) 248 | { 249 | if (!s.has_value()) 250 | { 251 | j = nullptr; 252 | } 253 | else 254 | { 255 | j = nlohmann::json::object(); 256 | 257 | j["transactionHash"] = eevm::to_hex_string_fixed(s->transaction_hash); 258 | j["transactionIndex"] = eevm::to_hex_string(s->transaction_index); 259 | j["blockHash"] = eevm::to_hex_string_fixed(s->block_hash); 260 | j["blockNumber"] = eevm::to_hex_string(s->block_number); 261 | j["from"] = eevm::to_checksum_address(s->from); 262 | if (s->to.has_value()) 263 | { 264 | j["to"] = eevm::to_checksum_address(s->to.value()); 265 | } 266 | else 267 | { 268 | j["to"] = nullptr; 269 | } 270 | j["cumulativeGasUsed"] = eevm::to_hex_string(s->cumulative_gas_used); 271 | j["gasUsed"] = eevm::to_hex_string(s->gas_used); 272 | if (s->contract_address.has_value()) 273 | { 274 | j["contractAddress"] = 275 | eevm::to_checksum_address(s->contract_address.value()); 276 | } 277 | else 278 | { 279 | j["contractAddress"] = nullptr; 280 | } 281 | j["logs"] = s->logs; 282 | j["logsBloom"] = eevm::to_hex_string(s->logs_bloom); 283 | j["status"] = eevm::to_hex_string(s->status); 284 | } 285 | } 286 | 287 | inline void from_json(const nlohmann::json& j, ReceiptResponse& s) 288 | { 289 | if (j.is_null()) 290 | { 291 | s = std::nullopt; 292 | } 293 | else 294 | { 295 | require_object(j); 296 | 297 | s.emplace(); 298 | s->transaction_hash = eevm::to_uint256(j["transactionHash"]); 299 | s->transaction_index = eevm::to_uint256(j["transactionIndex"]); 300 | s->block_hash = eevm::to_uint256(j["blockHash"]); 301 | s->block_number = eevm::to_uint256(j["blockNumber"]); 302 | s->from = eevm::to_uint256(j["from"]); 303 | from_optional_hex_str(j, "to", s->to); 304 | s->cumulative_gas_used = eevm::to_uint256(j["cumulativeGasUsed"]); 305 | s->gas_used = eevm::to_uint256(j["gasUsed"]); 306 | from_optional_hex_str(j, "contractAddress", s->contract_address); 307 | s->logs = j["logs"].getlogs)>(); 308 | array_from_hex_string(s->logs_bloom, j["logsBloom"]); 309 | s->status = eevm::to_uint256(j["status"]); 310 | } 311 | } 312 | } // namespace rpcresults 313 | } // namespace evm4ccf 314 | -------------------------------------------------------------------------------- /samples/erc20_deploy_and_transfer.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | import json 4 | import os 5 | import web3 6 | 7 | from utils import * 8 | import e2e_args 9 | import infra.ccf 10 | import provider 11 | 12 | from loguru import logger as LOG 13 | 14 | 15 | class ERC20Contract: 16 | def __init__(self, contract): 17 | self.contract = contract 18 | 19 | def get_total_supply(self, caller): 20 | return caller.call(self.contract.functions.totalSupply()) 21 | 22 | def get_token_balance(self, caller, address=None): 23 | if address is None: 24 | address = caller.account.address 25 | return caller.call(self.contract.functions.balanceOf(address)) 26 | 27 | def print_balances(self, callers): 28 | balances = [ 29 | (caller.account.address, self.get_token_balance(caller)) 30 | for caller in callers 31 | ] 32 | for a, b in balances: 33 | LOG.info(f"{a}: {b}") 34 | 35 | def transfer_tokens(self, sender, to, amount): 36 | return sender.send_signed( 37 | self.contract.functions.transfer(to.account.address, amount) 38 | ) 39 | 40 | def transfer_and_check(self, sender, to, amount): 41 | transfer_receipt = self.transfer_tokens(sender, to, amount) 42 | 43 | events = self.contract.events.Transfer().processReceipt(transfer_receipt) 44 | for e in events: 45 | args = e.args 46 | topic_src = args._from 47 | topic_dst = args._to 48 | topic_amt = args._value 49 | if ( 50 | sender.account.address == topic_src 51 | and to.account.address == topic_dst 52 | and amount == topic_amt 53 | ): 54 | return True 55 | 56 | return False 57 | 58 | 59 | def read_erc20_contract_from_file(): 60 | env_name = "CONTRACTS_DIR" 61 | contracts_dir = os.getenv(env_name) 62 | if contracts_dir is None: 63 | raise RuntimeError(f"Cannot find contracts, please set env var '{env_name}'") 64 | file_path = os.path.join(contracts_dir, "ERC20_combined.json") 65 | return read_contract_from_file(file_path, "ERC20.sol:ERC20Token") 66 | 67 | 68 | def test_deploy(network, args): 69 | erc20_abi, erc20_bin = read_erc20_contract_from_file() 70 | primary, term = network.find_primary() 71 | 72 | with primary.user_client(format="json") as ccf_client: 73 | w3 = web3.Web3(provider.CCFProvider(ccf_client)) 74 | 75 | owner = Caller(web3.Account.create(), w3) 76 | 77 | LOG.info("Contract deployment") 78 | erc20_spec = w3.eth.contract(abi=erc20_abi, bytecode=erc20_bin) 79 | deploy_receipt = owner.send_signed(erc20_spec.constructor(10000)) 80 | 81 | network.erc20_contract_address = deploy_receipt.contractAddress 82 | network.owner_account = owner.account 83 | 84 | return network 85 | 86 | 87 | def test_transfers(network, args): 88 | erc20_abi, erc20_bin = read_erc20_contract_from_file() 89 | primary, term = network.find_primary() 90 | 91 | with primary.user_client(format="json") as ccf_client: 92 | w3 = web3.Web3(provider.CCFProvider(ccf_client)) 93 | 94 | erc20_contract = ERC20Contract( 95 | w3.eth.contract(abi=erc20_abi, address=network.erc20_contract_address) 96 | ) 97 | 98 | owner = Caller(network.owner_account, w3) 99 | alice = Caller(web3.Account.create(), w3) 100 | bob = Caller(web3.Account.create(), w3) 101 | 102 | LOG.info("Get balance of owner") 103 | owner_balance = erc20_contract.get_token_balance(owner) 104 | LOG.info(f"Owner balance: {owner_balance}") 105 | 106 | first_amount = owner_balance // 5 107 | LOG.info( 108 | "Transferring {} tokens from {} to {}".format( 109 | first_amount, owner.account.address, alice.account.address 110 | ), 111 | True, 112 | ) 113 | assert erc20_contract.transfer_and_check(owner, alice, first_amount) 114 | 115 | second_amount = owner_balance - first_amount 116 | LOG.info( 117 | "Transferring {} tokens from {} to {}".format( 118 | second_amount, owner.account.address, bob.account.address 119 | ) 120 | ) 121 | assert erc20_contract.transfer_and_check(owner, bob, second_amount) 122 | 123 | third_amount = second_amount // 3 124 | LOG.info( 125 | "Transferring {} tokens from {} to {}".format( 126 | third_amount, bob.account.address, alice.account.address 127 | ) 128 | ) 129 | assert erc20_contract.transfer_and_check(bob, alice, third_amount,) 130 | 131 | LOG.info("Balances:") 132 | erc20_contract.print_balances([owner, alice, bob]) 133 | 134 | # Send many transfers, pausing between batches so that notifications should be received 135 | for batch in range(3): 136 | for i in range(20): 137 | sender, receiver = random.sample([alice, bob], 2) 138 | amount = random.randint(1, 10) 139 | erc20_contract.transfer_and_check( 140 | sender, receiver, amount, 141 | ) 142 | time.sleep(2) 143 | 144 | LOG.info("Final balances:") 145 | erc20_contract.print_balances([owner, alice, bob]) 146 | 147 | return network 148 | 149 | 150 | def run(args): 151 | hosts = ["localhost", "localhost"] 152 | 153 | with infra.ccf.network( 154 | hosts, args.build_dir, args.debug_nodes, args.perf_nodes, pdb=args.pdb 155 | ) as network: 156 | network.start_and_join(args) 157 | 158 | network = test_deploy(network, args) 159 | network = test_transfers(network, args) 160 | 161 | 162 | if __name__ == "__main__": 163 | args = e2e_args.cli_args() 164 | args.package = "libevm4ccf" 165 | 166 | run(args) 167 | -------------------------------------------------------------------------------- /samples/provider.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | import itertools 4 | import web3 5 | 6 | # Implementation of web3 Provider which talks framed-JSON-over-TLS to a CCF node 7 | class CCFProvider(web3.providers.BaseProvider): 8 | def __init__(self, ccf_client, logging=False): 9 | self.middlewares = [] 10 | self.ccf_client = ccf_client 11 | if not logging: 12 | self.disable_logging() 13 | self.supported_methods = ccf_client.do("listMethods", {}).result["methods"] 14 | 15 | def disable_logging(self): 16 | self.ccf_client.rpc_loggers = () 17 | 18 | def make_request(self, method, params): 19 | if method not in self.supported_methods: 20 | raise web3.exceptions.CannotHandleRequest( 21 | f"CCF does not support '{method}'" 22 | ) 23 | return self.ccf_client.rpc(method, params).to_dict() 24 | 25 | def isConnected(self): 26 | return self.ccf_client.conn.fileno() != -1 27 | -------------------------------------------------------------------------------- /samples/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | import binascii 4 | import json 5 | import random 6 | import os 7 | import time 8 | 9 | import web3 10 | 11 | from loguru import logger as LOG 12 | 13 | 14 | class Caller: 15 | def __init__(self, account, w3): 16 | self.account = account 17 | self.w3 = w3 18 | self.default_transaction = { 19 | "from": self.account.address, 20 | "gas": 0, 21 | "gasPrice": 0, 22 | } 23 | 24 | def _build_transaction(self, fn, **kwargs): 25 | nonce = self.w3.eth.getTransactionCount(self.account.address) 26 | txn = fn.buildTransaction({**self.default_transaction, "nonce": nonce}) 27 | txn.update(**kwargs) 28 | return txn 29 | 30 | def send(self, fn, **kwargs): 31 | txn = self._build_transaction(fn, **kwargs) 32 | tx_hash = self.w3.eth.sendTransaction(txn) 33 | return self.w3.eth.waitForTransactionReceipt(tx_hash) 34 | 35 | def send_signed(self, fn, **kwargs): 36 | txn = self._build_transaction(fn, **kwargs) 37 | signed = self.account.signTransaction(txn) 38 | tx_hash = self.w3.eth.sendRawTransaction(signed.rawTransaction) 39 | return self.w3.eth.waitForTransactionReceipt(tx_hash) 40 | 41 | def call(self, fn): 42 | return fn.call(self.default_transaction) 43 | 44 | 45 | def read_contract_from_file(file_path, contract_element): 46 | assert os.path.isfile(file_path), f"{file_path} is not a file" 47 | LOG.info(f"Loading contract from {file_path}") 48 | with open(file_path) as f: 49 | file_json = json.load(f) 50 | j = file_json["contracts"][contract_element] 51 | contract_abi = json.loads(j["abi"]) 52 | contract_bin = j["bin"] 53 | 54 | return contract_abi, contract_bin 55 | 56 | -------------------------------------------------------------------------------- /sphinx/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = EVM-for-CCF 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | 22 | 23 | livehtml: 24 | sphinx-autobuild -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" 25 | -------------------------------------------------------------------------------- /sphinx/requirements.txt: -------------------------------------------------------------------------------- 1 | breathe 2 | sphinx 3 | sphinx_rtd_theme 4 | pygments-style-solarized 5 | bottle 6 | sphinx-autobuild 7 | sphinxcontrib-mermaid 8 | sphinx-jsonschema 9 | -------------------------------------------------------------------------------- /sphinx/source/_static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/EVM-for-CCF/7f3ffb57fa952be201d5e3bb0be6146e2e525531/sphinx/source/_static/.gitkeep -------------------------------------------------------------------------------- /sphinx/source/_templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/EVM-for-CCF/7f3ffb57fa952be201d5e3bb0be6146e2e525531/sphinx/source/_templates/.gitkeep -------------------------------------------------------------------------------- /sphinx/source/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | Application Entry Point 5 | ----------------------- 6 | 7 | .. doxygenfunction:: ccfapp::get_rpc_handler 8 | :project: EVM-for-CCF 9 | 10 | RPCs 11 | ---- 12 | 13 | See :ref:`rpc_list` 14 | 15 | eEVM 16 | ---- 17 | 18 | .. doxygenstruct:: eevm::Account 19 | :project: EVM-for-CCF 20 | :undoc-members: 21 | :members: 22 | 23 | .. doxygenstruct:: eevm::GlobalState 24 | :project: EVM-for-CCF 25 | :undoc-members: 26 | :members: 27 | 28 | .. doxygenclass:: eevm::Processor 29 | :project: EVM-for-CCF 30 | :undoc-members: 31 | :members: 32 | 33 | Ethereum App 34 | ------------ 35 | 36 | .. doxygenclass:: evm4ccf::EthereumState 37 | :project: EVM-for-CCF 38 | :members: 39 | 40 | .. doxygenclass:: evm4ccf::EVMForCCFFrontend 41 | :project: EVM-for-CCF 42 | :members: 43 | :protected-members: 44 | :private-members: 45 | :undoc-members: 46 | -------------------------------------------------------------------------------- /sphinx/source/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Configuration file for the Sphinx documentation builder. 5 | # 6 | # This file does only contain a selection of the most common options. For a 7 | # full list see the documentation: 8 | # http://www.sphinx-doc.org/en/master/config 9 | 10 | # -- Path setup -------------------------------------------------------------- 11 | 12 | # If extensions (or modules to document with autodoc) are in another directory, 13 | # add these directories to sys.path here. If the directory is relative to the 14 | # documentation root, use os.path.abspath to make it absolute, like shown here. 15 | # 16 | # import os 17 | # import sys 18 | # sys.path.insert(0, os.path.abspath('.')) 19 | 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = u"EVM-for-CCF" 24 | copyright = u"Microsoft Corporation" 25 | author = u"Microsoft Research" 26 | 27 | # The short X.Y version 28 | version = u"" 29 | # The full version, including alpha/beta/rc tags 30 | release = u"" 31 | 32 | 33 | # -- General configuration --------------------------------------------------- 34 | 35 | # If your documentation needs a minimal Sphinx version, state it here. 36 | # 37 | # needs_sphinx = '1.0' 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | "sphinx.ext.todo", 44 | "sphinx.ext.mathjax", 45 | "sphinx.ext.ifconfig", 46 | "sphinx.ext.viewcode", 47 | "breathe", 48 | "sphinxcontrib.mermaid", 49 | "sphinx.ext.autosectionlabel", 50 | "sphinx.ext.githubpages", 51 | "sphinx-jsonschema", 52 | ] 53 | 54 | autosectionlabel_prefix_document = True 55 | 56 | # Add any paths that contain templates here, relative to this directory. 57 | templates_path = ["_templates"] 58 | 59 | # The suffix(es) of source filenames. 60 | # You can specify multiple suffix as a list of string: 61 | # 62 | # source_suffix = ['.rst', '.md'] 63 | source_suffix = ".rst" 64 | 65 | # The master toctree document. 66 | master_doc = "index" 67 | 68 | # The language for content autogenerated by Sphinx. Refer to documentation 69 | # for a list of supported languages. 70 | # 71 | # This is also used if you do content translation via gettext catalogs. 72 | # Usually you set "language" from the command line for these cases. 73 | language = None 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | # This pattern also affects html_static_path and html_extra_path . 78 | exclude_patterns = [] 79 | 80 | # The name of the Pygments (syntax highlighting) style to use. 81 | pygments_style = "solarizeddark" 82 | 83 | 84 | # -- Options for HTML output ------------------------------------------------- 85 | 86 | # The theme to use for HTML and HTML Help pages. See the documentation for 87 | # a list of builtin themes. 88 | # 89 | html_theme = "sphinx_rtd_theme" 90 | 91 | # Theme options are theme-specific and customize the look and feel of a theme 92 | # further. For a list of options available for each theme, see the 93 | # documentation. 94 | # 95 | # html_theme_options = {} 96 | 97 | # Add any paths that contain custom static files (such as style sheets) here, 98 | # relative to this directory. They are copied after the builtin static files, 99 | # so a file named "default.css" will overwrite the builtin "default.css". 100 | html_static_path = ["_static"] 101 | 102 | # Custom sidebar templates, must be a dictionary that maps document names 103 | # to template names. 104 | # 105 | # The default sidebars (for documents that don't match any pattern) are 106 | # defined by theme itself. Builtin themes are using these templates by 107 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 108 | # 'searchbox.html']``. 109 | # 110 | # html_sidebars = {} 111 | 112 | 113 | # -- Options for HTMLHelp output --------------------------------------------- 114 | 115 | # Output file base name for HTML help builder. 116 | htmlhelp_basename = "EVM4CCF" 117 | 118 | 119 | # -- Options for LaTeX output ------------------------------------------------ 120 | 121 | latex_elements = { 122 | # The paper size ('letterpaper' or 'a4paper'). 123 | # 124 | # 'papersize': 'letterpaper', 125 | # The font size ('10pt', '11pt' or '12pt'). 126 | # 127 | # 'pointsize': '10pt', 128 | # Additional stuff for the LaTeX preamble. 129 | # 130 | # 'preamble': '', 131 | # Latex figure (float) alignment 132 | # 133 | # 'figure_align': 'htbp', 134 | } 135 | 136 | # Grouping the document tree into LaTeX files. List of tuples 137 | # (source start file, target name, title, 138 | # author, documentclass [howto, manual, or own class]). 139 | latex_documents = [ 140 | ( 141 | master_doc, 142 | "EVM4CCF.tex", 143 | u"EVM for CCF Documentation", 144 | author, 145 | "manual", 146 | ) 147 | ] 148 | 149 | 150 | # -- Options for manual page output ------------------------------------------ 151 | 152 | # One entry per manual page. List of tuples 153 | # (source start file, name, description, authors, manual section). 154 | man_pages = [ 155 | (master_doc, "evm-for-ccf", u"EVM for CCF Documentation", [author], 1) 156 | ] 157 | 158 | 159 | # -- Options for Texinfo output ---------------------------------------------- 160 | 161 | # Grouping the document tree into Texinfo files. List of tuples 162 | # (source start file, target name, title, author, 163 | # dir menu entry, description, category) 164 | texinfo_documents = [ 165 | ( 166 | master_doc, 167 | "EVM for CCF", 168 | u"EVM for CCF Documentation", 169 | author, 170 | "EVM for CCF", 171 | "EVM sample app for CCF.", 172 | "Miscellaneous", 173 | ) 174 | ] 175 | 176 | 177 | # -- Extension configuration ------------------------------------------------- 178 | 179 | # -- Options for todo extension ---------------------------------------------- 180 | 181 | # If true, `todo` and `todoList` produce output, else they produce nothing. 182 | todo_include_todos = True 183 | 184 | # -- Breathe configuration 185 | 186 | # Setup the breathe extension 187 | breathe_projects = {"EVM-for-CCF": "../../doxygen/xml"} 188 | breathe_default_project = "EVM-for-CCF" 189 | 190 | html_context = { 191 | "source_url_prefix": "https://github.com/Microsoft/EVM-for-CCF", 192 | "display_github": True, 193 | "github_user": "Microsoft", 194 | "github_repo": "EVM-for-CCF", 195 | "github_version": "master/sphinx/source/", 196 | } -------------------------------------------------------------------------------- /sphinx/source/demos.rst: -------------------------------------------------------------------------------- 1 | Demos 2 | ===== 3 | 4 | A client currently needs to send `JSON-RPC `_ requests over `TLS `_, as with other CCF apps (HTTP and WebSockets support is coming soon). 5 | 6 | The following demos show deployment of an `ERC20 `_ contract, followed by transactions which transfer tokens between multiple accounts. The Solidity source of the contract is in ``tests/contracts/ERC20.sol``, and the precompiled result ``ERC20_combined.sol`` is parsed by the demos. 7 | 8 | The commands to run each of these demos (and the environment variables that may need to be set) can be seen by running: 9 | 10 | .. code-block:: bash 11 | 12 | ctest -VV -N 13 | 14 | Python 15 | ------ 16 | 17 | The source for this demo is in ``samples/erc20_deploy_and_transfer.py``. It uses a `web3.py `_ with a custom `provider` to communicate with CCF. The demo deploys a new CCF network consisting of 2 local nodes, deploys an instance of the parsed ERC20 contract, and then transfers tokens between ethereum addresses. 18 | 19 | Note that the ``Caller`` defined in ``samples/utils.py`` is used to work around some assumptions in ``web3.py``. For instance the ``gas`` and ``gasPrice`` fields must be filled in explicitly, or else ``web3`` will make unsupported RPC calls to find sensible values. It is possible to use this provider to interact with EVM-for-CCF from a live terminal, but care must be taken to avoid these unsupported RPCs. 20 | 21 | JSON Scenario 22 | ------------- 23 | 24 | This demo builds on CCF's generic `end-to-end JSON scenario `_ test - read that for an overview of the general scenario format. 25 | 26 | This scenario is specified by ``tests/erc20_transfers_scenario.json``. The transactions are similar to those above. First it calls ``eth_sendTransaction`` to deploy an ERC20 contract, then ``eth_sendTransaction`` to transfer tokens and ``eth_call`` to read token balances. One key difference is that this uses fixed addresses rather than generating fresh addresses (from new Ethereum identities) each run - this lets us statically predict the deployed contract address. 27 | 28 | The simplest way to test running your own contracts and transactions against EVM for CCF is to edit this scenario file, or pass your own scenario file on the command line. 29 | 30 | -------------------------------------------------------------------------------- /sphinx/source/implementation.rst: -------------------------------------------------------------------------------- 1 | Implementation 2 | ============== 3 | 4 | The basic structure of a CCF app can be seen in the `example logging app `_ in the CCF repo. This app implements :cpp:func:`ccfapp::get_rpc_handler` in a similar way: 5 | 6 | .. literalinclude:: ../../src/app/evm_for_ccf.cpp 7 | :language: cpp 8 | :start-after: SNIPPET_START: rpc_handler 9 | :end-before: SNIPPET_END: rpc_handler 10 | :dedent: 2 11 | 12 | Multiple tables are created in CCF's :cpp:type:`kv::Store`: 13 | 14 | * maps from ethereum addresses to each piece of account state (ether balance, executable code, transaction nonce) 15 | * a map for all the EVM-accessible per-account storage (keyed by a concatenation of the account address and storage key) 16 | * a map from transaction hashes to their results, sufficient for producing minimal transaction receipts 17 | 18 | .. literalinclude:: ../../src/app/evm_for_ccf.cpp 19 | :language: cpp 20 | :start-after: SNIPPET_START: initialization 21 | :end-before: SNIPPET_END: initialization 22 | :dedent: 2 23 | 24 | Interface between EVM execution and KV-storage 25 | ---------------------------------------------- 26 | 27 | The constructor installs handlers for each supported RPC method. The body of each these handlers constructs an instance of :cpp:type:`evm4ccf::EthereumState` with :cpp:class:`kv::Map::TxViews` over the required tables. This is an implementation of the abstract :cpp:type:`eevm::GlobalState` which reads and writes from those :cpp:class`TxViews`, backed by CCF's :cpp:class:`kv::Store`. 28 | 29 | Where the transaction results in the execution of EVM bytecode (ie - `eth_sendTransaction`, `eth_call`), the handler passes this :cpp:type:`evm4ccf::EthereumState` to :cpp:func:`evm4ccf::EthereumFrontend::run_in_evm` which in turn calls :cpp:func:`eevm::Processor::run`. Any ``SSTORE`` opcodes in the executed bytecode will then result in writes to the EthereumState's :cpp:class:`kv::Map::TxView` : 30 | 31 | .. literalinclude:: ../../src/app/account_proxy.h 32 | :language: cpp 33 | :start-after: SNIPPET_START: store_impl 34 | :end-before: SNIPPET_END: store_impl 35 | :dedent: 2 36 | 37 | This adds to the CCF-transaction's write set, updating an element in the ``Storage`` table to the provided value. If this transaction is successfully committed, that write set and corresponding storage update will be replicated across all CCF nodes. 38 | 39 | Notes 40 | ----- 41 | 42 | - eEVM does not track gas. Gas is not spent during computation, and out-of-gas exceptions will not occur. 43 | - eEVM does not support post-Homestead opcodes or precompiled contracts. When compiling to EVM bytecode, be sure to specify the target EVM version. 44 | - Previous block state is inaccessible. EVM bytecode can access blockchain state directly through opcodes like ``BLOCKHASH``, ``NUMBER``, ``TIMESTAMP``. These will return placeholders in EVM for CCF, since there is no historical storage to read from. 45 | - While ``eth_getTransactionReceipt`` is implemented, and can be used to retrieve the address of deployed contracts, many of the receipt fields are not applicable to EVM for CCF and are left empty. 46 | - Event logs are not stored. These could be stored in the KV as well, with some decisions made regarding their intended privacy level. But we believe it is simpler for contracts to store all of their effects in contract state, rather than reporting some through a side channel. Since execution and storage are much cheaper than in a proof-of-work blockchain, this side channel is unnecessary. 47 | -------------------------------------------------------------------------------- /sphinx/source/index.rst: -------------------------------------------------------------------------------- 1 | EVM for CCF Documentation 2 | ======================================= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | introduction 9 | implementation 10 | rpcinterface 11 | demos 12 | api 13 | 14 | Index 15 | ===== 16 | 17 | * :ref:`genindex` 18 | -------------------------------------------------------------------------------- /sphinx/source/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | Overview 5 | -------- 6 | 7 | `EVM for CCF`_ is a sample application for the `Confidential Consortium Framework (CCF) `_. It runs EVM bytecode transactions through `Enclave EVM (eEVM) `_, with all permanent state backed by CCF's replicated, fault-tolerant :cpp:class:`kv::Store`. Since transactions are executed inside the enclave this can provide confidential, attestable execution of EVM bytecode and smart contracts at significantly higher throughputs (and lower latencies) than most Ethereum networks. 8 | 9 | An EVM for CCF service is a distributed network of TEEs running this sample application. This service can look like a standard Ethereum service, exposing the same API methods based on the `Ethereum JSON RPC `_ specification, but it has several crucial differences: 10 | 11 | * Users trust responses because they come from an auditable TEE, rather than because they come from a local node. Users do not run their own local nodes repeating every transaction execution - they contact a trusted service. 12 | * Consensus is deterministic rather than probabilistic. Rather than waiting for multiple proof-of-work blocks, users know that their results have been persisted once CCF replicates them (via Raft or PBFT). 13 | * Private state can be kept in smart contracts. Contract state is not publically visible on the ledger, it is only visible to the executing nodes. So contracts can work directly with private state, rather than hashes or transforms of it. 14 | 15 | Building and running tests 16 | -------------------------- 17 | 18 | To build from scratch: 19 | 20 | .. code-block:: bash 21 | 22 | mkdir build 23 | cd build 24 | cmake -GNinja .. 25 | ninja 26 | 27 | To run tests: 28 | 29 | .. code-block:: bash 30 | 31 | cd build 32 | ./tests.sh -VV 33 | 34 | Some of the tests spin up a CCF network using the Python infrastructure from the CCF repo. This requires several Python libraries to be installed, including `web3.py `_. 35 | The ``tests.sh`` creates a temporary Python environment and installs these dependencies before running the tests. 36 | 37 | This project is not currently tested on wide variety of platforms. We develop on Ubuntu18.04 with Python3.7, compiling with Clang8 - there may be compatibility problems with other versions. 38 | 39 | .. _`EVM for CCF`: https://github.com/microsoft/EVM-for-CCF -------------------------------------------------------------------------------- /sphinx/source/rpcinterface.rst: -------------------------------------------------------------------------------- 1 | RPC Interface 2 | ============= 3 | 4 | Where RPC methods have the same name as `Ethereum JSON RPC`_ the app will accept requests in the same format and return compatible responses. Any `default block parameters `_ must be ``"latest"`` as the app only has access to the current state. 5 | 6 | For example, to retrieve an account's code: 7 | 8 | - Request 9 | 10 | .. code-block:: json 11 | 12 | { 13 | "id":1, 14 | "jsonrpc": "2.0", 15 | "method": "eth_getCode", 16 | "params": [ 17 | "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", 18 | "latest" 19 | ] 20 | } 21 | 22 | - Response 23 | 24 | .. code-block:: json 25 | 26 | { 27 | "id":1, 28 | "jsonrpc": "2.0", 29 | "result": "0x60016008..." 30 | } 31 | 32 | Ethereum addresses, 32-byte numbers and byte strings should be presented hex-encoded, prefixed with '0x'. Any missing params in json will result in an error during deserialization. 33 | 34 | .. _rpc_list: 35 | 36 | Supported RPCs 37 | -------------- 38 | 39 | The following RPCs are currently supported: 40 | 41 | * ``eth_call`` 42 | * ``eth_getBalance`` 43 | * ``eth_getCode`` 44 | * ``eth_getTransactionCount`` 45 | * ``eth_getTransactionReceipt`` 46 | * ``eth_sendTransaction`` 47 | * ``eth_sendRawTransaction`` 48 | 49 | Many RPCs are not supported as they break the privacy model. For instance ``eth_getStorageAt`` should not be implemented, as it allows users to read arbitrary state from EVM storage. We want all such access to go through bytecode execution (ie - to call a method on a contract, with potential access controls), so this RPC is not implemented. 50 | 51 | Others do not match the execution model more generally. The service is responsible solely for execution, it is not a node owning a specific user identity, so ``eth_accounts`` does not make sense. All RPCs which request block state, events, or gas costs are similarly inapplicable and not implemented. 52 | 53 | .. _`Ethereum JSON RPC`: https://github.com/ethereum/wiki/wiki/JSON-RPC 54 | -------------------------------------------------------------------------------- /src/app/account_proxy.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #pragma once 4 | 5 | // EVM-for-CCF 6 | #include "tables.h" 7 | 8 | // eEVM 9 | #include 10 | #include 11 | 12 | namespace evm4ccf 13 | { 14 | // This implements both eevm::Account and eevm::Storage via ccf's KV 15 | struct AccountProxy : public eevm::Account, public eevm::Storage 16 | { 17 | eevm::Address address; 18 | mutable tables::Accounts::Views accounts_views; 19 | tables::Storage::TxView& storage; 20 | 21 | AccountProxy( 22 | const eevm::Address& a, 23 | const tables::Accounts::Views& av, 24 | tables::Storage::TxView& st) : 25 | address(a), 26 | accounts_views(av), 27 | storage(st) 28 | {} 29 | 30 | // Implementation of eevm::Account 31 | eevm::Address get_address() const override 32 | { 33 | return address; 34 | } 35 | 36 | uint256_t get_balance() const override 37 | { 38 | return accounts_views.balances->get(address).value_or(0); 39 | } 40 | 41 | void set_balance(const uint256_t& b) override 42 | { 43 | accounts_views.balances->put(address, b); 44 | } 45 | 46 | Nonce get_nonce() const override 47 | { 48 | return accounts_views.nonces->get(address).value_or(0); 49 | } 50 | 51 | void increment_nonce() override 52 | { 53 | auto nonce = get_nonce(); 54 | ++nonce; 55 | accounts_views.nonces->put(address, nonce); 56 | } 57 | 58 | eevm::Code get_code() const override 59 | { 60 | return accounts_views.codes->get(address).value_or(eevm::Code{}); 61 | } 62 | 63 | void set_code(eevm::Code&& c) override 64 | { 65 | accounts_views.codes->put(address, c); 66 | } 67 | 68 | // Implementation of eevm::Storage 69 | std::pair translate(const uint256_t& key) 70 | { 71 | return std::make_pair(address, key); 72 | } 73 | 74 | // SNIPPET_START: store_impl 75 | void store(const uint256_t& key, const uint256_t& value) override 76 | { 77 | storage.put(translate(key), value); 78 | } 79 | // SNIPPET_END: store_impl 80 | 81 | uint256_t load(const uint256_t& key) override 82 | { 83 | return storage.get(translate(key)).value_or(0); 84 | } 85 | 86 | bool remove(const uint256_t& key) override 87 | { 88 | return storage.remove(translate(key)); 89 | } 90 | }; 91 | } // namespace evm4ccf 92 | -------------------------------------------------------------------------------- /src/app/ethereum_state.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #pragma once 4 | 5 | // EVM-for-CCF 6 | #include "tables.h" 7 | 8 | // CCF 9 | #include "crypto/hash.h" 10 | 11 | // eEVM 12 | #include 13 | #include 14 | #include 15 | 16 | // STL/3rd-party 17 | #include 18 | 19 | namespace evm4ccf 20 | { 21 | // Implementation of eevm::GlobalState backed by ccf's KV 22 | class EthereumState : public eevm::GlobalState 23 | { 24 | eevm::Block current_block = {}; 25 | 26 | tables::Accounts::Views accounts; 27 | tables::Storage::TxView& tx_storage; 28 | 29 | std::map> cache; 30 | 31 | eevm::AccountState add_to_cache(const eevm::Address& address) 32 | { 33 | auto ib = cache.insert(std::make_pair( 34 | address, 35 | std::make_unique(address, accounts, tx_storage))); 36 | 37 | if (!ib.second) 38 | { 39 | throw std::logic_error(fmt::format( 40 | "Added account proxy to cache at address {}, but an " 41 | "entry already existed", 42 | eevm::to_checksum_address(address))); 43 | } 44 | 45 | auto& proxy = ib.first->second; 46 | return eevm::AccountState(*proxy, *proxy); 47 | } 48 | 49 | public: 50 | template 51 | EthereumState( 52 | const tables::Accounts::Views& acc_views, 53 | tables::Storage::TxView* views) : 54 | accounts(acc_views), 55 | tx_storage(*views) 56 | {} 57 | 58 | void remove(const eevm::Address& addr) override 59 | { 60 | throw std::logic_error("not implemented"); 61 | } 62 | 63 | eevm::AccountState get(const eevm::Address& address) override 64 | { 65 | // If account is already in cache, it can be returned 66 | auto cache_it = cache.find(address); 67 | if (cache_it != cache.end()) 68 | { 69 | auto& proxy = cache_it->second; 70 | return eevm::AccountState(*proxy, *proxy); 71 | } 72 | 73 | // If account doesn't already exist, it should be created 74 | const auto kind_it = accounts.balances->get(address); 75 | if (!kind_it.has_value()) 76 | { 77 | return create(address, 0, {}); 78 | } 79 | 80 | // Account exists in kv but not in cache - add a proxy for it 81 | return add_to_cache(address); 82 | } 83 | 84 | eevm::AccountState create( 85 | const eevm::Address& address, 86 | const uint256_t& balance = 0u, 87 | const eevm::Code& code = {}) override 88 | { 89 | eevm::Account::Nonce initial_nonce = 0; 90 | if (!code.empty()) 91 | { 92 | // Nonce of contracts should start at 1 93 | initial_nonce = 1; 94 | } 95 | 96 | // Write initial balance 97 | const auto balance_it = accounts.balances->get(address); 98 | if (balance_it.has_value()) 99 | { 100 | throw std::logic_error(fmt::format( 101 | "Trying to create account at {}, but it already has a balance", 102 | eevm::to_checksum_address(address))); 103 | } 104 | else 105 | { 106 | accounts.balances->put(address, balance); 107 | } 108 | 109 | // Write initial code 110 | const auto code_it = accounts.codes->get(address); 111 | if (code_it.has_value()) 112 | { 113 | throw std::logic_error(fmt::format( 114 | "Trying to create account at {}, but it already has code", 115 | eevm::to_checksum_address(address))); 116 | } 117 | else 118 | { 119 | accounts.codes->put(address, code); 120 | } 121 | 122 | // Write initial nonce 123 | const auto nonce_it = accounts.nonces->get(address); 124 | if (nonce_it.has_value()) 125 | { 126 | throw std::logic_error(fmt::format( 127 | "Trying to create account at {}, but it already has a nonce", 128 | eevm::to_checksum_address(address))); 129 | } 130 | else 131 | { 132 | accounts.nonces->put(address, initial_nonce); 133 | } 134 | 135 | return add_to_cache(address); 136 | } 137 | 138 | const eevm::Block& get_current_block() override 139 | { 140 | return current_block; 141 | } 142 | 143 | uint256_t get_block_hash(uint8_t offset) override 144 | { 145 | return 0; 146 | } 147 | }; 148 | } // namespace evm4ccf 149 | -------------------------------------------------------------------------------- /src/app/evm_for_ccf.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | // EVM-for-CCF 5 | #include "account_proxy.h" 6 | #include "ethereum_state.h" 7 | #include "ethereum_transaction.h" 8 | #include "tables.h" 9 | 10 | // CCF 11 | #include "ds/hash.h" 12 | #include "node/rpc/nodeinterface.h" 13 | #include "node/rpc/userfrontend.h" 14 | #include "rpc_types.h" 15 | 16 | // eEVM 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | // STL/3rd-party 24 | #include 25 | 26 | namespace evm4ccf 27 | { 28 | using namespace std; 29 | using namespace eevm; 30 | using namespace ccf; 31 | using namespace ccfapp; 32 | 33 | // 34 | // RPC handler class 35 | // 36 | class EVMForCCFFrontend : public UserRpcFrontend 37 | { 38 | tables::Accounts accounts; 39 | tables::Storage& storage; 40 | tables::Results& tx_results; 41 | 42 | EthereumState make_state(Store::Tx& tx) 43 | { 44 | return EthereumState(accounts.get_views(tx), tx.get_view(storage)); 45 | } 46 | 47 | void install_standard_rpcs() 48 | { 49 | auto call = [this](RequestArgs& args) { 50 | CallerId caller_id = args.caller_id; 51 | Store::Tx& tx = args.tx; 52 | ethrpc::Call::Params cp = args.params; 53 | 54 | if (!cp.call_data.to.has_value()) 55 | { 56 | return jsonrpc::error( 57 | jsonrpc::StandardErrorCodes::INVALID_PARAMS, "Missing 'to' field"); 58 | } 59 | 60 | auto es = make_state(tx); 61 | 62 | const auto e = run_in_evm(cp.call_data, es).first; 63 | 64 | if (e.er == ExitReason::returned || e.er == ExitReason::halted) 65 | { 66 | // Call should have no effect so we don't commit it. 67 | // Just return the result. 68 | return jsonrpc::success(to_hex_string(e.output)); 69 | } 70 | else 71 | { 72 | return jsonrpc::error( 73 | jsonrpc::StandardErrorCodes::INTERNAL_ERROR, e.exmsg); 74 | } 75 | }; 76 | 77 | auto get_balance = [this](Store::Tx& tx, const nlohmann::json& params) { 78 | rpcparams::AddressWithBlock ab = params; 79 | if (ab.block_id != "latest") 80 | { 81 | return jsonrpc::error( 82 | jsonrpc::StandardErrorCodes::INVALID_PARAMS, 83 | "Can only request latest block"); 84 | } 85 | 86 | auto es = make_state(tx); 87 | 88 | const auto account_state = es.get(ab.address); 89 | return jsonrpc::success(to_hex_string(account_state.acc.get_balance())); 90 | }; 91 | 92 | auto get_code = [this](Store::Tx& tx, const nlohmann::json& params) { 93 | rpcparams::AddressWithBlock ab = params; 94 | if (ab.block_id != "latest") 95 | { 96 | return jsonrpc::error( 97 | jsonrpc::StandardErrorCodes::INVALID_PARAMS, 98 | "Can only request latest block"); 99 | } 100 | 101 | auto es = make_state(tx); 102 | 103 | const auto account_state = es.get(ab.address); 104 | return jsonrpc::success(to_hex_string(account_state.acc.get_code())); 105 | }; 106 | 107 | auto get_transaction_count = 108 | [this](Store::Tx& tx, const nlohmann::json& params) { 109 | rpcparams::GetTransactionCount gtcp = params; 110 | if (gtcp.block_id != "latest") 111 | { 112 | return jsonrpc::error( 113 | jsonrpc::StandardErrorCodes::INVALID_PARAMS, 114 | "Can only request latest block"); 115 | } 116 | 117 | auto es = make_state(tx); 118 | auto account_state = es.get(gtcp.address); 119 | 120 | return jsonrpc::success(to_hex_string(account_state.acc.get_nonce())); 121 | }; 122 | 123 | auto send_raw_transaction = [this](RequestArgs& args) { 124 | rpcparams::SendRawTransaction srtp = args.params; 125 | 126 | eevm::rlp::ByteString in = eevm::to_bytes(srtp.raw_transaction); 127 | 128 | EthereumTransactionWithSignature eth_tx(in); 129 | 130 | rpcparams::MessageCall tc; 131 | eth_tx.to_transaction_call(tc); 132 | 133 | return execute_transaction(args.caller_id, tc, args.tx); 134 | }; 135 | 136 | auto send_transaction = [this](RequestArgs& args) { 137 | rpcparams::SendTransaction stp = args.params; 138 | 139 | return execute_transaction(args.caller_id, stp.call_data, args.tx); 140 | }; 141 | 142 | auto get_transaction_receipt = 143 | [this](Store::Tx& tx, const nlohmann::json& params) { 144 | rpcparams::GetTransactionReceipt gtrp = params; 145 | 146 | const TxHash& tx_hash = gtrp.tx_hash; 147 | 148 | auto results_view = tx.get_view(tx_results); 149 | const auto r = results_view->get(tx_hash); 150 | 151 | // "or null when no receipt was found" 152 | rpcresults::ReceiptResponse response = nullopt; 153 | if (r.has_value()) 154 | { 155 | const auto& tx_result = r.value(); 156 | 157 | response.emplace(); 158 | response->transaction_hash = tx_hash; 159 | if (tx_result.contract_address.has_value()) 160 | { 161 | response->contract_address = tx_result.contract_address; 162 | } 163 | else 164 | { 165 | response->to = 0x0; 166 | } 167 | response->logs = tx_result.logs; 168 | response->status = 1; 169 | } 170 | 171 | return jsonrpc::success(response); 172 | }; 173 | 174 | install(ethrpc::Call::name, call, Read); 175 | install(ethrpc::GetBalance::name, get_balance, Read); 176 | install(ethrpc::GetCode::name, get_code, Read); 177 | install(ethrpc::GetTransactionCount::name, get_transaction_count, Read); 178 | install( 179 | ethrpc::GetTransactionReceipt::name, get_transaction_receipt, Read); 180 | install(ethrpc::SendRawTransaction::name, send_raw_transaction, Write); 181 | install(ethrpc::SendTransaction::name, send_transaction, Write); 182 | } 183 | 184 | public: 185 | // SNIPPET_START: initialization 186 | EVMForCCFFrontend(NetworkTables& nwt, AbstractNotifier& notifier) : 187 | UserRpcFrontend(*nwt.tables), 188 | accounts{tables.create("eth.account.balance"), 189 | tables.create("eth.account.code"), 190 | tables.create("eth.account.nonce")}, 191 | storage(tables.create("eth.storage")), 192 | tx_results(tables.create("eth.txresults")) 193 | // SNIPPET_END: initialization 194 | { 195 | install_standard_rpcs(); 196 | } 197 | 198 | private: 199 | static std::pair run_in_evm( 200 | const rpcparams::MessageCall& call_data, 201 | EthereumState& es, 202 | LogHandler& log_handler) 203 | { 204 | Address from = call_data.from; 205 | Address to; 206 | 207 | if (call_data.to.has_value()) 208 | { 209 | to = call_data.to.value(); 210 | } 211 | else 212 | { 213 | // If there's no to field, create a new account to deploy this to 214 | const auto from_state = es.get(from); 215 | to = eevm::generate_address( 216 | from_state.acc.get_address(), from_state.acc.get_nonce()); 217 | es.create(to, call_data.gas, to_bytes(call_data.data)); 218 | } 219 | 220 | Transaction eth_tx(from, log_handler); 221 | 222 | auto account_state = es.get(to); 223 | 224 | #ifdef RECORD_TRACE 225 | eevm::Trace tr; 226 | #endif 227 | 228 | Processor proc(es); 229 | const auto result = proc.run( 230 | eth_tx, 231 | from, 232 | account_state, 233 | to_bytes(call_data.data), 234 | call_data.value 235 | #ifdef RECORD_TRACE 236 | , 237 | &tr 238 | #endif 239 | ); 240 | 241 | #ifdef RECORD_TRACE 242 | if (result.er == ExitReason::threw) 243 | { 244 | LOG_INFO_FMT("--- Trace of failing evm execution ---\n{}", tr); 245 | } 246 | #endif 247 | 248 | return std::make_pair(result, account_state); 249 | } 250 | 251 | static pair run_in_evm( 252 | const rpcparams::MessageCall& call_data, EthereumState& es) 253 | { 254 | NullLogHandler ignore; 255 | return run_in_evm(call_data, es, ignore); 256 | } 257 | 258 | // TODO: This and similar should take EthereumTransaction, not 259 | // MessageCall. EthereumTransaction should be fully parsed, then 260 | // MessageCall can be removed 261 | pair execute_transaction( 262 | CallerId caller_id, 263 | const rpcparams::MessageCall& call_data, 264 | Store::Tx& tx) 265 | { 266 | auto es = make_state(tx); 267 | 268 | VectorLogHandler vlh; 269 | const auto [exec_result, tx_hash, to_address] = 270 | execute_transaction(call_data, es, vlh); 271 | 272 | if (exec_result.er == ExitReason::threw) 273 | { 274 | return jsonrpc::error( 275 | jsonrpc::StandardErrorCodes::INTERNAL_ERROR, exec_result.exmsg); 276 | } 277 | 278 | auto results_view = tx.get_view(tx_results); 279 | TxResult tx_result; 280 | if (!call_data.to.has_value()) 281 | { 282 | tx_result.contract_address = to_address; 283 | } 284 | 285 | tx_result.logs = vlh.logs; 286 | 287 | results_view->put(tx_hash, tx_result); 288 | 289 | return jsonrpc::success(eevm::to_hex_string_fixed(tx_hash)); 290 | } 291 | 292 | static std::tuple execute_transaction( 293 | const rpcparams::MessageCall& call_data, 294 | EthereumState& es, 295 | LogHandler& log_handler) 296 | { 297 | auto [exec_result, account_state] = 298 | run_in_evm(call_data, es, log_handler); 299 | 300 | if (exec_result.er == ExitReason::threw) 301 | { 302 | return std::make_tuple(exec_result, 0, 0); 303 | } 304 | 305 | if (!call_data.to.has_value()) 306 | { 307 | // New contract created, result is the code that should be deployed 308 | account_state.acc.set_code(std::move(exec_result.output)); 309 | } 310 | 311 | auto from_state = es.get(call_data.from); 312 | auto tx_nonce = from_state.acc.get_nonce(); 313 | from_state.acc.increment_nonce(); 314 | 315 | EthereumTransaction eth_tx(tx_nonce, call_data); 316 | const auto rlp_encoded = eth_tx.encode(); 317 | 318 | uint8_t h[32]; 319 | const auto raw = 320 | reinterpret_cast(rlp_encoded.data()); 321 | eevm::keccak_256(raw, rlp_encoded.size(), h); 322 | 323 | const auto tx_hash = eevm::from_big_endian(h); 324 | 325 | return std::make_tuple( 326 | exec_result, tx_hash, account_state.acc.get_address()); 327 | } 328 | }; // namespace evm4ccf 329 | } // namespace evm4ccf 330 | 331 | namespace ccfapp 332 | { 333 | // SNIPPET_START: rpc_handler 334 | std::shared_ptr get_rpc_handler( 335 | ccf::NetworkTables& nwt, ccf::AbstractNotifier& notifier) 336 | { 337 | return std::make_shared(nwt, notifier); 338 | } 339 | // SNIPPET_END: rpc_handler 340 | } // namespace ccfapp 341 | -------------------------------------------------------------------------------- /src/app/msgpacktypes.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #pragma once 4 | 5 | #include 6 | 7 | // To instantiate the kv map types above, all keys and values must be 8 | // convertible to msgpack 9 | namespace msgpack 10 | { 11 | MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) 12 | { 13 | namespace adaptor 14 | { 15 | // msgpack conversion for uint256_t 16 | template <> 17 | struct convert 18 | { 19 | msgpack::object const& operator()( 20 | msgpack::object const& o, uint256_t& v) const 21 | { 22 | const std::vector vec = 23 | o.via.array.ptr[0].as>(); 24 | v = eevm::from_big_endian(vec.data(), vec.size()); 25 | 26 | return o; 27 | } 28 | }; 29 | 30 | template <> 31 | struct pack 32 | { 33 | template 34 | packer& operator()( 35 | msgpack::packer& o, uint256_t const& v) const 36 | { 37 | std::vector big_end_val(0x20); // size of 256 bits in bytes 38 | eevm::to_big_endian(v, big_end_val.data()); 39 | o.pack_array(1); 40 | o.pack(big_end_val); 41 | return o; 42 | } 43 | }; 44 | 45 | // msgpack conversion for eevm::LogEntry 46 | template <> 47 | struct convert 48 | { 49 | msgpack::object const& operator()( 50 | msgpack::object const& o, eevm::LogEntry& v) const 51 | { 52 | auto addr = o.via.array.ptr[0].as(); 53 | auto data = o.via.array.ptr[1].as(); 54 | auto topics = o.via.array.ptr[2].as>(); 55 | v = {addr, data, topics}; 56 | 57 | return o; 58 | } 59 | }; 60 | 61 | template <> 62 | struct pack 63 | { 64 | template 65 | packer& operator()( 66 | msgpack::packer& o, eevm::LogEntry const& v) const 67 | { 68 | o.pack_array(3); 69 | o.pack(v.address); 70 | o.pack(v.data); 71 | o.pack(v.topics); 72 | return o; 73 | } 74 | }; 75 | 76 | // msgpack conversion for evm4ccf::TxResult 77 | template <> 78 | struct convert 79 | { 80 | msgpack::object const& operator()( 81 | msgpack::object const& o, evm4ccf::TxResult& v) const 82 | { 83 | auto addr = o.via.array.ptr[0].as(); 84 | if (addr != 0) 85 | { 86 | v.contract_address = addr; 87 | } 88 | v.logs = o.via.array.ptr[1].as>(); 89 | 90 | return o; 91 | } 92 | }; 93 | 94 | template <> 95 | struct pack 96 | { 97 | template 98 | packer& operator()( 99 | msgpack::packer& o, evm4ccf::TxResult const& v) const 100 | { 101 | o.pack_array(2); 102 | o.pack(v.contract_address.value_or(0x0)); 103 | o.pack(v.logs); 104 | return o; 105 | } 106 | }; 107 | 108 | // msgpack conversion for evm4ccf::BlockHeader 109 | template <> 110 | struct convert 111 | { 112 | msgpack::object const& operator()( 113 | msgpack::object const& o, evm4ccf::BlockHeader& v) const 114 | { 115 | v.number = o.via.array.ptr[0].as(); 116 | v.difficulty = o.via.array.ptr[1].as(); 117 | v.gas_limit = o.via.array.ptr[2].as(); 118 | v.gas_used = o.via.array.ptr[3].as(); 119 | v.timestamp = o.via.array.ptr[4].as(); 120 | v.miner = o.via.array.ptr[5].as(); 121 | v.block_hash = o.via.array.ptr[6].as(); 122 | 123 | return o; 124 | } 125 | }; 126 | 127 | template <> 128 | struct pack 129 | { 130 | template 131 | packer& operator()( 132 | msgpack::packer& o, evm4ccf::BlockHeader const& v) const 133 | { 134 | o.pack_array(7); 135 | o.pack(v.number); 136 | o.pack(v.difficulty); 137 | o.pack(v.gas_limit); 138 | o.pack(v.gas_used); 139 | o.pack(v.timestamp); 140 | o.pack(v.miner); 141 | o.pack(v.block_hash); 142 | return o; 143 | } 144 | }; 145 | } // namespace adaptor 146 | } // namespace msgpack 147 | } // namespace msgpack 148 | -------------------------------------------------------------------------------- /src/app/nljsontypes.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #pragma once 4 | 5 | namespace evm4ccf 6 | { 7 | inline void from_json(const nlohmann::json& j, TxResult& txr) 8 | { 9 | const auto it = j.find("address"); 10 | if (it != j.end() && !it->is_null()) 11 | { 12 | txr.contract_address = eevm::to_uint256(*it); 13 | } 14 | else 15 | { 16 | txr.contract_address = std::nullopt; 17 | } 18 | txr.logs = j["logs"].get(); 19 | } 20 | 21 | inline void to_json(nlohmann::json& j, const TxResult& txr) 22 | { 23 | if (txr.contract_address.has_value()) 24 | { 25 | j["address"] = eevm::to_hex_string(*txr.contract_address); 26 | } 27 | else 28 | { 29 | j["address"] = nullptr; 30 | } 31 | j["logs"] = txr.logs; 32 | } 33 | } // namespace evm4ccf 34 | -------------------------------------------------------------------------------- /src/app/oe_sign.conf: -------------------------------------------------------------------------------- 1 | # Enclave settings: 2 | Debug=1 3 | NumHeapPages=32768 4 | NumStackPages=1024 5 | NumTCS=8 6 | ProductID=1 7 | SecurityVersion=1 8 | -------------------------------------------------------------------------------- /src/app/tables.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #pragma once 4 | 5 | // EVM-for-CCF 6 | #include "rpc_types.h" 7 | 8 | // CCF 9 | #include "ds/hash.h" 10 | #include "enclave/appinterface.h" 11 | 12 | // eEVM 13 | #include 14 | #include 15 | 16 | // STL/3rd-party 17 | #include 18 | 19 | namespace evm4ccf 20 | { 21 | struct TxResult 22 | { 23 | std::optional contract_address; 24 | std::vector logs; 25 | }; 26 | } // namespace evm4ccf 27 | 28 | #include "msgpacktypes.h" 29 | #include "nljsontypes.h" 30 | 31 | // Implement std::hash for uint256, so it can be used as key in kv 32 | namespace std 33 | { 34 | template 35 | struct hash> 36 | { 37 | size_t operator()(const intx::uint& n) const 38 | { 39 | const auto words = intx::to_words(n); 40 | return hash_container(words); 41 | } 42 | }; 43 | } // namespace std 44 | 45 | namespace evm4ccf 46 | { 47 | inline bool operator==(const TxResult& l, const TxResult& r) 48 | { 49 | return l.contract_address == r.contract_address && l.logs == r.logs; 50 | } 51 | 52 | namespace tables 53 | { 54 | struct Accounts 55 | { 56 | using Balances = ccf::Store::Map; 57 | Balances& balances; 58 | 59 | using Codes = ccf::Store::Map; 60 | Codes& codes; 61 | 62 | using Nonces = ccf::Store::Map; 63 | Nonces& nonces; 64 | 65 | struct Views 66 | { 67 | Balances::TxView* balances; 68 | Codes::TxView* codes; 69 | Nonces::TxView* nonces; 70 | }; 71 | 72 | Views get_views(ccf::Store::Tx& tx) 73 | { 74 | return {tx.get_view(balances), tx.get_view(codes), tx.get_view(nonces)}; 75 | } 76 | }; 77 | 78 | using StorageKey = std::pair; 79 | using Storage = ccf::Store::Map; 80 | 81 | using Results = ccf::Store::Map; 82 | } // namespace tables 83 | } // namespace evm4ccf 84 | -------------------------------------------------------------------------------- /tests/contracts/Ballot.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | contract Ballot { 4 | 5 | struct Voter { 6 | uint weight; 7 | bool voted; 8 | uint8 vote; 9 | address delegate; 10 | } 11 | struct Proposal { 12 | uint voteCount; 13 | } 14 | 15 | address chairperson; 16 | mapping(address => Voter) voters; 17 | Proposal[] proposals; 18 | 19 | /// Create a new ballot with $(_numProposals) different proposals. 20 | constructor(uint8 _numProposals) public { 21 | chairperson = msg.sender; 22 | voters[chairperson].weight = 1; 23 | proposals.length = _numProposals; 24 | } 25 | 26 | /// Give $(toVoter) the right to vote on this ballot. 27 | /// May only be called by $(chairperson). 28 | function giveRightToVote(address toVoter) public { 29 | if (msg.sender != chairperson || voters[toVoter].voted) return; 30 | voters[toVoter].weight = 1; 31 | } 32 | 33 | /// Delegate your vote to the voter $(to). 34 | function delegate(address to) public { 35 | Voter storage sender = voters[msg.sender]; // assigns reference 36 | if (sender.voted) return; 37 | while (voters[to].delegate != address(0) && voters[to].delegate != msg.sender) 38 | to = voters[to].delegate; 39 | if (to == msg.sender) return; 40 | sender.voted = true; 41 | sender.delegate = to; 42 | Voter storage delegateTo = voters[to]; 43 | if (delegateTo.voted) 44 | proposals[delegateTo.vote].voteCount += sender.weight; 45 | else 46 | delegateTo.weight += sender.weight; 47 | } 48 | 49 | /// Give a single vote to proposal $(toProposal). 50 | function vote(uint8 toProposal) public { 51 | Voter storage sender = voters[msg.sender]; 52 | if (sender.voted || toProposal >= proposals.length) return; 53 | sender.voted = true; 54 | sender.vote = toProposal; 55 | proposals[toProposal].voteCount += sender.weight; 56 | } 57 | 58 | function winningProposal() public view returns (uint8 _winningProposal) { 59 | uint256 winningVoteCount = 0; 60 | for (uint8 prop = 0; prop < proposals.length; prop++) 61 | if (proposals[prop].voteCount > winningVoteCount) { 62 | winningVoteCount = proposals[prop].voteCount; 63 | _winningProposal = prop; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /tests/contracts/Ballot_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "contracts" : 3 | { 4 | "Ballot.sol:Ballot" : 5 | { 6 | "abi" : "[{\"constant\":false,\"inputs\":[{\"name\":\"to\",\"type\":\"address\"}],\"name\":\"delegate\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"winningProposal\",\"outputs\":[{\"name\":\"_winningProposal\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"toVoter\",\"type\":\"address\"}],\"name\":\"giveRightToVote\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"toProposal\",\"type\":\"uint8\"}],\"name\":\"vote\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_numProposals\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]", 7 | "bin" : "608060405234801561001057600080fd5b506040516104a23803806104a28339818101604052602081101561003357600080fd5b505160008054600160a060020a0319163317808255600160a060020a031681526001602081905260409091205560ff811661006f600282610076565b50506100c0565b81548183558181111561009a5760008381526020902061009a91810190830161009f565b505050565b6100bd91905b808211156100b957600081556001016100a5565b5090565b90565b6103d3806100cf6000396000f3fe608060405234801561001057600080fd5b5060043610610068577c010000000000000000000000000000000000000000000000000000000060003504635c19a95c811461006d578063609ff1bd146100955780639e7b8d61146100b3578063b3f98adc146100d9575b600080fd5b6100936004803603602081101561008357600080fd5b5035600160a060020a03166100f9565b005b61009d610259565b6040805160ff9092168252519081900360200190f35b610093600480360360208110156100c957600080fd5b5035600160a060020a03166102c1565b610093600480360360208110156100ef57600080fd5b503560ff1661031e565b3360009081526001602081905260409091209081015460ff161561011d5750610256565b5b600160a060020a038281166000908152600160208190526040909120015462010000900416158015906101755750600160a060020a0382811660009081526001602081905260409091200154620100009004163314155b156101a757600160a060020a03918216600090815260016020819052604090912001546201000090049091169061011e565b600160a060020a0382163314156101be5750610256565b6001818101805460ff1916821775ffffffffffffffffffffffffffffffffffffffff0000191662010000600160a060020a0386169081029190911790915560009081526020829052604090209081015460ff161561024b5781546001820154600280549091610100900460ff1690811061023457fe5b600091825260209091200180549091019055610253565b815481540181555b50505b50565b600080805b60025460ff821610156102bc578160028260ff168154811061027c57fe5b906000526020600020016000015411156102b45760028160ff16815481106102a057fe5b906000526020600020016000015491508092505b60010161025e565b505090565b600054600160a060020a0316331415806102f75750600160a060020a0381166000908152600160208190526040909120015460ff165b1561030157610256565b600160a060020a0316600090815260016020819052604090912055565b3360009081526001602081905260409091209081015460ff1680610347575060025460ff831610155b156103525750610256565b6001818101805460ff191690911761ff00191661010060ff85169081029190911790915581546002805491929091811061038857fe5b600091825260209091200180549091019055505056fea265627a7a72305820650fda092fc772dd6a8477aedfb4e7e8356497e4af16aab3f5d8e78126735b8c64736f6c634300050a0032", 8 | "bin-runtime" : "608060405234801561001057600080fd5b5060043610610068577c010000000000000000000000000000000000000000000000000000000060003504635c19a95c811461006d578063609ff1bd146100955780639e7b8d61146100b3578063b3f98adc146100d9575b600080fd5b6100936004803603602081101561008357600080fd5b5035600160a060020a03166100f9565b005b61009d610259565b6040805160ff9092168252519081900360200190f35b610093600480360360208110156100c957600080fd5b5035600160a060020a03166102c1565b610093600480360360208110156100ef57600080fd5b503560ff1661031e565b3360009081526001602081905260409091209081015460ff161561011d5750610256565b5b600160a060020a038281166000908152600160208190526040909120015462010000900416158015906101755750600160a060020a0382811660009081526001602081905260409091200154620100009004163314155b156101a757600160a060020a03918216600090815260016020819052604090912001546201000090049091169061011e565b600160a060020a0382163314156101be5750610256565b6001818101805460ff1916821775ffffffffffffffffffffffffffffffffffffffff0000191662010000600160a060020a0386169081029190911790915560009081526020829052604090209081015460ff161561024b5781546001820154600280549091610100900460ff1690811061023457fe5b600091825260209091200180549091019055610253565b815481540181555b50505b50565b600080805b60025460ff821610156102bc578160028260ff168154811061027c57fe5b906000526020600020016000015411156102b45760028160ff16815481106102a057fe5b906000526020600020016000015491508092505b60010161025e565b505090565b600054600160a060020a0316331415806102f75750600160a060020a0381166000908152600160208190526040909120015460ff165b1561030157610256565b600160a060020a0316600090815260016020819052604090912055565b3360009081526001602081905260409091209081015460ff1680610347575060025460ff831610155b156103525750610256565b6001818101805460ff191690911761ff00191661010060ff85169081029190911790915581546002805491929091811061038857fe5b600091825260209091200180549091019055505056fea265627a7a72305820650fda092fc772dd6a8477aedfb4e7e8356497e4af16aab3f5d8e78126735b8c64736f6c634300050a0032", 9 | "hashes" : 10 | { 11 | "delegate(address)" : "5c19a95c", 12 | "giveRightToVote(address)" : "9e7b8d61", 13 | "vote(uint8)" : "b3f98adc", 14 | "winningProposal()" : "609ff1bd" 15 | } 16 | } 17 | }, 18 | "version" : "0.5.10+commit.5a6ea5b1.Linux.g++" 19 | } 20 | -------------------------------------------------------------------------------- /tests/contracts/Call1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | // Simple pure functions used by unit tests (see Call1 test case) 4 | contract Call1 { 5 | function f(uint a, uint b) public pure returns (uint) { 6 | return a * (b + 42); 7 | } 8 | } -------------------------------------------------------------------------------- /tests/contracts/Call1_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "contracts" : 3 | { 4 | "Call1.sol:Call1" : 5 | { 6 | "abi" : "[{\"constant\":true,\"inputs\":[{\"name\":\"a\",\"type\":\"uint256\"},{\"name\":\"b\",\"type\":\"uint256\"}],\"name\":\"f\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"pure\",\"type\":\"function\"}]", 7 | "bin" : "608060405234801561001057600080fd5b5060b78061001f6000396000f3fe6080604052348015600f57600080fd5b50600436106044577c0100000000000000000000000000000000000000000000000000000000600035046313d1aa2e81146049575b600080fd5b606960048036036040811015605d57600080fd5b5080359060200135607b565b60408051918252519081900360200190f35b602a01029056fea265627a7a7230582063dcee43e6192ee1cf15316d839512e51f7c5b72b92d43b76adb851acd32774664736f6c634300050a0032", 8 | "bin-runtime" : "6080604052348015600f57600080fd5b50600436106044577c0100000000000000000000000000000000000000000000000000000000600035046313d1aa2e81146049575b600080fd5b606960048036036040811015605d57600080fd5b5080359060200135607b565b60408051918252519081900360200190f35b602a01029056fea265627a7a7230582063dcee43e6192ee1cf15316d839512e51f7c5b72b92d43b76adb851acd32774664736f6c634300050a0032", 9 | "hashes" : 10 | { 11 | "f(uint256,uint256)" : "13d1aa2e" 12 | } 13 | } 14 | }, 15 | "version" : "0.5.10+commit.5a6ea5b1.Linux.g++" 16 | } 17 | -------------------------------------------------------------------------------- /tests/contracts/Call2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | // Simple pure functions used by unit tests, displaying selection by function signature (see Call2 test case) 4 | contract Call2 { 5 | function get() public pure returns (uint) { 6 | return 42; 7 | } 8 | 9 | function add(uint _n) public pure returns (uint) { 10 | return _n + 42; 11 | } 12 | 13 | function mul(uint _n) public pure returns (uint) { 14 | return _n * 42; 15 | } 16 | } -------------------------------------------------------------------------------- /tests/contracts/Call2_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "contracts" : 3 | { 4 | "Call2.sol:Call2" : 5 | { 6 | "abi" : "[{\"constant\":true,\"inputs\":[{\"name\":\"_n\",\"type\":\"uint256\"}],\"name\":\"add\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"pure\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_n\",\"type\":\"uint256\"}],\"name\":\"mul\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"pure\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"pure\",\"type\":\"function\"}]", 7 | "bin" : "608060405234801561001057600080fd5b5060ef8061001f6000396000f3fe6080604052348015600f57600080fd5b50600436106058577c010000000000000000000000000000000000000000000000000000000060003504631003e2d28114605d578063131e2f181460895780636d4ce63c1460a3575b600080fd5b607760048036036020811015607157600080fd5b503560a9565b60408051918252519081900360200190f35b607760048036036020811015609d57600080fd5b503560af565b607760b5565b602a0190565b602a0290565b602a9056fea265627a7a72305820b288537d1006f1d70c47251b8f0acc64da3bc7c2b6f259acf52f5ad716e1619064736f6c634300050a0032", 8 | "bin-runtime" : "6080604052348015600f57600080fd5b50600436106058577c010000000000000000000000000000000000000000000000000000000060003504631003e2d28114605d578063131e2f181460895780636d4ce63c1460a3575b600080fd5b607760048036036020811015607157600080fd5b503560a9565b60408051918252519081900360200190f35b607760048036036020811015609d57600080fd5b503560af565b607760b5565b602a0190565b602a0290565b602a9056fea265627a7a72305820b288537d1006f1d70c47251b8f0acc64da3bc7c2b6f259acf52f5ad716e1619064736f6c634300050a0032", 9 | "hashes" : 10 | { 11 | "add(uint256)" : "1003e2d2", 12 | "get()" : "6d4ce63c", 13 | "mul(uint256)" : "131e2f18" 14 | } 15 | } 16 | }, 17 | "version" : "0.5.10+commit.5a6ea5b1.Linux.g++" 18 | } 19 | -------------------------------------------------------------------------------- /tests/contracts/ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | /*contract ERC20_Interface { 4 | function totalSupply() public view returns (uint256 supply); 5 | function balanceOf(address _owner) public view returns (uint256 balance); 6 | function transfer(address _to, uint256 _value) public returns (bool success); 7 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); 8 | function approve(address _spender, uint256 _value) public returns (bool success); 9 | function allowance(address _owner, address _spender) public view returns (uint256 remaining); 10 | 11 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 12 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 13 | }*/ 14 | 15 | contract ERC20Token { 16 | uint256 supply; 17 | mapping (address => uint256) balances; 18 | mapping (address => mapping (address => uint256)) allowances; 19 | 20 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 21 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 22 | 23 | constructor(uint256 s) public 24 | { 25 | supply = s; 26 | balances[msg.sender] = supply; 27 | } 28 | 29 | // Get the total token supply 30 | function totalSupply() public view returns (uint256) 31 | { 32 | return supply; 33 | } 34 | 35 | // Get the account balance of another account with address _owner 36 | function balanceOf(address _owner) public view returns (uint256) 37 | { 38 | return balances[_owner]; 39 | } 40 | 41 | // Send _value amount of tokens to address _to 42 | function transfer(address _to, uint256 _value) public returns (bool) 43 | { 44 | if (balances[msg.sender] >= _value) 45 | { 46 | balances[msg.sender] -= _value; 47 | balances[_to] += _value; 48 | emit Transfer(msg.sender, _to, _value); 49 | return true; 50 | } 51 | else 52 | { 53 | return false; 54 | } 55 | } 56 | 57 | // Send _value amount of tokens from address _from to address _to 58 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool) 59 | { 60 | if (balances[_from] >= _value && allowances[_from][msg.sender] >= _value) 61 | { 62 | balances[_from] -= _value; 63 | balances[_to] += _value; 64 | allowances[_from][msg.sender] -= _value; 65 | emit Transfer(msg.sender, _to, _value); 66 | return true; 67 | } 68 | else 69 | { 70 | return false; 71 | } 72 | } 73 | 74 | // Allow _spender to withdraw from your account, multiple times, up to the 75 | // _value amount. If this function is called again it overwrites the current 76 | // allowance with _value 77 | function approve(address _spender, uint256 _value) public returns (bool) 78 | { 79 | // We permit allowances to be larger than balances 80 | allowances[msg.sender][_spender] = _value; 81 | emit Approval(msg.sender, _spender, _value); 82 | return true; 83 | } 84 | 85 | // Returns the amount which _spender is still allowed to withdraw from _owner 86 | function allowance(address _owner, address _spender) public view returns (uint256) 87 | { 88 | return allowances[_owner][_spender]; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/contracts/ERC20_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "contracts" : 3 | { 4 | "ERC20.sol:ERC20Token" : 5 | { 6 | "abi" : "[{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"s\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"}]", 7 | "bin" : "608060405234801561001057600080fd5b506040516104423803806104428339818101604052602081101561003357600080fd5b50516000818155338152600160205260409020556103ec806100566000396000f3fe608060405234801561001057600080fd5b506004361061007e577c01000000000000000000000000000000000000000000000000000000006000350463095ea7b3811461008357806318160ddd146100c357806323b872dd146100dd57806370a0823114610113578063a9059cbb14610139578063dd62ed3e14610165575b600080fd5b6100af6004803603604081101561009957600080fd5b50600160a060020a038135169060200135610193565b604080519115158252519081900360200190f35b6100cb6101fa565b60408051918252519081900360200190f35b6100af600480360360608110156100f357600080fd5b50600160a060020a03813581169160208101359091169060400135610200565b6100cb6004803603602081101561012957600080fd5b5035600160a060020a03166102e6565b6100af6004803603604081101561014f57600080fd5b50600160a060020a038135169060200135610301565b6100cb6004803603604081101561017b57600080fd5b50600160a060020a038135811691602001351661038c565b336000818152600260209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60005490565b600160a060020a038316600090815260016020526040812054821180159061024b5750600160a060020a03841660009081526002602090815260408083203384529091529020548211155b156102db57600160a060020a038085166000818152600160209081526040808320805488900390559387168083528483208054880190559282526002815283822033808452908252918490208054879003905583518681529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016102df565b5060005b9392505050565b600160a060020a031660009081526001602052604090205490565b3360009081526001602052604081205482116103845733600081815260016020908152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016101f4565b5060006101f4565b600160a060020a0391821660009081526002602090815260408083209390941682529190915220549056fea265627a7a723058200693656f96e8c37e8decc578417ac2956ecad6f7626338fe5620153af10b940364736f6c634300050a0032", 8 | "bin-runtime" : "608060405234801561001057600080fd5b506004361061007e577c01000000000000000000000000000000000000000000000000000000006000350463095ea7b3811461008357806318160ddd146100c357806323b872dd146100dd57806370a0823114610113578063a9059cbb14610139578063dd62ed3e14610165575b600080fd5b6100af6004803603604081101561009957600080fd5b50600160a060020a038135169060200135610193565b604080519115158252519081900360200190f35b6100cb6101fa565b60408051918252519081900360200190f35b6100af600480360360608110156100f357600080fd5b50600160a060020a03813581169160208101359091169060400135610200565b6100cb6004803603602081101561012957600080fd5b5035600160a060020a03166102e6565b6100af6004803603604081101561014f57600080fd5b50600160a060020a038135169060200135610301565b6100cb6004803603604081101561017b57600080fd5b50600160a060020a038135811691602001351661038c565b336000818152600260209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60005490565b600160a060020a038316600090815260016020526040812054821180159061024b5750600160a060020a03841660009081526002602090815260408083203384529091529020548211155b156102db57600160a060020a038085166000818152600160209081526040808320805488900390559387168083528483208054880190559282526002815283822033808452908252918490208054879003905583518681529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016102df565b5060005b9392505050565b600160a060020a031660009081526001602052604090205490565b3360009081526001602052604081205482116103845733600081815260016020908152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016101f4565b5060006101f4565b600160a060020a0391821660009081526002602090815260408083209390941682529190915220549056fea265627a7a723058200693656f96e8c37e8decc578417ac2956ecad6f7626338fe5620153af10b940364736f6c634300050a0032", 9 | "hashes" : 10 | { 11 | "allowance(address,address)" : "dd62ed3e", 12 | "approve(address,uint256)" : "095ea7b3", 13 | "balanceOf(address)" : "70a08231", 14 | "totalSupply()" : "18160ddd", 15 | "transfer(address,uint256)" : "a9059cbb", 16 | "transferFrom(address,address,uint256)" : "23b872dd" 17 | } 18 | } 19 | }, 20 | "version" : "0.5.10+commit.5a6ea5b1.Linux.g++" 21 | } 22 | -------------------------------------------------------------------------------- /tests/contracts/Events.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | contract Events { 4 | constructor() public { 5 | log0(bytes32(uint256(0xdeadbeef))); 6 | } 7 | 8 | event Name(address a); 9 | function name(address a) public { 10 | emit Name(a); 11 | } 12 | 13 | event Interesting(uint indexed a, uint b, uint c); 14 | function emit_interesting(uint a, uint b, uint c) public { 15 | emit Interesting(a, b, c); 16 | } 17 | } -------------------------------------------------------------------------------- /tests/contracts/Events_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "contracts" : 3 | { 4 | "Events.sol:Events" : 5 | { 6 | "abi" : "[{\"constant\":false,\"inputs\":[{\"name\":\"a\",\"type\":\"address\"}],\"name\":\"name\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a\",\"type\":\"uint256\"},{\"name\":\"b\",\"type\":\"uint256\"},{\"name\":\"c\",\"type\":\"uint256\"}],\"name\":\"emit_interesting\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"a\",\"type\":\"address\"}],\"name\":\"Name\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"a\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"b\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"c\",\"type\":\"uint256\"}],\"name\":\"Interesting\",\"type\":\"event\"}]", 7 | "bin" : "608060405234801561001057600080fd5b506040805163deadbeef815290519081900360200190a0610175806100366000396000f3fe608060405234801561001057600080fd5b5060043610610052577c01000000000000000000000000000000000000000000000000000000006000350463019848928114610057578063789ab20b1461008c575b600080fd5b61008a6004803603602081101561006d57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166100b5565b005b61008a600480360360608110156100a257600080fd5b5080359060208101359060400135610101565b6040805173ffffffffffffffffffffffffffffffffffffffff8316815290517f3042fbbf8fb97fb253117bdf7c2385c241b9eaf856a585675ce0521e592ca7a59181900360200190a150565b6040805183815260208101839052815185927ffb1ce3763357ebcb5adbc480c1ddc23334bd4f9811f298c0640648b9b9e5642c928290030190a250505056fea265627a7a723058202bece3b8548aa8795ba7400b809dcd0ee1c5010c61d6aa6179c4d02150a6772c64736f6c634300050a0032", 8 | "bin-runtime" : "608060405234801561001057600080fd5b5060043610610052577c01000000000000000000000000000000000000000000000000000000006000350463019848928114610057578063789ab20b1461008c575b600080fd5b61008a6004803603602081101561006d57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166100b5565b005b61008a600480360360608110156100a257600080fd5b5080359060208101359060400135610101565b6040805173ffffffffffffffffffffffffffffffffffffffff8316815290517f3042fbbf8fb97fb253117bdf7c2385c241b9eaf856a585675ce0521e592ca7a59181900360200190a150565b6040805183815260208101839052815185927ffb1ce3763357ebcb5adbc480c1ddc23334bd4f9811f298c0640648b9b9e5642c928290030190a250505056fea265627a7a723058202bece3b8548aa8795ba7400b809dcd0ee1c5010c61d6aa6179c4d02150a6772c64736f6c634300050a0032", 9 | "hashes" : 10 | { 11 | "emit_interesting(uint256,uint256,uint256)" : "789ab20b", 12 | "name(address)" : "01984892" 13 | } 14 | } 15 | }, 16 | "version" : "0.5.10+commit.5a6ea5b1.Linux.g++" 17 | } 18 | -------------------------------------------------------------------------------- /tests/contracts/SimpleStore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | contract SimpleStore { 4 | uint n; 5 | 6 | constructor(uint _n) public { 7 | n = _n; 8 | } 9 | 10 | function get() public view returns (uint) { 11 | return n; 12 | } 13 | 14 | function set(uint _n) public { 15 | n = _n; 16 | } 17 | 18 | function add(uint _n) public { 19 | n = n + _n; 20 | } 21 | } -------------------------------------------------------------------------------- /tests/contracts/SimpleStore_combined.json: -------------------------------------------------------------------------------- 1 | { 2 | "contracts" : 3 | { 4 | "SimpleStore.sol:SimpleStore" : 5 | { 6 | "abi" : "[{\"constant\":false,\"inputs\":[{\"name\":\"_n\",\"type\":\"uint256\"}],\"name\":\"add\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_n\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_n\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]", 7 | "bin" : "608060405234801561001057600080fd5b5060405161013c38038061013c8339818101604052602081101561003357600080fd5b505160005560f6806100466000396000f3fe6080604052348015600f57600080fd5b50600436106058577c010000000000000000000000000000000000000000000000000000000060003504631003e2d28114605d57806360fe47b11460795780636d4ce63c146093575b600080fd5b607760048036036020811015607157600080fd5b503560ab565b005b607760048036036020811015608d57600080fd5b503560b6565b609960bb565b60408051918252519081900360200190f35b600080549091019055565b600055565b6000549056fea265627a7a7230582012ff7678d4ef1ba35bb591fe6d38603836a43e01ae9966222c9032e25db6faac64736f6c634300050a0032", 8 | "bin-runtime" : "6080604052348015600f57600080fd5b50600436106058577c010000000000000000000000000000000000000000000000000000000060003504631003e2d28114605d57806360fe47b11460795780636d4ce63c146093575b600080fd5b607760048036036020811015607157600080fd5b503560ab565b005b607760048036036020811015608d57600080fd5b503560b6565b609960bb565b60408051918252519081900360200190f35b600080549091019055565b600055565b6000549056fea265627a7a7230582012ff7678d4ef1ba35bb591fe6d38603836a43e01ae9966222c9032e25db6faac64736f6c634300050a0032", 9 | "hashes" : 10 | { 11 | "add(uint256)" : "1003e2d2", 12 | "get()" : "6d4ce63c", 13 | "set(uint256)" : "60fe47b1" 14 | } 15 | } 16 | }, 17 | "version" : "0.5.10+commit.5a6ea5b1.Linux.g++" 18 | } 19 | -------------------------------------------------------------------------------- /tests/contracts/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. 4 | if [ -z "$1" ] 5 | then 6 | echo "No file given" 7 | exit 1 8 | fi 9 | 10 | if [ ! -f "$1" ] 11 | then 12 | echo "File $1 not found" 13 | exit 2 14 | fi 15 | 16 | BASENAME=${1%.sol} 17 | COMBINED_PATH=${BASENAME}_combined.json 18 | 19 | # $ solc --version 20 | # solc, the solidity compiler commandline interface 21 | # Version: 0.5.10+commit.5a6ea5b1.Linux.g++ 22 | 23 | echo "Writing to ${COMBINED_PATH}" 24 | 25 | solc --combined-json abi,bin,bin-runtime,hashes --evm-version homestead --pretty-json --optimize "$1"> "${COMBINED_PATH}" 26 | -------------------------------------------------------------------------------- /tests/erc20_transfers_scenario.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosts": [ 3 | "localhost", 4 | "localhost" 5 | ], 6 | "package": "libevm4ccf", 7 | "connections": [ 8 | { 9 | "transactions": [ 10 | { 11 | "method": "eth_sendTransaction", 12 | "params": [ 13 | { 14 | "from": "0x4af4dcE351A4747B5b33Fcf66202612736401f95", 15 | "to": null, 16 | "data": "0x608060405234801561001057600080fd5b5060405160208061040783398101604090815290516000818155338152600160205291909120556103c1806100466000396000f3006080604052600436106100775763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663095ea7b3811461007c57806318160ddd146100b457806323b872dd146100db57806370a0823114610105578063a9059cbb14610126578063dd62ed3e1461014a575b600080fd5b34801561008857600080fd5b506100a0600160a060020a0360043516602435610171565b604080519115158252519081900360200190f35b3480156100c057600080fd5b506100c96101d8565b60408051918252519081900360200190f35b3480156100e757600080fd5b506100a0600160a060020a03600435811690602435166044356101de565b34801561011157600080fd5b506100c9600160a060020a03600435166102c4565b34801561013257600080fd5b506100a0600160a060020a03600435166024356102df565b34801561015657600080fd5b506100c9600160a060020a036004358116906024351661036a565b336000818152600260209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60005490565b600160a060020a03831660009081526001602052604081205482118015906102295750600160a060020a03841660009081526002602090815260408083203384529091529020548211155b156102b957600160a060020a038085166000818152600160209081526040808320805488900390559387168083528483208054880190559282526002815283822033808452908252918490208054879003905583518681529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016102bd565b5060005b9392505050565b600160a060020a031660009081526001602052604090205490565b3360009081526001602052604081205482116103625733600081815260016020908152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016101d2565b5060006101d2565b600160a060020a039182166000908152600260209081526040808320939094168252919091522054905600a165627a7a72305820d42f54ea7a0e7e46d0c27f34e6f541ecdbf2c5fba2a8e541da4d28212b0a232400290000000000000000000000000000000000000000000000000000000000002710", 17 | "gas": "0x0", 18 | "gasPrice": "0x0", 19 | "value": "0x0" 20 | } 21 | ], 22 | "expected_result": "0x84ac8ea0326deaaf5d39ea9d2c2d55e7e1a56203a91f6edd43fa57875150a0ad" 23 | }, 24 | { 25 | "method": "eth_getTransactionReceipt", 26 | "params": [ 27 | "0x84ac8ea0326deaaf5d39ea9d2c2d55e7e1a56203a91f6edd43fa57875150a0ad" 28 | ], 29 | "expected_result": { 30 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 31 | "blockNumber": "0x0", 32 | "contractAddress": "0xab2fcCB0c5F0499278801CE41F4bcCCA39676f2D", 33 | "cumulativeGasUsed": "0x0", 34 | "from": "0x0000000000000000000000000000000000000000", 35 | "gasUsed": "0x0", 36 | "logs": [], 37 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 38 | "status": "0x1", 39 | "to": null, 40 | "transactionHash": "0x84ac8ea0326deaaf5d39ea9d2c2d55e7e1a56203a91f6edd43fa57875150a0ad", 41 | "transactionIndex": "0x0" 42 | } 43 | }, 44 | { 45 | "method": "eth_call", 46 | "params": [ 47 | { 48 | "from": "0x4af4dcE351A4747B5b33Fcf66202612736401f95", 49 | "to": "0xab2fcCB0c5F0499278801CE41F4bcCCA39676f2D", 50 | "data": "0x70a082310000000000000000000000004af4dce351a4747b5b33fcf66202612736401f95", 51 | "gas": "0x0", 52 | "gasPrice": "0x0", 53 | "value": "0x0" 54 | }, 55 | "latest" 56 | ], 57 | "expected_result": "0x0000000000000000000000000000000000000000000000000000000000002710" 58 | }, 59 | { 60 | "method": "eth_sendTransaction", 61 | "params": [ 62 | { 63 | "from": "0x4af4dcE351A4747B5b33Fcf66202612736401f95", 64 | "to": "0xab2fcCB0c5F0499278801CE41F4bcCCA39676f2D", 65 | "data": "0xa9059cbb000000000000000000000000d2e1d33d92935599575089ef13c52492f242f51600000000000000000000000000000000000000000000000000000000000007d0", 66 | "gas": "0x0", 67 | "gasPrice": "0x0", 68 | "value": "0x0" 69 | } 70 | ], 71 | "expected_result": "0x22e2452cb8a6925e1b7b40fbac5d78eb6cc9ba1aec3aeefde957a488934a2a47" 72 | }, 73 | { 74 | "method": "eth_sendTransaction", 75 | "params": [ 76 | { 77 | "from": "0x4af4dcE351A4747B5b33Fcf66202612736401f95", 78 | "to": "0xab2fcCB0c5F0499278801CE41F4bcCCA39676f2D", 79 | "data": "0xa9059cbb000000000000000000000000c8803aec0cb11afa1fa9ca07ef41ad7ffef137f40000000000000000000000000000000000000000000000000000000000001f40", 80 | "gas": "0x0", 81 | "gasPrice": "0x0", 82 | "value": "0x0" 83 | } 84 | ], 85 | "expected_result": "0x9a2c0bab0d60bda4e7afd3496c30487565b52e592af8be4dd4f2c1c9ebf9601f" 86 | }, 87 | { 88 | "method": "eth_sendTransaction", 89 | "params": [ 90 | { 91 | "from": "0xc8803AEc0cB11aFa1FA9Ca07Ef41ad7fFeF137f4", 92 | "to": "0xab2fcCB0c5F0499278801CE41F4bcCCA39676f2D", 93 | "data": "0xa9059cbb000000000000000000000000d2e1d33d92935599575089ef13c52492f242f5160000000000000000000000000000000000000000000000000000000000000a6a", 94 | "gas": "0x0", 95 | "gasPrice": "0x0", 96 | "value": "0x0" 97 | } 98 | ], 99 | "expected_result": "0x83030dee43b568ac1d040ce0e78b15772f4e1a768019c06c74dddc4fa22ed406" 100 | }, 101 | { 102 | "method": "eth_call", 103 | "params": [ 104 | { 105 | "from": "0x4af4dcE351A4747B5b33Fcf66202612736401f95", 106 | "to": "0xab2fcCB0c5F0499278801CE41F4bcCCA39676f2D", 107 | "data": "0x70a082310000000000000000000000004af4dce351a4747b5b33fcf66202612736401f95", 108 | "gas": "0x0", 109 | "gasPrice": "0x0", 110 | "value": "0x0" 111 | }, 112 | "latest" 113 | ], 114 | "expected_result": "0x0000000000000000000000000000000000000000000000000000000000000000" 115 | }, 116 | { 117 | "method": "eth_call", 118 | "params": [ 119 | { 120 | "from": "0x4af4dcE351A4747B5b33Fcf66202612736401f95", 121 | "to": "0xab2fcCB0c5F0499278801CE41F4bcCCA39676f2D", 122 | "data": "0x70a08231000000000000000000000000d2e1d33d92935599575089ef13c52492f242f516", 123 | "gas": "0x0", 124 | "gasPrice": "0x0", 125 | "value": "0x0" 126 | }, 127 | "latest" 128 | ], 129 | "expected_result": "0x000000000000000000000000000000000000000000000000000000000000123a" 130 | }, 131 | { 132 | "method": "eth_call", 133 | "params": [ 134 | { 135 | "from": "0x4af4dcE351A4747B5b33Fcf66202612736401f95", 136 | "to": "0xab2fcCB0c5F0499278801CE41F4bcCCA39676f2D", 137 | "data": "0x70a08231000000000000000000000000c8803aec0cb11afa1fa9ca07ef41ad7ffef137f4", 138 | "gas": "0x0", 139 | "gasPrice": "0x0", 140 | "value": "0x0" 141 | }, 142 | "latest" 143 | ], 144 | "expected_result": "0x00000000000000000000000000000000000000000000000000000000000014d6" 145 | } 146 | ] 147 | } 148 | ] 149 | } -------------------------------------------------------------------------------- /tests/eth_call.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #include "ds/logger.h" 4 | #include "enclave/appinterface.h" 5 | #include "shared.h" 6 | 7 | #include 8 | #include 9 | 10 | using namespace ccf; 11 | using namespace evm4ccf; 12 | 13 | TEST_CASE("Call0" * doctest::test_suite("call")) 14 | { 15 | NetworkTables nwt; 16 | StubNotifier stubn; 17 | Store& tables = *nwt.tables; 18 | auto cert = setup_tables(tables); 19 | Ethereum frontend = ccfapp::get_rpc_handler(nwt, stubn); 20 | 21 | jsonrpc::SeqNo sn = 0; 22 | 23 | eevm::Address created; 24 | 25 | const auto mem_dest = 0; 26 | const auto ret_size = 32; 27 | const auto code = eevm::to_hex_string(std::vector{ 28 | // Push 5 29 | eevm::Opcode::PUSH1, 30 | 5, 31 | 32 | // Push 4 33 | eevm::Opcode::PUSH1, 34 | 4, 35 | 36 | // Add them 37 | eevm::Opcode::ADD, 38 | 39 | // Store the result info in memory (can only return from memory) 40 | eevm::Opcode::PUSH1, 41 | mem_dest, 42 | eevm::Opcode::MSTORE, 43 | 44 | // Return ret_size bytes, starting at mem_dest 45 | eevm::Opcode::PUSH1, 46 | ret_size, 47 | eevm::Opcode::PUSH1, 48 | mem_dest, 49 | eevm::Opcode::RETURN}); 50 | 51 | nlohmann::json storage; 52 | 53 | // Create an account 54 | { 55 | created = deploy_contract(code, frontend, cert); 56 | } 57 | 58 | // Code matches argument 59 | { 60 | auto in = ethrpc::GetCode::make(sn++); 61 | in.params.address = created; 62 | ethrpc::GetCode::Out out = do_rpc(frontend, cert, in); 63 | REQUIRE(out.result == code); 64 | } 65 | 66 | // Call 67 | { 68 | auto in = ethrpc::Call::make(sn++); 69 | in.params.call_data.to = created; 70 | in.params.call_data.data = "0x"; 71 | ethrpc::Call::Out out = do_rpc(frontend, cert, in); 72 | uint256_t res = get_result_value(out); 73 | REQUIRE(res == 0x9); 74 | } 75 | } 76 | 77 | TEST_CASE("Call1" * doctest::test_suite("call")) 78 | { 79 | NetworkTables nwt; 80 | StubNotifier stubn; 81 | Store& tables = *nwt.tables; 82 | auto cert = setup_tables(tables); 83 | Ethereum frontend = ccfapp::get_rpc_handler(nwt, stubn); 84 | 85 | jsonrpc::SeqNo sn = 0; 86 | 87 | eevm::Address created; 88 | 89 | // See Call1.sol for source 90 | // f(uint a, uint b) -> a * (b + 42) 91 | const auto compiled = read_bytecode("Call1"); 92 | const auto code = compiled.runtime; 93 | 94 | nlohmann::json storage; 95 | 96 | // Create instance of contract 97 | { 98 | created = deploy_contract(code, frontend, cert); 99 | } 100 | 101 | // Code matches argument 102 | { 103 | auto in = ethrpc::GetCode::make(sn++); 104 | in.params.address = created; 105 | ethrpc::GetCode::Out out = do_rpc(frontend, cert, in); 106 | REQUIRE(out.result == code); 107 | } 108 | 109 | // Call f(2, 1) 110 | { 111 | auto in = ethrpc::Call::make(sn++); 112 | in.params.call_data.to = created; 113 | in.params.call_data.data = 114 | abi_append(compiled.hashes["f(uint256,uint256)"], 2, 1); 115 | ethrpc::Call::Out out = do_rpc(frontend, cert, in); 116 | uint256_t res = get_result_value(out); 117 | REQUIRE(res == 86); 118 | } 119 | } 120 | 121 | TEST_CASE("Call2" * doctest::test_suite("call")) 122 | { 123 | NetworkTables nwt; 124 | StubNotifier stubn; 125 | Store& tables = *nwt.tables; 126 | auto cert = setup_tables(tables); 127 | Ethereum frontend = ccfapp::get_rpc_handler(nwt, stubn); 128 | jsonrpc::SeqNo sn = 0; 129 | 130 | eevm::Address created; 131 | Balance balance = 0xffff0000; 132 | // See Call2.sol for source 133 | // Contains 3 functions, get(), add(uint), mul(uint) 134 | const auto compiled = read_bytecode("Call2"); 135 | auto code = compiled.runtime; 136 | 137 | nlohmann::json storage; 138 | 139 | // Create instance of contract 140 | { 141 | created = deploy_contract(code, frontend, cert); 142 | } 143 | 144 | // Code matches argument 145 | { 146 | auto in = ethrpc::GetCode::make(sn++); 147 | in.params.address = created; 148 | ethrpc::GetCode::Out out = do_rpc(frontend, cert, in); 149 | REQUIRE(out.result == code); 150 | } 151 | 152 | { 153 | INFO("call get()"); 154 | auto in = ethrpc::Call::make(sn++); 155 | in.params.call_data.to = created; 156 | in.params.call_data.data = compiled.hashes["get()"]; 157 | ethrpc::Call::Out out = do_rpc(frontend, cert, in); 158 | uint256_t res = get_result_value(out); 159 | REQUIRE(res == 42); 160 | } 161 | 162 | { 163 | INFO("call add(1)"); 164 | auto in = ethrpc::Call::make(sn++); 165 | in.params.call_data.to = created; 166 | in.params.call_data.data = abi_append(compiled.hashes["add(uint256)"], 1); 167 | ethrpc::Call::Out out = do_rpc(frontend, cert, in); 168 | uint256_t res = get_result_value(out); 169 | REQUIRE(res == 43); 170 | } 171 | 172 | { 173 | INFO("call add(100)"); 174 | auto in = ethrpc::Call::make(sn++); 175 | in.params.call_data.to = created; 176 | in.params.call_data.data = abi_append(compiled.hashes["add(uint256)"], 100); 177 | ethrpc::Call::Out out = do_rpc(frontend, cert, in); 178 | uint256_t res = get_result_value(out); 179 | REQUIRE(res == 142); 180 | } 181 | 182 | { 183 | INFO("call mul(3)"); 184 | auto in = ethrpc::Call::make(sn++); 185 | in.params.call_data.to = created; 186 | in.params.call_data.data = abi_append(compiled.hashes["mul(uint256)"], 3); 187 | ethrpc::Call::Out out = do_rpc(frontend, cert, in); 188 | uint256_t res = get_result_value(out); 189 | REQUIRE(res == 126); 190 | } 191 | 192 | { 193 | INFO("call mul(10)"); 194 | auto in = ethrpc::Call::make(sn++); 195 | in.params.call_data.to = created; 196 | in.params.call_data.data = abi_append(compiled.hashes["mul(uint256)"], 10); 197 | ethrpc::Call::Out out = do_rpc(frontend, cert, in); 198 | uint256_t res = get_result_value(out); 199 | REQUIRE(res == 420); 200 | } 201 | 202 | { 203 | INFO("call mul(100)"); 204 | auto in = ethrpc::Call::make(sn++); 205 | in.params.call_data.to = created; 206 | in.params.call_data.data = abi_append(compiled.hashes["mul(uint256)"], 100); 207 | ethrpc::Call::Out out = do_rpc(frontend, cert, in); 208 | uint256_t res = get_result_value(out); 209 | REQUIRE(res == 4200); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /tests/eth_sendTransaction.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #include "ds/logger.h" 4 | #include "enclave/appinterface.h" 5 | #include "shared.h" 6 | 7 | #include 8 | 9 | using namespace ccf; 10 | using namespace evm4ccf; 11 | 12 | template 13 | nlohmann::json json_with( 14 | const nlohmann::json& j, const std::string& field, T&& v) 15 | { 16 | nlohmann::json result = j; 17 | result[field] = v; 18 | return result; 19 | } 20 | 21 | nlohmann::json json_without(const nlohmann::json& j, const std::string& field) 22 | { 23 | nlohmann::json result = j; 24 | result.erase(field); 25 | return result; 26 | } 27 | 28 | TEST_CASE("JSON format" * doctest::test_suite("transactions")) 29 | { 30 | const nlohmann::json basic_request = { 31 | {"from", "0xb60E8dD61C5d32be8058BB8eb970870F07233155"}, 32 | {"to", "0xd46E8dD67C5d32be8058Bb8Eb970870F07244567"}, 33 | {"gas", "0x76c0"}, 34 | {"gasPrice", "0x9184e72a000"}, 35 | {"value", "0x9184e72a"}, 36 | {"data", 37 | "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb97087" 38 | "0f072445675"}, 39 | }; 40 | 41 | { 42 | INFO("Basic roundtrip"); 43 | const auto tc = basic_request.get(); 44 | const nlohmann::json converted = tc; 45 | 46 | const auto target = std::string("Target: ") + basic_request.dump(); 47 | INFO(target); 48 | const auto actual = std::string("Actual: ") + converted.dump(); 49 | INFO(actual); 50 | 51 | CHECK(basic_request == converted); 52 | } 53 | 54 | // to 55 | { 56 | const auto j = json_without(basic_request, "to"); 57 | const auto tc = j.get(); 58 | CHECK(!tc.to.has_value()); 59 | } 60 | 61 | { 62 | const auto j = json_with(basic_request, "to", nullptr); 63 | const auto tc = j.get(); 64 | CHECK(!tc.to.has_value()); 65 | } 66 | 67 | { 68 | const auto j = json_with(basic_request, "to", ""); 69 | const auto tc = j.get(); 70 | CHECK(!tc.to.has_value()); 71 | } 72 | 73 | { 74 | const auto j = json_with(basic_request, "to", "0x0"); 75 | const auto tc = j.get(); 76 | CHECK(tc.to.has_value()); 77 | if (tc.to.has_value()) 78 | { 79 | CHECK(tc.to.value() == 0); 80 | } 81 | } 82 | 83 | { 84 | const auto j = json_with(basic_request, "to", "0x42"); 85 | const auto tc = j.get(); 86 | CHECK(tc.to.has_value()); 87 | if (tc.to.has_value()) 88 | { 89 | CHECK(tc.to.value() == 0x42); 90 | } 91 | } 92 | 93 | // gas 94 | const auto default_gas = rpcparams::MessageCall().gas; 95 | { 96 | const auto j = json_without(basic_request, "gas"); 97 | const auto tc = j.get(); 98 | CHECK(tc.gas == default_gas); 99 | } 100 | 101 | { 102 | const auto j = json_with(basic_request, "gas", nullptr); 103 | const auto tc = j.get(); 104 | CHECK(tc.gas == default_gas); 105 | } 106 | 107 | { 108 | const auto j = json_with(basic_request, "gas", ""); 109 | const auto tc = j.get(); 110 | CHECK(tc.gas == default_gas); 111 | } 112 | 113 | { 114 | const auto j = json_with(basic_request, "gas", "0x42"); 115 | const auto tc = j.get(); 116 | CHECK(tc.gas == 0x42); 117 | } 118 | 119 | // gas_price 120 | const auto default_gas_price = rpcparams::MessageCall().gas_price; 121 | { 122 | const auto j = json_without(basic_request, "gasPrice"); 123 | const auto tc = j.get(); 124 | CHECK(tc.gas_price == default_gas_price); 125 | } 126 | 127 | { 128 | const auto j = json_with(basic_request, "gasPrice", nullptr); 129 | const auto tc = j.get(); 130 | CHECK(tc.gas_price == default_gas_price); 131 | } 132 | 133 | { 134 | const auto j = json_with(basic_request, "gasPrice", ""); 135 | const auto tc = j.get(); 136 | CHECK(tc.gas_price == default_gas_price); 137 | } 138 | 139 | { 140 | const auto j = json_with(basic_request, "gasPrice", "0x42"); 141 | const auto tc = j.get(); 142 | CHECK(tc.gas_price == 0x42); 143 | } 144 | 145 | // value 146 | const auto default_value = rpcparams::MessageCall().value; 147 | { 148 | const auto j = json_without(basic_request, "value"); 149 | const auto tc = j.get(); 150 | CHECK(tc.value == default_value); 151 | } 152 | 153 | { 154 | const auto j = json_with(basic_request, "value", nullptr); 155 | const auto tc = j.get(); 156 | CHECK(tc.value == default_value); 157 | } 158 | 159 | { 160 | const auto j = json_with(basic_request, "value", ""); 161 | const auto tc = j.get(); 162 | CHECK(tc.value == default_value); 163 | } 164 | 165 | { 166 | const auto j = json_with(basic_request, "value", "0x42"); 167 | const auto tc = j.get(); 168 | CHECK(tc.value == 0x42); 169 | } 170 | } 171 | 172 | TEST_CASE("SendTransaction0" * doctest::test_suite("transactions")) 173 | { 174 | NetworkTables nwt; 175 | StubNotifier stubn; 176 | Store& tables = *nwt.tables; 177 | auto cert = setup_tables(tables); 178 | Ethereum frontend = ccfapp::get_rpc_handler(nwt, stubn); 179 | jsonrpc::SeqNo sn = 0; 180 | 181 | const auto compiled = read_bytecode("Call2"); 182 | 183 | // Create a contract by transaction 184 | TxHash deploy_tx_hash; 185 | { 186 | auto in = ethrpc::SendTransaction::make(sn++); 187 | in.params.call_data.data = compiled.deploy; 188 | const ethrpc::SendTransaction::Out out = do_rpc(frontend, cert, in); 189 | deploy_tx_hash = out.result; 190 | } 191 | 192 | // Get contract address from receipt 193 | eevm::Address deployed_address; 194 | { 195 | auto in = ethrpc::GetTransactionReceipt::make(sn++); 196 | in.params.tx_hash = deploy_tx_hash; 197 | ethrpc::GetTransactionReceipt::Out out = 198 | do_rpc(frontend, cert, in).get(); 199 | REQUIRE(out.result.has_value()); 200 | REQUIRE(out.result->contract_address.has_value()); 201 | deployed_address = out.result->contract_address.value(); 202 | } 203 | 204 | // Check the account is correctly created 205 | { 206 | auto in = ethrpc::GetCode::make(sn++); 207 | in.params.address = deployed_address; 208 | ethrpc::GetCode::Out out = do_rpc(frontend, cert, in); 209 | REQUIRE(out.result == compiled.runtime); 210 | } 211 | 212 | const auto mul_100 = abi_append(compiled.hashes["mul(uint256)"], 100); 213 | 214 | // Make read-only Call to deployed contract 215 | { 216 | auto in = ethrpc::Call::make(sn++); 217 | in.params.call_data.to = deployed_address; 218 | in.params.call_data.data = mul_100; 219 | ethrpc::Call::Out out = do_rpc(frontend, cert, in); 220 | uint256_t res = get_result_value(out); 221 | REQUIRE(res == 4200); 222 | } 223 | 224 | // Attempting to do the same with SendTransaction results in transaction hash 225 | // - output is lost 226 | { 227 | auto in = ethrpc::SendTransaction::make(sn++); 228 | in.params.call_data.to = deployed_address; 229 | in.params.call_data.data = mul_100; 230 | do_rpc(frontend, cert, in); // CHECKs internally that RPC doesn't error 231 | } 232 | } 233 | 234 | TEST_CASE("SendTransaction1" * doctest::test_suite("transactions")) 235 | { 236 | // Deploys a contract that uses storage 237 | // See SimpleStore.sol for source 238 | NetworkTables nwt; 239 | StubNotifier stubn; 240 | Store& tables = *nwt.tables; 241 | auto cert = setup_tables(tables); 242 | Ethereum frontend = ccfapp::get_rpc_handler(nwt, stubn); 243 | 244 | const auto store_compiled = read_bytecode("SimpleStore"); 245 | 246 | const auto constructor = abi_append(store_compiled.deploy, 15); 247 | 248 | const auto runtime = store_compiled.runtime; 249 | 250 | auto get = store_compiled.hashes["get()"]; 251 | auto add2 = abi_append(store_compiled.hashes["add(uint256)"], 2); 252 | auto set1 = abi_append(store_compiled.hashes["set(uint256)"], 1); 253 | 254 | TestAccount owner(frontend, tables); 255 | 256 | // Create a contract by transaction 257 | eevm::Address storetest = owner.deploy_contract(constructor); 258 | 259 | // Check the contract is correctly deployed 260 | REQUIRE(owner.get_code(storetest) == runtime); 261 | CHECK(15 == get_result_value(owner.contract_call(storetest, get))); 262 | 263 | owner.contract_transact(storetest, add2); 264 | CHECK(17 == get_result_value(owner.contract_call(storetest, get))); 265 | owner.contract_transact(storetest, add2); 266 | CHECK(19 == get_result_value(owner.contract_call(storetest, get))); 267 | 268 | owner.contract_transact(storetest, set1); 269 | CHECK(1 == get_result_value(owner.contract_call(storetest, get))); 270 | owner.contract_transact(storetest, add2); 271 | CHECK(3 == get_result_value(owner.contract_call(storetest, get))); 272 | 273 | owner.contract_transact(storetest, set1); 274 | CHECK(1 == get_result_value(owner.contract_call(storetest, get))); 275 | owner.contract_transact(storetest, add2); 276 | owner.contract_transact(storetest, add2); 277 | owner.contract_transact(storetest, add2); 278 | owner.contract_transact(storetest, add2); 279 | CHECK(9 == get_result_value(owner.contract_call(storetest, get))); 280 | } 281 | 282 | TEST_CASE("SendTransaction2" * doctest::test_suite("transactions")) 283 | { 284 | // Deploys a ballot contract. 285 | // See Ballot.sol for source. 286 | // Runs some plausible transactions to interact with it. 287 | // This tests a contract with persistent state, interaction with storage, 288 | // and some in-contract access controls. 289 | NetworkTables nwt; 290 | StubNotifier stubn; 291 | Store& tables = *nwt.tables; 292 | auto cert = setup_tables(tables); 293 | Ethereum frontend = ccfapp::get_rpc_handler(nwt, stubn); 294 | 295 | constexpr auto num_proposals = 5; 296 | const auto ballot_compiled = read_bytecode("Ballot"); 297 | auto make_ballot_constructor = [&ballot_compiled](size_t n) { 298 | std::stringstream ss; 299 | ss << ballot_compiled.deploy << std::hex << std::setfill('0') 300 | << std::setw(64) << n; 301 | return ss.str(); 302 | }; 303 | const auto ballot_constructor = make_ballot_constructor(num_proposals); 304 | const auto ballot_deployed = ballot_compiled.runtime; 305 | auto winning_proposal = ballot_compiled.hashes["winningProposal()"]; 306 | 307 | std::vector vote; 308 | for (size_t i = 0; i < num_proposals; ++i) 309 | { 310 | vote.push_back(abi_append(ballot_compiled.hashes["vote(uint8)"], i)); 311 | } 312 | 313 | TestAccount chairperson(frontend, tables); 314 | 315 | constexpr auto num_users = 4; 316 | std::vector users; 317 | for (size_t i = 0; i < num_users; ++i) 318 | users.push_back(TestAccount(frontend, tables)); 319 | 320 | auto make_give_right_to_vote = [&](const TestAccount& ta) { 321 | return abi_append( 322 | ballot_compiled.hashes["giveRightToVote(address)"], ta.address); 323 | }; 324 | std::vector give_right_to_vote; 325 | for (size_t i = 0; i < num_users; ++i) 326 | { 327 | give_right_to_vote.push_back(make_give_right_to_vote(users[i])); 328 | } 329 | 330 | std::vector delegate; 331 | for (size_t i = 0; i < num_users; ++i) 332 | { 333 | delegate.push_back(abi_append( 334 | ballot_compiled.hashes["delegate(address)"], users[i].address)); 335 | } 336 | 337 | // Create a contract by transaction 338 | eevm::Address ballot = chairperson.deploy_contract(ballot_constructor); 339 | REQUIRE(chairperson.get_code(ballot) == ballot_deployed); 340 | 341 | SUBCASE("default winner is 0") 342 | { 343 | const auto winner_hex = chairperson.contract_call(ballot, winning_proposal); 344 | const auto winner_n = get_result_value(winner_hex); 345 | CHECK(0 == winner_n); 346 | 347 | // Anyone can see this 348 | CHECK( 349 | 0 == get_result_value(users[0].contract_call(ballot, winning_proposal))); 350 | CHECK( 351 | 0 == get_result_value(users[1].contract_call(ballot, winning_proposal))); 352 | 353 | // Even new accounts 354 | TestAccount user_n(frontend, tables); 355 | CHECK( 356 | 0 == get_result_value(user_n.contract_call(ballot, winning_proposal))); 357 | } 358 | 359 | SUBCASE("only chairperson can vote initially") 360 | { 361 | constexpr auto winner = 1; 362 | users[0].contract_transact(ballot, vote[winner]); 363 | users[1].contract_transact(ballot, vote[winner]); 364 | CHECK( 365 | 0 == 366 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 367 | 368 | TestAccount user_n(frontend, tables); 369 | user_n.contract_transact(ballot, vote[winner]); 370 | CHECK( 371 | 0 == 372 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 373 | 374 | chairperson.contract_transact(ballot, vote[winner]); 375 | CHECK( 376 | winner == 377 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 378 | CHECK( 379 | winner == 380 | get_result_value(users[0].contract_call(ballot, winning_proposal))); 381 | CHECK( 382 | winner == 383 | get_result_value(user_n.contract_call(ballot, winning_proposal))); 384 | } 385 | 386 | SUBCASE("chairperson can give right to vote") 387 | { 388 | chairperson.contract_transact(ballot, give_right_to_vote[0]); 389 | chairperson.contract_transact(ballot, give_right_to_vote[1]); 390 | chairperson.contract_transact(ballot, give_right_to_vote[2]); 391 | 392 | // Un-nominated person still can't vote 393 | users[3].contract_transact(ballot, vote[2]); 394 | CHECK( 395 | 0 == 396 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 397 | 398 | // the nominees can 399 | users[0].contract_transact(ballot, vote[2]); 400 | CHECK( 401 | 2 == 402 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 403 | 404 | // they can outvote each other 405 | users[1].contract_transact(ballot, vote[1]); 406 | users[2].contract_transact(ballot, vote[1]); 407 | CHECK( 408 | 1 == 409 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 410 | 411 | // they can't change their vote/vote twice 412 | users[0].contract_transact(ballot, vote[2]); 413 | users[1].contract_transact(ballot, vote[2]); 414 | users[2].contract_transact(ballot, vote[2]); 415 | CHECK( 416 | 1 == 417 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 418 | } 419 | 420 | SUBCASE("votes can be delegated") 421 | { 422 | chairperson.contract_transact(ballot, give_right_to_vote[0]); 423 | chairperson.contract_transact(ballot, give_right_to_vote[1]); 424 | chairperson.contract_transact(ballot, give_right_to_vote[2]); 425 | chairperson.contract_transact(ballot, give_right_to_vote[3]); 426 | 427 | // chair votes for prop 1 428 | chairperson.contract_transact(ballot, vote[1]); 429 | CHECK( 430 | 1 == 431 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 432 | 433 | // 0 delegates to 1, who casts both votes for prop 2 434 | users[0].contract_transact(ballot, delegate[1]); 435 | users[1].contract_transact(ballot, vote[2]); 436 | CHECK( 437 | 2 == 438 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 439 | 440 | // 1 tries to delegate to 2, but this is ignored as 1 has voted 441 | // 2 votes for prop 3, but this isn't enough to change the result 442 | users[1].contract_transact(ballot, delegate[2]); 443 | users[2].contract_transact(ballot, vote[3]); 444 | CHECK( 445 | 2 == 446 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 447 | 448 | // 3 and a new user delegate to 2 449 | // their votes count towards prop 3 votes to 3, which is the new winner 450 | users[3].contract_transact(ballot, delegate[2]); 451 | TestAccount user_n(frontend, tables); 452 | chairperson.contract_transact(ballot, make_give_right_to_vote(user_n)); 453 | user_n.contract_transact(ballot, delegate[2]); 454 | CHECK( 455 | 3 == 456 | get_result_value(chairperson.contract_call(ballot, winning_proposal))); 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /tests/eth_signature.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #include "crypto/eth_signature.h" 4 | 5 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 6 | #include 7 | 8 | TEST_CASE("secp256k1-sign-verify" * doctest::test_suite("crypto")) 9 | { 10 | const unsigned char private_key[32] = {0xCC}; 11 | const unsigned char msg_hash[32] = {0xBF}; 12 | int recovery_id = 0; // 0 <= recovery_id <= 3 13 | 14 | EthSignature sig(private_key, msg_hash); 15 | 16 | const EthSignature::public_key_bytes_t& public_key = 17 | sig.recover_public_key(msg_hash); 18 | 19 | REQUIRE_NOTHROW(sig.verify(public_key, msg_hash)); 20 | } 21 | 22 | TEST_CASE("secp256k1-sign-verify-recovered" * doctest::test_suite("crypto")) 23 | { 24 | const unsigned char private_key[32] = {0xCC}; 25 | const unsigned char msg_hash[32] = {0xBF}; 26 | int recovery_id = 0; // 0 <= recovery_id <= 3 27 | 28 | EthSignature sig(private_key, msg_hash); 29 | 30 | const EthSignature::public_key_bytes_t& public_key = 31 | sig.recover_public_key(msg_hash); 32 | 33 | EthSignature sig2; 34 | REQUIRE_NOTHROW( 35 | sig2.verifyRecovered(sig.toBytes(&recovery_id), msg_hash, recovery_id)); 36 | } 37 | -------------------------------------------------------------------------------- /tests/event_logs.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #include "ds/logger.h" 4 | #include "enclave/appinterface.h" 5 | #include "rpc_types.h" 6 | #include "shared.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | // Utils for filtering logs. Should be moved to include if we ever need process 13 | // events in C++ 14 | namespace evm4ccf 15 | { 16 | namespace logfilter 17 | { 18 | struct Filter 19 | { 20 | std::set addresses; 21 | std::vector topics; 22 | }; 23 | 24 | inline bool matches_filter(const Filter& filter, const eevm::LogEntry& log) 25 | { 26 | // If filter defines some addresses but not this, it doesn't match 27 | if ( 28 | !filter.addresses.empty() && 29 | filter.addresses.find(log.address) == filter.addresses.end()) 30 | return false; 31 | 32 | for (size_t i = 0; i < filter.topics.size(); ++i) 33 | { 34 | if (i >= log.topics.size() || filter.topics[i] != log.topics[i]) 35 | { 36 | return false; 37 | } 38 | } 39 | 40 | return true; 41 | } 42 | 43 | inline void get_matching_log_entries( 44 | const Filter& filter, 45 | const std::vector& logs, 46 | std::vector& matches) 47 | { 48 | for (const auto& entry : logs) 49 | { 50 | if (matches_filter(filter, entry)) 51 | { 52 | matches.emplace_back(entry); 53 | } 54 | } 55 | } 56 | } // namespace logfilter 57 | } // namespace evm4ccf 58 | 59 | using namespace ccf; 60 | using namespace evm4ccf; 61 | 62 | using LogEntries = std::vector; 63 | using LogMap = std::map; 64 | using Match = std::pair; 65 | using Matches = std::vector; 66 | 67 | Matches get_matches(const LogMap& lm, const logfilter::Filter& f) 68 | { 69 | Matches matches; 70 | 71 | for (const auto& p : lm) 72 | { 73 | for (const auto& log_entry : p.second) 74 | { 75 | if (logfilter::matches_filter(f, log_entry)) 76 | { 77 | matches.emplace_back(std::make_pair(p.first, log_entry)); 78 | } 79 | } 80 | } 81 | 82 | return matches; 83 | }; 84 | 85 | TEST_CASE("Filter0" * doctest::test_suite("filter")) 86 | { 87 | // Tests the filter matching logic in isolation 88 | using namespace evm4ccf::logfilter; 89 | using namespace eevm; 90 | 91 | const TxHash tx0{0xbeef}; 92 | const TxHash tx1{0xcafe}; 93 | const TxHash tx2{0xfeed}; 94 | const TxHash logless{0xdead}; 95 | 96 | const Address address_a = eevm::to_uint256("0xa"); 97 | const Address address_b = eevm::to_uint256("0xbb"); 98 | const Address address_c = eevm::to_uint256("0xccc"); 99 | 100 | const log::Topic topic0 = eevm::to_uint256("0x0"); 101 | const log::Topic topic1 = eevm::to_uint256("0x10"); 102 | const log::Topic topic2 = eevm::to_uint256("0x200"); 103 | const log::Topic topic3 = eevm::to_uint256("0x3000"); 104 | 105 | const log::Data d0 = to_bytes("0x0123456789abcdef"); 106 | const log::Data d1 = to_bytes("0xffaffaffaffa"); 107 | const log::Data d2 = to_bytes( 108 | "0xabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba" 109 | "abbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba"); 110 | const log::Data d3 = to_bytes("0x1337"); 111 | const log::Data d4 = to_bytes("0x42"); 112 | 113 | // Create some log entries manually 114 | LogMap transaction_logs; 115 | { 116 | transaction_logs.emplace( 117 | tx0, 118 | LogEntries{{address_a, d0, {}}, 119 | {address_a, d1, {topic0}}, 120 | {address_a, d2, {topic0, topic1}}, 121 | {address_a, d3, {topic0, topic1, topic2}}, 122 | {address_a, d4, {topic0, topic1, topic2, topic3}}}); 123 | 124 | transaction_logs.emplace( 125 | tx1, 126 | LogEntries{{address_a, d0, {topic2}}, 127 | {address_b, d0, {topic2}}, 128 | {address_b, d0, {topic2}}, 129 | {address_b, d1, {topic2}}, 130 | {address_c, d1, {topic2}}}); 131 | 132 | transaction_logs.emplace( 133 | tx2, 134 | LogEntries{{address_c, d4, {topic2, topic2, topic2}}, 135 | {address_a, d1, {topic0, topic1}}, 136 | {address_a, d3, {topic1, topic0}}, 137 | {address_a, d0, {topic0, topic1, topic2}}, 138 | {address_c, d4, {topic2, topic2}}, 139 | {address_c, d4, {topic2, topic2, topic1}}, 140 | {address_b, d0, {topic2}}, 141 | {address_a, d4, {topic2}}, 142 | {address_b, d0, {topic0, topic1, topic2}}, 143 | {address_a, d2, {topic0, topic2, topic1}}}); 144 | 145 | transaction_logs.emplace(logless, LogEntries{}); 146 | } 147 | 148 | // Util functions for counting matches 149 | auto get_tx_count = [](const Matches& ms, const TxHash& n) { 150 | return std::count_if( 151 | ms.begin(), ms.end(), [&n](const auto& p) { return p.first == n; }); 152 | }; 153 | 154 | auto get_address_count = [](const Matches& ms, const Address& a) { 155 | return std::count_if(ms.begin(), ms.end(), [&a](const auto& p) { 156 | return p.second.address == a; 157 | }); 158 | }; 159 | 160 | auto get_data_count = [](const Matches& ms, const log::Data& d) { 161 | return std::count_if( 162 | ms.begin(), ms.end(), [&d](const auto& p) { return p.second.data == d; }); 163 | }; 164 | 165 | // Build some filters, check they return the expected log entries 166 | SUBCASE("filter can get everything") 167 | { 168 | Filter filter; 169 | filter.addresses = {}; 170 | filter.topics = {}; 171 | auto matches = get_matches(transaction_logs, filter); 172 | 173 | CHECK(matches.size() == 20); 174 | CHECK(0 == get_tx_count(matches, logless)); 175 | } 176 | 177 | SUBCASE("filter can get single result") 178 | { 179 | Filter filter; 180 | filter.addresses = {address_a}; 181 | filter.topics = {topic0, topic1, topic2, topic3}; 182 | auto matches = get_matches(transaction_logs, filter); 183 | 184 | CHECK(matches.size() == 1); 185 | const auto& p = matches[0]; 186 | CHECK(p.first == tx0); 187 | const auto& e = p.second; 188 | CHECK(e.address == address_a); 189 | CHECK(e.topics == filter.topics); 190 | CHECK(e.data == d4); 191 | CHECK(0 == get_tx_count(matches, logless)); 192 | } 193 | 194 | SUBCASE("filters can get multiple results") 195 | { 196 | Filter filter; 197 | filter.addresses = {address_a}; 198 | filter.topics = {topic0, topic1, topic2}; 199 | auto matches = get_matches(transaction_logs, filter); 200 | 201 | CHECK(matches.size() == 3); 202 | for (const auto& p : matches) 203 | { 204 | const auto& e = p.second; 205 | CHECK(e.address == address_a); 206 | CHECK(e.topics[0] == topic0); 207 | CHECK(e.topics[1] == topic1); 208 | CHECK(e.topics[2] == topic2); 209 | } 210 | CHECK(2 == get_tx_count(matches, tx0)); 211 | CHECK(1 == get_tx_count(matches, tx2)); 212 | CHECK(0 == get_tx_count(matches, logless)); 213 | } 214 | 215 | SUBCASE("entries can repeat, within and across transactions") 216 | { 217 | Filter filter; 218 | filter.addresses = {address_b}; 219 | filter.topics = {topic2}; 220 | auto matches = get_matches(transaction_logs, filter); 221 | 222 | CHECK(matches.size() == 4); 223 | for (const auto& p : matches) 224 | { 225 | const auto& e = p.second; 226 | CHECK(e.address == address_b); 227 | CHECK(e.topics[0] == topic2); 228 | } 229 | CHECK(3 == get_tx_count(matches, tx1)); 230 | CHECK(1 == get_tx_count(matches, tx2)); 231 | CHECK(3 == get_data_count(matches, d0)); 232 | CHECK(1 == get_data_count(matches, d1)); 233 | CHECK(0 == get_tx_count(matches, logless)); 234 | } 235 | 236 | SUBCASE("filter can omit address") 237 | { 238 | Filter filter; 239 | filter.addresses = {}; 240 | filter.topics = {topic0, topic1, topic2}; 241 | auto matches = get_matches(transaction_logs, filter); 242 | 243 | CHECK(matches.size() == 4); 244 | for (const auto& p : matches) 245 | { 246 | const auto& e = p.second; 247 | CHECK(e.topics[0] == topic0); 248 | CHECK(e.topics[1] == topic1); 249 | CHECK(e.topics[2] == topic2); 250 | } 251 | CHECK(2 == get_tx_count(matches, tx0)); 252 | CHECK(2 == get_tx_count(matches, tx2)); 253 | CHECK(3 == get_address_count(matches, address_a)); 254 | CHECK(1 == get_address_count(matches, address_b)); 255 | CHECK(2 == get_data_count(matches, d0)); 256 | CHECK(1 == get_data_count(matches, d3)); 257 | CHECK(1 == get_data_count(matches, d4)); 258 | CHECK(0 == get_tx_count(matches, logless)); 259 | } 260 | 261 | SUBCASE("filter can omit topics") 262 | { 263 | Filter filter; 264 | filter.addresses = {address_b, address_c}; 265 | filter.topics = {}; 266 | auto matches = get_matches(transaction_logs, filter); 267 | 268 | CHECK(matches.size() == 9); 269 | CHECK(0 == get_tx_count(matches, tx0)); 270 | CHECK(4 == get_tx_count(matches, tx1)); 271 | CHECK(5 == get_tx_count(matches, tx2)); 272 | CHECK(0 == get_address_count(matches, address_a)); 273 | CHECK(5 == get_address_count(matches, address_b)); 274 | CHECK(4 == get_address_count(matches, address_c)); 275 | CHECK(4 == get_data_count(matches, d0)); 276 | CHECK(2 == get_data_count(matches, d1)); 277 | CHECK(0 == get_data_count(matches, d2)); 278 | CHECK(0 == get_data_count(matches, d3)); 279 | CHECK(3 == get_data_count(matches, d4)); 280 | CHECK(0 == get_tx_count(matches, logless)); 281 | } 282 | } 283 | 284 | TEST_CASE("TransactionLogs0" * doctest::test_suite("logs")) 285 | { 286 | NetworkTables nwt; 287 | StubNotifier stubn; 288 | Store& tables = *nwt.tables; 289 | auto cert = setup_tables(tables); 290 | Ethereum frontend = ccfapp::get_rpc_handler(nwt, stubn); 291 | jsonrpc::SeqNo sn = 0; 292 | eevm::Address created; 293 | 294 | SUBCASE("log0") 295 | { 296 | // Store ED in memory, then call log0 to write this to the log 297 | const auto code = "0x60ED60005260206000A0"; 298 | 299 | // Create a contract with simple logging code 300 | { 301 | created = deploy_contract(code, frontend, cert); 302 | } 303 | 304 | // Send a transaction that calls this code 305 | TxHash tx_hash; 306 | { 307 | auto in = ethrpc::SendTransaction::make(sn++); 308 | in.params.call_data.from = created; 309 | in.params.call_data.to = created; 310 | in.params.call_data.data = "0x"; 311 | ethrpc::SendTransaction::Out out = do_rpc(frontend, cert, in); 312 | tx_hash = out.result; 313 | } 314 | 315 | // Get the produced logs from the TxReceipt 316 | std::vector logs; 317 | { 318 | auto in = ethrpc::GetTransactionReceipt::make(sn++); 319 | in.params.tx_hash = tx_hash; 320 | ethrpc::GetTransactionReceipt::Out out = do_rpc(frontend, cert, in); 321 | REQUIRE(out.result.has_value()); 322 | logs = out.result->logs; 323 | } 324 | 325 | // Check the logs match what we expect 326 | { 327 | REQUIRE(logs.size() == 1); 328 | const auto& log_entry = logs[0]; 329 | CHECK( 330 | eevm::from_big_endian(log_entry.data.data(), log_entry.data.size()) == 331 | 0xED); 332 | CHECK(log_entry.topics.empty()); 333 | } 334 | } 335 | 336 | // Store 0x1234ABCD in memory, then call log2 to write these 4 bytes to 337 | // the log with topics [0xE, 0xF] 338 | const auto code = "0x631234ABCD600052600F600E6004601CA2"; 339 | SUBCASE("log2") 340 | { 341 | // Deploy 342 | { 343 | created = deploy_contract(code, frontend, cert); 344 | } 345 | 346 | { 347 | // Call code 348 | auto in = ethrpc::SendTransaction::make(sn++); 349 | in.params.call_data.from = created; 350 | in.params.call_data.to = created; 351 | in.params.call_data.data = 352 | "0xFFAAFFAAFFAA"; // Input data is provided, but unused 353 | ethrpc::SendTransaction::Out out = do_rpc(frontend, cert, in); 354 | const auto tx_hash = out.result; 355 | 356 | // Get logs 357 | auto tx_in = ethrpc::GetTransactionReceipt::make(sn++); 358 | tx_in.params.tx_hash = tx_hash; 359 | ethrpc::GetTransactionReceipt::Out tx_out = do_rpc(frontend, cert, tx_in); 360 | REQUIRE(tx_out.result.has_value()); 361 | const auto logs = tx_out.result->logs; 362 | 363 | // Check log contents 364 | REQUIRE(logs.size() == 1); 365 | const auto& log_entry = logs[0]; 366 | CHECK(log_entry.data.size() == 4); 367 | CHECK( 368 | eevm::from_big_endian(log_entry.data.data(), log_entry.data.size()) == 369 | 0x1234ABCD); 370 | CHECK(log_entry.topics.size() == 2); 371 | CHECK(log_entry.topics[0] == 0xE); 372 | CHECK(log_entry.topics[1] == 0xF); 373 | } 374 | } 375 | 376 | SUBCASE("similar transactions produce distinct logs") 377 | { 378 | // Deploy 379 | { 380 | created = deploy_contract(code, frontend, cert); 381 | } 382 | 383 | std::vector all_logs; 384 | constexpr auto n = 5; 385 | for (auto i = 0; i < n; ++i) 386 | { 387 | // Send transactions, identical other than sequence number 388 | auto in = ethrpc::SendTransaction::make(sn++); 389 | in.params.call_data.from = created; 390 | in.params.call_data.to = created; 391 | ethrpc::SendTransaction::Out out = do_rpc(frontend, cert, in); 392 | const auto tx_hash = out.result; 393 | 394 | // Get logs 395 | auto tx_in = ethrpc::GetTransactionReceipt::make(sn++); 396 | tx_in.params.tx_hash = tx_hash; 397 | ethrpc::GetTransactionReceipt::Out tx_out = do_rpc(frontend, cert, tx_in); 398 | REQUIRE(tx_out.result.has_value()); 399 | all_logs.insert( 400 | all_logs.end(), tx_out.result->logs.begin(), tx_out.result->logs.end()); 401 | } 402 | 403 | CHECK(all_logs.size() == n); 404 | for (const auto& log_entry : all_logs) 405 | { 406 | CHECK(log_entry.data.size() == 4); 407 | CHECK( 408 | eevm::from_big_endian(log_entry.data.data(), log_entry.data.size()) == 409 | 0x1234ABCD); 410 | CHECK(log_entry.topics.size() == 2); 411 | CHECK(log_entry.topics[0] == 0xE); 412 | CHECK(log_entry.topics[1] == 0xF); 413 | } 414 | } 415 | } 416 | 417 | TEST_CASE("TransactionLogs1" * doctest::test_suite("logs")) 418 | { 419 | using namespace evm4ccf::logfilter; 420 | 421 | // Test filter installation, uninstallation, log-retrieval 422 | NetworkTables nwt; 423 | StubNotifier stubn; 424 | Store& tables = *nwt.tables; 425 | auto cert = setup_tables(tables); 426 | Ethereum frontend = ccfapp::get_rpc_handler(nwt, stubn); 427 | jsonrpc::SeqNo sn = 0; 428 | 429 | LogMap tx_logs; 430 | 431 | TestAccount owner(frontend, tables); 432 | TestAccount sender_a(frontend, tables); 433 | TestAccount sender_b(frontend, tables); 434 | 435 | auto retrieve_logs = [&](const TxHash& tx_hash) { 436 | auto in = ethrpc::GetTransactionReceipt::make(sn++); 437 | in.params.tx_hash = tx_hash; 438 | ethrpc::GetTransactionReceipt::Out out = do_rpc(frontend, cert, in); 439 | REQUIRE(out.result.has_value()); 440 | 441 | REQUIRE( 442 | tx_logs.find(tx_hash) == 443 | tx_logs.end()); // Shouldn't get duplicate tx hashes 444 | 445 | tx_logs[tx_hash] = out.result->logs; 446 | }; 447 | 448 | // Deploy a contract compiled from Solidity, that does logging through events 449 | // See Events.sol for source 450 | const auto compiled = read_bytecode("Events"); 451 | 452 | // Call constructor 453 | const auto constructor = compiled.deploy; 454 | const auto deployed_code = compiled.runtime; 455 | const auto a = eevm::keccak_256("Name(address)"); 456 | const auto topic_eventhash_name = eevm::from_big_endian(a.data(), a.size()); 457 | const auto b = eevm::keccak_256("Interesting(uint256,uint256,uint256)"); 458 | const auto topic_eventhash_interesting = 459 | eevm::from_big_endian(b.data(), b.size()); 460 | TxHash deploy_hash; 461 | const auto contract = owner.deploy_contract(constructor, &deploy_hash); 462 | REQUIRE(owner.get_code(contract) == deployed_code); 463 | 464 | retrieve_logs(deploy_hash); 465 | 466 | // Make helper functions to call the contract's public methods 467 | auto name_self = [&contract, &retrieve_logs](TestAccount& ta) { 468 | std::stringstream ss; 469 | ss << "01984892" << std::hex << std::setfill('0') << std::setw(64) 470 | << ta.address; 471 | const auto tx_hash = ta.contract_transact(contract, ss.str()); 472 | retrieve_logs(tx_hash); 473 | }; 474 | 475 | auto emit_event = [&contract, &retrieve_logs]( 476 | TestAccount& ta, 477 | const uint256_t& topic, 478 | const uint256_t& b, 479 | const uint256_t& c) { 480 | std::stringstream ss; 481 | ss << "789ab20b"; 482 | ss << std::hex << std::setfill('0') << std::setw(64) << topic; 483 | ss << std::hex << std::setfill('0') << std::setw(64) << b; 484 | ss << std::hex << std::setfill('0') << std::setw(64) << c; 485 | const auto tx_hash = ta.contract_transact(contract, ss.str()); 486 | retrieve_logs(tx_hash); 487 | }; 488 | 489 | auto get_event_count = 490 | [](const Matches& ms, const uint256_t& a, const uint256_t& b) { 491 | eevm::log::Data expected; 492 | expected.resize(64u); 493 | eevm::to_big_endian(a, expected.data()); 494 | eevm::to_big_endian(b, expected.data() + 32u); 495 | return std::count_if(ms.begin(), ms.end(), [&expected](const auto& p) { 496 | return p.second.data == expected; 497 | }); 498 | }; 499 | 500 | SUBCASE("transaction logs can be filtered") 501 | { 502 | Filter filter_everything; 503 | { 504 | INFO("constructor produced one event"); 505 | auto all_logs = get_matches(tx_logs, filter_everything); 506 | CHECK(all_logs.size() == 1); 507 | const auto& p = all_logs[0]; 508 | const auto& entry = p.second; 509 | CHECK(entry.address == contract); 510 | CHECK(entry.topics.empty()); 511 | CHECK( 512 | 0xdeadbeef == 513 | eevm::from_big_endian(entry.data.data(), entry.data.size())); 514 | } 515 | 516 | // Send transactions 517 | name_self(owner); 518 | name_self(sender_a); 519 | emit_event(sender_a, 0xbeef, 0xaaaa, 0xaaaa); 520 | emit_event(sender_b, 0xbeef, 0x1111, 0x1111); 521 | emit_event(sender_b, 0xfeeb, 0x2211, 0xaaaa); 522 | emit_event(sender_b, 0x0, 0x1111, 0x1111); 523 | emit_event(sender_b, 0xbeef, 0xcafe, 0xfeed); 524 | emit_event(sender_b, 0xbeef, 0xcafe, 0xfeed); 525 | emit_event(sender_a, 0xbeef, 0xcafe, 0xfeed); 526 | 527 | auto all_logs = get_matches(tx_logs, filter_everything); 528 | 529 | SUBCASE("only the contract has created logs") 530 | { 531 | Filter contract_only; 532 | contract_only.addresses = {contract}; 533 | auto contract_logs = get_matches(tx_logs, contract_only); 534 | CHECK(all_logs.size() == contract_logs.size()); 535 | for (const auto& log_pair : all_logs) 536 | { 537 | CHECK(log_pair.second.address == contract); 538 | } 539 | } 540 | 541 | SUBCASE("logs can be filtered by event") 542 | { 543 | Filter name_events_only; 544 | name_events_only.topics = {topic_eventhash_name}; 545 | auto name_event_logs = get_matches(tx_logs, name_events_only); 546 | CHECK(name_event_logs.size() == 2); 547 | std::set logged_names; 548 | for (const auto& log_pair : name_event_logs) 549 | logged_names.insert(eevm::from_big_endian( 550 | log_pair.second.data.data(), log_pair.second.data.size())); 551 | CHECK(logged_names.find(owner.address) != logged_names.end()); 552 | CHECK(logged_names.find(sender_a.address) != logged_names.end()); 553 | } 554 | 555 | SUBCASE("logs can be filtered by indexed topic") 556 | { 557 | Filter filter_beef; 558 | filter_beef.topics = {topic_eventhash_interesting, 0xbeef}; 559 | auto beef_logs = get_matches(tx_logs, filter_beef); 560 | CHECK(5 == beef_logs.size()); 561 | CHECK(1 == get_event_count(beef_logs, 0xaaaa, 0xaaaa)); 562 | CHECK(1 == get_event_count(beef_logs, 0x1111, 0x1111)); 563 | CHECK(3 == get_event_count(beef_logs, 0xcafe, 0xfeed)); 564 | } 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /tests/kv_conversions.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #include "../src/app/tables.h" 4 | #include "kv/kvserialiser.h" 5 | 6 | #include 7 | #include 8 | 9 | using namespace ccf; 10 | using namespace evm4ccf; 11 | 12 | #ifdef USE_NLJSON_KV_SERIALISER 13 | using KvWriter = kv::JsonWriter; 14 | using KvReader = kv::JsonReader; 15 | #else 16 | using KvWriter = kv::MsgPackWriter; 17 | using KvReader = kv::MsgPackReader; 18 | #endif 19 | 20 | // Template 21 | template 22 | void write_all(KvWriter& writer, Ts&&... ts); 23 | 24 | // Terminating final case 25 | template <> 26 | inline void write_all(KvWriter& writer) 27 | {} 28 | 29 | // General case - recurse 30 | template 31 | inline void write_all(KvWriter& writer, T&& t, Ts&&... ts) 32 | { 33 | writer.append(t); 34 | write_all(writer, std::forward(ts)...); 35 | } 36 | 37 | // Template 38 | template 39 | void read_and_compare_all(KvReader& reader, Ts&&... ts); 40 | 41 | // Terminating final case 42 | template <> 43 | inline void read_and_compare_all(KvReader& reader) 44 | { 45 | REQUIRE(reader.is_eos()); 46 | } 47 | 48 | // General case - recurse 49 | template 50 | inline void read_and_compare_all(KvReader& reader, T&& t, Ts&&... ts) 51 | { 52 | REQUIRE(reader.read_next>() == t); 53 | read_and_compare_all(reader, std::forward(ts)...); 54 | } 55 | 56 | // Require that the given arguments survive a roundtrip. Write all, read all, 57 | // check each matches 58 | template 59 | void require_roundtrip(Ts&&... ts) 60 | { 61 | KvWriter writer; 62 | 63 | write_all(writer, std::forward(ts)...); 64 | 65 | const auto raw_data = writer.get_raw_data(); 66 | KvReader reader(raw_data.data(), raw_data.size()); 67 | 68 | read_and_compare_all(reader, std::forward(ts)...); 69 | } 70 | 71 | // 72 | // Generators for creating plausible random contents 73 | // 74 | template 75 | T make_rand() 76 | { 77 | return (T)rand(); 78 | } 79 | 80 | std::vector rand_bytes(size_t max_len = 100) 81 | { 82 | std::vector result(rand() % max_len); 83 | if (result.empty()) // Don't allow empty bytes 84 | result.resize(1); 85 | for (size_t i = 0; i < result.size(); ++i) 86 | result[i] = (uint8_t)rand(); 87 | return result; 88 | } 89 | 90 | template <> 91 | std::vector make_rand>() 92 | { 93 | return rand_bytes(); 94 | } 95 | 96 | template <> 97 | uint256_t make_rand() 98 | { 99 | const auto bytes = rand_bytes(32); 100 | return eevm::from_big_endian(bytes.data(), bytes.size()); 101 | } 102 | 103 | template <> 104 | eevm::LogEntry make_rand() 105 | { 106 | std::vector topics(rand() % 4); 107 | for (size_t i = 0; i < topics.size(); ++i) 108 | topics[i] = make_rand(); 109 | return eevm::LogEntry{make_rand(), 110 | make_rand(), 111 | topics}; 112 | } 113 | 114 | template <> 115 | evm4ccf::TxResult make_rand() 116 | { 117 | std::vector logs(rand() % 10); 118 | for (size_t i = 0; i < logs.size(); ++i) 119 | logs[i] = make_rand(); 120 | return evm4ccf::TxResult{make_rand(), 121 | logs}; 122 | } 123 | 124 | template <> 125 | evm4ccf::BlockHeader make_rand() 126 | { 127 | return evm4ccf::BlockHeader{ 128 | make_rand(), 129 | make_rand(), 130 | make_rand(), 131 | make_rand(), 132 | make_rand(), 133 | make_rand(), 134 | make_rand()}; 135 | } 136 | 137 | using namespace intx; 138 | const uint256_t address = 0x4af4dcE351A4747B5b33Fcf66202612736401f95_u256; 139 | 140 | TEST_CASE("Hex-string conversion" * doctest::test_suite("conversions")) 141 | { 142 | using A32 = std::array; 143 | { 144 | A32 arr; 145 | for (size_t i = 0; i < arr.size(); ++i) 146 | { 147 | arr[i] = (uint8_t)i; 148 | } 149 | const auto s = eevm::to_hex_string(arr); 150 | A32 arr2; 151 | array_from_hex_string(arr2, s); 152 | 153 | REQUIRE(arr == arr2); 154 | } 155 | 156 | { 157 | const auto s = 158 | "0x9c93e6106f4b66c515d2e491e58799a8df69e95ad1ecf9263465d200203583e9"; 159 | A32 arr; 160 | array_from_hex_string(arr, s); 161 | const auto s2 = eevm::to_hex_string(arr); 162 | 163 | REQUIRE(s == s2); 164 | } 165 | } 166 | 167 | TEST_CASE("Empty" * doctest::test_suite("conversions")) 168 | { 169 | require_roundtrip(); 170 | } 171 | 172 | TEST_CASE("uint256_t" * doctest::test_suite("conversions")) 173 | { 174 | const uint256_t a = 0; 175 | const uint256_t b = 1; 176 | const uint256_t c = 0x123412341234123412341234123412341234_u256; 177 | const auto d = address; 178 | 179 | require_roundtrip(a, b, c, d); 180 | require_roundtrip(make_rand()); 181 | } 182 | 183 | TEST_CASE("eevm::LogEntry" * doctest::test_suite("conversions")) 184 | { 185 | const eevm::LogEntry a{}; 186 | const eevm::LogEntry b{0x1, {0x1, 0x2, 0x3, 0x4, 0x5, 0x6}, {0xaabb}}; 187 | const eevm::LogEntry c{address, {}, {0xa, 0xb, 0xc, 0xd}}; 188 | 189 | require_roundtrip(a, b, c); 190 | require_roundtrip(make_rand()); 191 | } 192 | 193 | TEST_CASE("evm4ccf::TxResult" * doctest::test_suite("conversions")) 194 | { 195 | const evm4ccf::TxResult a{}; 196 | const evm4ccf::TxResult b{0x1, 197 | {{0x1, {0x1, 0x2, 0x3, 0x4, 0x5, 0x6}, {0xaabb}}}}; 198 | const evm4ccf::TxResult c{address, 199 | { 200 | {0x1, {0x1, 0x2, 0x3, 0x4, 0x5, 0x6}, {0xaabb}}, 201 | {address, 202 | {0x0, 0x0, 0xff, 0xfe, 0xef, 0xee, 0xaa}, 203 | {0xaabb, 0xab, 0xcd, 0xdc}}, 204 | }}; 205 | 206 | require_roundtrip(a, b, c); 207 | require_roundtrip(make_rand()); 208 | } 209 | 210 | TEST_CASE("evm4ccf::BlockHeader" * doctest::test_suite("conversions")) 211 | { 212 | const evm4ccf::BlockHeader a{}; 213 | const evm4ccf::BlockHeader b{0, 1, 2, 3, 4}; 214 | const evm4ccf::BlockHeader c{0x55, 0x44, 0x33, 0x22, 0x11}; 215 | 216 | require_roundtrip(a, b, c); 217 | require_roundtrip(make_rand()); 218 | } 219 | 220 | TEST_CASE("mixed random" * doctest::test_suite("conversions")) 221 | { 222 | require_roundtrip( 223 | make_rand(), 224 | make_rand(), 225 | make_rand(), 226 | make_rand()); 227 | 228 | require_roundtrip( 229 | make_rand(), 230 | make_rand(), 231 | make_rand(), 232 | make_rand(), 233 | make_rand(), 234 | make_rand(), 235 | make_rand(), 236 | make_rand()); 237 | } 238 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | web3[dev]==4.9.2 2 | -------------------------------------------------------------------------------- /tests/shared.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #include "shared.h" 4 | 5 | #include "ds/files.h" 6 | #include "ethereum_transaction.h" 7 | 8 | #include 9 | 10 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 11 | #include 12 | 13 | using namespace ccf; 14 | using namespace evm4ccf; 15 | 16 | nlohmann::json unpack(const std::vector& data) 17 | { 18 | return unpack(data, s_packType); 19 | } 20 | 21 | std::vector setup_tables(Store& tables) 22 | { 23 | tables.set_encryptor(std::make_shared()); 24 | 25 | // Initialise VALUES tables 26 | ccf::Store::Tx tx; 27 | auto values = tables.get(ccf::Tables::VALUES); 28 | auto v = tx.get_view(*values); 29 | for (int id_type = 0; id_type < ccf::ValueIds::END_ID; id_type++) 30 | v->put(id_type, 0); 31 | 32 | REQUIRE(tx.commit() == kv::CommitSuccess::OK); 33 | 34 | return add_user_cert(tables); 35 | } 36 | 37 | uint256_t get_result_value(const std::string& s) 38 | { 39 | REQUIRE( 40 | s.size() <= 41 | 66); // Check the result is a single uint256 (hex-encoded string) 42 | return eevm::to_uint256(s); 43 | } 44 | 45 | uint256_t get_result_value(const ethrpc::Call::Out& response) 46 | { 47 | return get_result_value(response.result); 48 | } 49 | 50 | evm4ccf::ByteData make_deployment_code(const evm4ccf::ByteData& runtime_code) 51 | { 52 | const auto code_bytes = eevm::to_bytes(runtime_code); 53 | 54 | std::vector deploy_bytecode; 55 | 56 | if (deploy_bytecode.size() > 0xffff) 57 | { 58 | throw std::logic_error("This function only handles small code"); 59 | } 60 | 61 | // Reserve space for the code pushed below 62 | deploy_bytecode.reserve(5 * code_bytes.size() + 5); 63 | 64 | // Store runtime code in memory 65 | for (uint8_t i = 0u; i < code_bytes.size(); ++i) 66 | { 67 | // Push value 68 | deploy_bytecode.emplace_back(eevm::Opcode::PUSH1); 69 | deploy_bytecode.emplace_back(code_bytes[i]); 70 | 71 | // Push offset 72 | deploy_bytecode.emplace_back(eevm::Opcode::PUSH1); 73 | deploy_bytecode.emplace_back(i); 74 | 75 | // Store byte, popping offset then value 76 | deploy_bytecode.emplace_back(eevm::Opcode::MSTORE8); 77 | } 78 | 79 | // Return runtime code from memory 80 | { 81 | // Push size 82 | deploy_bytecode.emplace_back(eevm::Opcode::PUSH1); 83 | deploy_bytecode.emplace_back((uint8_t)code_bytes.size()); 84 | 85 | // Push offset 86 | deploy_bytecode.emplace_back(eevm::Opcode::PUSH1); 87 | deploy_bytecode.emplace_back(0u); 88 | 89 | // Return, popping offset then size 90 | deploy_bytecode.emplace_back(eevm::Opcode::RETURN); 91 | } 92 | 93 | return eevm::to_hex_string(deploy_bytecode); 94 | } 95 | 96 | CompiledBytecode read_bytecode(const std::string& contract_name) 97 | { 98 | constexpr auto env_var = "CONTRACTS_DIR"; 99 | const auto contracts_dir = getenv(env_var); 100 | if (!contracts_dir) 101 | { 102 | throw std::logic_error( 103 | "Test is trying to read contract '" + contract_name + 104 | "', but environment var " + env_var + " is not set"); 105 | } 106 | 107 | const std::string contract_path = 108 | std::string(contracts_dir) + "/" + contract_name + "_combined.json"; 109 | 110 | const auto j = files::slurp_json(contract_path); 111 | 112 | const std::string element_id = contract_name + ".sol:" + contract_name; 113 | const auto contract_element = j["contracts"][element_id]; 114 | const evm4ccf::ByteData deploy = 115 | "0x" + contract_element["bin"].get(); 116 | const evm4ccf::ByteData runtime = 117 | "0x" + contract_element["bin-runtime"].get(); 118 | const auto hashes = contract_element["hashes"]; 119 | return {deploy, runtime, hashes}; 120 | } 121 | 122 | nlohmann::json do_rpc( 123 | Ethereum& handler, 124 | const std::vector& cert, 125 | nlohmann::json pc, 126 | bool success) 127 | { 128 | auto rpc = pc.dump(); 129 | INFO("Sending RPC: " << rpc); 130 | 131 | const enclave::SessionContext session(0, cert); 132 | auto packed = pack(pc); 133 | const auto rpc_ctx = enclave::make_rpc_context(session, packed); 134 | auto r = handler->process(rpc_ctx); 135 | auto j = unpack(r.value()); 136 | 137 | auto response = j.dump(); 138 | INFO("Response: " << response); 139 | 140 | if (j.find(std::string(jsonrpc::ERR)) != j.end()) 141 | { 142 | REQUIRE_FALSE(success); 143 | } 144 | else 145 | { 146 | REQUIRE(success); 147 | } 148 | 149 | return j; 150 | } 151 | 152 | TestAccount::TestAccount(Ethereum ef, ccf::Store& tables) : 153 | frontend(ef), 154 | sn(0), 155 | privk(std::make_unique(MBEDTLS_ECP_DP_SECP256K1)), 156 | cert(add_user_cert(tables)) 157 | { 158 | address = evm4ccf::get_address_from_public_key_asn1(privk->public_key_asn1()); 159 | } 160 | 161 | ByteData TestAccount::get_code(const eevm::Address& contract) 162 | { 163 | auto in = ethrpc::GetCode::make(sn++); 164 | in.params.address = contract; 165 | ethrpc::GetCode::Out out = do_rpc(frontend, cert, in); 166 | 167 | return out.result; 168 | } 169 | 170 | eevm::Address TestAccount::deploy_contract( 171 | const ByteData& code, evm4ccf::TxHash* o_deploy_hash) 172 | { 173 | auto send_in = ethrpc::SendTransaction::make(sn++); 174 | send_in.params.call_data.from = address; 175 | send_in.params.call_data.data = code; 176 | ethrpc::SendTransaction::Out send_out = do_rpc(frontend, cert, send_in); 177 | const auto tx_hash = send_out.result; 178 | 179 | auto get_receipt_in = ethrpc::GetTransactionReceipt::make(sn++); 180 | get_receipt_in.params.tx_hash = tx_hash; 181 | ethrpc::GetTransactionReceipt::Out get_receipt_out = 182 | do_rpc(frontend, cert, get_receipt_in); 183 | REQUIRE(get_receipt_out.result.has_value()); 184 | 185 | if (o_deploy_hash != nullptr) 186 | *o_deploy_hash = tx_hash; 187 | 188 | return get_receipt_out.result->contract_address.value(); 189 | } 190 | 191 | DeployedContract TestAccount::deploy_private_contract( 192 | const ByteData& code, 193 | const ContractParticipants& participants, 194 | evm4ccf::TxHash* o_deploy_hash) 195 | { 196 | auto send_in = ethrpc::SendTransaction::make(sn++); 197 | send_in.params.call_data.from = address; 198 | send_in.params.call_data.data = code; 199 | send_in.params.call_data.private_for = participants; 200 | 201 | ethrpc::SendTransaction::Out send_out = do_rpc(frontend, cert, send_in); 202 | const auto tx_hash = send_out.result; 203 | 204 | auto get_receipt_in = ethrpc::GetTransactionReceipt::make(sn++); 205 | get_receipt_in.params.tx_hash = tx_hash; 206 | ethrpc::GetTransactionReceipt::Out get_receipt_out = 207 | do_rpc(frontend, cert, get_receipt_in); 208 | REQUIRE(get_receipt_out.result.has_value()); 209 | 210 | if (o_deploy_hash != nullptr) 211 | *o_deploy_hash = tx_hash; 212 | 213 | return DeployedContract( 214 | get_receipt_out.result->contract_address.value(), participants); 215 | } 216 | 217 | nlohmann::json TestAccount::contract_transact_raw( 218 | const DeployedContract& contract, 219 | const ByteData& code, 220 | bool expect_success /* = true*/) 221 | { 222 | auto in = ethrpc::SendTransaction::make(sn++); 223 | in.params.call_data.from = address; 224 | in.params.call_data.to = contract.address; 225 | in.params.call_data.data = code; 226 | 227 | if (contract.participants.has_value()) 228 | { 229 | in.params.call_data.private_for = contract.participants; 230 | } 231 | 232 | return do_rpc(frontend, cert, in, expect_success); 233 | } 234 | 235 | nlohmann::json TestAccount::contract_call_raw( 236 | const DeployedContract& contract, 237 | const ByteData& code, 238 | bool expect_success /* = true*/) 239 | { 240 | auto in = ethrpc::Call::make(sn++); 241 | in.params.call_data.from = address; 242 | in.params.call_data.to = contract.address; 243 | in.params.call_data.data = code; 244 | 245 | if (contract.participants.has_value()) 246 | { 247 | in.params.call_data.private_for = contract.participants; 248 | } 249 | 250 | return do_rpc(frontend, cert, in, expect_success); 251 | } 252 | -------------------------------------------------------------------------------- /tests/shared.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | #pragma once 4 | 5 | #include "node/encryptor.h" 6 | #include "node/networkstate.h" 7 | #include "rpc_types.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | constexpr jsonrpc::Pack s_packType = jsonrpc::Pack::MsgPack; 14 | using Ethereum = std::shared_ptr; 15 | 16 | template 17 | auto pack(const T& v) 18 | { 19 | return jsonrpc::pack(v, s_packType); 20 | } 21 | 22 | nlohmann::json unpack(const std::vector& data); 23 | 24 | std::vector setup_tables(ccf::Store& tables); 25 | 26 | inline std::vector add_user_cert(ccf::Store& tables) 27 | { 28 | // create a new cert 29 | auto kp = tls::make_key_pair(); 30 | auto ca = kp->self_sign("CN=name"); 31 | auto verifier = tls::make_verifier(ca); 32 | const auto data_first = verifier->raw()->raw.p; 33 | const auto data_last = data_first + verifier->raw()->raw.len; 34 | const auto cert = std::vector(data_first, data_last); 35 | 36 | // add cert as a user 37 | auto certs = tables.get(ccf::Tables::USER_CERTS); 38 | auto values = tables.get(ccf::Tables::VALUES); 39 | 40 | ccf::Store::Tx tx; 41 | 42 | const auto user_id = 43 | ccf::get_next_id(tx.get_view(*values), ccf::ValueIds::NEXT_USER_ID); 44 | 45 | auto user_certs_view = tx.get_view(*certs); 46 | user_certs_view->put(cert, user_id); 47 | if (tx.commit() != kv::CommitSuccess::OK) 48 | { 49 | throw std::runtime_error("user creation tx failed"); 50 | } 51 | 52 | return cert; 53 | } 54 | 55 | nlohmann::json do_rpc( 56 | Ethereum& handler, 57 | const std::vector& cert, 58 | nlohmann::json pc, 59 | bool success = true); 60 | 61 | uint256_t get_result_value(const std::string& s); 62 | uint256_t get_result_value(const evm4ccf::ethrpc::Call::Out& response); 63 | 64 | evm4ccf::ByteData make_deployment_code(const evm4ccf::ByteData& runtime_code); 65 | 66 | void make_service_identity( 67 | ccf::Store& tables, Ethereum& frontend, const ccf::Cert& cert); 68 | 69 | template 70 | eevm::Address deploy_contract(const evm4ccf::ByteData& runtime_code, Ts&&... ts) 71 | { 72 | size_t sn = 0; 73 | auto send_in = evm4ccf::ethrpc::SendTransaction::make(sn++); 74 | send_in.params.call_data.from = 0x01234; 75 | send_in.params.call_data.data = make_deployment_code(runtime_code); 76 | const evm4ccf::ethrpc::SendTransaction::Out send_out = 77 | do_rpc(std::forward(ts)..., send_in); 78 | 79 | auto get_in = evm4ccf::ethrpc::GetTransactionReceipt::make(sn++); 80 | get_in.params.tx_hash = send_out.result; 81 | const evm4ccf::ethrpc::GetTransactionReceipt::Out get_out = 82 | do_rpc(std::forward(ts)..., get_in); 83 | return get_out.result->contract_address.value(); 84 | } 85 | 86 | struct CompiledBytecode 87 | { 88 | evm4ccf::ByteData deploy; 89 | evm4ccf::ByteData runtime; 90 | nlohmann::json hashes; 91 | }; 92 | CompiledBytecode read_bytecode(const std::string& contract_name); 93 | 94 | struct DeployedContract 95 | { 96 | eevm::Address address; 97 | std::optional participants; 98 | 99 | DeployedContract(const eevm::Address& a) : 100 | address(a), 101 | participants(std::nullopt) 102 | {} 103 | 104 | DeployedContract( 105 | const eevm::Address& a, const evm4ccf::ContractParticipants& ps) : 106 | address(a), 107 | participants(ps) 108 | {} 109 | }; 110 | 111 | class TestAccount 112 | { 113 | Ethereum frontend; 114 | jsonrpc::SeqNo sn; 115 | std::unique_ptr privk; 116 | 117 | public: 118 | std::vector cert; 119 | eevm::Address address; 120 | 121 | TestAccount(Ethereum ef, ccf::Store& tables); 122 | 123 | evm4ccf::ByteData get_code(const eevm::Address& contract); 124 | 125 | eevm::Address deploy_contract( 126 | const evm4ccf::ByteData& code, evm4ccf::TxHash* o_deploy_hash = nullptr); 127 | 128 | DeployedContract deploy_private_contract( 129 | const evm4ccf::ByteData& code, 130 | const evm4ccf::ContractParticipants& participants, 131 | evm4ccf::TxHash* o_deploy_hash = nullptr); 132 | 133 | nlohmann::json contract_transact_raw( 134 | const DeployedContract& contract, 135 | const evm4ccf::ByteData& code, 136 | bool expect_success = true); 137 | 138 | evm4ccf::TxHash contract_transact( 139 | const DeployedContract& contract, const evm4ccf::ByteData& code) 140 | { 141 | evm4ccf::ethrpc::SendTransaction::Out out = 142 | contract_transact_raw(contract, code); 143 | return out.result; 144 | } 145 | 146 | nlohmann::json contract_call_raw( 147 | const DeployedContract& contract, 148 | const evm4ccf::ByteData& code, 149 | bool expect_success = true); 150 | 151 | evm4ccf::ByteData contract_call( 152 | const DeployedContract& contract, const evm4ccf::ByteData& code) 153 | { 154 | evm4ccf::ethrpc::Call::Out out = contract_call_raw(contract, code); 155 | return out.result; 156 | } 157 | }; 158 | 159 | class StubNotifier : public ccf::AbstractNotifier 160 | { 161 | void notify(const std::vector& data) override {} 162 | }; 163 | 164 | // Construct ABI-encoded function calls 165 | template 166 | std::string abi_append(const std::string& base, const T& t, Ts&&... ts) 167 | { 168 | const auto hexed = eevm::to_hex_string(t); 169 | const auto abi = fmt::format("{}{:0>64}", base, hexed.substr(2)); 170 | 171 | if constexpr (sizeof...(Ts) == 0) 172 | { 173 | return abi; 174 | } 175 | else 176 | { 177 | return abi_append(abi, std::forward(ts)...); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /tests/tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. 4 | 5 | set -e 6 | 7 | if [ ! -f "env/bin/activate" ] 8 | then 9 | python3.7 -m venv env 10 | fi 11 | 12 | source env/bin/activate 13 | pip install wheel 14 | pip install -U -r ../CCF/tests/requirements.txt 15 | pip install -U -r ../tests/requirements.txt 16 | 17 | ctest "$@" 18 | --------------------------------------------------------------------------------