Giter Club home page Giter Club logo

ink-playground's Introduction

ink! Playground

⚠️ This project has been archived. It is no longer maintained.

ink! Playground

An ink! Playground which provides a Browser based IDE for editing Smart Contracts written in ink!. A live deployment of the App can be found under ink-playground.substrate.io.

Features:

  • Uses the Monaco editor
  • Implements a WebAssembly version of Rust Analyzer for code editing
  • Allows saving and sharing of Smart Contract code
  • Implements a one click compile functionality of ink! Smart Contracts to WASM which is provided by a backend service

Table of Contents

Getting started

On Mac-OS

Before proceeding you need to configure the following environment variables (either on your terminal or better persist them into your .zshrc file):

export PATH="/usr/local/opt/llvm/bin/:$PATH"
export CC=/usr/local/opt/llvm/bin/clang
export AR=/usr/local/opt/llvm/bin/llvm-ar

On every OS

The ink! playground is a fronted app which is developed using TypeScript and React. It is contained in the packages/playground folder.

The repo contains a Rust backend which is implemented with the actix-web framework and which can be found in the 'crates/backend' folder.

The backend serves the frontend app and it also provides the backend services for compilation and Github gists creation(which we use to provide the code sharing functionality).

To clone and build the whole project on your local computer, enter:

  1. git clone https://github.com/paritytech/ink-playground

  2. cd ink-playground

  3. make install

  4. make generate

  5. make build

Then pull and tag the docker images which are used by the backend to compile, test & format Smart Contracts:

  1. make docker-pull-images

Finally, start the backend with:

  1. make backend-run

The last command starts the Rust webserver locally on your computer. You can then access localhost:4000 from your browser to open the locally compiled ink! Playground open.

Detailed usage instructions:

The make file and ink! playground

Important commands from the make file:

  • make install
    Installs all the required TypeScript dependencies from all monorepo packages by using yarn install.
  • make generate
    There are parts of the Frontend which are generated by some rust crates. Namely, these are:
    • rust analyzer
      The WebAssembly version of Rust Analyzer which is run by Frontend through some Web Workers. Generating only the rust analyzer WASM file can be done with make generate-rust-analyzer.
    • change.json
      A serialized version of the CrateGraph and project source dependencies of an ink! Smart Contract. This file is provided to the WASM version of rust analyzer and is generated under /packages/_generated/change. Generating only the change.json file can be done with make generate change-json.
    • bindings
      The actix-web Webserver backend provides some endpoints to the Frontend (/compile, /test, /gist). The crate generate-bindings from the Rust Monorepo automatically creates TypeScript types for the communication with these endpoints and provides them in /packages/_generated/commontypes. Generating only the bindings can be done with make generate bindings.
  • make build
    Builds the complete ink! Playground, involves:
    • frontend the react Frontend which needs to be provided with all three ingredients which are generated by make generate, as well as all npm dependencies which are installed by make install. Can also be build independently by make playground-build.
    • backend the actix-web backend which serves the Frontend and provides endpoint for testing, compilation and sharing of Smart Contract code. Can also be build independently by make backend-build.
  • make backend-run
    Starts a the actix web server backend and serves the release version of the playground frontend from the folder packages/playground/dist. Make sure to run make build first to build all the required Rust and frontend dependencies.
  • make playground-start
    Starts a dev build of the frontend repo of ink! Playground which allows for easy debugging of the Frontend code / UI, involving Rust Analyzer. Note that the compile/gist functionalities will not be available when invoking make playground-start
  • make docker-build
    Then deployment of the ink! Playground app is done in our CI by building and uploading the docker image which we generate from our Dockerfile. Running make docker-build generates this docker image and tags it with ink-playground.
  • make docker-run
    Starts the Docker container of playground under the sysbox runtime. Make sure that you have the sysbox docker runtime installed on your computer. Instructions on how to install it can be found here.
  • make ci
    With make ci, we run the majority of tests which we execute in our Github CI.

crate-extractor

Overview of the (sub-)repos

We can divide this Repo into two main contributions:

  • The folder crates is a monorepo containing the Rust source code, its separate crates serve functionalities like the web server which serves the frontend app, the backend services for Smart Contract compilation and Github Gist generation (code sharing) or the rust analyzer functionalities for the IDE.
  • The folder packages is a TypeScript/React monorepo, containing the Frontend App which is served by the Rust backend.

