From c674cd71b03115995663f8f2318db87ca62c3b68 Mon Sep 17 00:00:00 2001 From: dmelotik Date: Wed, 18 Jan 2023 21:37:16 -0700 Subject: [PATCH 01/12] initial commit + README.md --- spl-balance/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 spl-balance/README.md diff --git a/spl-balance/README.md b/spl-balance/README.md new file mode 100644 index 00000000..724bf459 --- /dev/null +++ b/spl-balance/README.md @@ -0,0 +1,3 @@ +# Solana SPL Token Balances + +This substream is designed to get the token balances of all SPL tokens on Solana. \ No newline at end of file From 46b193d39dbee6da2f39ecf3e8b56491715d97b2 Mon Sep 17 00:00:00 2001 From: dmelotik Date: Thu, 19 Jan 2023 15:02:27 -0700 Subject: [PATCH 02/12] setup project --- Cargo.lock | 15 +++++++++ Cargo.toml | 1 + Makefile | 2 ++ common/proto/sol_token.proto | 55 +++++++++++++++++++++++++++++++++ spl-balance/Cargo.toml | 22 +++++++++++++ spl-balance/Makefile | 7 +++++ spl-balance/build.rs | 10 ++++++ spl-balance/rust-toolchain.toml | 4 +++ spl-balance/src/lib.rs | 13 ++++++++ spl-balance/substreams.yaml | 21 +++++++++++++ 10 files changed, 150 insertions(+) create mode 100644 common/proto/sol_token.proto create mode 100644 spl-balance/Cargo.toml create mode 100644 spl-balance/Makefile create mode 100644 spl-balance/build.rs create mode 100644 spl-balance/rust-toolchain.toml create mode 100644 spl-balance/src/lib.rs create mode 100644 spl-balance/substreams.yaml diff --git a/Cargo.lock b/Cargo.lock index c0db0194..6ffeba8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -928,6 +928,21 @@ dependencies = [ "substreams-solana", ] +[[package]] +name = "spl-balance" +version = "0.1.0" +dependencies = [ + "anyhow", + "ethabi", + "hex-literal", + "num-bigint", + "prost 0.11.3", + "substreams 0.3.2", + "substreams-common", + "substreams-ethereum", + "substreams-helper", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5a1bd6a3..234771b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "uniswap-v2", "solana-sample", "ens-names", + "spl-balance", ] exclude = ["messari-cli"] diff --git a/Makefile b/Makefile index 150c34c2..32e3015a 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ build-all: $(MAKE) -C solana-sample build $(MAKE) -C eth-balance build $(MAKE) -C ens-names build + $(MAKE) -C spl-balance build .PHONY: run-all run-all: @@ -20,6 +21,7 @@ run-all: $(MAKE) -C erc721 run $(MAKE) -C network run $(MAKE) -C ens-names run + $(MAKE) -C spl-balance run .PHONY: test test: diff --git a/common/proto/sol_token.proto b/common/proto/sol_token.proto new file mode 100644 index 00000000..1ec26697 --- /dev/null +++ b/common/proto/sol_token.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package messari.sol_token.v1; + +message Mints { + repeated Mint mints = 1; +} + +message Mint { + string address = 1; + uint32 decimals = 2; + string mint_authority = 3; + string freeze_authority = 4; +} + +message Transfers { + repeated Transfer transfers = 1; +} + +message Transfer { + string signature = 1; + string from = 2; + string to = 3; + TokenAccount token = 4; + string mint = 5; + uint64 native_amount = 6; + uint64 amount = 7; + BalanceChanges balance_changes = 8; +} + +message BalanceChanges { + repeated TokenBalance items = 1; +} + +// balance change +message TokenBalance { + TokenAccount token = 1; + uint64 block_height = 2; + string address = 3; // account address of the balance change + string old_balance = 4; // BigInt, in token's native amount + string new_balance = 5; // BigInt, in token's native amount +} + +message Tokens { + repeated TokenAccount tokens = 1; +} + +message TokenAccount { + string address = 1; + string name = 2; + string symbol = 3; + string decimals = 4; + string owner = 5; + string mint = 6; +} diff --git a/spl-balance/Cargo.toml b/spl-balance/Cargo.toml new file mode 100644 index 00000000..fd4e4420 --- /dev/null +++ b/spl-balance/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "spl-balance" +version = "0.1.0" +description = "Messari's standardized substream for solana SPL tokens" +edition = "2021" +repository = "https://github.com/messari/substreams" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +prost = "0.11.2" +ethabi = "17.2.0" +num-bigint = "0.4" +hex-literal = "0.3.4" +substreams = { workspace = true } +substreams-ethereum = { workspace = true } +substreams-helper = { path = "../substreams-helper" } + +[build-dependencies] +anyhow = "1" +substreams-common = { path = "../common" } \ No newline at end of file diff --git a/spl-balance/Makefile b/spl-balance/Makefile new file mode 100644 index 00000000..90bdb5a8 --- /dev/null +++ b/spl-balance/Makefile @@ -0,0 +1,7 @@ +.PHONY: build +build: + cargo build --target wasm32-unknown-unknown --release + +.PHONY: run +run: + substreams run -e mainnet.sol.streamingfast.io:443 substreams.yaml map -s 14690152 diff --git a/spl-balance/build.rs b/spl-balance/build.rs new file mode 100644 index 00000000..fb321d31 --- /dev/null +++ b/spl-balance/build.rs @@ -0,0 +1,10 @@ +use anyhow::{Ok, Result}; +use substreams_common::codegen; + +fn main() -> Result<(), anyhow::Error> { + println!("cargo:rerun-if-changed=proto"); + println!("cargo:rerun-if-changed=abi"); + codegen::generate(None)?; + + Ok(()) +} diff --git a/spl-balance/rust-toolchain.toml b/spl-balance/rust-toolchain.toml new file mode 100644 index 00000000..0a0b90ae --- /dev/null +++ b/spl-balance/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.64.0" +components = [ "rustfmt" ] +targets = [ "wasm32-unknown-unknown" ] diff --git a/spl-balance/src/lib.rs b/spl-balance/src/lib.rs new file mode 100644 index 00000000..80025150 --- /dev/null +++ b/spl-balance/src/lib.rs @@ -0,0 +1,13 @@ +#[rustfmt::skip] +pub mod pb; + +use num_bigint; +use substreams::scalar::BigInt; +use substreams::store::{StoreNew, StoreSet, StoreSetRaw}; +use substreams::{store, Hex}; +use substreams_ethereum::pb::eth as pbeth; + +#[substreams::handlers::store] +fn store_balance(block: pbeth::v2::Block, output: store::StoreSetRaw) { + +} diff --git a/spl-balance/substreams.yaml b/spl-balance/substreams.yaml new file mode 100644 index 00000000..97d348bb --- /dev/null +++ b/spl-balance/substreams.yaml @@ -0,0 +1,21 @@ +specVersion: v0.1.0 +package: + name: spl_balance + version: v0.1.0 + +imports: + eth: https://github.com/streamingfast/sf-ethereum/releases/download/v0.10.2/ethereum-v0.10.4.spkg + +protobuf: + files: + - sol_token.proto + importPaths: + - ../common/proto + +binaries: + default: + type: wasm/rust-v1 + file: "../target/wasm32-unknown-unknown/release/spl-balance.wasm" + +modules: +# TODO From c2d958121c348a7ee49fe775d29f641ba75a6655 Mon Sep 17 00:00:00 2001 From: dmelotik Date: Fri, 20 Jan 2023 00:05:33 -0700 Subject: [PATCH 03/12] add first map module --- common/proto/sol_token.proto | 3 +-- spl-balance/src/abi.rs | 1 + spl-balance/src/lib.rs | 6 ++++-- spl-balance/src/pb.rs | 9 +++++++++ spl-balance/substreams.yaml | 8 +++++++- 5 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 spl-balance/src/abi.rs create mode 100644 spl-balance/src/pb.rs diff --git a/common/proto/sol_token.proto b/common/proto/sol_token.proto index 1ec26697..a27f1271 100644 --- a/common/proto/sol_token.proto +++ b/common/proto/sol_token.proto @@ -25,7 +25,6 @@ message Transfer { string mint = 5; uint64 native_amount = 6; uint64 amount = 7; - BalanceChanges balance_changes = 8; } message BalanceChanges { @@ -36,7 +35,7 @@ message BalanceChanges { message TokenBalance { TokenAccount token = 1; uint64 block_height = 2; - string address = 3; // account address of the balance change + string account = 3; // account address of the balance change string old_balance = 4; // BigInt, in token's native amount string new_balance = 5; // BigInt, in token's native amount } diff --git a/spl-balance/src/abi.rs b/spl-balance/src/abi.rs new file mode 100644 index 00000000..d55d99b3 --- /dev/null +++ b/spl-balance/src/abi.rs @@ -0,0 +1 @@ +// DO NOT EDIT - the file is generated by build script diff --git a/spl-balance/src/lib.rs b/spl-balance/src/lib.rs index 80025150..d50a7a70 100644 --- a/spl-balance/src/lib.rs +++ b/spl-balance/src/lib.rs @@ -6,8 +6,10 @@ use substreams::scalar::BigInt; use substreams::store::{StoreNew, StoreSet, StoreSetRaw}; use substreams::{store, Hex}; use substreams_ethereum::pb::eth as pbeth; +use substreams_solana::pb::sol as solana; +use pb::sol_token::v1 as proto; -#[substreams::handlers::store] -fn store_balance(block: pbeth::v2::Block, output: store::StoreSetRaw) { +#[substreams::handlers::map] +fn map_balances(block: solana::v1::Block) -> Result { } diff --git a/spl-balance/src/pb.rs b/spl-balance/src/pb.rs new file mode 100644 index 00000000..6d3d4de7 --- /dev/null +++ b/spl-balance/src/pb.rs @@ -0,0 +1,9 @@ +#[rustfmt::skip] +#[path = "../target/pb/messari.sol_token.v1.rs"] +pub(in crate::pb) mod sol_token_v1; + +pub mod sol_token { + pub mod v1 { + pub use super::super::sol_token_v1::*; + } +} diff --git a/spl-balance/substreams.yaml b/spl-balance/substreams.yaml index 97d348bb..872c31fb 100644 --- a/spl-balance/substreams.yaml +++ b/spl-balance/substreams.yaml @@ -18,4 +18,10 @@ binaries: file: "../target/wasm32-unknown-unknown/release/spl-balance.wasm" modules: -# TODO + - name: map_balances + kind: map + initialBlock: 0 + inputs: + - source: sf.solana.type.v1.Block + output: + type: proto:solana.spl.v1.BalanceChanges From c21815a50290c9b50ea82561cbd0a59c757362dc Mon Sep 17 00:00:00 2001 From: dmelotik Date: Fri, 20 Jan 2023 15:06:35 -0700 Subject: [PATCH 04/12] see pre/post balance changes --- Cargo.lock | 22 ++++++++++++++++++++-- Cargo.toml | 2 +- solana-sample/Makefile | 2 +- solana-sample/src/lib.rs | 2 +- solana-sample/substreams.yaml | 2 +- spl-balance/Cargo.toml | 3 ++- spl-balance/Makefile | 2 +- spl-balance/README.md | 5 ++++- spl-balance/src/lib.rs | 31 ++++++++++++++++++++++++++----- spl-balance/substreams.yaml | 3 +-- 10 files changed, 58 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ffeba8b..c78ab4d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + [[package]] name = "bumpalo" version = "3.11.0" @@ -925,7 +931,7 @@ dependencies = [ "prost-build 0.10.4", "prost-types 0.10.1", "substreams 0.0.11", - "substreams-solana", + "substreams-solana 0.1.0", ] [[package]] @@ -933,14 +939,15 @@ name = "spl-balance" version = "0.1.0" dependencies = [ "anyhow", + "bs58", "ethabi", "hex-literal", "num-bigint", "prost 0.11.3", "substreams 0.3.2", "substreams-common", - "substreams-ethereum", "substreams-helper", + "substreams-solana 0.2.0", ] [[package]] @@ -1170,6 +1177,17 @@ dependencies = [ "substreams 0.0.12", ] +[[package]] +name = "substreams-solana" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5e5ad4a64b8af93729ca06debc69b2c72eaec3ee44872dd593e16dfcc56dae7" +dependencies = [ + "prost 0.11.3", + "prost-build 0.11.1", + "prost-types 0.11.1", +] + [[package]] name = "substreams-uniswap-v2" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 234771b4..c535212b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ exclude = ["messari-cli"] [workspace.dependencies] substreams = "0.3.2" substreams-ethereum = "0.6.2" -substreams-solana = "0.1.0" +substreams-solana = "0.2.0" [build] target = "wasm32-unknown-unknown" diff --git a/solana-sample/Makefile b/solana-sample/Makefile index 38c0b985..3f7ecf17 100644 --- a/solana-sample/Makefile +++ b/solana-sample/Makefile @@ -4,4 +4,4 @@ build: .PHONY: run run: - substreams run -e mainnet.sol.streamingfast.io:443 substreams.yaml store_test -s 0 -t +10 + substreams run -e mainnet.sol.streamingfast.io:443 substreams.yaml store_test -s 100 diff --git a/solana-sample/src/lib.rs b/solana-sample/src/lib.rs index f095bb40..82a98d52 100644 --- a/solana-sample/src/lib.rs +++ b/solana-sample/src/lib.rs @@ -3,5 +3,5 @@ use substreams_solana::pb::sol as solana; #[substreams::handlers::store] fn store_test(block: solana::v1::Block, _output: store::StoreSet) { - log::info!("block height: {}", block.blockhash); + _output.set(0, "block".to_string(), &format!("{:?}", block).as_bytes().to_vec()); } diff --git a/solana-sample/substreams.yaml b/solana-sample/substreams.yaml index 7c2f1904..d04e608a 100644 --- a/solana-sample/substreams.yaml +++ b/solana-sample/substreams.yaml @@ -15,6 +15,6 @@ modules: - name: store_test kind: store updatePolicy: set - valueType: bytes + valueType: string inputs: - source: sf.solana.type.v1.Block diff --git a/spl-balance/Cargo.toml b/spl-balance/Cargo.toml index fd4e4420..e2083a74 100644 --- a/spl-balance/Cargo.toml +++ b/spl-balance/Cargo.toml @@ -9,12 +9,13 @@ repository = "https://github.com/messari/substreams" crate-type = ["cdylib"] [dependencies] +bs58 = "0.4.0" prost = "0.11.2" ethabi = "17.2.0" num-bigint = "0.4" hex-literal = "0.3.4" substreams = { workspace = true } -substreams-ethereum = { workspace = true } +substreams-solana = { workspace = true } substreams-helper = { path = "../substreams-helper" } [build-dependencies] diff --git a/spl-balance/Makefile b/spl-balance/Makefile index 90bdb5a8..f9a302ba 100644 --- a/spl-balance/Makefile +++ b/spl-balance/Makefile @@ -4,4 +4,4 @@ build: .PHONY: run run: - substreams run -e mainnet.sol.streamingfast.io:443 substreams.yaml map -s 14690152 + substreams run -e mainnet.sol.streamingfast.io:443 substreams.yaml map_balances -s 173531340 -t +1 diff --git a/spl-balance/README.md b/spl-balance/README.md index 724bf459..d70508bf 100644 --- a/spl-balance/README.md +++ b/spl-balance/README.md @@ -1,3 +1,6 @@ # Solana SPL Token Balances -This substream is designed to get the token balances of all SPL tokens on Solana. \ No newline at end of file +This substream is designed to get the token balances of all SPL tokens on Solana. + +## Notes + diff --git a/spl-balance/src/lib.rs b/spl-balance/src/lib.rs index d50a7a70..d747a161 100644 --- a/spl-balance/src/lib.rs +++ b/spl-balance/src/lib.rs @@ -1,15 +1,36 @@ -#[rustfmt::skip] pub mod pb; use num_bigint; +use pb::sol_token::v1 as proto; use substreams::scalar::BigInt; use substreams::store::{StoreNew, StoreSet, StoreSetRaw}; -use substreams::{store, Hex}; -use substreams_ethereum::pb::eth as pbeth; +use substreams::{store, Hex, log}; use substreams_solana::pb::sol as solana; -use pb::sol_token::v1 as proto; +use bs58; +// Map SPL token balance changes #[substreams::handlers::map] -fn map_balances(block: solana::v1::Block) -> Result { +fn map_balances( + block: solana::v1::Block, +) -> Result { + log::info!("extracting SPL balance changes"); + let mut balance_changes = proto::BalanceChanges { items: vec![] }; + let mut index = 0; + + for trx in block.transactions { + if let Some(meta) = trx.meta { + if let Some(_) = meta.err { + continue; + } + if let Some(transaction) = trx.transaction { + log::info!("{}", bs58::encode(&transaction.signatures[0]).into_string()) + } + + // let vec_string: String = meta.pre_balances.iter().map(|x| x.to_string()).collect::>().join(""); + log::info!("pre_balances: {} {} {} {}", meta.pre_balances.len(), meta.post_balances.len(), meta.pre_token_balances.len(), meta.post_token_balances.len()); + } + } + + Ok(balance_changes) } diff --git a/spl-balance/substreams.yaml b/spl-balance/substreams.yaml index 872c31fb..399b5de1 100644 --- a/spl-balance/substreams.yaml +++ b/spl-balance/substreams.yaml @@ -15,12 +15,11 @@ protobuf: binaries: default: type: wasm/rust-v1 - file: "../target/wasm32-unknown-unknown/release/spl-balance.wasm" + file: "../target/wasm32-unknown-unknown/release/spl_balance.wasm" modules: - name: map_balances kind: map - initialBlock: 0 inputs: - source: sf.solana.type.v1.Block output: From eadcc04b000411dbeffd13065faa903e8105877d Mon Sep 17 00:00:00 2001 From: dmelotik Date: Fri, 20 Jan 2023 22:38:39 -0700 Subject: [PATCH 05/12] do map_balances --- common/proto/sol_token.proto | 37 ++++++++++++++---------------------- spl-balance/src/lib.rs | 36 +++++++++++++++++++++++++++++------ spl-balance/substreams.yaml | 10 ++++++++++ 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/common/proto/sol_token.proto b/common/proto/sol_token.proto index a27f1271..2a33ef96 100644 --- a/common/proto/sol_token.proto +++ b/common/proto/sol_token.proto @@ -2,17 +2,6 @@ syntax = "proto3"; package messari.sol_token.v1; -message Mints { - repeated Mint mints = 1; -} - -message Mint { - string address = 1; - uint32 decimals = 2; - string mint_authority = 3; - string freeze_authority = 4; -} - message Transfers { repeated Transfer transfers = 1; } @@ -21,7 +10,7 @@ message Transfer { string signature = 1; string from = 2; string to = 3; - TokenAccount token = 4; + Token token = 4; string mint = 5; uint64 native_amount = 6; uint64 amount = 7; @@ -33,22 +22,24 @@ message BalanceChanges { // balance change message TokenBalance { - TokenAccount token = 1; - uint64 block_height = 2; - string account = 3; // account address of the balance change - string old_balance = 4; // BigInt, in token's native amount - string new_balance = 5; // BigInt, in token's native amount + Token token = 1; + string transaction_id = 2; + uint64 block_height = 3; + string account = 4; // account address of the balance change + string pre_balance = 5; // BigInt, in token's native amount + string post_balance = 6; // BigInt, in token's native amount } message Tokens { - repeated TokenAccount tokens = 1; + repeated Token tokens = 1; } -message TokenAccount { +message Token { string address = 1; - string name = 2; - string symbol = 3; + optional string name = 2; + optional string symbol = 3; string decimals = 4; - string owner = 5; - string mint = 6; + string mint_authority = 5; + string freeze_authority = 6; + string block_created = 7; } diff --git a/spl-balance/src/lib.rs b/spl-balance/src/lib.rs index d747a161..d264bbb4 100644 --- a/spl-balance/src/lib.rs +++ b/spl-balance/src/lib.rs @@ -1,12 +1,12 @@ pub mod pb; +use bs58; use num_bigint; use pb::sol_token::v1 as proto; use substreams::scalar::BigInt; use substreams::store::{StoreNew, StoreSet, StoreSetRaw}; -use substreams::{store, Hex, log}; +use substreams::{log, store, Hex}; use substreams_solana::pb::sol as solana; -use bs58; // Map SPL token balance changes #[substreams::handlers::map] @@ -15,7 +15,6 @@ fn map_balances( ) -> Result { log::info!("extracting SPL balance changes"); let mut balance_changes = proto::BalanceChanges { items: vec![] }; - let mut index = 0; for trx in block.transactions { if let Some(meta) = trx.meta { @@ -26,11 +25,36 @@ fn map_balances( log::info!("{}", bs58::encode(&transaction.signatures[0]).into_string()) } - // let vec_string: String = meta.pre_balances.iter().map(|x| x.to_string()).collect::>().join(""); - log::info!("pre_balances: {} {} {} {}", meta.pre_balances.len(), meta.post_balances.len(), meta.pre_token_balances.len(), meta.post_token_balances.len()); + for i in 0..meta.pre_token_balances.len() { + let pre_balance = &meta.pre_token_balances[i]; + let post_balance = &meta.post_token_balances[i]; + let mut pre_balance_amount = ""; + let mut post_balance_amount = ""; + // pre.owner = user address + // pre.mint = token address + if let Some(pre_token_amount) = &pre_balance.ui_token_amount { + pre_balance_amount = pre_token_amount.amount; + } + if let Some(post_token_amount) = &post_balance.ui_token_amount { + post_balance_amount = post_token_amount.amount; + } + + balance_changes.push(proto::TokenBalance { + token: proto::Token {}, // TODO: this should be fed from store_tokens using pre.mint + transaction_id: bs58::encode(&transaction.signatures[0]).into_string(), + block_height: block.block_height.block_height, + account: pre_balance.owner, + pre_balance: pre_balance_amount, + post_balance: post_balance_amount, + }); + } } } - Ok(balance_changes) } + +#[substreams::handlers::store] +fn store_tokens(block: solana::v1::Block, output: store::StoreSet) { + +} diff --git a/spl-balance/substreams.yaml b/spl-balance/substreams.yaml index 399b5de1..eed4cd56 100644 --- a/spl-balance/substreams.yaml +++ b/spl-balance/substreams.yaml @@ -18,9 +18,19 @@ binaries: file: "../target/wasm32-unknown-unknown/release/spl_balance.wasm" modules: + # maps the balance changes of owners of SPL tokens - name: map_balances kind: map inputs: - source: sf.solana.type.v1.Block + - source: store_tokens output: type: proto:solana.spl.v1.BalanceChanges + + # stores all SPL tokens to retrieve + - name: store_tokens + kind: store + updatePolicy: set + valueType: proto:messari.sol_token.v1.Token + inputs: + - source: sf.solana.type.v1.Block From cca822046556349aa9918fe018b4958076e252cc Mon Sep 17 00:00:00 2001 From: dmelotik Date: Mon, 23 Jan 2023 19:01:51 -0700 Subject: [PATCH 06/12] add TODO --- spl-balance/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spl-balance/src/lib.rs b/spl-balance/src/lib.rs index d264bbb4..fe2bbc00 100644 --- a/spl-balance/src/lib.rs +++ b/spl-balance/src/lib.rs @@ -56,5 +56,5 @@ fn map_balances( #[substreams::handlers::store] fn store_tokens(block: solana::v1::Block, output: store::StoreSet) { - + // TODO } From 340028b71c2de7ca0c57367c4bdbaf5521e51b14 Mon Sep 17 00:00:00 2001 From: dmelotik Date: Tue, 24 Jan 2023 21:54:30 -0700 Subject: [PATCH 07/12] update versions, attempt to get solana-sample to work --- Cargo.lock | 104 +++++++++++----------------------- Cargo.toml | 2 +- solana-sample/Cargo.toml | 7 +-- solana-sample/Makefile | 2 +- solana-sample/src/lib.rs | 5 +- solana-sample/substreams.yaml | 2 +- substreams-helper/src/pb.rs | 9 +++ 7 files changed, 51 insertions(+), 80 deletions(-) create mode 100644 substreams-helper/src/pb.rs diff --git a/Cargo.lock b/Cargo.lock index c78ab4d0..ab8a360d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ "ethabi", "num-bigint", "prost 0.11.3", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-ethereum", "tiny-keccak", @@ -208,7 +208,7 @@ dependencies = [ "ethabi", "hex-literal", "prost 0.11.3", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -224,7 +224,7 @@ dependencies = [ "hex-literal", "lazy_static", "prost 0.11.3", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -238,7 +238,7 @@ dependencies = [ "ethabi", "hex-literal", "prost 0.11.3", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -253,7 +253,7 @@ dependencies = [ "hex-literal", "num-bigint", "prost 0.11.3", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -526,7 +526,7 @@ dependencies = [ "hex-literal", "prost 0.11.3", "prost-types 0.11.1", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -927,11 +927,10 @@ dependencies = [ "ethabi", "getrandom", "hex-literal", - "prost 0.10.4", + "prost 0.11.3", "prost-build 0.10.4", - "prost-types 0.10.1", - "substreams 0.0.11", - "substreams-solana 0.1.0", + "substreams 0.5.1", + "substreams-solana", ] [[package]] @@ -944,10 +943,10 @@ dependencies = [ "hex-literal", "num-bigint", "prost 0.11.3", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-helper", - "substreams-solana 0.2.0", + "substreams-solana", ] [[package]] @@ -958,44 +957,31 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "substreams" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c204a4e230019ce60eb11f0a9561e9030eeb7dbc2c15bddd5ab9e58aa96b262" -dependencies = [ - "bigdecimal", - "hex", - "hex-literal", - "num-bigint", - "prost 0.10.4", - "prost-build 0.10.4", - "prost-types 0.10.1", - "substreams-macro 0.0.11", - "thiserror", -] - -[[package]] -name = "substreams" -version = "0.0.12" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cf0b1354a0212595433c76be5c419111253f826fe6030e3a01f90054d2d541e" +checksum = "c02767a4d87afb56de517be75bcfe985a8c0e2694ed9383e09807df29252d3b7" dependencies = [ "bigdecimal", "hex", "hex-literal", "num-bigint", - "prost 0.10.4", - "prost-build 0.10.4", - "prost-types 0.10.1", - "substreams-macro 0.0.12", + "num-traits", + "pad", + "prost 0.11.3", + "prost-build 0.11.1", + "prost-types 0.11.1", + "substreams-macro 0.3.2", "thiserror", + "wee_alloc", ] [[package]] name = "substreams" -version = "0.3.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02767a4d87afb56de517be75bcfe985a8c0e2694ed9383e09807df29252d3b7" +checksum = "67c01921ed54c4afb29a45b6cd484541a084f660216ac0c4b8d29734e1f93552" dependencies = [ + "anyhow", "bigdecimal", "hex", "hex-literal", @@ -1005,7 +991,7 @@ dependencies = [ "prost 0.11.3", "prost-build 0.11.1", "prost-types 0.11.1", - "substreams-macro 0.3.2", + "substreams-macro 0.5.1", "thiserror", "wee_alloc", ] @@ -1016,7 +1002,7 @@ version = "0.1.0" dependencies = [ "prost 0.11.3", "regex", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-ethereum", "thiserror", ] @@ -1030,7 +1016,7 @@ dependencies = [ "getrandom", "hex-literal", "prost 0.11.3", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -1044,7 +1030,7 @@ dependencies = [ "ethabi", "hex-literal", "prost 0.11.3", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-ethereum", ] @@ -1122,7 +1108,7 @@ dependencies = [ "num-bigint", "pad", "prost 0.11.3", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-ethereum", "thiserror", @@ -1131,21 +1117,9 @@ dependencies = [ [[package]] name = "substreams-macro" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0db235f75c362557d3ce78c481535b51a688e189a030dc2d8610d8b6526a20" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "thiserror", -] - -[[package]] -name = "substreams-macro" -version = "0.0.12" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f98d38372efa37bdfd7e071092f4f4dcdcaed5dc5e7290b0362a56081474867" +checksum = "465371d88738a62f6a186abee0ff9ac4c117fe087984cc37b1904bfe6f77cd62" dependencies = [ "proc-macro2", "quote", @@ -1155,9 +1129,9 @@ dependencies = [ [[package]] name = "substreams-macro" -version = "0.3.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465371d88738a62f6a186abee0ff9ac4c117fe087984cc37b1904bfe6f77cd62" +checksum = "c516462ddb71cf5c6b22dca3f0a444edf2559fda764a44a89e60cd129eb10561" dependencies = [ "proc-macro2", "quote", @@ -1165,18 +1139,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "substreams-solana" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41fbbf68b1302c9ca187f721d1b32f09bc75905f26f7a2bd42c3e16e5226241" -dependencies = [ - "prost 0.10.4", - "prost-build 0.10.4", - "prost-types 0.10.1", - "substreams 0.0.12", -] - [[package]] name = "substreams-solana" version = "0.2.0" @@ -1198,7 +1160,7 @@ dependencies = [ "hex-literal", "pad", "prost 0.11.3", - "substreams 0.3.2", + "substreams 0.5.1", "substreams-common", "substreams-ethereum", "substreams-helper", diff --git a/Cargo.toml b/Cargo.toml index c535212b..65f1f470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ exclude = ["messari-cli"] [workspace.dependencies] -substreams = "0.3.2" +substreams = "0.5.1" substreams-ethereum = "0.6.2" substreams-solana = "0.2.0" diff --git a/solana-sample/Cargo.toml b/solana-sample/Cargo.toml index c1ae3578..ac1eaf55 100644 --- a/solana-sample/Cargo.toml +++ b/solana-sample/Cargo.toml @@ -7,12 +7,11 @@ repository = "https://github.com/messari/substreams/solana-sample" [dependencies] getrandom = { version = "0.2", features = ["js"] } -substreams = "0.0.11" -substreams-solana = "0.1.0" +substreams = { workspace = true } +substreams-solana = { workspace = true } ethabi = "17.2.0" hex-literal = "0.3.4" -prost = { version = "0.10.1" } -prost-types = "0.10.1" +prost = "0.11.0" [lib] crate-type = ["cdylib"] diff --git a/solana-sample/Makefile b/solana-sample/Makefile index 3f7ecf17..80a92b12 100644 --- a/solana-sample/Makefile +++ b/solana-sample/Makefile @@ -4,4 +4,4 @@ build: .PHONY: run run: - substreams run -e mainnet.sol.streamingfast.io:443 substreams.yaml store_test -s 100 + substreams run substreams.yaml map_transfers -s 1000 -t +5 --debug-modules-initial-snapshot=store_pools \ No newline at end of file diff --git a/solana-sample/src/lib.rs b/solana-sample/src/lib.rs index 82a98d52..d0626e72 100644 --- a/solana-sample/src/lib.rs +++ b/solana-sample/src/lib.rs @@ -1,7 +1,8 @@ -use substreams::{log, store}; +use substreams::store; +use substreams::store::StoreSet; use substreams_solana::pb::sol as solana; #[substreams::handlers::store] -fn store_test(block: solana::v1::Block, _output: store::StoreSet) { +fn store_test(block: solana::v1::Block, _output: StoreSet) { _output.set(0, "block".to_string(), &format!("{:?}", block).as_bytes().to_vec()); } diff --git a/solana-sample/substreams.yaml b/solana-sample/substreams.yaml index d04e608a..93545275 100644 --- a/solana-sample/substreams.yaml +++ b/solana-sample/substreams.yaml @@ -17,4 +17,4 @@ modules: updatePolicy: set valueType: string inputs: - - source: sf.solana.type.v1.Block + - source: sf.solana.type.v2.Block diff --git a/substreams-helper/src/pb.rs b/substreams-helper/src/pb.rs new file mode 100644 index 00000000..6d3d4de7 --- /dev/null +++ b/substreams-helper/src/pb.rs @@ -0,0 +1,9 @@ +#[rustfmt::skip] +#[path = "../target/pb/messari.sol_token.v1.rs"] +pub(in crate::pb) mod sol_token_v1; + +pub mod sol_token { + pub mod v1 { + pub use super::super::sol_token_v1::*; + } +} From a5b23e34c05a1142dd12843f1fe1006b8b1a6d32 Mon Sep 17 00:00:00 2001 From: dmelotik Date: Tue, 24 Jan 2023 21:58:17 -0700 Subject: [PATCH 08/12] remove unused import --- solana-sample/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/solana-sample/src/lib.rs b/solana-sample/src/lib.rs index d0626e72..0ed3f938 100644 --- a/solana-sample/src/lib.rs +++ b/solana-sample/src/lib.rs @@ -1,4 +1,3 @@ -use substreams::store; use substreams::store::StoreSet; use substreams_solana::pb::sol as solana; From c5285314b4a5a78c7c6da877bfe38b92f4f7a305 Mon Sep 17 00:00:00 2001 From: dmelotik Date: Wed, 25 Jan 2023 11:19:02 -0700 Subject: [PATCH 09/12] update module --- Cargo.lock | 65 +++++++++---------------------------- Cargo.toml | 2 +- spl-balance/src/lib.rs | 30 +++++++++-------- spl-balance/substreams.yaml | 14 ++++---- 4 files changed, 40 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab8a360d..c5a30f53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ "ethabi", "num-bigint", "prost 0.11.3", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-ethereum", "tiny-keccak", @@ -208,7 +208,7 @@ dependencies = [ "ethabi", "hex-literal", "prost 0.11.3", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -224,7 +224,7 @@ dependencies = [ "hex-literal", "lazy_static", "prost 0.11.3", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -238,7 +238,7 @@ dependencies = [ "ethabi", "hex-literal", "prost 0.11.3", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -253,7 +253,7 @@ dependencies = [ "hex-literal", "num-bigint", "prost 0.11.3", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -526,7 +526,7 @@ dependencies = [ "hex-literal", "prost 0.11.3", "prost-types 0.11.1", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -929,7 +929,7 @@ dependencies = [ "hex-literal", "prost 0.11.3", "prost-build 0.10.4", - "substreams 0.5.1", + "substreams", "substreams-solana", ] @@ -943,7 +943,7 @@ dependencies = [ "hex-literal", "num-bigint", "prost 0.11.3", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-helper", "substreams-solana", @@ -970,28 +970,7 @@ dependencies = [ "prost 0.11.3", "prost-build 0.11.1", "prost-types 0.11.1", - "substreams-macro 0.3.2", - "thiserror", - "wee_alloc", -] - -[[package]] -name = "substreams" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c01921ed54c4afb29a45b6cd484541a084f660216ac0c4b8d29734e1f93552" -dependencies = [ - "anyhow", - "bigdecimal", - "hex", - "hex-literal", - "num-bigint", - "num-traits", - "pad", - "prost 0.11.3", - "prost-build 0.11.1", - "prost-types 0.11.1", - "substreams-macro 0.5.1", + "substreams-macro", "thiserror", "wee_alloc", ] @@ -1002,7 +981,7 @@ version = "0.1.0" dependencies = [ "prost 0.11.3", "regex", - "substreams 0.5.1", + "substreams", "substreams-ethereum", "thiserror", ] @@ -1016,7 +995,7 @@ dependencies = [ "getrandom", "hex-literal", "prost 0.11.3", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-ethereum", "substreams-helper", @@ -1030,7 +1009,7 @@ dependencies = [ "ethabi", "hex-literal", "prost 0.11.3", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-ethereum", ] @@ -1042,7 +1021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b06e9e1389570981b251e304262124f26c30d5c2abc3a07d2cf048634db7cbb" dependencies = [ "getrandom", - "substreams 0.3.2", + "substreams", "substreams-ethereum-abigen", "substreams-ethereum-core", "substreams-ethereum-derive", @@ -1078,7 +1057,7 @@ dependencies = [ "prost 0.11.3", "prost-build 0.11.1", "prost-types 0.11.1", - "substreams 0.3.2", + "substreams", ] [[package]] @@ -1108,7 +1087,7 @@ dependencies = [ "num-bigint", "pad", "prost 0.11.3", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-ethereum", "thiserror", @@ -1127,18 +1106,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "substreams-macro" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c516462ddb71cf5c6b22dca3f0a444edf2559fda764a44a89e60cd129eb10561" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "thiserror", -] - [[package]] name = "substreams-solana" version = "0.2.0" @@ -1160,7 +1127,7 @@ dependencies = [ "hex-literal", "pad", "prost 0.11.3", - "substreams 0.5.1", + "substreams", "substreams-common", "substreams-ethereum", "substreams-helper", diff --git a/Cargo.toml b/Cargo.toml index 65f1f470..c535212b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ exclude = ["messari-cli"] [workspace.dependencies] -substreams = "0.5.1" +substreams = "0.3.2" substreams-ethereum = "0.6.2" substreams-solana = "0.2.0" diff --git a/spl-balance/src/lib.rs b/spl-balance/src/lib.rs index fe2bbc00..1c23d26f 100644 --- a/spl-balance/src/lib.rs +++ b/spl-balance/src/lib.rs @@ -33,20 +33,22 @@ fn map_balances( // pre.owner = user address // pre.mint = token address if let Some(pre_token_amount) = &pre_balance.ui_token_amount { - pre_balance_amount = pre_token_amount.amount; + pre_balance_amount = &pre_token_amount.amount; } if let Some(post_token_amount) = &post_balance.ui_token_amount { - post_balance_amount = post_token_amount.amount; + post_balance_amount = &post_token_amount.amount; } - balance_changes.push(proto::TokenBalance { - token: proto::Token {}, // TODO: this should be fed from store_tokens using pre.mint - transaction_id: bs58::encode(&transaction.signatures[0]).into_string(), - block_height: block.block_height.block_height, - account: pre_balance.owner, - pre_balance: pre_balance_amount, - post_balance: post_balance_amount, - }); + log::info!("{} {} {}", pre_balance_amount, post_balance_amount, meta.pre_token_balances.len()); + + // balance_changes.push(proto::TokenBalance { + // token: proto::Token {}, // TODO: this should be fed from store_tokens using pre.mint + // transaction_id: bs58::encode(&transaction.signatures[0]).into_string(), + // block_height: block.block_height.block_height, + // account: pre_balance.owner, + // pre_balance: pre_balance_amount, + // post_balance: post_balance_amount, + // }); } } } @@ -54,7 +56,7 @@ fn map_balances( Ok(balance_changes) } -#[substreams::handlers::store] -fn store_tokens(block: solana::v1::Block, output: store::StoreSet) { - // TODO -} +// #[substreams::handlers::store] +// fn store_tokens(block: solana::v1::Block, output: store::StoreSet) { +// // TODO +// } diff --git a/spl-balance/substreams.yaml b/spl-balance/substreams.yaml index eed4cd56..4fbf9207 100644 --- a/spl-balance/substreams.yaml +++ b/spl-balance/substreams.yaml @@ -23,14 +23,14 @@ modules: kind: map inputs: - source: sf.solana.type.v1.Block - - source: store_tokens +# - source: store_tokens output: type: proto:solana.spl.v1.BalanceChanges # stores all SPL tokens to retrieve - - name: store_tokens - kind: store - updatePolicy: set - valueType: proto:messari.sol_token.v1.Token - inputs: - - source: sf.solana.type.v1.Block +# - name: store_tokens +# kind: store +# updatePolicy: set +# valueType: proto:messari.sol_token.v1.Token +# inputs: +# - source: sf.solana.type.v1.Block From 8334c5104d329637533cc0d73198937e79302d2e Mon Sep 17 00:00:00 2001 From: dmelotik Date: Fri, 27 Jan 2023 20:04:55 -0700 Subject: [PATCH 10/12] add store_tokens --- Cargo.lock | 127 ++---- common/proto/sol_token.proto | 24 +- solana-sample/Cargo.toml | 11 +- solana-sample/build.rs | 10 + solana-sample/proto/test.proto | 8 + solana-sample/rust-toolchain.toml | 4 + solana-sample/src/abi.rs | 1 + solana-sample/src/lib.rs | 7 +- solana-sample/src/pb.rs | 19 + solana-sample/substreams.yaml | 19 +- spl-balance/src/instruction.rs | 656 ++++++++++++++++++++++++++++++ spl-balance/src/lib.rs | 170 ++++++-- spl-balance/substreams.yaml | 14 +- 13 files changed, 905 insertions(+), 165 deletions(-) create mode 100644 solana-sample/build.rs create mode 100644 solana-sample/proto/test.proto create mode 100644 solana-sample/rust-toolchain.toml create mode 100644 solana-sample/src/abi.rs create mode 100644 solana-sample/src/pb.rs create mode 100644 spl-balance/src/instruction.rs diff --git a/Cargo.lock b/Cargo.lock index c5a30f53..0a15c3b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,12 +106,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" -[[package]] -name = "cc" -version = "1.0.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" - [[package]] name = "cfg-if" version = "0.1.10" @@ -139,15 +133,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "cmake" -version = "0.1.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" -dependencies = [ - "cc", -] - [[package]] name = "compoundv2-substreams" version = "0.1.0" @@ -155,7 +140,7 @@ dependencies = [ "anyhow", "ethabi", "num-bigint", - "prost 0.11.3", + "prost", "substreams", "substreams-common", "substreams-ethereum", @@ -207,7 +192,7 @@ dependencies = [ "anyhow", "ethabi", "hex-literal", - "prost 0.11.3", + "prost", "substreams", "substreams-common", "substreams-ethereum", @@ -223,7 +208,7 @@ dependencies = [ "ethabi", "hex-literal", "lazy_static", - "prost 0.11.3", + "prost", "substreams", "substreams-common", "substreams-ethereum", @@ -237,7 +222,7 @@ dependencies = [ "anyhow", "ethabi", "hex-literal", - "prost 0.11.3", + "prost", "substreams", "substreams-common", "substreams-ethereum", @@ -252,7 +237,7 @@ dependencies = [ "ethabi", "hex-literal", "num-bigint", - "prost 0.11.3", + "prost", "substreams", "substreams-common", "substreams-ethereum", @@ -524,8 +509,8 @@ dependencies = [ "chrono", "ethabi", "hex-literal", - "prost 0.11.3", - "prost-types 0.11.1", + "prost", + "prost-types", "substreams", "substreams-common", "substreams-ethereum", @@ -669,16 +654,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prost" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" -dependencies = [ - "bytes", - "prost-derive 0.10.1", -] - [[package]] name = "prost" version = "0.11.3" @@ -686,29 +661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b18e655c21ff5ac2084a5ad0611e827b3f92badf79f4910b5a5c58f4d87ff0" dependencies = [ "bytes", - "prost-derive 0.11.0", -] - -[[package]] -name = "prost-build" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" -dependencies = [ - "bytes", - "cfg-if 1.0.0", - "cmake", - "heck", - "itertools", - "lazy_static", - "log", - "multimap", - "petgraph", - "prost 0.10.4", - "prost-types 0.10.1", - "regex", - "tempfile", - "which", + "prost-derive", ] [[package]] @@ -724,26 +677,13 @@ dependencies = [ "log", "multimap", "petgraph", - "prost 0.11.3", - "prost-types 0.11.1", + "prost", + "prost-types", "regex", "tempfile", "which", ] -[[package]] -name = "prost-derive" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "prost-derive" version = "0.11.0" @@ -757,16 +697,6 @@ dependencies = [ "syn", ] -[[package]] -name = "prost-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" -dependencies = [ - "bytes", - "prost 0.10.4", -] - [[package]] name = "prost-types" version = "0.11.1" @@ -774,7 +704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" dependencies = [ "bytes", - "prost 0.11.3", + "prost", ] [[package]] @@ -924,12 +854,13 @@ dependencies = [ name = "solana-sample" version = "0.1.0" dependencies = [ + "anyhow", "ethabi", "getrandom", "hex-literal", - "prost 0.11.3", - "prost-build 0.10.4", + "prost", "substreams", + "substreams-common", "substreams-solana", ] @@ -942,7 +873,7 @@ dependencies = [ "ethabi", "hex-literal", "num-bigint", - "prost 0.11.3", + "prost", "substreams", "substreams-common", "substreams-helper", @@ -967,9 +898,9 @@ dependencies = [ "num-bigint", "num-traits", "pad", - "prost 0.11.3", - "prost-build 0.11.1", - "prost-types 0.11.1", + "prost", + "prost-build", + "prost-types", "substreams-macro", "thiserror", "wee_alloc", @@ -979,7 +910,7 @@ dependencies = [ name = "substreams-common" version = "0.1.0" dependencies = [ - "prost 0.11.3", + "prost", "regex", "substreams", "substreams-ethereum", @@ -994,7 +925,7 @@ dependencies = [ "ethabi", "getrandom", "hex-literal", - "prost 0.11.3", + "prost", "substreams", "substreams-common", "substreams-ethereum", @@ -1008,7 +939,7 @@ dependencies = [ "anyhow", "ethabi", "hex-literal", - "prost 0.11.3", + "prost", "substreams", "substreams-common", "substreams-ethereum", @@ -1054,9 +985,9 @@ dependencies = [ "ethabi", "getrandom", "num-bigint", - "prost 0.11.3", - "prost-build 0.11.1", - "prost-types 0.11.1", + "prost", + "prost-build", + "prost-types", "substreams", ] @@ -1086,7 +1017,7 @@ dependencies = [ "hex-literal", "num-bigint", "pad", - "prost 0.11.3", + "prost", "substreams", "substreams-common", "substreams-ethereum", @@ -1112,9 +1043,9 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5e5ad4a64b8af93729ca06debc69b2c72eaec3ee44872dd593e16dfcc56dae7" dependencies = [ - "prost 0.11.3", - "prost-build 0.11.1", - "prost-types 0.11.1", + "prost", + "prost-build", + "prost-types", ] [[package]] @@ -1126,7 +1057,7 @@ dependencies = [ "hex", "hex-literal", "pad", - "prost 0.11.3", + "prost", "substreams", "substreams-common", "substreams-ethereum", diff --git a/common/proto/sol_token.proto b/common/proto/sol_token.proto index 2a33ef96..92f3eab1 100644 --- a/common/proto/sol_token.proto +++ b/common/proto/sol_token.proto @@ -10,10 +10,11 @@ message Transfer { string signature = 1; string from = 2; string to = 3; - Token token = 4; + TokenAccount token = 4; string mint = 5; uint64 native_amount = 6; uint64 amount = 7; + BalanceChanges balance_changes = 8; } message BalanceChanges { @@ -22,24 +23,23 @@ message BalanceChanges { // balance change message TokenBalance { - Token token = 1; + TokenAccount token = 1; string transaction_id = 2; uint64 block_height = 3; - string account = 4; // account address of the balance change + string address = 4; // account address of the balance change string pre_balance = 5; // BigInt, in token's native amount string post_balance = 6; // BigInt, in token's native amount } message Tokens { - repeated Token tokens = 1; + repeated TokenAccount tokens = 1; } -message Token { +message TokenAccount { string address = 1; - optional string name = 2; - optional string symbol = 3; - string decimals = 4; - string mint_authority = 5; - string freeze_authority = 6; - string block_created = 7; -} + string name = 2; + string symbol = 3; + uint64 decimals = 4; + string freeze_authority = 5; + string mint_authority = 6; +} \ No newline at end of file diff --git a/solana-sample/Cargo.toml b/solana-sample/Cargo.toml index ac1eaf55..4a71d3a7 100644 --- a/solana-sample/Cargo.toml +++ b/solana-sample/Cargo.toml @@ -5,16 +5,17 @@ description = "Sample substreams for Solana" edition = "2021" repository = "https://github.com/messari/substreams/solana-sample" +[lib] +crate-type = ["cdylib"] + [dependencies] getrandom = { version = "0.2", features = ["js"] } substreams = { workspace = true } substreams-solana = { workspace = true } ethabi = "17.2.0" hex-literal = "0.3.4" -prost = "0.11.0" - -[lib] -crate-type = ["cdylib"] +prost = "0.11.2" [build-dependencies] -prost-build = "0.10.1" +anyhow = "1" +substreams-common = { path = "../common" } diff --git a/solana-sample/build.rs b/solana-sample/build.rs new file mode 100644 index 00000000..fb321d31 --- /dev/null +++ b/solana-sample/build.rs @@ -0,0 +1,10 @@ +use anyhow::{Ok, Result}; +use substreams_common::codegen; + +fn main() -> Result<(), anyhow::Error> { + println!("cargo:rerun-if-changed=proto"); + println!("cargo:rerun-if-changed=abi"); + codegen::generate(None)?; + + Ok(()) +} diff --git a/solana-sample/proto/test.proto b/solana-sample/proto/test.proto new file mode 100644 index 00000000..ded21b2b --- /dev/null +++ b/solana-sample/proto/test.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package messari.common.v1; + +message Block { + string item = 1; +} + diff --git a/solana-sample/rust-toolchain.toml b/solana-sample/rust-toolchain.toml new file mode 100644 index 00000000..0a0b90ae --- /dev/null +++ b/solana-sample/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.64.0" +components = [ "rustfmt" ] +targets = [ "wasm32-unknown-unknown" ] diff --git a/solana-sample/src/abi.rs b/solana-sample/src/abi.rs new file mode 100644 index 00000000..d55d99b3 --- /dev/null +++ b/solana-sample/src/abi.rs @@ -0,0 +1 @@ +// DO NOT EDIT - the file is generated by build script diff --git a/solana-sample/src/lib.rs b/solana-sample/src/lib.rs index 0ed3f938..8e66e4d2 100644 --- a/solana-sample/src/lib.rs +++ b/solana-sample/src/lib.rs @@ -1,7 +1,8 @@ -use substreams::store::StoreSet; +use substreams::pb; +use substreams::store::{StoreSetRaw, StoreSet, StoreNew}; use substreams_solana::pb::sol as solana; #[substreams::handlers::store] -fn store_test(block: solana::v1::Block, _output: StoreSet) { - _output.set(0, "block".to_string(), &format!("{:?}", block).as_bytes().to_vec()); +fn store_test(block: solana::v1::Block, _output: StoreSetRaw) { + _output.set(0, 0_u64, &block); } diff --git a/solana-sample/src/pb.rs b/solana-sample/src/pb.rs new file mode 100644 index 00000000..16e06701 --- /dev/null +++ b/solana-sample/src/pb.rs @@ -0,0 +1,19 @@ +#[rustfmt::skip] +#[path = "../target/pb/messari.common.v1.rs"] +pub(in crate::pb) mod common_v1; + +pub mod common { + pub mod v1 { + pub use super::super::common_v1::*; + } +} + +#[rustfmt::skip] +#[path = "../target/pb/messari.solana.type.rs"] +pub(in crate::pb) mod solana_type; + +pub mod solana { + pub mod type { + pub use super::super::solana_type::*; + } +} diff --git a/solana-sample/substreams.yaml b/solana-sample/substreams.yaml index 93545275..7b9bffc3 100644 --- a/solana-sample/substreams.yaml +++ b/solana-sample/substreams.yaml @@ -6,15 +6,22 @@ package: imports: sol: https://github.com/streamingfast/firehose-solana/releases/download/v0.1.0/solana-v0.1.0.spkg +protobuf: + files: + - test.proto + importPaths: + - ./proto + binaries: default: type: wasm/rust-v1 file: "../target/wasm32-unknown-unknown/release/solana_sample.wasm" modules: - - name: store_test - kind: store - updatePolicy: set - valueType: string - inputs: - - source: sf.solana.type.v2.Block + # Will store the entire solana block as a string + - name: store_test + kind: store + updatePolicy: set + valueType: proto:sf.solana.type.v2.Block + inputs: + - source: sf.solana.type.v2.Block diff --git a/spl-balance/src/instruction.rs b/spl-balance/src/instruction.rs new file mode 100644 index 00000000..873eda92 --- /dev/null +++ b/spl-balance/src/instruction.rs @@ -0,0 +1,656 @@ +use {crate::option::COption, std::convert::TryInto, substreams::errors::Error}; + +/// Instructions supported by the token program. +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +pub enum TokenInstruction<'a> { + /// Initializes a new mint and optionally deposits all the newly minted + /// tokens in an account. + /// + /// The `InitializeMint` instruction requires no signers and MUST be + /// included within the same Transaction as the system program's + /// `CreateAccount` instruction that creates the account being initialized. + /// Otherwise another party can acquire ownership of the uninitialized + /// account. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The mint to initialize. + /// 1. `[]` Rent sysvar + /// + /// + InitializeMint { + /// Number of base 10 digits to the right of the decimal place. + decimals: u8, + /// The authority/multisignature to mint tokens. + mint_authority: Vec, + /// The freeze authority/multisignature of the mint. + freeze_authority: COption>, + }, + /// Initializes a new account to hold tokens. If this account is associated + /// with the native mint then the token balance of the initialized account + /// will be equal to the amount of SOL in the account. If this account is + /// associated with another mint, that mint must be initialized before this + /// command can succeed. + /// + /// The `InitializeAccount` instruction requires no signers and MUST be + /// included within the same Transaction as the system program's + /// `CreateAccount` instruction that creates the account being initialized. + /// Otherwise another party can acquire ownership of the uninitialized + /// account. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The account to initialize. + /// 1. `[]` The mint this account will be associated with. + /// 2. `[]` The new account's owner/multisignature. + /// 3. `[]` Rent sysvar + InitializeAccount, + /// Initializes a multisignature account with N provided signers. + /// + /// Multisignature accounts can used in place of any single owner/delegate + /// accounts in any token instruction that require an owner/delegate to be + /// present. The variant field represents the number of signers (M) + /// required to validate this multisignature account. + /// + /// The `InitializeMultisig` instruction requires no signers and MUST be + /// included within the same Transaction as the system program's + /// `CreateAccount` instruction that creates the account being initialized. + /// Otherwise another party can acquire ownership of the uninitialized + /// account. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The multisignature account to initialize. + /// 1. `[]` Rent sysvar + /// 2. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= + /// 11. + InitializeMultisig { + /// The number of signers (M) required to validate this multisignature + /// account. + m: u8, + }, + /// Transfers tokens from one account to another either directly or via a + /// delegate. If this account is associated with the native mint then equal + /// amounts of SOL and Tokens will be transferred to the destination + /// account. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writable]` The source account. + /// 1. `[writable]` The destination account. + /// 2. `[signer]` The source account's owner/delegate. + /// + /// * Multisignature owner/delegate + /// 0. `[writable]` The source account. + /// 1. `[writable]` The destination account. + /// 2. `[]` The source account's multisignature owner/delegate. + /// 3. ..3+M `[signer]` M signer accounts. + Transfer { + /// The amount of tokens to transfer. + amount: u64, + }, + /// Approves a delegate. A delegate is given the authority over tokens on + /// behalf of the source account's owner. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The source account. + /// 1. `[]` The delegate. + /// 2. `[signer]` The source account owner. + /// + /// * Multisignature owner + /// 0. `[writable]` The source account. + /// 1. `[]` The delegate. + /// 2. `[]` The source account's multisignature owner. + /// 3. ..3+M `[signer]` M signer accounts + Approve { + /// The amount of tokens the delegate is approved for. + amount: u64, + }, + /// Revokes the delegate's authority. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The source account. + /// 1. `[signer]` The source account owner. + /// + /// * Multisignature owner + /// 0. `[writable]` The source account. + /// 1. `[]` The source account's multisignature owner. + /// 2. ..2+M `[signer]` M signer accounts + Revoke, + /// Sets a new authority of a mint or account. + /// + /// Accounts expected by this instruction: + /// + /// * Single authority + /// 0. `[writable]` The mint or account to change the authority of. + /// 1. `[signer]` The current authority of the mint or account. + /// + /// * Multisignature authority + /// 0. `[writable]` The mint or account to change the authority of. + /// 1. `[]` The mint's or account's current multisignature authority. + /// 2. ..2+M `[signer]` M signer accounts + SetAuthority { + /// The type of authority to update. + authority_type: AuthorityType, + /// The new authority + new_authority: COption>, + }, + /// Mints new tokens to an account. The native mint does not support + /// minting. + /// + /// Accounts expected by this instruction: + /// + /// * Single authority + /// 0. `[writable]` The mint. + /// 1. `[writable]` The account to mint tokens to. + /// 2. `[signer]` The mint's minting authority. + /// + /// * Multisignature authority + /// 0. `[writable]` The mint. + /// 1. `[writable]` The account to mint tokens to. + /// 2. `[]` The mint's multisignature mint-tokens authority. + /// 3. ..3+M `[signer]` M signer accounts. + MintTo { + /// The amount of new tokens to mint. + amount: u64, + }, + /// Burns tokens by removing them from an account. `Burn` does not support + /// accounts associated with the native mint, use `CloseAccount` instead. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writable]` The account to burn from. + /// 1. `[writable]` The token mint. + /// 2. `[signer]` The account's owner/delegate. + /// + /// * Multisignature owner/delegate + /// 0. `[writable]` The account to burn from. + /// 1. `[writable]` The token mint. + /// 2. `[]` The account's multisignature owner/delegate. + /// 3. ..3+M `[signer]` M signer accounts. + Burn { + /// The amount of tokens to burn. + amount: u64, + }, + /// Close an account by transferring all its SOL to the destination account. + /// Non-native accounts may only be closed if its token amount is zero. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The account to close. + /// 1. `[writable]` The destination account. + /// 2. `[signer]` The account's owner. + /// + /// * Multisignature owner + /// 0. `[writable]` The account to close. + /// 1. `[writable]` The destination account. + /// 2. `[]` The account's multisignature owner. + /// 3. ..3+M `[signer]` M signer accounts. + CloseAccount, + /// Freeze an Initialized account using the Mint's freeze_authority (if + /// set). + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The account to freeze. + /// 1. `[]` The token mint. + /// 2. `[signer]` The mint freeze authority. + /// + /// * Multisignature owner + /// 0. `[writable]` The account to freeze. + /// 1. `[]` The token mint. + /// 2. `[]` The mint's multisignature freeze authority. + /// 3. ..3+M `[signer]` M signer accounts. + FreezeAccount, + /// Thaw a Frozen account using the Mint's freeze_authority (if set). + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The account to freeze. + /// 1. `[]` The token mint. + /// 2. `[signer]` The mint freeze authority. + /// + /// * Multisignature owner + /// 0. `[writable]` The account to freeze. + /// 1. `[]` The token mint. + /// 2. `[]` The mint's multisignature freeze authority. + /// 3. ..3+M `[signer]` M signer accounts. + ThawAccount, + + /// Transfers tokens from one account to another either directly or via a + /// delegate. If this account is associated with the native mint then equal + /// amounts of SOL and Tokens will be transferred to the destination + /// account. + /// + /// This instruction differs from Transfer in that the token mint and + /// decimals value is checked by the caller. This may be useful when + /// creating transactions offline or within a hardware wallet. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writable]` The source account. + /// 1. `[]` The token mint. + /// 2. `[writable]` The destination account. + /// 3. `[signer]` The source account's owner/delegate. + /// + /// * Multisignature owner/delegate + /// 0. `[writable]` The source account. + /// 1. `[]` The token mint. + /// 2. `[writable]` The destination account. + /// 3. `[]` The source account's multisignature owner/delegate. + /// 4. ..4+M `[signer]` M signer accounts. + TransferChecked { + /// The amount of tokens to transfer. + amount: u64, + /// Expected number of base 10 digits to the right of the decimal place. + decimals: u8, + }, + /// Approves a delegate. A delegate is given the authority over tokens on + /// behalf of the source account's owner. + /// + /// This instruction differs from Approve in that the token mint and + /// decimals value is checked by the caller. This may be useful when + /// creating transactions offline or within a hardware wallet. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The source account. + /// 1. `[]` The token mint. + /// 2. `[]` The delegate. + /// 3. `[signer]` The source account owner. + /// + /// * Multisignature owner + /// 0. `[writable]` The source account. + /// 1. `[]` The token mint. + /// 2. `[]` The delegate. + /// 3. `[]` The source account's multisignature owner. + /// 4. ..4+M `[signer]` M signer accounts + ApproveChecked { + /// The amount of tokens the delegate is approved for. + amount: u64, + /// Expected number of base 10 digits to the right of the decimal place. + decimals: u8, + }, + /// Mints new tokens to an account. The native mint does not support + /// minting. + /// + /// This instruction differs from MintTo in that the decimals value is + /// checked by the caller. This may be useful when creating transactions + /// offline or within a hardware wallet. + /// + /// Accounts expected by this instruction: + /// + /// * Single authority + /// 0. `[writable]` The mint. + /// 1. `[writable]` The account to mint tokens to. + /// 2. `[signer]` The mint's minting authority. + /// + /// * Multisignature authority + /// 0. `[writable]` The mint. + /// 1. `[writable]` The account to mint tokens to. + /// 2. `[]` The mint's multisignature mint-tokens authority. + /// 3. ..3+M `[signer]` M signer accounts. + MintToChecked { + /// The amount of new tokens to mint. + amount: u64, + /// Expected number of base 10 digits to the right of the decimal place. + decimals: u8, + }, + /// Burns tokens by removing them from an account. `BurnChecked` does not + /// support accounts associated with the native mint, use `CloseAccount` + /// instead. + /// + /// This instruction differs from Burn in that the decimals value is checked + /// by the caller. This may be useful when creating transactions offline or + /// within a hardware wallet. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writable]` The account to burn from. + /// 1. `[writable]` The token mint. + /// 2. `[signer]` The account's owner/delegate. + /// + /// * Multisignature owner/delegate + /// 0. `[writable]` The account to burn from. + /// 1. `[writable]` The token mint. + /// 2. `[]` The account's multisignature owner/delegate. + /// 3. ..3+M `[signer]` M signer accounts. + BurnChecked { + /// The amount of tokens to burn. + amount: u64, + /// Expected number of base 10 digits to the right of the decimal place. + decimals: u8, + }, + /// Like InitializeAccount, but the owner pubkey is passed via instruction data + /// rather than the accounts list. This variant may be preferable when using + /// Cross Program Invocation from an instruction that does not need the owner's + /// `AccountInfo` otherwise. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The account to initialize. + /// 1. `[]` The mint this account will be associated with. + /// 3. `[]` Rent sysvar + InitializeAccount2 { + /// The new account's owner/multisignature. + owner: Vec, + }, + /// Given a wrapped / native token account (a token account containing SOL) + /// updates its amount field based on the account's underlying `lamports`. + /// This is useful if a non-wrapped SOL account uses `system_instruction::transfer` + /// to move lamports to a wrapped token account, and needs to have its token + /// `amount` field updated. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The native token account to sync with its underlying lamports. + SyncNative, + /// Like InitializeAccount2, but does not require the Rent sysvar to be provided + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The account to initialize. + /// 1. `[]` The mint this account will be associated with. + InitializeAccount3 { + /// The new account's owner/multisignature. + owner: Vec, + }, + /// Like InitializeMultisig, but does not require the Rent sysvar to be provided + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The multisignature account to initialize. + /// 1. ..1+N. `[]` The signer accounts, must equal to N where 1 <= N <= + /// 11. + InitializeMultisig2 { + /// The number of signers (M) required to validate this multisignature + /// account. + m: u8, + }, + /// Like InitializeMint, but does not require the Rent sysvar to be provided + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The mint to initialize. + /// + InitializeMint2 { + /// Number of base 10 digits to the right of the decimal place. + decimals: u8, + /// The authority/multisignature to mint tokens. + mint_authority: Vec, + /// The freeze authority/multisignature of the mint. + freeze_authority: COption>, + }, + /// Gets the required size of an account for the given mint as a little-endian + /// `u64`. + /// + /// Return data can be fetched using `sol_get_return_data` and deserializing + /// the return data as a little-endian `u64`. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[]` The mint to calculate for + GetAccountDataSize, // typically, there's also data, but this program ignores it + /// Initialize the Immutable Owner extension for the given token account + /// + /// Fails if the account has already been initialized, so must be called before + /// `InitializeAccount`. + /// + /// No-ops in this version of the program, but is included for compatibility + /// with the Associated Token Account program. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The account to initialize. + /// + /// Data expected by this instruction: + /// None + InitializeImmutableOwner, + /// Convert an Amount of tokens to a UiAmount `string`, using the given mint. + /// In this version of the program, the mint can only specify the number of decimals. + /// + /// Fails on an invalid mint. + /// + /// Return data can be fetched using `sol_get_return_data` and deserialized with + /// `String::from_utf8`. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[]` The mint to calculate for + AmountToUiAmount { + /// The amount of tokens to reformat. + amount: u64, + }, + /// Convert a UiAmount of tokens to a little-endian `u64` raw Amount, using the given mint. + /// In this version of the program, the mint can only specify the number of decimals. + /// + /// Return data can be fetched using `sol_get_return_data` and deserializing + /// the return data as a little-endian `u64`. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[]` The mint to calculate for + UiAmountToAmount { + /// The ui_amount of tokens to reformat. + ui_amount: &'a str, + }, + // Any new variants also need to be added to program-2022 `TokenInstruction`, so that the + // latter remains a superset of this instruction set. New variants also need to be added to + // token/js/src/instructions/types.ts to maintain @solana/spl-token compatability +} + +impl<'a> TokenInstruction<'a> { + pub fn unpack(input: &'a [u8]) -> Result { + let (&tag, rest) = input + .split_first() + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + Ok(match tag { + 0 => { + let (&decimals, rest) = rest + .split_first() + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + let (mint_authority, rest) = Self::unpack_pubkey(rest)?; + let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?; + Self::InitializeMint { + mint_authority, + freeze_authority, + decimals, + } + } + 1 => Self::InitializeAccount, + 2 => { + let &m = rest + .get(0) + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + Self::InitializeMultisig { m } + } + 3 | 4 | 7 | 8 => { + let amount = rest + .get(..8) + .and_then(|slice| slice.try_into().ok()) + .map(u64::from_le_bytes) + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + match tag { + 3 => Self::Transfer { amount }, + 4 => Self::Approve { amount }, + 7 => Self::MintTo { amount }, + 8 => Self::Burn { amount }, + _ => unreachable!(), + } + } + 5 => Self::Revoke, + 6 => { + let (authority_type, rest) = rest + .split_first() + .ok_or_else(|| Error::Unexpected(format!("Invalid Instruction"))) + .and_then(|(&t, rest)| Ok((AuthorityType::from(t)?, rest)))?; + let (new_authority, _rest) = Self::unpack_pubkey_option(rest)?; + + Self::SetAuthority { + authority_type, + new_authority, + } + } + 9 => Self::CloseAccount, + 10 => Self::FreezeAccount, + 11 => Self::ThawAccount, + 12 => { + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + let (&decimals, _rest) = rest + .split_first() + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + + Self::TransferChecked { amount, decimals } + } + 13 => { + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + let (&decimals, _rest) = rest + .split_first() + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + + Self::ApproveChecked { amount, decimals } + } + 14 => { + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + let (&decimals, _rest) = rest + .split_first() + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + + Self::MintToChecked { amount, decimals } + } + 15 => { + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + let (&decimals, _rest) = rest + .split_first() + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + + Self::BurnChecked { amount, decimals } + } + 16 => { + let (owner, _rest) = Self::unpack_pubkey(rest)?; + Self::InitializeAccount2 { owner } + } + 17 => Self::SyncNative, + 18 => { + let (owner, _rest) = Self::unpack_pubkey(rest)?; + Self::InitializeAccount3 { owner } + } + 19 => { + let &m = rest + .get(0) + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + Self::InitializeMultisig2 { m } + } + 20 => { + let (&decimals, rest) = rest + .split_first() + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + let (mint_authority, rest) = Self::unpack_pubkey(rest)?; + let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?; + Self::InitializeMint2 { + mint_authority, + freeze_authority, + decimals, + } + } + 21 => Self::GetAccountDataSize, + 22 => Self::InitializeImmutableOwner, + 23 => { + let (amount, _rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(Error::Unexpected(format!("Invalid Instruction")))?; + Self::AmountToUiAmount { amount } + } + 24 => { + let ui_amount = std::str::from_utf8(rest) + .map_err(|_| Error::Unexpected(format!("Invalid Instruction")))?; + Self::UiAmountToAmount { ui_amount } + } + _ => return Err(Error::Unexpected(format!("Invalid Instruction"))), + }) + } + + fn unpack_pubkey(input: &[u8]) -> Result<(Vec, &[u8]), Error> { + if input.len() >= 32 { + let (key, rest) = input.split_at(32); + let pk = key.to_vec(); + Ok((pk, rest)) + } else { + Err(Error::Unexpected(format!("Invalid Instruction"))) + } + } + + fn unpack_pubkey_option(input: &[u8]) -> Result<(COption>, &[u8]), Error> { + match input.split_first() { + Option::Some((&0, rest)) => Ok((COption::None, rest)), + Option::Some((&1, rest)) if rest.len() >= 32 => { + let (key, rest) = rest.split_at(32); + let pk = key.to_vec(); + Ok((COption::Some(pk), rest)) + } + _ => Err(Error::Unexpected(format!("Invalid Instruction"))), + } + } +} + +/// Specifies the authority type for SetAuthority instructions +#[repr(u8)] +#[derive(Clone, Debug, PartialEq)] +pub enum AuthorityType { + /// Authority to mint new tokens + MintTokens, + /// Authority to freeze any account associated with the Mint + FreezeAccount, + /// Owner of a given token account + AccountOwner, + /// Authority to close a token account + CloseAccount, +} + +impl AuthorityType { + fn from(index: u8) -> Result { + match index { + 0 => Ok(AuthorityType::MintTokens), + 1 => Ok(AuthorityType::FreezeAccount), + 2 => Ok(AuthorityType::AccountOwner), + 3 => Ok(AuthorityType::CloseAccount), + _ => Err(Error::Unexpected(format!("Invalid Instruction"))), + } + } +} diff --git a/spl-balance/src/lib.rs b/spl-balance/src/lib.rs index 1c23d26f..83d7e5a6 100644 --- a/spl-balance/src/lib.rs +++ b/spl-balance/src/lib.rs @@ -4,59 +4,161 @@ use bs58; use num_bigint; use pb::sol_token::v1 as proto; use substreams::scalar::BigInt; -use substreams::store::{StoreNew, StoreSet, StoreSetRaw}; +use substreams::store::{StoreGet, StoreNew, StoreSet, StoreSetRaw}; use substreams::{log, store, Hex}; use substreams_solana::pb::sol as solana; -// Map SPL token balance changes +use crate::instruction::TokenInstruction; + #[substreams::handlers::map] fn map_balances( block: solana::v1::Block, + token_store: StoreGet, ) -> Result { log::info!("extracting SPL balance changes"); - let mut balance_changes = proto::BalanceChanges { items: vec![] }; + let mut balance_changes = vec![]; - for trx in block.transactions { - if let Some(meta) = trx.meta { + for tx in block.transactions { + if let Some(meta) = tx.meta { if let Some(_) = meta.err { continue; } - if let Some(transaction) = trx.transaction { - log::info!("{}", bs58::encode(&transaction.signatures[0]).into_string()) + if let Some(transaction) = tx.transaction { + if let Some(msg) = transaction.message { + for i in 0..meta.pre_token_balances.len() { + let pre_balance = &meta.pre_token_balances[i]; + let post_balance = &meta.post_token_balances[i]; + // TODO pre.mint = token address + if let Some(pre_token_amount) = &pre_balance.ui_token_amount { + if let Some(post_token_amount) = &post_balance.ui_token_amount { + balance_changes.push(proto::TokenBalance { + token: get_token_store(&token_store, &pre_balance.mint), + transaction_id: bs58::encode(&transaction.signatures[0]) + .into_string(), + block_height: block.block_height.as_ref().unwrap().block_height, + address: bs58::encode( + &msg.account_keys[post_balance.account_index as usize], + ) + .into_string(), + pre_balance: pre_token_amount.amount.to_string(), + post_balance: post_token_amount.amount.to_string(), + }); + } + } + } + } } + } + } - for i in 0..meta.pre_token_balances.len() { - let pre_balance = &meta.pre_token_balances[i]; - let post_balance = &meta.post_token_balances[i]; - let mut pre_balance_amount = ""; - let mut post_balance_amount = ""; - // pre.owner = user address - // pre.mint = token address - if let Some(pre_token_amount) = &pre_balance.ui_token_amount { - pre_balance_amount = &pre_token_amount.amount; - } - if let Some(post_token_amount) = &post_balance.ui_token_amount { - post_balance_amount = &post_token_amount.amount; - } + Ok(proto::BalanceChanges { + items: balance_changes, + }) +} - log::info!("{} {} {}", pre_balance_amount, post_balance_amount, meta.pre_token_balances.len()); +#[substreams::handlers::store] +fn store_tokens(block: solana::v1::Block, output: store::StoreSet) { + log::info!("extracting token mints"); + for tx in block.transactions { + if let Some(meta) = tx.meta { + if let Some(_) = meta.err { + continue; + } + if let Some(transaction) = tx.transaction { + if let Some(msg) = transaction.message { + for inst in msg.instructions { + let program_id = &msg.account_keys[inst.program_id_index as usize]; - // balance_changes.push(proto::TokenBalance { - // token: proto::Token {}, // TODO: this should be fed from store_tokens using pre.mint - // transaction_id: bs58::encode(&transaction.signatures[0]).into_string(), - // block_height: block.block_height.block_height, - // account: pre_balance.owner, - // pre_balance: pre_balance_amount, - // post_balance: post_balance_amount, - // }); + // check if the token program is being called to create a token + if bs58::encode(program_id).into_string() + != "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + { + continue; + } + let instruction = TokenInstruction::unpack(&inst.data)?; + match instruction { + TokenInstruction::InitializeMint { + decimals, + mint_authority, + freeze_authority, + } => { + log::info!("Instruction: InitializeMint"); + output.set( + 0, + format!( + "address:{}", + bs58::encode(msg.account_keys[inst.accounts[0] as usize]) + .into_string() + ), + get_token( + msg.account_keys[inst.accounts[0] as usize].to_vec(), + decimals, + mint_authority, + freeze_authority, + ), + ); + } + TokenInstruction::InitializeMint2 { + decimals, + mint_authority, + freeze_authority, + } => { + log::info!("Instruction: InitializeMint2"); + output.set( + 0, + format!( + "address:{}", + bs58::encode(msg.account_keys[inst.accounts[0] as usize]) + .into_string() + ), + get_token( + msg.account_keys[inst.accounts[0] as usize].to_vec(), + decimals, + mint_authority, + freeze_authority, + ), + ); + } + _ => {} + } + } + } } } } +} - Ok(balance_changes) +// helper to create a TokenAccount entity +fn get_token( + token_address: Vec, + decimal: u8, + mint_authority: Vec, + freeze_authority_opt: COption>, +) -> proto::TokenAccount { + let mut token = proto::TokenAccount { + address: bs58::encode(&token_address).into_string(), + name: "".to_string(), + symbol: "".to_string(), + decimals: decimal.into(), + freeze_authority: "".to_string(), + mint_authority: bs58::encode(&mint_authority).into_string(), + }; + + // set freeze authority if it exists + if freeze_authority_opt.is_some() { + token.freeze_authority = bs58::encode(&freeze_authority_opt.unwrap()).into_string(); + } + return token; } -// #[substreams::handlers::store] -// fn store_tokens(block: solana::v1::Block, output: store::StoreSet) { -// // TODO -// } +fn get_token_store( + token_store: &StoreGet, + token_address: &String, +) -> Result { + return match &token_store.get_last(format!("address:{}", &token_address)) { + None => Err(Error::Unexpected( + format!("token {} not found", token_address).to_string(), + )), + Some(bytes) => Ok(decode(bytes).unwrap()), + }; +} diff --git a/spl-balance/substreams.yaml b/spl-balance/substreams.yaml index 4fbf9207..bca9dae4 100644 --- a/spl-balance/substreams.yaml +++ b/spl-balance/substreams.yaml @@ -23,14 +23,14 @@ modules: kind: map inputs: - source: sf.solana.type.v1.Block -# - source: store_tokens + - store: store_tokens output: type: proto:solana.spl.v1.BalanceChanges # stores all SPL tokens to retrieve -# - name: store_tokens -# kind: store -# updatePolicy: set -# valueType: proto:messari.sol_token.v1.Token -# inputs: -# - source: sf.solana.type.v1.Block + - name: store_tokens + kind: store + updatePolicy: set + valueType: proto:messari.sol_token.v1.Token + inputs: + - source: sf.solana.type.v1.Block From c8713b41b33776181f9e540b9c71960eb6a8d06f Mon Sep 17 00:00:00 2001 From: dmelotik Date: Wed, 1 Feb 2023 22:31:33 -0700 Subject: [PATCH 11/12] fix core file build errors --- README.md | 20 ++++---- common/proto/sol_token.proto | 4 +- spl-balance/README.md | 3 +- spl-balance/src/abi.rs | 1 - spl-balance/src/instruction.rs | 2 +- spl-balance/src/lib.rs | 90 ++++++++++++++++++++-------------- spl-balance/substreams.yaml | 2 +- 7 files changed, 69 insertions(+), 53 deletions(-) delete mode 100644 spl-balance/src/abi.rs diff --git a/README.md b/README.md index 8704de8e..5cf75919 100644 --- a/README.md +++ b/README.md @@ -33,16 +33,16 @@ 🛠 = Feature complete. Additional testing required. ✅ = Production-ready. -| Substream | Status | Description | -|------------------| :------: |--------------------------------------------| -| Ethereum Network | 🔨 | Network-level metrics and statistics | -| ETH Balance | 🛠 | ETH balance for every Ethereum address | -| ERC20 Holdings | 🛠 | ERC20 balance for every Ethereum address | -| ERC20 Price | 🛠 | Pricing module for ERC20 tokens | -| SPL Holdings | | SPL token balance for every Solana address | -| ENS Look Up | 🔨 | ENS records for lookup and reverse lookup | -| Uniswap v2 | 🔨 | Substreams for Uniswap v2 | -| Compound v2 | 🔨 | Substreams for Compound v2 | +| Substream | Status | Description | +|------------------|:------:|--------------------------------------------| +| Ethereum Network | 🔨 | Network-level metrics and statistics | +| ETH Balance | 🛠 | ETH balance for every Ethereum address | +| ERC20 Holdings | 🛠 | ERC20 balance for every Ethereum address | +| ERC20 Price | 🛠 | Pricing module for ERC20 tokens | +| SPL Holdings | 🛠 | SPL token balance for every Solana address | +| ENS Look Up | 🔨 | ENS records for lookup and reverse lookup | +| Uniswap v2 | 🔨 | Substreams for Uniswap v2 | +| Compound v2 | 🔨 | Substreams for Compound v2 | ## Workflow diff --git a/common/proto/sol_token.proto b/common/proto/sol_token.proto index 92f3eab1..fba92695 100644 --- a/common/proto/sol_token.proto +++ b/common/proto/sol_token.proto @@ -42,4 +42,6 @@ message TokenAccount { uint64 decimals = 4; string freeze_authority = 5; string mint_authority = 6; -} \ No newline at end of file + string tx_created = 7; + uint64 block_created = 8; +} diff --git a/spl-balance/README.md b/spl-balance/README.md index d70508bf..d9bc8411 100644 --- a/spl-balance/README.md +++ b/spl-balance/README.md @@ -1,6 +1,7 @@ # Solana SPL Token Balances -This substream is designed to get the token balances of all SPL tokens on Solana. +This substream is designed to get the token balances of all SPL tokens on Solana. In addition `store_tokens` will get all SPL tokens and metadata. ## Notes +- TODO \ No newline at end of file diff --git a/spl-balance/src/abi.rs b/spl-balance/src/abi.rs deleted file mode 100644 index d55d99b3..00000000 --- a/spl-balance/src/abi.rs +++ /dev/null @@ -1 +0,0 @@ -// DO NOT EDIT - the file is generated by build script diff --git a/spl-balance/src/instruction.rs b/spl-balance/src/instruction.rs index 873eda92..cdd5faa3 100644 --- a/spl-balance/src/instruction.rs +++ b/spl-balance/src/instruction.rs @@ -1,4 +1,4 @@ -use {crate::option::COption, std::convert::TryInto, substreams::errors::Error}; +use {core::option::COption, std::convert::TryInto, substreams::errors::Error}; /// Instructions supported by the token program. #[repr(C)] diff --git a/spl-balance/src/lib.rs b/spl-balance/src/lib.rs index 83d7e5a6..c8b134ca 100644 --- a/spl-balance/src/lib.rs +++ b/spl-balance/src/lib.rs @@ -1,19 +1,16 @@ +pub mod instruction; pub mod pb; use bs58; -use num_bigint; use pb::sol_token::v1 as proto; -use substreams::scalar::BigInt; -use substreams::store::{StoreGet, StoreNew, StoreSet, StoreSetRaw}; -use substreams::{log, store, Hex}; +use substreams::log; +use substreams::store::{StoreGet, StoreGetProto, StoreNew, StoreSet, StoreSetProto}; use substreams_solana::pb::sol as solana; -use crate::instruction::TokenInstruction; - #[substreams::handlers::map] fn map_balances( block: solana::v1::Block, - token_store: StoreGet, + token_store: StoreGetProto, ) -> Result { log::info!("extracting SPL balance changes"); let mut balance_changes = vec![]; @@ -31,18 +28,26 @@ fn map_balances( // TODO pre.mint = token address if let Some(pre_token_amount) = &pre_balance.ui_token_amount { if let Some(post_token_amount) = &post_balance.ui_token_amount { - balance_changes.push(proto::TokenBalance { - token: get_token_store(&token_store, &pre_balance.mint), - transaction_id: bs58::encode(&transaction.signatures[0]) + let try_token_store = + token_store.get_last(&format!("address:{}", &pre_balance.mint)); + if let Some(token) = try_token_store { + balance_changes.push(proto::TokenBalance { + token: Some(token), + transaction_id: bs58::encode(&transaction.signatures[0]) + .into_string(), + block_height: block + .block_height + .as_ref() + .unwrap() + .block_height, + address: bs58::encode( + &msg.account_keys[post_balance.account_index as usize], + ) .into_string(), - block_height: block.block_height.as_ref().unwrap().block_height, - address: bs58::encode( - &msg.account_keys[post_balance.account_index as usize], - ) - .into_string(), - pre_balance: pre_token_amount.amount.to_string(), - post_balance: post_token_amount.amount.to_string(), - }); + pre_balance: pre_token_amount.amount.to_string(), + post_balance: post_token_amount.amount.to_string(), + }); + } } } } @@ -57,7 +62,7 @@ fn map_balances( } #[substreams::handlers::store] -fn store_tokens(block: solana::v1::Block, output: store::StoreSet) { +fn store_tokens(block: solana::v1::Block, output: StoreSetProto) { log::info!("extracting token mints"); for tx in block.transactions { if let Some(meta) = tx.meta { @@ -75,13 +80,13 @@ fn store_tokens(block: solana::v1::Block, output: store::StoreSet) { { continue; } - let instruction = TokenInstruction::unpack(&inst.data)?; + let instruction = instruction::TokenInstruction::unpack(&inst.data); match instruction { - TokenInstruction::InitializeMint { + Ok(instruction::TokenInstruction::InitializeMint { decimals, mint_authority, freeze_authority, - } => { + }) => { log::info!("Instruction: InitializeMint"); output.set( 0, @@ -90,19 +95,21 @@ fn store_tokens(block: solana::v1::Block, output: store::StoreSet) { bs58::encode(msg.account_keys[inst.accounts[0] as usize]) .into_string() ), - get_token( + &get_token( msg.account_keys[inst.accounts[0] as usize].to_vec(), decimals, mint_authority, freeze_authority, + bs58::encode(&transaction.signatures[0]).into_string(), + block.block_height.as_ref().unwrap().block_height, ), ); } - TokenInstruction::InitializeMint2 { + Ok(instruction::TokenInstruction::InitializeMint2 { decimals, mint_authority, freeze_authority, - } => { + }) => { log::info!("Instruction: InitializeMint2"); output.set( 0, @@ -111,11 +118,13 @@ fn store_tokens(block: solana::v1::Block, output: store::StoreSet) { bs58::encode(msg.account_keys[inst.accounts[0] as usize]) .into_string() ), - get_token( + &get_token( msg.account_keys[inst.accounts[0] as usize].to_vec(), decimals, mint_authority, freeze_authority, + bs58::encode(&transaction.signatures[0]).into_string(), + block.block_height.as_ref().unwrap().block_height, ), ); } @@ -133,7 +142,9 @@ fn get_token( token_address: Vec, decimal: u8, mint_authority: Vec, - freeze_authority_opt: COption>, + freeze_authority_opt: Option>, + tx_id: String, + block_height: u64, ) -> proto::TokenAccount { let mut token = proto::TokenAccount { address: bs58::encode(&token_address).into_string(), @@ -142,6 +153,8 @@ fn get_token( decimals: decimal.into(), freeze_authority: "".to_string(), mint_authority: bs58::encode(&mint_authority).into_string(), + tx_created: tx_id, + block_created: block_height, }; // set freeze authority if it exists @@ -151,14 +164,15 @@ fn get_token( return token; } -fn get_token_store( - token_store: &StoreGet, - token_address: &String, -) -> Result { - return match &token_store.get_last(format!("address:{}", &token_address)) { - None => Err(Error::Unexpected( - format!("token {} not found", token_address).to_string(), - )), - Some(bytes) => Ok(decode(bytes).unwrap()), - }; -} +// pull the TokenAccount using the key provided +// fn get_token_store( +// token_store: StoreGetProto, +// token_address: &String, +// ) -> Result { +// return match &token_store.get_last(format!("address:{}", &token_address)) { +// None => Err(Error::Unexpected( +// format!("token {} not found", token_address).to_string(), +// )), +// Some(token) => token, +// }; +// } diff --git a/spl-balance/substreams.yaml b/spl-balance/substreams.yaml index bca9dae4..422405e0 100644 --- a/spl-balance/substreams.yaml +++ b/spl-balance/substreams.yaml @@ -31,6 +31,6 @@ modules: - name: store_tokens kind: store updatePolicy: set - valueType: proto:messari.sol_token.v1.Token + valueType: proto:messari.sol_token.v1.TokenAccount inputs: - source: sf.solana.type.v1.Block From 426d351c7536887bf28e6902ccc64ba8836a558d Mon Sep 17 00:00:00 2001 From: dmelotik Date: Thu, 2 Feb 2023 10:45:03 -0700 Subject: [PATCH 12/12] push new files --- spl-balance/src/abi.rs | 1 + spl-balance/src/instruction.rs | 4 +- spl-balance/src/option.rs | 951 +++++++++++++++++++++++++++++++++ 3 files changed, 955 insertions(+), 1 deletion(-) create mode 100644 spl-balance/src/abi.rs create mode 100644 spl-balance/src/option.rs diff --git a/spl-balance/src/abi.rs b/spl-balance/src/abi.rs new file mode 100644 index 00000000..d55d99b3 --- /dev/null +++ b/spl-balance/src/abi.rs @@ -0,0 +1 @@ +// DO NOT EDIT - the file is generated by build script diff --git a/spl-balance/src/instruction.rs b/spl-balance/src/instruction.rs index cdd5faa3..a423d2de 100644 --- a/spl-balance/src/instruction.rs +++ b/spl-balance/src/instruction.rs @@ -1,4 +1,6 @@ -use {core::option::COption, std::convert::TryInto, substreams::errors::Error}; +use crate::option::COption; +use std::convert::TryInto; +use substreams::errors::Error; /// Instructions supported by the token program. #[repr(C)] diff --git a/spl-balance/src/option.rs b/spl-balance/src/option.rs new file mode 100644 index 00000000..c5e78807 --- /dev/null +++ b/spl-balance/src/option.rs @@ -0,0 +1,951 @@ +//! A C representation of Rust's `std::option::Option` used across the FFI +//! boundary for Solana program interfaces +//! +//! This implementation mostly matches `std::option` except iterators since the iteration +//! trait requires returning `std::option::Option` + +use std::{ + convert, mem, + ops::{Deref, DerefMut}, +}; + +/// A C representation of Rust's `std::option::Option` +#[repr(C)] +#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +pub enum COption { + /// No value + None, + /// Some value `T` + Some(T), +} + +///////////////////////////////////////////////////////////////////////////// +// Type implementation +///////////////////////////////////////////////////////////////////////////// + +impl COption { + ///////////////////////////////////////////////////////////////////////// + // Querying the contained values + ///////////////////////////////////////////////////////////////////////// + + /// Returns `true` if the option is a [`COption::Some`] value. + /// + /// # Examples + /// + /// ```ignore + /// let x: COption = COption::Some(2); + /// assert_eq!(x.is_some(), true); + /// + /// let x: COption = COption::None; + /// assert_eq!(x.is_some(), false); + /// ``` + /// + /// [`COption::Some`]: #variant.COption::Some + #[must_use = "if you intended to assert that this has a value, consider `.unwrap()` instead"] + #[inline] + pub fn is_some(&self) -> bool { + match *self { + COption::Some(_) => true, + COption::None => false, + } + } + + /// Returns `true` if the option is a [`COption::None`] value. + /// + /// # Examples + /// + /// ```ignore + /// let x: COption = COption::Some(2); + /// assert_eq!(x.is_none(), false); + /// + /// let x: COption = COption::None; + /// assert_eq!(x.is_none(), true); + /// ``` + /// + /// [`COption::None`]: #variant.COption::None + #[must_use = "if you intended to assert that this doesn't have a value, consider \ + `.and_then(|| panic!(\"`COption` had a value when expected `COption::None`\"))` instead"] + #[inline] + pub fn is_none(&self) -> bool { + !self.is_some() + } + + /// Returns `true` if the option is a [`COption::Some`] value containing the given value. + /// + /// # Examples + /// + /// ```ignore + /// #![feature(option_result_contains)] + /// + /// let x: COption = COption::Some(2); + /// assert_eq!(x.contains(&2), true); + /// + /// let x: COption = COption::Some(3); + /// assert_eq!(x.contains(&2), false); + /// + /// let x: COption = COption::None; + /// assert_eq!(x.contains(&2), false); + /// ``` + #[must_use] + #[inline] + pub fn contains(&self, x: &U) -> bool + where + U: PartialEq, + { + match self { + COption::Some(y) => x == y, + COption::None => false, + } + } + + ///////////////////////////////////////////////////////////////////////// + // Adapter for working with references + ///////////////////////////////////////////////////////////////////////// + + /// Converts from `&COption` to `COption<&T>`. + /// + /// # Examples + /// + /// Converts an `COption<`[`String`]`>` into an `COption<`[`usize`]`>`, preserving the original. + /// The [`map`] method takes the `self` argument by value, consuming the original, + /// so this technique uses `as_ref` to first take an `COption` to a reference + /// to the value inside the original. + /// + /// [`map`]: enum.COption.html#method.map + /// [`String`]: ../../std/string/struct.String.html + /// [`usize`]: ../../std/primitive.usize.html + /// + /// ```ignore + /// let text: COption = COption::Some("Hello, world!".to_string()); + /// // First, cast `COption` to `COption<&String>` with `as_ref`, + /// // then consume *that* with `map`, leaving `text` on the stack. + /// let text_length: COption = text.as_ref().map(|s| s.len()); + /// println!("still can print text: {:?}", text); + /// ``` + #[inline] + pub fn as_ref(&self) -> COption<&T> { + match *self { + COption::Some(ref x) => COption::Some(x), + COption::None => COption::None, + } + } + + /// Converts from `&mut COption` to `COption<&mut T>`. + /// + /// # Examples + /// + /// ```ignore + /// let mut x = COption::Some(2); + /// match x.as_mut() { + /// COption::Some(v) => *v = 42, + /// COption::None => {}, + /// } + /// assert_eq!(x, COption::Some(42)); + /// ``` + #[inline] + pub fn as_mut(&mut self) -> COption<&mut T> { + match *self { + COption::Some(ref mut x) => COption::Some(x), + COption::None => COption::None, + } + } + + ///////////////////////////////////////////////////////////////////////// + // Getting to contained values + ///////////////////////////////////////////////////////////////////////// + + /// Unwraps an option, yielding the content of a [`COption::Some`]. + /// + /// # Panics + /// + /// Panics if the value is a [`COption::None`] with a custom panic message provided by + /// `msg`. + /// + /// [`COption::Some`]: #variant.COption::Some + /// [`COption::None`]: #variant.COption::None + /// + /// # Examples + /// + /// ```ignore + /// let x = COption::Some("value"); + /// assert_eq!(x.expect("the world is ending"), "value"); + /// ``` + /// + /// ```ignore{.should_panic} + /// let x: COption<&str> = COption::None; + /// x.expect("the world is ending"); // panics with `the world is ending` + /// ``` + #[inline] + pub fn expect(self, msg: &str) -> T { + match self { + COption::Some(val) => val, + COption::None => expect_failed(msg), + } + } + + /// Moves the value `v` out of the `COption` if it is [`COption::Some(v)`]. + /// + /// In general, because this function may panic, its use is discouraged. + /// Instead, prefer to use pattern matching and handle the [`COption::None`] + /// case explicitly. + /// + /// # Panics + /// + /// Panics if the self value equals [`COption::None`]. + /// + /// [`COption::Some(v)`]: #variant.COption::Some + /// [`COption::None`]: #variant.COption::None + /// + /// # Examples + /// + /// ```ignore + /// let x = COption::Some("air"); + /// assert_eq!(x.unwrap(), "air"); + /// ``` + /// + /// ```ignore{.should_panic} + /// let x: COption<&str> = COption::None; + /// assert_eq!(x.unwrap(), "air"); // fails + /// ``` + #[inline] + pub fn unwrap(self) -> T { + match self { + COption::Some(val) => val, + COption::None => panic!("called `COption::unwrap()` on a `COption::None` value"), + } + } + + /// Returns the contained value or a default. + /// + /// Arguments passed to `unwrap_or` are eagerly evaluated; if you are passing + /// the result of a function call, it is recommended to use [`unwrap_or_else`], + /// which is lazily evaluated. + /// + /// [`unwrap_or_else`]: #method.unwrap_or_else + /// + /// # Examples + /// + /// ```ignore + /// assert_eq!(COption::Some("car").unwrap_or("bike"), "car"); + /// assert_eq!(COption::None.unwrap_or("bike"), "bike"); + /// ``` + #[inline] + pub fn unwrap_or(self, def: T) -> T { + match self { + COption::Some(x) => x, + COption::None => def, + } + } + + /// Returns the contained value or computes it from a closure. + /// + /// # Examples + /// + /// ```ignore + /// let k = 10; + /// assert_eq!(COption::Some(4).unwrap_or_else(|| 2 * k), 4); + /// assert_eq!(COption::None.unwrap_or_else(|| 2 * k), 20); + /// ``` + #[inline] + pub fn unwrap_or_else T>(self, f: F) -> T { + match self { + COption::Some(x) => x, + COption::None => f(), + } + } + + ///////////////////////////////////////////////////////////////////////// + // Transforming contained values + ///////////////////////////////////////////////////////////////////////// + + /// Maps an `COption` to `COption` by applying a function to a contained value. + /// + /// # Examples + /// + /// Converts an `COption<`[`String`]`>` into an `COption<`[`usize`]`>`, consuming the original: + /// + /// [`String`]: ../../std/string/struct.String.html + /// [`usize`]: ../../std/primitive.usize.html + /// + /// ```ignore + /// let maybe_some_string = COption::Some(String::from("Hello, World!")); + /// // `COption::map` takes self *by value*, consuming `maybe_some_string` + /// let maybe_some_len = maybe_some_string.map(|s| s.len()); + /// + /// assert_eq!(maybe_some_len, COption::Some(13)); + /// ``` + #[inline] + pub fn map U>(self, f: F) -> COption { + match self { + COption::Some(x) => COption::Some(f(x)), + COption::None => COption::None, + } + } + + /// Applies a function to the contained value (if any), + /// or returns the provided default (if not). + /// + /// # Examples + /// + /// ```ignore + /// let x = COption::Some("foo"); + /// assert_eq!(x.map_or(42, |v| v.len()), 3); + /// + /// let x: COption<&str> = COption::None; + /// assert_eq!(x.map_or(42, |v| v.len()), 42); + /// ``` + #[inline] + pub fn map_or U>(self, default: U, f: F) -> U { + match self { + COption::Some(t) => f(t), + COption::None => default, + } + } + + /// Applies a function to the contained value (if any), + /// or computes a default (if not). + /// + /// # Examples + /// + /// ```ignore + /// let k = 21; + /// + /// let x = COption::Some("foo"); + /// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 3); + /// + /// let x: COption<&str> = COption::None; + /// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 42); + /// ``` + #[inline] + pub fn map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U { + match self { + COption::Some(t) => f(t), + COption::None => default(), + } + } + + /// Transforms the `COption` into a [`Result`], mapping [`COption::Some(v)`] to + /// [`Ok(v)`] and [`COption::None`] to [`Err(err)`]. + /// + /// Arguments passed to `ok_or` are eagerly evaluated; if you are passing the + /// result of a function call, it is recommended to use [`ok_or_else`], which is + /// lazily evaluated. + /// + /// [`Result`]: ../../std/result/enum.Result.html + /// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok + /// [`Err(err)`]: ../../std/result/enum.Result.html#variant.Err + /// [`COption::None`]: #variant.COption::None + /// [`COption::Some(v)`]: #variant.COption::Some + /// [`ok_or_else`]: #method.ok_or_else + /// + /// # Examples + /// + /// ```ignore + /// let x = COption::Some("foo"); + /// assert_eq!(x.ok_or(0), Ok("foo")); + /// + /// let x: COption<&str> = COption::None; + /// assert_eq!(x.ok_or(0), Err(0)); + /// ``` + #[inline] + pub fn ok_or(self, err: E) -> Result { + match self { + COption::Some(v) => Ok(v), + COption::None => Err(err), + } + } + + /// Transforms the `COption` into a [`Result`], mapping [`COption::Some(v)`] to + /// [`Ok(v)`] and [`COption::None`] to [`Err(err())`]. + /// + /// [`Result`]: ../../std/result/enum.Result.html + /// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok + /// [`Err(err())`]: ../../std/result/enum.Result.html#variant.Err + /// [`COption::None`]: #variant.COption::None + /// [`COption::Some(v)`]: #variant.COption::Some + /// + /// # Examples + /// + /// ```ignore + /// let x = COption::Some("foo"); + /// assert_eq!(x.ok_or_else(|| 0), Ok("foo")); + /// + /// let x: COption<&str> = COption::None; + /// assert_eq!(x.ok_or_else(|| 0), Err(0)); + /// ``` + #[inline] + pub fn ok_or_else E>(self, err: F) -> Result { + match self { + COption::Some(v) => Ok(v), + COption::None => Err(err()), + } + } + + ///////////////////////////////////////////////////////////////////////// + // Boolean operations on the values, eager and lazy + ///////////////////////////////////////////////////////////////////////// + + /// Returns [`COption::None`] if the option is [`COption::None`], otherwise returns `optb`. + /// + /// [`COption::None`]: #variant.COption::None + /// + /// # Examples + /// + /// ```ignore + /// let x = COption::Some(2); + /// let y: COption<&str> = COption::None; + /// assert_eq!(x.and(y), COption::None); + /// + /// let x: COption = COption::None; + /// let y = COption::Some("foo"); + /// assert_eq!(x.and(y), COption::None); + /// + /// let x = COption::Some(2); + /// let y = COption::Some("foo"); + /// assert_eq!(x.and(y), COption::Some("foo")); + /// + /// let x: COption = COption::None; + /// let y: COption<&str> = COption::None; + /// assert_eq!(x.and(y), COption::None); + /// ``` + #[inline] + pub fn and(self, optb: COption) -> COption { + match self { + COption::Some(_) => optb, + COption::None => COption::None, + } + } + + /// Returns [`COption::None`] if the option is [`COption::None`], otherwise calls `f` with the + /// wrapped value and returns the result. + /// + /// COption::Some languages call this operation flatmap. + /// + /// [`COption::None`]: #variant.COption::None + /// + /// # Examples + /// + /// ```ignore + /// fn sq(x: u32) -> COption { COption::Some(x * x) } + /// fn nope(_: u32) -> COption { COption::None } + /// + /// assert_eq!(COption::Some(2).and_then(sq).and_then(sq), COption::Some(16)); + /// assert_eq!(COption::Some(2).and_then(sq).and_then(nope), COption::None); + /// assert_eq!(COption::Some(2).and_then(nope).and_then(sq), COption::None); + /// assert_eq!(COption::None.and_then(sq).and_then(sq), COption::None); + /// ``` + #[inline] + pub fn and_then COption>(self, f: F) -> COption { + match self { + COption::Some(x) => f(x), + COption::None => COption::None, + } + } + + /// Returns [`COption::None`] if the option is [`COption::None`], otherwise calls `predicate` + /// with the wrapped value and returns: + /// + /// - [`COption::Some(t)`] if `predicate` returns `true` (where `t` is the wrapped + /// value), and + /// - [`COption::None`] if `predicate` returns `false`. + /// + /// This function works similar to [`Iterator::filter()`]. You can imagine + /// the `COption` being an iterator over one or zero elements. `filter()` + /// lets you decide which elements to keep. + /// + /// # Examples + /// + /// ```ignore + /// fn is_even(n: &i32) -> bool { + /// n % 2 == 0 + /// } + /// + /// assert_eq!(COption::None.filter(is_even), COption::None); + /// assert_eq!(COption::Some(3).filter(is_even), COption::None); + /// assert_eq!(COption::Some(4).filter(is_even), COption::Some(4)); + /// ``` + /// + /// [`COption::None`]: #variant.COption::None + /// [`COption::Some(t)`]: #variant.COption::Some + /// [`Iterator::filter()`]: ../../std/iter/trait.Iterator.html#method.filter + #[inline] + pub fn filter bool>(self, predicate: P) -> Self { + if let COption::Some(x) = self { + if predicate(&x) { + return COption::Some(x); + } + } + COption::None + } + + /// Returns the option if it contains a value, otherwise returns `optb`. + /// + /// Arguments passed to `or` are eagerly evaluated; if you are passing the + /// result of a function call, it is recommended to use [`or_else`], which is + /// lazily evaluated. + /// + /// [`or_else`]: #method.or_else + /// + /// # Examples + /// + /// ```ignore + /// let x = COption::Some(2); + /// let y = COption::None; + /// assert_eq!(x.or(y), COption::Some(2)); + /// + /// let x = COption::None; + /// let y = COption::Some(100); + /// assert_eq!(x.or(y), COption::Some(100)); + /// + /// let x = COption::Some(2); + /// let y = COption::Some(100); + /// assert_eq!(x.or(y), COption::Some(2)); + /// + /// let x: COption = COption::None; + /// let y = COption::None; + /// assert_eq!(x.or(y), COption::None); + /// ``` + #[inline] + pub fn or(self, optb: COption) -> COption { + match self { + COption::Some(_) => self, + COption::None => optb, + } + } + + /// Returns the option if it contains a value, otherwise calls `f` and + /// returns the result. + /// + /// # Examples + /// + /// ```ignore + /// fn nobody() -> COption<&'static str> { COption::None } + /// fn vikings() -> COption<&'static str> { COption::Some("vikings") } + /// + /// assert_eq!(COption::Some("barbarians").or_else(vikings), COption::Some("barbarians")); + /// assert_eq!(COption::None.or_else(vikings), COption::Some("vikings")); + /// assert_eq!(COption::None.or_else(nobody), COption::None); + /// ``` + #[inline] + pub fn or_else COption>(self, f: F) -> COption { + match self { + COption::Some(_) => self, + COption::None => f(), + } + } + + /// Returns [`COption::Some`] if exactly one of `self`, `optb` is [`COption::Some`], otherwise returns [`COption::None`]. + /// + /// [`COption::Some`]: #variant.COption::Some + /// [`COption::None`]: #variant.COption::None + /// + /// # Examples + /// + /// ```ignore + /// let x = COption::Some(2); + /// let y: COption = COption::None; + /// assert_eq!(x.xor(y), COption::Some(2)); + /// + /// let x: COption = COption::None; + /// let y = COption::Some(2); + /// assert_eq!(x.xor(y), COption::Some(2)); + /// + /// let x = COption::Some(2); + /// let y = COption::Some(2); + /// assert_eq!(x.xor(y), COption::None); + /// + /// let x: COption = COption::None; + /// let y: COption = COption::None; + /// assert_eq!(x.xor(y), COption::None); + /// ``` + #[inline] + pub fn xor(self, optb: COption) -> COption { + match (self, optb) { + (COption::Some(a), COption::None) => COption::Some(a), + (COption::None, COption::Some(b)) => COption::Some(b), + _ => COption::None, + } + } + + ///////////////////////////////////////////////////////////////////////// + // Entry-like operations to insert if COption::None and return a reference + ///////////////////////////////////////////////////////////////////////// + + /// Inserts `v` into the option if it is [`COption::None`], then + /// returns a mutable reference to the contained value. + /// + /// [`COption::None`]: #variant.COption::None + /// + /// # Examples + /// + /// ```ignore + /// let mut x = COption::None; + /// + /// { + /// let y: &mut u32 = x.get_or_insert(5); + /// assert_eq!(y, &5); + /// + /// *y = 7; + /// } + /// + /// assert_eq!(x, COption::Some(7)); + /// ``` + #[inline] + pub fn get_or_insert(&mut self, v: T) -> &mut T { + self.get_or_insert_with(|| v) + } + + /// Inserts a value computed from `f` into the option if it is [`COption::None`], then + /// returns a mutable reference to the contained value. + /// + /// [`COption::None`]: #variant.COption::None + /// + /// # Examples + /// + /// ```ignore + /// let mut x = COption::None; + /// + /// { + /// let y: &mut u32 = x.get_or_insert_with(|| 5); + /// assert_eq!(y, &5); + /// + /// *y = 7; + /// } + /// + /// assert_eq!(x, COption::Some(7)); + /// ``` + #[inline] + pub fn get_or_insert_with T>(&mut self, f: F) -> &mut T { + if let COption::None = *self { + *self = COption::Some(f()) + } + + match *self { + COption::Some(ref mut v) => v, + COption::None => unreachable!(), + } + } + + ///////////////////////////////////////////////////////////////////////// + // Misc + ///////////////////////////////////////////////////////////////////////// + + /// Replaces the actual value in the option by the value given in parameter, + /// returning the old value if present, + /// leaving a [`COption::Some`] in its place without deinitializing either one. + /// + /// [`COption::Some`]: #variant.COption::Some + /// + /// # Examples + /// + /// ```ignore + /// let mut x = COption::Some(2); + /// let old = x.replace(5); + /// assert_eq!(x, COption::Some(5)); + /// assert_eq!(old, COption::Some(2)); + /// + /// let mut x = COption::None; + /// let old = x.replace(3); + /// assert_eq!(x, COption::Some(3)); + /// assert_eq!(old, COption::None); + /// ``` + #[inline] + pub fn replace(&mut self, value: T) -> COption { + mem::replace(self, COption::Some(value)) + } +} + +impl COption<&T> { + /// Maps an `COption<&T>` to an `COption` by copying the contents of the + /// option. + /// + /// # Examples + /// + /// ```ignore + /// let x = 12; + /// let opt_x = COption::Some(&x); + /// assert_eq!(opt_x, COption::Some(&12)); + /// let copied = opt_x.copied(); + /// assert_eq!(copied, COption::Some(12)); + /// ``` + pub fn copied(self) -> COption { + self.map(|&t| t) + } +} + +impl COption<&mut T> { + /// Maps an `COption<&mut T>` to an `COption` by copying the contents of the + /// option. + /// + /// # Examples + /// + /// ```ignore + /// let mut x = 12; + /// let opt_x = COption::Some(&mut x); + /// assert_eq!(opt_x, COption::Some(&mut 12)); + /// let copied = opt_x.copied(); + /// assert_eq!(copied, COption::Some(12)); + /// ``` + pub fn copied(self) -> COption { + self.map(|&mut t| t) + } +} + +impl COption<&T> { + /// Maps an `COption<&T>` to an `COption` by cloning the contents of the + /// option. + /// + /// # Examples + /// + /// ```ignore + /// let x = 12; + /// let opt_x = COption::Some(&x); + /// assert_eq!(opt_x, COption::Some(&12)); + /// let cloned = opt_x.cloned(); + /// assert_eq!(cloned, COption::Some(12)); + /// ``` + pub fn cloned(self) -> COption { + self.map(|t| t.clone()) + } +} + +impl COption<&mut T> { + /// Maps an `COption<&mut T>` to an `COption` by cloning the contents of the + /// option. + /// + /// # Examples + /// + /// ```ignore + /// let mut x = 12; + /// let opt_x = COption::Some(&mut x); + /// assert_eq!(opt_x, COption::Some(&mut 12)); + /// let cloned = opt_x.cloned(); + /// assert_eq!(cloned, COption::Some(12)); + /// ``` + pub fn cloned(self) -> COption { + self.map(|t| t.clone()) + } +} + +impl COption { + /// Returns the contained value or a default + /// + /// Consumes the `self` argument then, if [`COption::Some`], returns the contained + /// value, otherwise if [`COption::None`], returns the [default value] for that + /// type. + /// + /// # Examples + /// + /// Converts a string to an integer, turning poorly-formed strings + /// into 0 (the default value for integers). [`parse`] converts + /// a string to any other type that implements [`FromStr`], returning + /// [`COption::None`] on error. + /// + /// ```ignore + /// let good_year_from_input = "1909"; + /// let bad_year_from_input = "190blarg"; + /// let good_year = good_year_from_input.parse().ok().unwrap_or_default(); + /// let bad_year = bad_year_from_input.parse().ok().unwrap_or_default(); + /// + /// assert_eq!(1909, good_year); + /// assert_eq!(0, bad_year); + /// ``` + /// + /// [`COption::Some`]: #variant.COption::Some + /// [`COption::None`]: #variant.COption::None + /// [default value]: ../default/trait.Default.html#tymethod.default + /// [`parse`]: ../../std/primitive.str.html#method.parse + /// [`FromStr`]: ../../std/str/trait.FromStr.html + #[inline] + pub fn unwrap_or_default(self) -> T { + match self { + COption::Some(x) => x, + COption::None => T::default(), + } + } +} + +impl COption { + /// Converts from `COption` (or `&COption`) to `COption<&T::Target>`. + /// + /// Leaves the original COption in-place, creating a new one with a reference + /// to the original one, additionally coercing the contents via [`Deref`]. + /// + /// [`Deref`]: ../../std/ops/trait.Deref.html + /// + /// # Examples + /// + /// ```ignore + /// #![feature(inner_deref)] + /// + /// let x: COption = COption::Some("hey".to_owned()); + /// assert_eq!(x.as_deref(), COption::Some("hey")); + /// + /// let x: COption = COption::None; + /// assert_eq!(x.as_deref(), COption::None); + /// ``` + pub fn as_deref(&self) -> COption<&T::Target> { + self.as_ref().map(|t| t.deref()) + } +} + +impl COption { + /// Converts from `COption` (or `&mut COption`) to `COption<&mut T::Target>`. + /// + /// Leaves the original `COption` in-place, creating a new one containing a mutable reference to + /// the inner type's `Deref::Target` type. + /// + /// # Examples + /// + /// ```ignore + /// #![feature(inner_deref)] + /// + /// let mut x: COption = COption::Some("hey".to_owned()); + /// assert_eq!(x.as_deref_mut().map(|x| { + /// x.make_ascii_uppercase(); + /// x + /// }), COption::Some("HEY".to_owned().as_mut_str())); + /// ``` + pub fn as_deref_mut(&mut self) -> COption<&mut T::Target> { + self.as_mut().map(|t| t.deref_mut()) + } +} + +impl COption> { + /// Transposes an `COption` of a [`Result`] into a [`Result`] of an `COption`. + /// + /// [`COption::None`] will be mapped to [`Ok`]`(`[`COption::None`]`)`. + /// [`COption::Some`]`(`[`Ok`]`(_))` and [`COption::Some`]`(`[`Err`]`(_))` will be mapped to + /// [`Ok`]`(`[`COption::Some`]`(_))` and [`Err`]`(_)`. + /// + /// [`COption::None`]: #variant.COption::None + /// [`Ok`]: ../../std/result/enum.Result.html#variant.Ok + /// [`COption::Some`]: #variant.COption::Some + /// [`Err`]: ../../std/result/enum.Result.html#variant.Err + /// + /// # Examples + /// + /// ```ignore + /// #[derive(Debug, Eq, PartialEq)] + /// struct COption::SomeErr; + /// + /// let x: Result, COption::SomeErr> = Ok(COption::Some(5)); + /// let y: COption> = COption::Some(Ok(5)); + /// assert_eq!(x, y.transpose()); + /// ``` + #[inline] + pub fn transpose(self) -> Result, E> { + match self { + COption::Some(Ok(x)) => Ok(COption::Some(x)), + COption::Some(Err(e)) => Err(e), + COption::None => Ok(COption::None), + } + } +} + +// This is a separate function to reduce the code size of .expect() itself. +#[inline(never)] +#[cold] +fn expect_failed(msg: &str) -> ! { + panic!("{}", msg) +} + +// // This is a separate function to reduce the code size of .expect_none() itself. +// #[inline(never)] +// #[cold] +// fn expect_none_failed(msg: &str, value: &dyn fmt::Debug) -> ! { +// panic!("{}: {:?}", msg, value) +// } + +///////////////////////////////////////////////////////////////////////////// +// Trait implementations +///////////////////////////////////////////////////////////////////////////// + +impl Clone for COption { + #[inline] + fn clone(&self) -> Self { + match self { + COption::Some(x) => COption::Some(x.clone()), + COption::None => COption::None, + } + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + match (self, source) { + (COption::Some(to), COption::Some(from)) => to.clone_from(from), + (to, from) => *to = from.clone(), + } + } +} + +impl Default for COption { + /// Returns [`COption::None`][COption::None]. + /// + /// # Examples + /// + /// ```ignore + /// let opt: COption = COption::default(); + /// assert!(opt.is_none()); + /// ``` + #[inline] + fn default() -> COption { + COption::None + } +} + +impl From for COption { + fn from(val: T) -> COption { + COption::Some(val) + } +} + +impl<'a, T> From<&'a COption> for COption<&'a T> { + fn from(o: &'a COption) -> COption<&'a T> { + o.as_ref() + } +} + +impl<'a, T> From<&'a mut COption> for COption<&'a mut T> { + fn from(o: &'a mut COption) -> COption<&'a mut T> { + o.as_mut() + } +} + +impl COption> { + /// Converts from `COption>` to `COption` + /// + /// # Examples + /// Basic usage: + /// ```ignore + /// #![feature(option_flattening)] + /// let x: COption> = COption::Some(COption::Some(6)); + /// assert_eq!(COption::Some(6), x.flatten()); + /// + /// let x: COption> = COption::Some(COption::None); + /// assert_eq!(COption::None, x.flatten()); + /// + /// let x: COption> = COption::None; + /// assert_eq!(COption::None, x.flatten()); + /// ``` + /// Flattening once only removes one level of nesting: + /// ```ignore + /// #![feature(option_flattening)] + /// let x: COption>> = COption::Some(COption::Some(COption::Some(6))); + /// assert_eq!(COption::Some(COption::Some(6)), x.flatten()); + /// assert_eq!(COption::Some(6), x.flatten().flatten()); + /// ``` + #[inline] + pub fn flatten(self) -> COption { + self.and_then(convert::identity) + } +} + +impl From> for COption { + fn from(option: Option) -> Self { + match option { + Some(value) => COption::Some(value), + None => COption::None, + } + } +}