Repository: paritytech/ink
Pull request #1100 (2022-01-10 01:10:02)

Update metadate to support payable constructors
On top of #1065. I figured I'd split it out to make reviews easier.


codecov-commenter: # [Codecov](https://codecov.io/gh/paritytech/ink/pull/1100?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech) Report
> Merging [#1100](https://codecov.io/gh/paritytech/ink/pull/1100?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech) (6275cc3) into [hc-make-constructors-non-payable](https://codecov.io/gh/paritytech/ink/commit/aebeb39685cdf6f1592cb6644f994ac1092898c8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech) (aebeb39) will **increase** coverage by `0.06%`.
> The diff coverage is `87.50%`.

[![Impacted file tree graph](https://codecov.io/gh/paritytech/ink/pull/1100/graphs/tree.svg?width=650&height=150&src=pr&token=tYyVaCZb7o&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech)](https://codecov.io/gh/paritytech/ink/pull/1100?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech)

```diff
@@                         Coverage Diff                          @@
##           hc-make-constructors-non-payable    #1100      +/-   ##
====================================================================
+ Coverage                             78.82%   78.89%   +0.06%     
====================================================================
  Files                                   251      251              
  Lines                                  9384     9390       +6     
====================================================================
+ Hits                                   7397     7408      +11     
+ Misses                                 1987     1982       -5     
```


| [Impacted Files](https://codecov.io/gh/paritytech/ink/pull/1100?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech) | Coverage Δ | |
|---|---|---|
| [crates/metadata/src/lib.rs](https://codecov.io/gh/paritytech/ink/pull/1100/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL21ldGFkYXRhL3NyYy9saWIucnM=) | `0.00% <0.00%> (ø)` | |
| [crates/lang/codegen/src/generator/metadata.rs](https://codecov.io/gh/paritytech/ink/pull/1100/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL2xhbmcvY29kZWdlbi9zcmMvZ2VuZXJhdG9yL21ldGFkYXRhLnJz) | `97.39% <100.00%> (+0.02%)` | :arrow_up: |
| [crates/metadata/src/specs.rs](https://codecov.io/gh/paritytech/ink/pull/1100/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL21ldGFkYXRhL3NyYy9zcGVjcy5ycw==) | `73.95% <100.00%> (+0.62%)` | :arrow_up: |
| [...ates/storage/src/collections/hashmap/fuzz\_tests.rs](https://codecov.io/gh/paritytech/ink/pull/1100/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL3N0b3JhZ2Uvc3JjL2NvbGxlY3Rpb25zL2hhc2htYXAvZnV6el90ZXN0cy5ycw==) | `100.00% <0.00%> (+5.31%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/paritytech/ink/pull/1100?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/paritytech/ink/pull/1100?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech). Last update [aebeb39...6275cc3](https://codecov.io/gh/paritytech/ink/pull/1100?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech).


Patch:
diff --git a/crates/lang/codegen/src/generator/metadata.rs b/crates/lang/codegen/src/generator/metadata.rs
index 55a69478a0..622221cc81 100644
--- a/crates/lang/codegen/src/generator/metadata.rs
+++ b/crates/lang/codegen/src/generator/metadata.rs
@@ -122,6 +122,7 @@ impl Metadata<'_> {
             .iter()
             .filter_map(|attr| attr.extract_docs());
         let selector_bytes = constructor.composed_selector().hex_lits();
+        let is_payable = constructor.is_payable();
         let constructor = constructor.callable();
         let ident = constructor.ident();
         let args = constructor.inputs().map(Self::generate_dispatch_argument);
@@ -133,6 +134,7 @@ impl Metadata<'_> {
                 .args([
                     #( #args ),*
                 ])
+                .payable(#is_payable)
                 .docs([
                     #( #docs ),*
                 ])
diff --git a/crates/metadata/src/lib.rs b/crates/metadata/src/lib.rs
index 6543222001..86bc9e285c 100644
--- a/crates/metadata/src/lib.rs
+++ b/crates/metadata/src/lib.rs
@@ -71,12 +71,14 @@ pub enum MetadataVersioned {
     /// Version 1 of the contract metadata.
     V1(MetadataVersionDeprecated),
     /// Version 2 of the contract metadata.
-    V2(InkProject),
+    V2(MetadataVersionDeprecated),
+    /// Version 3 of the contract metadata.
+    V3(InkProject),
 }
 
 impl From<InkProject> for MetadataVersioned {
     fn from(ink_project: InkProject) -> Self {
-        MetadataVersioned::V2(ink_project)
+        MetadataVersioned::V3(ink_project)
     }
 }
 
diff --git a/crates/metadata/src/specs.rs b/crates/metadata/src/specs.rs
index c56e034694..437916cd56 100644
--- a/crates/metadata/src/specs.rs
+++ b/crates/metadata/src/specs.rs
@@ -227,6 +227,8 @@ pub struct ConstructorSpec<F: Form = MetaForm> {
     pub label: F::String,
     /// The selector hash of the message.
     pub selector: Selector,
+    /// If the constructor accepts any `value` from the caller.
+    pub payable: bool,
     /// The parameters of the deployment handler.
     pub args: Vec<MessageParamSpec<F>>,
     /// The deployment handler documentation.
@@ -240,6 +242,7 @@ impl IntoPortable for ConstructorSpec {
         ConstructorSpec {
             label: self.label.into_portable(registry),
             selector: self.selector,
+            payable: self.payable,
             args: self
                 .args
                 .into_iter()
@@ -266,6 +269,11 @@ where
         &self.selector
     }
 
+    /// Returns if the constructor is payable by the caller.
+    pub fn payable(&self) -> &bool {
+        &self.payable
+    }
+
     /// Returns the parameters of the deployment handler.
     pub fn args(&self) -> &[MessageParamSpec<F>] {
         &self.args
@@ -285,20 +293,21 @@ where
 /// compile-time instead of at run-time. This is useful to better
 /// debug code-gen macros.
 #[must_use]
-pub struct ConstructorSpecBuilder<Selector> {
+pub struct ConstructorSpecBuilder<Selector, IsPayable> {
     spec: ConstructorSpec,
-    marker: PhantomData<fn() -> Selector>,
+    marker: PhantomData<fn() -> (Selector, IsPayable)>,
 }
 
 impl ConstructorSpec {
     /// Creates a new constructor spec builder.
     pub fn from_label(
         label: &'static str,
-    ) -> ConstructorSpecBuilder<Missing<state::Selector>> {
+    ) -> ConstructorSpecBuilder<Missing<state::Selector>, Missing<state::IsPayable>> {
         ConstructorSpecBuilder {
             spec: Self {
                 label,
                 selector: Selector::default(),
+                payable: Default::default(),
                 args: Vec::new(),
                 docs: Vec::new(),
             },
@@ -307,9 +316,12 @@ impl ConstructorSpec {
     }
 }
 
-impl ConstructorSpecBuilder<Missing<state::Selector>> {
+impl<P> ConstructorSpecBuilder<Missing<state::Selector>, P> {
     /// Sets the function selector of the message.
-    pub fn selector(self, selector: [u8; 4]) -> ConstructorSpecBuilder<state::Selector> {
+    pub fn selector(
+        self,
+        selector: [u8; 4],
+    ) -> ConstructorSpecBuilder<state::Selector, P> {
         ConstructorSpecBuilder {
             spec: ConstructorSpec {
                 selector: selector.into(),
@@ -320,7 +332,23 @@ impl ConstructorSpecBuilder<Missing<state::Selector>> {
     }
 }
 
-impl<S> ConstructorSpecBuilder<S> {
+impl<S> ConstructorSpecBuilder<S, Missing<state::IsPayable>> {
+    /// Sets if the constructor is payable, thus accepting value for the caller.
+    pub fn payable(
+        self,
+        is_payable: bool,
+    ) -> ConstructorSpecBuilder<S, state::IsPayable> {
+        ConstructorSpecBuilder {
+            spec: ConstructorSpec {
+                payable: is_payable,
+                ..self.spec
+            },
+            marker: PhantomData,
+        }
+    }
+}
+
+impl<S, P> ConstructorSpecBuilder<S, P> {
     /// Sets the input arguments of the message specification.
     pub fn args<A>(self, args: A) -> Self
     where
@@ -344,7 +372,7 @@ impl<S> ConstructorSpecBuilder<S> {
     }
 }
 
-impl ConstructorSpecBuilder<state::Selector> {
+impl ConstructorSpecBuilder<state::Selector, state::IsPayable> {
     /// Finishes construction of the constructor.
     pub fn done(self) -> ConstructorSpec {
         self.spec
@@ -368,7 +396,7 @@ pub struct MessageSpec<F: Form = MetaForm> {
     selector: Selector,
     /// If the message is allowed to mutate the contract state.
     mutates: bool,
-    /// If the message is payable by the caller.
+    /// If the message accepts any `value` from the caller.
     payable: bool,
     /// The parameters of the message.
     args: Vec<MessageParamSpec<F>>,
@@ -508,7 +536,7 @@ impl<S, P, R> MessageSpecBuilder<S, Missing<state::Mutates>, P, R> {
 }
 
 impl<S, M, R> MessageSpecBuilder<S, M, Missing<state::IsPayable>, R> {
-    /// Sets if the message is mutable, thus taking `&mut self` or not thus taking `&self`.
+    /// Sets if the message is payable, thus accepting value for the caller.
     pub fn payable(
         self,
         is_payable: bool,


Test patch:
diff --git a/crates/metadata/src/tests.rs b/crates/metadata/src/tests.rs
index d7451680a4..44d5d8492d 100644
--- a/crates/metadata/src/tests.rs
+++ b/crates/metadata/src/tests.rs
@@ -26,6 +26,7 @@ fn spec_constructor_selector_must_serialize_to_hex() {
     let label = "foo";
     let cs = ConstructorSpec::from_label(label)
         .selector(123_456_789u32.to_be_bytes())
+        .payable(true)
         .done();
     let mut registry = Registry::new();
     let portable_spec = cs.into_portable(&mut registry);
@@ -40,6 +41,7 @@ fn spec_constructor_selector_must_serialize_to_hex() {
         json,
         json!({
             "label": "foo",
+            "payable": true,
             "selector": "0x075bcd15",
             "args": [],
             "docs": []
@@ -55,6 +57,7 @@ fn spec_contract_json() {
         .constructors(vec![
             ConstructorSpec::from_label("new")
                 .selector([94u8, 189u8, 136u8, 214u8])
+                .payable(true)
                 .args(vec![MessageParamSpec::new("init_value")
                     .of_type(TypeSpec::with_name_segs::<i32, _>(
                         vec!["i32"].into_iter().map(AsRef::as_ref),
@@ -64,6 +67,7 @@ fn spec_contract_json() {
                 .done(),
             ConstructorSpec::from_label("default")
                 .selector([2u8, 34u8, 255u8, 24u8])
+                .payable(Default::default())
                 .args(Vec::new())
                 .docs(Vec::new())
                 .done(),
@@ -120,12 +124,14 @@ fn spec_contract_json() {
                     ],
                     "docs": [],
                     "label": "new",
+                    "payable": true,
                     "selector": "0x5ebd88d6"
                 },
                 {
                     "args": [],
                     "docs": [],
                     "label": "default",
+                    "payable": false,
                     "selector": "0x0222ff18"
                 }
             ],
@@ -177,6 +183,7 @@ fn trim_docs() {
     let cs = ConstructorSpec::from_label(label)
         .selector(123_456_789u32.to_be_bytes())
         .docs(vec![" foobar      "])
+        .payable(Default::default())
         .done();
     let mut registry = Registry::new();
     let compact_spec = cs.into_portable(&mut registry);
@@ -191,6 +198,7 @@ fn trim_docs() {
         json,
         json!({
             "label": "foo",
+            "payable": false,
             "selector": "0x075bcd15",
             "args": [],
             "docs": ["foobar"]