Skip to content
This repository was archived by the owner on Mar 12, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions order-book/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
config
contracts/node_modules
frontend-console
server
2 changes: 2 additions & 0 deletions order-book/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
machine
.vscode
28 changes: 28 additions & 0 deletions order-book/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
FROM node:16-buster-slim

# install git
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y \
build-essential git python3 \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

# copy machine
WORKDIR /app/machine
COPY machine .

# build to bring node_modules
WORKDIR /app/contracts
COPY ["contracts/package.json", "contracts/yarn.lock", "./"]
RUN yarn install --non-interactive

# build app
COPY contracts .
RUN yarn install --non-interactive

# expose hardhat node port
EXPOSE 8545

WORKDIR /app/contracts
ENTRYPOINT ["npx", "hardhat"]
CMD ["node"]
26 changes: 26 additions & 0 deletions order-book/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2022 Cartesi Pte. Ltd.
#
# SPDX-License-Identifier: Apache-2.0
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use
# this file except in compliance with the License. You may obtain a copy of the
# License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.

MACHINE_DIR := machine

.PHONY: clean console

$(MACHINE_DIR):
@make -C server
@mv server/machine $@

console:
@make -C server console

clean:
@make -C server clean
@rm -rf $(MACHINE_DIR)
306 changes: 306 additions & 0 deletions order-book/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
# Order Book DApp

