You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Add automatic state change detection with ContractState wrapper
- Wrap contract state access in ContractState<T, idx> template with .get() (const) and .mut() (marks dirty) accessors
- Move all contract state fields into nested struct StateData for each contract
- Add needsCleanup() const method to containers
- Add using declaration to unhide const operator() overload in QpiContextProcedureCall
- Use .get() instead of .mut() for const proposal method calls
- Update contracts.md: describe StateData, state.get()/state.mut() accessors,
and dirty state digest recomputation at end of tick
- Update contracts_proposals.md: update all code examples to use
state.get().proposals / state.mut().proposals
- Update README.md: mention StateData and accessor pattern
- Update EmptyTemplate.h: add empty StateData with usage comments
Copy file name to clipboardExpand all lines: doc/contracts.md
+9-10Lines changed: 9 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -26,12 +26,13 @@ A contract also never gets access to uninitialized memory (all memory is initial
26
26
27
27
Each contract is implemented in one C++ header file in the directory `src/contracts`.
28
28
29
-
A contract has a state struct, containing all its data as member variables.
29
+
A contract is defined as a struct inheriting from `ContractBase`. All persistent data of the contract must be declared inside a nested `struct StateData`.
30
30
The memory available to the contract is allocated statically, but extending the state will be possible between epochs through special `EXPAND` events.
31
31
32
-
The state struct also includes the procedures and functions of the contract, which have to be defined using special macros such as `PUBLIC_PROCEDURE()`, `PRIVATE_FUNCTION()`, or `BEGIN_EPOCH()`.
33
-
Functions cannot modify the state, but they are useful to query information with the network message `RequestContractFunction`.
34
-
Procedures can modify the state and are either invoked by special transactions (user procedures) or internal core events (system procedures).
32
+
The contract struct also includes the procedures and functions of the contract, which have to be defined using special macros such as `PUBLIC_PROCEDURE()`, `PRIVATE_FUNCTION()`, or `BEGIN_EPOCH()`.
33
+
The state is accessed through a `ContractState` wrapper named `state`: use `state.get()` for read-only access and `state.mut()` for write access. Calling `state.mut()` marks the state as dirty for automatic change detection. The digest of dirty contract states is recomputed at the end of each tick, so that only contracts whose state actually changed incur the cost of rehashing.
34
+
Functions can only read the state (via `state.get()`), making them useful to query information with the network message `RequestContractFunction`.
35
+
Procedures can modify the state (via `state.mut()`) and are either invoked by special transactions (user procedures) or internal core events (system procedures).
35
36
36
37
Contract developers should be aware of the following parts of the Qubic protocol that are not implemented yet in the core:
37
38
- Execution of contract procedures will cost fees that will be paid from its contract fee reserve.
@@ -163,7 +164,7 @@ In order to make the function available for external requests through the `Reque
163
164
`[INPUT_TYPE]` is an integer greater or equal to 1 and less or equal to 65535, which identifies the function to call in the `RequestContractFunction` message (`inputType` member).
164
165
If the `inputSize` member in `RequestContractFunction` does not match `sizeof([NAME]_input)`, the input data is either cut off or padded with zeros.
165
166
166
-
The contract state is passed to the function as a const reference named `state`.
167
+
The contract state is accessed through a `ContractState` wrapper named `state`. In functions, only `state.get()` is available, returning a const reference to the `StateData`. For example, `state.get().myField` reads a field from the state.
167
168
168
169
Use the macro with the postfix `_WITH_LOCALS` if the function needs local variables, because (1) the contract state cannot be modified within contract functions and (2) creating local variables / objects on the regular function call stack is forbidden.
169
170
With these macros, you have to define the struct `[NAME]_locals`.
@@ -201,8 +202,7 @@ In order to make the function available for invocation by transactions, you need
201
202
`REGISTER_USER_PROCEDURE` has its own set of input types, so the same input type number may be used for both `REGISTER_USER_PROCEDURE` and `REGISTER_USER_FUNCTION` (for example there may be one function with input type 1 and one procedure with input type 1).
202
203
If the `inputSize` member in `Transaction` does not match `sizeof([NAME]_input)`, the input data is either cut off or padded with zeros.
203
204
204
-
The contract state is passed to the procedure as a reference named `state`.
205
-
And it can be modified (in contrast to contract functions).
205
+
The contract state is accessed through a `ContractState` wrapper named `state`. In procedures, both `state.get()` (read-only) and `state.mut()` (read-write) are available. Use `state.mut()` when modifying state fields, e.g. `state.mut().myField = newValue`. Calling `state.mut()` marks the state as dirty so that its digest is recomputed at the end of the tick.
206
206
207
207
Use the macro with the postfix `_WITH_LOCALS` if the procedure needs local variables, because creating local variables / objects on the regular function call stack is forbidden.
208
208
With these macros, you have to define the struct `[NAME]_locals`.
@@ -252,8 +252,7 @@ System procedures 1 to 5 have no input and output.
252
252
The input and output of system procedures 6 to 9 are discussed in the section about [management rights transfer](#management-rights-transfer).
253
253
The system procedure 11 and 12 are discussed in the section about [contracts as shareholder of other contracts](contracts_proposals.md#contracts-as-shareholders-of-other-contracts)
254
254
255
-
The contract state is passed to each of the procedures as a reference named `state`.
256
-
And it can be modified (in contrast to contract functions).
255
+
The contract state is accessed through a `ContractState` wrapper named `state`, the same as in user procedures. Use `state.get()` for reading and `state.mut()` for modifying state fields.
257
256
258
257
For each of the macros above, there is a variant with the postfix `_WITH_LOCALS`.
259
258
These can be used, if the procedure needs local variables, because creating local variables / objects on the regular function call stack is forbidden.
Copy file name to clipboardExpand all lines: doc/contracts_proposals.md
+46-31Lines changed: 46 additions & 31 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ There are some general characteristics of the proposal voting:
10
10
- Each proposal has a type and some types of proposals commonly trigger action (such as setting a contract state variable or transferring QUs to another entity) after the end of the epoch if the proposal is accepted by getting enough votes.
11
11
- The proposer entity can have at most one proposal at a time. Setting a new proposal with the same seed will overwrite the previous one.
12
12
- Number of simultaneous proposals per epoch is limited as configured by the contract. The data structures storing the proposal and voting state are stored as a part of the contract state.
13
-
- In this data storage, commonly named `state.proposals`, and the function/procedure interface, each proposal is identified by a proposal index.
13
+
- In this data storage, commonly named `proposals` (a member of `StateData`, accessed via `state.get().proposals` or `state.mut().proposals`), and the function/procedure interface, each proposal is identified by a proposal index.
14
14
- The types of proposals that are allowed are restricted as configured by the contract.
15
15
- The common types of proposals have a predefined set of options that the voters can vote for. Option 0 is always "no change".
16
16
- Each vote, which is connected to each voter entity, can have a value (most commonly an option index) or `NO_VOTE_VALUE` (which means abstaining).
@@ -63,10 +63,11 @@ Features:
63
63
64
64
If you need more than these features, go through the following steps anyway and continue reading the section about understanding the shareholder voting implementation.
65
65
66
-
#### 1. Setup proposal storage
66
+
#### 1. Setup proposal types and storage
67
67
68
-
First, you need to add the proposal storage to your contract state.
69
-
You can easily do this using the QPI macro `DEFINE_SHAREHOLDER_PROPOSAL_STORAGE(numProposalSlots, assetName)`.
68
+
First, you need to add the proposal types and storage to your contract.
69
+
Use the QPI macro `DEFINE_SHAREHOLDER_PROPOSAL_TYPES(numProposalSlots, assetName)` to define the required types,
70
+
and declare the `proposals` field manually inside your `StateData`.
70
71
With the yes/no shareholder proposals supported by this macro, each proposal slot occupies 22144 Bytes of state memory.
71
72
The number of proposal slots limits how many proposals can be open for voting simultaneously.
72
73
The `assetName` that you have to pass as the second argument is the `uint64` representation of your contract's 7-character asset name.
Replace "QUTIL" by your contract's asset name as given in `contractDescriptions` in `src/contact_core/contract_def.h`.
81
82
You will get an integer that we recommend to assign to a `constexpr uint64` with a name following the scheme `QUTIL_CONTRACT_ASSET_NAME`.
82
83
83
-
When you have decided about the number of proposal slots and found out the the asset name, you can define the proposal storage similarly to this example taken from the contract QUTIL:
84
+
When you have decided about the number of proposal slots and found out the the asset name, you can define the proposal types and storage similarly to this example taken from the contract QUTIL:
`DEFINE_SHAREHOLDER_PROPOSAL_STORAGE` defines a state object `state.proposals` and the types `ProposalDataT`, `ProposersAndVotersT`, and `ProposalVotingT`.
102
+
`DEFINE_SHAREHOLDER_PROPOSAL_TYPES` defines the types `ProposalDataT`, `ProposersAndVotersT`, and `ProposalVotingT`.
103
+
The `proposals` field of type `ProposalVotingT` must be declared manually inside `StateData`.
97
104
Make sure to have no name clashes with these.
98
105
Using other names isn't possible if you want to benefit from the QPI macros for simplifying the implementation.
@@ -231,7 +238,7 @@ The following elements are required to support shareholder proposals and voting:
231
238
- `GetShareholderVotes`: Function for getting the votes of a shareholder. Usually shouldn't require a custom implementation.
232
239
- `GetShareholderVotingResults`: Function for getting the vote results summary. Usually doesn't require a custom implementation.
233
240
- `SET_SHAREHOLDER_PROPOSAL` and `SET_SHAREHOLDER_VOTES`: These are notification procedures required to handle voting of other contracts that are shareholder of your contract. They usually just invoke `SetShareholderProposal` or `SetShareholderVote`, respectively.
234
-
- Proposal data storage and types: The default implementations expect the object `state.proposals` and the types `ProposalDataT`, `ProposersAndVotersT`, and `ProposalVotingT`, which can be defined via `DEFINE_SHAREHOLDER_PROPOSAL_STORAGE` in some cases.
241
+
- Proposal types and storage: The default implementations expect the types `ProposalDataT`, `ProposersAndVotersT`, and `ProposalVotingT` (defined via `DEFINE_SHAREHOLDER_PROPOSAL_TYPES`) and the object `proposals` of type `ProposalVotingT` in `StateData` (accessed via `state.get().proposals` or `state.mut().proposals`).
235
242
236
243
QPI provides default implementations through several macros, as used in the [Introduction to Shareholder Proposals](#introduction-to-shareholder-proposals).
237
244
The following tables gives an overview about when the macros can be used.
@@ -244,7 +251,7 @@ Finally, multi-variable proposals change more than one variable if accepted. The
244
251
245
252
Default implementation can be used? | 1-var yes/no | 1-var N option | 1-var scalar | multi-var
The `proposals` field must be declared manually inside `StateData`:
285
+
286
+
```C++
287
+
structStateData
288
+
{
276
289
ProposalVotingT proposals;
290
+
// ...
291
+
};
277
292
```
278
293
279
294
With `ProposalDataT` your have the following options:
@@ -285,7 +300,7 @@ With `ProposalDataT` your have the following options:
285
300
The number of proposal slots linearly scales the storage and digest compute requirements. So we recommend to use a quite low number here, similar to the number of variables that can be set in your state.
286
301
287
302
`ProposalVotingT` combines `ProposersAndVotersT` and `ProposalDataT` into the class used for storing all proposal and voting data.
288
-
It is instantiated as `state.proposals`.
303
+
It is instantiated as `proposals` inside `StateData` (accessed via `state.get().proposals` for reads and `state.mut().proposals` for writes).
289
304
290
305
In order to support MultiVariables proposals that allow to change multiple variables in a single proposal, the variable values need to be stored separately, for example in an array of `numProposalSlots` structs, one for each potential proposal.
291
306
See the contract TestExampleA to see how to support multi-variable proposals.
0 commit comments