FlowCommon
Inherits: ERC721Holder, ERC1155Holder, Multicall, ReentrancyGuard, IInterpreterCallerV2, DeployerDiscoverableMetaV2
Common functionality for flows. Largely handles the evaluable
registration and dispatch. Also implementes the necessary interfaces for
a smart contract to receive ERC721 and ERC1155 tokens.
Flow contracts are expected to be deployed via. a proxy/factory as clones
of an implementation contract. This makes flows cheap to deploy and every
flow contract can be initialized with a different set of flows. This gives
strong guarantees that the flow contract is only capable of evaluating
registered flows, and that individual flow contracts cannot collide state
with each other, given a correctly implemented interpreter store. Combining
proxies with rainlang gives us a very powerful and flexible system for
composing flows without significant gas overhead. Typically a flow contract
deployment will cost well under 1M gas, which is very cheap for bespoke
logic, without significant runtime overheads. This allows for new UX patterns
where users can cheaply create many different tools such as NFT mints,
auctions, escrows, etc. and aim to horizontally scale rather than design
monolithic protocols.
This does NOT implement the preview and flow logic directly because each
flow implementation has different requirements for the mint and burn logic
of the flow tokens. In the future, this may be refactored so that a single
flow contract can handle all flows.
FlowCommon
is Multicall
so it is NOT compatible with receiving ETH. This
is because Multicall
uses delegatecall
in a loop which reuses msg.value
for each loop iteration, effectively "double spending" the ETH it receives.
This is a known issue with Multicall
so in the future, we may refactor
FlowCommon
to not use Multicall
and instead implement flow batching
directly in the flow contracts.
State Variables
registeredFlows
This mapping tracks all flows that are registered at initialization.
This is used to ensure that only registered flows are evaluated.
Inheriting contracts MUST check this mapping before evaluating a flow,
else anons can deploy their own evaluable and drain the contract.
isRegistered
will be set to FLOW_IS_REGISTERED
for each registered
flow.
mapping(bytes32 evaluableHash => uint256 isRegistered) internal registeredFlows;
Functions
constructor
Forwards config to DeployerDiscoverableMetaV2
and disables
initializers. The initializers are disabled because inheriting contracts
are expected to implement some kind of initialization logic that is
compatible with cloning via. proxy/factory. Disabling initializers
in the implementation contract forces that the only way to initialize
the contract is via. a proxy, which should also strongly encourage
patterns that atomically clone and initialize via. some factory.
constructor(bytes32 metaHash, DeployerDiscoverableMetaV2ConstructionConfig memory config)
DeployerDiscoverableMetaV2(metaHash, config);
Parameters
Name | Type | Description |
---|---|---|
metaHash | bytes32 | As per DeployerDiscoverableMetaV2 . |
config | DeployerDiscoverableMetaV2ConstructionConfig | As per DeployerDiscoverableMetaV2 . |
flowCommonInit
Common initialization logic for inheriting contracts. This MUST be called by inheriting contracts in their initialization logic (and only).
function flowCommonInit(EvaluableConfigV2[] memory evaluableConfigs, uint256 flowMinOutputs)
internal
onlyInitializing;
Parameters
Name | Type | Description |
---|---|---|
evaluableConfigs | EvaluableConfigV2[] | The evaluable configs to register at initialization. Each of these represents a flow that defines valid token movements at runtime for the inheriting contract. |
flowMinOutputs | uint256 | The minimum number of outputs for each flow. All flows share the same minimum number of outputs for simplicity. |
_flowStack
Standard evaluation logic for flows. This includes critical guards to ensure that only registered flows are evaluated. This is the only function that inheriting contracts should call to evaluate flows. The start and end pointers to the stack are returned so that inheriting contracts can easily scan the stack for sentinels, which is the expected pattern to determine what token moments are required.
function _flowStack(Evaluable memory evaluable, uint256[] memory callerContext, SignedContextV1[] memory signedContexts)
internal
returns (Pointer, Pointer, uint256[] memory);
Parameters
Name | Type | Description |
---|---|---|
evaluable | Evaluable | The evaluable to evaluate. |
callerContext | uint256[] | The caller context to evaluate the evaluable with. |
signedContexts | SignedContextV1[] | The signed contexts to evaluate the evaluable with. |
Returns
Name | Type | Description |
---|---|---|
<none> | Pointer | The bottom of the stack after evaluation. |
<none> | Pointer | The top of the stack after evaluation. |
<none> | uint256[] | The key-value pairs that were emitted during evaluation. |
Events
FlowInitialized
This event is emitted when a flow is registered at initialization.
event FlowInitialized(address sender, Evaluable evaluable);
Parameters
Name | Type | Description |
---|---|---|
sender | address | The address that registered the flow. |
evaluable | Evaluable | The evaluable of the flow that was registered. The hash of this evaluable is used as the key in registeredFlows so users MUST provide the same evaluable when they evaluate the flow. |