dusk-network / dev-tools

Monorepo containing all our developer tools
0 stars 0 forks source link

Simplify smart contract development #2

Open HDauven opened 1 month ago

HDauven commented 1 month ago

Summary

This issue comes from the need to simplify smart contract development on Dusk by abstracting away implementation details like the static mut STATE and no_mangle functions.

It follows from the discussion in the issue Investigate foreign trait exposing.

Possible solution design or implementation

We can create a #[contract] macro that generates the necessary boilerplate code automatically. We can take and extend the functionality of the piecrust-macros I did here, and make it do the following:

Example

The following input:

#![no_std]

use piecrust_macros::contract;

#[contract]
pub mod counter {
    pub struct Counter {
        value: i64,
    }

    impl Counter {
        pub fn read_value(&self) -> i64 {
            self.value
        }

        pub fn increment(&mut self) {
            let value = self.value + 1;
            self.value = value;
        }
    }

    pub trait Resettable {
        fn reset(&mut self);
    }

    impl Resettable for Counter {
        fn reset(&mut self) {
            self.value = 0;
        }
    }
}

Should generate the following output:

#![no_std]

use piecrust_uplink as uplink;

pub mod counter {
    pub struct Counter {
        value: i64,
    }

    static mut STATE: Counter = Counter { value: 0 };

    impl Counter {
        pub fn read_value(&self) -> i64 {
            self.value
        }

        pub fn increment(&mut self) {
            let value = self.value + 1;
            self.value = value;
        }
    }

    pub trait Resettable {
        fn reset(&mut self);
    }

    impl Resettable for Counter {
        fn reset(&mut self) {
            self.value = 0;
        }
    }
}

#[no_mangle]
pub unsafe fn read_value(arg_len: u32) -> u32 {
    uplink::wrap_call(arg_len, |_: ()| STATE.read_value())
}

#[no_mangle]
pub unsafe fn increment(arg_len: u32) -> u32 {
    uplink::wrap_call(arg_len, |_: ()| STATE.increment())
}

#[no_mangle]
pub unsafe fn reset(arg_len: u32) -> u32 {
    uplink::wrap_call(arg_len, |_: ()| STATE.reset())
}

By implementing this feature set, developers no longer need to create the no_mangle functions and do not have to define the state explicitly.

Neotamandua commented 1 month ago

How the static mut STATE gets created should also be standardised and defined. Otherwise it will be unclear how certain data structures within the field of the struct would be created and with which values.

The simplest approach here, in my opinion, would be to require the Default trait to be implemented for the public struct. This way the macro can always create the state with the default() method.

(There could also be another way with the init() function)