The contributions of crates

  • backend

    This is the main crate of the web server which serves the frontend app. It is based on the Actix Web framework. It serves the directory of the compiled playground app which is located in the /packages/playground/dist folder after executing make build (which involves the compilation of the production bundle of the Frontend app).

  • change_json

    The IDE of the frontend app contains a WebAssembly version of Rust Analyzer. We need to provide Rust Analyzer the source code and crate graph of the analyzed smart contract. Usually, Rust Analyzer will scan the file system, load the dependency data of a rust project into an object, the change object and will send this object to its db. Since we can not access the file system in browser based wasm, we had to find another approach. For this we (de-)serialize the change object which is encoding the data. This crate contains the methods/traits to (de-)serialize the change object.

  • contract

    This is the sample crate which serves as a blueprint for creating the serialized change object.

  • crate_extractor

    Parses Crates and CrateGraph (the package dependency graph) of a Rust Project into a JSON file for Rust Analyzer(=RA) and provides a library for (de-)serialization of these data structures which can be referenced by a WASM implementation of RA.

    For a detailed description, refer to the corresponding section of ARCHITECTURE.md

    Usage:

    Enter:

    gh clone https://github.com/paritytech/ink-playground

    cd ink-playground

    cargo run -p crate-extractor -- create -i <input> -o <output>

    Where <input> points to the Cargo.toml of the project you which to analyze and <output> denotes the path to the resulting '.json' file. Both are optional parameters and default to /Cargo.toml and ./change.json.

  • generate_bindings

    Utilizes ts-rs to auto-generate TypeScript bindings for the API endpoints that the Actix Web framework of the backend crate provides. The bindings are generated into the /packages/_generated/commontypes folder, where they are consumed by the Frontend App.

  • rust_analyzer_wasm

    A LSP (Language Server Protocol) of Rust Analyzer for the monaco editor which is compiled to WebAssembly and which we execute in the Browser. This crate gets compiled to the /packages/playground/pkg subfolder of the playground package. Its compiled version provides the executable WebAssembly code and the corresponding TypeScript types.

  • sandbox

    Provides the sandbox environment which triggers the compilation of ink! Smart Contracts which is executed by the backend server from the backend crate.

The contributions of packages

  • playground

This is the main Frontend Webapp which provides the ink! Playground with Rust Analyzer and compilation functionalities.

  • ink-editor

This isolates the components providing the monaco editor with integrated Rust Analyzer and the API endpoints for code compilation and Github gist creation into its own package.

  • components

Contains the basic UI building blocks. Makes use of PRIMEREACT UI library.

  • _generated

This is the target directory for auto generated types, files & bindings. It currently contains the change package which receives the change.json file from the crate_extractor crate and the commontypes package which carries the bindings and types which allow the Frontend (specifically the API being contained in ink-editor package) to communicate with the backend service for code compilation and Gist generation.

  • docker_tests

Based on the jest testing framework, this packages executes some basic tests against an already running playground container (serves website, provides compile endpoint).

The Dockerfiles for ink! playground

We have two Dockerfiles for two different docker images in this repo: ink-playground and ink-compiler:

  • ink-compiler
    Is used to compile ink! Smart Contracts in a sandboxed environment. We execute the resulting image in a sandboxed environment without network access. The image is a derived from docker.io/paritytech/ink-ci-linux:production and ships with a pre-compiled ink! Smart Contract.

  • ink-playground
    Is the main ink! Playground Dockerfile which is generated by the ./Dockerfile. It is used to deploy the playground on our server. It is executed by using Nestybox's Sysbox runtime. We require this runtime to compile ink! Smart Contracts through a sandboxed Docker in Docker execution within the executed Docker image by using the ink-compiler Docker image.

    The Image is build in multiple steps by a multi stage build process to improve its image size.

    For a better understanding of the steps which are involved in building this image, refer to the build dependencies section of ARCHITECTURE.md.

ink-playground's People

Contributors

achimcc avatar cmichi avatar dependabot[bot] avatar joshorndorff avatar nmauersberg avatar nuke-web3 avatar radhe-zeeve avatar sergejparity avatar statictype avatar thought2 avatar zukunft-indie-org avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ink-playground's Issues

Add ink! dependencies to Playground

Implement loading of a .json file as part of the FE

This will allow ink! Playground/Rust Analyzer to provide error correction/syntax hightining and auto-completion for ink dependencies/sourcecode.

  • Implement loading it Typescript and passing as string to RA

  • Since the file which contains the ink! Dependencies is approx 50mb large (however, only 5mb gzipped), it would be nice to optimize the way in which it is loaded. Possible approaches:

    • Split into chunks
    • Stream the JSON and batch process it

