State Facets Initialization
Setting States and Selectors
When internal state facets are initialized with the respective uint256 values, you can begin initializing the StateFacetStorage.
To simplify development, you can use an enum to define the states, with each state corresponding to its index position and
represented as a uint256 value.
enum STATES {
NON_EXISTENT, // 0
FRACTIONS_CREATED, // 1
PURCHASE, // 2
RECEIVE, // 3
DONE, // 4
NON_FUNDED, // 5
REJECTED, // 6
} // NON_EXISTENT (default)State
0is the default (nonexistent campaign) state, State1always follows acreateFractions()call, and all subsequent states are configurable based on platform requirements.
After defining the states in the enum, you have to create a variable (_initStateArg) that would contain encoded
data which will be passed to the function when initializing the StateFacetStorage.
Besides states, it includes before and after hooks - facet functions that get executed when the changeState() function is called.
For instance, when changeState(campaignId, from = X, to = Y) is called:
- First, all
afterHooksassociated with stateX(the departing state) are executed, if any. - Then, all
beforeHooksassociated with stateY(the arriving state) are executed, if any.
You must use selectors when filling out the before and after hook arrays. Below you can find a code example that highlights
the creation of encoded data for the initialization of the StateFacetStorage. Consider this example solely for demonstration
purposes, as it should be properly refined for your specific use case:
const _initStateArg = hre.ethers.utils.defaultAbiCoder.encode(
["uint256", "bytes4[][]", "bytes4[][]", "uint256[][]"],
[
hre.ethers.BigNumber.from("7"), // The platform has 7 distinct states (0,1,2,3,4,5,6)
[["0x8f3c1a7b"], ["0x3b5afc80", "0xa11498f2"], [], [], [], [], []], // Selectors for before hooks in any state
[["0xd2e47c19"], ["0xc9427b3d"], [], [], [], [], []], // Selectors for after hooks in any state
[
[STATES.FRACTIONS_CREATED], // 0 --> {1}
[STATES.PURCHASE, STATES.REJECTED], // 1 --> {2, 6}
[STATES.RECEIVE, STATES.NON_FUNDED], // 2 --> {3, 5}
[STATES.DONE], // 3 --> {4}
[], // 4 --> null
[], // 5 --> null
[], // 6 --> null
],
]
);
await stateFacet.initStateFacet(_initStateArg);This 2D array represents a state transition matrix, where each row corresponds to a specific state, and the entries in
that row indicate the states it can transition to. In this matrix X, the array at index 1 is
[STATES.PURCHASE, STATES.REJECTED], indicating that from state 1 (FRACTIONS_CREATED), transitions to state
2 (PURCHASE) and state 6 (REJECTED) are allowed.
X[1][1] implies a valid transition from state 1 to state 6. Indices 4, 5, and 6 (corresponding to states
DONE, NON_FUNDED, and REJECTED, respectively) contain empty arrays. This indicates that once a campaign reaches
any of these terminal states, no further transitions are allowed—it remains in that state permanently.
The state and selector arrays can include multiple state options and function selectors, which are executed in sequence.
Each index in the state array aligns with the corresponding before and after hook arrays at the same position. For example:
["0x8f3c1a7b"]
["0xd2e47c19"]
[STATES.FRACTIONS_CREATED]To learn how to obtain function selectors for each required facet and initialize them, refer to the RWA Fraction Market guide.
The changeState() function in StateFacetStorage reveals an important detail: the after hooks are
executed before the before hooks, which may seem counterintuitive at first glance:
function changeState(Layout storage l, uint256 campaignId, uint256 fromState, uint256 toState) internal {
if (l.stateOfId[campaignId] != fromState) revert FromStateNotCurrent(fromState, l.stateOfId[campaignId]);
if (toState >= l.totalStatesSupported) revert StateExceedsSupportedLimit(toState, l.totalStatesSupported);
StateHooks storage stateHooksInfoFrom = l.stateHooksInfo[fromState];
StateHooks storage stateHooksInfoTo = l.stateHooksInfo[toState];
if (!stateHooksInfoFrom.isStateTransitionSupported[toState]) revert StateTransitionNotSupported(fromState, toState);
execute(stateHooksInfoFrom.afterSelectors, campaignId);
execute(stateHooksInfoTo.beforeSelectors, campaignId);
l.stateOfId[campaignId] = toState;
}The reason this happens is because when a campaign transitions from state x to state y, it first goes through
the after hooks, marking the end of the current state x, and then, upon entering state y, it calls the
before hooks of the new state:

Updated 2 months ago
