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

default will be used if call a contract without explicit init #169

Closed
wants to merge 16 commits into from

Conversation

ailisp
Copy link
Member

@ailisp ailisp commented Aug 5, 2022

At first time calling a contract, if init was not called, the default() will be used to initialize the contract

@ailisp ailisp requested a review from volovyks as a code owner August 5, 2022 09:23
@ailisp ailisp marked this pull request as draft August 5, 2022 09:24
@ailisp
Copy link
Member Author

ailisp commented Aug 5, 2022

@volovyk-s the test is passing locally, it seems ci doesn't pickup the change in near-contract.ts
I made an mistake. Low level contract has a manual init, counter test is reused for low level contract. counter-ts test works when skip init, but lowlevel counter would not. CI works well

@ailisp ailisp marked this pull request as ready for review August 5, 2022 09:50
@ailisp ailisp requested a review from idea404 August 5, 2022 10:01
for (const item in c) {
if (c[item].constructor?.deserialize !== undefined) {
this[item] = c[item].constructor.deserialize(this[item]);
}
}
}
else {
throw new Error("Contract state is empty");
Object.assign(this, c);
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe we could make this optional. If default is defined, then grab it and use it, if not throw error. Since not every contract might want to have a default.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe there is such an option in RS SDK. This design does not allow developers to have an uninitialized contract.

Copy link
Member Author

Choose a reason for hiding this comment

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

I see, you guys are right. In RS SDK, developer can right panic in impl Default to have a "must explicit initialize contract", but here it's not possible. If we throw error in default(), then because deserialization also calls default(), it will not work

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like Serhii's @Nearbindgen defaultState proposal is a great solution to the problem, developer can configure the contract to allow or prohibit default there. We're shipping freeze for next week, so let's take more time to get consensus on the solution!

Copy link
Collaborator

@gagdiez gagdiez left a comment

Choose a reason for hiding this comment

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

I am using "request changes" just to the questions get visibility.

@volovyks
Copy link
Collaborator

volovyks commented Aug 5, 2022

I believe we need to provide an end-to-end solution. Extend this PR with modified @NearBindgen that can generate default() method and call it with default parameters. As we discussed:

@NearBindgen({defaultState: { param1: 'defaultVal' } })
class Bla extends NearContract() {}

@gagdiez
Copy link
Collaborator

gagdiez commented Aug 5, 2022

I believe we need to provide an end-to-end solution. Extend this PR with modified @NearBindgen that can generate default() method and call it with default parameters. As we discussed:

@NearBindgen({defaultState: { param1: 'defaultVal' } })
class Bla extends NearContract() {}

wouldn't it get impossible to read with more than a couple params? I think implementing a default method is something clean and simple to understand for everyone

@idea404
Copy link
Contributor

idea404 commented Aug 5, 2022

I prefer Serhii's proposal. You can always auto format an object. Or perhaps even import this object from another module or define it above in the module and refer to it.

@ailisp ailisp marked this pull request as draft August 5, 2022 15:50
@gagdiez
Copy link
Collaborator

gagdiez commented Aug 7, 2022

we noticed with Ben and Serhii that default() can raise an error when the constructor is made programmatically private (i.e. asserting that predecessor is current_account).

Making the constructor private is common practice.

Does this mean that we cannot derive the state types from the constructor? Or maybe we just need need to duplicate it somewhere else?

@ailisp
Copy link
Member Author

ailisp commented Aug 9, 2022

(i.e. asserting that predecessor is current_account).

One workaround i can think of is to initialize by assert predecessor == current_account and set some marker state (i.e. initialized = true) in init() method, instead of constructor, and require the marker state in every method.

Or maybe we just need need to duplicate it somewhere else?

Not sure if i get your idea perfectly. Do you mean something like:

class Bla extends NearContract() {
  constructor() {
// can have programmatically private behavior
  }
  default() {
// instead of call constructor, manually construct a Bla (duplicate part of constructor)
  }
}

Or:

@NearBindgen({defaultState: { param1: 'defaultVal' } })
class Bla extends NearContract() {
  constructor() {
// can have programmatically private behavior
  }
// default semantic is same as above one, but it's generated from @NearBindgen({defaultState... 
}

I think either of them will solve this problem

@gagdiez
Copy link
Collaborator

gagdiez commented Aug 22, 2022

I think the direction we are going right now is to force users to always give us an instance of the Contract. Right?

This is because we cannot serialise the typed structure of our contract, therefore we do not know how to deserialise the state when needed, therefore we need the user to give us an instantiated object for us to fill.


I see that @volovyks is implementing an @init decorator. Maybe for now we can use the constructor as default, and the init decorator as initialisation function to reproduce the Rust behaviour:

  • If the state is [] (not initialised) we use the constructor (new Class()) as state
  • If the object gets written, then the state is now initialized
  • If @init is called, then the state is now initialized
  • @init cannot be called once the state is initialized

@volovyks
Copy link
Collaborator

@gagdiez @init decorator is an option, but now I'm trying to make constructor the only way to initialize the contract. I will communicate the design with DevRel once I have a proof that it works

@ailisp
Copy link
Member Author

ailisp commented Sep 2, 2022

closed as a superset and better solution is achieved in #200

@ailisp ailisp closed this Sep 2, 2022
@ailisp ailisp deleted the default-init branch September 2, 2022 09:03
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.

4 participants