Introduce automatic ABI maintenance mechanism (1/2; prepare) (#10335)
* Introduce automatic ABI maintenance mechanism * Compile fix... * Docs fix... * Programs compilation fix... * Simplify source credit Co-authored-by: Michael Vines <mvines@gmail.com> * Cargo.lock... Co-authored-by: Michael Vines <mvines@gmail.com>
This commit is contained in:
parent
b515cc3ae5
commit
e63e7937cb
|
@ -4566,6 +4566,7 @@ dependencies = [
|
|||
"pbkdf2",
|
||||
"rand 0.7.3",
|
||||
"rand_chacha 0.2.2",
|
||||
"rustc_version",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_derive",
|
||||
|
@ -4574,6 +4575,7 @@ dependencies = [
|
|||
"solana-crate-features",
|
||||
"solana-logger",
|
||||
"solana-sdk-macro",
|
||||
"solana-sdk-macro-frozen-abi",
|
||||
"thiserror",
|
||||
"tiny-bip39",
|
||||
]
|
||||
|
@ -4588,6 +4590,17 @@ dependencies = [
|
|||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-sdk-macro-frozen-abi"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"proc-macro2 1.0.17",
|
||||
"quote 1.0.1",
|
||||
"rustc_version",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-stake-accounts"
|
||||
version = "1.3.0"
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
* [Snapshot Verification](implemented-proposals/snapshot-verification.md)
|
||||
* [Cross-Program Invocation](implemented-proposals/cross-program-invocation.md)
|
||||
* [Program Derived Addresses](implemented-proposals/program-derived-addresses.md)
|
||||
* [ABI Management](implemented-proposals/abi-management.md)
|
||||
* [Accepted Design Proposals](proposals/README.md)
|
||||
* [Ledger Replication](proposals/ledger-replication-to-implement.md)
|
||||
* [Optimistic Confirmation and Slashing](proposals/optimistic-confirmation-and-slashing.md)
|
||||
|
@ -114,6 +115,5 @@
|
|||
* [Slashing](proposals/slashing.md)
|
||||
* [Tick Verification](proposals/tick-verification.md)
|
||||
* [Block Confirmation](proposals/block-confirmation.md)
|
||||
* [ABI Management](proposals/abi-management.md)
|
||||
* [Rust Clients](proposals/rust-clients.md)
|
||||
* [Optimistic Confirmation](proposals/optimistic_confirmation.md)
|
||||
|
|
|
@ -55,7 +55,7 @@ fields.
|
|||
# Example
|
||||
|
||||
```patch
|
||||
+#[frozen_abi(digest="1c6a53e9")]
|
||||
+#[frozen_abi(digest="eXSMM7b89VY72V...")]
|
||||
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Vote {
|
||||
/// A stack of votes starting with the oldest vote
|
||||
|
@ -73,16 +73,85 @@ digest from the assertion test error message.
|
|||
|
||||
In general, once we add `frozen_abi` and its change is published in the stable
|
||||
release channel, its digest should never change. If such a change is needed, we
|
||||
should opt for defining a new struct like `FooV1`. And special release flow like
|
||||
hard forks should be approached.
|
||||
should opt for defining a new `struct` like `FooV1`. And special release flow
|
||||
like hard forks should be approached.
|
||||
|
||||
# Implementation remarks
|
||||
|
||||
We use some degree of macro machinery to automatically generate unit tests
|
||||
and calculate a digest from ABI items. This is doable by clever use of
|
||||
`serde::Serialize` (`[1]`) and `any::typename` (`[2]`). For a precedent for similar
|
||||
`serde::Serialize` (`[1]`) and `any::type_name` (`[2]`). For a precedent for similar
|
||||
implementation, `ink` from the Parity Technologies `[3]` could be informational.
|
||||
|
||||
# Implementation details
|
||||
|
||||
The implementation's goal is to detect unintended ABI changes automatically as
|
||||
much as possible. To that end, the digest of structural ABI information is
|
||||
calculated with best-effort accuracy and stability.
|
||||
|
||||
When the ABI digest check is run, it dynamically computes an ABI digest by
|
||||
recursively digesting the ABI of fields of the ABI item, by re-using the
|
||||
`serde`'s serialization functionality, proc macro and generic specialization.
|
||||
And then, the check `assert!`s that its finalized digest value is identical as
|
||||
what is specified in the `frozen_abi` attribute.
|
||||
|
||||
To realize that, it creates an example instance of the type and a custom
|
||||
`Serializer` instance for `serde` to recursively traverse its fields as if
|
||||
serializing the example for real. This traversing must be done via `serde` to
|
||||
really capture what kinds of data actually would be serialized by `serde`, even
|
||||
considering custom non-`derive`d `Serialize` trait implementations.
|
||||
|
||||
# The ABI digesting process
|
||||
|
||||
This part is a bit complex. There is three inter-depending parts: `AbiExample`,
|
||||
`AbiDigester` and `AbiEnumVisitor`.
|
||||
|
||||
First, the generated test creates an example instance of the digested type with
|
||||
a trait called `AbiExample`, which should be implemented for all of digested
|
||||
types like the `Serialize` and return `Self` like the `Default` trait. Usually,
|
||||
it's provided via generic trait specialization for most of common types. Also
|
||||
it is possible to `derive` for `struct` and `enum` and can be hand-written if
|
||||
needed.
|
||||
|
||||
The custom `Serializer` is called `AbiDigester`. And when it's called by `serde`
|
||||
to serialize some data, it recursively collects ABI information as much as
|
||||
possible. `AbiDigester`'s internal state for the ABI digest is updated
|
||||
differentially depending on the type of data. This logic is specifically
|
||||
redirected via with a trait called `AbiEnumVisitor` for each `enum` type. As the
|
||||
name suggests, there is no need to implement `AbiEnumVisitor` for other types.
|
||||
|
||||
To summarize this interplay, `serde` handles the recursive serialization control
|
||||
flow in tandem with `AbiDigester`. The initial entry point in tests and child
|
||||
`AbiDigester`s use `AbiExample` recursively to create an example object
|
||||
hierarchal graph. And `AbiDigester` uses `AbiEnumVisitor` to inquiry the actual
|
||||
ABI information using the constructed sample.
|
||||
|
||||
`Default` isn't enough for `AbiExample`. Various collection's `::default()` is
|
||||
empty, yet, we want to digest them with actual items. And, ABI digesting can't
|
||||
be realized only with `AbiEnumVisitor`. `AbiExample` is required because an
|
||||
actual instance of type is needed to actually traverse the data via `serde`.
|
||||
|
||||
On the other hand, ABI digesting can't be done only with `AbiExample`, either.
|
||||
`AbiEnumVisitor` is required because all variants of an `enum` cannot be
|
||||
traversed just with a single variant of it as a ABI example.
|
||||
|
||||
Digestable information:
|
||||
|
||||
- rust's type name
|
||||
- `serde`'s data type name
|
||||
- all fields in `struct`
|
||||
- all variants in `enum`
|
||||
- `struct`: normal(`struct {...}`) and tuple-style (`struct(...)`)
|
||||
- `enum`: normal variants and `struct`- and `tuple`- styles.
|
||||
- attributes: `serde(serialize_with=...)` and `serde(skip)`
|
||||
|
||||
Not digestable information:
|
||||
|
||||
- Any custom serialize code path not touched by the sample provided by
|
||||
`AbiExample`. (technically not possible)
|
||||
- generics (must be a concrete type; use `frozen_abi` on concrete type
|
||||
aliases)
|
||||
|
||||
# References
|
||||
|
||||
1. [(De)Serialization with type info · Issue #1095 · serde-rs/serde](https://github.com/serde-rs/serde/issues/1095#issuecomment-345483479)
|
|
@ -1647,6 +1647,7 @@ dependencies = [
|
|||
"pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_bytes 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1655,6 +1656,7 @@ dependencies = [
|
|||
"solana-crate-features 1.3.0",
|
||||
"solana-logger 1.3.0",
|
||||
"solana-sdk-macro 1.3.0",
|
||||
"solana-sdk-macro-frozen-abi 1.3.0",
|
||||
"thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -1672,6 +1674,17 @@ dependencies = [
|
|||
"syn 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-sdk-macro-frozen-abi"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-stake-program"
|
||||
version = "1.3.0"
|
||||
|
|
|
@ -2450,6 +2450,7 @@ dependencies = [
|
|||
"pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_bytes 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2458,6 +2459,7 @@ dependencies = [
|
|||
"solana-crate-features 1.3.0",
|
||||
"solana-logger 1.3.0",
|
||||
"solana-sdk-macro 1.3.0",
|
||||
"solana-sdk-macro-frozen-abi 1.3.0",
|
||||
"thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -2471,6 +2473,17 @@ dependencies = [
|
|||
"syn 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-sdk-macro-frozen-abi"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-stake-program"
|
||||
version = "1.3.0"
|
||||
|
|
|
@ -2540,6 +2540,7 @@ dependencies = [
|
|||
"pbkdf2",
|
||||
"rand 0.7.3",
|
||||
"rand_chacha 0.2.2",
|
||||
"rustc_version",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_derive",
|
||||
|
@ -2548,6 +2549,7 @@ dependencies = [
|
|||
"solana-crate-features",
|
||||
"solana-logger",
|
||||
"solana-sdk-macro",
|
||||
"solana-sdk-macro-frozen-abi",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
|
@ -2561,6 +2563,17 @@ dependencies = [
|
|||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-sdk-macro-frozen-abi"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"proc-macro2 1.0.17",
|
||||
"quote 1.0.6",
|
||||
"rustc_version",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana_libra_bytecode_verifier"
|
||||
version = "0.0.1-sol5"
|
||||
|
|
|
@ -54,9 +54,13 @@ ed25519-dalek = { version = "=1.0.0-pre.3", optional = true }
|
|||
solana-crate-features = { path = "../crate-features", version = "1.3.0", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.3.0", optional = true }
|
||||
solana-sdk-macro = { path = "macro", version = "1.3.0" }
|
||||
solana-sdk-macro-frozen-abi = { path = "macro-frozen-abi", version = "1.3.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
tiny-bip39 = "0.7.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.2"
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
extern crate rustc_version;
|
||||
use rustc_version::{version_meta, Channel};
|
||||
|
||||
fn main() {
|
||||
// Copied and adapted from
|
||||
// https://github.com/Kimundi/rustc-version-rs/blob/1d692a965f4e48a8cb72e82cda953107c0d22f47/README.md#example
|
||||
// Licensed under Apache-2.0 + MIT
|
||||
match version_meta().unwrap().channel {
|
||||
Channel::Stable => {
|
||||
println!("cargo:rustc-cfg=RUSTC_WITHOUT_SPECIALIZATION");
|
||||
}
|
||||
Channel::Beta => {
|
||||
println!("cargo:rustc-cfg=RUSTC_WITHOUT_SPECIALIZATION");
|
||||
}
|
||||
Channel::Nightly => {
|
||||
println!("cargo:rustc-cfg=RUSTC_WITH_SPECIALIZATION");
|
||||
}
|
||||
Channel::Dev => {
|
||||
println!("cargo:rustc-cfg=RUSTC_WITH_SPECIALIZATION");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "solana-sdk-macro-frozen-abi"
|
||||
version = "1.3.0"
|
||||
description = "Solana SDK Macro frozen abi"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
build = "../build.rs"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.2"
|
|
@ -0,0 +1,453 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
// This file littered with these essential cfgs so ensure them.
|
||||
#[cfg(not(any(RUSTC_WITH_SPECIALIZATION, RUSTC_WITHOUT_SPECIALIZATION)))]
|
||||
compile_error!("rustc_version is missing in build dependency and build.rs is not specified");
|
||||
|
||||
#[cfg(any(RUSTC_WITH_SPECIALIZATION, RUSTC_WITHOUT_SPECIALIZATION))]
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
// Define dummy macro_attribute and macro_derive for stable rustc
|
||||
|
||||
#[cfg(RUSTC_WITHOUT_SPECIALIZATION)]
|
||||
#[proc_macro_attribute]
|
||||
pub fn frozen_abi(_attrs: TokenStream, item: TokenStream) -> TokenStream {
|
||||
item
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITHOUT_SPECIALIZATION)]
|
||||
#[proc_macro_derive(AbiExample)]
|
||||
pub fn derive_abi_sample(_item: TokenStream) -> TokenStream {
|
||||
"".parse().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITHOUT_SPECIALIZATION)]
|
||||
#[proc_macro_derive(AbiEnumVisitor)]
|
||||
pub fn derive_abi_enum_visitor(_item: TokenStream) -> TokenStream {
|
||||
"".parse().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree::Group};
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
use quote::quote;
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
use syn::{
|
||||
parse_macro_input, Attribute, AttributeArgs, Error, Fields, Ident, Item, ItemEnum, ItemStruct,
|
||||
ItemType, Lit, Meta, NestedMeta, Variant,
|
||||
};
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn filter_serde_attrs(attrs: &[Attribute]) -> bool {
|
||||
let mut skip = false;
|
||||
|
||||
for attr in attrs {
|
||||
let ss = &attr.path.segments.first().unwrap().ident.to_string();
|
||||
if ss.starts_with("serde") {
|
||||
for token in attr.tokens.clone() {
|
||||
if let Group(token) = token {
|
||||
for ident in token.stream() {
|
||||
if ident.to_string() == "skip" {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skip
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn filter_allow_attrs(attrs: &mut Vec<Attribute>) {
|
||||
attrs.retain(|attr| {
|
||||
let ss = &attr.path.segments.first().unwrap().ident.to_string();
|
||||
ss.starts_with("allow")
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn quote_for_specialization_detection() -> TokenStream2 {
|
||||
lazy_static! {
|
||||
static ref SPECIALIZATION_DETECTOR_INJECTED: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
}
|
||||
|
||||
if !SPECIALIZATION_DETECTOR_INJECTED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
SPECIALIZATION_DETECTOR_INJECTED.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
quote! {
|
||||
mod specialization_detector {
|
||||
trait SpecializedTrait {
|
||||
fn specialized_fn() {}
|
||||
}
|
||||
impl<T: Sized> SpecializedTrait for T {
|
||||
default fn specialized_fn() {}
|
||||
}
|
||||
impl<T: Sized + Default> SpecializedTrait for T {
|
||||
fn specialized_fn() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn derive_abi_sample_enum_type(input: ItemEnum) -> TokenStream {
|
||||
let type_name = &input.ident;
|
||||
|
||||
let mut sample_variant = quote! {};
|
||||
let mut sample_variant_found = false;
|
||||
|
||||
for variant in &input.variants {
|
||||
let variant_name = &variant.ident;
|
||||
let variant = &variant.fields;
|
||||
if *variant == Fields::Unit {
|
||||
sample_variant.extend(quote! {
|
||||
#type_name::#variant_name
|
||||
});
|
||||
} else if let Fields::Unnamed(variant_fields) = variant {
|
||||
let mut fields = quote! {};
|
||||
for field in &variant_fields.unnamed {
|
||||
if !(field.ident.is_none() && field.colon_token.is_none()) {
|
||||
unimplemented!("tuple enum: {:?}", field);
|
||||
}
|
||||
let field_type = &field.ty;
|
||||
fields.extend(quote! {
|
||||
<#field_type>::example(),
|
||||
});
|
||||
}
|
||||
sample_variant.extend(quote! {
|
||||
#type_name::#variant_name(#fields)
|
||||
});
|
||||
} else if let Fields::Named(variant_fields) = variant {
|
||||
let mut fields = quote! {};
|
||||
for field in &variant_fields.named {
|
||||
if field.ident.is_none() || field.colon_token.is_none() {
|
||||
unimplemented!("tuple enum: {:?}", field);
|
||||
}
|
||||
let field_type = &field.ty;
|
||||
let field_name = &field.ident;
|
||||
fields.extend(quote! {
|
||||
#field_name: <#field_type>::example(),
|
||||
});
|
||||
}
|
||||
sample_variant.extend(quote! {
|
||||
#type_name::#variant_name{#fields}
|
||||
});
|
||||
} else {
|
||||
unimplemented!("{:?}", variant);
|
||||
}
|
||||
|
||||
if !sample_variant_found {
|
||||
sample_variant_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !sample_variant_found {
|
||||
unimplemented!("empty enum");
|
||||
}
|
||||
|
||||
let mut attrs = input.attrs.clone();
|
||||
filter_allow_attrs(&mut attrs);
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let injection = quote_for_specialization_detection();
|
||||
|
||||
let result = quote! {
|
||||
#injection
|
||||
#[automatically_derived]
|
||||
#( #attrs )*
|
||||
impl #impl_generics ::solana_sdk::abi_example::AbiExample for #type_name #ty_generics #where_clause {
|
||||
fn example() -> Self {
|
||||
::log::info!(
|
||||
"AbiExample for enum: {}",
|
||||
std::any::type_name::<#type_name #ty_generics>()
|
||||
);
|
||||
#sample_variant
|
||||
}
|
||||
}
|
||||
};
|
||||
result.into()
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn derive_abi_sample_struct_type(input: ItemStruct) -> TokenStream {
|
||||
let type_name = &input.ident;
|
||||
let mut sample_fields = quote! {};
|
||||
let fields = &input.fields;
|
||||
|
||||
match fields {
|
||||
Fields::Named(_) => {
|
||||
for field in fields {
|
||||
let field_name = &field.ident;
|
||||
sample_fields.extend(quote! {
|
||||
#field_name: AbiExample::example(),
|
||||
});
|
||||
}
|
||||
sample_fields = quote! {
|
||||
{ #sample_fields }
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(_) => {
|
||||
for _ in fields {
|
||||
sample_fields.extend(quote! {
|
||||
AbiExample::example(),
|
||||
});
|
||||
}
|
||||
sample_fields = quote! {
|
||||
( #sample_fields )
|
||||
}
|
||||
}
|
||||
_ => unimplemented!("fields: {:?}", fields),
|
||||
}
|
||||
|
||||
let mut attrs = input.attrs.clone();
|
||||
filter_allow_attrs(&mut attrs);
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let turbofish = ty_generics.as_turbofish();
|
||||
let injection = quote_for_specialization_detection();
|
||||
|
||||
let result = quote! {
|
||||
#injection
|
||||
#[automatically_derived]
|
||||
#( #attrs )*
|
||||
impl #impl_generics ::solana_sdk::abi_example::AbiExample for #type_name #ty_generics #where_clause {
|
||||
fn example() -> Self {
|
||||
::log::info!(
|
||||
"AbiExample for struct: {}",
|
||||
std::any::type_name::<#type_name #ty_generics>()
|
||||
);
|
||||
use ::solana_sdk::abi_example::AbiExample;
|
||||
|
||||
#type_name #turbofish #sample_fields
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
#[proc_macro_derive(AbiExample)]
|
||||
pub fn derive_abi_sample(item: TokenStream) -> TokenStream {
|
||||
let item = parse_macro_input!(item as Item);
|
||||
|
||||
match item {
|
||||
Item::Struct(input) => derive_abi_sample_struct_type(input),
|
||||
Item::Enum(input) => derive_abi_sample_enum_type(input),
|
||||
_ => Error::new_spanned(item, "AbiSample isn't applicable; only for struct and enum")
|
||||
.to_compile_error()
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn do_derive_abi_enum_visitor(input: ItemEnum) -> TokenStream {
|
||||
let type_name = &input.ident;
|
||||
let mut serialized_variants = quote! {};
|
||||
let mut variant_count = 0;
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
for variant in &input.variants {
|
||||
// Don't digest a variant with serde(skip)
|
||||
if filter_serde_attrs(&variant.attrs) {
|
||||
continue;
|
||||
};
|
||||
let sample_variant = quote_sample_variant(&type_name, &ty_generics, &variant);
|
||||
variant_count += 1;
|
||||
serialized_variants.extend(quote! {
|
||||
#sample_variant;
|
||||
Serialize::serialize(&sample_variant, digester.create_enum_child())?;
|
||||
});
|
||||
}
|
||||
|
||||
let type_str = format!("{}", type_name);
|
||||
(quote! {
|
||||
impl #impl_generics ::solana_sdk::abi_example::AbiEnumVisitor for #type_name #ty_generics #where_clause {
|
||||
fn visit_for_abi(&self, digester: &mut ::solana_sdk::abi_digester::AbiDigester) -> ::solana_sdk::abi_digester::DigestResult {
|
||||
let enum_name = #type_str;
|
||||
use ::serde::ser::Serialize;
|
||||
use ::solana_sdk::abi_example::AbiExample;
|
||||
digester.update_with_string(format!("enum {} (variants = {})", enum_name, #variant_count));
|
||||
#serialized_variants
|
||||
Ok(digester.create_child())
|
||||
}
|
||||
}
|
||||
}).into()
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
#[proc_macro_derive(AbiEnumVisitor)]
|
||||
pub fn derive_abi_enum_visitor(item: TokenStream) -> TokenStream {
|
||||
let item = parse_macro_input!(item as Item);
|
||||
|
||||
match item {
|
||||
Item::Enum(input) => do_derive_abi_enum_visitor(input),
|
||||
_ => Error::new_spanned(item, "AbiEnumVisitor not applicable; only for enum")
|
||||
.to_compile_error()
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn quote_for_test(
|
||||
test_mod_ident: &Ident,
|
||||
type_name: &Ident,
|
||||
expected_digest: &str,
|
||||
) -> TokenStream2 {
|
||||
// escape from nits.sh...
|
||||
let p = Ident::new(&("ep".to_owned() + "rintln"), Span::call_site());
|
||||
quote! {
|
||||
#[cfg(test)]
|
||||
mod #test_mod_ident {
|
||||
use super::*;
|
||||
use ::solana_sdk::{abi_digester::AbiEnumVisitor, abi_example::AbiExample};
|
||||
|
||||
#[test]
|
||||
fn test_abi_digest() {
|
||||
::solana_logger::setup();
|
||||
let mut digester = ::solana_sdk::abi_digester::AbiDigester::create();
|
||||
let example = <#type_name>::example();
|
||||
let result = <_>::visit_for_abi(&&example, &mut digester);
|
||||
let mut hash = digester.finalize();
|
||||
// pretty-print error
|
||||
if result.is_err() {
|
||||
::log::error!("digest error: {:#?}", result);
|
||||
}
|
||||
result.unwrap();
|
||||
if ::std::env::var("SOLANA_ABI_BULK_UPDATE").is_ok() {
|
||||
if #expected_digest != format!("{}", hash) {
|
||||
#p!("sed -i -e 's/{}/{}/g' $(git grep --files-with-matches frozen_abi)", #expected_digest, hash);
|
||||
}
|
||||
::log::warn!("Not testing the abi digest under SOLANA_ABI_BULK_UPDATE!");
|
||||
} else {
|
||||
assert_eq!(#expected_digest, format!("{}", hash), "Possibly ABI changed? Confirm the diff by rerunning before and after this test failed with SOLANA_ABI_DUMP_DIR");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn test_mod_name(type_name: &Ident) -> Ident {
|
||||
Ident::new(
|
||||
&format!("{}_frozen_abi", type_name.to_string()),
|
||||
Span::call_site(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn frozen_abi_type_alias(input: ItemType, expected_digest: &str) -> TokenStream {
|
||||
let type_name = &input.ident;
|
||||
let test = quote_for_test(&test_mod_name(type_name), type_name, &expected_digest);
|
||||
let result = quote! {
|
||||
#input
|
||||
#test
|
||||
};
|
||||
result.into()
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn frozen_abi_struct_type(input: ItemStruct, expected_digest: &str) -> TokenStream {
|
||||
let type_name = &input.ident;
|
||||
let test = quote_for_test(&test_mod_name(type_name), type_name, &expected_digest);
|
||||
let result = quote! {
|
||||
#input
|
||||
#test
|
||||
};
|
||||
result.into()
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn quote_sample_variant(
|
||||
type_name: &Ident,
|
||||
ty_generics: &syn::TypeGenerics,
|
||||
variant: &Variant,
|
||||
) -> TokenStream2 {
|
||||
let variant_name = &variant.ident;
|
||||
let variant = &variant.fields;
|
||||
if *variant == Fields::Unit {
|
||||
quote! {
|
||||
let sample_variant: #type_name #ty_generics = #type_name::#variant_name;
|
||||
}
|
||||
} else if let Fields::Unnamed(variant_fields) = variant {
|
||||
let mut fields = quote! {};
|
||||
for field in &variant_fields.unnamed {
|
||||
if !(field.ident.is_none() && field.colon_token.is_none()) {
|
||||
unimplemented!();
|
||||
}
|
||||
let ty = &field.ty;
|
||||
fields.extend(quote! {
|
||||
<#ty>::example(),
|
||||
});
|
||||
}
|
||||
quote! {
|
||||
let sample_variant: #type_name #ty_generics = #type_name::#variant_name(#fields);
|
||||
}
|
||||
} else if let Fields::Named(variant_fields) = variant {
|
||||
let mut fields = quote! {};
|
||||
for field in &variant_fields.named {
|
||||
if field.ident.is_none() || field.colon_token.is_none() {
|
||||
unimplemented!();
|
||||
}
|
||||
let field_type_name = &field.ty;
|
||||
let field_name = &field.ident;
|
||||
fields.extend(quote! {
|
||||
#field_name: <#field_type_name>::example(),
|
||||
});
|
||||
}
|
||||
quote! {
|
||||
let sample_variant: #type_name #ty_generics = #type_name::#variant_name{#fields};
|
||||
}
|
||||
} else {
|
||||
unimplemented!("variant: {:?}", variant)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
fn frozen_abi_enum_type(input: ItemEnum, expected_digest: &str) -> TokenStream {
|
||||
let type_name = &input.ident;
|
||||
let test = quote_for_test(&test_mod_name(type_name), type_name, &expected_digest);
|
||||
let result = quote! {
|
||||
#input
|
||||
#test
|
||||
};
|
||||
result.into()
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
#[proc_macro_attribute]
|
||||
pub fn frozen_abi(attrs: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let args = parse_macro_input!(attrs as AttributeArgs);
|
||||
let mut expected_digest: Option<String> = None;
|
||||
for arg in args {
|
||||
match arg {
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("digest") => {
|
||||
if let Lit::Str(lit) = nv.lit {
|
||||
expected_digest = Some(lit.value());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let expected_digest = expected_digest.expect("the required \"digest\" = ... is missing.");
|
||||
|
||||
let item = parse_macro_input!(item as Item);
|
||||
match item {
|
||||
Item::Struct(input) => frozen_abi_struct_type(input, &expected_digest),
|
||||
Item::Enum(input) => frozen_abi_enum_type(input, &expected_digest),
|
||||
Item::Type(input) => frozen_abi_type_alias(input, &expected_digest),
|
||||
_ => Error::new_spanned(
|
||||
item,
|
||||
"frozen_abi isn't applicable; only for struct, enum and type",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into(),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,635 @@
|
|||
use crate::abi_example::{normalize_type_name, AbiEnumVisitor};
|
||||
use crate::hash::{Hash, Hasher};
|
||||
|
||||
use log::*;
|
||||
|
||||
use serde::ser::Error as SerdeError;
|
||||
use serde::ser::*;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use std::any::type_name;
|
||||
use std::io::Write;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AbiDigester {
|
||||
data_types: std::rc::Rc<std::cell::RefCell<Vec<String>>>,
|
||||
depth: usize,
|
||||
for_enum: bool,
|
||||
opaque_scope: Option<String>,
|
||||
}
|
||||
|
||||
pub type DigestResult = Result<AbiDigester, DigestError>;
|
||||
type NoResult = Result<(), DigestError>;
|
||||
type Sstr = &'static str;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DigestError {
|
||||
#[error("Option::None is serialized; no ABI digest for Option::Some")]
|
||||
NoneIsSerialized,
|
||||
#[error("nested error")]
|
||||
Node(Sstr, Box<DigestError>),
|
||||
#[error("leaf error")]
|
||||
Leaf(Sstr, Sstr, Box<DigestError>),
|
||||
}
|
||||
|
||||
impl SerdeError for DigestError {
|
||||
fn custom<T: std::fmt::Display>(_msg: T) -> DigestError {
|
||||
unreachable!("This error should never be used");
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestError {
|
||||
pub(crate) fn wrap_by_type<T: ?Sized>(e: DigestError) -> DigestError {
|
||||
DigestError::Node(type_name::<T>(), Box::new(e))
|
||||
}
|
||||
|
||||
pub(crate) fn wrap_by_str(e: DigestError, s: Sstr) -> DigestError {
|
||||
DigestError::Node(s, Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
const INDENT_WIDTH: usize = 4;
|
||||
|
||||
impl AbiDigester {
|
||||
pub fn create() -> Self {
|
||||
AbiDigester {
|
||||
data_types: std::rc::Rc::new(std::cell::RefCell::new(vec![])),
|
||||
for_enum: false,
|
||||
depth: 0,
|
||||
opaque_scope: None,
|
||||
}
|
||||
}
|
||||
|
||||
// must create separate instances because we can't pass the single instnace to
|
||||
// `.serialize()` multiple times
|
||||
pub fn create_new(&self) -> Self {
|
||||
Self {
|
||||
data_types: self.data_types.clone(),
|
||||
depth: self.depth,
|
||||
for_enum: false,
|
||||
opaque_scope: self.opaque_scope.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_new_opaque(&self, top_scope: &str) -> Self {
|
||||
Self {
|
||||
data_types: self.data_types.clone(),
|
||||
depth: self.depth,
|
||||
for_enum: false,
|
||||
opaque_scope: Some(top_scope.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_child(&self) -> Self {
|
||||
Self {
|
||||
data_types: self.data_types.clone(),
|
||||
depth: self.depth + 1,
|
||||
for_enum: false,
|
||||
opaque_scope: self.opaque_scope.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_enum_child(&self) -> Self {
|
||||
Self {
|
||||
data_types: self.data_types.clone(),
|
||||
depth: self.depth + 1,
|
||||
for_enum: true,
|
||||
opaque_scope: self.opaque_scope.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn digest_data<T: ?Sized + Serialize>(&mut self, value: &T) -> DigestResult {
|
||||
let type_name = normalize_type_name(type_name::<T>());
|
||||
if type_name.ends_with("__SerializeWith")
|
||||
|| (self.opaque_scope.is_some()
|
||||
&& type_name.starts_with(self.opaque_scope.as_ref().unwrap()))
|
||||
{
|
||||
// we can't use the AbiEnumVisitor trait for these cases.
|
||||
value.serialize(self.create_new())
|
||||
} else {
|
||||
// Don't call value.visit_for_abi(...) to prefer autoref specialization
|
||||
// resolution for IgnoreAsHelper
|
||||
<&T>::visit_for_abi(&value, &mut self.create_new())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, strs: &[&str]) {
|
||||
let mut buf = strs
|
||||
.iter()
|
||||
.map(|s| {
|
||||
// this is a bit crude, but just normalize all strings as if they're
|
||||
// `type_name`s!
|
||||
normalize_type_name(s)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
buf = format!("{:0width$}{}\n", "", buf, width = self.depth * INDENT_WIDTH);
|
||||
info!("updating with: {}", buf.trim_end());
|
||||
(*self.data_types.borrow_mut()).push(buf);
|
||||
}
|
||||
|
||||
pub fn update_with_type<T: ?Sized>(&mut self, label: &str) {
|
||||
self.update(&[label, type_name::<T>()]);
|
||||
}
|
||||
|
||||
pub fn update_with_string(&mut self, label: String) {
|
||||
self.update(&[&label]);
|
||||
}
|
||||
|
||||
fn digest_primitive<T: Serialize>(mut self) -> Result<AbiDigester, DigestError> {
|
||||
self.update_with_type::<T>("primitive");
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn digest_element<T: ?Sized + Serialize>(&mut self, v: &T) -> NoResult {
|
||||
self.update_with_type::<T>("element");
|
||||
self.create_child().digest_data(v).map(|_| ())
|
||||
}
|
||||
|
||||
fn digest_named_field<T: ?Sized + Serialize>(&mut self, key: Sstr, v: &T) -> NoResult {
|
||||
self.update_with_string(format!("field {}: {}", key, type_name::<T>()));
|
||||
self.create_child()
|
||||
.digest_data(v)
|
||||
.map(|_| ())
|
||||
.map_err(|e| DigestError::wrap_by_str(e, key))
|
||||
}
|
||||
|
||||
fn digest_unnamed_field<T: ?Sized + Serialize>(&mut self, v: &T) -> NoResult {
|
||||
self.update_with_type::<T>("field");
|
||||
self.create_child().digest_data(v).map(|_| ())
|
||||
}
|
||||
|
||||
fn check_for_enum(&mut self, label: &'static str, variant: &'static str) -> NoResult {
|
||||
if !self.for_enum {
|
||||
panic!("derive AbiEnumVisitor or implement it for the enum, which contains a variant ({}) named {}", label, variant);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> Hash {
|
||||
let mut hasher = Hasher::default();
|
||||
|
||||
for buf in (*self.data_types.borrow()).iter() {
|
||||
hasher.hash(buf.as_bytes());
|
||||
}
|
||||
|
||||
let hash = hasher.result();
|
||||
|
||||
if let Ok(dir) = std::env::var("SOLANA_ABI_DUMP_DIR") {
|
||||
let thread_name = std::thread::current()
|
||||
.name()
|
||||
.unwrap_or("unknown-test-thread")
|
||||
.replace(':', "_");
|
||||
if thread_name == "main" {
|
||||
error!("Bad thread name detected for dumping; Maybe, --test-threads=1? Sorry, SOLANA_ABI_DUMP_DIR doesn't work under 1; increase it");
|
||||
}
|
||||
|
||||
let path = format!("{}/{}_{}", dir, thread_name, hash,);
|
||||
let mut file = std::fs::File::create(path).unwrap();
|
||||
for buf in (*self.data_types.borrow()).iter() {
|
||||
file.write_all(buf.as_bytes()).unwrap();
|
||||
}
|
||||
file.sync_data().unwrap();
|
||||
}
|
||||
|
||||
hash
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializer for AbiDigester {
|
||||
type Ok = Self;
|
||||
type Error = DigestError;
|
||||
type SerializeSeq = Self;
|
||||
type SerializeTuple = Self;
|
||||
type SerializeTupleStruct = Self;
|
||||
type SerializeTupleVariant = Self;
|
||||
type SerializeMap = Self;
|
||||
type SerializeStruct = Self;
|
||||
type SerializeStructVariant = Self;
|
||||
|
||||
fn serialize_bool(self, _data: bool) -> DigestResult {
|
||||
self.digest_primitive::<bool>()
|
||||
}
|
||||
|
||||
fn serialize_i8(self, _data: i8) -> DigestResult {
|
||||
self.digest_primitive::<i8>()
|
||||
}
|
||||
|
||||
fn serialize_i16(self, _data: i16) -> DigestResult {
|
||||
self.digest_primitive::<i16>()
|
||||
}
|
||||
|
||||
fn serialize_i32(self, _data: i32) -> DigestResult {
|
||||
self.digest_primitive::<i32>()
|
||||
}
|
||||
|
||||
fn serialize_i64(self, _data: i64) -> DigestResult {
|
||||
self.digest_primitive::<i64>()
|
||||
}
|
||||
|
||||
fn serialize_i128(self, _data: i128) -> DigestResult {
|
||||
self.digest_primitive::<i128>()
|
||||
}
|
||||
|
||||
fn serialize_u8(self, _data: u8) -> DigestResult {
|
||||
self.digest_primitive::<u8>()
|
||||
}
|
||||
|
||||
fn serialize_u16(self, _data: u16) -> DigestResult {
|
||||
self.digest_primitive::<u16>()
|
||||
}
|
||||
|
||||
fn serialize_u32(self, _data: u32) -> DigestResult {
|
||||
self.digest_primitive::<u32>()
|
||||
}
|
||||
|
||||
fn serialize_u64(self, _data: u64) -> DigestResult {
|
||||
self.digest_primitive::<u64>()
|
||||
}
|
||||
|
||||
fn serialize_u128(self, _data: u128) -> DigestResult {
|
||||
self.digest_primitive::<u128>()
|
||||
}
|
||||
|
||||
fn serialize_f32(self, _data: f32) -> DigestResult {
|
||||
self.digest_primitive::<f32>()
|
||||
}
|
||||
|
||||
fn serialize_f64(self, _data: f64) -> DigestResult {
|
||||
self.digest_primitive::<f64>()
|
||||
}
|
||||
|
||||
fn serialize_char(self, _data: char) -> DigestResult {
|
||||
self.digest_primitive::<char>()
|
||||
}
|
||||
|
||||
fn serialize_str(self, _data: &str) -> DigestResult {
|
||||
self.digest_primitive::<&str>()
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> DigestResult {
|
||||
self.digest_primitive::<()>()
|
||||
}
|
||||
|
||||
fn serialize_bytes(mut self, v: &[u8]) -> DigestResult {
|
||||
self.update_with_string(format!("bytes [u8] (len = {})", v.len()));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> DigestResult {
|
||||
Err(DigestError::NoneIsSerialized)
|
||||
}
|
||||
|
||||
fn serialize_some<T>(mut self, v: &T) -> DigestResult
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
// emulate the ABI digest for the Option enum; see TestMyOption
|
||||
self.update(&["enum Option (variants = 2)"]);
|
||||
let mut variant_digester = self.create_child();
|
||||
|
||||
variant_digester.update_with_string("variant(0) None (unit)".to_owned());
|
||||
variant_digester
|
||||
.update_with_string(format!("variant(1) Some({}) (newtype)", type_name::<T>()));
|
||||
variant_digester.create_child().digest_data(v)
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(mut self, name: Sstr) -> DigestResult {
|
||||
self.update(&["struct", name, "(unit)"]);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(mut self, _name: Sstr, index: u32, variant: Sstr) -> DigestResult {
|
||||
self.check_for_enum("unit_variant", variant)?;
|
||||
self.update_with_string(format!("variant({}) {} (unit)", index, variant));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T>(mut self, name: Sstr, v: &T) -> DigestResult
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
self.update_with_string(format!("struct {}({}) (newtype)", name, type_name::<T>()));
|
||||
self.create_child()
|
||||
.digest_data(v)
|
||||
.map_err(|e| DigestError::wrap_by_str(e, "newtype_struct"))
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T>(
|
||||
mut self,
|
||||
_name: Sstr,
|
||||
i: u32,
|
||||
variant: Sstr,
|
||||
v: &T,
|
||||
) -> DigestResult
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
self.check_for_enum("newtype_variant", variant)?;
|
||||
self.update_with_string(format!(
|
||||
"variant({}) {}({}) (newtype)",
|
||||
i,
|
||||
variant,
|
||||
type_name::<T>()
|
||||
));
|
||||
self.create_child()
|
||||
.digest_data(v)
|
||||
.map_err(|e| DigestError::wrap_by_str(e, "newtype_variant"))
|
||||
}
|
||||
|
||||
fn serialize_seq(mut self, len: Option<usize>) -> DigestResult {
|
||||
self.update_with_string(format!("seq (elements = {})", len.unwrap()));
|
||||
Ok(self.create_child())
|
||||
}
|
||||
|
||||
fn serialize_tuple(mut self, len: usize) -> DigestResult {
|
||||
self.update_with_string(format!("tuple (elements = {})", len));
|
||||
Ok(self.create_child())
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(mut self, name: Sstr, len: usize) -> DigestResult {
|
||||
self.update_with_string(format!("struct {} (fields = {}) (tuple)", name, len));
|
||||
Ok(self.create_child())
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
mut self,
|
||||
_name: Sstr,
|
||||
i: u32,
|
||||
variant: Sstr,
|
||||
len: usize,
|
||||
) -> DigestResult {
|
||||
self.check_for_enum("tuple_variant", variant)?;
|
||||
self.update_with_string(format!("variant({}) {} (fields = {})", i, variant, len));
|
||||
Ok(self.create_child())
|
||||
}
|
||||
|
||||
fn serialize_map(mut self, len: Option<usize>) -> DigestResult {
|
||||
self.update_with_string(format!("map (entries = {})", len.unwrap()));
|
||||
Ok(self.create_child())
|
||||
}
|
||||
|
||||
fn serialize_struct(mut self, name: Sstr, len: usize) -> DigestResult {
|
||||
self.update_with_string(format!("struct {} (fields = {})", name, len));
|
||||
Ok(self.create_child())
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(
|
||||
mut self,
|
||||
_name: Sstr,
|
||||
i: u32,
|
||||
variant: Sstr,
|
||||
len: usize,
|
||||
) -> DigestResult {
|
||||
self.check_for_enum("struct_variant", variant)?;
|
||||
self.update_with_string(format!(
|
||||
"variant({}) struct {} (fields = {})",
|
||||
i, variant, len
|
||||
));
|
||||
Ok(self.create_child())
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeSeq for AbiDigester {
|
||||
type Ok = Self;
|
||||
type Error = DigestError;
|
||||
|
||||
fn serialize_element<T: ?Sized + Serialize>(&mut self, data: &T) -> NoResult {
|
||||
self.digest_element(data)
|
||||
}
|
||||
|
||||
fn end(self) -> DigestResult {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeTuple for AbiDigester {
|
||||
type Ok = Self;
|
||||
type Error = DigestError;
|
||||
|
||||
fn serialize_element<T: ?Sized + Serialize>(&mut self, data: &T) -> NoResult {
|
||||
self.digest_element(data)
|
||||
}
|
||||
|
||||
fn end(self) -> DigestResult {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
impl SerializeTupleStruct for AbiDigester {
|
||||
type Ok = Self;
|
||||
type Error = DigestError;
|
||||
|
||||
fn serialize_field<T: ?Sized + Serialize>(&mut self, data: &T) -> NoResult {
|
||||
self.digest_unnamed_field(data)
|
||||
}
|
||||
|
||||
fn end(self) -> DigestResult {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeTupleVariant for AbiDigester {
|
||||
type Ok = Self;
|
||||
type Error = DigestError;
|
||||
|
||||
fn serialize_field<T: ?Sized + Serialize>(&mut self, data: &T) -> NoResult {
|
||||
self.digest_unnamed_field(data)
|
||||
}
|
||||
|
||||
fn end(self) -> DigestResult {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeMap for AbiDigester {
|
||||
type Ok = Self;
|
||||
type Error = DigestError;
|
||||
|
||||
fn serialize_key<T: ?Sized + Serialize>(&mut self, key: &T) -> NoResult {
|
||||
self.update_with_type::<T>("key");
|
||||
self.create_child().digest_data(key).map(|_| ())
|
||||
}
|
||||
|
||||
fn serialize_value<T: ?Sized + Serialize>(&mut self, value: &T) -> NoResult {
|
||||
self.update_with_type::<T>("value");
|
||||
self.create_child().digest_data(value).map(|_| ())
|
||||
}
|
||||
|
||||
fn end(self) -> DigestResult {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeStruct for AbiDigester {
|
||||
type Ok = Self;
|
||||
type Error = DigestError;
|
||||
|
||||
fn serialize_field<T: ?Sized + Serialize>(&mut self, key: Sstr, data: &T) -> NoResult {
|
||||
self.digest_named_field(key, data)
|
||||
}
|
||||
|
||||
fn end(self) -> DigestResult {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeStructVariant for AbiDigester {
|
||||
type Ok = Self;
|
||||
type Error = DigestError;
|
||||
|
||||
fn serialize_field<T: ?Sized + Serialize>(&mut self, key: Sstr, data: &T) -> NoResult {
|
||||
self.digest_named_field(key, data)
|
||||
}
|
||||
|
||||
fn end(self) -> DigestResult {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicIsize;
|
||||
|
||||
#[frozen_abi(digest = "CQiGCzsGquChkwffHjZKFqa3tCYtS3GWYRRYX7iDR38Q")]
|
||||
type TestTypeAlias = i32;
|
||||
|
||||
#[frozen_abi(digest = "Apwkp9Ah9zKirzwuSzVoU9QRc43EghpkD1nGVakJLfUY")]
|
||||
#[derive(Serialize, AbiExample)]
|
||||
struct TestStruct {
|
||||
test_field: i8,
|
||||
test_field2: i8,
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "4LbuvQLX78XPbm4hqqZcHFHpseDJcw4qZL9EUZXSi2Ss")]
|
||||
#[derive(Serialize, AbiExample)]
|
||||
struct TestTupleStruct(i8, i8);
|
||||
|
||||
#[frozen_abi(digest = "FNHa6mNYJZa59Fwbipep5dXRXcFreaDHn9jEUZEH1YLv")]
|
||||
#[derive(Serialize, AbiExample)]
|
||||
struct TestNewtypeStruct(i8);
|
||||
|
||||
#[frozen_abi(digest = "5qio5qYurHDv6fq5kcwP2ue2RBEazSZF8CPk2kUuwC2j")]
|
||||
#[derive(Serialize, AbiExample)]
|
||||
struct TestStructReversed {
|
||||
test_field2: i8,
|
||||
test_field: i8,
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "DLLrTWprsMjdJGR447A4mui9HpqxbKdsFXBfaWPcwhny")]
|
||||
#[derive(Serialize, AbiExample)]
|
||||
struct TestStructAnotherType {
|
||||
test_field: i16,
|
||||
test_field2: i8,
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "Hv597t4PieHYvgiXnwRSpKBRTWqteUS4nHZHY6ZxX69v")]
|
||||
#[derive(Serialize, AbiExample)]
|
||||
struct TestNest {
|
||||
nested_field: [TestStruct; 5],
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "GttWH8FAY3teUjTaSds9mL3YbiDQ7qWw7WAvDXKd4ZzX")]
|
||||
type TestUnitStruct = std::marker::PhantomData<i8>;
|
||||
|
||||
#[frozen_abi(digest = "2zvXde11f8sNnFbc9E6ZZeFxV7D2BTVLKEZmNTsCDBpS")]
|
||||
#[derive(Serialize, AbiExample, AbiEnumVisitor)]
|
||||
enum TestEnum {
|
||||
VARIANT1,
|
||||
VARIANT2,
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "6keb3v7GXLahhL6zoinzCWwSvB3KhmvZMB3tN2mamAm3")]
|
||||
#[derive(Serialize, AbiExample, AbiEnumVisitor)]
|
||||
enum TestTupleVariant {
|
||||
VARIANT1(u8, u16),
|
||||
VARIANT2(u8, u16),
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "CKxzv7VjyUrNR9fGJpTpKyMBWJM4gepKshCS8oV14T1Q")]
|
||||
#[derive(Serialize, AbiExample)]
|
||||
struct TestVecEnum {
|
||||
enums: Vec<TestTupleVariant>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, AbiExample)]
|
||||
struct TestGenericStruct<T: Ord> {
|
||||
test_field: T,
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "2Dr5k3Z513mV4KrGeUfcMwjsVHLmVyLiZarmfnXawEbf")]
|
||||
type TestConcreteStruct = TestGenericStruct<i64>;
|
||||
|
||||
#[derive(Serialize, AbiExample, AbiEnumVisitor)]
|
||||
enum TestGenericEnum<T: serde::Serialize + Sized + Ord> {
|
||||
TestVariant(T),
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "2B2HqxHaziSfW3kdxJqV9vEMpCpRaEipXL6Bskv1GV7J")]
|
||||
type TestConcreteEnum = TestGenericEnum<u128>;
|
||||
|
||||
#[frozen_abi(digest = "GyExD8nkYb9e6tijFL5S1gFtdN9GfY6L2sUDjTLhVGn4")]
|
||||
type TestMap = HashMap<char, i128>;
|
||||
|
||||
#[frozen_abi(digest = "AFLTVyVBkjc1SAPnzyuwTvmie994LMhJGN7PrP7hCVwL")]
|
||||
type TestVec = Vec<f32>;
|
||||
|
||||
#[frozen_abi(digest = "F5RniBQtNMBiDnyLEf72aQKHskV1TuBrD4jrEH5odPAW")]
|
||||
type TestArray = [f64; 10];
|
||||
|
||||
#[frozen_abi(digest = "8cgZGpckC4dFovh3QuZpgvcvK2125ig7P4HsK9KCw39N")]
|
||||
type TestUnit = ();
|
||||
|
||||
#[frozen_abi(digest = "FgnBPy2T5iNNbykMteq1M4FRpNeSkzRoi9oXeCjEW6uq")]
|
||||
type TestResult = Result<u8, u16>;
|
||||
|
||||
#[frozen_abi(digest = "F5s6YyJkfz7LM56q5j9RzTLa7QX4Utx1ecNkHX5UU9Fp")]
|
||||
type TestAtomic = AtomicIsize;
|
||||
|
||||
#[frozen_abi(digest = "7rH7gnEhJ8YouzqPT6VPyUDELvL51DGednSPcoLXG2rg")]
|
||||
type TestOptionWithIsize = Option<isize>;
|
||||
|
||||
#[derive(Serialize, AbiExample, AbiEnumVisitor)]
|
||||
enum TestMyOption<T: serde::Serialize + Sized + Ord> {
|
||||
None,
|
||||
Some(T),
|
||||
}
|
||||
#[frozen_abi(digest = "BzXkoRacijFTCPW4PyyvhkqMVgcuhmvPXjZfMsHJCeet")]
|
||||
type TestMyOptionWithIsize = TestMyOption<isize>;
|
||||
|
||||
#[frozen_abi(digest = "9PMdHRb49BpkywrmPoJyZWMsEmf5E1xgmsFGkGmea5RW")]
|
||||
type TestBitVec = bv::BitVec<u64>;
|
||||
|
||||
mod skip_should_be_same {
|
||||
#[frozen_abi(digest = "4LbuvQLX78XPbm4hqqZcHFHpseDJcw4qZL9EUZXSi2Ss")]
|
||||
#[derive(Serialize, AbiExample)]
|
||||
struct TestTupleStruct(i8, i8, #[serde(skip)] i8);
|
||||
|
||||
#[frozen_abi(digest = "Hk7BYjZ71upWQJAx2PqoNcapggobPmFbMJd34xVdvRso")]
|
||||
#[derive(Serialize, AbiExample)]
|
||||
struct TestStruct {
|
||||
test_field: i8,
|
||||
#[serde(skip)]
|
||||
_skipped_test_field: i8,
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "2zvXde11f8sNnFbc9E6ZZeFxV7D2BTVLKEZmNTsCDBpS")]
|
||||
#[derive(Serialize, AbiExample, AbiEnumVisitor)]
|
||||
enum TestEnum {
|
||||
VARIANT1,
|
||||
VARIANT2,
|
||||
#[serde(skip)]
|
||||
#[allow(dead_code)]
|
||||
VARIANT3,
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "6keb3v7GXLahhL6zoinzCWwSvB3KhmvZMB3tN2mamAm3")]
|
||||
#[derive(Serialize, AbiExample, AbiEnumVisitor)]
|
||||
enum TestTupleVariant {
|
||||
VARIANT1(u8, u16),
|
||||
VARIANT2(u8, u16, #[serde(skip)] u32),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,507 @@
|
|||
use crate::abi_digester::{AbiDigester, DigestError, DigestResult};
|
||||
|
||||
use log::*;
|
||||
|
||||
use serde::Serialize;
|
||||
use std::any::type_name;
|
||||
|
||||
pub trait AbiExample: Sized {
|
||||
fn example() -> Self;
|
||||
}
|
||||
|
||||
// Following code snippets are copied and adapted from the official rustc implementation to
|
||||
// implement AbiExample trait for most of basic types.
|
||||
// These are licensed under Apache-2.0 + MIT (compatible because we're Apache-2.0)
|
||||
|
||||
// Source: https://github.com/rust-lang/rust/blob/ba18875557aabffe386a2534a1aa6118efb6ab88/src/libcore/tuple.rs#L7
|
||||
macro_rules! tuple_example_impls {
|
||||
($(
|
||||
$Tuple:ident {
|
||||
$(($idx:tt) -> $T:ident)+
|
||||
}
|
||||
)+) => {
|
||||
$(
|
||||
impl<$($T:AbiExample),+> AbiExample for ($($T,)+) {
|
||||
fn example() -> Self {
|
||||
($({ let x: $T = AbiExample::example(); x},)+)
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
// Source: https://github.com/rust-lang/rust/blob/ba18875557aabffe386a2534a1aa6118efb6ab88/src/libcore/tuple.rs#L110
|
||||
tuple_example_impls! {
|
||||
Tuple1 {
|
||||
(0) -> A
|
||||
}
|
||||
Tuple2 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
}
|
||||
Tuple3 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
(2) -> C
|
||||
}
|
||||
Tuple4 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
(2) -> C
|
||||
(3) -> D
|
||||
}
|
||||
Tuple5 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
(2) -> C
|
||||
(3) -> D
|
||||
(4) -> E
|
||||
}
|
||||
Tuple6 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
(2) -> C
|
||||
(3) -> D
|
||||
(4) -> E
|
||||
(5) -> F
|
||||
}
|
||||
Tuple7 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
(2) -> C
|
||||
(3) -> D
|
||||
(4) -> E
|
||||
(5) -> F
|
||||
(6) -> G
|
||||
}
|
||||
Tuple8 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
(2) -> C
|
||||
(3) -> D
|
||||
(4) -> E
|
||||
(5) -> F
|
||||
(6) -> G
|
||||
(7) -> H
|
||||
}
|
||||
Tuple9 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
(2) -> C
|
||||
(3) -> D
|
||||
(4) -> E
|
||||
(5) -> F
|
||||
(6) -> G
|
||||
(7) -> H
|
||||
(8) -> I
|
||||
}
|
||||
Tuple10 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
(2) -> C
|
||||
(3) -> D
|
||||
(4) -> E
|
||||
(5) -> F
|
||||
(6) -> G
|
||||
(7) -> H
|
||||
(8) -> I
|
||||
(9) -> J
|
||||
}
|
||||
Tuple11 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
(2) -> C
|
||||
(3) -> D
|
||||
(4) -> E
|
||||
(5) -> F
|
||||
(6) -> G
|
||||
(7) -> H
|
||||
(8) -> I
|
||||
(9) -> J
|
||||
(10) -> K
|
||||
}
|
||||
Tuple12 {
|
||||
(0) -> A
|
||||
(1) -> B
|
||||
(2) -> C
|
||||
(3) -> D
|
||||
(4) -> E
|
||||
(5) -> F
|
||||
(6) -> G
|
||||
(7) -> H
|
||||
(8) -> I
|
||||
(9) -> J
|
||||
(10) -> K
|
||||
(11) -> L
|
||||
}
|
||||
}
|
||||
|
||||
// Source: https://github.com/rust-lang/rust/blob/ba18875557aabffe386a2534a1aa6118efb6ab88/src/libcore/array/mod.rs#L417
|
||||
macro_rules! array_example_impls {
|
||||
{$n:expr, $t:ident $($ts:ident)*} => {
|
||||
impl<T> AbiExample for [T; $n] where T: AbiExample {
|
||||
fn example() -> Self {
|
||||
[$t::example(), $($ts::example()),*]
|
||||
}
|
||||
}
|
||||
array_example_impls!{($n - 1), $($ts)*}
|
||||
};
|
||||
{$n:expr,} => {
|
||||
impl<T> AbiExample for [T; $n] {
|
||||
fn example() -> Self { [] }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
array_example_impls! {32, T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T}
|
||||
|
||||
// Source: https://github.com/rust-lang/rust/blob/ba18875557aabffe386a2534a1aa6118efb6ab88/src/libcore/default.rs#L137
|
||||
macro_rules! example_impls {
|
||||
($t:ty, $v:expr) => {
|
||||
impl AbiExample for $t {
|
||||
fn example() -> Self {
|
||||
$v
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
example_impls! { (), () }
|
||||
example_impls! { bool, false }
|
||||
example_impls! { char, '\x00' }
|
||||
|
||||
example_impls! { usize, 0 }
|
||||
example_impls! { u8, 0 }
|
||||
example_impls! { u16, 0 }
|
||||
example_impls! { u32, 0 }
|
||||
example_impls! { u64, 0 }
|
||||
example_impls! { u128, 0 }
|
||||
|
||||
example_impls! { isize, 0 }
|
||||
example_impls! { i8, 0 }
|
||||
example_impls! { i16, 0 }
|
||||
example_impls! { i32, 0 }
|
||||
example_impls! { i64, 0 }
|
||||
example_impls! { i128, 0 }
|
||||
|
||||
example_impls! { f32, 0.0f32 }
|
||||
example_impls! { f64, 0.0f64 }
|
||||
example_impls! { String, String::new() }
|
||||
example_impls! { std::time::Duration, std::time::Duration::from_secs(0) }
|
||||
|
||||
use std::sync::atomic::*;
|
||||
|
||||
// Source: https://github.com/rust-lang/rust/blob/ba18875557aabffe386a2534a1aa6118efb6ab88/src/libcore/sync/atomic.rs#L1199
|
||||
macro_rules! atomic_example_impls {
|
||||
($atomic_type: ident) => {
|
||||
impl AbiExample for $atomic_type {
|
||||
fn example() -> Self {
|
||||
Self::new(AbiExample::example())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
atomic_example_impls! { AtomicU8 }
|
||||
atomic_example_impls! { AtomicU16 }
|
||||
atomic_example_impls! { AtomicU32 }
|
||||
atomic_example_impls! { AtomicU64 }
|
||||
atomic_example_impls! { AtomicUsize }
|
||||
atomic_example_impls! { AtomicI8 }
|
||||
atomic_example_impls! { AtomicI16 }
|
||||
atomic_example_impls! { AtomicI32 }
|
||||
atomic_example_impls! { AtomicI64 }
|
||||
atomic_example_impls! { AtomicIsize }
|
||||
atomic_example_impls! { AtomicBool }
|
||||
|
||||
#[cfg(not(feature = "program"))]
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
#[cfg(not(feature = "program"))]
|
||||
impl<T: Default, U: ArrayLength<T>> AbiExample for GenericArray<T, U> {
|
||||
fn example() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
use bv::{BitVec, BlockType};
|
||||
impl<T: BlockType> AbiExample for BitVec<T> {
|
||||
fn example() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BlockType> IgnoreAsHelper for BitVec<T> {}
|
||||
impl<T: BlockType> EvenAsOpaque for BitVec<T> {}
|
||||
|
||||
pub(crate) fn normalize_type_name(type_name: &str) -> String {
|
||||
type_name.chars().filter(|c| *c != '&').collect()
|
||||
}
|
||||
|
||||
type Placeholder = ();
|
||||
|
||||
impl<T: Sized> AbiExample for T {
|
||||
default fn example() -> Self {
|
||||
<Placeholder>::type_erased_example()
|
||||
}
|
||||
}
|
||||
|
||||
// this works like a type erasure and a hatch to escape type error to runtime error
|
||||
trait TypeErasedExample<T> {
|
||||
fn type_erased_example() -> T;
|
||||
}
|
||||
|
||||
impl<T: Sized> TypeErasedExample<T> for Placeholder {
|
||||
default fn type_erased_example() -> T {
|
||||
panic!(
|
||||
"derive or implement AbiExample/AbiEnumVisitor for {}",
|
||||
type_name::<T>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Serialize> TypeErasedExample<T> for Placeholder {
|
||||
default fn type_erased_example() -> T {
|
||||
let original_type_name = type_name::<T>();
|
||||
let normalized_type_name = normalize_type_name(original_type_name);
|
||||
|
||||
if normalized_type_name.starts_with("solana") {
|
||||
panic!(
|
||||
"derive or implement AbiExample/AbiEnumVisitor for {}",
|
||||
original_type_name
|
||||
);
|
||||
} else {
|
||||
panic!(
|
||||
"new unrecognized type for ABI digest!: {}",
|
||||
original_type_name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AbiExample> AbiExample for Option<T> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (Option<T>): {}", type_name::<Self>());
|
||||
Some(T::example())
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: AbiExample, E: AbiExample> AbiExample for Result<O, E> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (Result<O, E>): {}", type_name::<Self>());
|
||||
Ok(O::example())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AbiExample> AbiExample for Box<T> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (Box<T>): {}", type_name::<Self>());
|
||||
Box::new(T::example())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AbiExample for Box<dyn Fn(&mut T) -> () + Sync + Send> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (Box<T>): {}", type_name::<Self>());
|
||||
Box::new(move |_t: &mut T| {})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AbiExample> AbiExample for Box<[T]> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (Box<[T]>): {}", type_name::<Self>());
|
||||
Box::new([T::example()])
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AbiExample> AbiExample for std::marker::PhantomData<T> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (PhantomData<T>): {}", type_name::<Self>());
|
||||
<std::marker::PhantomData<T>>::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AbiExample> AbiExample for std::sync::Arc<T> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (Arc<T>): {}", type_name::<Self>());
|
||||
std::sync::Arc::new(T::example())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AbiExample> AbiExample for std::rc::Rc<T> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (Rc<T>): {}", type_name::<Self>());
|
||||
std::rc::Rc::new(T::example())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AbiExample> AbiExample for std::sync::Mutex<T> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (Mutex<T>): {}", type_name::<Self>());
|
||||
std::sync::Mutex::new(T::example())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AbiExample> AbiExample for std::sync::RwLock<T> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (RwLock<T>): {}", type_name::<Self>());
|
||||
std::sync::RwLock::new(T::example())
|
||||
}
|
||||
}
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
|
||||
impl<
|
||||
T: std::cmp::Eq + std::hash::Hash + AbiExample,
|
||||
S: AbiExample,
|
||||
H: std::hash::BuildHasher + Default,
|
||||
> AbiExample for HashMap<T, S, H>
|
||||
{
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (HashMap<T, S, H>): {}", type_name::<Self>());
|
||||
let mut map = HashMap::default();
|
||||
map.insert(T::example(), S::example());
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::cmp::Ord + AbiExample, S: AbiExample> AbiExample for BTreeMap<T, S> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (BTreeMap<T, S>): {}", type_name::<Self>());
|
||||
let mut map = BTreeMap::default();
|
||||
map.insert(T::example(), S::example());
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AbiExample> AbiExample for Vec<T> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (Vec<T>): {}", type_name::<Self>());
|
||||
vec![T::example()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::cmp::Eq + std::hash::Hash + AbiExample, H: std::hash::BuildHasher + Default> AbiExample
|
||||
for HashSet<T, H>
|
||||
{
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (HashSet<T, H>): {}", type_name::<Self>());
|
||||
let mut set: HashSet<T, H> = HashSet::default();
|
||||
set.insert(T::example());
|
||||
set
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::cmp::Ord + AbiExample> AbiExample for BTreeSet<T> {
|
||||
fn example() -> Self {
|
||||
info!("AbiExample for (BTreeSet<T>): {}", type_name::<Self>());
|
||||
let mut set: BTreeSet<T> = BTreeSet::default();
|
||||
set.insert(T::example());
|
||||
set
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "program"))]
|
||||
impl AbiExample for memmap::MmapMut {
|
||||
fn example() -> Self {
|
||||
memmap::MmapMut::map_anon(1).expect("failed to map the data file")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "program"))]
|
||||
impl AbiExample for std::path::PathBuf {
|
||||
fn example() -> Self {
|
||||
std::path::PathBuf::from(String::example())
|
||||
}
|
||||
}
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
impl AbiExample for SocketAddr {
|
||||
fn example() -> Self {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)
|
||||
}
|
||||
}
|
||||
|
||||
// This is a control flow indirection needed for digesting all variants of an enum
|
||||
pub trait AbiEnumVisitor: Serialize {
|
||||
fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult;
|
||||
}
|
||||
|
||||
pub trait IgnoreAsHelper {}
|
||||
pub trait EvenAsOpaque {}
|
||||
|
||||
impl<T: Serialize + ?Sized> AbiEnumVisitor for T {
|
||||
default fn visit_for_abi(&self, _digester: &mut AbiDigester) -> DigestResult {
|
||||
unreachable!(
|
||||
"AbiEnumVisitor must be implemented for {}",
|
||||
type_name::<T>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize + ?Sized + AbiExample> AbiEnumVisitor for T {
|
||||
default fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult {
|
||||
info!("AbiEnumVisitor for (default): {}", type_name::<T>());
|
||||
T::example()
|
||||
.serialize(digester.create_new())
|
||||
.map_err(DigestError::wrap_by_type::<T>)
|
||||
}
|
||||
}
|
||||
|
||||
// even (experimental) rust specialization isn't enough for us, resort to
|
||||
// the autoref hack: https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md
|
||||
// relevant test: TestVecEnum
|
||||
impl<T: Serialize + ?Sized + AbiEnumVisitor> AbiEnumVisitor for &T {
|
||||
default fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult {
|
||||
info!("AbiEnumVisitor for (&default): {}", type_name::<T>());
|
||||
// Don't call self.visit_for_abi(...) to avoid the infinite recursion!
|
||||
T::visit_for_abi(&self, digester)
|
||||
}
|
||||
}
|
||||
|
||||
// force to call self.serialize instead of T::visit_for_abi() for serialization
|
||||
// helper structs like ad-hoc iterator `struct`s
|
||||
impl<T: Serialize + IgnoreAsHelper> AbiEnumVisitor for &T {
|
||||
default fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult {
|
||||
info!("AbiEnumVisitor for (IgnoreAsHelper): {}", type_name::<T>());
|
||||
self.serialize(digester.create_new())
|
||||
.map_err(DigestError::wrap_by_type::<T>)
|
||||
}
|
||||
}
|
||||
|
||||
// force to call self.serialize instead of T::visit_for_abi() to work around the
|
||||
// inability of implementing AbiExample for private structs from other crates
|
||||
impl<T: Serialize + IgnoreAsHelper + EvenAsOpaque> AbiEnumVisitor for &T {
|
||||
default fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult {
|
||||
info!("AbiEnumVisitor for (IgnoreAsOpaque): {}", type_name::<T>());
|
||||
let top_scope = type_name::<T>().split("::").next().unwrap();
|
||||
self.serialize(digester.create_new_opaque(top_scope))
|
||||
.map_err(DigestError::wrap_by_type::<T>)
|
||||
}
|
||||
}
|
||||
|
||||
// Because Option and Result enums are so common enums, provide generic trait implementations
|
||||
// The digesting pattern must match with what is derived from #[derive(AbiEnumVisitor)]
|
||||
impl<T: AbiEnumVisitor> AbiEnumVisitor for Option<T> {
|
||||
fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult {
|
||||
info!("AbiEnumVisitor for (Option<T>): {}", type_name::<Self>());
|
||||
|
||||
let variant: Self = Option::Some(T::example());
|
||||
// serde calls serialize_some(); not serialize_variant();
|
||||
// so create_new is correct, not create_enum_child or create_enum_new
|
||||
variant.serialize(digester.create_new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: AbiEnumVisitor, E: AbiEnumVisitor> AbiEnumVisitor for Result<O, E> {
|
||||
fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult {
|
||||
info!("AbiEnumVisitor for (Result<O, E>): {}", type_name::<Self>());
|
||||
|
||||
digester.update(&["enum Result (variants = 2)"]);
|
||||
let variant: Self = Result::Ok(O::example());
|
||||
variant.serialize(digester.create_enum_child())?;
|
||||
|
||||
let variant: Self = Result::Err(E::example());
|
||||
variant.serialize(digester.create_enum_child())?;
|
||||
|
||||
Ok(digester.create_child())
|
||||
}
|
||||
}
|
|
@ -1,6 +1,13 @@
|
|||
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(specialization))]
|
||||
|
||||
// Allows macro expansion of `use ::solana_sdk::*` to work within this crate
|
||||
extern crate self as solana_sdk;
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
pub mod abi_digester;
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
pub mod abi_example;
|
||||
|
||||
pub mod account;
|
||||
pub mod account_utils;
|
||||
pub mod bpf_loader;
|
||||
|
@ -91,3 +98,8 @@ pub mod transport;
|
|||
extern crate serde_derive;
|
||||
pub extern crate bs58;
|
||||
extern crate log as logger;
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate solana_sdk_macro_frozen_abi;
|
||||
|
|
Loading…
Reference in New Issue