Commit 48fb16f0 authored by Michael Müller's avatar Michael Müller Committed by Hero Bird

[abi] Improve JSON encoding of selectors + layout key (#207)

* Export selector as hex string

To have an unambiguous exported representation.
The previously used `u32` can be interpreted
differently depending on the endianness of the
target system.

* Improve JSON encoding of Key `layout` in Metadata

* Add tests for selector serialization

* Encode selector as [u8; 4]

* Remove unused import

* Satisfy rustfmt check

* Improve messages! macro

* Convert tabs to spaces.
* Replace `DELIMITER` with `@delimiter`.
* Improve comments.

* Improve comment

* Ensure old ABI format stays the same

* Reduce code duplication by introducing selector_to_expr()

* Satisfy rustfmt
parent b0429dea
Pipeline #54736 failed with stages
in 1 minute and 33 seconds
...@@ -19,6 +19,9 @@ derive_more = { version = "0.15", default-features = false, features = ["no_std" ...@@ -19,6 +19,9 @@ derive_more = { version = "0.15", default-features = false, features = ["no_std"
ink_abi_derive = { version = "0.1.0", path = "derive", default-features = false, optional = true } ink_abi_derive = { version = "0.1.0", path = "derive", default-features = false, optional = true }
type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"] } type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"] }
[dev-dependencies]
serde_json = "1.0"
[features] [features]
default = [ default = [
"std", "std",
......
...@@ -15,7 +15,11 @@ ...@@ -15,7 +15,11 @@
// along with ink!. If not, see <http://www.gnu.org/licenses/>. // along with ink!. If not, see <http://www.gnu.org/licenses/>.
use derive_more::From; use derive_more::From;
use serde::Serialize; use serde::{
Serialize,
Serializer,
};
use std::fmt::Write;
use type_metadata::{ use type_metadata::{
form::{ form::{
CompactForm, CompactForm,
...@@ -158,7 +162,7 @@ impl IntoCompact for LayoutField { ...@@ -158,7 +162,7 @@ impl IntoCompact for LayoutField {
#[serde(bound = "F::TypeId: Serialize")] #[serde(bound = "F::TypeId: Serialize")]
pub struct LayoutRange<F: Form = MetaForm> { pub struct LayoutRange<F: Form = MetaForm> {
/// The single key for cells or the starting key address for chunks. /// The single key for cells or the starting key address for chunks.
#[serde(rename = "range.offset")] #[serde(rename = "range.offset", serialize_with = "serialize_key")]
offset: LayoutKey, offset: LayoutKey,
/// The amount of associated key addresses starting from the offset key. /// The amount of associated key addresses starting from the offset key.
#[serde(rename = "range.len")] #[serde(rename = "range.len")]
...@@ -205,3 +209,52 @@ impl LayoutRange { ...@@ -205,3 +209,52 @@ impl LayoutRange {
} }
} }
} }
fn serialize_key<S>(key: &LayoutKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = key.0;
let mut hex = String::with_capacity(bytes.len() * 2 + 2);
write!(hex, "0x").expect("failed writing to string");
for byte in &bytes {
write!(hex, "{:02x}", byte).expect("failed writing to string");
}
serializer.serialize_str(&hex)
}
#[cfg(test)]
mod tests {
use super::*;
use type_metadata::{
form::{
Form,
MetaForm,
},
IntoCompact,
Registry,
};
#[test]
fn key_must_serialize_to_hex() {
// given
let type_id = <MetaForm as Form>::TypeId::new::<u32>();
let offset = LayoutKey([1; 32]);
let cs: LayoutRange<MetaForm> = LayoutRange {
offset,
len: 1337,
elem_ty: type_id,
};
let mut registry = Registry::new();
// when
let json = serde_json::to_string(&cs.into_compact(&mut registry)).unwrap();
// then
assert_eq!(
json,
"{\"range.offset\":\"0x0101010101010101010101010101010101010101010101010101010101010101\",\"range.len\":1337,\"range.elem_type\":1}"
);
}
}
...@@ -15,7 +15,10 @@ ...@@ -15,7 +15,10 @@
// along with ink!. If not, see <http://www.gnu.org/licenses/>. // along with ink!. If not, see <http://www.gnu.org/licenses/>.
use core::marker::PhantomData; use core::marker::PhantomData;
use serde::Serialize; use serde::{
Serialize,
Serializer,
};
use type_metadata::{ use type_metadata::{
form::{ form::{
CompactForm, CompactForm,
...@@ -190,7 +193,8 @@ pub struct ConstructorSpec<F: Form = MetaForm> { ...@@ -190,7 +193,8 @@ pub struct ConstructorSpec<F: Form = MetaForm> {
/// The name of the message. /// The name of the message.
name: F::String, name: F::String,
/// The selector hash of the message. /// The selector hash of the message.
selector: u32, #[serde(serialize_with = "serialize_selector")]
selector: [u8; 4],
/// The parameters of the deploy handler. /// The parameters of the deploy handler.
args: Vec<MessageParamSpec<F>>, args: Vec<MessageParamSpec<F>>,
/// The deploy handler documentation. /// The deploy handler documentation.
...@@ -234,7 +238,7 @@ impl ConstructorSpec { ...@@ -234,7 +238,7 @@ impl ConstructorSpec {
ConstructorSpecBuilder { ConstructorSpecBuilder {
spec: Self { spec: Self {
name, name,
selector: 0, selector: [0u8; 4],
args: Vec::new(), args: Vec::new(),
docs: Vec::new(), docs: Vec::new(),
}, },
...@@ -245,7 +249,7 @@ impl ConstructorSpec { ...@@ -245,7 +249,7 @@ impl ConstructorSpec {
impl ConstructorSpecBuilder<Missing<state::Selector>> { impl ConstructorSpecBuilder<Missing<state::Selector>> {
/// Sets the function selector of the message. /// Sets the function selector of the message.
pub fn selector(self, selector: u32) -> ConstructorSpecBuilder<state::Selector> { pub fn selector(self, selector: [u8; 4]) -> ConstructorSpecBuilder<state::Selector> {
ConstructorSpecBuilder { ConstructorSpecBuilder {
spec: ConstructorSpec { spec: ConstructorSpec {
selector, selector,
...@@ -294,7 +298,8 @@ pub struct MessageSpec<F: Form = MetaForm> { ...@@ -294,7 +298,8 @@ pub struct MessageSpec<F: Form = MetaForm> {
/// The name of the message. /// The name of the message.
name: F::String, name: F::String,
/// The selector hash of the message. /// The selector hash of the message.
selector: u32, #[serde(serialize_with = "serialize_selector")]
selector: [u8; 4],
/// If the message is allowed to mutate the contract state. /// If the message is allowed to mutate the contract state.
mutates: bool, mutates: bool,
/// The parameters of the message. /// The parameters of the message.
...@@ -333,7 +338,7 @@ impl MessageSpec { ...@@ -333,7 +338,7 @@ impl MessageSpec {
MessageSpecBuilder { MessageSpecBuilder {
spec: Self { spec: Self {
name, name,
selector: 0, selector: [0u8; 4],
mutates: false, mutates: false,
args: Vec::new(), args: Vec::new(),
return_type: ReturnTypeSpec::new(None), return_type: ReturnTypeSpec::new(None),
...@@ -358,7 +363,10 @@ pub struct MessageSpecBuilder<Selector, Mutates, Returns> { ...@@ -358,7 +363,10 @@ pub struct MessageSpecBuilder<Selector, Mutates, Returns> {
impl<M, R> MessageSpecBuilder<Missing<state::Selector>, M, R> { impl<M, R> MessageSpecBuilder<Missing<state::Selector>, M, R> {
/// Sets the function selector of the message. /// Sets the function selector of the message.
pub fn selector(self, selector: u32) -> MessageSpecBuilder<state::Selector, M, R> { pub fn selector(
self,
selector: [u8; 4],
) -> MessageSpecBuilder<state::Selector, M, R> {
MessageSpecBuilder { MessageSpecBuilder {
spec: MessageSpec { spec: MessageSpec {
selector, selector,
...@@ -795,3 +803,38 @@ impl MessageParamSpecBuilder { ...@@ -795,3 +803,38 @@ impl MessageParamSpecBuilder {
self.spec self.spec
} }
} }
fn serialize_selector<S>(s: &[u8; 4], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex = format!("0x{:02X}{:02X}{:02X}{:02X}", s[0], s[1], s[2], s[3]);
serializer.serialize_str(&hex)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn construct_selector_must_serialize_to_hex() {
// given
let name = <MetaForm as Form>::String::from("foo");
let cs: ConstructorSpec<MetaForm> = ConstructorSpec {
name,
selector: 123456789u32.to_be_bytes(),
args: Vec::new(),
docs: Vec::new(),
};
let mut registry = Registry::new();
// when
let json = serde_json::to_string(&cs.into_compact(&mut registry)).unwrap();
// then
assert_eq!(
json,
"{\"name\":1,\"selector\":\"0x075BCD15\",\"args\":[],\"docs\":[]}"
);
}
}
...@@ -40,10 +40,9 @@ struct CallAbi { ...@@ -40,10 +40,9 @@ struct CallAbi {
impl CallAbi { impl CallAbi {
/// Creates new call ABI data for the given selector. /// Creates new call ABI data for the given selector.
pub fn new(selector: u32) -> Self { pub fn new(selector: [u8; 4]) -> Self {
let bytes = selector.to_le_bytes();
Self { Self {
raw: vec![bytes[0], bytes[1], bytes[2], bytes[3]], raw: vec![selector[0], selector[1], selector[2], selector[3]],
} }
} }
...@@ -177,7 +176,7 @@ where ...@@ -177,7 +176,7 @@ where
E::Balance: Default, E::Balance: Default,
{ {
/// Instantiates an evaluatable (returns data) remote smart contract call. /// Instantiates an evaluatable (returns data) remote smart contract call.
pub fn eval(account_id: E::AccountId, selector: u32) -> Self { pub fn eval(account_id: E::AccountId, selector: [u8; 4]) -> Self {
Self { Self {
account_id, account_id,
gas_limit: 0, gas_limit: 0,
...@@ -194,7 +193,7 @@ where ...@@ -194,7 +193,7 @@ where
E::Balance: Default, E::Balance: Default,
{ {
/// Instantiates a non-evaluatable (returns no data) remote smart contract call. /// Instantiates a non-evaluatable (returns no data) remote smart contract call.
pub fn invoke(account_id: E::AccountId, selector: u32) -> Self { pub fn invoke(account_id: E::AccountId, selector: [u8; 4]) -> Self {
Self { Self {
account_id, account_id,
gas_limit: 0, gas_limit: 0,
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
use crate::{ use crate::{
ast, ast,
gen::selector_to_expr,
hir, hir,
}; };
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
...@@ -121,7 +122,7 @@ fn generate_abi_constructor(contract: &hir::Contract) -> TokenStream2 { ...@@ -121,7 +122,7 @@ fn generate_abi_constructor(contract: &hir::Contract) -> TokenStream2 {
quote! { quote! {
ink_abi::ConstructorSpec::new("on_deploy") ink_abi::ConstructorSpec::new("on_deploy")
.selector(0) .selector([0u8; 4])
.args(vec![ .args(vec![
#(#args ,)* #(#args ,)*
]) ])
...@@ -136,7 +137,7 @@ fn generate_abi_messages<'a>( ...@@ -136,7 +137,7 @@ fn generate_abi_messages<'a>(
contract: &'a hir::Contract, contract: &'a hir::Contract,
) -> impl Iterator<Item = TokenStream2> + 'a { ) -> impl Iterator<Item = TokenStream2> + 'a {
contract.messages.iter().map(|message| { contract.messages.iter().map(|message| {
let selector = message.selector(); let selector = selector_to_expr(message.selector());
let is_mut = message.is_mut(); let is_mut = message.is_mut();
let docs = message.docs().map(trim_doc_string); let docs = message.docs().map(trim_doc_string);
let name = message.sig.ident.to_string(); let name = message.sig.ident.to_string();
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
use crate::{ use crate::{
ast, ast,
gen::selector_to_expr,
hir, hir,
}; };
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
...@@ -236,7 +237,7 @@ fn generate_call_enhancer_messages<'a>( ...@@ -236,7 +237,7 @@ fn generate_call_enhancer_messages<'a>(
.map(|ident| quote! { #ident }); .map(|ident| quote! { #ident });
let output = &message.sig.decl.output; let output = &message.sig.decl.output;
let (_impl_generics, type_generics, where_clause) = message.sig.decl.generics.split_for_impl(); let (_impl_generics, type_generics, where_clause) = message.sig.decl.generics.split_for_impl();
let selector = message.selector(); let selector = selector_to_expr(message.selector());
match output { match output {
syn::ReturnType::Default => quote_spanned! { ident.span() => syn::ReturnType::Default => quote_spanned! { ident.span() =>
#(#attrs)* #(#attrs)*
......
...@@ -21,11 +21,11 @@ ...@@ -21,11 +21,11 @@
use crate::{ use crate::{
ast, ast,
gen::selector_to_expr,
hir, hir,
}; };
use proc_macro2::{ use proc_macro2::{
Ident, Ident,
Span,
TokenStream as TokenStream2, TokenStream as TokenStream2,
}; };
use quote::{ use quote::{
...@@ -473,12 +473,7 @@ fn codegen_for_messages(tokens: &mut TokenStream2, contract: &hir::Contract) { ...@@ -473,12 +473,7 @@ fn codegen_for_messages(tokens: &mut TokenStream2, contract: &hir::Contract) {
for attr in &message.attrs { for attr in &message.attrs {
attr.to_tokens(&mut content) attr.to_tokens(&mut content)
} }
let msg_selector = message.selector(); let msg_id = selector_to_expr(message.selector());
let msg_id = syn::LitInt::new(
msg_selector.into(),
syn::IntSuffix::None,
Span::call_site(),
);
msg_id.to_tokens(&mut content); msg_id.to_tokens(&mut content);
<Token![=>]>::default().to_tokens(&mut content); <Token![=>]>::default().to_tokens(&mut content);
use heck::CamelCase as _; use heck::CamelCase as _;
......
...@@ -23,6 +23,10 @@ mod test; ...@@ -23,6 +23,10 @@ mod test;
use crate::hir; use crate::hir;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::quote; use quote::quote;
use syn::{
parse_str,
Expr,
};
/// Generates code for the given contract. /// Generates code for the given contract.
/// ///
...@@ -39,3 +43,13 @@ pub fn generate_code(contract: &hir::Contract) -> TokenStream2 { ...@@ -39,3 +43,13 @@ pub fn generate_code(contract: &hir::Contract) -> TokenStream2 {
as_dependency::generate_code(&mut tokens, contract); as_dependency::generate_code(&mut tokens, contract);
tokens tokens
} }
/// The function is required because syn doesn't provide a
/// `ToTokens` implementation for `[u8; 4]`.
fn selector_to_expr(selector: [u8; 4]) -> Expr {
let selector = format!(
"[{}, {}, {}, {}]",
selector[0], selector[1], selector[2], selector[3]
);
parse_str::<syn::Expr>(&selector).expect("failed to parse selector")
}
...@@ -507,14 +507,14 @@ impl Message { ...@@ -507,14 +507,14 @@ impl Message {
} }
/// Returns the message selector for this message. /// Returns the message selector for this message.
pub fn selector(&self) -> u32 { pub fn selector(&self) -> [u8; 4] {
raw_message_selector(self.sig.ident.to_string().as_str()) raw_message_selector(self.sig.ident.to_string().as_str())
} }
} }
fn raw_message_selector(name: &str) -> u32 { fn raw_message_selector(name: &str) -> [u8; 4] {
let keccak = ink_utils::hash::keccak256(name.as_bytes()); let keccak = ink_utils::hash::keccak256(name.as_bytes());
u32::from_le_bytes([keccak[0], keccak[1], keccak[2], keccak[3]]) [keccak[3], keccak[2], keccak[1], keccak[0]]
} }
impl From<&ast::ItemImplMethod> for Message { impl From<&ast::ItemImplMethod> for Message {
...@@ -570,8 +570,8 @@ mod tests { ...@@ -570,8 +570,8 @@ mod tests {
#[test] #[test]
fn message_selectors() { fn message_selectors() {
assert_eq!(raw_message_selector("inc"), 257544423); assert_eq!(raw_message_selector("inc"), [15, 89, 208, 231]);
assert_eq!(raw_message_selector("get"), 4266279973); assert_eq!(raw_message_selector("get"), [254, 74, 68, 37]);
assert_eq!(raw_message_selector("compare"), 363906316); assert_eq!(raw_message_selector("compare"), [21, 176, 197, 12]);
} }
} }
...@@ -478,9 +478,11 @@ impl TryFrom<&hir::Message> for MessageDescription { ...@@ -478,9 +478,11 @@ impl TryFrom<&hir::Message> for MessageDescription {
type Error = syn::Error; type Error = syn::Error;
fn try_from(message: &hir::Message) -> Result<Self> { fn try_from(message: &hir::Message) -> Result<Self> {
let s = message.selector();
let selector = u32::from_le_bytes([s[3], s[2], s[1], s[0]]).into();
Ok(Self { Ok(Self {
name: message.sig.ident.to_string(), name: message.sig.ident.to_string(),
selector: message.selector().into(), selector,
mutates: message.is_mut(), mutates: message.is_mut(),
args: { args: {
message message
......
...@@ -115,13 +115,13 @@ fn contract_compiles() { ...@@ -115,13 +115,13 @@ fn contract_compiles() {
/// # Note /// # Note
/// ///
/// Also emits an event. /// Also emits an event.
257544423 => Inc(); [15, 89, 208, 231] => Inc();
/// Decrements the internal counter. /// Decrements the internal counter.
/// ///
/// # Note /// # Note
/// ///
/// Also emits an event. /// Also emits an event.
1772705147 => Dec(); [105, 169, 85, 123] => Dec();
} }
} }
...@@ -310,14 +310,14 @@ fn contract_compiles() { ...@@ -310,14 +310,14 @@ fn contract_compiles() {
ink_abi::ContractSpec::new("CallCounter") ink_abi::ContractSpec::new("CallCounter")
.constructors(vec![ .constructors(vec![
ink_abi::ConstructorSpec::new("on_deploy") ink_abi::ConstructorSpec::new("on_deploy")
.selector(0) .selector([0u8; 4])
.args(vec![]) .args(vec![])
.docs(vec![]) .docs(vec![])
.done() .done()
]) ])
.messages(vec![ .messages(vec![
ink_abi::MessageSpec::new("inc") ink_abi::MessageSpec::new("inc")
.selector(257544423u32) .selector([15, 89, 208, 231])
.mutates(true) .mutates(true)
.args(vec![]) .args(vec![])
.docs(vec![ .docs(vec![
...@@ -332,7 +332,7 @@ fn contract_compiles() { ...@@ -332,7 +332,7 @@ fn contract_compiles() {
) )
.done(), .done(),
ink_abi::MessageSpec::new("dec") ink_abi::MessageSpec::new("dec")
.selector(1772705147u32) .selector([105, 169, 85, 123])
.mutates(true) .mutates(true)
.args(vec![]) .args(vec![])
.docs(vec![ .docs(vec![
...@@ -496,7 +496,7 @@ fn contract_compiles() { ...@@ -496,7 +496,7 @@ fn contract_compiles() {
/// Also emits an event. /// Also emits an event.
pub fn inc(self,) -> ink_core::env::CallBuilder<Env, ()> { pub fn inc(self,) -> ink_core::env::CallBuilder<Env, ()> {
ink_core::env::CallBuilder::<Env, ()>::invoke( ink_core::env::CallBuilder::<Env, ()>::invoke(
self.contract.account_id.clone(), 257544423u32) self.contract.account_id.clone(), [15, 89, 208, 231])
} }
/// Decrements the internal counter. /// Decrements the internal counter.
...@@ -506,7 +506,7 @@ fn contract_compiles() { ...@@ -506,7 +506,7 @@ fn contract_compiles() {
/// Also emits an event. /// Also emits an event.
pub fn dec(self,) -> ink_core::env::CallBuilder<Env, ()> { pub fn dec(self,) -> ink_core::env::CallBuilder<Env, ()> {
ink_core::env::CallBuilder::<Env, ()>::invoke( ink_core::env::CallBuilder::<Env, ()>::invoke(
self.contract.account_id.clone(), 1772705147u32) self.contract.account_id.clone(), [105, 169, 85, 123])
} }
} }
} }
......
...@@ -90,9 +90,9 @@ fn contract_compiles() { ...@@ -90,9 +90,9 @@ fn contract_compiles() {
ink_model::messages! { ink_model::messages! {
/// Flips the internal boolean. /// Flips the internal boolean.
970692492 => Flip(); [57, 219, 151, 140] => Flip();
/// Returns the internal boolean. /// Returns the internal boolean.
4266279973 => Get() -> bool; [254, 74, 68, 37] => Get() -> bool;
} }
} }
...@@ -208,7 +208,7 @@ fn contract_compiles() { ...@@ -208,7 +208,7 @@ fn contract_compiles() {
ink_abi::ContractSpec::new("Flipper") ink_abi::ContractSpec::new("Flipper")
.constructors(vec![ .constructors(vec![
ink_abi::ConstructorSpec::new("on_deploy") ink_abi::ConstructorSpec::new("on_deploy")
.selector(0) .selector([0u8; 4])
.args(vec![]) .args(vec![])
.docs(vec![ .docs(vec![
"The internal boolean is initialized with `true`.",