Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 9 additions & 3 deletions src/lib/LibFlow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,23 @@ library LibFlow {
ERC721Transfer[] memory erc721;
ERC1155Transfer[] memory erc1155;
Pointer tuplesPointer;
// erc20
// erc20: each tuple is 4 stack words read top-down as
// (token, from, to, amount). The cast below reinterprets the
// tuple memory as `ERC20Transfer[]`; this is only correct so
// long as `ERC20Transfer` declares fields in exactly that order.
(stackTop, tuplesPointer) = stackBottom.consumeSentinelTuples(stackTop, RAIN_FLOW_SENTINEL, 4);
assembly ("memory-safe") {
erc20 := tuplesPointer
}
// erc721
// erc721: each tuple is 4 stack words read top-down as
// (token, from, to, id). `ERC721Transfer` field order must match.
(stackTop, tuplesPointer) = stackBottom.consumeSentinelTuples(stackTop, RAIN_FLOW_SENTINEL, 4);
assembly ("memory-safe") {
erc721 := tuplesPointer
}
// erc1155
// erc1155: each tuple is 5 stack words read top-down as
// (token, from, to, id, amount). `ERC1155Transfer` field order
// must match.
(stackTop, tuplesPointer) = stackBottom.consumeSentinelTuples(stackTop, RAIN_FLOW_SENTINEL, 5);
assembly ("memory-safe") {
erc1155 := tuplesPointer
Expand Down
63 changes: 62 additions & 1 deletion test/src/concrete/Flow.preview.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
pragma solidity =0.8.25;

import {FlowTest} from "test/abstract/FlowTest.sol";
import {IFlowV5, FlowTransferV1, ERC20Transfer, ERC721Transfer, ERC1155Transfer} from "src/interface/IFlowV5.sol";
import {
IFlowV5,
FlowTransferV1,
ERC20Transfer,
ERC721Transfer,
ERC1155Transfer,
RAIN_FLOW_SENTINEL,
Sentinel
} from "src/interface/IFlowV5.sol";
import {EvaluableV2} from "rain.interpreter.interface/lib/caller/LibEvaluable.sol";
import {LibEvaluable} from "rain.interpreter.interface/lib/caller/LibEvaluable.sol";

Expand Down Expand Up @@ -175,4 +183,57 @@ contract FlowPreviewTest is FlowTest {
keccak256(abi.encode(flowTransfer)), keccak256(abi.encode(flow.stackToFlow(stack))), "wrong compare Structs"
);
}

/// Pins the positional layout of stack tuples against the named fields of
/// `ERC20Transfer` / `ERC721Transfer` / `ERC1155Transfer`. The stack is
/// authored by hand with distinct markers per field — exactly what a
/// rainlang author writes — so that any future reorder of the struct
/// fields produces field-named assertions that fail.
function testFlowStackToFlowFieldOrderPinned() external {
(IFlowV5 flow,) = deployFlow();

// Stack layout (low index = bottom of stack, high index = top; top is
// consumed first by stackToFlow):
// [sentinel, erc1155 tuple (5), sentinel, erc721 tuple (4), sentinel, erc20 tuple (4)]
// Markers chosen so each field is unique: top nibble = token type
// (A=erc20, B=erc721, C=erc1155), next nibble = field index in tuple.
uint256[] memory stack = new uint256[](16);
stack[0] = Sentinel.unwrap(RAIN_FLOW_SENTINEL);
stack[1] = uint256(uint160(0xC0)); // erc1155 token
stack[2] = uint256(uint160(0xC1)); // erc1155 from
stack[3] = uint256(uint160(0xC2)); // erc1155 to
stack[4] = 0xC3C3; // erc1155 id
stack[5] = 0xC4C4; // erc1155 amount
stack[6] = Sentinel.unwrap(RAIN_FLOW_SENTINEL);
stack[7] = uint256(uint160(0xB0)); // erc721 token
stack[8] = uint256(uint160(0xB1)); // erc721 from
stack[9] = uint256(uint160(0xB2)); // erc721 to
stack[10] = 0xB3B3; // erc721 id
stack[11] = Sentinel.unwrap(RAIN_FLOW_SENTINEL);
stack[12] = uint256(uint160(0xA0)); // erc20 token
stack[13] = uint256(uint160(0xA1)); // erc20 from
stack[14] = uint256(uint160(0xA2)); // erc20 to
stack[15] = 0xA3A3; // erc20 amount

FlowTransferV1 memory result = flow.stackToFlow(stack);

assertEq(result.erc20.length, 1, "erc20 length");
assertEq(result.erc20[0].token, address(uint160(0xA0)), "erc20 token");
assertEq(result.erc20[0].from, address(uint160(0xA1)), "erc20 from");
assertEq(result.erc20[0].to, address(uint160(0xA2)), "erc20 to");
assertEq(result.erc20[0].amount, 0xA3A3, "erc20 amount");

assertEq(result.erc721.length, 1, "erc721 length");
assertEq(result.erc721[0].token, address(uint160(0xB0)), "erc721 token");
assertEq(result.erc721[0].from, address(uint160(0xB1)), "erc721 from");
assertEq(result.erc721[0].to, address(uint160(0xB2)), "erc721 to");
assertEq(result.erc721[0].id, 0xB3B3, "erc721 id");

assertEq(result.erc1155.length, 1, "erc1155 length");
assertEq(result.erc1155[0].token, address(uint160(0xC0)), "erc1155 token");
assertEq(result.erc1155[0].from, address(uint160(0xC1)), "erc1155 from");
assertEq(result.erc1155[0].to, address(uint160(0xC2)), "erc1155 to");
assertEq(result.erc1155[0].id, 0xC3C3, "erc1155 id");
assertEq(result.erc1155[0].amount, 0xC4C4, "erc1155 amount");
}
}
Loading