Implement React Context

Implement React Context.

The different funcitonalities of the app, especially the Terminal/Messaging and the Darkmode require a shared global state solution.

We will use React Context now for simplicity since it shipd with react.

  • Implement React Context together with a good and scalable solution for seperating state/context/types/async actions.
  • Context should make use of useReducer hook and provide dispatch to its consumers.
  • For async action consider the solution which is discussed here: https://kentcdodds.com/blog/how-to-use-react-context-effectively#what-about-async-actions
  • Since the funcitonality related to async actions is currently not implemented (Gists Creation/Sharing, Compile), add a dummy async action
  • Provide tests

Add code formatting to CI

Prettier/rustfmt should run on CI, so we don't accidentally merge differently formatted source files.

Improve CI

Improve GitHub CI:

  • forbid direct push to main
  • can we forbid merge commits by CI?

Define an optimized Color Scheme in Monaco for Tokens delivered by Rust Analyzer

Define an optimized Color Scheme in Monaco for Tokens delivered by Rust Analyzer

Rust analyzer returns a set of Tokens for Syntax Highlightning. Assign individuakl colors to those Tokens which are inspired byb the Color Scheme which Visual Studio Code uses together with its Rust Analyzer Plugin:

  • Collect a list of Tokens returned by Rust Analyzer for ink! Smart Contracts
  • Assign the matching colors to these Tokens
  • Implement the derived Color Scheme, look e.g. at this example for inspiration

Mapping of tag -> token is handled in /packages/playground/src/compnents/Editor/utils/configureLanguage.ts by the function fixTag

See https://code.visualstudio.com/blogs/2017/02/08/syntax-highlighting-optimizations for further details

Upgrade Rust Analyzer

The project currently references Rust Analyzer Version 0.0.72.

We should upgrade to Version 0.0.77.

Will have some small breaking changes which needs to be fixed.

Implement Dark Mode

The Settings Switch from the Header menu should toggle the global dark mode over Context state.

  • Can we use https://tailwindcss.com/docs/dark-mode functionality and trigger change by the Context darkmode boolean?
  • Don't define indivivual colors allo ver the app, define Primary/Secondary/Tertiary Colors for Dark and for Bright in Tailwind Config.
  • Add tests for switchin between dark/bright mode.

Update/(re-)write README.md and Architecture.md

The current README contains to much information about architectural decisions. At the same time, it is not clear what precisely the getting started isntrucitons will lead to (building all packages/crates and starting the webserver).

  • provide a better description of how to use all the provided crates/packages.
  • create a ARCHITECTURE.md file and move the architectural part of the current README there. Add the image depicting the build flow and if neccessary other architectural information. For inspiration, see: https://matklad.github.io/2021/02/06/ARCHITECTURE.md.html
  • Would be gre4at to also have a CONTRIBUTE.md file with a contribution guideline, especially for external contributors, but also to reflect on our own practices.

Backend: Log message, when server has started

Maybe a bit silly, but the obvious did not work:

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let opts: Opts = Opts::parse();
    let port = opts.port;
    let frontend_folder = opts.frontend_folder;
    let host = "127.0.0.1";

    if !Path::new(&frontend_folder).is_dir() {
        panic!("{} is not a valid directory.", frontend_folder);
    }

    print!("before: Backend server running at {}:{}", host, port);

    HttpServer::new(move || {
        App::new()
            .wrap(
                middleware::DefaultHeaders::new()
                    .header("Cross-Origin-Opener-Policy", "same-origin")
                    .header("Cross-Origin-Embedder-Policy", "require-corp"),
            )
            .service(serve_frontend(&frontend_folder))
            .route("/compile", get().to(manual_hello))
    })
    .bind(format!("{}:{}", host, port))?
    .run()
    .await?;

    print!("after: Backend server running at {}:{}", host, port);

    Ok(())
}

Both messages are logged before the process terminates (e.g. user hits Ctrl+C)

Optimize Dockerfile for Smart Contract compilation

Check the currently used Dockerfile:

https://github.com/achimcc/ink-backend/blob/main/compiler/Dockerfile

Used by https://github.com/achimcc/ink-backend/blob/main/src/sandbox.rs to compile ink! Smart Contracts to WASM

Try to opmtimize the build performance. Currently the build process slows down drastically if the genereated Dockerimage is > 1 day old.

(!) Docker commands should be changed to be executed as non-root user for securioty concerns

