tests: example rust contracts moved from warp-wasm-templates

This commit is contained in:
robal
2023-03-17 17:55:17 +01:00
parent c1d8fb7a52
commit dbe52e82cd
75 changed files with 17993 additions and 1 deletions

6
.gitignore vendored
View File

@@ -2,6 +2,8 @@
node_modules/
crates/warp-contracts/target
crates/pst/contract/definition/bindings/
crates/pst/cache/
# Optional eslint cache
.eslintcache
@@ -28,4 +30,6 @@ yalc.lock
bundles/
.secrets
logs
logs
target

3
crates/pst/.eslintignore Normal file
View File

@@ -0,0 +1,3 @@
node_modules
lib
src/__tests__/integration/data

18
crates/pst/.eslintrc.json Normal file
View File

@@ -0,0 +1,18 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"no-console": 1, // warning
"multiline-ternary": "off",
"no-nested-ternary": "off",
"no-multiple-empty-lines": "off",
"prettier/prettier": 2 // error
}
}

View File

@@ -0,0 +1,3 @@
node_modules
lib
src/__tests__/integration/data

6
crates/pst/.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"semi": true,
"trailingComma": "none",
"singleQuote": true,
"printWidth": 120
}

441
crates/pst/Cargo.lock generated Normal file
View File

