diff --git a/.gas-snapshot b/.gas-snapshot index e69de29b..ad2c4b95 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -0,0 +1 @@ +IFlowV4Test:testSentinelValue() (gas: 332) \ No newline at end of file diff --git a/src/concrete/basic/Flow.sol b/src/concrete/basic/Flow.sol index 31d198ea..9e43b189 100644 --- a/src/concrete/basic/Flow.sol +++ b/src/concrete/basic/Flow.sol @@ -8,17 +8,33 @@ import { LibContext, MIN_FLOW_SENTINELS } from "../../abstract/FlowCommon.sol"; -import {IFlowV4, Evaluable, FlowTransferV1, SignedContextV1, EvaluableConfigV2, LibFlow} from "../../lib/LibFlow.sol"; +import {IFlowV4, LibFlow} from "../../lib/LibFlow.sol"; import {LibUint256Matrix} from "rain.solmem/lib/LibUint256Matrix.sol"; import {Pointer} from "rain.solmem/lib/LibPointer.sol"; +import {LibUint256Array} from "rain.solmem/lib/LibUint256Array.sol"; +import {Evaluable, EvaluableConfigV2} from "rain.interpreter/src/lib/caller/LibEvaluable.sol"; +import {FlowTransferV1} from "../../interface/unstable/IFlowV4.sol"; +import {SignedContextV1} from "rain.interpreter/src/interface/IInterpreterCallerV2.sol"; +/// @dev The hash of the meta data expected to be passed to `FlowCommon`'s +/// constructor. bytes32 constant CALLER_META_HASH = bytes32(0x95de68a447a477b8fab10701f1265b3e85a98b24710b3e40e6a96aa6d76263bc); +/// @title Flow +/// See `IFlowV4` docs. contract Flow is ICloneableV2, IFlowV4, FlowCommon { using LibUint256Matrix for uint256[]; + using LibUint256Array for uint256[]; + /// Forwards to `FlowCommon` constructor. constructor(DeployerDiscoverableMetaV2ConstructionConfig memory config) FlowCommon(CALLER_META_HASH, config) {} + /// Overloaded typed initialize function MUST revert with this error. + /// As per `ICloneableV2` interface. + function initialize(EvaluableConfigV2[] memory) external pure { + revert InitializeSignatureFn(); + } + /// @inheritdoc ICloneableV2 function initialize(bytes calldata data) external initializer returns (bytes32) { EvaluableConfigV2[] memory flowConfig = abi.decode(data, (EvaluableConfigV2[])); @@ -28,25 +44,12 @@ contract Flow is ICloneableV2, IFlowV4, FlowCommon { return ICLONEABLE_V2_SUCCESS; } - function _previewFlow(Evaluable memory evaluable, uint256[][] memory context) - internal - view - returns (FlowTransferV1 memory, uint256[] memory) - { - (Pointer stackBottom, Pointer stackTop, uint256[] memory kvs) = flowStack(evaluable, context); - return (LibFlow.stackToFlow(stackBottom, stackTop), kvs); - } - - function previewFlow( - Evaluable memory evaluable, - uint256[] memory callerContext, - SignedContextV1[] memory signedContexts - ) external view virtual returns (FlowTransferV1 memory) { - uint256[][] memory context = LibContext.build(callerContext.matrixFrom(), signedContexts); - (FlowTransferV1 memory flowTransfer,) = _previewFlow(evaluable, context); - return flowTransfer; + /// @inheritdoc IFlowV4 + function stackToFlow(uint256[] memory stack) external pure virtual override returns (FlowTransferV1 memory) { + return LibFlow.stackToFlow(stack.dataPointer(), stack.endPointer()); } + /// @inheritdoc IFlowV4 function flow(Evaluable memory evaluable, uint256[] memory callerContext, SignedContextV1[] memory signedContexts) external virtual @@ -55,7 +58,8 @@ contract Flow is ICloneableV2, IFlowV4, FlowCommon { { uint256[][] memory context = LibContext.build(callerContext.matrixFrom(), signedContexts); emit Context(msg.sender, context); - (FlowTransferV1 memory flowTransfer, uint256[] memory kvs) = _previewFlow(evaluable, context); + (Pointer stackBottom, Pointer stackTop, uint256[] memory kvs) = flowStack(evaluable, context); + FlowTransferV1 memory flowTransfer = LibFlow.stackToFlow(stackBottom, stackTop); LibFlow.flow(flowTransfer, evaluable.store, kvs); return flowTransfer; } diff --git a/src/concrete/erc1155/FlowERC1155.sol b/src/concrete/erc1155/FlowERC1155.sol index f31fb195..7130d6e4 100644 --- a/src/concrete/erc1155/FlowERC1155.sol +++ b/src/concrete/erc1155/FlowERC1155.sol @@ -14,14 +14,15 @@ import { FlowERC1155IOV1, SignedContextV1, FlowERC1155ConfigV2, - ERC1155SupplyChange + ERC1155SupplyChange, + RAIN_FLOW_ERC1155_SENTINEL } from "../../interface/unstable/IFlowERC1155V4.sol"; import {LibBytecode} from "lib/rain.interpreter/src/lib/bytecode/LibBytecode.sol"; import {IInterpreterV1} from "rain.interpreter/src/interface/IInterpreterV1.sol"; import {IInterpreterStoreV1} from "rain.interpreter/src/interface/IInterpreterStoreV1.sol"; import {Evaluable, DEFAULT_STATE_NAMESPACE} from "rain.interpreter/src/lib/caller/LibEvaluable.sol"; import {Pointer} from "rain.solmem/lib/LibPointer.sol"; -import {SENTINEL_HIGH_BITS, LibFlow} from "../../lib/LibFlow.sol"; +import {LibFlow} from "../../lib/LibFlow.sol"; import {SourceIndex} from "rain.interpreter/src/interface/IInterpreterV1.sol"; import { MIN_FLOW_SENTINELS, @@ -31,9 +32,6 @@ import { } from "../../abstract/FlowCommon.sol"; import {LibContext} from "rain.interpreter/src/lib/caller/LibContext.sol"; -Sentinel constant RAIN_FLOW_ERC1155_SENTINEL = - Sentinel.wrap(uint256(keccak256(bytes("RAIN_FLOW_ERC1155_SENTINEL")) | SENTINEL_HIGH_BITS)); - bytes32 constant CALLER_META_HASH = bytes32(0x7ea70f837234357ec1bb5b777e04453ebaf3ca778a98805c4bb20a738d559a21); SourceIndex constant HANDLE_TRANSFER_ENTRYPOINT = SourceIndex.wrap(0); @@ -51,6 +49,12 @@ contract FlowERC1155 is ICloneableV2, IFlowERC1155V4, FlowCommon, ERC1155 { constructor(DeployerDiscoverableMetaV2ConstructionConfig memory config) FlowCommon(CALLER_META_HASH, config) {} + /// Overloaded typed initialize function MUST revert with this error. + /// As per `ICloneableV2` interface. + function initialize(FlowERC1155ConfigV2 memory) external pure { + revert InitializeSignatureFn(); + } + /// @inheritdoc ICloneableV2 function initialize(bytes calldata data) external initializer returns (bytes32) { FlowERC1155ConfigV2 memory flowERC1155Config = abi.decode(data, (FlowERC1155ConfigV2)); diff --git a/src/concrete/erc20/FlowERC20.sol b/src/concrete/erc20/FlowERC20.sol index 4d392b7a..7884380d 100644 --- a/src/concrete/erc20/FlowERC20.sol +++ b/src/concrete/erc20/FlowERC20.sol @@ -15,9 +15,9 @@ import { } from "../../interface/unstable/IFlowERC20V4.sol"; import {LibBytecode} from "lib/rain.interpreter/src/lib/bytecode/LibBytecode.sol"; import {EncodedDispatch, LibEncodedDispatch} from "rain.interpreter/src/lib/caller/LibEncodedDispatch.sol"; - +import {RAIN_FLOW_ERC20_SENTINEL} from "../../interface/unstable/IFlowERC20V4.sol"; import {Sentinel, LibStackSentinel} from "rain.solmem/lib/LibStackSentinel.sol"; -import {SENTINEL_HIGH_BITS, LibFlow} from "../../lib/LibFlow.sol"; +import {LibFlow} from "../../lib/LibFlow.sol"; import { FlowCommon, DeployerDiscoverableMetaV2, @@ -32,23 +32,34 @@ import {LibContext} from "rain.interpreter/src/lib/caller/LibContext.sol"; bytes32 constant CALLER_META_HASH = bytes32(0xff0499e4ee7171a54d176cfe13165a7ea512d146dbd99d42b3d3ec9963025acf); -Sentinel constant RAIN_FLOW_ERC20_SENTINEL = - Sentinel.wrap(uint256(keccak256(bytes("RAIN_FLOW_ERC20_SENTINEL")) | SENTINEL_HIGH_BITS)); - SourceIndex constant HANDLE_TRANSFER_ENTRYPOINT = SourceIndex.wrap(0); uint256 constant HANDLE_TRANSFER_MIN_OUTPUTS = 0; uint16 constant HANDLE_TRANSFER_MAX_OUTPUTS = 0; /// @title FlowERC20 +/// See `IFlowERC20V4` for documentation. contract FlowERC20 is ICloneableV2, IFlowERC20V4, FlowCommon, ERC20 { using LibStackSentinel for Pointer; using LibUint256Matrix for uint256[]; + /// @dev True if we need to eval `handleTransfer` on every transfer. For many + /// tokens this will be false, so we don't want to invoke the external + /// interpreter call just to cause a noop. bool private sEvalHandleTransfer; + + /// @dev The evaluable that will be used to evaluate `handleTransfer` on + /// every transfer. This is only set if `sEvalHandleTransfer` is true. Evaluable internal sEvaluable; + /// Forwards the `FlowCommon` constructor arguments to the `FlowCommon` constructor(DeployerDiscoverableMetaV2ConstructionConfig memory config) FlowCommon(CALLER_META_HASH, config) {} + /// Overloaded typed initialize function MUST revert with this error. + /// As per `ICloneableV2` interface. + function initialize(FlowERC20ConfigV2 memory) external pure { + revert InitializeSignatureFn(); + } + /// @inheritdoc ICloneableV2 function initialize(bytes calldata data) external initializer returns (bytes32) { FlowERC20ConfigV2 memory flowERC20Config = abi.decode(data, (FlowERC20ConfigV2)); diff --git a/src/concrete/erc721/FlowERC721.sol b/src/concrete/erc721/FlowERC721.sol index 5b19f13a..31acae3c 100644 --- a/src/concrete/erc721/FlowERC721.sol +++ b/src/concrete/erc721/FlowERC721.sol @@ -18,7 +18,7 @@ import { } from "../../interface/unstable/IFlowERC721V4.sol"; import {LibBytecode} from "lib/rain.interpreter/src/lib/bytecode/LibBytecode.sol"; import {SourceIndex} from "rain.interpreter/src/interface/IInterpreterV1.sol"; -import {LibFlow, SENTINEL_HIGH_BITS} from "../../lib/LibFlow.sol"; +import {LibFlow} from "../../lib/LibFlow.sol"; import { FlowCommon, DeployerDiscoverableMetaV2ConstructionConfig, @@ -30,13 +30,11 @@ import {Evaluable, DEFAULT_STATE_NAMESPACE} from "rain.interpreter/src/lib/calle import {IInterpreterV1} from "rain.interpreter/src/interface/IInterpreterV1.sol"; import {IInterpreterStoreV1} from "rain.interpreter/src/interface/IInterpreterStoreV1.sol"; import {Pointer} from "rain.solmem/lib/LibPointer.sol"; +import {RAIN_FLOW_ERC721_SENTINEL} from "../../interface/unstable/IFlowERC721V4.sol"; /// Thrown when burner of tokens is not the owner of tokens. error BurnerNotOwner(); -Sentinel constant RAIN_FLOW_ERC721_SENTINEL = - Sentinel.wrap(uint256(keccak256(bytes("RAIN_FLOW_ERC721_SENTINEL")) | SENTINEL_HIGH_BITS)); - bytes32 constant CALLER_META_HASH = bytes32(0x7f7944a4b89741668c06a27ffde94e19be970cd0506786de91aee01c2893d4ef); SourceIndex constant HANDLE_TRANSFER_ENTRYPOINT = SourceIndex.wrap(0); @@ -58,6 +56,12 @@ contract FlowERC721 is ICloneableV2, IFlowERC721V4, FlowCommon, ERC721 { constructor(DeployerDiscoverableMetaV2ConstructionConfig memory config) FlowCommon(CALLER_META_HASH, config) {} + /// Overloaded typed initialize function MUST revert with this error. + /// As per `ICloneableV2` interface. + function initialize(FlowERC721ConfigV2 memory) external pure { + revert InitializeSignatureFn(); + } + /// @inheritdoc ICloneableV2 function initialize(bytes calldata data) external initializer returns (bytes32) { FlowERC721ConfigV2 memory flowERC721Config = abi.decode(data, (FlowERC721ConfigV2)); diff --git a/src/interface/unstable/IFlowERC1155V4.sol b/src/interface/unstable/IFlowERC1155V4.sol index c405a199..c1bdc331 100644 --- a/src/interface/unstable/IFlowERC1155V4.sol +++ b/src/interface/unstable/IFlowERC1155V4.sol @@ -3,9 +3,14 @@ pragma solidity ^0.8.18; import {SignedContextV1} from "rain.interpreter/src/interface/IInterpreterCallerV2.sol"; import {Evaluable, EvaluableConfigV2} from "rain.interpreter/src/lib/caller/LibEvaluable.sol"; +import {Sentinel} from "rain.solmem/lib/LibStackSentinel.sol"; +import {SENTINEL_HIGH_BITS} from "./IFlowV4.sol"; import {FlowERC1155IOV1, ERC1155SupplyChange} from "../IFlowERC1155V3.sol"; +Sentinel constant RAIN_FLOW_ERC1155_SENTINEL = + Sentinel.wrap(uint256(keccak256(bytes("RAIN_FLOW_ERC1155_SENTINEL")) | SENTINEL_HIGH_BITS)); + struct FlowERC1155ConfigV2 { string uri; EvaluableConfigV2 evaluableConfig; diff --git a/src/interface/unstable/IFlowERC20V4.sol b/src/interface/unstable/IFlowERC20V4.sol index edd712bd..4d4a0f4a 100644 --- a/src/interface/unstable/IFlowERC20V4.sol +++ b/src/interface/unstable/IFlowERC20V4.sol @@ -3,8 +3,12 @@ pragma solidity ^0.8.18; import {SignedContextV1} from "rain.interpreter/src/interface/IInterpreterCallerV2.sol"; import {Evaluable, EvaluableConfigV2} from "rain.interpreter/src/lib/caller/LibEvaluable.sol"; - +import {Sentinel} from "rain.solmem/lib/LibStackSentinel.sol"; import {FlowERC20IOV1, ERC20SupplyChange} from "../IFlowERC20V3.sol"; +import {SENTINEL_HIGH_BITS} from "./IFlowV4.sol"; + +Sentinel constant RAIN_FLOW_ERC20_SENTINEL = + Sentinel.wrap(uint256(keccak256(bytes("RAIN_FLOW_ERC20_SENTINEL")) | SENTINEL_HIGH_BITS)); /// Constructor config. /// @param name As per Open Zeppelin `ERC20Upgradeable`. diff --git a/src/interface/unstable/IFlowERC721V4.sol b/src/interface/unstable/IFlowERC721V4.sol index 9282cbd0..999aeffb 100644 --- a/src/interface/unstable/IFlowERC721V4.sol +++ b/src/interface/unstable/IFlowERC721V4.sol @@ -8,6 +8,9 @@ import {FlowERC721IOV1, ERC721SupplyChange} from "../IFlowERC721V3.sol"; import "./IFlowV4.sol"; +Sentinel constant RAIN_FLOW_ERC721_SENTINEL = + Sentinel.wrap(uint256(keccak256(bytes("RAIN_FLOW_ERC721_SENTINEL")) | SENTINEL_HIGH_BITS)); + /// Constructor config. /// @param name As per Open Zeppelin `ERC721Upgradeable`. /// @param symbol As per Open Zeppelin `ERC721Upgradeable`. diff --git a/src/interface/unstable/IFlowV4.sol b/src/interface/unstable/IFlowV4.sol index 2562e466..54a32c58 100644 --- a/src/interface/unstable/IFlowV4.sol +++ b/src/interface/unstable/IFlowV4.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: CAL pragma solidity ^0.8.18; -import "rain.interpreter/src/interface/IInterpreterCallerV2.sol"; -import "rain.interpreter/src/lib/caller/LibEvaluable.sol"; +import {SignedContextV1} from "rain.interpreter/src/interface/IInterpreterCallerV2.sol"; +import {EvaluableConfigV2, Evaluable} from "rain.interpreter/src/lib/caller/LibEvaluable.sol"; +import {Sentinel} from "rain.solmem/lib/LibStackSentinel.sol"; +import {Pointer} from "rain.solmem/lib/LibPointer.sol"; import {FlowTransferV1, ERC20Transfer, ERC721Transfer, ERC1155Transfer} from "../IFlowV3.sol"; @@ -10,20 +12,158 @@ import {FlowTransferV1, ERC20Transfer, ERC721Transfer, ERC1155Transfer} from ".. /// @param unregisteredHash Hash of the unregistered flow. error UnregisteredFlow(bytes32 unregisteredHash); +/// @dev Sets the high bits of all flow sentinels to guarantee that the numeric +/// value of the sentinel will never collide with a token amount or address. This +/// guarantee holds as long as the token supply is less than 2^252, and the +/// that token IDs have no specific reason to collide with the sentinel. +/// i.e. There won't be random collisions because the space of token IDs is +/// too large. +bytes32 constant SENTINEL_HIGH_BITS = bytes32(0xF000000000000000000000000000000000000000000000000000000000000000); + +/// @dev We want a sentinel with the following properties: +/// - Won't collide with token amounts (| with very large number) +/// - Won't collide with token addresses +/// - Won't collide with common values like `type(uint256).max` and +/// `type(uint256).min` +/// - Won't collide with other sentinels from unrelated contexts +Sentinel constant RAIN_FLOW_SENTINEL = + Sentinel.wrap(uint256(keccak256(bytes("RAIN_FLOW_SENTINEL")) | SENTINEL_HIGH_BITS)); + /// @title IFlowV4 /// @notice Interface for a flow contract that does NOT require native minting /// or burning of itself as a token. This is the base case that all other flow /// interfaces model themselves after, with the addition of token minting and /// burning. +/// +/// Current functionality only allows for moving third party tokens between +/// accounts. Token standards ERC20, ERC721, and ERC1155 are supported. +/// +/// The basic lifecycle of a flow is: +/// - `Flow` is deployed as a reference implementation to be cloned, with its +/// initializers disabled on construction. +/// - `Flow` is cloned and initialized with an abi encoded list of evaluable +/// configs that define every possible movement of tokens that can occur due +/// to the clone. The EOA that deployed the clone DOES NOT have any special +/// privileges over the clone, although they could grant themselves privileges +/// by flowing tokens to themselves or similar within the evaluables. Ideally +/// the EOA doesn't introduce "admin" features as it would be a security risk +/// to themselves and others. In the case that they do, all priviledges will +/// be visible in the rainlang code of the evaluable, there's no hidden +/// functionality that can be introduced to the clone bytecode. +/// - Anyone can call `flow` on the clone, passing in one of the evaluables set +/// during initialization. If the evaluable passed by the caller does not +/// match an initialized evaluable, the flow MUST revert with +/// `UnregisteredFlow`. The entirety of the resulting stack from the evaluation +/// defines all the token movements that MUST occur as a result of the flow. +/// ANY failures during the flow MUST revert the entire flow, leaving the +/// state of the tokens unchanged. +/// +/// The structure of the stack can be thought of as a simple list of transfers. +/// All the erc20 tokens are moved first, then the erc721 tokens, then the +/// erc1155 tokens. Each token type is separated in the stack by a sentinel +/// value. The sentinel is a constant, `RAIN_FLOW_SENTINEL`, that is guaranteed +/// to not collide with any token amounts or addresses. The sentinel is also +/// guaranteed to not collide with any other sentinels from other contexts, to +/// the extent that we can guarantee that with raw cryptographic collision +/// resistance. This sentinel can be thought of as similar to the null terminator +/// in a c string, it's a value that is guaranteed to not be a valid value for +/// the type of data it's separating. The main benefit in this context, for +/// rainlang authors, is that they can always use the same constant value in +/// all their rainlang code to separate the different token types, rather than +/// needing to manually calculate the length of the tuples they're wanting to +/// flow over in each token type (which would be very error prone). +/// +/// Currently every token transfer type MUST be present in every flow stack, +/// which is awkward as it means that if you want to flow erc20 tokens, you +/// MUST also flow erc721 and erc1155 tokens, even if you don't want to. This +/// is a limitation of the current implementation, and will be fixed in a future +/// version. +/// +/// Each individual token transfer is simply a list of values, where the values +/// are specific to the token type. +/// - erc20 transfers are a list of 4 values: +/// - address of the token contract +/// - address of the token sender +/// - address of the token recipient +/// - amount of tokens to transfer +/// - erc721 transfers are a list of 4 values: +/// - address of the token contract +/// - address of the token sender +/// - address of the token recipient +/// - token id to transfer +/// - erc1155 transfers are a list of 5 values: +/// - address of the token contract +/// - address of the token sender +/// - address of the token recipient +/// - token id to transfer +/// - amount of tokens to transfer +/// +/// The final stack is processed from the bottom up, so the first token transfer +/// in the stack is the last one to be processed. +/// +/// For example, a rainlang expression that transfers 1e18 erc20 token 0xf00baa +/// from the flow contract to the address 0xdeadbeef, and 1 erc721 token address +/// 0x1234 and id 5678 from the address 0xdeadbeef to the flow contract, would +/// result in the following rainlang/stack: +/// +/// ``` +/// /* sentinel is always the same. */ +/// sentinel: 0xfea74d0c9bf4a3c28f0dd0674db22a3d7f8bf259c56af19f4ac1e735b156974f, +/// /* erc1155 transfers are first, just a sentinel as there's nothing to do */ +/// _: sentinel, +/// /* erc721 transfers are next, with the token id as the last value */ +/// _: 0x1234 0xdeadbeef context<0 1>() 5678, +/// /* erc20 transfers are last, with the amount as the last value */ +/// _: 0xf00baa context<0 1>() 0xdeadbeef 1e18; +/// ``` +/// +/// Note that for all token transfers the sender of the tokens MUST be either +/// the flow contract itself, or the caller of the flow contract. This is to +/// prevent the flow contract from being able to transfer tokens from arbitrary +/// addresses without their consent. Even if some address has approved the flow +/// contract to transfer tokens on their behalf, the flow contract MUST NOT +/// transfer tokens from that address unless the caller of the flow contract +/// is that address. +/// +/// Note that native gas movements are not supported in this version of the +/// flow contract. This is because the current reference implementation uses +/// `Multicall` to batch together multiple calls to the flow contract, and +/// this involves a loop over a delegate call, which is not safe to do with +/// native gas movements. This will be fixed in a future version of the interface +/// where batching is handled by the flow contract itself, rather than relying +/// on `Multicall`. interface IFlowV4 { + /// MUST be emitted when the flow contract is initialized. + /// @param sender The EOA that deployed the flow contract. + /// @param config The list of evaluable configs that define the flows. event Initialize(address sender, EvaluableConfigV2[] config); - function previewFlow( - Evaluable calldata evaluable, - uint256[] calldata callerContext, - SignedContextV1[] calldata signedContexts - ) external view returns (FlowTransferV1 calldata flowTransfer); + /// Given a stack of values, convert it to a flow transfer. MUST NOT modify + /// state but MAY revert if the stack is malformed. The intended workflow is + /// that the interpreter contract is called to produce a stack then the stack + /// is converted to a flow transfer struct, to allow the caller to preview + /// a flow before actually executing it. By accepting a stack as input, the + /// caller can preview any possible flow, not just ones that have been + /// registered with the flow contract, and can preview flows that may not + /// even be possible to execute due to the state of the tokens, or access + /// gating that would exclude the caller, etc. + /// @param stack The stack of values to convert to a flow transfer. + /// @return flowTransfer The resulting flow transfer. + function stackToFlow(uint256[] memory stack) external pure returns (FlowTransferV1 memory flowTransfer); + /// Given an evaluable, caller context, and signed contexts, evaluate the + /// evaluable and return the resulting flow transfer. MUST process the + /// flow transfer atomically, either all of it succeeds or none of it + /// succeeds. MUST revert if the evaluable is not registered with the flow + /// contract. MUST revert if the evaluable reverts. MUST revert if the + /// evaluable returns a stack that is malformed. MUST revert if the evaluable + /// returns a stack that contains a token transfer that is not allowed by + /// the flow contract (e.g. if a token is being moved from an address that + /// is not the caller or the flow contract). + /// @param evaluable The evaluable to evaluate. + /// @param callerContext The caller context to pass to the evaluable. + /// @param signedContexts The signed contexts to pass to the evaluable. + /// @return flowTransfer The resulting flow transfer. function flow( Evaluable calldata evaluable, uint256[] calldata callerContext, diff --git a/src/lib/LibFlow.sol b/src/lib/LibFlow.sol index 39a3b0f4..e68ee064 100644 --- a/src/lib/LibFlow.sol +++ b/src/lib/LibFlow.sol @@ -1,17 +1,16 @@ // SPDX-License-Identifier: CAL pragma solidity ^0.8.18; -import "../interface/unstable/IFlowV4.sol"; - -import "rain.solmem/lib/LibStackPointer.sol"; -import "rain.solmem/lib/LibStackSentinel.sol"; -import "rain.interpreter/src/interface/IInterpreterStoreV1.sol"; +import {IFlowV4, RAIN_FLOW_SENTINEL} from "../interface/unstable/IFlowV4.sol"; +import {Pointer} from "rain.solmem/lib/LibPointer.sol"; +import {FlowTransferV1, ERC20Transfer, ERC721Transfer, ERC1155Transfer} from "../interface/unstable/IFlowV4.sol"; +import {IInterpreterStoreV1, DEFAULT_STATE_NAMESPACE} from "rain.interpreter/src/interface/IInterpreterStoreV1.sol"; +import {LibStackSentinel} from "rain.solmem/lib/LibStackSentinel.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC721} from "openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; import {IERC1155} from "openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol"; -import {SafeCast} from "openzeppelin-contracts/contracts/utils/math/SafeCast.sol"; /// @dev Thrown for unsupported native transfers. error UnsupportedNativeFlow(); @@ -25,60 +24,45 @@ error UnsupportedERC721Flow(); /// @dev Thrown for unsupported erc1155 transfers. error UnsupportedERC1155Flow(); -bytes32 constant SENTINEL_HIGH_BITS = bytes32(0xF000000000000000000000000000000000000000000000000000000000000000); - -/// @dev We want a sentinel with the following properties: -/// - Won't collide with token amounts (| with very large number) -/// - Won't collide with token addresses -/// - Won't collide with common values like `type(uint256).max` and -/// `type(uint256).min` -/// - Won't collide with other sentinels from unrelated contexts -Sentinel constant RAIN_FLOW_SENTINEL = - Sentinel.wrap(uint256(keccak256(bytes("RAIN_FLOW_SENTINEL")) | SENTINEL_HIGH_BITS)); - library LibFlow { using SafeERC20 for IERC20; - using LibPointer for Pointer; - using LibStackPointer for Pointer; using LibStackSentinel for Pointer; - using SafeCast for uint256; using LibFlow for FlowTransferV1; - using LibUint256Array for uint256[]; - function stackToFlow(Pointer stackBottom_, Pointer stackTop_) internal pure returns (FlowTransferV1 memory) { + function stackToFlow(Pointer stackBottom, Pointer stackTop) internal pure returns (FlowTransferV1 memory) { unchecked { - ERC20Transfer[] memory erc20_; - ERC721Transfer[] memory erc721_; - ERC1155Transfer[] memory erc1155_; - Pointer tuplesPointer_; + ERC20Transfer[] memory erc20; + ERC721Transfer[] memory erc721; + ERC1155Transfer[] memory erc1155; + Pointer tuplesPointer; // erc20 - (stackTop_, tuplesPointer_) = stackBottom_.consumeSentinelTuples(stackTop_, RAIN_FLOW_SENTINEL, 4); + (stackTop, tuplesPointer) = stackBottom.consumeSentinelTuples(stackTop, RAIN_FLOW_SENTINEL, 4); assembly ("memory-safe") { - erc20_ := tuplesPointer_ + erc20 := tuplesPointer } // erc721 - (stackTop_, tuplesPointer_) = stackBottom_.consumeSentinelTuples(stackTop_, RAIN_FLOW_SENTINEL, 4); + (stackTop, tuplesPointer) = stackBottom.consumeSentinelTuples(stackTop, RAIN_FLOW_SENTINEL, 4); assembly ("memory-safe") { - erc721_ := tuplesPointer_ + erc721 := tuplesPointer } // erc1155 - (stackTop_, tuplesPointer_) = stackBottom_.consumeSentinelTuples(stackTop_, RAIN_FLOW_SENTINEL, 5); + (stackTop, tuplesPointer) = stackBottom.consumeSentinelTuples(stackTop, RAIN_FLOW_SENTINEL, 5); assembly ("memory-safe") { - erc1155_ := tuplesPointer_ + erc1155 := tuplesPointer } - return FlowTransferV1(erc20_, erc721_, erc1155_); + return FlowTransferV1(erc20, erc721, erc1155); } } - function flowERC20(FlowTransferV1 memory flowTransfer_) internal { + function flowERC20(FlowTransferV1 memory flowTransfer) internal { unchecked { - ERC20Transfer memory transfer_; - for (uint256 i_ = 0; i_ < flowTransfer_.erc20.length; i_++) { - transfer_ = flowTransfer_.erc20[i_]; - if (transfer_.from == msg.sender) { - IERC20(transfer_.token).safeTransferFrom(msg.sender, transfer_.to, transfer_.amount); - } else if (transfer_.from == address(this)) { - IERC20(transfer_.token).safeTransfer(transfer_.to, transfer_.amount); + ERC20Transfer memory transfer; + for (uint256 i = 0; i < flowTransfer.erc20.length; i++) { + transfer = flowTransfer.erc20[i]; + if (transfer.from == msg.sender) { + IERC20(transfer.token).safeTransferFrom(msg.sender, transfer.to, transfer.amount); + } else if (transfer.from == address(this)) { + IERC20(transfer.token).safeTransfer(transfer.to, transfer.amount); } else { // We don't support `from` as anyone other than `you` or `me` // as this would allow for all kinds of issues re: approvals. @@ -88,44 +72,42 @@ library LibFlow { } } - function flowERC721(FlowTransferV1 memory flowTransfer_) internal { + function flowERC721(FlowTransferV1 memory flowTransfer) internal { unchecked { - ERC721Transfer memory transfer_; - for (uint256 i_ = 0; i_ < flowTransfer_.erc721.length; i_++) { - transfer_ = flowTransfer_.erc721[i_]; - if (transfer_.from != msg.sender && transfer_.from != address(this)) { + ERC721Transfer memory transfer; + for (uint256 i = 0; i < flowTransfer.erc721.length; i++) { + transfer = flowTransfer.erc721[i]; + if (transfer.from != msg.sender && transfer.from != address(this)) { revert UnsupportedERC721Flow(); } - IERC721(transfer_.token).safeTransferFrom(transfer_.from, transfer_.to, transfer_.id); + IERC721(transfer.token).safeTransferFrom(transfer.from, transfer.to, transfer.id); } } } - function flowERC1155(FlowTransferV1 memory flowTransfer_) internal { + function flowERC1155(FlowTransferV1 memory flowTransfer) internal { unchecked { - ERC1155Transfer memory transfer_; - for (uint256 i_ = 0; i_ < flowTransfer_.erc1155.length; i_++) { - transfer_ = flowTransfer_.erc1155[i_]; - if (transfer_.from != msg.sender && transfer_.from != address(this)) { + ERC1155Transfer memory transfer; + for (uint256 i = 0; i < flowTransfer.erc1155.length; i++) { + transfer = flowTransfer.erc1155[i]; + if (transfer.from != msg.sender && transfer.from != address(this)) { revert UnsupportedERC1155Flow(); } // @todo safeBatchTransferFrom support. // @todo data support. - IERC1155(transfer_.token).safeTransferFrom( - transfer_.from, transfer_.to, transfer_.id, transfer_.amount, "" - ); + IERC1155(transfer.token).safeTransferFrom(transfer.from, transfer.to, transfer.id, transfer.amount, ""); } } } - function flow(FlowTransferV1 memory flowTransfer_, IInterpreterStoreV1 interpreterStore_, uint256[] memory kvs_) + function flow(FlowTransferV1 memory flowTransfer, IInterpreterStoreV1 interpreterStore, uint256[] memory kvs) internal { - if (kvs_.length > 0) { - interpreterStore_.set(DEFAULT_STATE_NAMESPACE, kvs_); + if (kvs.length > 0) { + interpreterStore.set(DEFAULT_STATE_NAMESPACE, kvs); } - flowTransfer_.flowERC20(); - flowTransfer_.flowERC721(); - flowTransfer_.flowERC1155(); + flowTransfer.flowERC20(); + flowTransfer.flowERC721(); + flowTransfer.flowERC1155(); } } diff --git a/test/interface/unstable/IFlowV4.t.sol b/test/interface/unstable/IFlowV4.t.sol new file mode 100644 index 00000000..b6325e5e --- /dev/null +++ b/test/interface/unstable/IFlowV4.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.19; + +import "forge-std/Test.sol"; + +import {IFlowV4, RAIN_FLOW_SENTINEL} from "src/interface/unstable/IFlowV4.sol"; +import {Sentinel} from "rain.solmem/lib/LibStackSentinel.sol"; + +contract IFlowV4Test is Test { + function testSentinelValue() external { + assertEq( + 0xfea74d0c9bf4a3c28f0dd0674db22a3d7f8bf259c56af19f4ac1e735b156974f, Sentinel.unwrap(RAIN_FLOW_SENTINEL) + ); + } +}