FlowCommon

Git Source

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

NameTypeDescription
metaHashbytes32As per DeployerDiscoverableMetaV2.
configDeployerDiscoverableMetaV2ConstructionConfigAs 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

NameTypeDescription
evaluableConfigsEvaluableConfigV2[]The evaluable configs to register at initialization. Each of these represents a flow that defines valid token movements at runtime for the inheriting contract.
flowMinOutputsuint256The 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

NameTypeDescription
evaluableEvaluableThe evaluable to evaluate.
callerContextuint256[]The caller context to evaluate the evaluable with.
signedContextsSignedContextV1[]The signed contexts to evaluate the evaluable with.

Returns

NameTypeDescription
<none>PointerThe bottom of the stack after evaluation.
<none>PointerThe 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

NameTypeDescription
senderaddressThe address that registered the flow.
evaluableEvaluableThe 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.