Skip to content

Commit

Permalink
Restrict wildcard selectors to have exactly one other message (#1708)
Browse files Browse the repository at this point in the history
* Add some tests/todos

* Testing

* Add test for using well known selector without wildcard

* Require reserved selector

* Define reserved selector const

* WIP defining wildcard selector complement

* Compiles with wildcard selector complement

* Return combined error on 2 or more messages

* Fix up compile test for wildcard selector complement

* Add error for when wildcard complement used without wildcard

* Fix test

* Fmt

* Fmt

* Clear up error combine code

* Fix some wildcard complement tests

* WIP use correct wildcard complement selector

* Introduce Symbol parsing for MetaValue

* Remove unused imports

* Add wildcard-selector example

* Calculate correct wildcard complement selector

* Use well known wildcard complement constant

* Fix UI test

* WIP wildcard selector integration test

* WIP wildcard selector integration test

* Test wildcard

* Wildcard complement test, define const in prelude and reexport

* spellcheck

* Clippy

* Use underscores for param bindings
  • Loading branch information
ascjones authored Apr 20, 2023
1 parent 55088cc commit 137d18d
Show file tree
Hide file tree
Showing 23 changed files with 575 additions and 69 deletions.
4 changes: 2 additions & 2 deletions crates/e2e/macro/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl TryFrom<ast::AttributeArgs> for E2EConfig {
"E2E test",
))
}
if let ast::PathOrLit::Lit(syn::Lit::Str(lit_str)) = &arg.value {
if let ast::MetaValue::Lit(syn::Lit::Str(lit_str)) = &arg.value {
additional_contracts = Some((lit_str.clone(), arg))
} else {
return Err(format_err_spanned!(
Expand All @@ -69,7 +69,7 @@ impl TryFrom<ast::AttributeArgs> for E2EConfig {
if let Some((_, ast)) = environment {
return Err(duplicate_config_err(ast, arg, "environment", "E2E test"))
}
if let ast::PathOrLit::Path(path) = &arg.value {
if let ast::MetaValue::Path(path) = &arg.value {
environment = Some((path.clone(), arg))
} else {
return Err(format_err_spanned!(
Expand Down
9 changes: 9 additions & 0 deletions crates/e2e/src/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ impl<E, RetType> Message<E, RetType>
where
E: Environment,
{
/// Create a new message from the given account id and encoded message data.
pub fn new(account_id: E::AccountId, exec_input: Vec<u8>) -> Self {
Self {
account_id,
exec_input,
_return_type: Default::default(),
}
}

/// The account id of the contract being called to invoke the message.
pub fn account_id(&self) -> &E::AccountId {
&self.account_id
Expand Down
1 change: 1 addition & 0 deletions crates/e2e/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod xts;

pub use builders::{
build_message,
Message,
MessageBuilder,
};
pub use client::{
Expand Down
1 change: 1 addition & 0 deletions crates/ink/ir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ proc-macro2 = "1.0"
itertools = { version = "0.10", default-features = false }
either = { version = "1.5", default-features = false }
blake2 = "0.10"
ink_prelude = { version = "4.1.0", path = "../../prelude/", default-features = false }

[features]
default = ["std"]
Expand Down
24 changes: 12 additions & 12 deletions crates/ink/ir/src/ast/attr_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl Parse for AttributeArgs {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::PathOrLit;
use crate::ast::MetaValue;
use quote::quote;

impl AttributeArgs {
Expand Down Expand Up @@ -81,7 +81,7 @@ mod tests {
AttributeArgs::new(vec![MetaNameValue {
name: syn::parse_quote! { name },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Lit(syn::parse_quote! { true }),
value: MetaValue::Lit(syn::parse_quote! { true }),
}])
)
}
Expand All @@ -93,7 +93,7 @@ mod tests {
AttributeArgs::new(vec![MetaNameValue {
name: syn::parse_quote! { name },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Lit(syn::parse_quote! { "string literal" }),
value: MetaValue::Lit(syn::parse_quote! { "string literal" }),
}])
)
}
Expand All @@ -105,7 +105,7 @@ mod tests {
AttributeArgs::new(vec![MetaNameValue {
name: syn::parse_quote! { name },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Path(syn::parse_quote! { MyIdentifier }),
value: MetaValue::Path(syn::parse_quote! { MyIdentifier }),
}])
)
}
Expand All @@ -117,7 +117,7 @@ mod tests {
AttributeArgs::new(vec![MetaNameValue {
name: syn::parse_quote! { name },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Path(syn::parse_quote! { ::this::is::my::Path }),
value: MetaValue::Path(syn::parse_quote! { ::this::is::my::Path }),
}])
)
}
Expand All @@ -130,7 +130,7 @@ mod tests {
AttributeArgs::new(vec![MetaNameValue {
name: syn::parse_quote! { name },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Path(
value: MetaValue::Path(
syn::parse_quote! { this::is::my::relative::Path }
),
}])
Expand All @@ -143,7 +143,7 @@ mod tests {
expected_args.push_value(MetaNameValue {
name: syn::parse_quote! { name },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Path(syn::parse_quote! { value }),
value: MetaValue::Path(syn::parse_quote! { value }),
});
expected_args.push_punct(<Token![,]>::default());
assert_eq!(
Expand All @@ -169,27 +169,27 @@ mod tests {
MetaNameValue {
name: syn::parse_quote! { name1 },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Path(syn::parse_quote! { ::root::Path }),
value: MetaValue::Path(syn::parse_quote! { ::root::Path }),
},
MetaNameValue {
name: syn::parse_quote! { name2 },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Lit(syn::parse_quote! { false }),
value: MetaValue::Lit(syn::parse_quote! { false }),
},
MetaNameValue {
name: syn::parse_quote! { name3 },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Lit(syn::parse_quote! { "string literal" }),
value: MetaValue::Lit(syn::parse_quote! { "string literal" }),
},
MetaNameValue {
name: syn::parse_quote! { name4 },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Lit(syn::parse_quote! { 42 }),
value: MetaValue::Lit(syn::parse_quote! { 42 }),
},
MetaNameValue {
name: syn::parse_quote! { name5 },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Lit(syn::parse_quote! { 7.7 }),
value: MetaValue::Lit(syn::parse_quote! { 7.7 }),
},
])
)
Expand Down
57 changes: 46 additions & 11 deletions crates/ink/ir/src/ast/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl ToTokens for Meta {
pub struct MetaNameValue {
pub name: syn::Path,
pub eq_token: syn::token::Eq,
pub value: PathOrLit,
pub value: MetaValue,
}

impl Parse for MetaNameValue {
Expand Down Expand Up @@ -107,35 +107,40 @@ impl MetaNameValue {
}
}

/// Either a path or a literal.
/// Represents a value in a meta name-value pair.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PathOrLit {
pub enum MetaValue {
Path(syn::Path),
Lit(syn::Lit),
Symbol(Symbol),
}

impl Parse for PathOrLit {
impl Parse for MetaValue {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
if input.peek(Token![_]) || input.peek(Token![@]) {
return input.parse::<Symbol>().map(MetaValue::Symbol)
}
if input.fork().peek(syn::Lit) {
return input.parse::<syn::Lit>().map(PathOrLit::Lit)
return input.parse::<syn::Lit>().map(MetaValue::Lit)
}
if input.fork().peek(Ident::peek_any) || input.fork().peek(Token![::]) {
return input.call(parse_meta_path).map(PathOrLit::Path)
return input.call(parse_meta_path).map(MetaValue::Path)
}
Err(input.error("cannot parse into either literal or path"))
Err(input.error("expected a literal, a path or a punct for a meta value"))
}
}

impl ToTokens for PathOrLit {
impl ToTokens for MetaValue {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
Self::Lit(lit) => lit.to_tokens(tokens),
Self::Path(path) => path.to_tokens(tokens),
Self::Symbol(symbol) => symbol.to_tokens(tokens),
}
}
}

impl PathOrLit {
impl MetaValue {
/// Returns the value of the literal if it is a boolean literal.
pub fn as_bool(&self) -> Option<bool> {
match self {
Expand All @@ -161,6 +166,33 @@ impl PathOrLit {
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Symbol {
Underscore(Token![_]),
AtSign(Token![@]),
}

impl Parse for Symbol {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Token![_]) {
Ok(Symbol::Underscore(input.parse()?))
} else if input.peek(Token![@]) {
Ok(Symbol::AtSign(input.parse()?))
} else {
Err(input.error("expected either a `_` or a `@` symbol"))
}
}
}

impl ToTokens for Symbol {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
Self::Underscore(underscore) => underscore.to_tokens(tokens),
Self::AtSign(at_sign) => at_sign.to_tokens(tokens),
}
}
}

/// Like [`syn::Path::parse_mod_style`] but accepts keywords in the path.
///
/// # Note
Expand Down Expand Up @@ -194,7 +226,10 @@ fn parse_meta_path(input: ParseStream) -> Result<syn::Path, syn::Error> {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::PathOrLit;
use crate::ast::{
MetaValue,
Symbol,
};
use quote::quote;

#[test]
Expand All @@ -204,7 +239,7 @@ mod tests {
Meta::NameValue(MetaNameValue {
name: syn::parse_quote! { selector },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Path(syn::Path::from(quote::format_ident!("_"))),
value: MetaValue::Symbol(Symbol::Underscore(syn::parse_quote! { _ })),
})
)
}
Expand Down
3 changes: 2 additions & 1 deletion crates/ink/ir/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub use self::{
meta::{
Meta,
MetaNameValue,
PathOrLit,
MetaValue,
Symbol,
},
};
41 changes: 25 additions & 16 deletions crates/ink/ir/src/ir/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use core::result::Result;
use std::collections::HashMap;

use ink_prelude::IIP2_WILDCARD_COMPLEMENT_SELECTOR;
use proc_macro2::{
Span,
TokenStream as TokenStream2,
Expand Down Expand Up @@ -531,22 +532,27 @@ pub enum SelectorOrWildcard {
/// annotated with the wildcard selector will be invoked.
Wildcard,
/// A user provided selector.
UserProvided(ir::Selector),
UserProvided(Selector),
}

impl SelectorOrWildcard {
/// Create a new `SelectorOrWildcard::Selector` from the supplied bytes.
fn selector(bytes: [u8; 4]) -> SelectorOrWildcard {
fn selector(bytes: [u8; 4]) -> Self {
SelectorOrWildcard::UserProvided(Selector::from(bytes))
}

/// The selector of the wildcard complement message.
pub fn wildcard_complement() -> Self {
Self::selector(IIP2_WILDCARD_COMPLEMENT_SELECTOR)
}
}

impl TryFrom<&ast::PathOrLit> for SelectorOrWildcard {
impl TryFrom<&ast::MetaValue> for SelectorOrWildcard {
type Error = syn::Error;

fn try_from(value: &ast::PathOrLit) -> Result<Self, Self::Error> {
fn try_from(value: &ast::MetaValue) -> Result<Self, Self::Error> {
match value {
ast::PathOrLit::Lit(lit) => {
ast::MetaValue::Lit(lit) => {
if let syn::Lit::Str(_) = lit {
return Err(format_err_spanned!(
lit,
Expand All @@ -571,16 +577,19 @@ impl TryFrom<&ast::PathOrLit> for SelectorOrWildcard {
"expected 4-digit hexcode for `selector` argument, e.g. #[ink(selector = 0xC0FEBABE]"
))
}
ast::PathOrLit::Path(path) => {
if path.is_ident("_") {
Ok(SelectorOrWildcard::Wildcard)
} else {
Err(format_err_spanned!(
path,
"expected `selector` argument to be either a 4-digit hexcode or `_`"
))
ast::MetaValue::Symbol(symbol) => {
match symbol {
ast::Symbol::Underscore(_) => Ok(SelectorOrWildcard::Wildcard),
ast::Symbol::AtSign(_) => Ok(SelectorOrWildcard::wildcard_complement()),
}
}
ast::MetaValue::Path(path) => {
Err(format_err_spanned!(
path,
"unexpected path for `selector` argument, expected a 4-digit hexcode or one of \
the wildcard symbols: `_` or `@`"
))
}
}
}
}
Expand All @@ -601,11 +610,11 @@ pub struct Namespace {
bytes: Vec<u8>,
}

impl TryFrom<&ast::PathOrLit> for Namespace {
impl TryFrom<&ast::MetaValue> for Namespace {
type Error = syn::Error;

fn try_from(value: &ast::PathOrLit) -> Result<Self, Self::Error> {
if let ast::PathOrLit::Lit(syn::Lit::Str(lit_str)) = value {
fn try_from(value: &ast::MetaValue) -> Result<Self, Self::Error> {
if let ast::MetaValue::Lit(syn::Lit::Str(lit_str)) = value {
let argument = lit_str.value();
syn::parse_str::<syn::Ident>(&argument).map_err(|_error| {
format_err_spanned!(
Expand Down
2 changes: 1 addition & 1 deletion crates/ink/ir/src/ir/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl TryFrom<ast::AttributeArgs> for Config {
if let Some((_, ast)) = env {
return Err(duplicate_config_err(ast, arg, "env", "contract"))
}
if let ast::PathOrLit::Path(path) = &arg.value {
if let ast::MetaValue::Path(path) = &arg.value {
env = Some((Environment { path: path.clone() }, arg))
} else {
return Err(format_err_spanned!(
Expand Down
7 changes: 7 additions & 0 deletions crates/ink/ir/src/ir/item_impl/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ where
<C as Callable>::has_wildcard_selector(self.callable)
}

fn has_wildcard_complement_selector(&self) -> bool {
<C as Callable>::has_wildcard_complement_selector(self.callable)
}

fn visibility(&self) -> Visibility {
<C as Callable>::visibility(self.callable)
}
Expand Down Expand Up @@ -180,6 +184,9 @@ pub trait Callable {
/// Returns `true` if the ink! callable is flagged as a wildcard selector.
fn has_wildcard_selector(&self) -> bool;

/// Returns `true` if the ink! callable is flagged as a wildcard complement selector.
fn has_wildcard_complement_selector(&self) -> bool;

/// Returns the visibility of the ink! callable.
fn visibility(&self) -> Visibility;

Expand Down
9 changes: 5 additions & 4 deletions crates/ink/ir/src/ir/item_impl/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,11 @@ impl Callable for Constructor {
}

fn has_wildcard_selector(&self) -> bool {
if let Some(SelectorOrWildcard::Wildcard) = self.selector {
return true
}
false
matches!(self.selector, Some(SelectorOrWildcard::Wildcard))
}

fn has_wildcard_complement_selector(&self) -> bool {
self.selector == Some(SelectorOrWildcard::wildcard_complement())
}

fn is_payable(&self) -> bool {
Expand Down
Loading

0 comments on commit 137d18d

Please sign in to comment.