1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! Plasm Lockdrop Oracle module client.
//!
//! Lockdrop Oracle has REST HTTP API:
//!
//! **GET**
//! - /btc/ticker - returns BTC price in USD
//! - /eth/ticker - returns ETH price in USD
//!
//! **POST**
//! - /eth/lock
//!   Body: LockCheck struct
//!   Returns: `OK` when lock success
//!
//! - /btc/lock
//!   Body: LockCheck struct
//!   Returns `OK` when lock success
//!

use codec::{Decode, Encode};
use frame_support::debug;
use sp_core::{ecdsa, H256};
use sp_runtime::{offchain::http::Request, RuntimeDebug};
use sp_std::prelude::*;

/// HTTP source of currency price.
pub trait PriceOracle<T: sp_std::str::FromStr> {
    /// HTTP request URI
    fn uri() -> &'static str;

    /// Fetch price data, parse it and return raw dollar rate.
    /// Note: this method requires off-chain worker context.
    fn fetch() -> Result<T, ()> {
        let uri = Self::uri();
        debug::debug!(
            target: "lockdrop-offchain-worker",
            "Price oracle request to {}", uri
        );

        let request = Request::get(uri).send().map_err(|e| {
            debug::error!(
                target: "lockdrop-offchain-worker",
                "Request error: {:?}", e
            );
        })?;

        let response = request.wait().map_err(|e| {
            debug::error!(
                target: "lockdrop-offchain-worker",
                "Response error: {:?}", e
            );
        })?;

        let body = response.body().collect::<Vec<_>>();
        let price = sp_std::str::from_utf8(&body[..]).map_err(|e| {
            debug::error!(
                target: "lockdrop-offchain-worker",
                "Response body isn't UTF-8 string: {:?}", e
            );
        })?;

        price.parse().map_err(|_| {
            debug::error!(
                target: "lockdrop-offchain-worker",
                "Response body string parsing error"
            );
        })
    }
}

/// BTC price oracle.
pub struct BitcoinPrice;
impl<T: sp_std::str::FromStr> PriceOracle<T> for BitcoinPrice {
    fn uri() -> &'static str {
        "http://127.0.0.1:34347/btc/ticker"
    }
}

/// ETH price oracle.
pub struct EthereumPrice;
impl<T: sp_std::str::FromStr> PriceOracle<T> for EthereumPrice {
    fn uri() -> &'static str {
        "http://127.0.0.1:34347/eth/ticker"
    }
}

/// Lock check request parameters.
#[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode)]
pub struct LockCheck {
    /// Transaction hash.
    pub tx_hash: H256,
    /// Sender public key.
    pub public_key: ecdsa::Public,
    /// Lock duration in seconds.
    pub duration: u64,
    /// Lock value in units.
    pub value: u128,
}

/// HTTP source of blockchain transactions.
pub trait LockOracle {
    /// HTTP request URI
    fn uri() -> &'static str;

    /// Check lock transaction data.
    /// Note: this method requires off-chain worker context.
    fn check(
        tx_hash: H256,
        public_key: ecdsa::Public,
        duration: u64,
        value: u128,
    ) -> Result<bool, ()> {
        let lock = LockCheck {
            tx_hash,
            public_key,
            duration,
            value,
        };
        let request = Request::post(Self::uri(), vec![lock.encode()])
            .send()
            .map_err(|e| {
                debug::error!(
                    target: "lockdrop-offchain-worker",
                    "Request error: {:?}", e
                );
            })?;

        let response = request.wait().map_err(|e| {
            debug::error!(
                target: "lockdrop-offchain-worker",
                "Response error: {:?}", e
            );
        })?;

        Ok(response.code == 200)
    }
}

/// Bitcoin chain transactions oracle.
pub struct BitcoinLock;
impl LockOracle for BitcoinLock {
    fn uri() -> &'static str {
        "http://127.0.0.1:34347/btc/lock"
    }
}

/// Ethereum chain transactions oracle.
pub struct EthereumLock;
impl LockOracle for EthereumLock {
    fn uri() -> &'static str {
        "http://127.0.0.1:34347/eth/lock"
    }
}