add weighted price oracle

This commit is contained in:
Alexey Proshutinskiy 2022-01-14 05:29:46 +03:00
parent 4766077e03
commit 455b7c6736
22 changed files with 7954 additions and 0 deletions

View File

@ -0,0 +1,155 @@
# Weighted Price Oracle With Fluence And Aqua
## About Fluence
Fluence provides an open Web3 protocol, framework and associated tooling to develop and host applications, interfaces and backends on permissionless peer-to-peer networks. An integral part of the Fluence solution is the Aquamarine stack comprised of Aqua and Marine. Aqua is a new programming language and paradigm purpose-built to program distributed networks and compose applications from distributed services. Marine is a general purpose Wasm runtime and toolkit, allows developers to build distributed services that can be composed into distributed applications by Aqua.
Fluence Developer Resources:
* [Developer Documentation](https://doc.fluence.dev/docs/)
Aqua Developer Resources:
* [Aqua Book](https://app.gitbook.com/@fluence/s/aqua-book/)
* [Aqua Playground](https://github.com/fluencelabs/aqua-playground)
* [Aqua repo](https://github.com/fluencelabs/aqua)
Marine Developer Resources:
* [Marine repo](https://github.com/fluencelabs/marine)
* [Marine SDK](https://github.com/fluencelabs/marine-rs-sdk)
## Overview
Price (feed) oracles are probably the most used and in-demand oracle type and tend to have a significant impact on the success and profitability of DeFi and related on- and off-chain operations. In this example, we demonstrate how to create a decentralized, off-chain price (feed) oracle on the Fluence peer-to-peer network from a set of distributed, re-usable Marine services composed by Aqua and use TrustGraph for peer prioritization.
Figure 1: Stylized Price Oracle Network And Service Process
// TODO: insert diagram
As outlined in Figure 1, we use one or more services distributed across the Fluence peer-to-peer network to obtain price quotes from sources. For example, we could have one service capable of querying one or more sources such as DEX contracts deployed on multiple network peers allowing us to poll price sources in parallel. We then join these results and submit them to a processing service also deployed on the peer-to-peer network to establish, for example, an oracle point-estimate. Once we've obtained our oracle, we return it to the peer-client, e.g., a browser.
## Quick Start
Look at [client-peer](./client-peer) directory for a peer-client based on the Fluence JS-SDK. To run the headless client:
```bash
% cd client-peer
% npm instal
% npm start run
```
which gives us:
```text
# <snip>
hello crypto investors
created a fluence client 12D3KooWEKui98wfChxaPou6qcNcH5zY3LzfXRwJbTViKUdY7aWi with relay 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi
// TODO: add smth about trusts chain and weights
get_weighted_price result: { error_msg: '', result: 3989.19, success: true }
```
where the Aqua script can be found in the `aqua-scripts` directory and the compiled Aqua code is found in the `get_crypto_prices.ts` file. For more on the Aqua script, see below.
## Service Development And Deployment
**Prerequisites:** If you want to follow along, compile Wasm modules, create services and deploy service, you need Rust, Node and a few Fluence tools installed. Please see follow the [Setup](https://doc.fluence.dev/docs/tutorials_tutorials/recipes_setting_up) instructions.
Applications are composed from one or more services available on one or more Fluence peer-to-peer nodes. Services are comprised of one or more Wasm modules providing a wide range of compute functionality and access to persistance, e.g. IPFS and SQLite. For the purpose of our objective, we need a service that can call on some API to source price quotes. In an ideal, production world, this would be calling on a large set of DEX contacts to obtain price pairs and, say, lquidity, over a particular window of time. For our purposes, we simplify the process and call on the [Coingecko API](https://www.coingecko.com/api/documentations/v3) and add a random jitter to each quote retrieved to give us some variance.
For implementation details, see [price_getter_service]("./../price_getter_service/src/main.rs), which compiles to our desired wasm32-wasi target. Since Wasm modules don't have sockets but we need to use cUrl, which is provided by the host node. In order to do that, we fist need to write an adapter that allows us to access the cUrl service from a Wasm module and then link that service to our price_getter service. See [cUrl adapter example](./marine-scripts/curl_adapter) for more details on the implementation of our [curl adapter service](./curl_adapter/src/main.rs).
Figure 3: Stylized Service Creation By Marine Module Linking
<img src="images/figure_2.png " width="400" />
As seen in Figure 3, we link the price_getter module and curl adapter module into a price_getter service ready for deployment to the Fluence peer-to-peer network. Before we proceed, we have one more service to consider: the price quote processing service which yields the oracle. Again, we simplified what could be an extensive processing algorithm into a simple weighted mean calculation, see [weighted_mean_service]("./../weighted_mean_service/src/main.rs") for implementation details. Unlike the price getter service, weighted mean service is a simple, FaaS compute module that deploys on any number of network peers.
For peer prioritization we will use built-in TrustGraph service. TODO: smth about weights
We now have our code in place and area ready to compile and our compilation instructions are contain in the `scripts/build.sh` script, which basically instructs the the code is compiled with `marine` and that the resulting Wasm modules are copied to the `artifacts` directory. In the project directory:
```bash
./scripts/build.sh
```
which gives you the updated Wasm modules in the `artifacts` directory.
The next step is to deploy the two services to one or more peers and we use the `fldist` tool to get this done. First, we need to now what peers are available and we can get an enumeration from:
```bash
fldist env
```
*Please note that multiple service instances have been already deployed and the (peer id, service id) tuples can be found in [data]("./data/deployed_services.json) json file. While your more than welcome to deploy your services, you don't have to in order to use them.*
Pick any of the peer ids from the listed peers to deploy your services. Let's say we use peer id `12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi`:
```bash
$ fldist --node-id 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi new_service --ms artifacts/curl_adapter.wasm:configs/curl_adapter_cfg.json artifacts/price_getter_service.wasm:configs/price_getter_service_cfg.json --name price-getter-service-0
service id: f5b456fa-ee18-4df1-b18b-84fe7ebc7ad0 # <--- REMEMBER service id !!
service created successfully
```
to deploy a price-getter service and
```bash
$ fldist --node-id 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi new_service --ms artifacts/weighted_mean_service.wasm:configs/weighted_mean_service_cfg.json --name weighted_mean-service-0
service id: debecd02-ba7d-40a2-92ab-08a9321da2cf # <--- REMEMBER service id !!
service created successfully
```
to deploy a weighted mean service. Please take note of the service-id you get back for each fo the deployments, which are needed to locate the service in the future.
That's it for service development and deployment!
## Application Composition with Aqua
Aqua allows us to compose distributed services into decentralized applications such as our price oracle app. However, Aqua permits a great degree of freedom of *how* to compose services. As Aqua combines [Layer 3 and Layer 7](https://en.wikipedia.org/wiki/OSI_model) programming, i.e., network and application programming, respectively, Aqua allows us to specify parallel or sequential workflows in response to service availability and deployment.
With just a few lines of code, we can program the network and application layers to compose hose peer-to-peer services into powerful decentralized applications, look at the `get_weighted_price` function:
```aqua
-- aqua-scripts/get_crypto_prices.aqua
data NodeServicePair:
node: string
service_id: string
func get_weighted_price(coin: string, currency: string, getter_topo: []NodeServicePair, mean_topo: NodeServicePair) -> Result:
prices: *f64
weights: *u32
for topo <- getter_topo: --< For each instance of the getter topology
on topo.node: --< On each specified node
PriceGetterService topo.service_id --< And service id
ts_ms <- Peer.timestamp_ms()
res <- PriceGetterService.price_getter(coin, currency, ts_ms) --< Run the price getter service to obtain a quote
prices <- F64Op.identity(res.result)
on HOST_PEER_ID: --< On our relay
for topo <- getter_topo: --< For each instance of the getter topology
res <- get_weight(topo.node) --< Obtain subjective weight of each node in trust graph
weights <- U32Op.identity(res.weight)
on mean_topo.node: --< After loops, create the Mean service binding on the specified node
WeightedMeanService mean_topo.service_id
result <- WeightedMeanService.weighted_mean(prices, weights) --< Process the price quote array
<- result --< Return the result to the client peer
```
To compile our Aqua script, we use the `aqua` tool and either compile our code to raw Air:
```text
% aqua -i aqua-scripts -o air-scripts -a
```
or to a ready-made typescript stub:
```text
% aqua -i aqua-scripts -o air-scripts
```
## Summary
We illustrated how to create decentralized weighted price oracle application with Aqua and the Fluence stack with different peers and use TrustGraph for prioritization.

View File

@ -0,0 +1,16 @@
import Peer from "@fluencelabs/aqua-lib/builtin.aqua"
use add_trust from "@fluencelabs/trust-graph/trust-graph-api.aqua" as TrustGraph
export add_trust
func add_trust(issued_for_peer_id: string, expires_at_sec: u64) -> ?string:
on HOST_PEER_ID:
error <- TrustGraph.add_trust(issued_for_peer_id, expires_at_sec)
<- error
func timestamp_sec() -> u64:
on HOST_PEER_ID:
result <- Peer.timestamp_sec()
<- result

View File

@ -0,0 +1,43 @@
import "@fluencelabs/aqua-lib/builtin.aqua"
import "@fluencelabs/trust-graph/trust-graph-api.aqua"
data Result:
result: f64
success: bool
error_msg: string
service PriceGetterService:
price_getter(coin: string, currency: string, timestamp_ms: u64) -> Result
service WeightedMeanService:
weighted_mean(data: []f64, weights: []u32) -> Result
service F64Op("op"):
identity(x:f64) -> f64
service U32Op("op"):
identity(x:u32) -> u32
data NodeServicePair:
node: string
service_id: string
func get_weighted_price(coin: string, currency: string, getter_topo: []NodeServicePair, mean_topo: NodeServicePair) -> Result:
prices: *f64
weights: *u32
for topo <- getter_topo:
on topo.node:
PriceGetterService topo.service_id
ts_ms <- Peer.timestamp_ms()
res <- PriceGetterService.price_getter(coin, currency, ts_ms)
prices <- F64Op.identity(res.result)
on HOST_PEER_ID:
for topo <- getter_topo:
res <- get_weight(topo.node)
weights <- U32Op.identity(res.weight)
on mean_topo.node:
WeightedMeanService mean_topo.service_id
result <- WeightedMeanService.weighted_mean(prices, weights)
<- result

View File

@ -0,0 +1,5 @@
dist/
# fluence
src/_aqua/*

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
{
"name": "price-oracle",
"version": "0.1.0",
"description": "Fluence Price Oracle Demo",
"main": "./dist/src/index.js",
"typings": "./dist/src/index.d.ts",
"files": [
"dist/*"
],
"bic": [
"client-peer/*",
"*.aqua",
"package-lock.json"
],
"dependencies": {
"@fluencelabs/fluence": "0.17.0",
"@fluencelabs/fluence-network-environment": "1.0.10",
"it-all": "^1.0.5"
},
"scripts": {
"compile-aqua": "aqua -i ../aqua-scripts -o src/_aqua",
"prebuild": "npm run compile-aqua",
"build": "tsc",
"prestart:local": "npm run build",
"start:local": "node dist/src/index.js local",
"prestart:remote": "npm run build",
"start:remote": "node dist/src/index.js krasnodar",
"start": "npm run start:remote"
},
"repository": {
"type": "git",
"url": "git+https://github.com/fluencelabs/examples/aqua-examples/price-oracle"
},
"keywords": [
"aqua",
"fluence"
],
"author": "Fluence Labs",
"license": "MIT",
"bugs": {
"url": "git+https://github.com/fluencelabs/examples/issues"
},
"homepage": "git+https://github.com/fluencelabs/examples/aqua-examples/price-oracle#readme",
"devDependencies": {
"@fluencelabs/aqua": "^0.5.2-256",
"@fluencelabs/aqua-lib": "^0.3.2",
"@fluencelabs/trust-graph": "0.1.2-hl-api.0",
"typescript": "^4.5.2",
"bs58": "^4.0.1"
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* 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.
*/
import { setLogLevel, Fluence, KeyPair } from "@fluencelabs/fluence";
import { krasnodar } from "@fluencelabs/fluence-network-environment";
import { get_weighted_price } from "./_aqua/get_crypto_prices";
import * as tg from "./_aqua/export";
const bs58 = require('bs58');
interface NodeServicePair {
node: string;
service_id: string;
}
let getter_topo: Array<NodeServicePair>;
let mean_topo: Array<NodeServicePair>;
// description of the services' locations, copypaste from data/deployed_services.json
getter_topo = [
{
node: "12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS",
service_id: "7d495484-f0f0-4153-b4f0-fbb0fd162965"
},
{
node: "12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi",
service_id: "a79386c1-d6fc-4a41-ab5a-60599b151345"
}
];
mean_topo = [
{
node: "12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS",
service_id: "02fba79b-c72c-4ae6-b54b-8c7d871caf36"
},
{
node: "12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi",
service_id: "d4aef7ec-4d2b-4643-9d7b-93a63387f1f0"
}
];
async function add_new_trust_checked(issued_for_peer_id: string, expires_at_sec: number) {
let error = await tg.add_trust(issued_for_peer_id, expires_at_sec);
if (error !== null) {
console.error("%s", error);
} else {
console.log("Trust issued for %s successfully added", issued_for_peer_id)
}
}
async function main() {
console.log("hello crypto investors");
// Uncomment to enable debug logs:
// setLogLevel('DEBUG');
// this key is bundled in all tg builtins
// defined in https://github.com/fluencelabs/trust-graph/blob/master/example_secret_key.ed25519
let sk = bs58.decode("E5ay3731i4HN8XjJozouV92RDMGAn3qSnb9dKSnujiWv").slice(0, 32); // first 32 bytes - secret key, second - public key
let example_kp = await KeyPair.fromEd25519SK(sk);
// create the Fluence client for the Krasnodar testnet
await Fluence.start({ connectTo: krasnodar[0], KeyPair: example_kp });
console.log(
"created a fluence client %s with relay %s",
Fluence.getStatus().peerId,
Fluence.getStatus().relayPeerId
);
await add_new_trust_checked(getter_topo[0].node, 9999999999);
await add_new_trust_checked(getter_topo[1].node, 9999999999);
const weighted_price_result = await get_weighted_price("bitcoin", "usd", getter_topo, mean_topo[0]);
console.log("get_weighted_price result: ", weighted_price_result);
await Fluence.stop();
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -0,0 +1,62 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@ -0,0 +1,19 @@
modules_dir = "artifacts/"
[[module]]
name = "curl_adapter"
logger_enabled = false
mem_pages_count = 1
[module.mounted_binaries]
curl = "/usr/bin/curl"
[[module]]
name = "price_getter_service"
logger_enabled = false
mem_pages_count = 1
[[module]]
name = "weighted_mean_service"
logger_enabled = false
mem_pages_count = 1

View File

@ -0,0 +1,8 @@
{
"name": "curl_adapter",
"mountedBinaries": {
"curl": "/usr/bin/curl"
},
"mem_page_count": 1,
"logger_enabled": false
}

View File

@ -0,0 +1,5 @@
{
"name": "price_getter_service",
"mem_page_count": 1,
"logger_enabled": false
}

View File

@ -0,0 +1,5 @@
{
"name": "weighted_mean_service",
"mem_page_count": 1,
"logger_enabled": false
}

View File

@ -0,0 +1,14 @@
[package]
name = "curl_adapter"
version = "0.1.0"
authors = ["Fluence Labs"]
edition = "2018"
publish = false
[[bin]]
path = "src/main.rs"
name = "curl_adapter"
[dependencies]
marine-rs-sdk = "0.6.14"
log = "0.4.8"

View File

@ -0,0 +1,37 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* 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.
*/
#![allow(improper_ctypes)]
use marine_rs_sdk::marine;
use marine_rs_sdk::module_manifest;
use marine_rs_sdk::MountedBinaryResult;
module_manifest!();
pub fn main() {}
#[marine]
pub fn curl_request(cmd: Vec<String>) -> MountedBinaryResult {
curl(cmd)
}
#[marine]
#[link(wasm_import_module = "host")]
extern "C" {
fn curl(cmd: Vec<String>) -> MountedBinaryResult;
}

View File

@ -0,0 +1,22 @@
{
"price_getter": [
{
"node": "12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS",
"service_id": "7d495484-f0f0-4153-b4f0-fbb0fd162965"
},
{
"node": "12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi",
"service_id": "a79386c1-d6fc-4a41-ab5a-60599b151345"
}
],
"mean": [
{
"node": "12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS",
"service_id": "02fba79b-c72c-4ae6-b54b-8c7d871caf36"
},
{
"node": "12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi",
"service_id": "d4aef7ec-4d2b-4643-9d7b-93a63387f1f0"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -0,0 +1,22 @@
[package]
name = "price_getter_service"
version = "0.1.0"
authors = ["boneyard93501 <4523011+boneyard93501@users.noreply.github.com>"]
edition = "2018"
description = "price-getter-service, a Marine wasi module"
license = "Apache-2.0"
[[bin]]
name = "price_getter_service"
path = "src/main.rs"
[dependencies]
marine-rs-sdk = "0.6.14"
log = "0.4.14"
picorand = "0.1.1"
fstrings = "0.2.3"
serde_json = "1.0.57"
[dev]
[profile.release]
opt-level = "s"

View File

@ -0,0 +1,94 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* 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.
*/
use marine_rs_sdk::{marine, module_manifest, MountedBinaryResult};
use picorand::{PicoRandGenerate, WyRand, RNG};
use serde_json;
#[macro_use]
extern crate fstrings;
module_manifest!();
pub fn main() {}
#[marine]
pub struct Result {
pub result: f64,
pub success: bool,
pub error_msg: String,
}
#[marine]
pub fn price_getter(coin: String, currency: String, timestamp_ms: u64) -> Result {
let url =
f!("https://api.coingecko.com/api/v3/simple/price?ids={coin}&vs_currencies={currency}");
let curl_cmd = vec![
"-X".to_string(),
"GET".to_string(),
"-H".to_string(),
"Accept: application/json".to_string(),
url,
];
let response = curl_request(curl_cmd);
let result = String::from_utf8(response.stdout);
match result {
Ok(res) => {
let json_res = serde_json::from_str(&res.clone());
if json_res.is_err() {
return Result {
result: -1f64,
success: false,
error_msg: "Failure to complete call".to_string(),
};
}
let json_res: serde_json::Value = json_res.unwrap();
let value = json_res[coin.to_lowercase()][currency.to_lowercase()].as_f64();
if value.is_none() {
return Result {
result: -1f64,
success: false,
error_msg:
"No price value from source available. Check your coin and currency values."
.to_string(),
};
}
let value: f64 = value.unwrap();
let mut rng = RNG::<WyRand, u16>::new(timestamp_ms);
let multiplier = rng.generate_range(2, 5) as f64;
let rnd_value = value * (1f64 + multiplier / 100f64);
Result {
result: format!("{:.2}", rnd_value).parse::<f64>().unwrap(),
success: true,
error_msg: "".to_string(),
}
}
Err(_) => Result {
result: -1f64,
success: false,
error_msg: String::from_utf8(response.stderr).unwrap(),
},
}
}
#[marine]
#[link(wasm_import_module = "curl_adapter")]
extern "C" {
pub fn curl_request(cmd: Vec<String>) -> MountedBinaryResult;
}

View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash -o errexit -o nounset -o pipefail
mkdir -p artifacts
cd curl_adapter
cargo update --aggressive
marine build --release
cd ..
cd price_getter_service
cargo update --aggressive
marine build --release
cd ..
cd weighted_mean_service
cargo update --aggressive
marine build --release
cd ..
rm -f artifacts/*.wasm
cp curl_adapter/target/wasm32-wasi/release/curl_adapter.wasm artifacts/
cp price_getter_service/target/wasm32-wasi/release/price_getter_service.wasm artifacts/
cp weighted_mean_service/target/wasm32-wasi/release/weighted_mean_service.wasm artifacts/

View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash -o errexit -o nounset -o pipefail
fldist --node-id 12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS new_service --ms artifacts/curl_adapter.wasm:configs/curl_adapter_cfg.json artifacts/price_getter_service.wasm:configs/price_getter_service_cfg.json --name price-getter-service-0
fldist --node-id 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi new_service --ms artifacts/curl_adapter.wasm:configs/curl_adapter_cfg.json artifacts/price_getter_service.wasm:configs/price_getter_service_cfg.json --name price-getter-service-0
fldist --node-id 12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS new_service --ms artifacts/weighted_mean_service.wasm:configs/weighted_mean_service_cfg.json --name weighted-mean-service-0
fldist --node-id 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi new_service --ms artifacts/weighted_mean_service.wasm:configs/weighted_mean_service_cfg.json --name weighted-mean-service-0

View File

@ -0,0 +1,19 @@
[package]
name = "price_getter_service"
version = "0.1.0"
authors = ["boneyard93501 <4523011+boneyard93501@users.noreply.github.com>"]
edition = "2018"
description = "price-getter-service, a Marine wasi module"
license = "Apache-2.0"
[[bin]]
name = "weighted_mean_service"
path = "src/main.rs"
[dependencies]
marine-rs-sdk = "0.6.14"
log = "0.4.14"
[dev]
[profile.release]
opt-level = "s"

View File

@ -0,0 +1,55 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* 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.
*/
use marine_rs_sdk::{marine, module_manifest};
module_manifest!();
fn main() {}
#[marine]
pub struct Result {
pub result: f64,
pub success: bool,
pub error_msg: String,
}
#[marine]
pub fn weighted_mean(data: Vec<f64>, weights: Vec<u32>) -> Result {
match calc_weighted_mean(data.iter(), weights.iter()) {
Some(res) => Result {
result: res,
success: true,
error_msg: "".to_string(),
},
None => Result {
result: -1f64,
success: false,
error_msg: "Failure to calculate mean. Check your inputs.".to_string(),
},
}
}
fn calc_weighted_mean<'a>(data: impl ExactSizeIterator<Item = &'a f64>, weights: impl ExactSizeIterator<Item = &'a u32>) -> Option<f64> {
let n = data.len();
if n < 1 || n != weights.len() {
return None;
}
let mut weights_sum = 0u32;
let res = data.zip(weights).map(|(&a, &b)| { weights_sum += b; a * b as f64}).sum::<f64>() / weights_sum as f64;
let res = format!("{:.2}", res).parse::<f64>().unwrap();
Some(res)
}