The Order Book DApp is a Cartesi Rollups application where users can enter buy and sell orders which are matched against each other creating transactions similarly to a traditional order book based exchange.
The application creates and runs an SQLite database internally which holds the product, order and transaction data of the exchange. New orders are immediately checked against the order book to find matches once created and any transactions are recorded in the database.
You can send inputs with predefined structures to interact with the database and you get the results back as a notice. The available inputs are listed under [Example inputs](#example-inputs).

The following actions are supported:

- Create new orders
- Modify orders
- Delete orders by user
- Query orders and market data
- Query transactions
- Query supported products
- Add new products

## How it works

Upon building and starting the application, a sqlite database is created with 5 tradeable products and an empty order book. Users can add orders to the order book which can get executed fully, executed partly, or wait for later execution, subject to market conditions. Order properties consist of the following:

- **product** (the product against which the base product is traded)
- **side** ("bid" for buy, "offer" for sale orders)
- **amount** (units of product to be traded)
- **unit price** (the limit, aka best price the user would settle the trade at)
- **closing time** (a deadline for the order)

Once a buy (bid) or sell (offer) order is added into the order book, the matching function of the application loops through all open counter-side orders (offers against bids, bids against offers) of the product to find the best available price on the market. The best price is the lowest price for a buy order and the highest price for a sell order.
If the best price is within the range of the order price limit, the matching function selects the counter-order associated with the best price. If there are more than one orders in the order book at the best price, the least recent order is selected as counter-order. Once the counter-order is selected, a transaction is initiated for the tradeable amount, which is the lower order amount of the order and the counter-order. When the transaction is created, the amount of the two orders are each reduced by the transacted amount.
If there is any remaining open amount on the order, the loop of the matching function starts again to look for further matches. The loop continues until either the order amount reaches 0 (i.e. the full amount has traded) or the order book runs out of open orders whose price would match the order. Order matching is triggered again when a new order is added into the order book.

Example:

- **User A** adds a bid at 8212 for 3 units. As the order book is empty, there are no transactions.
- **User B** adds an offer at 8217 for 2 units. As the best bid is lower than the offer price, there are no transactions.
- **User C** adds a bid at 8219 for 4 units. As the best offer is lower than the bid price, the order can be (partly) executed. The best price is associated with **User B**'s offer, which will be matched against the bid. 2 units are traded, which is the full amount of the offer, while 2 units will remain open on **User C**'s bid. The trading price is 8217, which was the best offer price on the market.
- **User D** adds an offer at 8210 for 5 units. The best price on the market is 8212 associated with **User A**'s bid. 3 units trade at that price, resulting in **User A**'s order being fully settled. The matching function looks for further matches and finds the current best price, which is **user C**'s remaining 2 units bid at 8219, against whom the remaining 2 units are traded at that price. With the last transaction, all open orders have been settled.

## Building the environment

To run the Cartesi Order Book example, clone the repository then, build the back-end for Cartesi Order Book:

```shell
$ cd order-book
$ make machine
```

## Running the environment

In order to start the containers in production mode, simply run:

```shell
$ docker-compose up --build
```

_Note:_ If you decide to use [Docker Compose V2](https://docs.docker.com/compose/cli-command/), make sure you set the [compatibility flag](https://docs.docker.com/compose/cli-command-compatibility/) when executing the command (e.g., `docker compose --compatibility up`).

Allow some time for the infrastructure to be ready.
How much will depend on your system, but after some time showing the error `"concurrent call in session"`, eventually the container logs will repeatedly show the following:

```shell
server_manager_1 | Received GetVersion
server_manager_1 | Received GetStatus
server_manager_1 | default_rollups_id
server_manager_1 | Received GetSessionStatus for session default_rollups_id
server_manager_1 | 0
server_manager_1 | Received GetEpochStatus for session default_rollups_id epoch 0
```

To stop the containers, first end the process with `Ctrl + C`.
Then, remove the containers and associated volumes by executing:

```shell
$ docker-compose down -v
```

## Interacting with the application

With the infrastructure in place, you can interact with the application using a set of Hardhat tasks.

First, go to a separate terminal window, switch to the `order-book/contracts` directory, and run `yarn`:

```shell
$ cd order-book/contracts/
$ yarn
```

Then, send an input as follows:

```shell
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"test","data":{}}'
```

The input will have been accepted when you receive a response similar to the following one:

````shell
Added input '{"resource":"test"}' to epoch '0' (index: '0', timestamp: 1650886109, signer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, tx: 0x6a07b324d4bad477f82fd6db85d8440520a657cc674f0048217817d7188370ed)```

In order to verify the notices generated by your inputs, run the command:

```shell
$ yarn hardhat --network localhost order-book:getNotices --epoch 0
```

The response should be something like this:

```shell
{"session_id":"default_rollups_id","epoch_index":"0","input_index":"0","notice_index":"0","payload":"7b22737461747573223a207b2273756363657373223a20747275652c20226d657373616765223a20227465737420696e707574207265636569766564227d7d"}
```

To retrieve notices interpreting the payload as a UTF-8 string, you can use the `--payload string` option:

```shell
$ yarn hardhat --network localhost order-book:getNotices --epoch 0 --payload string
{"session_id":"default_rollups_id","epoch_index":"0","input_index":"0","notice_index":"0","payload":"{\"status\": {\"success\": true, \"message\": \"test input received\"}}"
```

Finally, note that you can check the available options for all Hardhat tasks using the `--help` switch:

```shell
$ yarn hardhat --help
```

## Input format

The input for the application should be in the following structure:

```json
{
"resource": "order",
"action": "create",
"data": {
"side": "bid",
"unit_price": 8212,
"amount": 2,
"symbol": "ETH",
"closing_time": 1650616219
}
}
```

- `side` can be `bid` for buy orders and `offer` for sell orders
- `unit_price` is the price you want to exchange the asset at
- `amount` is the amount of the asset you want to exchange
- `symbol` is the symbol of the asset
- `closing_time` is the time your offer times out. If set to `0` it never times out

All assets are traded in CTSI so the price should be given in CTSI.

### Example inputs

```shell
# Accounts

# Get all accounts
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"account","action":"get"}'

# Add account
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"account","action":"add"}'


# Orders

## Create a new limit buy order
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"create","data":{"type":"limit","side":"bid","unit_price":11200,"amount":2,"symbol":"ETH","closing_time":1872128654}}'

## Create a new limit sell order
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"create","data":{"type":"limit","side":"offer","unit_price":11208,"amount":21,"symbol":"ETH","closing_time":1872128654}}'

## Create a new market buy order
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"create","data":{"type":"market","side":"bid","unit_price":0,"amount":32,"symbol":"ETH","closing_time":1872128654}}'

## Get your orders
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"get"}'

## Modify an order
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"modify","data":{"id":1,"unit_price":8214,"amount":3,"closing_time":1872128659}}'

## Cancel an order
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"cancel","data":{"id":1}}'

## Get all open orders for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"get_book","data":{"symbol":"ETH"}}'

## Get all open + closed orders for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"get_orders","data":{"symbol":"ETH"}}'

## Get all bids for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"get_bids","data":{"symbol":"ETH"}}'

## Get all offers for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"get_offers","data":{"symbol":"ETH"}}'

## Get the highest bid for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"get_bid","data":{"symbol":"ETH"}}'

## Get the lowest offer for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"get_offer","data":{"symbol":"ETH"}}'

## Get best price for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"get_best","data":{"symbol":"ETH","side":"bid"}}'