Is Caching possible in the build process? Keep in mind that concurrent builds should be possible. Check if https://github.com/paritytech/cachepot is useful.

Tasks:

Drop `envy` and `serde` from backend, use `clap`

We found out that clap can parse environment variables as well, so we don't need those extra dependencies.

And the refactoring is a minor task as we already have a working pattern in the create_change CLI.

Implement App Layout

Add a general App Layout. Inspect the Figma Layout for inspiration. Style only in dark mode first.

  • Might make sense to add a Template Component which receives Header, Terminal, Editor as Props (?)
  • The Terminal should be resizable, https://www.primefaces.org/primereact/showcase/#/splitter can be used for this. The current Icon/Button that ships with the Splitter component is to small. Replace it with something more visible, so that users don't miss the resize feature of the Terminal.
  • The header component should be left empty for this issue, it is an individual one.
  • The current Prototype has the animated Status bar. Since this is redundant with the Terminal, we skip the Status Bar for a first iteration. Keep in mind that the Terminal messages should be somehow animated instead...
  • Add tests, at least Snapshot tests for ful page rendering.

Add Monaco Webeditor to Frontend

Add basic Monaco Webeditor (without Rust Analyzer) to the Frontend/Webpack Repo

Explore different options:

Keep in mind that the appearance of Monaco should be customizable (Light/Dark Mode, numbering, display minimap).

Explore different strategies to speed up initial loading of Monaco Webeditor, check https://medium.com/@ollelauribostr/dynamic-imports-speeding-up-the-initial-loading-time-of-webassembly-studio-9f50b975472a for inspiration.

Further inspiration regarding a custom setup and to learn basics about LSP can be found here: https://betterprogramming.pub/create-a-custom-web-editor-using-typescript-react-antlr-and-monaco-editor-part-1-2f710c69c18c
and here:
https://betterprogramming.pub/create-a-custom-web-editor-using-typescript-react-antlr-and-monaco-editor-bcfc7554e446

Evaluate option to share Contract code with ipfs instead of GitHub Gists

In the Prototype, the sharing functionality is provided by creating Github Gists. This works, but it relies on the Server and also on GitHub servers. A more decentralised solution would be great.

  • Explore options to save Contract Code on ipfs instead of in GitHub Gists.
  • Can we do this from within the broswer without requiring a user login?
  • Can we use https://www.textile.io/ for this? This would be the best solution, since it is already used in Contracts Explorer.
  • Or other tools, possibly: https://thegraph.com/en/ ?

Implement Terminal Component

Implement a Terminal component. For inspiration, see: https://github.com/achimcc/ink-playground-frontend/tree/comlink/www/components/Terminal. Feedback from our UX designer

@erler : The Status bar is redundant, would be great to have animated messages as part of the terminal. It is important that they are animated, due to potentially long loading times of the app.

  • Scroll Bar should look better as in the prototype and should be aligned with Monaco Editor scroll bar.
  • Implement a message Format together with a message array in Context. So that messages can be dispatched to the Context. The Message Format should carry an enum for Serverity (Info/Success/Error realted to colors) and fields for Prompt Text (Or an Enum which identifies the message dispatcher) and Message text.
  • Implement a Terminal component which receives messages from Context and renders them.
  • Add a concept for animated messages
  • (!) Add tests for dispatch and appearance of messages

Initiate a new wasm Project

Initiate a new wasm Project
The initial structure for the Playground/UI crate:

  • use wasm-pack for this: https://github.com/rustwasm/wasm-pack
  • add a custom React Webpack project
  • add Implementation of a small sample function
  • add rayon and bindgen-rayon to the rust source, expose init thread pool
  • add wasm-pack-plugin to webpack
  • add comlink and run the function which is expozed by wasm-pack through a Webworker
  • add tests by jest and wasm-pack

Avoid same project names in `crates` and `packages`

We have a hybrid monorepo, containing rust crates and ts packages (currently only one).

Technically it's ok if crates have the same name as ts packages. But I'd suggest to avoid this.

Concretely: one of crates/playground and packages/playground should be renamed.

`make test-playground` fails