@@ -0,0 +1,441 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "async-stream"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e"
dependencies = [
"async-stream-impl",
"futures-core",
]
[[package]]
name = "async-stream-impl"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "bytes"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "dyn-clone"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2"
[[package]]
name = "futures-core"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "itoa"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "once_cell"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "proc-macro2"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "schemars"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn",
]
[[package]]
name = "serde"
version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_derive_internals"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "socket2"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42"
dependencies = [
"autocfg",
"once_cell",
"pin-project-lite",
"socket2",
]
[[package]]
name = "tokio-stream"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-test"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3"
dependencies = [
"async-stream",
"bytes",
"futures-core",
"tokio",
"tokio-stream",
]
[[package]]
name = "unicode-ident"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "warp-contracts"
version = "0.1.2"
dependencies = [
"js-sys",
"serde",
"serde-wasm-bindgen",
"warp-contracts-core",
"warp-contracts-macro",
"wasm-bindgen",
"wasm-bindgen-futures",
]
[[package]]
name = "warp-contracts-core"
version = "0.1.2"
dependencies = [
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "warp-contracts-macro"
version = "0.1.2"
dependencies = [
"quote",
"syn",
"warp-contracts-core",
]
[[package]]
name = "warp_pst"
version = "0.1.0"
dependencies = [
"schemars",
"serde",
"serde_json",
"strum",
"strum_macros",
"tokio-test",
"warp-contracts",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "web-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

2
crates/pst/Cargo.toml Normal file
View File

@@ -0,0 +1,2 @@
[workspace]
members = ["contract/definition"]

178
crates/pst/README.md Normal file
View File

@@ -0,0 +1,178 @@
# 🦀 Warp contracts - Rust template
Following repository is a template for writing SmartWeave contracts in Rust and building them into WASM binaries which can be then processed by Warp SDK.
It contains an example implementation of a PST contract - which you can use as a base for implementing your own contract.
If you are not familiar with the concept of Profit Sharing Tokens, check out a [tutorial](https://academy.warp.cc/tutorials/pst/introduction/intro) for writing your first PST contract in our Warp Academy.
- [Installation](#-installation)
- [Code structure](#-code-structure)
- [Writing contract](#-writing-contract)
- [Accessing JavaScript imports](#accessing-javascript-imports)
- [Foreign contract read](#foreign-contract-read)
- [Foreign contract write](#foreign-contract-write)
- [Build](#-build)
- [Typescript bindings](#typescript-bindings)
- [Tests](#-tests)
- [Deploy](#-deploy)
- [Using SDK](#-using-sdk)
## 📦 Installation
You will need:
- Rust :-) (https://doc.rust-lang.org/cargo/getting-started/installation.html)
- [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) (on Apple's M1s you may need Rosetta `softwareupdate --install-rosetta` for wasm-pack to run)
- [Node.js](https://nodejs.org/en/download/) version 16.5 or above
- [yarn](https://yarnpkg.com/getting-started/install) installed
To install all Node.js dependencies run the following command:
```bash
yarn install
```
## 🔍 Code structure
- [deploy/](deploy) - contains deployment scripts for localhost/testnet/mainnet and contract's initial state definition
- [contract](contract) - contains definitions and implementation for the contract
- [definition/](contract/definition) - contains contract definitions (e.g. actions, errors and state) which define shape of the contract, it also includes tools to generate JSON files from contract definition files
- [action.rs](contract/definition/src/action.rs) - contains enums of the `WriteAction`, `ReadAction` and `ReadResponse`, each of it includes structs with specific definitons (e.g. `Balance` struct), it also contains `ActionResult` and `HandlerResult` enums
- [error.rs](contract/defintion/src/error.rs) - contains the definition of contract's business errors (e.g. "not enough balance")
- [generate_json.rs](contract/definition/src/generate_json.rs) - tool to generate JSON schemas from the definitions
- [state.rs](contract/definition/src/state.rs) - contains the definition of contract's state
- [implementation](contract/implementation) - contains implementation for the contract definitions, it will be compiled to WASM and used when deploying contract to the blockchain
- [pkg/](pkg) - generated by `wasm-pack` during build process - contains compiled wasm binary, js "glue" code, etc.
- [src/](src) - contains the source code of the contract implementing contract definitions
- [actions/](contract/implementation/src/actions) - the main part of the contract - contains `actions` (functions) that can be called to interact
with the contract (and either change its internal state or return a view of the current state).
These functions contain all the business logic of the contract
- [contract_utils/](contract/implementation/src/contract_utils) - contains low-level code responsible for mapping types, storing state,
definition of functions imported from js, etc.
🔥Do Not Edit🔥, unless you really know what you're doing :-)
- [contract.rs](contract/implementation/src/contract.rs) - entry point (from the contract's developer perspective) to the contract.
Contains the `handle` function that calls specific contract's functions based on passed action
- [tests/](tests) - contains integration tests written in Jest
## 🧑‍💻 Writing contract
If you want to edit contract's code and create your own implementation you can do it by following these steps:
1. Edit `init-state.json` by adding the initial state for your contract - [deploy/state/init-state.json](deploy/state/init-state.json)
2. Modify the state definition of the contract - [contract/definition/src/state.rs](contract/definition/src/state.rs)
3. Edit/add actions which user will be able to call while interacting with the contract - [contract/definiton/src/actions](contract/definition/src/actions) and [contract/implementation/src/actions](contract/implementation/src/actions).
We suggest keeping each action in a separate file.
4. Add above action functions to the pattern matching in `handle` function in [contract/implementation/src/contract.rs](contract/implementation/src/contract.rs)
### Accessing JavaScript imports
An example of how to access imports can be found here: [contract/implementation/src/contract.rs](contract/implementation/src/contract.rs)
### Foreign contract read
An example of how to read other contract state can be found here: [contract/implementation/src/actions/foreign_read.rs](contract/implementation/src/actions/foreign_read.rs)
### Foreign contract write
An example of how to call other contract function can be found here: [contract/implementation/src/actions/foreign_write.rs](contract/implementation/src/actions/foreign_write.rs)
Keep in mind that internal contract writes require the flag `internalWrites` to be turned on in the
evaluation options (for both calling and callee contracts). See [tests/contract.spec.ts](tests/contract.spec.ts#L111).
In order to access the calling contract tx id - use `SmartWeave::caller()`.
`SmartWeave::caller()` returns:
1. same value as `Transaction::owner()` - for standard interactions with contract
2. transaction id of the calling contract - in case of internal writes
## 👷 Build
Compile your contract to WASM binary by running following command:
```bash
yarn build
```
## Typescript bindings
Rust contract definitions can be compiled to Typescript:
1. Firstly JSON schemas are generated from Rust contract definitions using [schemars](https://github.com/GREsau/schemars).
2. Then, JSON schemas are compiled to Typescript using [json-schema-to-typescript](https://github.com/bcherny/json-schema-to-typescript).
3. Lastly, a helper class is generated from typescript bindings which allows to easily interact with the contract. Instead of using `writeInteraction` method each time, specific functions can be called within the contract, e.g.:
```ts
async transfer(transfer: Transfer, options?: WriteInteractionOptions): Promise<WriteInteractionResponse | null> {
return await this.contract.writeInteraction<BaseInput & Transfer>({ function: 'transfer', ...transfer }, options);
}
```
Generate JSON:
```bash
yarn gen-json
```
Compile JSON to Typescript:
```bash
yarn gen-ts
```
Gnerate JSON and compile to Typescript:
```bash
yarn gen-bindings
```
Files will be generated in [contract/definition/bindings](contract/definition/bindings).
## 🧪 Tests
Write tests for your contract (we will use Jest library for testing) - you can find a template in the [tests/](tests) folder.
Run tests with
```bash
yarn test
```
## 📜 Deploy
Deploy your contract to one of the networks (mainnet/Warp public testnet/localhost) by running following command (`network`: `mainnet` | `testnet` | `local`)
Please note that in case of local deployment, you need to have `ArLocal` instance running - `npx arlocal`.
```bash
yarn deploy:[network]
```
💡**NOTE**: If you want to deploy your contract locally you need to run Arlocal by typing following command:
```bash
npx arlocal
```
💡**NOTE**: When using mainnet please put your wallet key in [deploy/mainnet/.secrets/wallet-mainnet.json](deploy/mainnet/.secrets/wallet-mainnet.json). `.secrets` folder has been added to `.gitignore` so your key is kept securely.
You can view deploy script code [here](deploy/scripts/deploy.js).
## 🟥 Using SDK
Optionally - you can run one of the scripts which uses Warp SDK to interact with the contract. Using SDKs' methods works exactly the same as in case of a regular JS contract.
💡**NOTE** You will need to have a file with the wallet key and a file with the contract id to run these scripts. If you do not have them please run a [deploy](#-deploy) script.
1. `read` - reads contract state, check out the code in [deploy/scripts/read-contract-state.js](deploy/scripts/read-contract-state.js)
```bash
npm run read:[network]
```
2. `balance` - get balance for a wallet address, check out the code in [deploy/scripts/interact-balance.js](deploy/scripts/interact-balance.js)
```bash
npm run balance:[network]
```
3. `transfer` - transfer specific amount of tokens to the indicated wallet, check out the code in [deploy/scripts/interact-transfer.js](deploy/scripts/interact-transfer.js)

517
crates/pst/contract/Cargo.lock generated Normal file
View File

@@ -0,0 +1,517 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "async-recursion"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-stream"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e"
dependencies = [
"async-stream-impl",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-stream-impl"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-trait"
version = "0.1.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "dyn-clone"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
[[package]]
name = "futures-core"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "proc-macro2"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "schemars"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn",
]
[[package]]
name = "serde"
version = "1.0.156"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.156"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_derive_internals"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
dependencies = [
"autocfg",
"pin-project-lite",
"windows-sys",
]
[[package]]
name = "tokio-stream"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-test"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3"
dependencies = [
"async-stream",
"bytes",
"futures-core",
"tokio",
"tokio-stream",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "warp-contracts"
version = "0.1.2"
dependencies = [
"js-sys",
"serde",
"serde-wasm-bindgen",
"warp-contracts-core",
"warp-contracts-macro",
"wasm-bindgen",
"wasm-bindgen-futures",
]
[[package]]
name = "warp-contracts-core"
version = "0.1.2"
dependencies = [
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "warp-contracts-macro"
version = "0.1.2"
dependencies = [
"quote",
"syn",
"warp-contracts-core",
]
[[package]]
name = "warp_pst"
version = "0.1.0"
dependencies = [
"schemars",
"serde",
"serde_json",
"strum",
"strum_macros",
"tokio-test",
"warp-contracts",
]
[[package]]
name = "warp_pst_implementation"
version = "0.1.0"
dependencies = [
"async-recursion",
"async-trait",
"console_error_panic_hook",
"serde",
"serde-wasm-bindgen",
"tokio-test",
"warp-contracts",
"warp_pst",
"wasm-bindgen",
"wasm-bindgen-futures",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "web-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"

View File

@@ -0,0 +1,2 @@
[workspace]
members = ["definition", "implementation"]

View File

@@ -0,0 +1,15 @@
[package]
name = "warp_pst"
version = "0.1.0"
edition = "2021"
description = "Warp PST template"
license = "MIT"
[dependencies]
warp-contracts = { version = "0.1.2", features = ["debug"], path = "../../../warp-contracts" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio-test = "0.4.2"
schemars = "0.8.10"
strum = "0.24.1"
strum_macros = "0.24.3"

View File

@@ -0,0 +1,160 @@
mod req {
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct Balance {
pub target: String,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct KvPut {
pub key: String,
pub value: String,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct KvGet {
pub key: String,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct Transfer {
pub qty: u64,
pub target: String,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct Evolve {
pub value: String,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct ForeignRead {
pub contract_tx_id: String,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct ForeignView {
pub contract_tx_id: String,
pub target: String,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct ForeignWrite {
pub contract_tx_id: String,
pub qty: u64,
pub target: String,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
#[serde(rename_all = "camelCase", tag = "function")]
pub enum Action {
Balance(Balance),
Transfer(Transfer),
Evolve(Evolve),
ForeignView(ForeignView),
ForeignRead(ForeignRead),
ForeignWrite(ForeignWrite),
KvGet(KvGet),
KvPut(KvPut),
}
}
pub use req::*;
mod res {
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
use crate::{error::PstError, state::PstState};
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct PstBalanceResult {
pub balance: u64,
pub ticker: String,
pub target: String,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct PstKvGetResult {
pub key: String,
pub value: String,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct PstForeignViewResult {
pub balance: u64,
pub ticker: String,
pub target: String,
}
#[derive(JsonSchema, Clone, PartialEq, Debug, Serialize, Deserialize, Hash, Eq, EnumIter)]
#[serde(rename_all = "camelCase", tag = "function")]
pub enum PstViewResponse {
BalanceResult(PstBalanceResult),
KvGetResult(PstKvGetResult),
ForeignViewResult(PstForeignViewResult),
}
pub type PstViewResult =
warp_contracts::handler_result::ViewResult<PstViewResponse, PstError>;
pub type PstWriteResult =
warp_contracts::handler_result::WriteResult<PstState, PstError>;
}
pub use res::*;
mod bindings {
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
use super::*;
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, EnumIter)]
#[serde(rename_all = "camelCase", tag = "function")]
pub enum View {
Balance(Balance),
BalanceResult(PstBalanceResult),
ForeignView(ForeignView),
ForeignViewResult(PstForeignViewResult),
KvGet(KvGet),
KvGetResult(PstKvGetResult),
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, EnumIter)]
#[serde(rename_all = "camelCase", tag = "function")]
pub enum WriteAction {
Transfer(Transfer),
Evolve(Evolve),
ForeignRead(ForeignRead),
ForeignWrite(ForeignWrite),
KvPut(KvPut),
}
}
pub use bindings::*;

View File

@@ -0,0 +1,13 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(JsonSchema, Serialize, Deserialize, Debug)]
#[serde(tag = "kind", content = "data")]
pub enum PstError {
TransferAmountMustBeHigherThanZero,
IDontLikeThisContract,
CallerBalanceNotEnough(u64),
OnlyOwnerCanEvolve,
EvolveNotAllowed,
WalletHasNoBalanceDefined(String),
}

View File

@@ -0,0 +1,41 @@
#[cfg(test)]
mod tests {
use std::path::Path;
use std::{env, fs};
use schemars::JsonSchema;
use crate::action::{View, WriteAction};
use crate::state::ContractState;
const SCHEMAS_DIR: &str = "./bindings/json";
fn generate<T: JsonSchema>(name: &str) -> Result<(), std::io::Error> {
let schema = schemars::schema_for!(T);
let schema_file = Path::new(SCHEMAS_DIR).join(name).with_extension("json");
fs::create_dir_all(SCHEMAS_DIR)?;
fs::write(&schema_file, serde_json::to_string_pretty(&schema)?)?;
Ok(())
}
#[test]
fn generate_json() -> Result<(), std::io::Error> {
let run = if let Ok(run) = env::var("GENERATE_JSON") {
run == "true" || run == "1"
} else {
false
};
if !run {
return Ok(());
}
generate::<ContractState>("ContractState")?;
generate::<WriteAction>("WriteAction")?;
generate::<View>("View")?;
Ok(())
}
}

View File

@@ -0,0 +1,4 @@
pub mod action;
pub mod error;
pub mod generate_json;
pub mod state;

View File

@@ -0,0 +1,24 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use strum_macros::EnumIter;
#[derive(JsonSchema, Serialize, Deserialize, Clone, Default, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PstState {
pub ticker: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub owner: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub evolve: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub can_evolve: Option<bool>,
pub balances: HashMap<String, u64>,
}
#[derive(JsonSchema, Clone, Debug, Serialize, Deserialize, EnumIter)]
#[serde(rename_all = "camelCase", tag = "function")]
pub enum ContractState {
State(PstState),
}

View File

@@ -0,0 +1,19 @@
[package]
name = "warp_pst_implementation"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
console_error_panic_hook = "0.1.7"
warp_pst = { path = "../definition" }
warp-contracts = { version = "0.1.2", features = ["debug"], path = "../../../warp-contracts" }
wasm-bindgen = "=0.2.84"
wasm-bindgen-futures = "=0.4.34"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "=0.5.0"
async-recursion = "1.0.0"
async-trait = "0.1.56"
tokio-test = "0.4.2"

View File

@@ -0,0 +1,20 @@
use super::ViewActionable;
use warp_pst::{
action::{Balance, PstBalanceResult, PstViewResponse::BalanceResult, PstViewResult},
error::PstError::*,
state::PstState,
};
impl ViewActionable for Balance {
fn action(self, _caller: String, state: &PstState) -> PstViewResult {
if !state.balances.contains_key(&self.target) {
return PstViewResult::ContractError(WalletHasNoBalanceDefined(self.target));
}
let balance_response = PstBalanceResult {
balance: *state.balances.get(&self.target).unwrap(),
ticker: state.ticker.clone(),
target: self.target,
};
PstViewResult::Success(BalanceResult(balance_response))
}
}

View File

@@ -0,0 +1,23 @@
use super::WriteActionable;
use warp_contracts::js_imports::Transaction;
use warp_pst::{
action::{Evolve, PstWriteResult},
error::PstError::*,
state::PstState,
};
impl WriteActionable for Evolve {
fn action(self, _caller: String, mut state: PstState) -> PstWriteResult {
match state.can_evolve {
Some(true) => {
if state.owner == Transaction::owner() {
state.evolve = Option::from(self.value);
PstWriteResult::Success(state)
} else {
PstWriteResult::ContractError(OnlyOwnerCanEvolve)
}
}
_ => PstWriteResult::ContractError(EvolveNotAllowed),
}
}
}

View File

@@ -0,0 +1,31 @@
use super::AsyncWriteActionable;
use async_trait::async_trait;
use warp_contracts::{foreign_call::read_foreign_contract_state, js_imports::log};
use warp_pst::{
action::{ForeignRead, PstWriteResult},
error::PstError::*,
state::PstState,
};
#[async_trait(?Send)]
impl AsyncWriteActionable for ForeignRead {
async fn action(self, _caller: String, mut state: PstState) -> PstWriteResult {
if self.contract_tx_id == "bad_contract" {
return PstWriteResult::ContractError(IDontLikeThisContract);
}
let foreign_contract_state: PstState =
match read_foreign_contract_state(&self.contract_tx_id).await {
Ok(s) => s,
Err(e) => return PstWriteResult::RuntimeError(e),
};
// Some dummy logic - just for the sake of the integration test
if foreign_contract_state.ticker == "FOREIGN_PST" {
log("Adding to tokens");
for val in state.balances.values_mut() {
*val += 1000;
}
}
PstWriteResult::Success(state)
}
}

View File

@@ -0,0 +1,43 @@
use super::AsyncViewActionable;
use async_trait::async_trait;
use serde::Serialize;
use warp_contracts::{foreign_call::view_foreign_contract_state, handler_result::ViewResult::*};
use warp_pst::{
action::{
ForeignView, PstBalanceResult, PstForeignViewResult, PstViewResponse::*, PstViewResult,
},
state::PstState,
};
#[derive(Serialize, Debug)]
struct BalanceInput {
function: String,
target: String,
}
#[async_trait(?Send)]
impl AsyncViewActionable for ForeignView {
async fn action(self, _caller: String, _state: &PstState) -> PstViewResult {
let foreign_contract_state = view_foreign_contract_state(
&self.contract_tx_id,
BalanceInput {
target: self.target,
function: "balance".to_string(),
},
)
.await;
match foreign_contract_state {
Success(PstBalanceResult {
balance,
ticker,
target,
}) => Success(ForeignViewResult(PstForeignViewResult {
balance,
ticker,
target,
})),
ContractError(e) => ContractError(e),
RuntimeError(e) => RuntimeError(e),
}
}
}

View File

@@ -0,0 +1,36 @@
use super::AsyncWriteActionable;
use async_trait::async_trait;
use serde::Serialize;
use warp_contracts::{foreign_call::write_foreign_contract, handler_result::WriteResult::*};
use warp_pst::{
action::{ForeignWrite, PstWriteResult},
error::PstError,
state::PstState,
};
#[derive(Serialize)]
struct Input {
function: String,
qty: u64,
target: String,
}
#[async_trait(?Send)]
impl AsyncWriteActionable for ForeignWrite {
async fn action(self, _caller: String, state: PstState) -> PstWriteResult {
match write_foreign_contract::<Input, PstError>(
&self.contract_tx_id,
Input {
function: "transfer".to_string(),
qty: self.qty,
target: self.target,
},
)
.await
{
Success(_) => Success(state),
ContractError(e) => ContractError(e),
RuntimeError(e) => RuntimeError(e),
}
}
}

View File

@@ -0,0 +1,28 @@
use super::AsyncViewActionable;
use async_trait::async_trait;
use warp_contracts::{handler_result::ViewResult::*, kv_operations::kv_get};
use warp_pst::{
action::{KvGet, PstKvGetResult, PstViewResponse, PstViewResult},
state::PstState,
};
#[async_trait(?Send)]
impl AsyncViewActionable for KvGet {
async fn action(self, _caller: String, _state: &PstState) -> PstViewResult {
match kv_get(&self.key).await {
Success(a) => {
PstViewResult::Success(PstViewResponse::KvGetResult(PstKvGetResult {
key: self.key,
value: a,
}))
}
ContractError(_) => {
PstViewResult::Success(PstViewResponse::KvGetResult(PstKvGetResult {
key: self.key,
value: "".to_owned(),
}))
},
RuntimeError(e) => RuntimeError(e),
}
}
}

View File

@@ -0,0 +1,24 @@
use super::AsyncWriteActionable;
use async_trait::async_trait;
use warp_contracts::{
js_imports::{log, Transaction},
kv_operations::kv_put,
};
use warp_pst::{
action::{KvPut, PstWriteResult},
state::PstState,
};
#[async_trait(?Send)]
impl AsyncWriteActionable for KvPut {
async fn action(self, caller: String, state: PstState) -> PstWriteResult {
let owner = Transaction::owner();
log(&format!("caller {caller}"));
log(&format!("Transaction owner {owner}"));
match kv_put(&self.key, &self.value).await {
Err(e) => PstWriteResult::RuntimeError(e),
Ok(_) => PstWriteResult::Success(state),
}
}
}

View File

@@ -0,0 +1,36 @@
use async_trait::async_trait;
use warp_pst::{action::{PstViewResult, PstWriteResult}, state::PstState};
pub mod balance;
pub mod evolve;
pub mod foreign_read;
pub mod foreign_view;
pub mod foreign_write;
pub mod kv_get;
pub mod kv_put;
pub mod transfer;
pub use balance::*;
pub use evolve::*;
pub use foreign_read::*;
pub use foreign_view::*;
pub use foreign_write::*;
pub use transfer::*;
pub trait ViewActionable {
fn action(self, caller: String, state: &PstState) -> PstViewResult;
}
#[async_trait(?Send)]
pub trait AsyncViewActionable {
async fn action(self, caller: String, state: &PstState) -> PstViewResult;
}
pub trait WriteActionable {
fn action(self, caller: String, state: PstState) -> PstWriteResult;
}
#[async_trait(?Send)]
pub trait AsyncWriteActionable {
async fn action(self, caller: String, state: PstState) -> PstWriteResult;
}

View File

@@ -0,0 +1,36 @@
use super::WriteActionable;
use warp_contracts::js_imports::{log, SmartWeave, Transaction};
use warp_pst::{
action::{PstWriteResult, Transfer},
error::PstError::*,
state::PstState,
};
impl WriteActionable for Transfer {
fn action(self, _caller: String, mut state: PstState) -> PstWriteResult {
log(("caller ".to_owned() + &SmartWeave::caller()).as_str());
log(("Transaction owner ".to_owned() + &Transaction::owner()).as_str());
if self.qty == 0 {
return PstWriteResult::ContractError(TransferAmountMustBeHigherThanZero);
}
let caller = Transaction::owner();
let balances = &mut state.balances;
// Checking if caller has enough funds
let caller_balance = *balances.get(&caller).unwrap_or(&0);
if caller_balance < self.qty {
return PstWriteResult::ContractError(CallerBalanceNotEnough(caller_balance));
}
// Update caller balance
balances.insert(caller, caller_balance - self.qty);
// Update target balance
let target_balance = *balances.get(&self.target).unwrap_or(&0);
balances.insert(self.target, target_balance + self.qty);
PstWriteResult::Success(state)
}
}

View File

@@ -0,0 +1,55 @@
use warp_pst::{
action::{Action, PstWriteResult, PstViewResult},
state::PstState,
};
use wasm_bindgen::prelude::wasm_bindgen;
use crate::actions::*;
use warp_contracts::{js_imports::*, warp_contract};
#[warp_contract(write)]
pub async fn handle_write(state: PstState, action: Action) -> PstWriteResult {
console_error_panic_hook::set_once();
let effective_caller = SmartWeave::caller();
//Example of accessing functions imported from js:
log("log from contract");
log(&("Transaction::id()".to_owned() + &Transaction::id()));
log(&("Transaction::owner()".to_owned() + &Transaction::owner()));
log(&("Transaction::target()".to_owned() + &Transaction::target()));
log(&("Block::height()".to_owned() + &Block::height().to_string()));
log(&("Block::indep_hash()".to_owned() + &Block::indep_hash()));
log(&("Block::timestamp()".to_owned() + &Block::timestamp().to_string()));
log(&("Contract::id()".to_owned() + &Contract::id()));
log(&("Contract::owner()".to_owned() + &Contract::owner()));
log(&("SmartWeave::caller()".to_owned() + &SmartWeave::caller()));
// for vrf-compatible interactions
log(&("Vrf::value()".to_owned() + &Vrf::value()));
log(&("Vrf::randomInt(7)".to_owned() + &Vrf::randomInt(7).to_string()));
match action {
Action::Transfer(action) => action.action(effective_caller, state),
Action::Evolve(action) => action.action(effective_caller, state),
Action::ForeignRead(action) => action.action(effective_caller, state).await,
Action::ForeignWrite(action) => action.action(effective_caller, state).await,
Action::KvPut(action) => action.action(effective_caller, state).await,
_ => PstWriteResult::RuntimeError("invalid method for write".to_owned()),
}
}
#[warp_contract(view)]
pub async fn handle_view(state: &PstState, action: Action) -> PstViewResult {
console_error_panic_hook::set_once();
let effective_caller = SmartWeave::caller();
match action {
Action::Balance(action) => action.action(effective_caller, state),
Action::ForeignView(action) => action.action(effective_caller, state).await,
Action::KvGet(action) => action.action(effective_caller, state).await,
_ => PstViewResult::RuntimeError("invalid method for view".to_owned()),
}
}

View File

@@ -0,0 +1,2 @@
mod actions;
mod contract;

View File

@@ -0,0 +1,9 @@
const { deploy } = require('../scripts/deploy');
deploy(
'localhost',
1984,
'http',
'local',
'deploy/local/wallet_local.json'
).finally();

View File

@@ -0,0 +1,9 @@
const { interactBalance } = require('../scripts/interact-balance');
interactBalance(
'localhost',
1984,
'http',
'local',
'deploy/local/wallet_local.json'
).finally();

View File

@@ -0,0 +1,9 @@
const { interactTransfer } = require('../scripts/interact-transfer');
interactTransfer(
'localhost',
1984,
'http',
'local',
'deploy/local/wallet_local.json'
).finally();

View File

@@ -0,0 +1,9 @@
const { readContractState } = require('../scripts/read-contract-state');
readContractState(
'localhost',
1984,
'http',
'local',
'deploy/local/wallet_local.json'
).finally();

View File

@@ -0,0 +1 @@
tom87dg3RgkUGTfOwIO-eNb5WCvoGkFCS44B7koVW_I

View File

@@ -0,0 +1,9 @@
const { deploy } = require('../scripts/deploy');
deploy(
'arweave.net',
443,
'https',
'mainnet',
'deploy/mainnet/wallet_mainnet.json'
).finally();

View File

@@ -0,0 +1,9 @@
const { interactBalance } = require('../scripts/interact-balance');
interactBalance(
'arweave.net',
443,
'https',
'mainnet',
'deploy/mainnet/wallet_mainnet.json'
).finally();

View File

@@ -0,0 +1,9 @@
const { interactTransfer } = require('../scripts/interact-transfer');
interactTransfer(
'arweave.net',
443,
'https',
'mainnet',
'deploy/mainnet/wallet_mainnet.json'
).finally();

View File

@@ -0,0 +1,9 @@
const { readContractState } = require('../scripts/read-contract-state');
readContractState(
'arweave.net',
443,
'https',
'mainnet',
'deploy/mainnet/wallet_mainnet.json'
).finally();

View File

@@ -0,0 +1,56 @@
const fs = require('fs');
const path = require('path');
const { WarpFactory, defaultCacheOptions } = require('warp-contracts');
const { mineBlock } = require('./utils/mine-block');
const { loadWallet, walletAddress } = require('./utils/load-wallet');
const { connectArweave } = require('./utils/connect-arweave');
module.exports.deploy = async function (host, port, protocol, target, walletJwk) {
const arweave = connectArweave(host, port, protocol);
const warp = module.exports.getWarpInstance(port, target);
const wallet = await loadWallet(arweave, walletJwk, target);
const walletAddr = await walletAddress(arweave, wallet);
const contractSrc = fs.readFileSync(path.join(__dirname, '../../contract/implementation/pkg/rust-contract_bg.wasm'));
const stateFromFile = JSON.parse(fs.readFileSync(path.join(__dirname, '../state/init-state.json'), 'utf-8'));
const initialState = {
...stateFromFile,
...{
owner: walletAddr,
balances: {
...stateFromFile.balances,
[walletAddr]: 10000000,
},
},
};
const {contractTxId} = await warp.createContract.deploy(
{
wallet,
initState: JSON.stringify(initialState),
src: contractSrc,
wasmSrcCodeDir: path.join(__dirname, '../../contract/implementation/src'),
wasmGlueCode: path.join(__dirname, '../../contract/implementation/pkg/rust-contract.js'),
}
);
fs.writeFileSync(path.join(__dirname, `../${target}/contract-tx-id.txt`), contractTxId);
if (target == 'testnet' || target == 'local') {
await mineBlock(arweave);
}
if (target == 'testnet') {
console.log(`Check contract at https://sonar.warp.cc/#/app/contract/${contractTxId}?network=testnet`);
} else {
console.log('Contract tx id', contractTxId);
}
};
module.exports.getWarpInstance = function (port, target) {
if (target == 'local') {
return WarpFactory.forLocal(port);
} else if (target == 'testnet') {
return WarpFactory.forTestnet();
} else {
return WarpFactory.forMainnet({ ...defaultCacheOptions, inMemory: true });
}
}

View File

@@ -0,0 +1,23 @@
const { loadWallet } = require('./utils/load-wallet');
const { connectArweave } = require('./utils/connect-arweave');
const { connectPstContract } = require('./utils/connect-pst-contract');
const { contractTxId } = require('./utils/contract-tx-id');
module.exports.interactBalance = async function (
host,
port,
protocol,
target,
walletJwk
) {
const arweave = connectArweave(host, port, protocol);
const wallet = await loadWallet(arweave, walletJwk, target, true);
const walletAddress = await arweave.wallets.jwkToAddress(wallet);
const txId = contractTxId(target);
const pst = await connectPstContract(arweave, wallet, txId, target);
const balance = await pst.currentBalance(walletAddress);
console.log(balance);
};

View File

@@ -0,0 +1,29 @@
const { loadWallet } = require('./utils/load-wallet');
const { connectArweave } = require('./utils/connect-arweave');
const { connectPstContract } = require('./utils/connect-pst-contract');
const { contractTxId } = require('./utils/contract-tx-id');
const { mineBlock } = require('./utils/mine-block');
module.exports.interactTransfer = async function (host, port, protocol, target, walletJwk) {
const arweave = connectArweave(host, port, protocol);
const wallet = await loadWallet(arweave, walletJwk, target, true);
const txId = contractTxId(target);
const pst = await connectPstContract(arweave, wallet, txId, target);
const transferId = await pst.transfer({
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 555,
});
await mineBlock(arweave);
const state = await pst.currentState();
console.log('Updated state:', state);
console.log('Contract tx id', txId);
if (target == 'testnet') {
console.log(`Check transfer interaction at https://sonar.warp.cc/#/app/interaction/${transferId}?network=testnet`);
} else {
console.log('Transfer tx id', transferId);
}
};

View File

@@ -0,0 +1,22 @@
const { loadWallet } = require('./utils/load-wallet');
const { connectArweave } = require('./utils/connect-arweave');
const { connectContract } = require('./utils/connect-contract');
const { contractTxId } = require(`./utils/contract-tx-id`);
module.exports.readContractState = async function (
host,
port,
protocol,
target,
walletJwk
) {
const arweave = connectArweave(host, port, protocol);
const wallet = await loadWallet(arweave, walletJwk, target, true);
const txId = contractTxId(target);
const contract = await connectContract(arweave, wallet, txId, target);
const { cachedValue } = await contract.readState();
console.log('Current state:', cachedValue.state);
console.log('Contract tx id', txId);
};

View File

@@ -0,0 +1,4 @@
module.exports.addFunds = async function (arweave, wallet) {
const walletAddress = await arweave.wallets.getAddress(wallet);
await arweave.api.get(`/mint/${walletAddress}/1000000000000000`);
};

View File

@@ -0,0 +1,9 @@
const Arweave = require('arweave');
module.exports.connectArweave = function (host, port, protocol) {
return Arweave.init({
host: host,
port: port,
protocol: protocol,
});
};

View File

@@ -0,0 +1,15 @@
const { getWarpInstance } = require("../deploy");
module.exports.connectContract = async function (
arweave,
wallet,
contractTxId,
target
) {
console.log('Target:', target);
const warp = getWarpInstance(arweave.api.config.port, target);
return warp
.contract(contractTxId)
.connect(wallet);
};

View File

@@ -0,0 +1,14 @@
const { getWarpInstance } = require("../deploy");
module.exports.connectPstContract = async function (
arweave,
wallet,
contractTxId,
target
) {
const warp = getWarpInstance(arweave.api.config.port, target);
return warp
.pst(contractTxId)
.connect(wallet);
};

View File

@@ -0,0 +1,19 @@
const fs = require('fs');
const path = require('path');
module.exports.contractTxId = function (target) {
let txId;
try {
txId = fs
.readFileSync(
path.join(__dirname, `../../../deploy/${target}/contract-tx-id.txt`),
'utf-8'
)
.trim();
} catch (e) {
throw new Error(
'Contract tx id file not found! Please run deploy script first.'
);
}
return txId;
};

View File

@@ -0,0 +1,10 @@
const fs = require('fs');
const path = require('path');
module.exports.generateWallet = async function (arweave, target) {
const wallet = await arweave.wallets.generate();
fs.writeFileSync(
path.join(__dirname, `../../${target}/wallet_${target}.json`),
JSON.stringify(wallet)
);
};

View File

@@ -0,0 +1,33 @@
const fs = require('fs');
const { addFunds } = require('./addFunds');
const { generateWallet } = require('./create-testnet-wallet');
const path = require('path');
module.exports.loadWallet = async function (
arweave,
walletJwk,
target,
generated
) {
let wallet;
if (!generated) {
await generateWallet(arweave, target);
}
try {
wallet = JSON.parse(fs.readFileSync(path.join(walletJwk), 'utf-8'));
} catch (e) {
throw new Error('Wallet file not found! Please run deploy script first.');
}
if (target == 'testnet' || target == 'local') {
await addFunds(arweave, wallet);
}
return wallet;
};
module.exports.walletAddress = async function (arweave, wallet) {
return arweave.wallets.getAddress(wallet);
};

View File

@@ -0,0 +1,3 @@
module.exports.mineBlock = async function (arweave) {
await arweave.api.get('mine');
};

View File

@@ -0,0 +1,7 @@
{
"ticker": "PST TMPL RUST",
"name": "PST Template Rust",
"balances": {},
"canEvolve": true,
"evolve": null
}

View File

@@ -0,0 +1 @@
Y8sad_he0w0ryzxKvS7PGEXQbxBl142KX5OTpiPDYV0

View File

@@ -0,0 +1,9 @@
const { deploy } = require('../scripts/deploy');
deploy(
'arweave.net',
443,
'https',
'testnet',
'deploy/testnet/wallet_testnet.json'
).finally();

View File

@@ -0,0 +1,9 @@
const { interactBalance } = require('../scripts/interact-balance');
interactBalance(
'testnet.redstone.tools',
443,
'https',
'testnet',
'deploy/testnet/wallet_testnet.json'
).finally();

View File

@@ -0,0 +1,9 @@
const { interactTransfer } = require('../scripts/interact-transfer');
interactTransfer(
'testnet.redstone.tools',
443,
'https',
'testnet',
'deploy/testnet/wallet_testnet.json'
).finally();

View File

@@ -0,0 +1,9 @@
const { readContractState } = require('../scripts/read-contract-state');
readContractState(
'testnet.redstone.tools',
443,
'https',
'testnet',
'deploy/testnet/wallet_testnet.json'
).finally();

View File

@@ -0,0 +1 @@
{"kty":"RSA","n":"zvtUbqKK76RcA1nKTmOjcZgkqLEOw40mLEmWA_Q_TgBYN4NJcZHPfWehS2Vlbalhf6ZiNu_J0Q-zk3tnk12Fkt-UDpje0uxkkigihwxOGmpB2gi6yTRpQfFg4ccG3QY9nx-R2hEOJaY056dXm9UY4gFVI_dYvqZbPt0_N8FmUgNVq9OgtkUVY07PAZA55kiqh2POWycsbv3FPgPt1D7QDj7jDuqRKBfMdHPqZF9qBiEo5kIyAPm8QCbQ0DY-pIjEMY0ENL4b2yTGRQNz-WH0wIAddGFPo9oGma8RdU5Uk82IJWpXxFF2Z4BOqVQnBBnIxXZNJpAN2WQ6xmGDtwRVBnV0l4nfzzpELEOZ98nlyY1YMBqBPqjnKP2_9M2FjdzsV8pnX5kUQE7_SjH0yAKEhRtYXcsvMF7Hy61rAUjD1RgIxqx-aJeJHGugc9I5IpobocX5dzdoRujSOIg_jTT7LzzP6BE2CK71crq-5cYicGje-DkwBoeRxJaRIZbAkp0AOsg1yQnURm3oGZaRw3qoqmcFp1eku5_HVORZJBvG5u2KSxa8giO6sNC0RN1NyD-RsnNLZ82eedpO8sn4rV_9LlTDqFcHgCuoOjt5A8GDYdJN5v6RljqHAcUWIwPC_rC9b4y80WO1vmND1P6j2uUj2wMKKQbOHVphyq2IZTS3skM","e":"AQAB","d":"ehG50uHj5L9fUM6UxLR4wVbEUATUOzo0iCwB4GUdLKiBXoP8PZGL-F60vp2XOxyJFtqO0-2JbdW_x7wg8sVWMK_RRuabi3PFQHmRG2qDaYq_OZKqbl2NsklkJOPXRWUX1I-u2hfy6vth-jk0LwB3g1rb1rFa0l2UNLgRP5W8_aNf1E2kW7rUiVQCO97OduTmRaD0I8gvqWna-N8iVWmSFMS77qEiaK3Yc9mlI_stsV2_HEVv7ila_86kmhdTzH-ojbyn_V8dWJoQoMUPkcVWrm0gcfGuIWlFOUAJoNBIiz7NC5vzFSIiHZqLScwKkp2OTf6eM58Du_EjxLngD4Drjyr-ev_ziEeWVRVd-HgWzhhBplOuFyW7jViKLuJ87mKmArbuA1TpZvpjuraAMi9DNE_2NRuJJn0cYtQLMeDQhZo7TiYVs55jGjEvel2iPLcMAmL_ac6zvLlX2o5XP2MdYv1nfYGyhBz9XO-6toolVsNtXkNPayEDBeU_bPn2igRrjQuGQuuvpbGQMtcNUVgKtUXzzwkJZxji6wt8GVF2D74COPYRFhBYO3TlBNpr_aVI537sJx7P38BPDqH4Tc0MpV72mINqMgJ56mFsepT75UzBAC8BDw5AKeq1rqIE4XoVFTuEXURmLwdDJJhc4Dl_Syc3h0IRiL-bnEZA7usvddE","p":"6_z2eBPZAhMNJ5edjMYW9Zo-zh0y-BCevTQn-4jhKE7z07nNAbofP_PHtEMZYZifSAmkN8QG6TQnyrn-txWfUTGBfE6CwE-GO-AVAJF_OneBZK-9X4W8hNrbRTsGGXRSfF35cuOmMwmqUkqxq03BTnK5pT0PzIHFv2OEm0d5ukq78eC3cd54f1M9QZ9ZUBtOAdvXB0A-EIQBw24m5KtLctJfGB9rGxRyrjyDrvFLSvgUn-G2kjcRnWn_l2mzTPjSct7Niyf1QXjBA11HwT7T98f13-iUY_U29Klk_0DOyL3ynVUHu-SdcPz5JGszetxx9S_gBKAK-FiIWp8sHKsvmQ","q":"4Iir0P8qYme7wRHmDRiOcSSDz9XjrGwYZU8WYbIQK-ff4XOwcXmJs-wq0N1HfjZhYiMaWFRser5oXyT8Xpkw-6gCjfSHIezZ3m6OCUfSeHQ_94OYfLr_CMCA6gOb4fU7x1Y4cqxzWbb7V1W24Kv4gUa9A30hjfnmh-1pT5EMJiu-_1KECgmSmuH0PZQg3OtWfvldJCbx-FltaE9wxQElRzcfulOLDXS7_AksJUnNmY7jWj9IVoG-nmKRvAAjgf0agXOE26icuHiM1dn9oQVAri2AGTbTkmTHWCdSBoZ4G53qwo36wZQvKD4u4TMAFh5ydmlOomn7Px86xi0ciH3KOw","dp":"KC9zaQ84LPpBizRuR8KTtk8F0uN2AngSD_YJuPOeI9cN_kfteRXQrHs-zpt-fvgWZ5X8uOJQqvWOsR7rGRI9hv3_JsPX0Be6rAeEjAw6tiITjqm-fb2wVI9QN4HNkBgW08bM65uIebhzoH_HsXwUJt-ybUjwn8qZefXgZvDM8cQ4LQAvPNy9eDEchUg1VUbRCc_91eZCq11PT5A6X0YsGuln-BuhiYL55GG5qti8EZAdMvykslFeVofuVkJRnhHhBF6ccc3kHboKZCBGT8n7Hn6WiAJ7AmqHaTJPWIgYrmZqIhCQuJY9mTf61RDMO2e-oBn-88qtE40_6u6f_GHKQQ","dq":"eZIGVVaTAEziH5stUBHnreLza2iHqSet3cyAdc0PbHZThaI5-nav1Dcex4_H0dJnz9bpwMGVKrBFmp5P9nhDST6ig3HOaNPw8roxNV0p3AY1TDJ4MbCdvYNdSVdC44kAIOHSFmTA3-ZvF85VAjiaiIRgeq8Zp-GSrC4jQu1qApDVDTEERNsCAdIth9nYIUVaw80IXTao9KqWzk-U41XHdVOnXh81vsdhsQjWWiono-j1uDtjU1NUfjUoav44O31rCIQffz8_-7Fpr7Aj3zcU2jnQjdZdn3npZRWFF_tetLVAEq8FAiLVlZh1kYEnntJFb099P_raCkdCb2KlfFF_WQ","qi":"sxxizN6dhWM8Ww2Vr7Ti-vDV0YqYGdAN_Fy8gAaLtINbhk2YrzBO-q3M_HAXendE8SHFNM2f9xAnpzTJND9aww27MkSmaslY65VVjoLkoh2YONmaePKV8T0MG2ffEl1lnhvS_nq63aJWNBJm7U_wizv0yYbYTi0QI0R7wJiXaWwlA4qFPsBwbUYgzSnxU2_cesjesShbQ-shVkKdscN6QD4feEvI9io28ZDqrpHRc7TrKIYK_RKSj_8DpI1qAMlZ4VUHF-nEAEFeZ__Z4rC3rL0CvgpaCwmSKw8eHidfZCIpnOV7nFm_8_tWVphOdaodRXVXfEfOY2_8qv1UYgEUSQ"}

16
crates/pst/jest.config.js Normal file
View File

@@ -0,0 +1,16 @@
module.exports = {
// Automatically clear mock calls and instances between every test
clearMocks: true,
moduleFileExtensions: ['ts', 'js'],
testPathIgnorePatterns: ['/tests\/data/'],
testEnvironment: 'node',
transformIgnorePatterns: ['<rootDir>/node_modules/(?!@assemblyscript/.*)'],
transform: {
'^.+\\.(ts|js)$': 'ts-jest',
},
};

63
crates/pst/package.json Normal file
View File

@@ -0,0 +1,63 @@
{
"name": "warp-rust-template",
"version": "0.0.1",
"description": "Warp WASM Rust contract template",
"main": "index.js",
"author": "Redstone Team <dev@redstone.finance>",
"license": "MIT",
"scripts": {
"build": "wasm-pack build --target nodejs --release --out-name rust-contract contract/implementation",
"build:dev": "wasm-pack build --target nodejs --dev --out-name rust-contract contract/implementation",
"gen-bindings": "yarn gen-json && yarn gen-ts",
"gen-json": "GENERATE_JSON=1 cargo test generate_json -- --nocapture && yarn format:json-bindings",
"gen-ts": "ts-node --transpileOnly ./scripts/generate-ts.ts && yarn format:ts-bindings",
"test": "jest ./tests",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"format:json-bindings": "eslint 'contract/definition/bindings/json/*.json' --fix",
"format:ts-bindings": "eslint 'contract/definition/bindings/ts/*.ts' --fix",
"prettier:format": "prettier --config .prettierrc '**/**/*.ts' --write",
"deploy:local": "yarn build && node deploy/local/deploy-local.js",
"deploy:testnet": "yarn build && node deploy/testnet/deploy-test.js",
"deploy:mainnet": "yarn build && node deploy/mainnet/deploy-mainnet.js",
"read:local": "node deploy/local/read-contract-state-local.js",
"read:testnet": "node deploy/testnet/read-contract-state-testnet.js",
"read:mainnet": "node deploy/mainnet/read-contract-state-mainnet.js",
"transfer:local": "node deploy/local/interact-transfer-local.js",
"transfer:testnet": "node deploy/testnet/interact-transfer-testnet.js",
"transfer:mainnet": "node deploy/mainnet/interact-transfer-mainnet.js",
"balance:local": "node deploy/local/interact-balance-local.js",
"balance:testnet": "node deploy/testnet/interact-balance-testnet.js",
"balance:mainnet": "node deploy/mainnet/interact-balance-mainnet.js"
},
"engines": {
"node": ">=16.5"
},
"dependencies": {
"arlocal": "^1.1.59",
"arweave": "1.12.4",
"json-schema-to-typescript": "^11.0.1",
"typescript": "^4.6.2",
"warp-contracts": "1.2.56",
"warp-contracts-plugin-deploy": "1.0"
},
"resolutions": {
"arweave": "1.12.4"
},
"overrides": {
"arweave": "1.12.4"
},
"devDependencies": {
"@types/jest": "^27.4.1",
"@types/node": "^18.6.3",
"@typescript-eslint/eslint-plugin": "^5.40.0",
"@typescript-eslint/parser": "^5.40.0",
"eslint": "^8.25.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.1",
"jest": "^28.1.3",
"prettier": "^2.3.2",
"ts-jest": "^28.0.7",
"ts-node": "^10.9.1"
}
}

View File

@@ -0,0 +1,43 @@
// NOTE: It is currently required to use a script file in order to run json2ts instead of using its
// CLI because the CLI interprets `--additionalProperties false` as `false` being a string.
import { parse, join } from 'node:path';
import { readdirSync, writeFileSync, mkdirSync, readFileSync } from 'node:fs';
import { compileFromFile } from 'json-schema-to-typescript';
import { writeImplementationFile } from './generation-utils';
const BINDINGS_ROOT = './contract/definition/bindings';
const BINDINGS_JSON = join(BINDINGS_ROOT, 'json');
const BINDINGS_TS = join(BINDINGS_ROOT, 'ts');
mkdirSync(BINDINGS_TS, { recursive: true });
const getFile = (name: string) => {
const filePath = join(BINDINGS_JSON, name);
return JSON.parse(readFileSync(filePath, 'utf-8'));
};
for (const fileName of readdirSync(BINDINGS_JSON)) {
const jsonPath = join(BINDINGS_JSON, fileName);
const tsPath = join(BINDINGS_TS, parse(fileName).name + '.ts');
const file = getFile(fileName);
delete file['$schema'];
file.oneOf.forEach((f) => {
f['$schema'] = 'http://json-schema.org/draft-07/schema#';
f.title = f.properties.function.enum[0];
delete f.properties.function;
});
writeFileSync(join(BINDINGS_JSON, fileName), JSON.stringify(file));
compileFromFile(jsonPath, {
additionalProperties: false
}).then((tsContent) => writeFileSync(tsPath, tsContent));
}
writeImplementationFile(BINDINGS_TS, [
getFile('View.json'),
getFile('WriteAction.json'),
getFile('ContractState.json')
// eslint-disable-next-line
]).catch((e) => console.log(e));

View File

@@ -0,0 +1,114 @@
import { writeFileSync } from 'node:fs';
import { join } from 'node:path';
import path from 'path';
// eslint-disable-next-line
export const writeImplementationFile = async (bindings: any, actions: any[]) => {
let resImpl = ``;
const actionsView = getActionsName(actions[0]);
const actionsWrite = getActionsName(actions[1]);
resImpl = `
/**
* This file was automatically generated. Do not modify it, if you encounter any problems \n- please raise an issue: https://github.com/warp-contracts/warp-wasm-templates/issues.
*/\n\n
import { WriteInteractionOptions, WriteInteractionResponse, Contract, Warp, ArWallet, ContractError, EvaluationOptions } from 'warp-contracts';\nimport { ${actionsView} } from './View';\nimport { ${actionsWrite} } from './WriteAction';\nimport { State } from './ContractState';\n\n`;
resImpl += `
export interface BaseInput {
function: string;
}
export class ${makeFirstCharUpper(implName)}Contract {
readonly contract: Contract<State>;
constructor(contractId: string, warp: Warp) {
this.contract = warp.contract<State>(contractId);
}
connect(wallet: ArWallet) {
this.contract.connect(wallet);
return this;
}
setEvaluationOptions(evaluationOptions: Partial<EvaluationOptions>) {
this.contract.setEvaluationOptions(evaluationOptions);
return this;
}
async currentState(): Promise<State> {
const { cachedValue } = await this.contract.readState();
return cachedValue.state;
}
\n`;
for (const readObj of actions[0].oneOf) {
resImpl += generateInteractions(readObj, true);
}
for (const writeObj of actions[1].oneOf) {
resImpl += generateInteractions(writeObj, false);
}
resImpl += `}`;
writeFileSync(join(bindings, `${makeFirstCharUpper(implName)}Contract.ts`), resImpl);
};
const generateInteractions = (functionObj, read: boolean) => {
const interactionName = functionObj.title;
const interactionNameUpper = makeFirstCharUpper(interactionName);
if (read) {
if (functionObj.title.includes('Result')) {
return '';
} else {
return `async ${interactionName}(${interactionName}: ${interactionNameUpper}): Promise<${interactionNameUpper}Result> {
const interactionResult = await this.contract.viewState<BaseInput & ${makeFirstCharUpper(
interactionNameUpper
)}, ${interactionNameUpper}Result>({ function: '${interactionName}', ...${interactionName} });
if (interactionResult.type == 'error') {
throw new ContractError(interactionResult.error);
} else if (interactionResult.type == 'exception') {
throw Error(interactionResult.errorMessage);
}
return interactionResult.result;
}\n\n`;
}
} else {
return `async ${interactionName}(
${interactionName}: ${interactionNameUpper},
options?: WriteInteractionOptions
): Promise<WriteInteractionResponse | null> {
return await this.contract.writeInteraction<BaseInput & ${interactionNameUpper}>(
{ function: '${interactionName}', ...${interactionName} },
options
);
}\n\n`;
}
};
export const interfaceString = (interfaceName: string, properties: string) => {
return `export interface ${interfaceName}{ ${properties}}\n`;
};
// eslint-disable-next-line
const getFunctionNames = (list: any[]) => {
const functionNames = [];
for (const typeObj of list) {
const functionName = typeObj.title;
functionName != 'function' && functionNames.push(functionName.charAt(0).toUpperCase() + functionName.slice(1));
}
return functionNames;
};
const getActionsName = (action) => {
let actionsName = ``;
let actionsFunctions = [];
actionsFunctions = [...actionsFunctions, ...getFunctionNames(action.oneOf || action.anyOf)];
actionsFunctions.forEach((a) => {
actionsFunctions.indexOf(a) == actionsFunctions.length - 1 ? (actionsName += a) : (actionsName += `${a}, `);
});
return actionsName;
};
const implName = path.basename(process.cwd());
const makeFirstCharUpper = (s: string) => {
return s.charAt(0).toUpperCase() + s.slice(1);
};

View File

@@ -0,0 +1,377 @@
import fs from 'fs';
import ArLocal from 'arlocal';
import Arweave from 'arweave';
import { JWKInterface } from 'arweave/node/lib/wallet';
import {
InteractionResult,
LoggerFactory,
PstState,
Warp,
SmartWeaveTags,
WarpFactory,
TagsParser,
ArweaveWrapper,
WasmSrc
} from 'warp-contracts';
import { DeployPlugin } from 'warp-contracts-plugin-deploy';
import path from 'path';
import { PstContract } from '../contract/definition/bindings/ts/PstContract';
import { State } from '../contract/definition/bindings/ts/ContractState';
jest.setTimeout(30000);
describe('Testing the Rust WASM Profit Sharing Token', () => {
let contractSrc: Buffer;
let contractGlueCodeFile: string;
let wallet: JWKInterface;
let walletAddress: string;
let initialState: State;
let arweave: Arweave;
let arlocal: ArLocal;
let warp: Warp;
let pst: PstContract;
let pst2: PstContract;
let pst3: PstContract;
let contractTxId: string;
let properForeignContractTxId: string;
let wrongForeignContractTxId: string;
let arweaveWrapper: ArweaveWrapper;
let tagsParser: TagsParser;
beforeAll(async () => {
// note: each tests suit (i.e. file with tests that Jest is running concurrently
// with another files has to have ArLocal set to a different port!)
arlocal = new ArLocal(1820, false);
await arlocal.start();
tagsParser = new TagsParser();
LoggerFactory.INST.logLevel('error');
LoggerFactory.INST.logLevel('debug', 'WASM:Rust');
//LoggerFactory.INST.logLevel('debug', 'WasmContractHandlerApi');
warp = WarpFactory.forLocal(1820).use(new DeployPlugin());
({ arweave } = warp);
arweaveWrapper = new ArweaveWrapper(arweave);
({ jwk: wallet, address: walletAddress } = await warp.generateWallet());
contractSrc = fs.readFileSync(path.join(__dirname, '../contract/implementation/pkg/rust-contract_bg.wasm'));
const contractSrcCodeDir: string = path.join(__dirname, '../contract/implementation/src');
contractGlueCodeFile = path.join(__dirname, '../contract/implementation/pkg/rust-contract.js');
const stateFromFile: PstState = JSON.parse(fs.readFileSync(path.join(__dirname, './data/token-pst.json'), 'utf8'));
initialState = {
...stateFromFile,
...{
owner: walletAddress,
balances: {
...stateFromFile.balances,
[walletAddress]: 555669
}
}
};
// deploying contract using the new SDK.
({ contractTxId } = await warp.deploy({
wallet,
initState: JSON.stringify(initialState),
src: contractSrc,
wasmSrcCodeDir: contractSrcCodeDir,
wasmGlueCode: contractGlueCodeFile
}));
({ contractTxId: properForeignContractTxId } = await warp.deploy({
wallet,
initState: JSON.stringify({
...initialState,
...{
ticker: 'FOREIGN_PST',
name: 'foreign contract'
}
}),
src: contractSrc,
wasmSrcCodeDir: contractSrcCodeDir,
wasmGlueCode: contractGlueCodeFile
}));
({ contractTxId: wrongForeignContractTxId } = await warp.deploy({
wallet,
initState: JSON.stringify({
...initialState,
...{
ticker: 'FOREIGN_PST_2',
name: 'foreign contract 2'
}
}),
src: contractSrc,
wasmSrcCodeDir: contractSrcCodeDir,
wasmGlueCode: contractGlueCodeFile
}));
pst = new PstContract(contractTxId, warp);
pst2 = new PstContract(properForeignContractTxId, warp);
pst3 = new PstContract(wrongForeignContractTxId, warp);
// connecting wallet to the PST contract
pst.connect(wallet).setEvaluationOptions({ internalWrites: true });
pst2.connect(wallet).setEvaluationOptions({ internalWrites: true });
pst3.connect(wallet).setEvaluationOptions({ useKVStorage: true });
});
afterAll(async () => {
await arlocal.stop();
});
it('should properly deploy contract', async () => {
const contractTx = await arweave.transactions.get(contractTxId);
expect(contractTx).not.toBeNull();
const contractSrcTxId = tagsParser.getTag(contractTx, SmartWeaveTags.CONTRACT_SRC_TX_ID);
const contractSrcTx = await arweave.transactions.get(contractSrcTxId);
expect(tagsParser.getTag(contractSrcTx, SmartWeaveTags.CONTENT_TYPE)).toEqual('application/wasm');
expect(tagsParser.getTag(contractSrcTx, SmartWeaveTags.WASM_LANG)).toEqual('rust');
expect(tagsParser.getTag(contractSrcTx, SmartWeaveTags.WASM_META)).toBeTruthy();
const srcTxData = await arweaveWrapper.txData(contractSrcTxId);
const wasmSrc = new WasmSrc(srcTxData);
expect(wasmSrc.wasmBinary()).not.toBeNull();
expect(wasmSrc.additionalCode()).toEqual(fs.readFileSync(contractGlueCodeFile, 'utf-8'));
expect((await wasmSrc.sourceCode()).size).toEqual(11);
});
it('should read pst state and balance data', async () => {
expect(await pst.currentState()).toEqual(initialState);
expect((await pst.balance({ target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M' })).balance).toEqual(10000000);
expect((await pst.balance({ target: '33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA' })).balance).toEqual(23111222);
expect((await pst.balance({ target: walletAddress })).balance).toEqual(555669);
});
it('should properly transfer tokens', async () => {
await pst.transfer(
{
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 555
},
{ vrf: true }
);
expect((await pst.currentState()).balances[walletAddress]).toEqual(555669 - 555);
expect((await pst.currentState()).balances['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M']).toEqual(10000000 + 555);
});
it('should properly use KV', async () => {
await pst3.kvPut(
{
key: 'key1',
value: 'value1'
},
{ vrf: true }
);
const ok = await pst3.kvGet({ key: 'key1' });
expect(ok.key).toEqual('key1');
expect(ok.value).toEqual('value1');
const nok = await pst3.kvGet({ key: 'non-existent' });
expect(nok.key).toEqual('non-existent');
expect(nok.value).toEqual('');
});
it('should properly view contract state', async () => {
const result = await pst.balance({ target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M' });
expect(result.balance).toEqual(10000000 + 555);
expect(result.ticker).toEqual('EXAMPLE_PST_TOKEN');
expect(result.target).toEqual('uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M');
});
// note: the dummy logic on the test contract should add 1000 tokens
// to each address, if the foreign contract state 'ticker' field = 'FOREIGN_PST'
it('should properly read foreign contract state', async () => {
await pst.foreignRead(
{
contractTxId: wrongForeignContractTxId
},
{ vrf: true }
);
expect((await pst.currentState()).balances[walletAddress]).toEqual(555669 - 555);
expect((await pst.currentState()).balances['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M']).toEqual(10000000 + 555);
await pst.foreignRead(
{
contractTxId: properForeignContractTxId
},
{ vrf: true }
);
expect((await pst.currentState()).balances[walletAddress]).toEqual(555669 - 555 + 1000);
expect((await pst.currentState()).balances['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M']).toEqual(
10000000 + 555 + 1000
);
});
it('should properly view foreign contract state', async () => {
const res = await pst.foreignView({
contractTxId: properForeignContractTxId,
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M'
});
expect(res.ticker).toEqual('FOREIGN_PST');
expect(res.target).toEqual('uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M');
expect(res.balance).toEqual(10_000_000);
});
it('should propagate error from view foreign contract state', async () => {
let exc;
try {
await pst.foreignView({
contractTxId: properForeignContractTxId,
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M-Invalid'
});
} catch (e) {
exc = e;
}
expect(exc).toHaveProperty('error.kind', 'WalletHasNoBalanceDefined');
expect(exc).toHaveProperty('error.data', 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M-Invalid');
});
it('should properly perform internal write', async () => {
const balance = (await pst2.balance({ target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M' })).balance;
await pst.foreignWrite(
{
contractTxId: properForeignContractTxId,
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 555
},
{ vrf: true }
);
expect((await pst2.balance({ target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M' })).balance).toEqual(
balance + 555
);
expect((await pst2.balance({ target: walletAddress })).balance).toEqual(555669 - 555);
});
it('should properly perform dry write with overwritten caller', async () => {
const { address: overwrittenCaller } = await warp.generateWallet();
await pst.transfer(
{
target: overwrittenCaller,
qty: 1000
},
{ vrf: true }
);
// note: transfer should be done from the "overwrittenCaller" address, not the "walletAddress"
const result: InteractionResult<State, unknown> = await pst.contract.dryWrite(
{
function: 'transfer',
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 333
},
overwrittenCaller,
undefined,
undefined,
true
);
expect(result.state.balances[walletAddress]).toEqual(555114 - 1000 + 1000);
expect(result.state.balances['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M']).toEqual(10000000 + 1000 + 555 + 333);
expect(result.state.balances[overwrittenCaller]).toEqual(1000 - 333);
});
it('should properly handle runtime errors', async () => {
const result = await pst.contract.dryWrite(
{
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 555
},
undefined,
undefined,
undefined,
true
);
expect(result.type).toEqual('exception');
expect(result).toHaveProperty('errorMessage');
});
it('should properly handle contract errors', async () => {
const result = await pst.contract.dryWrite(
{
function: 'transfer',
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 0
},
undefined,
undefined,
undefined,
true
);
expect(result.type).toEqual('error');
expect(result.error).toHaveProperty('kind', 'TransferAmountMustBeHigherThanZero');
});
xit('should return stable gas results', async () => {
const results = [];
for (let i = 0; i < 10; i++) {
const result = await pst.contract.dryWrite(
{
function: 'transfer',
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 555
},
undefined,
undefined,
undefined,
true
);
results.push(result);
}
results.forEach((result) => {
expect(result.gasUsed).toEqual(9388933);
});
});
xit('should honor gas limits', async () => {
pst.setEvaluationOptions({
gasLimit: 5000000
});
const result = await pst.contract.dryWrite(
{
function: 'transfer',
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 555
},
undefined,
undefined,
undefined,
true
);
expect(result.type).toEqual('exception');
expect(result.errorMessage.startsWith('[RE:OOG] Out of gas!')).toBeTruthy();
});
it("should properly evolve contract's source code", async () => {
const balance = (await pst.currentState()).balances[walletAddress];
const newSource = fs.readFileSync(path.join(__dirname, './data/token-evolve.js'), 'utf8');
const srcTx = await warp.createSource({ src: newSource }, wallet);
const newSrcTxId = await warp.saveSource(srcTx);
await pst.evolve({ value: newSrcTxId }, { vrf: true });
// note: the evolved balance always adds 555 to the result
expect((await pst.balance({ target: walletAddress })).balance).toEqual(balance + 555);
});
});

View File

@@ -0,0 +1,70 @@
export function handle(state, action) {
const balances = state.balances;
const canEvolve = state.canEvolve;
const input = action.input;
const caller = action.caller;
if (input.function === 'transfer') {
const target = input.target;
const qty = input.qty;
if (!Number.isInteger(qty)) {
throw new ContractError('Invalid value for "qty". Must be an integer');
}
if (!target) {
throw new ContractError('No target specified');
}
if (qty <= 0 || caller === target) {
throw new ContractError('Invalid token transfer');
}
if (balances[caller] < qty) {
throw new ContractError(
`Caller balance not high enough to send ${qty} token(s)!`
);
}
// Lower the token balance of the caller
balances[caller] -= qty;
if (target in balances) {
// Wallet already exists in state, add new tokens
balances[target] += qty;
} else {
// Wallet is new, set starting balance
balances[target] = qty;
}
return { state };
}
if (input.function === 'balance') {
const target = input.target;
const ticker = state.ticker;
if (typeof target !== 'string') {
throw new ContractError('Must specify target to get balance for');
}
if (typeof balances[target] !== 'number') {
throw new ContractError('Cannot get balance, target does not exist');
}
return { result: { target, ticker, balance: balances[target] + 555 } };
}
if (input.function === 'evolve' && canEvolve) {
if (state.owner !== caller) {
throw new ContractError('Only the owner can evolve a contract.');
}
state.evolve = input.value;
return { state };
}
throw new ContractError(
`No function supplied or function not recognised: "${input.function}"`
);
}

View File

@@ -0,0 +1,9 @@
{
"ticker": "EXAMPLE_PST_TOKEN",
"owner": "uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M",
"canEvolve": true,
"balances": {
"uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M": 10000000,
"33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA": 23111222
}
}

10
crates/pst/tsconfig.json Normal file
View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"moduleResolution": "Node",
"target": "ES2018",
"lib": ["dom"],
"esModuleInterop": true,
"downlevelIteration": true
},
"exclude": ["node_modules", "tools", "contract/implementation/pkg"]
}

7326
crates/pst/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

225
crates/toy-contract/Cargo.lock generated Normal file
View File

@@ -0,0 +1,225 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "proc-macro2"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "toy-contract"
version = "0.1.0"
dependencies = [
"js-sys",
"serde",
"serde-wasm-bindgen",
"warp-contracts",
"wasm-bindgen",
"wasm-bindgen-futures",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "warp-contracts"
version = "0.1.2"
dependencies = [
"js-sys",
"serde",
"serde-wasm-bindgen",
"warp-contracts-core",
"warp-contracts-macro",
"wasm-bindgen",
"wasm-bindgen-futures",
]
[[package]]
name = "warp-contracts-core"
version = "0.1.2"
dependencies = [
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-futures",
]
[[package]]
name = "warp-contracts-macro"
version = "0.1.2"
dependencies = [
"quote",
"syn",
"warp-contracts-core",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "web-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
dependencies = [
"js-sys",
"wasm-bindgen",
]

View File

@@ -0,0 +1,18 @@
[package]
name = "toy-contract"
version = "0.1.0"
edition = "2021"
description = "test"
repository = "not applicable"
license = "MIT"
[lib]
crate-type = ["cdylib"]
[dependencies]
warp-contracts = { version = "0.1.2", path = "../warp-contracts" }
wasm-bindgen = "=0.2.84"
wasm-bindgen-futures = { version = "0.4.34" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.5"
js-sys = "0.3.61"

View File

View File

View File

@@ -0,0 +1,16 @@
module.exports = {
// Automatically clear mock calls and instances between every test
clearMocks: true,
moduleFileExtensions: ['ts', 'js'],
testPathIgnorePatterns: ['/tests\/data/'],
testEnvironment: 'node',
transformIgnorePatterns: ['<rootDir>/node_modules/(?!@assemblyscript/.*)'],
transform: {
'^.+\\.(ts|js)$': 'ts-jest',
},
};

View File

@@ -0,0 +1,64 @@
{
"name": "warp-rust-template",
"version": "0.0.1",
"description": "Warp WASM Rust contract template",
"main": "index.js",
"author": "Redstone Team <dev@redstone.finance>",
"license": "MIT",
"scripts": {
"build": "wasm-pack build --target nodejs --release --out-name rust-contract .",
"build:dev": "wasm-pack build --target nodejs --dev --out-name rust-contract .",
"build:profiling": "wasm-pack build --target nodejs --profiling --out-name rust-contract .",
"gen-bindings": "yarn gen-json && yarn gen-ts",
"gen-json": "GENERATE_JSON=1 cargo test generate_json -- --nocapture && yarn format:json-bindings",
"gen-ts": "ts-node --transpileOnly ./scripts/generate-ts.ts && yarn format:ts-bindings",
"test": "jest ./tests",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"format:json-bindings": "eslint 'contract/definition/bindings/json/*.json' --fix",
"format:ts-bindings": "eslint 'contract/definition/bindings/ts/*.ts' --fix",
"prettier:format": "prettier --config .prettierrc '**/**/*.ts' --write",
"deploy:local": "yarn build && node deploy/local/deploy-local.js",
"deploy:testnet": "yarn build && node deploy/testnet/deploy-test.js",
"deploy:mainnet": "yarn build && node deploy/mainnet/deploy-mainnet.js",
"read:local": "node deploy/local/read-contract-state-local.js",
"read:testnet": "node deploy/testnet/read-contract-state-testnet.js",
"read:mainnet": "node deploy/mainnet/read-contract-state-mainnet.js",
"transfer:local": "node deploy/local/interact-transfer-local.js",
"transfer:testnet": "node deploy/testnet/interact-transfer-testnet.js",
"transfer:mainnet": "node deploy/mainnet/interact-transfer-mainnet.js",
"balance:local": "node deploy/local/interact-balance-local.js",
"balance:testnet": "node deploy/testnet/interact-balance-testnet.js",
"balance:mainnet": "node deploy/mainnet/interact-balance-mainnet.js"
},
"engines": {
"node": ">=16.5"
},
"dependencies": {
"arlocal": "^1.1.59",
"arweave": "1.12.4",
"json-schema-to-typescript": "^11.0.1",
"typescript": "^4.6.2",
"warp-contracts": "1.2.56",
"warp-contracts-plugin-deploy": "^1.0.0"
},
"resolutions": {
"arweave": "1.12.4"
},
"overrides": {
"arweave": "1.12.4"
},
"devDependencies": {
"@types/jest": "^27.4.1",
"@types/node": "^18.6.3",
"@typescript-eslint/eslint-plugin": "^5.40.0",
"@typescript-eslint/parser": "^5.40.0",
"eslint": "^8.25.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.1",
"jest": "^28.1.3",
"prettier": "^2.3.2",
"ts-jest": "^28.0.7",
"ts-node": "^10.9.1"
}
}

View File

@@ -0,0 +1,28 @@
use serde::{Serialize, Deserialize};
use warp_contracts::{warp_contract, handler_result::{WriteResult, ViewResult}};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct State {
x: u8,
}
#[derive(Debug, Deserialize)]
pub struct Action {
x: u8,
}
#[derive(Debug, Serialize)]
pub struct View {
x: u8,
}
#[warp_contract(write)]
pub fn handle(mut state: State, action: Action) -> WriteResult<State, ()> {
state.x = action.x;
WriteResult::Success(state)
}
#[warp_contract(view)]
pub fn view(state: &State, _action: Action) -> ViewResult<View, ()> {
ViewResult::Success(View { x: state.x })
}

View File

@@ -0,0 +1,116 @@
import fs from 'fs';
import ArLocal from 'arlocal';
import Arweave from 'arweave';
import { JWKInterface } from 'arweave/node/lib/wallet';
import {
LoggerFactory,
Warp,
SmartWeaveTags,
WarpFactory,
TagsParser,
ArweaveWrapper,
WasmSrc,
Contract
} from 'warp-contracts';
import { DeployPlugin } from 'warp-contracts-plugin-deploy';
import path from 'path';
jest.setTimeout(30000);
class State {
constructor(public x: number) {}
}
class View {
constructor(public x: number) {}
}
class Action {
constructor(public x: number) {}
}
describe('Testing the Rust WASM Profit Sharing Token', () => {
let contractSrc: Buffer;
let contractGlueCodeFile: string;
let wallet: JWKInterface;
const initialState = new State(0);
let arweave: Arweave;
let arlocal: ArLocal;
let warp: Warp;
let toyContract: Contract<State>;
let contractTxId: string;
let arweaveWrapper: ArweaveWrapper;
let tagsParser: TagsParser;
beforeAll(async () => {
arlocal = new ArLocal(1820, false);
await arlocal.start();
tagsParser = new TagsParser();
LoggerFactory.INST.logLevel('debug');
LoggerFactory.INST.logLevel('debug', 'WASM:Rust');
//LoggerFactory.INST.logLevel('debug', 'WasmContractHandlerApi');
warp = WarpFactory.forLocal(1820).use(new DeployPlugin());
({ arweave } = warp);
arweaveWrapper = new ArweaveWrapper(arweave);
({ jwk: wallet } = await warp.generateWallet());
contractSrc = fs.readFileSync(path.join(__dirname, '../pkg/rust-contract_bg.wasm'));
const contractSrcCodeDir: string = path.join(__dirname, '../src');
contractGlueCodeFile = path.join(__dirname, '../pkg/rust-contract.js');
// deploying contract using the new SDK.
({ contractTxId } = await warp.deploy({
wallet,
initState: JSON.stringify(initialState),
src: contractSrc,
wasmSrcCodeDir: contractSrcCodeDir,
wasmGlueCode: contractGlueCodeFile
}));
toyContract = warp.contract<State>(contractTxId);
// connecting wallet to the PST contract
toyContract.connect(wallet);
});
afterAll(async () => {
await arlocal.stop();
});
it('should properly deploy contract', async () => {
const contractTx = await arweave.transactions.get(contractTxId);
expect(contractTx).not.toBeNull();
const contractSrcTxId = tagsParser.getTag(contractTx, SmartWeaveTags.CONTRACT_SRC_TX_ID);
const contractSrcTx = await arweave.transactions.get(contractSrcTxId);
expect(tagsParser.getTag(contractSrcTx, SmartWeaveTags.CONTENT_TYPE)).toEqual('application/wasm');
expect(tagsParser.getTag(contractSrcTx, SmartWeaveTags.WASM_LANG)).toEqual('rust');
expect(tagsParser.getTag(contractSrcTx, SmartWeaveTags.WASM_META)).toBeTruthy();
const srcTxData = await arweaveWrapper.txData(contractSrcTxId);
const wasmSrc = new WasmSrc(srcTxData);
expect(wasmSrc.wasmBinary()).not.toBeNull();
expect(wasmSrc.additionalCode()).toEqual(fs.readFileSync(contractGlueCodeFile, 'utf-8'));
expect((await wasmSrc.sourceCode()).size).toEqual(1);
});
it('should read state', async () => {
const { cachedValue } = await toyContract.readState();
expect(cachedValue.state).toEqual(initialState);
});
it('should write/view state', async () => {
await toyContract.writeInteraction<Action>(new Action(4));
const interactionResult = await toyContract.viewState<Action, View>(new Action(17));
expect(interactionResult.result.x).toEqual(4);
});
});

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"moduleResolution": "Node",
"target": "ES2018",
"lib": ["dom"],
"esModuleInterop": true,
"downlevelIteration": true
},
"exclude": ["node_modules"]
}

File diff suppressed because it is too large Load Diff