├── oracle ├── .gitignore ├── .cargo │ └── config ├── scripts │ ├── test │ ├── reset │ ├── deploy │ ├── new │ └── build ├── README.md ├── Cargo.toml ├── Cargo.lock └── src │ └── lib.rs ├── client ├── scripts │ ├── test │ ├── fund-oracle-client │ └── build ├── .cargo │ └── config ├── Cargo.toml ├── src │ └── lib.rs └── Cargo.lock ├── .gitignore ├── near-link-token ├── .cargo │ └── config ├── scripts │ ├── test │ ├── deploy │ ├── new │ └── build ├── Cargo.toml ├── README.md ├── Cargo.lock └── src │ └── lib.rs ├── assets └── chainlink-diagram.png ├── test ├── remake ├── LICENSE.md └── README.md /oracle/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /client/scripts/test: -------------------------------------------------------------------------------- 1 | cargo test -- --nocapture 2 | -------------------------------------------------------------------------------- /client/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-C", "link-args=-s"] -------------------------------------------------------------------------------- /oracle/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-C", "link-args=-s"] -------------------------------------------------------------------------------- /oracle/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo test -- --nocapture 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.wasm 3 | *.wat 4 | **/node_modules 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /near-link-token/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-C", "link-args=-s"] -------------------------------------------------------------------------------- /near-link-token/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo test -- --nocapture 4 | -------------------------------------------------------------------------------- /oracle/scripts/reset: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | near call oracle.$NEAR_ACCT reset --accountId oracle.$NEAR_ACCT 4 | -------------------------------------------------------------------------------- /oracle/scripts/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | near deploy --accountId oracle.$NEAR_ACCT --wasmFile res/oracle.wasm 4 | -------------------------------------------------------------------------------- /assets/chainlink-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcontractkit/near-protocol-contracts/HEAD/assets/chainlink-diagram.png -------------------------------------------------------------------------------- /near-link-token/scripts/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | near deploy --accountId near-link.$NEAR_ACCT --wasmFile res/near_link_token.wasm 4 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd near-link-token && ./scripts/test && cd .. 4 | cd oracle && ./scripts/test && cd .. 5 | cd client && ./scripts/test && cd .. 6 | -------------------------------------------------------------------------------- /oracle/scripts/new: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | near call oracle.$NEAR_ACCT new '{"link_id": "near-link.'$NEAR_ACCT'", "owner_id": "oracle.'$NEAR_ACCT'"}' --accountId oracle.$NEAR_ACCT 4 | -------------------------------------------------------------------------------- /client/scripts/fund-oracle-client: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | near call near-link.$NEAR_ACCT transfer '{"new_owner_id": "client.'$NEAR_ACCT'", "amount": "50"}' --accountId near-link.$NEAR_ACCT 3 | -------------------------------------------------------------------------------- /near-link-token/scripts/new: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | near call near-link.$NEAR_ACCT new '{"owner_id": "near-link.'$NEAR_ACCT'", "total_supply": "1000000"}' --accountId near-link.$NEAR_ACCT 4 | -------------------------------------------------------------------------------- /near-link-token/scripts/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo build --target wasm32-unknown-unknown --release 4 | mkdir -p ./res 5 | cp target/wasm32-unknown-unknown/release/near_link_token.wasm ./res 6 | -------------------------------------------------------------------------------- /client/scripts/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo build --target wasm32-unknown-unknown --release 4 | mkdir -p ./res 5 | cp target/wasm32-unknown-unknown/release/client.wasm ./res 6 | #wasm-opt -Oz --output ./res/fun_token.wasm ./res/fun_token.wasm 7 | -------------------------------------------------------------------------------- /oracle/scripts/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo build --target wasm32-unknown-unknown --release 4 | mkdir -p ./res 5 | cp target/wasm32-unknown-unknown/release/oracle.wasm ./res 6 | #wasm-opt -Oz --output ./res/status_message_collections.wasm ./res/status_message_collections.wasm 7 | -------------------------------------------------------------------------------- /oracle/README.md: -------------------------------------------------------------------------------- 1 | # Oracle 2 | 3 | The [Chainlink Oracle contract](https://github.com/smartcontractkit/chainlink/blob/develop/evm-contracts/src/v0.6/Oracle.sol) ported to Rust. 4 | 5 | ## Testing 6 | To test run: 7 | ```bash 8 | cargo test --package oracle -- --nocapture 9 | ``` 10 | -------------------------------------------------------------------------------- /remake: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | near delete oracle.$NEAR_ACCT $NEAR_ACCT 4 | near create-account oracle.$NEAR_ACCT --masterAccount $NEAR_ACCT 5 | near delete client.$NEAR_ACCT $NEAR_ACCT 6 | near create-account client.$NEAR_ACCT --masterAccount $NEAR_ACCT 7 | near delete oracle-node.$NEAR_ACCT $NEAR_ACCT 8 | near create-account oracle-node.$NEAR_ACCT --masterAccount $NEAR_ACCT 9 | near delete near-link.$NEAR_ACCT $NEAR_ACCT 10 | near create-account near-link.$NEAR_ACCT --masterAccount $NEAR_ACCT 11 | -------------------------------------------------------------------------------- /oracle/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oracle" 3 | version = "0.1.0" 4 | authors = ["Near Inc "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "1.0.45" 13 | near-sdk = "0.11.0" 14 | borsh = "0.6.1" 15 | wee_alloc = "0.4.5" 16 | base64 = "0.12.1" 17 | 18 | [profile.release] 19 | codegen-units = 1 20 | # Tell `rustc` to optimize for small code size. 21 | opt-level = "z" 22 | lto = true 23 | debug = false 24 | panic = "abort" 25 | 26 | [workspace] 27 | members = [] 28 | -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client" 3 | version = "0.1.0" 4 | authors = ["Near Inc "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "1.0.45" 13 | near-sdk = "0.11.0" 14 | borsh = "0.6.0" 15 | wee_alloc = { version = "0.4.5", default-features = false, features = [] } 16 | base64 = "0.12.1" 17 | 18 | [profile.release] 19 | codegen-units = 1 20 | # Tell `rustc` to optimize for small code size. 21 | opt-level = "z" 22 | lto = true 23 | debug = false 24 | panic = "abort" 25 | 26 | [workspace] 27 | members = [] 28 | -------------------------------------------------------------------------------- /near-link-token/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "near-link-token" 3 | version = "0.2.0" 4 | authors = ["Near Inc "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | serde = { version = "*", features = ["derive"] } 12 | serde_json = "*" 13 | near-sdk = "0.11.0" 14 | borsh = "*" 15 | wee_alloc = { version = "0.4.5", default-features = false, features = [] } 16 | 17 | [profile.release] 18 | codegen-units = 1 19 | # Tell `rustc` to optimize for small code size. 20 | opt-level = "z" 21 | lto = true 22 | debug = false 23 | panic = "abort" 24 | overflow-checks = true 25 | 26 | [workspace] 27 | members = [] 28 | -------------------------------------------------------------------------------- /near-link-token/README.md: -------------------------------------------------------------------------------- 1 | # Fungible token 2 | 3 | Example implementation of a Fungible Token Standard (NEP#21). 4 | 5 | NOTES: 6 | 7 | - The maximum balance value is limited by U128 (2\*\*128 - 1). 8 | - JSON calls should pass U128 as a base-10 string. E.g. "100". 9 | - The contract optimizes the inner trie structure by hashing account IDs. It will prevent some 10 | abuse of deep tries. Shouldn't be an issue, once NEAR clients implement full hashing of keys. 11 | - This contract doesn't optimize the amount of storage, since any account can create unlimited 12 | amount of allowances to other accounts. It's unclear how to address this issue unless, this 13 | contract limits the total number of different allowances possible at the same time. 14 | And even if it limits the total number, it's still possible to transfer small amounts to 15 | multiple accounts. 16 | 17 | ## Building 18 | 19 | To build run: 20 | 21 | ```bash 22 | ./build 23 | ``` 24 | 25 | ## Testing 26 | 27 | To test run: 28 | 29 | ```bash 30 | cargo test --package fungible-token -- --nocapture 31 | ``` 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 SmartContract Chainlink Limited SEZC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/src/lib.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use near_sdk::{env, ext_contract, near_bindgen, AccountId}; 3 | use near_sdk::collections::TreeMap; 4 | use base64::{decode}; 5 | use std::str; 6 | use near_sdk::json_types::U128; 7 | use std::collections::HashMap; 8 | 9 | #[global_allocator] 10 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 11 | const SINGLE_CALL_GAS: u64 = 200_000_000_000_000; 12 | 13 | pub type Base64String = String; 14 | 15 | #[ext_contract(ext_oracle)] 16 | pub trait ExtOracleContract { 17 | fn request(&mut self, payment: U128, spec_id: Base64String, callback_address: AccountId, callback_method: String, nonce: U128, data_version: U128, data: Base64String); 18 | } 19 | 20 | #[near_bindgen] 21 | #[derive(BorshDeserialize, BorshSerialize)] 22 | struct ClientContract { 23 | // Note: for this simple demo we'll store the oracle node in state like this 24 | // There's no reason why client contracts can't call various oracle contracts. 25 | oracle_account: AccountId, 26 | nonce: u128, 27 | received: TreeMap, 28 | } 29 | 30 | impl Default for ClientContract { 31 | fn default() -> Self { 32 | panic!("Oracle client should be initialized before usage") 33 | } 34 | } 35 | 36 | #[near_bindgen] 37 | impl ClientContract { 38 | #[allow(dead_code)] 39 | #[init] 40 | pub fn new(oracle_account: AccountId) -> Self { 41 | Self { 42 | oracle_account, 43 | nonce: 0_u128, 44 | received: TreeMap::new(b"r".to_vec()), 45 | } 46 | } 47 | 48 | /// symbol: Base64-encoded token symbol 49 | #[allow(dead_code)] // This function gets called from the oracle 50 | pub fn get_token_price(&mut self, symbol: String, spec_id: Base64String) -> U128 { 51 | // For the sake of demo, a few hardcoded values 52 | let payment = U128(10); 53 | self.nonce += 1; 54 | let nonce: U128 = self.nonce.into(); 55 | 56 | ext_oracle::request(payment, spec_id, env::current_account_id(), "token_price_callback".to_string(), nonce, U128(1), symbol, &self.oracle_account, 0, SINGLE_CALL_GAS); 57 | U128(self.nonce) 58 | } 59 | 60 | #[allow(dead_code)] // This function gets called from the oracle 61 | pub fn token_price_callback(&mut self, nonce: U128, answer: Base64String) { 62 | let base64_price = match str::from_utf8(answer.as_bytes()) { 63 | Ok(val) => val, 64 | Err(_) => env::panic(b"Invalid UTF-8 sequence provided from oracle contract."), 65 | }; 66 | let decoded_price_vec = decode(base64_price).unwrap(); 67 | let price_readable = match str::from_utf8(decoded_price_vec.as_slice()) { 68 | Ok(val) => val, 69 | Err(_) => env::panic(b"Invalid UTF-8 sequence in Base64 decoded value."), 70 | }; 71 | env::log(format!("Client contract received price: {:?}", price_readable).as_bytes()); 72 | self.received.insert(&nonce.0, &price_readable.to_string()); 73 | } 74 | 75 | // using String instead of U128 because 76 | // the trait `std::cmp::Eq` is not implemented for `near_sdk::json_types::integers::U128` 77 | #[allow(dead_code)] 78 | pub fn get_received_vals(&self, max: U128) -> HashMap { 79 | let mut counter: u128 = 0; 80 | let mut result: HashMap = HashMap::new(); 81 | for answer in self.received.iter() { 82 | if counter == max.0 || counter > self.received.len() as u128 { 83 | break; 84 | } 85 | result.insert(answer.0.to_string(), answer.1); 86 | counter += 1; 87 | } 88 | result 89 | } 90 | 91 | #[allow(dead_code)] 92 | pub fn get_received_val(&self, nonce: U128) -> String { 93 | let nonce_u128: u128 = nonce.into(); 94 | self.received.get(&nonce_u128).unwrap_or("-1".to_string()) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | use near_sdk::{MockedBlockchain, StorageUsage}; 102 | use near_sdk::{testing_env, VMContext}; 103 | use base64::{encode}; 104 | 105 | fn link() -> AccountId { "link_near".to_string() } 106 | 107 | fn alice() -> AccountId { "alice_near".to_string() } 108 | 109 | fn bob() -> AccountId { "bob_near".to_string() } 110 | fn oracle() -> AccountId { "oracle.testnet".to_string() } 111 | 112 | fn get_context(signer_account_id: AccountId, storage_usage: StorageUsage) -> VMContext { 113 | VMContext { 114 | current_account_id: alice(), 115 | signer_account_id, 116 | signer_account_pk: vec![0, 1, 2], 117 | predecessor_account_id: alice(), 118 | input: vec![], 119 | block_index: 0, 120 | block_timestamp: 0, 121 | epoch_height: 0, 122 | account_balance: 0, 123 | account_locked_balance: 0, 124 | storage_usage, 125 | attached_deposit: 0, 126 | prepaid_gas: 10u64.pow(18), 127 | random_seed: vec![0, 1, 2], 128 | is_view: false, 129 | output_data_receivers: vec![], 130 | } 131 | } 132 | 133 | #[test] 134 | fn test_token_price() { 135 | let context = get_context(alice(), 0); 136 | testing_env!(context); 137 | let mut contract = ClientContract::new(oracle() ); 138 | let mut returned_nonce = contract.get_token_price("eyJnZXQiOiJodHRwczovL21pbi1hcGkuY3J5cHRvY29tcGFyZS5jb20vZGF0YS9wcmljZT9mc3ltPUVUSCZ0c3ltcz1VU0QiLCJwYXRoIjoiVVNEIiwidGltZXMiOjEwMH0".to_string(), "dW5pcXVlIHNwZWMgaWQ=".to_string()); 139 | assert_eq!(U128(1), returned_nonce); 140 | returned_nonce = contract.get_token_price("eyJnZXQiOiJodHRwczovL21pbi1hcGkuY3J5cHRvY29tcGFyZS5jb20vZGF0YS9wcmljZT9mc3ltPUVUSCZ0c3ltcz1VU0QiLCJwYXRoIjoiVVNEIiwidGltZXMiOjEwMH0".to_string(), "dW5pcXVlIHNwZWMgaWQ=".to_string()); 141 | assert_eq!(U128(2), returned_nonce); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /near-link-token/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "Inflector" 5 | version = "0.11.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 8 | 9 | [[package]] 10 | name = "autocfg" 11 | version = "1.0.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 14 | 15 | [[package]] 16 | name = "base64" 17 | version = "0.11.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 20 | 21 | [[package]] 22 | name = "block-buffer" 23 | version = "0.7.3" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 26 | dependencies = [ 27 | "block-padding", 28 | "byte-tools", 29 | "byteorder", 30 | "generic-array", 31 | ] 32 | 33 | [[package]] 34 | name = "block-padding" 35 | version = "0.1.5" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 38 | dependencies = [ 39 | "byte-tools", 40 | ] 41 | 42 | [[package]] 43 | name = "borsh" 44 | version = "0.6.1" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "2f9dada4c07fa726bc195503048581e7b1719407f7fbef82741f7b149d3921b3" 47 | dependencies = [ 48 | "borsh-derive", 49 | ] 50 | 51 | [[package]] 52 | name = "borsh-derive" 53 | version = "0.6.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "47c6bed3dd7695230e85bd51b6a4e4e4dc7550c1974a79c11e98a8a055211a61" 56 | dependencies = [ 57 | "borsh-derive-internal", 58 | "borsh-schema-derive-internal", 59 | "syn", 60 | ] 61 | 62 | [[package]] 63 | name = "borsh-derive-internal" 64 | version = "0.6.1" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "d34f80970434cd6524ae676b277d024b87dd93ecdd3f53bf470d61730dc6cb80" 67 | dependencies = [ 68 | "proc-macro2", 69 | "quote", 70 | "syn", 71 | ] 72 | 73 | [[package]] 74 | name = "borsh-schema-derive-internal" 75 | version = "0.6.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "b3b93230d3769ea99ac75a8a7fee2a229defbc56fe8816c9cde8ed78c848aa33" 78 | dependencies = [ 79 | "proc-macro2", 80 | "quote", 81 | "syn", 82 | ] 83 | 84 | [[package]] 85 | name = "bs58" 86 | version = "0.3.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" 89 | 90 | [[package]] 91 | name = "byte-tools" 92 | version = "0.3.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 95 | 96 | [[package]] 97 | name = "byteorder" 98 | version = "1.3.4" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 101 | 102 | [[package]] 103 | name = "cfg-if" 104 | version = "0.1.10" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 107 | 108 | [[package]] 109 | name = "digest" 110 | version = "0.8.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 113 | dependencies = [ 114 | "generic-array", 115 | ] 116 | 117 | [[package]] 118 | name = "fake-simd" 119 | version = "0.1.2" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 122 | 123 | [[package]] 124 | name = "generic-array" 125 | version = "0.12.3" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 128 | dependencies = [ 129 | "typenum", 130 | ] 131 | 132 | [[package]] 133 | name = "indexmap" 134 | version = "1.3.2" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" 137 | dependencies = [ 138 | "autocfg", 139 | ] 140 | 141 | [[package]] 142 | name = "itoa" 143 | version = "0.4.5" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 146 | 147 | [[package]] 148 | name = "keccak" 149 | version = "0.1.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" 152 | 153 | [[package]] 154 | name = "libc" 155 | version = "0.2.69" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" 158 | 159 | [[package]] 160 | name = "memory_units" 161 | version = "0.4.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 164 | 165 | [[package]] 166 | name = "near-link-token" 167 | version = "0.2.0" 168 | dependencies = [ 169 | "borsh", 170 | "near-sdk", 171 | "serde", 172 | "serde_json", 173 | "wee_alloc", 174 | ] 175 | 176 | [[package]] 177 | name = "near-rpc-error-core" 178 | version = "0.1.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "ffa8dbf8437a28ac40fcb85859ab0d0b8385013935b000c7a51ae79631dd74d9" 181 | dependencies = [ 182 | "proc-macro2", 183 | "quote", 184 | "serde", 185 | "serde_json", 186 | "syn", 187 | ] 188 | 189 | [[package]] 190 | name = "near-rpc-error-macro" 191 | version = "0.1.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "0c6111d713e90c7c551dee937f4a06cb9ea2672243455a4454cc7566387ba2d9" 194 | dependencies = [ 195 | "near-rpc-error-core", 196 | "proc-macro2", 197 | "quote", 198 | "serde", 199 | "serde_json", 200 | "syn", 201 | ] 202 | 203 | [[package]] 204 | name = "near-runtime-fees" 205 | version = "0.9.1" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "8f4992274c8acb33fa1246715d3aafbce5688ae82243c779b561f8eaff1bb6f1" 208 | dependencies = [ 209 | "num-rational", 210 | "serde", 211 | ] 212 | 213 | [[package]] 214 | name = "near-sdk" 215 | version = "0.11.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "81319d4d44283f63467e4f02b6209297b10643c7aeb62e2ee41e0c31b43e2375" 218 | dependencies = [ 219 | "base64", 220 | "borsh", 221 | "bs58", 222 | "near-runtime-fees", 223 | "near-sdk-macros", 224 | "near-vm-logic", 225 | "serde", 226 | ] 227 | 228 | [[package]] 229 | name = "near-sdk-core" 230 | version = "0.11.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "1f3767fc2a61e6577f1336e06d6962a6c61fc39299573b8a25696fd09ce96ffb" 233 | dependencies = [ 234 | "Inflector", 235 | "proc-macro2", 236 | "quote", 237 | "syn", 238 | ] 239 | 240 | [[package]] 241 | name = "near-sdk-macros" 242 | version = "0.11.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "27c06b45c56028b0e1241b2196397d449091665f3f08d543415373505df5e05f" 245 | dependencies = [ 246 | "near-sdk-core", 247 | "proc-macro2", 248 | "quote", 249 | "syn", 250 | ] 251 | 252 | [[package]] 253 | name = "near-vm-errors" 254 | version = "0.9.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "386c2c07ef37ae52ad43860ef69c6322bbc1e610ae0c08c1d7f5ff56f4c28e6a" 257 | dependencies = [ 258 | "borsh", 259 | "near-rpc-error-macro", 260 | "serde", 261 | ] 262 | 263 | [[package]] 264 | name = "near-vm-logic" 265 | version = "0.9.1" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "a6da6c80d3428f45248577820bfc943b8261a6f11d6721037e5c3f43484047cd" 268 | dependencies = [ 269 | "base64", 270 | "bs58", 271 | "byteorder", 272 | "near-runtime-fees", 273 | "near-vm-errors", 274 | "serde", 275 | "sha2", 276 | "sha3", 277 | ] 278 | 279 | [[package]] 280 | name = "num-bigint" 281 | version = "0.2.6" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" 284 | dependencies = [ 285 | "autocfg", 286 | "num-integer", 287 | "num-traits", 288 | ] 289 | 290 | [[package]] 291 | name = "num-integer" 292 | version = "0.1.42" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 295 | dependencies = [ 296 | "autocfg", 297 | "num-traits", 298 | ] 299 | 300 | [[package]] 301 | name = "num-rational" 302 | version = "0.2.4" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" 305 | dependencies = [ 306 | "autocfg", 307 | "num-bigint", 308 | "num-integer", 309 | "num-traits", 310 | "serde", 311 | ] 312 | 313 | [[package]] 314 | name = "num-traits" 315 | version = "0.2.11" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 318 | dependencies = [ 319 | "autocfg", 320 | ] 321 | 322 | [[package]] 323 | name = "opaque-debug" 324 | version = "0.2.3" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 327 | 328 | [[package]] 329 | name = "proc-macro2" 330 | version = "1.0.12" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "8872cf6f48eee44265156c111456a700ab3483686b3f96df4cf5481c89157319" 333 | dependencies = [ 334 | "unicode-xid", 335 | ] 336 | 337 | [[package]] 338 | name = "quote" 339 | version = "1.0.4" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "4c1f4b0efa5fc5e8ceb705136bfee52cfdb6a4e3509f770b478cd6ed434232a7" 342 | dependencies = [ 343 | "proc-macro2", 344 | ] 345 | 346 | [[package]] 347 | name = "ryu" 348 | version = "1.0.4" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" 351 | 352 | [[package]] 353 | name = "serde" 354 | version = "1.0.110" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" 357 | dependencies = [ 358 | "serde_derive", 359 | ] 360 | 361 | [[package]] 362 | name = "serde_derive" 363 | version = "1.0.110" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" 366 | dependencies = [ 367 | "proc-macro2", 368 | "quote", 369 | "syn", 370 | ] 371 | 372 | [[package]] 373 | name = "serde_json" 374 | version = "1.0.53" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" 377 | dependencies = [ 378 | "indexmap", 379 | "itoa", 380 | "ryu", 381 | "serde", 382 | ] 383 | 384 | [[package]] 385 | name = "sha2" 386 | version = "0.8.1" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" 389 | dependencies = [ 390 | "block-buffer", 391 | "digest", 392 | "fake-simd", 393 | "opaque-debug", 394 | ] 395 | 396 | [[package]] 397 | name = "sha3" 398 | version = "0.8.2" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" 401 | dependencies = [ 402 | "block-buffer", 403 | "byte-tools", 404 | "digest", 405 | "keccak", 406 | "opaque-debug", 407 | ] 408 | 409 | [[package]] 410 | name = "syn" 411 | version = "1.0.19" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "e8e5aa70697bb26ee62214ae3288465ecec0000f05182f039b477001f08f5ae7" 414 | dependencies = [ 415 | "proc-macro2", 416 | "quote", 417 | "unicode-xid", 418 | ] 419 | 420 | [[package]] 421 | name = "typenum" 422 | version = "1.12.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 425 | 426 | [[package]] 427 | name = "unicode-xid" 428 | version = "0.2.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 431 | 432 | [[package]] 433 | name = "wee_alloc" 434 | version = "0.4.5" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 437 | dependencies = [ 438 | "cfg-if", 439 | "libc", 440 | "memory_units", 441 | "winapi", 442 | ] 443 | 444 | [[package]] 445 | name = "winapi" 446 | version = "0.3.8" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 449 | dependencies = [ 450 | "winapi-i686-pc-windows-gnu", 451 | "winapi-x86_64-pc-windows-gnu", 452 | ] 453 | 454 | [[package]] 455 | name = "winapi-i686-pc-windows-gnu" 456 | version = "0.4.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 459 | 460 | [[package]] 461 | name = "winapi-x86_64-pc-windows-gnu" 462 | version = "0.4.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 465 | -------------------------------------------------------------------------------- /client/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "Inflector" 5 | version = "0.11.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 8 | 9 | [[package]] 10 | name = "autocfg" 11 | version = "1.0.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 14 | 15 | [[package]] 16 | name = "base64" 17 | version = "0.11.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 20 | 21 | [[package]] 22 | name = "base64" 23 | version = "0.12.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" 26 | 27 | [[package]] 28 | name = "block-buffer" 29 | version = "0.7.3" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 32 | dependencies = [ 33 | "block-padding", 34 | "byte-tools", 35 | "byteorder", 36 | "generic-array", 37 | ] 38 | 39 | [[package]] 40 | name = "block-padding" 41 | version = "0.1.5" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 44 | dependencies = [ 45 | "byte-tools", 46 | ] 47 | 48 | [[package]] 49 | name = "borsh" 50 | version = "0.6.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "2f9dada4c07fa726bc195503048581e7b1719407f7fbef82741f7b149d3921b3" 53 | dependencies = [ 54 | "borsh-derive", 55 | ] 56 | 57 | [[package]] 58 | name = "borsh-derive" 59 | version = "0.6.1" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "47c6bed3dd7695230e85bd51b6a4e4e4dc7550c1974a79c11e98a8a055211a61" 62 | dependencies = [ 63 | "borsh-derive-internal", 64 | "borsh-schema-derive-internal", 65 | "syn", 66 | ] 67 | 68 | [[package]] 69 | name = "borsh-derive-internal" 70 | version = "0.6.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "d34f80970434cd6524ae676b277d024b87dd93ecdd3f53bf470d61730dc6cb80" 73 | dependencies = [ 74 | "proc-macro2", 75 | "quote", 76 | "syn", 77 | ] 78 | 79 | [[package]] 80 | name = "borsh-schema-derive-internal" 81 | version = "0.6.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "b3b93230d3769ea99ac75a8a7fee2a229defbc56fe8816c9cde8ed78c848aa33" 84 | dependencies = [ 85 | "proc-macro2", 86 | "quote", 87 | "syn", 88 | ] 89 | 90 | [[package]] 91 | name = "bs58" 92 | version = "0.3.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" 95 | 96 | [[package]] 97 | name = "byte-tools" 98 | version = "0.3.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 101 | 102 | [[package]] 103 | name = "byteorder" 104 | version = "1.3.4" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 107 | 108 | [[package]] 109 | name = "cfg-if" 110 | version = "0.1.10" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 113 | 114 | [[package]] 115 | name = "client" 116 | version = "0.1.0" 117 | dependencies = [ 118 | "base64 0.12.1", 119 | "borsh", 120 | "near-sdk", 121 | "serde", 122 | "serde_json", 123 | "wee_alloc", 124 | ] 125 | 126 | [[package]] 127 | name = "digest" 128 | version = "0.8.1" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 131 | dependencies = [ 132 | "generic-array", 133 | ] 134 | 135 | [[package]] 136 | name = "fake-simd" 137 | version = "0.1.2" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 140 | 141 | [[package]] 142 | name = "generic-array" 143 | version = "0.12.3" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 146 | dependencies = [ 147 | "typenum", 148 | ] 149 | 150 | [[package]] 151 | name = "indexmap" 152 | version = "1.3.2" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" 155 | dependencies = [ 156 | "autocfg", 157 | ] 158 | 159 | [[package]] 160 | name = "itoa" 161 | version = "0.4.5" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 164 | 165 | [[package]] 166 | name = "keccak" 167 | version = "0.1.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" 170 | 171 | [[package]] 172 | name = "libc" 173 | version = "0.2.70" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" 176 | 177 | [[package]] 178 | name = "memory_units" 179 | version = "0.4.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 182 | 183 | [[package]] 184 | name = "near-rpc-error-core" 185 | version = "0.1.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "ffa8dbf8437a28ac40fcb85859ab0d0b8385013935b000c7a51ae79631dd74d9" 188 | dependencies = [ 189 | "proc-macro2", 190 | "quote", 191 | "serde", 192 | "serde_json", 193 | "syn", 194 | ] 195 | 196 | [[package]] 197 | name = "near-rpc-error-macro" 198 | version = "0.1.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "0c6111d713e90c7c551dee937f4a06cb9ea2672243455a4454cc7566387ba2d9" 201 | dependencies = [ 202 | "near-rpc-error-core", 203 | "proc-macro2", 204 | "quote", 205 | "serde", 206 | "serde_json", 207 | "syn", 208 | ] 209 | 210 | [[package]] 211 | name = "near-runtime-fees" 212 | version = "0.9.1" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "8f4992274c8acb33fa1246715d3aafbce5688ae82243c779b561f8eaff1bb6f1" 215 | dependencies = [ 216 | "num-rational", 217 | "serde", 218 | ] 219 | 220 | [[package]] 221 | name = "near-sdk" 222 | version = "0.11.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "81319d4d44283f63467e4f02b6209297b10643c7aeb62e2ee41e0c31b43e2375" 225 | dependencies = [ 226 | "base64 0.11.0", 227 | "borsh", 228 | "bs58", 229 | "near-runtime-fees", 230 | "near-sdk-macros", 231 | "near-vm-logic", 232 | "serde", 233 | ] 234 | 235 | [[package]] 236 | name = "near-sdk-core" 237 | version = "0.11.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "1f3767fc2a61e6577f1336e06d6962a6c61fc39299573b8a25696fd09ce96ffb" 240 | dependencies = [ 241 | "Inflector", 242 | "proc-macro2", 243 | "quote", 244 | "syn", 245 | ] 246 | 247 | [[package]] 248 | name = "near-sdk-macros" 249 | version = "0.11.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "27c06b45c56028b0e1241b2196397d449091665f3f08d543415373505df5e05f" 252 | dependencies = [ 253 | "near-sdk-core", 254 | "proc-macro2", 255 | "quote", 256 | "syn", 257 | ] 258 | 259 | [[package]] 260 | name = "near-vm-errors" 261 | version = "0.9.1" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "386c2c07ef37ae52ad43860ef69c6322bbc1e610ae0c08c1d7f5ff56f4c28e6a" 264 | dependencies = [ 265 | "borsh", 266 | "near-rpc-error-macro", 267 | "serde", 268 | ] 269 | 270 | [[package]] 271 | name = "near-vm-logic" 272 | version = "0.9.1" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "a6da6c80d3428f45248577820bfc943b8261a6f11d6721037e5c3f43484047cd" 275 | dependencies = [ 276 | "base64 0.11.0", 277 | "bs58", 278 | "byteorder", 279 | "near-runtime-fees", 280 | "near-vm-errors", 281 | "serde", 282 | "sha2", 283 | "sha3", 284 | ] 285 | 286 | [[package]] 287 | name = "num-bigint" 288 | version = "0.2.6" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" 291 | dependencies = [ 292 | "autocfg", 293 | "num-integer", 294 | "num-traits", 295 | ] 296 | 297 | [[package]] 298 | name = "num-integer" 299 | version = "0.1.43" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 302 | dependencies = [ 303 | "autocfg", 304 | "num-traits", 305 | ] 306 | 307 | [[package]] 308 | name = "num-rational" 309 | version = "0.2.4" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" 312 | dependencies = [ 313 | "autocfg", 314 | "num-bigint", 315 | "num-integer", 316 | "num-traits", 317 | "serde", 318 | ] 319 | 320 | [[package]] 321 | name = "num-traits" 322 | version = "0.2.12" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 325 | dependencies = [ 326 | "autocfg", 327 | ] 328 | 329 | [[package]] 330 | name = "opaque-debug" 331 | version = "0.2.3" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 334 | 335 | [[package]] 336 | name = "proc-macro2" 337 | version = "1.0.12" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "8872cf6f48eee44265156c111456a700ab3483686b3f96df4cf5481c89157319" 340 | dependencies = [ 341 | "unicode-xid", 342 | ] 343 | 344 | [[package]] 345 | name = "quote" 346 | version = "1.0.4" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "4c1f4b0efa5fc5e8ceb705136bfee52cfdb6a4e3509f770b478cd6ed434232a7" 349 | dependencies = [ 350 | "proc-macro2", 351 | ] 352 | 353 | [[package]] 354 | name = "ryu" 355 | version = "1.0.4" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" 358 | 359 | [[package]] 360 | name = "serde" 361 | version = "1.0.110" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" 364 | dependencies = [ 365 | "serde_derive", 366 | ] 367 | 368 | [[package]] 369 | name = "serde_derive" 370 | version = "1.0.110" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" 373 | dependencies = [ 374 | "proc-macro2", 375 | "quote", 376 | "syn", 377 | ] 378 | 379 | [[package]] 380 | name = "serde_json" 381 | version = "1.0.53" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" 384 | dependencies = [ 385 | "indexmap", 386 | "itoa", 387 | "ryu", 388 | "serde", 389 | ] 390 | 391 | [[package]] 392 | name = "sha2" 393 | version = "0.8.1" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" 396 | dependencies = [ 397 | "block-buffer", 398 | "digest", 399 | "fake-simd", 400 | "opaque-debug", 401 | ] 402 | 403 | [[package]] 404 | name = "sha3" 405 | version = "0.8.2" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" 408 | dependencies = [ 409 | "block-buffer", 410 | "byte-tools", 411 | "digest", 412 | "keccak", 413 | "opaque-debug", 414 | ] 415 | 416 | [[package]] 417 | name = "syn" 418 | version = "1.0.20" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "dd1b5e337360b1fae433c59fcafa0c6b77c605e92540afa5221a7b81a9eca91d" 421 | dependencies = [ 422 | "proc-macro2", 423 | "quote", 424 | "unicode-xid", 425 | ] 426 | 427 | [[package]] 428 | name = "typenum" 429 | version = "1.12.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 432 | 433 | [[package]] 434 | name = "unicode-xid" 435 | version = "0.2.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 438 | 439 | [[package]] 440 | name = "wee_alloc" 441 | version = "0.4.5" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 444 | dependencies = [ 445 | "cfg-if", 446 | "libc", 447 | "memory_units", 448 | "winapi", 449 | ] 450 | 451 | [[package]] 452 | name = "winapi" 453 | version = "0.3.8" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 456 | dependencies = [ 457 | "winapi-i686-pc-windows-gnu", 458 | "winapi-x86_64-pc-windows-gnu", 459 | ] 460 | 461 | [[package]] 462 | name = "winapi-i686-pc-windows-gnu" 463 | version = "0.4.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 466 | 467 | [[package]] 468 | name = "winapi-x86_64-pc-windows-gnu" 469 | version = "0.4.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 472 | -------------------------------------------------------------------------------- /oracle/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "Inflector" 5 | version = "0.11.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 8 | 9 | [[package]] 10 | name = "autocfg" 11 | version = "1.0.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 14 | 15 | [[package]] 16 | name = "base64" 17 | version = "0.11.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 20 | 21 | [[package]] 22 | name = "base64" 23 | version = "0.12.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" 26 | 27 | [[package]] 28 | name = "block-buffer" 29 | version = "0.7.3" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 32 | dependencies = [ 33 | "block-padding", 34 | "byte-tools", 35 | "byteorder", 36 | "generic-array", 37 | ] 38 | 39 | [[package]] 40 | name = "block-padding" 41 | version = "0.1.5" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 44 | dependencies = [ 45 | "byte-tools", 46 | ] 47 | 48 | [[package]] 49 | name = "borsh" 50 | version = "0.6.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "2f9dada4c07fa726bc195503048581e7b1719407f7fbef82741f7b149d3921b3" 53 | dependencies = [ 54 | "borsh-derive", 55 | ] 56 | 57 | [[package]] 58 | name = "borsh-derive" 59 | version = "0.6.1" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "47c6bed3dd7695230e85bd51b6a4e4e4dc7550c1974a79c11e98a8a055211a61" 62 | dependencies = [ 63 | "borsh-derive-internal", 64 | "borsh-schema-derive-internal", 65 | "syn", 66 | ] 67 | 68 | [[package]] 69 | name = "borsh-derive-internal" 70 | version = "0.6.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "d34f80970434cd6524ae676b277d024b87dd93ecdd3f53bf470d61730dc6cb80" 73 | dependencies = [ 74 | "proc-macro2", 75 | "quote", 76 | "syn", 77 | ] 78 | 79 | [[package]] 80 | name = "borsh-schema-derive-internal" 81 | version = "0.6.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "b3b93230d3769ea99ac75a8a7fee2a229defbc56fe8816c9cde8ed78c848aa33" 84 | dependencies = [ 85 | "proc-macro2", 86 | "quote", 87 | "syn", 88 | ] 89 | 90 | [[package]] 91 | name = "bs58" 92 | version = "0.3.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "b170cd256a3f9fa6b9edae3e44a7dfdfc77e8124dbc3e2612d75f9c3e2396dae" 95 | 96 | [[package]] 97 | name = "byte-tools" 98 | version = "0.3.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 101 | 102 | [[package]] 103 | name = "byteorder" 104 | version = "1.3.4" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 107 | 108 | [[package]] 109 | name = "cfg-if" 110 | version = "0.1.10" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 113 | 114 | [[package]] 115 | name = "digest" 116 | version = "0.8.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 119 | dependencies = [ 120 | "generic-array", 121 | ] 122 | 123 | [[package]] 124 | name = "fake-simd" 125 | version = "0.1.2" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 128 | 129 | [[package]] 130 | name = "generic-array" 131 | version = "0.12.3" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 134 | dependencies = [ 135 | "typenum", 136 | ] 137 | 138 | [[package]] 139 | name = "indexmap" 140 | version = "1.3.2" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" 143 | dependencies = [ 144 | "autocfg", 145 | ] 146 | 147 | [[package]] 148 | name = "itoa" 149 | version = "0.4.5" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 152 | 153 | [[package]] 154 | name = "keccak" 155 | version = "0.1.0" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" 158 | 159 | [[package]] 160 | name = "libc" 161 | version = "0.2.68" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" 164 | 165 | [[package]] 166 | name = "memory_units" 167 | version = "0.4.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 170 | 171 | [[package]] 172 | name = "near-rpc-error-core" 173 | version = "0.1.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "ffa8dbf8437a28ac40fcb85859ab0d0b8385013935b000c7a51ae79631dd74d9" 176 | dependencies = [ 177 | "proc-macro2", 178 | "quote", 179 | "serde", 180 | "serde_json", 181 | "syn", 182 | ] 183 | 184 | [[package]] 185 | name = "near-rpc-error-macro" 186 | version = "0.1.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "0c6111d713e90c7c551dee937f4a06cb9ea2672243455a4454cc7566387ba2d9" 189 | dependencies = [ 190 | "near-rpc-error-core", 191 | "proc-macro2", 192 | "quote", 193 | "serde", 194 | "serde_json", 195 | "syn", 196 | ] 197 | 198 | [[package]] 199 | name = "near-runtime-fees" 200 | version = "0.9.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "8f4992274c8acb33fa1246715d3aafbce5688ae82243c779b561f8eaff1bb6f1" 203 | dependencies = [ 204 | "num-rational", 205 | "serde", 206 | ] 207 | 208 | [[package]] 209 | name = "near-sdk" 210 | version = "0.11.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "81319d4d44283f63467e4f02b6209297b10643c7aeb62e2ee41e0c31b43e2375" 213 | dependencies = [ 214 | "base64 0.11.0", 215 | "borsh", 216 | "bs58", 217 | "near-runtime-fees", 218 | "near-sdk-macros", 219 | "near-vm-logic", 220 | "serde", 221 | ] 222 | 223 | [[package]] 224 | name = "near-sdk-core" 225 | version = "0.11.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "1f3767fc2a61e6577f1336e06d6962a6c61fc39299573b8a25696fd09ce96ffb" 228 | dependencies = [ 229 | "Inflector", 230 | "proc-macro2", 231 | "quote", 232 | "syn", 233 | ] 234 | 235 | [[package]] 236 | name = "near-sdk-macros" 237 | version = "0.11.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "27c06b45c56028b0e1241b2196397d449091665f3f08d543415373505df5e05f" 240 | dependencies = [ 241 | "near-sdk-core", 242 | "proc-macro2", 243 | "quote", 244 | "syn", 245 | ] 246 | 247 | [[package]] 248 | name = "near-vm-errors" 249 | version = "0.9.1" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "386c2c07ef37ae52ad43860ef69c6322bbc1e610ae0c08c1d7f5ff56f4c28e6a" 252 | dependencies = [ 253 | "borsh", 254 | "near-rpc-error-macro", 255 | "serde", 256 | ] 257 | 258 | [[package]] 259 | name = "near-vm-logic" 260 | version = "0.9.1" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "a6da6c80d3428f45248577820bfc943b8261a6f11d6721037e5c3f43484047cd" 263 | dependencies = [ 264 | "base64 0.11.0", 265 | "bs58", 266 | "byteorder", 267 | "near-runtime-fees", 268 | "near-vm-errors", 269 | "serde", 270 | "sha2", 271 | "sha3", 272 | ] 273 | 274 | [[package]] 275 | name = "num-bigint" 276 | version = "0.2.6" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" 279 | dependencies = [ 280 | "autocfg", 281 | "num-integer", 282 | "num-traits", 283 | ] 284 | 285 | [[package]] 286 | name = "num-integer" 287 | version = "0.1.42" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 290 | dependencies = [ 291 | "autocfg", 292 | "num-traits", 293 | ] 294 | 295 | [[package]] 296 | name = "num-rational" 297 | version = "0.2.4" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" 300 | dependencies = [ 301 | "autocfg", 302 | "num-bigint", 303 | "num-integer", 304 | "num-traits", 305 | "serde", 306 | ] 307 | 308 | [[package]] 309 | name = "num-traits" 310 | version = "0.2.11" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 313 | dependencies = [ 314 | "autocfg", 315 | ] 316 | 317 | [[package]] 318 | name = "opaque-debug" 319 | version = "0.2.3" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 322 | 323 | [[package]] 324 | name = "oracle" 325 | version = "0.1.0" 326 | dependencies = [ 327 | "base64 0.12.1", 328 | "borsh", 329 | "near-sdk", 330 | "serde", 331 | "serde_json", 332 | "wee_alloc", 333 | ] 334 | 335 | [[package]] 336 | name = "proc-macro2" 337 | version = "1.0.10" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" 340 | dependencies = [ 341 | "unicode-xid", 342 | ] 343 | 344 | [[package]] 345 | name = "quote" 346 | version = "1.0.3" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" 349 | dependencies = [ 350 | "proc-macro2", 351 | ] 352 | 353 | [[package]] 354 | name = "ryu" 355 | version = "1.0.3" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" 358 | 359 | [[package]] 360 | name = "serde" 361 | version = "1.0.106" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" 364 | dependencies = [ 365 | "serde_derive", 366 | ] 367 | 368 | [[package]] 369 | name = "serde_derive" 370 | version = "1.0.106" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" 373 | dependencies = [ 374 | "proc-macro2", 375 | "quote", 376 | "syn", 377 | ] 378 | 379 | [[package]] 380 | name = "serde_json" 381 | version = "1.0.51" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" 384 | dependencies = [ 385 | "indexmap", 386 | "itoa", 387 | "ryu", 388 | "serde", 389 | ] 390 | 391 | [[package]] 392 | name = "sha2" 393 | version = "0.8.1" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" 396 | dependencies = [ 397 | "block-buffer", 398 | "digest", 399 | "fake-simd", 400 | "opaque-debug", 401 | ] 402 | 403 | [[package]] 404 | name = "sha3" 405 | version = "0.8.2" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" 408 | dependencies = [ 409 | "block-buffer", 410 | "byte-tools", 411 | "digest", 412 | "keccak", 413 | "opaque-debug", 414 | ] 415 | 416 | [[package]] 417 | name = "syn" 418 | version = "1.0.17" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" 421 | dependencies = [ 422 | "proc-macro2", 423 | "quote", 424 | "unicode-xid", 425 | ] 426 | 427 | [[package]] 428 | name = "typenum" 429 | version = "1.11.2" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" 432 | 433 | [[package]] 434 | name = "unicode-xid" 435 | version = "0.2.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 438 | 439 | [[package]] 440 | name = "wee_alloc" 441 | version = "0.4.5" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 444 | dependencies = [ 445 | "cfg-if", 446 | "libc", 447 | "memory_units", 448 | "winapi", 449 | ] 450 | 451 | [[package]] 452 | name = "winapi" 453 | version = "0.3.8" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 456 | dependencies = [ 457 | "winapi-i686-pc-windows-gnu", 458 | "winapi-x86_64-pc-windows-gnu", 459 | ] 460 | 461 | [[package]] 462 | name = "winapi-i686-pc-windows-gnu" 463 | version = "0.4.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 466 | 467 | [[package]] 468 | name = "winapi-x86_64-pc-windows-gnu" 469 | version = "0.4.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 472 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Integrating NEAR and Chainlink 2 | 3 | This repository serves to demonstrate how a smart contract on NEAR can access off-chain data using an incentivized oracle solution with fungible tokens as payments. This repository is in continual development and tracking issues [here](https://github.com/smartcontractkit/near-protocol-contracts/issues). 4 | 5 | There are a number of subdirectories in the project that represent the moving pieces of a simple oracle system. 6 | 7 | - Client Contract (The contract that wants a token price from an off-chain API) 8 | - Oracle Contract (An on-chain smart contract that accepts a fungible token payment and stores a request to be processed off-chain) 9 | - Oracle Node (An off-chain machine continuously polling the Oracle Contract on NEAR, and fulfilling requests) 10 | - **Note**: code for the Oracle Node is not included in this repository, but one can use an oracle protocol like Chainlink 11 | - Fungible Token (The token paid by the Client Contract to the Oracle Contract in exchange for getting an answer to the Client's request) 12 | 13 | ![Chainlink and NEAR diagram](assets/chainlink-diagram.png) 14 | 15 | ## Get NEAR-CLI, Rust, and set up testnet accounts 16 | 17 | We'll be using [NEAR CLI](https://docs.near.org/docs/development/near-cli), a command line tool that makes things simpler. Please have [NodeJS version 12 or greater](https://nodejs.org/en/download/package-manager/). Then install globally with: 18 | 19 | ```bash 20 | npm install -g near-cli 21 | ``` 22 | 23 | These smart contracts are written in Rust. Please follow these directions to get Rust going on your local machine. 24 | 25 | Install Rustup: 26 | 27 | ```bash 28 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 29 | ``` 30 | 31 | ([Official documentation](https://www.rust-lang.org/tools/install)) 32 | 33 | Follow the directions which includes running: 34 | 35 | ```bash 36 | source $HOME/.cargo/env 37 | ``` 38 | 39 | Add wasm target to your toolchain: 40 | 41 | ```bash 42 | rustup target add wasm32-unknown-unknown 43 | ``` 44 | 45 | ([Info on wasm32-unknown-unknown](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/webassembly-support.html)) 46 | 47 | Rust is now ready on your machine. 48 | 49 | Next, create a NEAR testnet account with [Wallet](https://wallet.testnet.near.org). 50 | 51 | Set an environment variable to use in these examples. For instance, if your test account is `oracle.testnet` set it like so in your terminal: 52 | 53 | ```bash 54 | export NEAR_ACCT=oracle.testnet 55 | ``` 56 | 57 | (**Windows users**: please look into using `set` instead of `export`, surrounding the environment variable in `%` instead of beginning with `$`, and using escaped double-quotes `\"` where necessary instead of the single-quotes provided in these instructions.) 58 | 59 | Create sub-accounts:: 60 | 61 | ```bash 62 | near create-account oracle.$NEAR_ACCT --masterAccount $NEAR_ACCT 63 | near create-account client.$NEAR_ACCT --masterAccount $NEAR_ACCT 64 | near create-account oracle-node.$NEAR_ACCT --masterAccount $NEAR_ACCT 65 | near create-account near-link.$NEAR_ACCT --masterAccount $NEAR_ACCT 66 | ``` 67 | 68 | We've gone over the different roles earlier, but let's focus on what will happen to get a request fulfilled. 69 | 70 | **Client Contract** will call the **Oracle Contract** to make a request for external data. 71 | **Client Contract** gives the **Oracle Contract** an allowance to take NEAR LINK from it. Before officially adding the request, it will `transfer_from` to capture the payment, keeping track of this amount in the `withdrawable_token` state variable. 72 | The **Oracle Node** will be continuously polling the state of its **Oracle Contract** using the paginated `get_requests` function. 73 | The **Oracle Node** will get the API results needed, and send back the answer to the **Oracle Contract**. 74 | The **Oracle Contract** makes a cross-contract call to the callback address (NEAR account) at the callback method provided. It has now fulfilled the request and removes it from state. 75 | 76 | ## Build, deploy, and initialize 77 | 78 | Let's begin! 79 | 80 | Build the oracle, client, and NEAR LINK contracts with: 81 | 82 | ```bash 83 | ./build 84 | ``` 85 | 86 | Run all tests: 87 | 88 | ```bash 89 | ./test 90 | ``` 91 | 92 | Then deploy and instantiate like so… 93 | 94 | NEAR LINK 95 | 96 | ```bash 97 | near deploy --accountId near-link.$NEAR_ACCT --wasmFile near-link-token/res/near_link_token.wasm --initFunction new --initArgs '{"owner_id": "near-link.'$NEAR_ACCT'", "total_supply": "1000000"}' 98 | ``` 99 | 100 | Oracle contract 101 | 102 | ```bash 103 | near deploy --accountId oracle.$NEAR_ACCT --wasmFile oracle/res/oracle.wasm --initFunction new --initArgs '{"link_id": "near-link.'$NEAR_ACCT'", "owner_id": "oracle.'$NEAR_ACCT'"}' 104 | ``` 105 | 106 | Client contract 107 | 108 | ```bash 109 | near deploy --accountId client.$NEAR_ACCT --wasmFile client/res/client.wasm --initFunction new --initArgs '{"oracle_account": "oracle.'$NEAR_ACCT'"}' 110 | ``` 111 | 112 | ## Minor housekeeping 113 | 114 | Before the **oracle node** can fulfill the request, they must be authorized. We might as well do this from the get-go. 115 | 116 | ```bash 117 | near call oracle.$NEAR_ACCT add_authorization '{"node": "oracle-node.'$NEAR_ACCT'"}' --accountId oracle.$NEAR_ACCT 118 | ``` 119 | 120 | (Optional) Check authorization to confirm: 121 | 122 | ```bash 123 | near view oracle.$NEAR_ACCT is_authorized '{"node": "oracle-node.'$NEAR_ACCT'"}' 124 | ``` 125 | 126 | ## Give fungible tokens and set allowances 127 | 128 | Give 50 NEAR LINK to client: 129 | 130 | ```bash 131 | near call near-link.$NEAR_ACCT transfer '{"new_owner_id": "client.'$NEAR_ACCT'", "amount": "50"}' --accountId near-link.$NEAR_ACCT --amount .0365 132 | ``` 133 | 134 | **Note**: above, we use the `amount` flag in order to pay for the state required. (See more about [state staking here](https://docs.near.org/docs/concepts/storage)) 135 | 136 | (Optional) Check balance to confirm: 137 | 138 | ```bash 139 | near view near-link.$NEAR_ACCT get_balance '{"owner_id": "client.'$NEAR_ACCT'"}' 140 | ``` 141 | 142 | **client contract** gives **oracle contract** allowance to spend 20 NEAR LINK on their behalf: 143 | 144 | ```bash 145 | near call near-link.$NEAR_ACCT inc_allowance '{"escrow_account_id": "oracle.'$NEAR_ACCT'", "amount": "20"}' --accountId client.$NEAR_ACCT --amount .0696 146 | ``` 147 | 148 | (Optional) Check allowance to confirm: 149 | 150 | ```bash 151 | near view near-link.$NEAR_ACCT get_allowance '{"owner_id": "client.'$NEAR_ACCT'", "escrow_account_id": "oracle.'$NEAR_ACCT'"}' 152 | ``` 153 | 154 | ## Make a request 155 | 156 | Let's make a request to a Chainlink node and request an ETH-USD price: 157 | 158 | - Packed JSON arguments: `{"get":"https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD","path":"USD","times":100}` 159 | - Base64 encoded arguments: `eyJnZXQiOiJodHRwczovL21pbi1hcGkuY3J5cHRvY29tcGFyZS5jb20vZGF0YS9wcmljZT9mc3ltPUVUSCZ0c3ltcz1VU0QiLCJwYXRoIjoiVVNEIiwidGltZXMiOjEwMH0=` 160 | 161 | We'll show two ways to have the client contract send the oracle contract a request. First, we'll directly call the oracle contract using the key pair (i.e. keys) from the client contract. 162 | 163 | 1. **Client contract** makes a direct request to **oracle contract** with payment of 10 NEAR LINK. We can do this because we have the key pair for the client contract. 164 | 165 | ```bash 166 | near call oracle.$NEAR_ACCT request '{"payment": "10", "spec_id": "dW5pcXVlIHNwZWMgaWQ=", "callback_address": "client.'$NEAR_ACCT'", "callback_method": "token_price_callback", "nonce": "1", "data_version": "1", "data": "eyJnZXQiOiJodHRwczovL21pbi1hcGkuY3J5cHRvY29tcGFyZS5jb20vZGF0YS9wcmljZT9mc3ltPUVUSCZ0c3ltcz1VU0QiLCJwYXRoIjoiVVNEIiwidGltZXMiOjEwMH0="}' --accountId client.$NEAR_ACCT --gas 300000000000000 167 | ``` 168 | 169 | 2. **Any NEAR account** calls the **client contract**, providing request arguments. Upon receiving this, the **client contract** sends a cross-contract call to the **oracle contract** to store the request. (Payment and other values are hardcoded here, the nonce is automatically incremented. This assumes that the **client contract** contract only wants to use one oracle contract.) 170 | 171 | ```bash 172 | near call client.$NEAR_ACCT get_token_price '{"symbol": "eyJnZXQiOiJodHRwczovL21pbi1hcGkuY3J5cHRvY29tcGFyZS5jb20vZGF0YS9wcmljZT9mc3ltPUVUSCZ0c3ltcz1VU0QiLCJwYXRoIjoiVVNEIiwidGltZXMiOjEwMH0=", "spec_id": "dW5pcXVlIHNwZWMgaWQ="}' --accountId client.$NEAR_ACCT --gas 300000000000000 173 | ``` 174 | 175 | ## View pending requests 176 | 177 | The oracle node is continually polling the state of the **oracle contract** to see the paginated request _summary_. This shows which accounts have requests pending and the total amount of pending requests: 178 | 179 | ```bash 180 | near view oracle.$NEAR_ACCT get_requests_summary '{"max_num_accounts": "10"}' 181 | ``` 182 | 183 | **Note**: aside from `get_requests_summary` there is also `get_requests_summary_from`. Since the [`TreeMap` data structure](https://docs.rs/near-sdk/1.0.0/near_sdk/collections/struct.TreeMap.html) is ordered, the former will list the first N (`max_num_accounts`). Usage of `get_requests_summary_from` is for paging, providing a window of results to return. Please see function details for parameters and usage. 184 | 185 | For folks who prefer to see a more low-level approach to hitting the RPC, here's the [curl](https://en.wikipedia.org/wiki/CURL) command performing the same query: 186 | 187 | ```bash 188 | curl -d '{"jsonrpc": "2.0", "method": "query", "id": "chainlink", "params": {"request_type": "call_function", "finality": "final", "account_id": "oracle.'$NEAR_ACCT'", "method_name": "get_requests_summary", "args_base64": "eyJtYXhfbnVtX2FjY291bnRzIjogIjEwIn0="}}' -H 'Content-Type: application/json' https://rpc.testnet.near.org 189 | ``` 190 | 191 | The above will return something like: 192 | 193 | ```json 194 | { 195 | "jsonrpc": "2.0", 196 | "result": { 197 | "result": [ 198 | 91, 199 | 123, 200 | 34, 201 | 97, 202 | 99, 203 | 99, 204 | 111, 205 | 117, 206 | 110, 207 | 116, 208 | 34, 209 | 58, 210 | 34, 211 | 99, 212 | 108, 213 | 105, 214 | 101, 215 | 110, 216 | 116, 217 | 46, 218 | 100, 219 | 101, 220 | 109, 221 | 111, 222 | 46, 223 | 116, 224 | 101, 225 | 115, 226 | 116, 227 | 110, 228 | 101, 229 | 116, 230 | 34, 231 | 44, 232 | 34, 233 | 116, 234 | 111, 235 | 116, 236 | 97, 237 | 108, 238 | 95, 239 | 114, 240 | 101, 241 | 113, 242 | 117, 243 | 101, 244 | 115, 245 | 116, 246 | 115, 247 | 34, 248 | 58, 249 | 49, 250 | 125, 251 | 93 252 | ], 253 | "logs": [], 254 | "block_height": 10551293, 255 | "block_hash": "Ljh67tYk5bGXPu9TamJNG4vHp18cEBDxebKHpEUeZEo" 256 | }, 257 | "id": "chainlink" 258 | } 259 | ``` 260 | 261 | We'll outline a quick way to see the results if the machine has [Python installed](https://docs.python-guide.org/starting/install3/osx/). Copy the value of the innermost `result` key, which is an array of unsigned 8-bit integers. 262 | 263 | Open the Python REPL with the command `python` and see the prompt with `>>>`. 264 | 265 | Enter the below replacing BYTE_ARRAY with the the innermost result value (including the square brackets): 266 | 267 | ```python 268 | res = BYTE_ARRAY 269 | ``` 270 | 271 | then: 272 | 273 | ```python 274 | ''.join(chr(x) for x in res) 275 | ``` 276 | 277 | and python will print something like: 278 | 279 | ```text 280 | '[{"account":"client.demo.testnet","total_requests":1}]' 281 | ``` 282 | 283 | The previous command (calling the method `get_requests_summary`) is useful if there has been significant scaling from many client accounts/contracts. To see the individual requests for a particular user, use the following command: 284 | 285 | ```bash 286 | near view oracle.$NEAR_ACCT get_requests '{"account": "client.'$NEAR_ACCT'", "max_requests": "10"}' 287 | ``` 288 | 289 | The **oracle node** uses the passed request arguments to fetch the price of (for example) Basic Attention Token (BAT) and finds it is at \$0.19 per token. 290 | The data `0.19` as a `Vec` is `MTkuMQ==` 291 | 292 | There's a third method to get all the requests, ordered by account name and nonce, where a specified maximum number of results is provided. 293 | 294 | ```bash 295 | near view oracle.$NEAR_ACCT get_all_requests '{"max_num_accounts": "100", "max_requests": "100"}' 296 | ``` 297 | 298 | ## Fulfill the request 299 | 300 | **Oracle Node** uses its NEAR account keys to fulfill the request: 301 | 302 | ```bash 303 | near call oracle.$NEAR_ACCT fulfill_request '{"account": "client.'$NEAR_ACCT'", "nonce": "0", "data": "MTkuMQ=="}' --accountId oracle-node.$NEAR_ACCT --gas 300000000000000 304 | ``` 305 | 306 | (Optional) Check the **client contract** for the values it has saved: 307 | 308 | ```bash 309 | near view client.$NEAR_ACCT get_received_vals '{"max": "100"}' 310 | ``` 311 | 312 | ## Check final balance/allowance 313 | 314 | (Optional) Check the balance of **client contract**: 315 | 316 | ```bash 317 | near view near-link.$NEAR_ACCT get_balance '{"owner_id": "client.'$NEAR_ACCT'"}' 318 | ``` 319 | 320 | Expect `40` 321 | 322 | (Optional) Check the allowance of **oracle contract**: 323 | 324 | ```bash 325 | near view near-link.$NEAR_ACCT get_allowance '{"owner_id": "client.'$NEAR_ACCT'", "escrow_account_id": "oracle.'$NEAR_ACCT'"}' 326 | ``` 327 | 328 | Expect `10` 329 | 330 | The oracle node and oracle contract are assumed to be owned by the same person/entity. The oracle contract has "withdrawable tokens" that can be taken when it's most convenient. Some oracles may choose to transfer these tokens immediately after fulfillment. Here we are using the withdrawable pattern, where gas is conserved by not transferring after each request fulfillment. 331 | 332 | Also, when the feature of cancelling requests is implemented, the withdrawable tokens is used to ensure the correct amount of fungible tokens can be withdrawn without interfering with possible cancellations within a given period. 333 | 334 | ## Withdraw tokens 335 | 336 | (Optional) Check the withdrawable tokens on the oracle contract with this command: 337 | 338 | ```bash 339 | near view oracle.$NEAR_ACCT get_withdrawable_tokens 340 | ``` 341 | 342 | (Optional) Check the fungible token balance of the client and the base account we'll be extracting to it. (This is the original account we set the `NEAR_ACCT` environment variable to, for demonstration purposes) 343 | 344 | ```bash 345 | near view near-link.$NEAR_ACCT get_balance '{"owner_id": "oracle.'$NEAR_ACCT'"}' 346 | near view near-link.$NEAR_ACCT get_balance '{"owner_id": "'$NEAR_ACCT'"}' 347 | ``` 348 | 349 | Finally, withdraw the fungible tokens from the oracle contract into the oracle node, the base account, who presumably owns both the oracle node and oracle contract. 350 | 351 | ```bash 352 | near call oracle.$NEAR_ACCT withdraw '{"recipient": "oracle-node.'$NEAR_ACCT'", "amount": "10"}' --accountId oracle.$NEAR_ACCT --gas 300000000000000 353 | ``` 354 | 355 | You may use the previous two `get_balance` view methods to confirm that the fungible tokens have indeed been withdrawn. 356 | 357 | ## Notes 358 | 359 | The client is responsible for making sure there is enough allowance for fungible token transfers. It may be advised to add a cushion in addition to expected fungible token transfers as duplicate requests will also decrease allowance. 360 | 361 | **Scenario**: a client accidentally sends the same request or a request with the same nonce. The fungible token transfer occurs, decrementing the allowance on the fungible token contract. Then it is found that it's a duplicate, and the fungible tokens are returned. In this case, the allowance will not be increased as this can only be done by the client itself. 362 | 363 | One way to handle this is for the client to have logic to increase the allowance if it receives the response indicating a duplicate request has been sent. Another way might be to increase the allowance before each request. Again, this decision is up to the owner of the client contract. 364 | 365 | ## Outstanding work 366 | 367 | There are various issues opened in this repository. As mentioned early in this document, this is an ever-growing repository. Soon we'll implement using the expiration to allow the client contract to cancel their request and receive the tokens back if it's within the window. There's also work in terms of setting up the [PreCoordinator](https://github.com/smartcontractkit/chainlink/blob/develop/evm-contracts/src/v0.5/PreCoordinator.sol) and whatnot. 368 | 369 | Contributors are welcome to get involved! 370 | -------------------------------------------------------------------------------- /near-link-token/src/lib.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Fungible Token implementation with JSON serialization. 3 | * NOTES: 4 | * - The maximum balance value is limited by U128 (2**128 - 1). 5 | * - JSON calls should pass U128 as a base-10 string. E.g. "100". 6 | * - The contract optimizes the inner trie structure by hashing account IDs. It will prevent some 7 | * abuse of deep tries. Shouldn't be an issue, once NEAR clients implement full hashing of keys. 8 | * - The contract tracks the change in storage before and after the call. If the storage increases, 9 | * the contract requires the caller of the contract to attach enough deposit to the function call 10 | * to cover the storage cost. 11 | * This is done to prevent a denial of service attack on the contract by taking all available storage. 12 | * If the storage decreases, the contract will issue a refund for the cost of the released storage. 13 | * The unused tokens from the attached deposit are also refunded, so it's safe to 14 | * attach more deposit than required. 15 | * - To prevent the deployed contract from being modified or deleted, it should not have any access 16 | * keys on its account. 17 | */ 18 | use borsh::{BorshDeserialize, BorshSerialize}; 19 | use near_sdk::collections::UnorderedMap; 20 | use near_sdk::json_types::U128; 21 | use near_sdk::{env, near_bindgen, AccountId, Balance, Promise, StorageUsage}; 22 | 23 | #[global_allocator] 24 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 25 | 26 | /// Price per 1 byte of storage from mainnet genesis config. 27 | const STORAGE_PRICE_PER_BYTE: Balance = 100000000000000000000; 28 | 29 | /// Contains balance and allowances information for one account. 30 | #[derive(BorshDeserialize, BorshSerialize)] 31 | pub struct Account { 32 | /// Current account balance. 33 | pub balance: Balance, 34 | /// Escrow Account ID hash to the allowance amount. 35 | /// Allowance is the amount of tokens the Escrow Account ID can spent on behalf of the account 36 | /// owner. 37 | pub allowances: UnorderedMap, Balance>, 38 | } 39 | 40 | impl Account { 41 | /// Initializes a new Account with 0 balance and no allowances for a given `account_hash`. 42 | pub fn new(account_hash: Vec) -> Self { 43 | Self { balance: 0, allowances: UnorderedMap::new(account_hash) } 44 | } 45 | 46 | /// Sets allowance for account `escrow_account_id` to `allowance`. 47 | pub fn set_allowance(&mut self, escrow_account_id: &AccountId, allowance: Balance) { 48 | let escrow_hash = env::sha256(escrow_account_id.as_bytes()); 49 | if allowance > 0 { 50 | self.allowances.insert(&escrow_hash, &allowance); 51 | } else { 52 | self.allowances.remove(&escrow_hash); 53 | } 54 | } 55 | 56 | /// Returns the allowance of account `escrow_account_id`. 57 | pub fn get_allowance(&self, escrow_account_id: &AccountId) -> Balance { 58 | let escrow_hash = env::sha256(escrow_account_id.as_bytes()); 59 | self.allowances.get(&escrow_hash).unwrap_or(0) 60 | } 61 | } 62 | 63 | #[near_bindgen] 64 | #[derive(BorshDeserialize, BorshSerialize)] 65 | pub struct FungibleToken { 66 | /// sha256(AccountID) -> Account details. 67 | pub accounts: UnorderedMap, Account>, 68 | 69 | /// Total supply of the all token. 70 | pub total_supply: Balance, 71 | } 72 | 73 | impl Default for FungibleToken { 74 | fn default() -> Self { 75 | panic!("Fun token should be initialized before usage") 76 | } 77 | } 78 | 79 | #[near_bindgen] 80 | impl FungibleToken { 81 | /// Initializes the contract with the given total supply owned by the given `owner_id`. 82 | #[init] 83 | pub fn new(owner_id: AccountId, total_supply: U128) -> Self { 84 | let total_supply = total_supply.into(); 85 | assert!(!env::state_exists(), "Already initialized"); 86 | let mut ft = Self { accounts: UnorderedMap::new(b"a".to_vec()), total_supply }; 87 | let mut account = ft.get_account(&owner_id); 88 | account.balance = total_supply; 89 | ft.set_account(&owner_id, &account); 90 | ft 91 | } 92 | 93 | /// Increments the `allowance` for `escrow_account_id` by `amount` on the account of the caller of this contract 94 | /// (`predecessor_id`) who is the balance owner. 95 | /// Requirements: 96 | /// * Caller of the method has to attach deposit enough to cover storage difference at the 97 | /// fixed storage price defined in the contract. 98 | #[payable] 99 | pub fn inc_allowance(&mut self, escrow_account_id: AccountId, amount: U128) { 100 | let initial_storage = env::storage_usage(); 101 | assert!( 102 | env::is_valid_account_id(escrow_account_id.as_bytes()), 103 | "Escrow account ID is invalid" 104 | ); 105 | let owner_id = env::predecessor_account_id(); 106 | assert_ne!(escrow_account_id, owner_id, "Can not increment allowance for yourself"); 107 | let mut account = self.get_account(&owner_id); 108 | let current_allowance = account.get_allowance(&escrow_account_id); 109 | account.set_allowance(&escrow_account_id, current_allowance.saturating_add(amount.0)); 110 | self.set_account(&owner_id, &account); 111 | self.refund_storage(initial_storage); 112 | } 113 | 114 | /// Decrements the `allowance` for `escrow_account_id` by `amount` on the account of the caller of this contract 115 | /// (`predecessor_id`) who is the balance owner. 116 | /// Requirements: 117 | /// * Caller of the method has to attach deposit enough to cover storage difference at the 118 | /// fixed storage price defined in the contract. 119 | #[payable] 120 | pub fn dec_allowance(&mut self, escrow_account_id: AccountId, amount: U128) { 121 | let initial_storage = env::storage_usage(); 122 | assert!( 123 | env::is_valid_account_id(escrow_account_id.as_bytes()), 124 | "Escrow account ID is invalid" 125 | ); 126 | let owner_id = env::predecessor_account_id(); 127 | assert_ne!(escrow_account_id, owner_id, "Can not decrement allowance for yourself"); 128 | let mut account = self.get_account(&owner_id); 129 | let current_allowance = account.get_allowance(&escrow_account_id); 130 | account.set_allowance(&escrow_account_id, current_allowance.saturating_sub(amount.0)); 131 | self.set_account(&owner_id, &account); 132 | self.refund_storage(initial_storage); 133 | } 134 | 135 | /// Transfers the `amount` of tokens from `owner_id` to the `new_owner_id`. 136 | /// Requirements: 137 | /// * `amount` should be a positive integer. 138 | /// * `owner_id` should have balance on the account greater or equal than the transfer `amount`. 139 | /// * If this function is called by an escrow account (`owner_id != predecessor_account_id`), 140 | /// then the allowance of the caller of the function (`predecessor_account_id`) on 141 | /// the account of `owner_id` should be greater or equal than the transfer `amount`. 142 | /// * Caller of the method has to attach deposit enough to cover storage difference at the 143 | /// fixed storage price defined in the contract. 144 | #[payable] 145 | pub fn transfer_from(&mut self, owner_id: AccountId, new_owner_id: AccountId, amount: U128) { 146 | let initial_storage = env::storage_usage(); 147 | assert!( 148 | env::is_valid_account_id(new_owner_id.as_bytes()), 149 | "New owner's account ID is invalid" 150 | ); 151 | let amount = amount.into(); 152 | if amount == 0 { 153 | env::panic(b"Can't transfer 0 tokens"); 154 | } 155 | assert_ne!( 156 | owner_id, new_owner_id, 157 | "The new owner should be different from the current owner" 158 | ); 159 | // Retrieving the account from the state. 160 | let mut account = self.get_account(&owner_id); 161 | 162 | // Checking and updating unlocked balance 163 | if account.balance < amount { 164 | env::panic(b"Not enough balance"); 165 | } 166 | account.balance -= amount; 167 | 168 | // If transferring by escrow, need to check and update allowance. 169 | let escrow_account_id = env::predecessor_account_id(); 170 | if escrow_account_id != owner_id { 171 | let allowance = account.get_allowance(&escrow_account_id); 172 | if allowance < amount { 173 | env::panic(b"Not enough allowance"); 174 | } 175 | account.set_allowance(&escrow_account_id, allowance - amount); 176 | } 177 | 178 | // Saving the account back to the state. 179 | self.set_account(&owner_id, &account); 180 | 181 | // Deposit amount to the new owner and save the new account to the state. 182 | let mut new_account = self.get_account(&new_owner_id); 183 | new_account.balance += amount; 184 | self.set_account(&new_owner_id, &new_account); 185 | self.refund_storage(initial_storage); 186 | } 187 | 188 | /// Transfer `amount` of tokens from the caller of the contract (`predecessor_id`) to 189 | /// `new_owner_id`. 190 | /// Act the same was as `transfer_from` with `owner_id` equal to the caller of the contract 191 | /// (`predecessor_id`). 192 | /// Requirements: 193 | /// * Caller of the method has to attach deposit enough to cover storage difference at the 194 | /// fixed storage price defined in the contract. 195 | #[payable] 196 | pub fn transfer(&mut self, new_owner_id: AccountId, amount: U128) { 197 | // NOTE: New owner's Account ID checked in transfer_from. 198 | // Storage fees are also refunded in transfer_from. 199 | self.transfer_from(env::predecessor_account_id(), new_owner_id, amount); 200 | } 201 | 202 | /// Returns total supply of tokens. 203 | pub fn get_total_supply(&self) -> U128 { 204 | self.total_supply.into() 205 | } 206 | 207 | /// Returns balance of the `owner_id` account. 208 | pub fn get_balance(&self, owner_id: AccountId) -> U128 { 209 | self.get_account(&owner_id).balance.into() 210 | } 211 | 212 | /// Returns current allowance of `escrow_account_id` for the account of `owner_id`. 213 | /// 214 | /// NOTE: Other contracts should not rely on this information, because by the moment a contract 215 | /// receives this information, the allowance may already be changed by the owner. 216 | /// So this method should only be used on the front-end to see the current allowance. 217 | pub fn get_allowance(&self, owner_id: AccountId, escrow_account_id: AccountId) -> U128 { 218 | assert!( 219 | env::is_valid_account_id(escrow_account_id.as_bytes()), 220 | "Escrow account ID is invalid" 221 | ); 222 | self.get_account(&owner_id).get_allowance(&escrow_account_id).into() 223 | } 224 | } 225 | 226 | impl FungibleToken { 227 | /// Helper method to get the account details for `owner_id`. 228 | fn get_account(&self, owner_id: &AccountId) -> Account { 229 | assert!(env::is_valid_account_id(owner_id.as_bytes()), "Owner's account ID is invalid"); 230 | let account_hash = env::sha256(owner_id.as_bytes()); 231 | self.accounts.get(&account_hash).unwrap_or_else(|| Account::new(account_hash)) 232 | } 233 | 234 | /// Helper method to set the account details for `owner_id` to the state. 235 | fn set_account(&mut self, owner_id: &AccountId, account: &Account) { 236 | let account_hash = env::sha256(owner_id.as_bytes()); 237 | if account.balance > 0 || !account.allowances.is_empty() { 238 | self.accounts.insert(&account_hash, &account); 239 | } else { 240 | self.accounts.remove(&account_hash); 241 | } 242 | } 243 | 244 | fn refund_storage(&self, initial_storage: StorageUsage) { 245 | let current_storage = env::storage_usage(); 246 | let attached_deposit = env::attached_deposit(); 247 | let refund_amount = if current_storage > initial_storage { 248 | let required_deposit = 249 | Balance::from(current_storage - initial_storage) * STORAGE_PRICE_PER_BYTE; 250 | assert!( 251 | required_deposit <= attached_deposit, 252 | "The required attached deposit is {}, but the given attached deposit is is {}", 253 | required_deposit, 254 | attached_deposit, 255 | ); 256 | attached_deposit - required_deposit 257 | } else { 258 | attached_deposit 259 | + Balance::from(initial_storage - current_storage) * STORAGE_PRICE_PER_BYTE 260 | }; 261 | if refund_amount > 0 { 262 | env::log(format!("Refunding {} tokens for storage", refund_amount).as_bytes()); 263 | Promise::new(env::predecessor_account_id()).transfer(refund_amount); 264 | } 265 | } 266 | } 267 | 268 | #[cfg(not(target_arch = "wasm32"))] 269 | #[cfg(test)] 270 | mod tests { 271 | use near_sdk::MockedBlockchain; 272 | use near_sdk::{testing_env, VMContext}; 273 | 274 | use super::*; 275 | 276 | fn alice() -> AccountId { 277 | "alice.near".to_string() 278 | } 279 | fn bob() -> AccountId { 280 | "bob.near".to_string() 281 | } 282 | fn carol() -> AccountId { 283 | "carol.near".to_string() 284 | } 285 | 286 | fn get_context(predecessor_account_id: AccountId) -> VMContext { 287 | VMContext { 288 | current_account_id: alice(), 289 | signer_account_id: bob(), 290 | signer_account_pk: vec![0, 1, 2], 291 | predecessor_account_id, 292 | input: vec![], 293 | block_index: 0, 294 | block_timestamp: 0, 295 | account_balance: 1_000_000_000_000_000_000_000_000_000u128, 296 | account_locked_balance: 0, 297 | storage_usage: 10u64.pow(6), 298 | attached_deposit: 0, 299 | prepaid_gas: 10u64.pow(18), 300 | random_seed: vec![0, 1, 2], 301 | is_view: false, 302 | output_data_receivers: vec![], 303 | epoch_height: 0, 304 | } 305 | } 306 | 307 | #[test] 308 | fn test_new() { 309 | let context = get_context(carol()); 310 | testing_env!(context); 311 | let total_supply = 1_000_000_000_000_000u128; 312 | let contract = FungibleToken::new(bob(), total_supply.into()); 313 | assert_eq!(contract.get_total_supply().0, total_supply); 314 | assert_eq!(contract.get_balance(bob()).0, total_supply); 315 | } 316 | 317 | #[test] 318 | #[should_panic] 319 | fn test_new_twice_fails() { 320 | let context = get_context(carol()); 321 | testing_env!(context); 322 | let total_supply = 1_000_000_000_000_000u128; 323 | { 324 | let _contract = FungibleToken::new(bob(), total_supply.into()); 325 | } 326 | FungibleToken::new(bob(), total_supply.into()); 327 | } 328 | 329 | #[test] 330 | fn test_transfer() { 331 | let mut context = get_context(carol()); 332 | testing_env!(context.clone()); 333 | let total_supply = 1_000_000_000_000_000u128; 334 | let mut contract = FungibleToken::new(carol(), total_supply.into()); 335 | context.storage_usage = env::storage_usage(); 336 | 337 | context.attached_deposit = 1000 * STORAGE_PRICE_PER_BYTE; 338 | testing_env!(context.clone()); 339 | let transfer_amount = total_supply / 3; 340 | contract.transfer(bob(), transfer_amount.into()); 341 | context.storage_usage = env::storage_usage(); 342 | context.account_balance = env::account_balance(); 343 | 344 | context.is_view = true; 345 | context.attached_deposit = 0; 346 | testing_env!(context.clone()); 347 | assert_eq!(contract.get_balance(carol()).0, (total_supply - transfer_amount)); 348 | assert_eq!(contract.get_balance(bob()).0, transfer_amount); 349 | } 350 | 351 | #[test] 352 | #[should_panic(expected = "The new owner should be different from the current owner")] 353 | fn test_transfer_fail_self() { 354 | let mut context = get_context(carol()); 355 | testing_env!(context.clone()); 356 | let total_supply = 1_000_000_000_000_000u128; 357 | let mut contract = FungibleToken::new(carol(), total_supply.into()); 358 | context.storage_usage = env::storage_usage(); 359 | 360 | context.attached_deposit = 1000 * STORAGE_PRICE_PER_BYTE; 361 | testing_env!(context.clone()); 362 | let transfer_amount = total_supply / 3; 363 | contract.transfer(carol(), transfer_amount.into()); 364 | } 365 | 366 | #[test] 367 | #[should_panic(expected = "Can not increment allowance for yourself")] 368 | fn test_self_inc_allowance_fail() { 369 | let mut context = get_context(carol()); 370 | testing_env!(context.clone()); 371 | let total_supply = 1_000_000_000_000_000u128; 372 | let mut contract = FungibleToken::new(carol(), total_supply.into()); 373 | context.attached_deposit = STORAGE_PRICE_PER_BYTE * 1000; 374 | testing_env!(context.clone()); 375 | contract.inc_allowance(carol(), (total_supply / 2).into()); 376 | } 377 | 378 | #[test] 379 | #[should_panic(expected = "Can not decrement allowance for yourself")] 380 | fn test_self_dec_allowance_fail() { 381 | let mut context = get_context(carol()); 382 | testing_env!(context.clone()); 383 | let total_supply = 1_000_000_000_000_000u128; 384 | let mut contract = FungibleToken::new(carol(), total_supply.into()); 385 | context.attached_deposit = STORAGE_PRICE_PER_BYTE * 1000; 386 | testing_env!(context.clone()); 387 | contract.dec_allowance(carol(), (total_supply / 2).into()); 388 | } 389 | 390 | #[test] 391 | fn test_saturating_dec_allowance() { 392 | let mut context = get_context(carol()); 393 | testing_env!(context.clone()); 394 | let total_supply = 1_000_000_000_000_000u128; 395 | let mut contract = FungibleToken::new(carol(), total_supply.into()); 396 | context.attached_deposit = STORAGE_PRICE_PER_BYTE * 1000; 397 | testing_env!(context.clone()); 398 | contract.dec_allowance(bob(), (total_supply / 2).into()); 399 | assert_eq!(contract.get_allowance(carol(), bob()), 0.into()) 400 | } 401 | 402 | #[test] 403 | fn test_saturating_inc_allowance() { 404 | let mut context = get_context(carol()); 405 | testing_env!(context.clone()); 406 | let total_supply = std::u128::MAX; 407 | let mut contract = FungibleToken::new(carol(), total_supply.into()); 408 | context.attached_deposit = STORAGE_PRICE_PER_BYTE * 1000; 409 | testing_env!(context.clone()); 410 | contract.inc_allowance(bob(), total_supply.into()); 411 | contract.inc_allowance(bob(), total_supply.into()); 412 | assert_eq!(contract.get_allowance(carol(), bob()), std::u128::MAX.into()) 413 | } 414 | 415 | #[test] 416 | #[should_panic( 417 | expected = "The required attached deposit is 33100000000000000000000, but the given attached deposit is is 0" 418 | )] 419 | fn test_self_allowance_fail_no_deposit() { 420 | let mut context = get_context(carol()); 421 | testing_env!(context.clone()); 422 | let total_supply = 1_000_000_000_000_000u128; 423 | let mut contract = FungibleToken::new(carol(), total_supply.into()); 424 | context.attached_deposit = 0; 425 | testing_env!(context.clone()); 426 | contract.inc_allowance(bob(), (total_supply / 2).into()); 427 | } 428 | 429 | #[test] 430 | fn test_carol_escrows_to_bob_transfers_to_alice() { 431 | // Acting as carol 432 | let mut context = get_context(carol()); 433 | testing_env!(context.clone()); 434 | let total_supply = 1_000_000_000_000_000u128; 435 | let mut contract = FungibleToken::new(carol(), total_supply.into()); 436 | context.storage_usage = env::storage_usage(); 437 | 438 | context.is_view = true; 439 | testing_env!(context.clone()); 440 | assert_eq!(contract.get_total_supply().0, total_supply); 441 | 442 | let allowance = total_supply / 3; 443 | let transfer_amount = allowance / 3; 444 | context.is_view = false; 445 | context.attached_deposit = STORAGE_PRICE_PER_BYTE * 1000; 446 | testing_env!(context.clone()); 447 | contract.inc_allowance(bob(), allowance.into()); 448 | context.storage_usage = env::storage_usage(); 449 | context.account_balance = env::account_balance(); 450 | 451 | context.is_view = true; 452 | context.attached_deposit = 0; 453 | testing_env!(context.clone()); 454 | assert_eq!(contract.get_allowance(carol(), bob()).0, allowance); 455 | 456 | // Acting as bob now 457 | context.is_view = false; 458 | context.attached_deposit = STORAGE_PRICE_PER_BYTE * 1000; 459 | context.predecessor_account_id = bob(); 460 | testing_env!(context.clone()); 461 | contract.transfer_from(carol(), alice(), transfer_amount.into()); 462 | context.storage_usage = env::storage_usage(); 463 | context.account_balance = env::account_balance(); 464 | 465 | context.is_view = true; 466 | context.attached_deposit = 0; 467 | testing_env!(context.clone()); 468 | assert_eq!(contract.get_balance(carol()).0, total_supply - transfer_amount); 469 | assert_eq!(contract.get_balance(alice()).0, transfer_amount); 470 | assert_eq!(contract.get_allowance(carol(), bob()).0, allowance - transfer_amount); 471 | } 472 | 473 | #[test] 474 | fn test_carol_escrows_to_bob_locks_and_transfers_to_alice() { 475 | // Acting as carol 476 | let mut context = get_context(carol()); 477 | testing_env!(context.clone()); 478 | let total_supply = 1_000_000_000_000_000u128; 479 | let mut contract = FungibleToken::new(carol(), total_supply.into()); 480 | context.storage_usage = env::storage_usage(); 481 | 482 | context.is_view = true; 483 | testing_env!(context.clone()); 484 | assert_eq!(contract.get_total_supply().0, total_supply); 485 | 486 | let allowance = total_supply / 3; 487 | let transfer_amount = allowance / 3; 488 | context.is_view = false; 489 | context.attached_deposit = STORAGE_PRICE_PER_BYTE * 1000; 490 | testing_env!(context.clone()); 491 | contract.inc_allowance(bob(), allowance.into()); 492 | context.storage_usage = env::storage_usage(); 493 | context.account_balance = env::account_balance(); 494 | 495 | context.is_view = true; 496 | context.attached_deposit = 0; 497 | testing_env!(context.clone()); 498 | assert_eq!(contract.get_allowance(carol(), bob()).0, allowance); 499 | assert_eq!(contract.get_balance(carol()).0, total_supply); 500 | 501 | // Acting as bob now 502 | context.is_view = false; 503 | context.attached_deposit = STORAGE_PRICE_PER_BYTE * 1000; 504 | context.predecessor_account_id = bob(); 505 | testing_env!(context.clone()); 506 | contract.transfer_from(carol(), alice(), transfer_amount.into()); 507 | context.storage_usage = env::storage_usage(); 508 | context.account_balance = env::account_balance(); 509 | 510 | context.is_view = true; 511 | context.attached_deposit = 0; 512 | testing_env!(context.clone()); 513 | assert_eq!(contract.get_balance(carol()).0, (total_supply - transfer_amount)); 514 | assert_eq!(contract.get_balance(alice()).0, transfer_amount); 515 | assert_eq!(contract.get_allowance(carol(), bob()).0, allowance - transfer_amount); 516 | } 517 | 518 | #[test] 519 | fn test_self_allowance_set_for_refund() { 520 | let mut context = get_context(carol()); 521 | testing_env!(context.clone()); 522 | let total_supply = 1_000_000_000_000_000u128; 523 | let mut contract = FungibleToken::new(carol(), total_supply.into()); 524 | context.storage_usage = env::storage_usage(); 525 | 526 | let initial_balance = context.account_balance; 527 | let initial_storage = context.storage_usage; 528 | context.attached_deposit = STORAGE_PRICE_PER_BYTE * 1000; 529 | testing_env!(context.clone()); 530 | contract.inc_allowance(bob(), (total_supply / 2).into()); 531 | context.storage_usage = env::storage_usage(); 532 | context.account_balance = env::account_balance(); 533 | assert_eq!( 534 | context.account_balance, 535 | initial_balance 536 | + Balance::from(context.storage_usage - initial_storage) * STORAGE_PRICE_PER_BYTE 537 | ); 538 | 539 | let initial_balance = context.account_balance; 540 | let initial_storage = context.storage_usage; 541 | testing_env!(context.clone()); 542 | context.attached_deposit = 0; 543 | testing_env!(context.clone()); 544 | contract.dec_allowance(bob(), (total_supply / 2).into()); 545 | context.storage_usage = env::storage_usage(); 546 | context.account_balance = env::account_balance(); 547 | assert!(context.storage_usage < initial_storage); 548 | assert!(context.account_balance < initial_balance); 549 | assert_eq!( 550 | context.account_balance, 551 | initial_balance 552 | - Balance::from(initial_storage - context.storage_usage) * STORAGE_PRICE_PER_BYTE 553 | ); 554 | } 555 | } 556 | -------------------------------------------------------------------------------- /oracle/src/lib.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use serde::{Serialize, Deserialize}; 3 | use near_sdk::collections::{TreeMap, UnorderedSet}; 4 | use near_sdk::json_types::{U128, U64}; 5 | use near_sdk::{AccountId, env, near_bindgen, PromiseResult}; 6 | use serde_json::json; 7 | use std::str; 8 | use std::collections::HashMap; 9 | 10 | #[global_allocator] 11 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 12 | 13 | const EXPIRY_TIME: u64 = 5 * 60 * 1_000_000_000; 14 | 15 | // max gas: 300_000_000_000_000 16 | 17 | const MINIMUM_CONSUMER_GAS_LIMIT: u64 = 1_000_000_000; 18 | const SINGLE_CALL_GAS: u64 = 50_000_000_000_000; // 5 x 10^13 19 | const TRANSFER_FROM_NEAR_COST: u128 = 36_500_000_000_000_000_000_000; // 365 x 10^20 20 | 21 | pub type Base64String = String; 22 | 23 | #[derive(Default, BorshDeserialize, BorshSerialize, Debug, Clone)] 24 | #[derive(Serialize, Deserialize)] 25 | pub struct OracleRequest { 26 | caller_account: AccountId, 27 | request_spec: Base64String, 28 | callback_address: AccountId, 29 | callback_method: String, 30 | data: Base64String, 31 | payment: u128, 32 | expiration: u64 33 | } 34 | 35 | #[derive(Serialize, Deserialize)] 36 | pub struct SummaryJSON { 37 | account: AccountId, 38 | total_requests: u16, // TODO: choosing u16? need to enforce if so 39 | } 40 | 41 | #[derive(Serialize, Deserialize)] 42 | pub struct RequestsJSON { 43 | nonce: U128, 44 | request: OracleRequest, 45 | } 46 | 47 | #[near_bindgen] 48 | #[derive(BorshDeserialize, BorshSerialize)] 49 | pub struct Oracle { 50 | pub owner: AccountId, 51 | pub link_account: AccountId, 52 | pub withdrawable_tokens: u128, 53 | pub nonces: TreeMap, 54 | pub requests: TreeMap>, 55 | pub authorized_nodes: UnorderedSet, 56 | } 57 | 58 | impl Default for Oracle { 59 | fn default() -> Self { 60 | panic!("Oracle should be initialized before usage") 61 | } 62 | } 63 | 64 | #[near_bindgen] 65 | impl Oracle { 66 | /// Initializes the contract with the given total supply owned by the given `owner_id` and `withdrawable_tokens` 67 | #[init] 68 | pub fn new(link_id: AccountId, owner_id: AccountId) -> Self { 69 | assert!(env::is_valid_account_id(owner_id.as_bytes()), "Owner's account ID is invalid"); 70 | assert!(env::is_valid_account_id(link_id.as_bytes()), "Link token account ID is invalid"); 71 | assert!(!env::state_exists(), "Already initialized"); 72 | Self { 73 | owner: owner_id, 74 | link_account: link_id, 75 | withdrawable_tokens: 0_u128, 76 | nonces: TreeMap::new(b"nonces".to_vec()), 77 | requests: TreeMap::new(b"requests".to_vec()), 78 | authorized_nodes: UnorderedSet::new(b"authorized_nodes".to_vec()), 79 | } 80 | } 81 | 82 | /// This is the entry point that will use the escrow transfer_from. 83 | /// Afterwards, it essentially calls itself (store_request) which stores the request in state. 84 | pub fn request(&mut self, payment: U128, spec_id: Base64String, callback_address: AccountId, callback_method: String, nonce: U128, data_version: U128, data: Base64String) { 85 | self._check_callback_address(&callback_address); 86 | let nonce_u128: u128 = nonce.into(); 87 | 88 | let entry_option = self.requests.get(&env::predecessor_account_id()); 89 | if entry_option.is_some() { 90 | // Ensure there isn't already the same nonce 91 | let nonce_entry = entry_option.unwrap(); 92 | if nonce_entry.contains_key(&nonce_u128) { 93 | env::panic(b"Existing account and nonce in requests"); 94 | } 95 | } 96 | 97 | let last_nonce_option: Option = self.get_nonce(env::predecessor_account_id()); 98 | if last_nonce_option.is_some() { 99 | let last_nonce_u128: u128 = last_nonce_option.unwrap().into(); 100 | assert!(last_nonce_u128 < nonce_u128, format!("Invalid, already used nonce: {:?}", nonce_u128)); 101 | } 102 | let has_nonce_option = self.nonces.get(&env::predecessor_account_id()); 103 | let transfer_cost = if has_nonce_option.is_some() { 104 | 0u128 105 | } else { 106 | TRANSFER_FROM_NEAR_COST 107 | }; 108 | // first transfer token 109 | let promise_transfer_tokens = env::promise_create( 110 | self.link_account.clone(), 111 | b"transfer_from", 112 | json!({ 113 | "owner_id": env::predecessor_account_id(), 114 | "new_owner_id": env::current_account_id(), 115 | "amount": payment, 116 | }).to_string().as_bytes(), 117 | transfer_cost, 118 | SINGLE_CALL_GAS, 119 | ); 120 | 121 | // call this contract's request function after the transfer 122 | let promise_call_self_request = env::promise_then( 123 | promise_transfer_tokens, 124 | env::current_account_id(), 125 | b"store_request", 126 | json!({ 127 | "sender": env::predecessor_account_id(), 128 | "payment": payment, 129 | "spec_id": spec_id, 130 | "callback_address": callback_address, 131 | "callback_method": callback_method, 132 | "nonce": nonce, 133 | "data_version": data_version, 134 | "data": data 135 | }).to_string().as_bytes(), 136 | 0, 137 | SINGLE_CALL_GAS 138 | ); 139 | 140 | env::promise_return(promise_call_self_request); 141 | } 142 | 143 | /// Accounts/contracts should call request, which in turn calls this contract via a promise 144 | #[allow(unused_variables)] // for data_version, which is also not used in Solidity as I understand 145 | pub fn store_request(&mut self, sender: AccountId, payment: U128, spec_id: Base64String, callback_address: AccountId, callback_method: String, nonce: U128, data_version: U128, data: Base64String) { 146 | // this method should only ever be called from this contract 147 | self._only_owner_predecessor(); 148 | 149 | // TODO: fix this "if" workaround until I can figure out how to write tests with promises 150 | if cfg!(target_arch = "wasm32") { 151 | assert_eq!(env::promise_results_count(), 1); 152 | // ensure successful promise, meaning tokens are transferred 153 | match env::promise_result(0) { 154 | PromiseResult::Successful(_) => {}, 155 | PromiseResult::Failed => env::panic(b"The promise failed. See receipt failures."), 156 | PromiseResult::NotReady => env::panic(b"The promise was not ready."), 157 | }; 158 | } 159 | 160 | // cast arguments in order to be formatted 161 | let payment_u128: u128 = payment.into(); 162 | let nonce_u128: u128 = nonce.into(); 163 | let expiration: u64 = env::block_timestamp() + EXPIRY_TIME; 164 | 165 | // store request 166 | let oracle_request = OracleRequest { 167 | caller_account: sender.clone(), 168 | request_spec: spec_id, 169 | callback_address, 170 | callback_method, 171 | data, 172 | payment: payment_u128, 173 | expiration, 174 | }; 175 | 176 | // Insert request and commitment into state. 177 | /* 178 | account => 179 | nonce => { Request } 180 | */ 181 | let nonce_request_entry = self.requests.get(&sender); 182 | let mut nonce_request = if nonce_request_entry.is_none() { 183 | TreeMap::new(sender.clone().into_bytes()) 184 | } else { 185 | nonce_request_entry.unwrap() 186 | }; 187 | nonce_request.insert(&nonce_u128, &oracle_request); 188 | self.requests.insert(&sender.clone(), &nonce_request); 189 | self.nonces.insert(&sender.clone(), &nonce.clone()); 190 | env::log(format!("Inserted request with\nKey: {:?}\nValue: {:?}", nonce_u128.clone(), oracle_request.clone()).as_bytes()); 191 | } 192 | 193 | /// Note that the request_id here is String instead of Vec as might be expected from the Solidity contract 194 | pub fn fulfill_request(&mut self, account: AccountId, nonce: U128, data: Base64String) { 195 | self._only_authorized_node(); 196 | 197 | // TODO: this is probably going to be too low at first, adjust 198 | assert!(env::prepaid_gas() - env::used_gas() > MINIMUM_CONSUMER_GAS_LIMIT, "Must provide consumer enough gas"); 199 | 200 | // Get the request 201 | let account_requests = self.requests.get(&account); 202 | if account_requests.is_none() { 203 | env::panic(b"Did not find the account to fulfill."); 204 | } 205 | let nonce_u128: u128 = nonce.into(); 206 | let request_option = account_requests.unwrap().get(&nonce_u128); 207 | if request_option.is_none() { 208 | env::panic(b"Did not find the request (nonce) to fulfill."); 209 | } 210 | let request = request_option.unwrap(); 211 | 212 | let promise_perform_callback = env::promise_create( 213 | request.callback_address, 214 | request.callback_method.as_bytes(), 215 | json!({ 216 | "nonce": nonce.clone(), 217 | "answer": data 218 | }).to_string().as_bytes(), 219 | 0, 220 | SINGLE_CALL_GAS 221 | ); 222 | 223 | let promise_post_callback = env::promise_then( 224 | promise_perform_callback, 225 | env::current_account_id(), 226 | b"fulfillment_post_callback", 227 | json!({ 228 | "account": account, 229 | "nonce": nonce 230 | }).to_string().as_bytes(), 231 | 0, 232 | SINGLE_CALL_GAS 233 | ); 234 | 235 | env::promise_return(promise_post_callback); 236 | } 237 | 238 | pub fn fulfillment_post_callback(&mut self, account: AccountId, nonce: U128) { 239 | self._only_owner_predecessor(); 240 | // TODO: fix this "if" workaround until I can figure out how to write tests with promises 241 | if cfg!(target_arch = "wasm32") { 242 | assert_eq!(env::promise_results_count(), 1); 243 | // ensure successful promise, meaning tokens are transferred 244 | match env::promise_result(0) { 245 | PromiseResult::Successful(_) => {}, 246 | PromiseResult::Failed => env::panic(b"(fulfillment_post_callback) The promise failed. See receipt failures."), 247 | PromiseResult::NotReady => env::panic(b"The promise was not ready."), 248 | }; 249 | } 250 | // Remove request from state 251 | let mut account_requests = self.requests.get(&account).unwrap(); 252 | let nonce_u128: u128 = nonce.into(); 253 | let payment = account_requests.get(&nonce_u128).unwrap().payment; 254 | account_requests.remove(&nonce_u128); 255 | // Must overwrite the new TreeMap with the account key 256 | self.requests.insert(&account, &account_requests); 257 | env::log(b"Request has completed successfully and been removed."); 258 | self.withdrawable_tokens += payment; 259 | } 260 | 261 | pub fn is_authorized(&self, node: AccountId) -> bool { 262 | self.authorized_nodes.contains(&node) 263 | } 264 | 265 | pub fn add_authorization(&mut self, node: AccountId) { 266 | self._only_owner(); 267 | assert!(env::is_valid_account_id(node.as_bytes()), "Account ID is invalid"); 268 | self.authorized_nodes.insert(&node); 269 | } 270 | 271 | pub fn remove_authorization(&mut self, node: AccountId) { 272 | self._only_owner(); 273 | 274 | self.authorized_nodes.remove(&node); 275 | } 276 | 277 | pub fn withdraw(&mut self, recipient: AccountId, amount: U128) { 278 | self._only_owner(); 279 | assert!( 280 | env::is_valid_account_id(recipient.as_bytes()), 281 | "Recipient account ID is invalid." 282 | ); 283 | let amount_u128: u128 = amount.into(); 284 | self._has_available_funds(amount_u128); 285 | 286 | let promise_withdraw = env::promise_create( 287 | self.link_account.clone(), 288 | b"transfer", 289 | json!({ 290 | "owner_id": env::current_account_id(), 291 | "new_owner_id": recipient.clone(), 292 | "amount": amount.clone(), 293 | }).to_string().as_bytes(), 294 | 0, 295 | SINGLE_CALL_GAS, 296 | ); 297 | 298 | // call this contract's panic function after refunding 299 | let promise_decrement_withdrawable = env::promise_then( 300 | promise_withdraw, 301 | env::current_account_id(), 302 | b"post_withdraw", 303 | json!({ 304 | "amount": amount.clone() 305 | }).to_string().as_bytes(), 306 | 0, 307 | SINGLE_CALL_GAS * 2 308 | ); 309 | 310 | env::promise_return(promise_decrement_withdrawable); 311 | } 312 | 313 | pub fn post_withdraw(&mut self, amount: U128) { 314 | self._only_owner_predecessor(); 315 | assert_eq!(env::promise_results_count(), 1); 316 | match env::promise_result(0) { 317 | PromiseResult::Successful(_) => {}, 318 | PromiseResult::Failed => env::panic(b"(post_withdraw) The promise failed. See receipt failures."), 319 | PromiseResult::NotReady => env::panic(b"The promise was not ready."), 320 | }; 321 | 322 | let amount_u128: u128 = amount.into(); 323 | self.withdrawable_tokens -= amount_u128.clone(); 324 | env::log(b"Decremented withdrawable tokens") 325 | } 326 | 327 | /// Get up to first 65K accounts that have their own associated nonces => requests 328 | pub fn get_requests_summary(&self, max_num_accounts: U64) -> Vec { 329 | let mut counter: u64 = 0; 330 | let max_num_accounts_u64: u64 = max_num_accounts.into(); 331 | let mut result: Vec = Vec::with_capacity(max_num_accounts_u64 as usize); 332 | 333 | for req in self.requests.iter() { 334 | self._request_summary_iterate(&max_num_accounts_u64, req, &mut result, &mut counter); 335 | } 336 | 337 | result 338 | } 339 | 340 | pub fn get_requests_summary_from(&self, from_account: AccountId, max_num_accounts: U64) -> Vec { 341 | let mut counter: u64 = 0; 342 | let max_num_accounts_u64: u64 = max_num_accounts.into(); 343 | let mut result: Vec = Vec::with_capacity(max_num_accounts_u64 as usize); 344 | 345 | for req in self.requests.iter_from(from_account) { 346 | self._request_summary_iterate(&max_num_accounts_u64, req, &mut result, &mut counter); 347 | } 348 | 349 | result 350 | } 351 | 352 | /// Helper function while iterating through request summaries 353 | fn _request_summary_iterate(&self, max_num_accounts: &u64, req: (AccountId, TreeMap), result: &mut Vec, counter: &mut u64) { 354 | if *counter == *max_num_accounts || *counter > self.requests.len() { 355 | return 356 | } 357 | let account = req.0; 358 | let total_requests = req.1.len() as u16; 359 | result.push(SummaryJSON { 360 | account, 361 | total_requests 362 | }); 363 | 364 | *counter += 1; 365 | } 366 | 367 | pub fn get_requests(&self, account: AccountId, max_requests: U64) -> Vec { 368 | let max_requests_u64: u64 = max_requests.into(); 369 | if !self.requests.contains_key(&account) { 370 | env::panic(format!("Account {} has no requests.", account).as_bytes()); 371 | } 372 | let mut counter: u64 = 0; 373 | let mut result: Vec = Vec::with_capacity(max_requests_u64 as usize); 374 | let account_requests_map = self.requests.get(&account).unwrap(); 375 | 376 | for req in account_requests_map.iter() { 377 | self._request_iterate(&max_requests_u64, req, &mut result, &mut counter); 378 | } 379 | 380 | result 381 | } 382 | 383 | /// Helper function while iterating through account requests 384 | fn _request_iterate(&self, max_requests: &u64, req: (u128, OracleRequest), result: &mut Vec, counter: &mut u64) { 385 | if *counter == *max_requests || *counter > self.requests.len() { 386 | return 387 | } 388 | let nonce = req.0; 389 | let oracle_request = req.1; 390 | result.push(RequestsJSON { 391 | nonce: U128(nonce), 392 | request: oracle_request, 393 | }); 394 | 395 | *counter += 1; 396 | } 397 | 398 | pub fn get_all_requests(&self, max_num_accounts: U64, max_requests: U64) -> HashMap> { 399 | let max_requests_u64: u64 = max_requests.into(); 400 | let max_num_accounts_u64: u64 = max_num_accounts.into(); 401 | let mut account_counter: u64 = 0; 402 | let mut result: HashMap> = HashMap::new(); 403 | 404 | for account_requests in self.requests.iter() { 405 | if account_counter == max_num_accounts_u64 || account_counter > self.requests.len() { 406 | break 407 | } 408 | let mut requests: Vec = Vec::new(); 409 | let mut request_counter: u64 = 0; 410 | for nonce_request in account_requests.1.iter() { 411 | if request_counter == max_requests_u64 || request_counter > account_requests.1.len() { 412 | break 413 | } 414 | let req = RequestsJSON { 415 | nonce: U128(nonce_request.0), 416 | request: nonce_request.1 417 | }; 418 | requests.push(req); 419 | request_counter += 1; 420 | } 421 | result.insert(account_requests.0.clone(), requests); 422 | account_counter += 1; 423 | } 424 | result 425 | } 426 | 427 | pub fn get_nonce(&self, account: AccountId) -> Option { 428 | self.nonces.get(&account) 429 | } 430 | 431 | pub fn get_nonces(&self) -> HashMap { 432 | let mut result: HashMap = HashMap::new(); 433 | for nonce in self.nonces.iter() { 434 | result.insert(nonce.0.clone(), nonce.1.clone()); 435 | } 436 | result 437 | } 438 | 439 | pub fn get_withdrawable_tokens(&self) -> u128 { 440 | self.withdrawable_tokens 441 | } 442 | 443 | pub fn reset(&mut self) { 444 | self._only_owner(); 445 | self.requests.clear(); 446 | env::log(b"Commitments and requests are cleared."); 447 | } 448 | 449 | /// Can be called after a cross-contract call before enforcing a panic 450 | pub fn panic(&mut self, error_message: String) { 451 | self._only_owner_predecessor(); 452 | env::panic(error_message.as_bytes()); 453 | } 454 | 455 | fn _has_available_funds(&mut self, amount: u128) { 456 | assert!(self.withdrawable_tokens >= amount, "Amount requested is greater than withdrawable balance."); 457 | } 458 | 459 | fn _only_owner(&mut self) { 460 | assert_eq!(env::signer_account_id(), env::current_account_id(), "Only contract owner can call this method."); 461 | } 462 | 463 | /// This is a helper function with the promises happening. 464 | /// The predecessor will be this account calling itself after transferring 465 | /// fungible tokens. Used for functions called via promises where we 466 | /// do not want end user accounts calling them directly. 467 | fn _only_owner_predecessor(&mut self) { 468 | assert_eq!(env::predecessor_account_id(), env::current_account_id(), "Only contract owner can sign transactions for this method."); 469 | } 470 | 471 | fn _only_authorized_node(&mut self) { 472 | assert!(self.authorized_nodes.contains(&env::signer_account_id()) || env::signer_account_id() == env::current_account_id(), 473 | "Not an authorized node to fulfill requests."); 474 | } 475 | 476 | fn _check_callback_address(&mut self, callback_address: &AccountId) { 477 | assert_ne!(callback_address, &self.link_account, "Cannot callback to LINK."); 478 | assert_ne!(callback_address, &env::current_account_id(), "Callback address cannot be the oracle contract."); 479 | } 480 | 481 | /// This method is not compile to the smart contract. It is used in tests only. 482 | #[cfg(not(target_arch = "wasm32"))] 483 | pub fn get_all_authorizations(&self) -> Vec { 484 | let nodes_vectorized = self.authorized_nodes.as_vector(); 485 | let length = nodes_vectorized.len(); 486 | let mut ret = Vec::new(); 487 | for idx in 0..length { 488 | ret.push(nodes_vectorized.get(idx).unwrap()); 489 | } 490 | ret 491 | } 492 | 493 | /// This method is not compile to the smart contract. It is used in tests only. 494 | #[cfg(not(target_arch = "wasm32"))] 495 | pub fn test_callback(&self, data: Vec) { 496 | println!("Received test callback with data: {:?}", data); 497 | } 498 | } 499 | 500 | #[cfg(test)] 501 | mod tests { 502 | use super::*; 503 | use near_sdk::{MockedBlockchain, StorageUsage}; 504 | use near_sdk::{testing_env, VMContext}; 505 | use base64::{encode}; 506 | 507 | fn link() -> AccountId { "link_near".to_string() } 508 | fn alice() -> AccountId { "alice_near".to_string() } 509 | fn bob() -> AccountId { "bob_near".to_string() } 510 | 511 | fn get_context(signer_account_id: AccountId, storage_usage: StorageUsage) -> VMContext { 512 | VMContext { 513 | current_account_id: alice(), 514 | signer_account_id, 515 | signer_account_pk: vec![0, 1, 2], 516 | predecessor_account_id: alice(), 517 | input: vec![], 518 | block_index: 0, 519 | block_timestamp: 0, 520 | epoch_height: 0, 521 | account_balance: 0, 522 | account_locked_balance: 0, 523 | storage_usage, 524 | attached_deposit: 0, 525 | prepaid_gas: 10u64.pow(18), 526 | random_seed: vec![0, 1, 2], 527 | is_view: false, 528 | output_data_receivers: vec![], 529 | } 530 | } 531 | 532 | #[test] 533 | fn make_request_validate_commitment() { 534 | let context = get_context(alice(), 0); 535 | testing_env!(context); 536 | let mut contract = Oracle::new(link(), alice(), ); 537 | let sender = alice(); 538 | let payment_json: U128 = 51319_u128.into(); 539 | let spec_id = encode("unique spec id".to_string()); 540 | let nonce = 1_u128; 541 | let nonce_json: U128 = nonce.into(); 542 | let data_version_json: U128 = 131_u128.into(); 543 | let data = encode("BAT".to_string()); 544 | contract.store_request( sender, payment_json, spec_id, "callback.sender.testnet".to_string(), "my_callback_fn".to_string(), nonce_json, data_version_json, data); 545 | 546 | // second validate the serialized requests 547 | let max_requests: U64 = 1u64.into(); 548 | let serialized_output = contract.get_requests(alice(), max_requests); 549 | let expiration_string = contract.requests.get(&alice()).unwrap().get(&nonce).unwrap().expiration.to_string(); 550 | let expected_before_expiration = "[{\"nonce\":\"1\",\"request\":{\"caller_account\":\"alice_near\",\"request_spec\":\"dW5pcXVlIHNwZWMgaWQ=\",\"callback_address\":\"callback.sender.testnet\",\"callback_method\":\"my_callback_fn\",\"data\":\"QkFU\",\"payment\":51319,\"expiration\":"; 551 | let expected_after_expiration = "}}]"; 552 | let expected_result = format!("{}{}{}", expected_before_expiration, expiration_string, expected_after_expiration); 553 | let output_string = serde_json::to_string(serialized_output.as_slice()); 554 | assert_eq!(expected_result, output_string.unwrap()); 555 | } 556 | 557 | #[test] 558 | #[should_panic( 559 | expected = "Existing account and nonce in requests" 560 | )] 561 | fn make_duplicate_request() { 562 | let mut context = get_context(alice(), 0); 563 | context.attached_deposit = TRANSFER_FROM_NEAR_COST; 564 | testing_env!(context.clone()); 565 | let mut contract = Oracle::new(link(), alice()); 566 | let payment: U128 = 6_u128.into(); 567 | let spec_id = encode("unique spec id".to_string()); 568 | let callback_address = "callback.testnet".to_string(); 569 | let callback_method = "test_callback".to_string(); 570 | let nonce: U128 = 1_u128.into(); 571 | let data_version: U128 = 131_u128.into(); 572 | let data = encode("BAT".to_string()); 573 | 574 | contract.request(payment.clone(), spec_id.clone(), callback_address.clone(), callback_method.clone(), nonce.clone(), data_version.clone(), data.clone()); 575 | context.prepaid_gas = 10u64.pow(18); 576 | contract.store_request(alice(), payment.clone(), spec_id.clone(), callback_address.clone(), callback_method.clone(), nonce.clone(), data_version.clone(), data.clone()); 577 | testing_env!(context.clone()); 578 | 579 | contract.request(payment.clone(), spec_id.clone(), callback_address.clone(), callback_method.clone(), nonce.clone(), data_version.clone(), data.clone()); 580 | contract.store_request(alice(), payment, spec_id, callback_address, callback_method, nonce, data_version, data); 581 | } 582 | 583 | #[test] 584 | #[should_panic( 585 | expected = "Invalid, already used nonce: 7" 586 | )] 587 | fn make_invalid_nonce_request() { 588 | let mut context = get_context(alice(), 0); 589 | context.attached_deposit = TRANSFER_FROM_NEAR_COST; 590 | testing_env!(context.clone()); 591 | let mut contract = Oracle::new(link(), alice()); 592 | let payment: U128 = 6_u128.into(); 593 | let spec_id = encode("unique spec id".to_string()); 594 | let callback_address = "callback.testnet".to_string(); 595 | let callback_method = "test_callback".to_string(); 596 | let data_version: U128 = 131_u128.into(); 597 | let data = encode("BAT".to_string()); 598 | 599 | contract.request(payment.clone(), spec_id.clone(), callback_address.clone(), callback_method.clone(), 8_u128.into(), data_version.clone(), data.clone()); 600 | context.prepaid_gas = 10u64.pow(18); 601 | contract.store_request(alice(), payment.clone(), spec_id.clone(), callback_address.clone(), callback_method.clone(), 8_u128.into(), data_version.clone(), data.clone()); 602 | testing_env!(context.clone()); 603 | 604 | let default: U128 = 0_u128.into(); 605 | let current_nonce: u128 = contract.get_nonce(alice()).unwrap_or(default).into(); 606 | assert_eq!(current_nonce, 8_u128.into()); 607 | 608 | let current_mapped_nonce: U128 = *contract.get_nonces().get(&alice()).clone().unwrap_or(&default); 609 | assert_eq!(current_mapped_nonce, 8_u128.into()); 610 | 611 | contract.request(payment.clone(), spec_id.clone(), callback_address.clone(), callback_method.clone(), 7_u128.into(), data_version.clone(), data.clone()); 612 | contract.store_request(alice(), payment, spec_id, callback_address, callback_method, 7_u128.into(), data_version, data); 613 | } 614 | 615 | #[test] 616 | fn check_authorization() { 617 | let context = get_context(alice(), 0); 618 | testing_env!(context); 619 | let mut contract = Oracle::new(link(), alice()); 620 | let mut authorizations = contract.get_all_authorizations(); 621 | let empty_vec: Vec = Vec::new(); 622 | assert_eq!(empty_vec, authorizations); 623 | contract.add_authorization(alice()); 624 | authorizations = contract.get_all_authorizations(); 625 | let only_alice: Vec = vec![alice()]; 626 | assert_eq!(only_alice, authorizations); 627 | contract.add_authorization(bob()); 628 | let bob_is_authorized = contract.is_authorized(bob()); 629 | assert_eq!(true, bob_is_authorized); 630 | contract.remove_authorization(bob()); 631 | assert_eq!(only_alice, authorizations); 632 | } 633 | 634 | #[test] 635 | fn multiple_requests_to_json() { 636 | // Context: Alice 637 | let mut context = get_context(alice(), 0); 638 | testing_env!(context); 639 | // Set up contract 640 | let mut contract = Oracle::new(link(), alice()); 641 | // Alice stores two requests 642 | contract.store_request( alice(), 6_u128.into(), "unique-id".to_string(), "callback.testnet".to_string(), "test_callback".to_string(), 1_u128.into(), 131_u128.into(), "BAT".to_string()); 643 | contract.store_request( alice(), 6_u128.into(), "unique-id".to_string(), "callback.testnet".to_string(), "test_callback".to_string(), 2_u128.into(), 131_u128.into(), "NEAR".to_string()); 644 | // Context: Bob 645 | context = get_context(bob(), env::storage_usage()); 646 | testing_env!(context); 647 | contract.store_request( bob(), 6_u128.into(), "unique-id".to_string(), "callback.testnet".to_string(), "test_callback".to_string(), 1_u128.into(), 131_u128.into(), "BAT".to_string()); 648 | // Context: Link 649 | context = get_context(link(), env::storage_usage()); 650 | testing_env!(context); 651 | contract.store_request( link(), 6_u128.into(), "unique-id".to_string(), "callback.testnet".to_string(), "test_callback".to_string(), 1_u128.into(), 131_u128.into(), "BAT".to_string()); 652 | 653 | let max_num_accounts: U64 = 2u64.into(); 654 | let mut json_result = contract.get_requests_summary(max_num_accounts); 655 | let mut output_string = serde_json::to_string(json_result.as_slice()); 656 | let mut expected_result = "[{\"account\":\"alice_near\",\"total_requests\":2},{\"account\":\"bob_near\",\"total_requests\":1}]"; 657 | assert_eq!(output_string.unwrap(), expected_result); 658 | 659 | // now start after "alice_near" 660 | json_result = contract.get_requests_summary_from(alice(), max_num_accounts); 661 | expected_result = "[{\"account\":\"bob_near\",\"total_requests\":1},{\"account\":\"link_near\",\"total_requests\":1}]"; 662 | output_string = serde_json::to_string(json_result.as_slice()); 663 | assert_eq!(output_string.unwrap(), expected_result); 664 | } 665 | 666 | #[test] 667 | fn add_request_fulfill() { 668 | let mut context = get_context(alice(), 0); 669 | context.attached_deposit = TRANSFER_FROM_NEAR_COST; 670 | testing_env!(context); 671 | let mut contract = Oracle::new(link(), alice()); 672 | // make request 673 | let payment: U128 = 6_u128.into(); 674 | let spec_id = encode("unique spec id".to_string()); 675 | let callback_address = "callback.testnet".to_string(); 676 | let callback_method = "test_callback".to_string(); 677 | let nonce= 1_u128; 678 | let nonce_json: U128 = nonce.into(); 679 | let data_version: U128 = 131_u128.into(); 680 | let data = encode("BAT".to_string()); 681 | 682 | println!("Number of requests: {}", contract.requests.len()); 683 | contract.request(payment.clone(), spec_id.clone(), callback_address.clone(), callback_method.clone(), nonce_json.clone(), data_version, data.clone()); 684 | contract.store_request(alice(), payment, spec_id, callback_address.clone(), callback_method.clone(), nonce_json.clone(), data_version, data.clone()); 685 | let max_num_accounts: U64 = 1u64.into(); 686 | println!("{}", serde_json::to_string(contract.get_requests_summary(max_num_accounts).as_slice()).unwrap()); 687 | // authorize bob 688 | contract.add_authorization(bob()); 689 | 690 | // fulfill request 691 | let context = get_context(bob(), env::storage_usage()); 692 | testing_env!(context); 693 | contract.fulfill_request(alice(), 1.into(), data); 694 | } 695 | } --------------------------------------------------------------------------------