Skip to content

Commit

Permalink
Clarify the constructor return value semantics. (#1527)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmkozh authored Aug 19, 2024
1 parent 8d1ea4a commit 5ef1b45
Showing 1 changed file with 11 additions and 6 deletions.
17 changes: 11 additions & 6 deletions core/cap-0058.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,23 @@ index 87dd32d..7da2f1d 100644

#### Constructor function

Every Wasm that exports `__constructor` function is considered to have a constructor. `__constructor` function may take an arbitrary number of arbitrary `SCVal`(XDR)/`Val`(Soroban host) arguments (0 arguments are supported as well). `__constructor` must return `Val::VOID` (in the Rust SDK that is equivalent to returning no value). Returning any value that is not `Val::VOID` will result in error and contract won't be instantiated.
Every Wasm that exports `__constructor` function is considered to have a constructor. `__constructor` function may take an arbitrary number of arbitrary `SCVal`(XDR)/`Val`(Soroban host) arguments (0 arguments are supported as well).

The constructor has the following semantics from the Soroban host standpoint:

- For any Wasm with Soroban environment version less than 22, `__constructor` function is treated as any other unused Soroban 'reserved' function (any contract function that has name starting with double `_`), i.e. it can be exported, but can not be invoked. These contracts are considered to not have any constructor, even past protocol 22.
- Keep in mind, that it is not possible to upload Wasm with v22 environment prior to v22 protocol upgrade, thus it is guaranteed that for any given Wasm uploaded on-chain either all of, or none of the instances have constructor support (depending on the env version)
- For any Wasm with Soroban environment version 22 or greater `__constructor` is treated as constructor function:
- When a new contract is created from that Wasm, the environment calls `__constructor` with user-provided arguments immediately after create a contract instance entry in the storage (without returning control to the caller)
- If `__constructor` hasn't finished successfully (i.e. if VM traps, or a function returns an error), the instantiation fails
- When a new contract is created from that Wasm, the environment calls `__constructor` with user-provided arguments immediately after creating a contract instance entry in the storage (without returning control to the caller)
- If `__constructor` hasn't finished successfully (i.e. if VM traps, or a function returns an error), creation function fails and all the changes are rolled back
- If `__constructor` succeeds, but returns a non-error value that is not `Val::Void` (unit-type return value in Rust), then then the creation function is considered to have failed as well
- If a contract with constructor is instantiated by a function that doesn't allow specifying the constructor arguments, then constuctor is called with 0 arguments.
- If a contract without constructor has any constructor arguments passed to it (i.e. >=1 arguments), the instantiation fails
- If a contract without constructor has any constructor arguments passed to it (i.e. >=1 arguments), the creation function fails

Other than the semantics described above, `__constructor` behaves as a normal contract function that can manipulate contract storage, do cross-contract calls etc.

`__constructor` must return `Val::VOID` (in the Rust SDK that is equivalent to returning no value). Returning any value that is not `Val::VOID` will result in error and contract won't be instantiated.

##### 'Default' constructor

The semantics of contracts that don't have a constructor (i.e. those created before protocol 22 and those that simply don't have `__constructor` defined) can be significantly simplified by treating them as having a 'default' constructor that accepts no arguments and performs no operations. This streamlines the user experience as it makes calling the contracts without constructor to behave exactly the same as contracts with a 0-argument constructor. That's why, for example, it is possible to instantiate a constructor-less contract with a function that expects constructor arguments, but only as long as 0 arguments are passed (passing >0 arguments to the 'default' constructor would cause it to fail).
Expand Down Expand Up @@ -216,9 +219,11 @@ Allowing to overload constructors would be pretty tricky as Wasm doesn't support

If 'overload' behavior is necessary, it can be implemented in custom fashion via e.g. using a single `enum` argument with different variants containing different sets of arguments.

### Returning values from constructor is not allowed
### Returning custom values from constructor is not allowed

The constructor return value has no semantics from the perspective of this CAP, so in theory we could allow contracts to return arbitrary values from constructors and just discard them after calling the constructor. However, this might lead to incorrect developer expectations. For example, a developer might return a custom 'error-code' as integer, or a boolean flag and expect that it is respected by the Soroban host. This is an unlikely case, but since we don't want to make any potentially incorrect assumptions about behavior of the user-defined contracts, we explicitly disallow returning any non-error values besides `Val::VOID`.

The constructor return value has no semantics from the perspective of this CAP, so in theory we could allow contracts to return arbitrary values from constructors and just discard them after calling the constructor. However, this might lead to incorrect developer expectations - for example, an improperly typed contract error might get discarded and contract might get instantiated even though the developer intention was to rollback. That's why we explicitly disallow return any values besides `Val::VOID`.
Note, that returning an error-type value (`Val::Error`) has the same consequences as returning any other custom value (i.e. the constructor is considered to have failed and contract creation is aborted), even though semantically these cases are different (the constructor failed vs constructor succeeded, but returned an unsupported value).

### Constructor is not called when Wasm is updated

Expand Down

0 comments on commit 5ef1b45

Please sign in to comment.