Unverified Commit bd4b51e3 authored by Robin Freyler's avatar Robin Freyler Committed by GitHub
Browse files

Implement selector_id!, selector_bytes! and blake2x256! macros (#947)

* add selector_id! and selector_bytes! proc macros

* implement blake2x256! macro

* re-export blake2x256! macro from ink_lang crate

* apply rustfmt

* add BLAKE2b to hunspell dictionary

* add UI tests for blake2x256! macro

* improve span for non-literal inputs to blake2x256! macro

* add non-literal input failure UI test to blake2x256! macro

* improve error span for non-literal selector_{id,bytes}! macro inputs

* rename UI test blake2x256 -> blake2x256_macro

* rename UI test

* add UI tests for selector_id! proc. macro

* fix UI test

* fix UI test expectation

* add UI tests for seletor_bytes! macro

* make flaky and broken codecov CI happy again ...
parent 7c19f63e
Pipeline #160560 failed with stages
in 9 minutes and 22 seconds
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
ABI ABI
AST AST
BLAKE2 BLAKE2
BLAKE2b
DApp DApp
ERC ERC
FFI FFI
......
// Copyright 2018-2021 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::GenerateCode;
use derive_more::From;
use ir::HexLiteral;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote_spanned;
/// Generates code for the `selector_id!` macro.
#[derive(From)]
pub struct Blake2x256<'a> {
/// The `blake2x256!` macro input.
macro_input: &'a ir::Blake2x256Macro,
}
impl GenerateCode for Blake2x256<'_> {
/// Generates `selector_id!` macro code.
fn generate_code(&self) -> TokenStream2 {
let span = self.macro_input.input().span();
let hash_bytes = self
.macro_input
.hash()
.map(|byte| byte.hex_padded_suffixed());
quote_spanned!(span=> [ #( #hash_bytes ),* ] )
}
}
...@@ -26,6 +26,7 @@ macro_rules! impl_as_ref_for_generator { ...@@ -26,6 +26,7 @@ macro_rules! impl_as_ref_for_generator {
}; };
} }
mod blake2b;
mod chain_extension; mod chain_extension;
mod contract; mod contract;
mod cross_calling; mod cross_calling;
...@@ -35,10 +36,12 @@ mod events; ...@@ -35,10 +36,12 @@ mod events;
mod ink_test; mod ink_test;
mod item_impls; mod item_impls;
mod metadata; mod metadata;
mod selector;
mod storage; mod storage;
mod trait_def; mod trait_def;
pub use self::{ pub use self::{
blake2b::Blake2x256,
chain_extension::ChainExtension, chain_extension::ChainExtension,
contract::Contract, contract::Contract,
cross_calling::{ cross_calling::{
...@@ -51,6 +54,10 @@ pub use self::{ ...@@ -51,6 +54,10 @@ pub use self::{
ink_test::InkTest, ink_test::InkTest,
item_impls::ItemImpls, item_impls::ItemImpls,
metadata::Metadata, metadata::Metadata,
selector::{
SelectorBytes,
SelectorId,
},
storage::Storage, storage::Storage,
trait_def::TraitDefinition, trait_def::TraitDefinition,
}; };
// Copyright 2018-2021 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::GenerateCode;
use derive_more::From;
use ir::HexLiteral;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote_spanned;
/// Generates code for the `selector_id!` macro.
#[derive(From)]
pub struct SelectorId<'a> {
/// The contract to generate code for.
macro_input: &'a ir::SelectorMacro<ir::marker::SelectorId>,
}
impl GenerateCode for SelectorId<'_> {
/// Generates `selector_id!` macro code.
fn generate_code(&self) -> TokenStream2 {
let span = self.macro_input.input().span();
let selector_id = self
.macro_input
.selector()
.into_be_u32()
.hex_padded_suffixed();
quote_spanned!(span=> #selector_id)
}
}
/// Generates code for the `selector_bytes!` macro.
#[derive(From)]
pub struct SelectorBytes<'a> {
/// The contract to generate code for.
macro_input: &'a ir::SelectorMacro<ir::marker::SelectorBytes>,
}
impl GenerateCode for SelectorBytes<'_> {
/// Generates `selector_id!` macro code.
fn generate_code(&self) -> TokenStream2 {
let span = self.macro_input.input().span();
let selector_bytes = self.macro_input.selector().hex_lits();
quote_spanned!(span=> [ #( #selector_bytes ),* ] )
}
}
...@@ -43,6 +43,18 @@ impl<'a> CodeGenerator for &'a ir::ChainExtension { ...@@ -43,6 +43,18 @@ impl<'a> CodeGenerator for &'a ir::ChainExtension {
type Generator = generator::ChainExtension<'a>; type Generator = generator::ChainExtension<'a>;
} }
impl<'a> CodeGenerator for &'a ir::SelectorMacro<ir::marker::SelectorId> {
type Generator = generator::SelectorId<'a>;
}
impl<'a> CodeGenerator for &'a ir::SelectorMacro<ir::marker::SelectorBytes> {
type Generator = generator::SelectorBytes<'a>;
}
impl<'a> CodeGenerator for &'a ir::Blake2x256Macro {
type Generator = generator::Blake2x256<'a>;
}
/// Generates the entire code for the given ink! contract. /// Generates the entire code for the given ink! contract.
pub fn generate_code<T>(entity: T) -> TokenStream2 pub fn generate_code<T>(entity: T) -> TokenStream2
where where
......
...@@ -12,6 +12,10 @@ ...@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use core::convert::TryFrom;
use proc_macro2::TokenStream as TokenStream2;
use syn::spanned::Spanned as _;
/// Computes the BLAKE-2b 256-bit hash for the given input and stores it in output. /// Computes the BLAKE-2b 256-bit hash for the given input and stores it in output.
pub fn blake2b_256(input: &[u8], output: &mut [u8]) { pub fn blake2b_256(input: &[u8], output: &mut [u8]) {
use ::blake2::digest::{ use ::blake2::digest::{
...@@ -22,3 +26,58 @@ pub fn blake2b_256(input: &[u8], output: &mut [u8]) { ...@@ -22,3 +26,58 @@ pub fn blake2b_256(input: &[u8], output: &mut [u8]) {
blake2.update(input); blake2.update(input);
blake2.finalize_variable(|result| output.copy_from_slice(result)); blake2.finalize_variable(|result| output.copy_from_slice(result));
} }
/// Computes the BLAKE2b-256 bit hash of a string or byte string literal.
///
/// # Note
///
/// This is mainly used for analysis and codegen of the `blake2x256!` macro.
#[derive(Debug)]
pub struct Blake2x256Macro {
hash: [u8; 32],
input: syn::Lit,
}
impl Blake2x256Macro {
/// Returns the underlying selector.
pub fn hash(&self) -> [u8; 32] {
self.hash
}
/// Returns the literal input of the BLAKE-2b hash.
pub fn input(&self) -> &syn::Lit {
&self.input
}
}
impl TryFrom<TokenStream2> for Blake2x256Macro {
type Error = syn::Error;
fn try_from(input: TokenStream2) -> Result<Self, Self::Error> {
let input_span = input.span();
let lit = syn::parse2::<syn::Lit>(input).map_err(|error| {
format_err!(
input_span,
"expected string or byte string literal as input: {}",
error
)
})?;
let input_bytes = match lit {
syn::Lit::Str(ref lit_str) => lit_str.value().into_bytes(),
syn::Lit::ByteStr(ref byte_str) => byte_str.value(),
invalid => {
return Err(format_err!(
invalid.span(),
"expected string or byte string literal as input. found {:?}",
invalid,
))
}
};
let mut output = [0u8; 32];
blake2b_256(&input_bytes, &mut output);
Ok(Self {
hash: output,
input: lit,
})
}
}
...@@ -28,6 +28,14 @@ mod selector; ...@@ -28,6 +28,14 @@ mod selector;
mod trait_def; mod trait_def;
pub mod utils; pub mod utils;
/// Marker types and definitions.
pub mod marker {
pub use super::selector::{
SelectorBytes,
SelectorId,
};
}
#[cfg(test)] #[cfg(test)]
use self::attrs::Attribute; use self::attrs::Attribute;
...@@ -43,6 +51,10 @@ use self::attrs::{ ...@@ -43,6 +51,10 @@ use self::attrs::{
}; };
pub use self::{ pub use self::{
attrs::Namespace, attrs::Namespace,
blake2::{
blake2b_256,
Blake2x256Macro,
},
chain_extension::{ chain_extension::{
ChainExtension, ChainExtension,
ChainExtensionMethod, ChainExtensionMethod,
...@@ -76,7 +88,10 @@ pub use self::{ ...@@ -76,7 +88,10 @@ pub use self::{
IterEvents, IterEvents,
IterItemImpls, IterItemImpls,
}, },
selector::Selector, selector::{
Selector,
SelectorMacro,
},
trait_def::{ trait_def::{
InkTrait, InkTrait,
InkTraitConstructor, InkTraitConstructor,
......
...@@ -14,8 +14,12 @@ ...@@ -14,8 +14,12 @@
use super::blake2::blake2b_256; use super::blake2::blake2b_256;
use crate::literal::HexLiteral; use crate::literal::HexLiteral;
use core::convert::TryFrom;
use proc_macro2::TokenStream as TokenStream2;
use std::marker::PhantomData;
use syn::spanned::Spanned as _;
/// A function selector. /// The selector of an ink! dispatchable.
/// ///
/// # Note /// # Note
/// ///
...@@ -65,6 +69,68 @@ impl From<[u8; 4]> for Selector { ...@@ -65,6 +69,68 @@ impl From<[u8; 4]> for Selector {
} }
} }
/// Used as generic parameter for the `selector_id!` macro.
pub enum SelectorId {}
/// Used as generic parameter for the `selector_bytes!` macro.
pub enum SelectorBytes {}
/// The selector ID of an ink! dispatchable.
///
/// # Note
///
/// This is mainly used for analysis and codegen of the `selector_id!` macro.
#[derive(Debug)]
pub struct SelectorMacro<T> {
selector: Selector,
input: syn::Lit,
_marker: PhantomData<fn() -> T>,
}
impl<T> SelectorMacro<T> {
/// Returns the underlying selector.
pub fn selector(&self) -> Selector {
self.selector
}
/// Returns the literal input of the selector ID.
pub fn input(&self) -> &syn::Lit {
&self.input
}
}
impl<T> TryFrom<TokenStream2> for SelectorMacro<T> {
type Error = syn::Error;
fn try_from(input: TokenStream2) -> Result<Self, Self::Error> {
let input_span = input.span();
let lit = syn::parse2::<syn::Lit>(input).map_err(|error| {
format_err!(
input_span,
"expected string or byte string literal as input: {}",
error
)
})?;
let input_bytes = match lit {
syn::Lit::Str(ref lit_str) => lit_str.value().into_bytes(),
syn::Lit::ByteStr(ref byte_str) => byte_str.value(),
invalid => {
return Err(format_err!(
invalid.span(),
"expected string or byte string literal as input. found {:?}",
invalid,
))
}
};
let selector = Selector::new(&input_bytes);
Ok(Self {
selector,
input: lit,
_marker: PhantomData,
})
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
......
...@@ -36,6 +36,9 @@ mod literal; ...@@ -36,6 +36,9 @@ mod literal;
pub use self::{ pub use self::{
ir::{ ir::{
blake2b_256,
marker,
Blake2x256Macro,
Callable, Callable,
CallableKind, CallableKind,
CallableWithSelector, CallableWithSelector,
...@@ -66,6 +69,7 @@ pub use self::{ ...@@ -66,6 +69,7 @@ pub use self::{
Namespace, Namespace,
Receiver, Receiver,
Selector, Selector,
SelectorMacro,
Storage, Storage,
Visibility, Visibility,
}, },
......
// Copyright 2018-2021 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use core::convert::TryFrom as _;
use ink_lang_codegen::generate_code;
use ink_lang_ir::Blake2x256Macro;
use proc_macro2::TokenStream as TokenStream2;
use syn::Result;
pub fn generate_blake2x256_hash(input: TokenStream2) -> TokenStream2 {
match generate_blake2x256_hash_or_err(input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
}
pub fn generate_blake2x256_hash_or_err(input: TokenStream2) -> Result<TokenStream2> {
let hash = Blake2x256Macro::try_from(input)?;
Ok(generate_code(&hash))
}
...@@ -14,13 +14,81 @@ ...@@ -14,13 +14,81 @@
extern crate proc_macro; extern crate proc_macro;
mod blake2b;
mod chain_extension; mod chain_extension;
mod contract; mod contract;
mod ink_test; mod ink_test;
mod selector;
mod trait_def; mod trait_def;
use proc_macro::TokenStream; use proc_macro::TokenStream;
/// Computes and expands into the BLAKE2b 256-bit hash of the string input.
///
/// # Note
///
/// - The computation takes place at compilation time of the crate.
/// - The returned value is of type `[u8; 32]`.
///
/// # Example
///
/// ```
/// # use ink_lang_macro::blake2x256;
/// # use ink_lang_ir::blake2b_256;
/// assert_eq!(
/// blake2x256!("hello"),
/// {
/// let mut output = [0u8; 32];
/// blake2b_256(b"hello", &mut output);
/// output
/// }
/// );
/// ```
#[proc_macro]
pub fn blake2x256(input: TokenStream) -> TokenStream {
blake2b::generate_blake2x256_hash(input.into()).into()
}
/// Computes the ink! selector of the string and expands into its `u32` representation.
///
/// # Note
///
/// The computation takes place at compilation time of the crate.
///
/// # Example
///
/// ```
/// # use ink_lang_macro::selector_id;
/// assert_eq!(
/// selector_id!("hello"),
/// 843960066,
/// );
/// ```
#[proc_macro]
pub fn selector_id(input: TokenStream) -> TokenStream {
selector::generate_selector_id(input.into()).into()
}
/// Computes the ink! selector of the string and expands into its byte representation.
///
/// # Note
///
/// The computation takes place at compilation time of the crate.
///
/// # Example
///
/// ```
/// # use ink_lang_macro::selector_bytes;
/// assert_eq!(
/// selector_bytes!("hello"),
/// [50, 77, 207, 2],
/// );
/// ```
#[proc_macro]
pub fn selector_bytes(input: TokenStream) -> TokenStream {
selector::generate_selector_bytes(input.into()).into()
}
/// Entry point for writing ink! smart contracts. /// Entry point for writing ink! smart contracts.
/// ///
/// If you are a beginner trying to learn ink! we recommend you to check out /// If you are a beginner trying to learn ink! we recommend you to check out
......
// Copyright 2018-2021 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use core::convert::TryFrom as _;
use ink_lang_codegen::generate_code;
use ink_lang_ir::{
marker::{
SelectorBytes,
SelectorId,
},
SelectorMacro,
};
use proc_macro2::TokenStream as TokenStream2;
use syn::Result;
pub fn generate_selector_id(input: TokenStream2) -> TokenStream2 {
match generate_selector_id_or_err(input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
}
pub fn generate_selector_id_or_err(input: TokenStream2) -> Result<TokenStream2> {
let selector = SelectorMacro::<SelectorId>::try_from(input)?;
Ok(generate_code(&selector))
}
pub fn generate_selector_bytes(input: TokenStream2) -> TokenStream2 {
match generate_selector_bytes_or_err(input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
}
pub fn generate_selector_bytes_or_err(input: TokenStream2) -> Result<TokenStream2> {
let selector = SelectorMacro::<SelectorBytes>::try_from(input)?;
Ok(generate_code(&selector))
}
...@@ -16,6 +16,15 @@ ...@@ -16,6 +16,15 @@
fn compile_tests() { fn compile_tests() {
let t = trybuild::TestCases::new(); let t = trybuild::TestCases::new();
t.pass("tests/ui/blake2b/pass/*.rs");
t.compile_fail("tests/ui/blake2b/fail/*.rs");