Here is the result of the Jest testing:

 FAIL  __tests__/Editor.test.tsx
  ● Test suite failed to run

    src/components/WasmTest/Wasm.tsx:7:44 - error TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', or 'system'.

    7     new Worker(new URL('./wasm.worker.ts', import.meta.url), {
                                                 ~~~~~~~~~~~

 FAIL  __tests__/Welcome.test.tsx
  ● Test suite failed to run

    src/components/WasmTest/Wasm.tsx:7:44 - error TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', or 'system'.

    7     new Worker(new URL('./wasm.worker.ts', import.meta.url), {
                                                 ~~~~~~~~~~~

Test Suites: 2 failed, 2 total
Tests:       0 total
Snapshots:   0 total
Time:        63.12 s, estimated 90 s

Implement Header with Control Popups

Implement Header with Popup Buttons similar to the Prototype

  • Use these provided ink! Logos: ink_logos.zip
  • Take the design from Figma into account
  • The compile/ Share Buttons should be mocked for now.
  • Add tests, also for Opening menus/clicking buttons.

Find ways to improve the initial loading of Change.json

In the prototype, the ink! dependencies which are contained ion the Change.json file are received by calling fetch() to obtain them from the Netlify server after the initial page-load. This can take up to 2 min. Are there ways to improve the loading speed, like:

  • Load them from another server?
  • Split the file into multiple smaller files?
  • Stream the .json file
  • Directly subscribe to stream of JSON data instead of transferring file(-s)?

The Change.jsonm file is currently fetched, however, it is approx. 50 MB big (~5MB gzipped teransfer volume). It is probably not cached for long due to its size? Are there ways to more persistently cache this data?

Another approach whhcihs should be considered to improve the performance of loading and applying the Change.json file, is to fetch the data through Comlink Webworkers and to pass them to another Webworker thread which executes rust analyzer, see e.g.:

https://issueexplorer.com/issue/GoogleChromeLabs/comlink/450

Add a Rust Webframework Crate

Explore different Rust Web Frameworks:

  • Rocket
  • Actix Web
  • Gotham
    Implement one of them which serves the current state of the UI as a https Webserver.

This Crate is inteded to grow into a full backend service which provides options for Smart Contract Compilation and Code Sharing/Gist Creation

Tasks:

Create a buildscript/makefile for the project

Since multiple packages/contributions are involved in the project, we should have a makefile/buildscript which builds all connected components in the required order.

This is the flow of the build process:

Playground_Build_Workflow drawio

Therefore, the makefile should:

  • (1) build crate extractor, which depends on change_json
  • (2) run crate extractor on a ink project to generate the required change.json
  • (3) build the wasm dependencies from the playground crate
  • (4) build the frontend project, which uses the wasm deondencies from (3) and the change.json file from (2)
  • (5) build the backend, which uses the Frontend code from (4)

Create a (de-serializable) data structure 'RustProject' for ink-dependencies/rust projects

Create a (de-serializable) data structure 'ChangeJSON' for ink-dependencies/rust projects

Should allow to convert the Change object into a ChangeJson object which is (de-)serializable and vice-versa

  • First define a JSON structure for the CrateGraph which is the most complex part of Change
  • Add (de-)serializable structures for the remaining parts of Change: roots and files_changed
  • Implement ChangeJson by utilizing the structures from Step 1 and Step 2

CLI Command for .json creation

Add a CLI command for Rust Analyzer or write a small extension of Rust Analyzer

  • the CLI command creates 'RustProject' for a provided Cargo.toml path
  • serializes it and writes it into a .json file
  • optional: add a read option to the CLI tool for consistency checking of the JSON file
  • optional: add a subcommand which allows to split the .JSON file into multiple smaller files

Add Rust Analyzer to Frontend/Monaco Client

Add rust Analyzer to the Monaco Client
Add the Frontend implementation of Rust Analyzer WASM WebWorkers.

Create wasm version of Rust Analyzer (=RA WASM)

Create wasm version of Rust Analyzer

don't Copy & Paste the React/JS code from the Project

This should provide the Rust implementation which compiles to WASM.

Add `ChangeJson` deserialization to RA WASM

Parse the ink dependencies which are received as a JSON file by #1 and load them into RA

  • RA should receive a string as part of its LSP functionalities
  • deserialize the string into a 'RustProject' struct
  • Create a 'Change' object out of the deserialized struct and apply it to RA base_db

Refactor Frontend Repo into a separate folder

In the current PR #14 the playground crate is a hybrid repo containing the rust code together with the WASM/JS bindings as well as the Frontend code. Refactor the structure so that the Frontend part obtains its own separate folder.

Consider:

  • wasm-pack build plugin should still work, that means that editing rust sourcecode should recompile the frontend when executing yarn start
  • testing setup (wasm-pack test folder and jest tests) as well as github CI should still be functional after refactoring

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.