## Get the bid-offer spread for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"get_spread","data":{"symbol":"ETH"}}'

## Get market data (highest bid, lowest offer and associated amount, bid-offer spread, mid-price) for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"order","action":"get_market","data":{"symbol":"ETH"}}'


# Transactions

# Get all transactions for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"transaction","action":"get","data":{"symbol":"ETH"}}'

# Get last traded transaction for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"transaction","action":"get_last_traded","data":{"symbol":"ETH"}}'

# Get highest traded transaction for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"transaction","action":"get_highest_traded","data":{"symbol":"ETH"}}'

# Get lowest traded transaction for a product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"transaction","action":"get_lowest_traded","data":{"symbol":"ETH"}}'


# Products

# Get all supported products
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"product","action":"get"}'

# Add new product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"product","action":"add","data":{"symbol":"DMC","name":"Dummycoin"}}'


# Funds

# Get user funds
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"fund","action":"get"}'

# Get available user funds (not including those reserved in open orders)
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"fund","action":"get_available"}'

# Add user funds for product
$ yarn hardhat --network localhost order-book:addInput --input '{"resource":"fund","action":"add","data":{"symbol":"CTSI","amount":30000}}'
```

## Advancing time

To advance time, in order to simulate the passing of epochs, run:

```shell
$ yarn hardhat --network localhost util:advanceTime --seconds 864010
```

## Running the environment in host mode

When developing an application, it is often important to easily test and debug it. For that matter, it is possible to run the Cartesi Rollups environment in [host mode](../README.md#host-mode), so that the DApp's back-end can be executed directly on the host machine, allowing it to be debugged using regular development tools such as an IDE.

The first step is to run the environment in host mode using the following command:

```shell
$ docker-compose -f docker-compose.yml -f docker-compose-host.yml up --build
```

The next step is to run the order-book server in your machine. The application is written in Python, so you need to have `python3` installed.

In order to start the order-book server, run the following commands in a dedicated terminal:

```shell
$ cd order-book/server/
$ python3 -m venv .env
$ . .env/bin/activate
$ pip install -r requirements.txt
$ HTTP_DISPATCHER_URL="http://127.0.0.1:5004" gunicorn --reload --workers 1 --bind 0.0.0.0:5003 order-book:app
```

This will run the order-book server on port `5003` and send the corresponding notices to port `5004`. The server will also automatically reload if there is a change in the source code, enabling fast development iterations.

The final command, which effectively starts the server, can also be configured in an IDE to allow interactive debugging using features like breakpoints. In that case, it may be interesting to add the parameter `--timeout 0` to gunicorn, to avoid having it time out when the debugger stops at a breakpoint.

After the server successfully starts, it should print an output like the following:

```
[2022-01-21 12:38:23,971] INFO in order-book: HTTP dispatcher url is http://127.0.0.1:5004
[2022-01-21 12:38:23 -0500] [79032] [INFO] Starting gunicorn 19.9.0
[2022-01-21 12:38:23 -0500] [79032] [INFO] Listening at: http://0.0.0.0:5003 (79032)
[2022-01-21 12:38:23 -0500] [79032] [INFO] Using worker: sync
[2022-01-21 12:38:23 -0500] [79035] [INFO] Booting worker with pid: 79035
```

After that, you can interact with the application normally [as explained above](#interacting-with-the-application).

When you add an input, you should see it being processed by the order-book server as follows:

```shell
[2022-01-21 15:58:39,555] INFO in order-book: Received advance request body {'metadata': {'msg_sender': '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', 'epoch_index': 0, 'input_index': 0, 'block_number': 11, 'time_stamp': 1642791522}, 'payload': '0x636172746573690d0a'}
[2022-01-21 15:58:39,556] INFO in order-book: Adding notice
[2022-01-21 15:58:39,650] INFO in order-book: Received notice status 201 body b'{"index":0}'
[2022-01-21 15:58:39,651] INFO in order-book: Finishing
[2022-01-21 15:58:39,666] INFO in order-book: Received finish status 202
```

Finally, to stop the containers, removing any associated volumes, execute:

```shell
$ docker-compose -f docker-compose.yml -f docker-compose-host.yml down -v
```
````
10 changes: 10 additions & 0 deletions order-book/config/bs-config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[block_subscriber]

# max delay between retries in seconds
max_delay = 64

# max number of retries
max_retries = 5

# timeout for block subscriber in seconds
timeout = 120
Loading