Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dan): basic template macro #4358

Merged
merged 11 commits into from
Jul 29, 2022

Conversation

mrnaveira
Copy link
Contributor

Description

  • New engine package to encapsulate the macro logic. In the future we may consider extracting it to a separate project or repo.
  • The user template code handling is encapsulated into the ast module.
  • Each section of the generated template code is split into different modules:
    • definition: generates a verbatim module of the user code, needed for the template macro to compile properly.
    • abi: generates the ABI section of the contract wasm, needed in the engine to know the contract function signatures.
    • dispatcher: executes a call invocation, by deciding which user-defined code to call and encoding the result.
    • dependencies: helper functions that need to be included into the wasm, as well as the tari_engine import signature.
  • Current limitations:
    • The function arguments are not yet processed and encoded, so at the moment it only supports user-defined functions with no parameters. But return values are handled.
    • Struct fields and self is not handled yet
    • Calls to the tari engine import are not being done

Motivation and Context

Template developers need an ergonomic way of writing contract code. This PR aims to provide a new macro template! to allow easy development of new basic contracts. And for basic I mean no struct fields, no resource access and no composability, only stateless functions. Those features will be added in future PRs.

With this PR, the existing "Hello World" example is rewritten as:

use tari_template_macros::template;

template! {
    struct HelloWorld {}

    impl HelloWorld { 
        pub fn greet() -> String {
            "Hello World!".to_string()
        }
    }
}

How Has This Been Tested?

  • The unit test for the "Hello World" contract example now is written using the new macro. It compiles and runs in our engine successfully
  • New unit tests for some complex sections of the macro

Copy link
Member

@sdbondi sdbondi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A perfect start!

We can look avoiding the template! {... } wrapper in another PR.


use common::{generate_abi, generate_main, TemplateImpl};
use tari_template_abi::{encode_with_len, FunctionDef, Type};
template! {
Copy link
Member

@sdbondi sdbondi Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scoping is why we'll need to put something like

#[tari::contract]
mod hello_world {
   pub struct HelloWorld { etc.}
}

If we want to avoid wrapping in template! {...}, unless there's another way to get the scoping (I can't think of any) - the main reason is that code completion on some editors breaks when wrapped in declarative macros.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine IMO

It also allows some flexibility, because you can also write your contract in my_contract.rs, and then in lib.rs have

#[tari::contract]
mod my_contract;

@CjS77
Copy link
Collaborator

CjS77 commented Jul 28, 2022

Looks great. Why is it all in a tests folder though?

@sdbondi
Copy link
Member

sdbondi commented Jul 29, 2022

Yeah, let's move the macro out of tests and then it should be good to merge

EDIT: on second thoughts, I want to prevent conflicts with #4350 since I already moved out the "common" crate that was in tests in that PR. Let's make things easy and merge this one and move it out later

@aviator-app aviator-app bot merged commit 594ca0e into tari-project:development Jul 29, 2022
stringhandler pushed a commit that referenced this pull request Aug 3, 2022
Description
---
* Function parameters are correctly encoded and passed to the user function
* Component parameters (i.e. `self`) are transparently handled to the user code:
    1. The ABI specifies a `u32` type component id
    2. The state is retrieved from the engine with that component id (not the real call yet, as it's not implemented in the engine)
    3. The state is decoded and passed to the function on the `self` parameter
    4. If the attribute is mutable (`&mut self`) then we call the engine to update the component state
* Unit return types (`()`) are now supported in functions (see the `State.set` function as an example)
* Expanded the `ast.rs` module with convenient functions to detect `self` and constructor functions

Motivation and Context
---
Following the previous work on the template macro (#4358, #4361), this PR aims to solve some of the previous limitations:
* The function arguments must be processed and encoded
* Struct fields and `self` must be handled
* Calls to the tari engine import should be done instead of mocked

With those implemented, the `state` test example is now written as:
```
use tari_template_macros::template;

#[template]
mod state_template {
    pub struct State {
        pub value: u32,
    }

    impl State {
        pub fn new() -> Self {
            Self { value: 0 }
        }

        pub fn set(&mut self, value: u32) {
            self.value = value;
        }

        pub fn get(&self) -> u32 {
            self.value
        }
    }
}
```
Please keep in mind that this is the simplest example that manages the contract state, but it currently supports function logic as complex as the user wants, as long as it is valid Rust code.

Also, for now I didn't find necessary to mark constructor functions in any special way. Right now, a function is automatically considered a constructor (and the component instantiated) if it returns `Self`.

Lastly, as the state managing itself is not yet implemented on the engine, state is not conserved between calls. But this PR encapsulates component related logic in a single module (`component.rs`) so it should be relatively simple to implement in the future.

How Has This Been Tested?
---
The unit test for the `state` example, now rewritten using the template macro, pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants