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 @@
ABI
AST
BLAKE2
BLAKE2b
DApp
ERC
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 {
};
}
mod blake2b;
mod chain_extension;
mod contract;
mod cross_calling;
......@@ -35,10 +36,12 @@ mod events;
mod ink_test;
mod item_impls;
mod metadata;
mod selector;
mod storage;
mod trait_def;
pub use self::{
blake2b::Blake2x256,
chain_extension::ChainExtension,
contract::Contract,
cross_calling::{
......@@ -51,6 +54,10 @@ pub use self::{
ink_test::InkTest,
item_impls::ItemImpls,
metadata::Metadata,
selector::{
SelectorBytes,
SelectorId,
},
storage::Storage,
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 {
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.
pub fn generate_code<T>(entity: T) -> TokenStream2
where
......
......@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// 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.
pub fn blake2b_256(input: &[u8], output: &mut [u8]) {
use ::blake2::digest::{
......@@ -22,3 +26,58 @@ pub fn blake2b_256(input: &[u8], output: &mut [u8]) {
blake2.update(input);
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;
mod trait_def;
pub mod utils;
/// Marker types and definitions.
pub mod marker {
pub use super::selector::{
SelectorBytes,
SelectorId,
};
}
#[cfg(test)]
use self::attrs::Attribute;
......@@ -43,6 +51,10 @@ use self::attrs::{
};
pub use self::{
attrs::Namespace,
blake2::{
blake2b_256,
Blake2x256Macro,
},
chain_extension::{
ChainExtension,
ChainExtensionMethod,
......@@ -76,7 +88,10 @@ pub use self::{
IterEvents,
IterItemImpls,
},
selector::Selector,
selector::{
Selector,
SelectorMacro,
},
trait_def::{
InkTrait,
InkTraitConstructor,
......
......@@ -14,8 +14,12 @@
use super::blake2::blake2b_256;
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
///
......@@ -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)]
mod tests {
use super::*;
......
......@@ -36,6 +36,9 @@ mod literal;
pub use self::{
ir::{
blake2b_256,
marker,
Blake2x256Macro,
Callable,
CallableKind,
CallableWithSelector,
......@@ -66,6 +69,7 @@ pub use self::{
Namespace,
Receiver,
Selector,
SelectorMacro,
Storage,
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 @@
extern crate proc_macro;
mod blake2b;
mod chain_extension;
mod contract;
mod ink_test;
mod selector;
mod trait_def;
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.
///
/// 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 @@
fn compile_tests() {
let t = trybuild::TestCases::new();
t.pass("tests/ui/blake2b/pass/*.rs");
t.compile_fail("tests/ui/blake2b/fail/*.rs");
t.pass("tests/ui/selector_id/pass/*.rs");
t.compile_fail("tests/ui/selector_id/fail/*.rs");
t.pass("tests/ui/selector_bytes/pass/*.rs");
t.compile_fail("tests/ui/selector_bytes/fail/*.rs");
t.pass("tests/ui/contract/pass/01-noop-contract.rs");
t.pass("tests/ui/contract/pass/02-flipper-contract.rs");
t.pass("tests/ui/contract/pass/03-incrementer-contract.rs");
......
use ink_lang as ink;
const _: [u8; 32] = ink::blake2x256!(true);
fn main() {}
error: expected string or byte string literal as input. found Bool(LitBool { value: true })
--> $DIR/invalid_parameter_type_01.rs:3:38
|
3 | const _: [u8; 32] = ink::blake2x256!(true);
| ^^^^
use ink_lang as ink;
const _: [u8; 32] = ink::blake2x256!(42);
fn main() {}
error: expected string or byte string literal as input. found Int(LitInt { token: 42 })
--> $DIR/invalid_parameter_type_02.rs:3:38
|
3 | const _: [u8; 32] = ink::blake2x256!(42);
| ^^
use ink_lang as ink;
const _: [u8; 32] = ink::blake2x256!();
fn main() {}
error: expected string or byte string literal as input: unexpected end of input, expected literal
--> $DIR/missing_parameter.rs:3:21
|
3 | const _: [u8; 32] = ink::blake2x256!();
| ^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `ink::blake2x256` (in Nightly builds, run with -Z macro-backtrace for more info)
use ink_lang as ink;
const INPUT: &str = "test";
const _: [u8; 32] = ink::blake2x256!(INPUT);
fn main() {}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment