State record in init function

What happens if I call a function / entry point from init that uses the implicit state (before init returns with the initial state obviously)? Are the record values initialized to default values? What happens if that function does put(state{}) with an update?

Is there another way to make sure that some functionality, that is programmed based on the use of state, can be executed automatically when the contract is created although it cannot be called until after init returned?

Thanks!

The compiler will make sure that you do not touch the state in any code reachable from the init-function (neither putting, nor reading).

So, no you can’t run code as part of init that does something with the state :slight_smile:

Thank you Hans.

Since state is a ‘contract-global’ concern, that limits init() quite strongly. The cleanliness is obvious but it means that there is a strong limitation on what code can auto-execute when a contract is deployed. In that light I think an additional constructor function would be in order for completeness. Without, like in my case, one will have to work around the lack of it and functionally have to deal with and protect half-initialized contracts that still need one more call of some entry point to be really fully ready to go. So there will be a pattern of a flag called ‘initialized’ and an extra entrypoint call the norm right after deployment.

… to avoid having to unpack functions that one uses in normal operation into a version for the init() function and a separate version for other functions to call. This is what we did now: ‘manually inlining’ stuff into init(), that is fully duplicated by a function, which init() could call.

Sorry for the slow response, it has been a long day at the Aeternity universe…

Do you have a small but illustrating example of this, I might be missing something in the explanation and it is always easier with a concrete example to discuss!?

Sure, thanks:

The example comes from this concrete piece of source code: https://gitlab.com/lexon-foundation/coop/blob/master/src/contracts/coop_charta.aes

See the readme, on what it is for: https://gitlab.com/lexon-foundation/coop/blob/master/README.md

But as an example I think it is generic. The init function duplicates some of the functionality of the entry point create_membership.

stateful entrypoint create_membership(_member : address, _owned_shares : int) =
    let membership = { owned_shares = _owned_shares }
    put(state{ membership_map[_member] = membership })
    put(state{ total_shares = state.total_shares + state.member_s_initial_number_of_shares })

the part of the init that duplicates it, in an enforced copy-paste style of programming is:

let founder_s_initial_number_of_shares = _founder_s_initial_number_of_shares
     ...
    { ...
      total_shares = _founder_s_initial_number_of_shares,
	  ...
      membership_map = { [founder] = {owned_shares=founder_s_initial_number_of_shares} }

This is very baked in and the preferred way that is impossible because if how init and the state record work would be as follows.

Below is a cleaned-up example, so that this thread will not be derailed by inefficiencies in our code that are intended but irrelevant for this discussion.

  • does not work:
public stateful entrypoint init(_founder_s_initial_number_of_shares : int, _application_fee : int, _member_s_initial_number_of_shares : int) =
    let founder = Call.caller 
    let director = founder
    put (state{ founder = founder,
      director = director,
      application_fee = _application_fee,
	  member_s_initial_number_of_shares = _member_s_initial_number_of_shares,
      total_shares = 0,
      founder_s_initial_number_of_shares = _founder_s_initial_number_of_shares,
      application_map = {},
      membership_map = {} )
    create_membership(founder, founder_s_initial_number_of_shares) }

My main point, again, is that being unable to use certain functions that are stateful in init while not having another constructor that is always executed at contract creation is unduly limiting and will lead to either duplication of code, i.e. avoidable errors, and/or contracts that are deployed and not fully initialized. Which latter will need the added overhead to check that before anything else can be called they reach the fully initialized state, which will clutter the code and add error potential. The validity of this concern can be seen from the optimization where you do not actually store the init code on-chain but throw it away. I am talking about MORE code that should be thrown away, too, because it is only used once in the very beginning. Just it can’t because it had to call stateful functions.

The example above gets around all that by duplicating functionality of a stateful function. What I lose is the clarity of setting 0 rap {} the initial state. Instead I put stuff right in. This is correct and optimized but less clear than I would prefer. Which also points to this being a design flaw.

I understand that implementation concerns might make my hole point mute. I can imagine how and why the limitation exists. But with these things the intuition would be if I run up against it this way, maybe it is not serving the app programmers well enough staying to close to implementation purity.

Instead of duplicating the functionality of the stateful function you can break it into a pure function and a stateful wrapper:

  function _create_membership(s : state, _member : address, _owned_shares : int) =
      let membership = { owned_shares = _owned_shares }
      s{ membership_map[_member] = membership,
         total_shares = s.total_shares + s.member_s_initial_number_of_shares }

  stateful entrypoint create_membership(_member : address, _owned_shares : int) =
      put(_create_membership(state, _member, _owned_shares))

  stateful entrypoint init(_founder_s_initial_number_of_shares : int, _application_fee : int, _member_s_initial_number_of_shares : int) =
      let founder = Call.caller
      let director = founder
      let s = { founder = founder,
                director = director,
                application_fee = _application_fee,
                member_s_initial_number_of_shares = _member_s_initial_number_of_shares,
                total_shares = 0,
                founder_s_initial_number_of_shares = _founder_s_initial_number_of_shares,
                application_map = {},
                membership_map = {} }
      _create_membership(s, founder, _founder_s_initial_number_of_shares)
1 Like

I like that a lot, thanks so much, Ulf!

It answers most of my question. But since the example is the desired output from a code creator - i.e. we want this file to be created by a transpiler - it is obvious that the transpiler will have to heavily special-case this.

The rule will be, whenever a function is called from the constructor, break out its core, make the implicit state variable explicit and create an additional wrapper with the actually desired name that calls that broken out core. Of course, what we are bumping up against is the understandable sugar of the ‘implicit’ state. That sometimes is not implicit because it’s not there.

The special-casing, the typo-prone way of duplicating the function header, the separation of the real entry point and its code, and the way you require a programmer in such cases to add a function hull, still point towards that the requirement to avoid the implicit state variable might not be right for a constructor.

I appreciate it’s the way in C++ too and I understand why it might be the cleaner implementation the way it is, underneath. But the cost is less elegant, more verbose and error-prone code on the app level - for any realistically complex smart contract I guess - hinting at optimization one level too deep in my view. Maybe it’s incurable because the slippery slope starts with making state implicit.