Skip to content
Open
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
49 changes: 49 additions & 0 deletions test/src/concrete/Flow.transfer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -428,4 +428,53 @@ contract FlowTransferTest is FlowTest {
flow.flow(evaluable, new uint256[](0), new SignedContextV1[](0));
vm.stopPrank();
}

/// `IFlowV5.flow()` MUST process the flow atomically. When a later
/// transfer fails, the entire flow MUST revert and earlier transfers
/// must NOT have observable side effects. With mocks, we observe this
/// by asserting the outer revert (transaction revert rolls back any
/// state) and that the failing transfer's selector is the revert
/// reason — i.e. the inner failure was not caught and squashed.
/// forge-config: default.fuzz.runs = 100
function testFlowAtomicRollbackOnLaterTransferFailure(
address alice,
address bob,
uint256 erc20Amount,
uint256 erc1155TokenId,
uint256 erc1155Amount
) external {
vm.assume(alice != address(0));
vm.assume(bob != alice);
vm.assume(Sentinel.unwrap(RAIN_FLOW_SENTINEL) != erc20Amount);
vm.assume(Sentinel.unwrap(RAIN_FLOW_SENTINEL) != erc1155TokenId);
vm.assume(Sentinel.unwrap(RAIN_FLOW_SENTINEL) != erc1155Amount);
vm.label(alice, "Alice");
vm.label(bob, "Bob");

(IFlowV5 flow, EvaluableV2 memory evaluable) = deployFlow();
assumeEtchable(alice, address(flow));
assumeEtchable(bob, address(flow));

// ERC20 from alice → flow (would succeed); ERC1155 from bob → alice
// (will revert because `bob` is neither `msg.sender` nor `flow`).
ERC20Transfer[] memory erc20Transfers = new ERC20Transfer[](1);
erc20Transfers[0] = ERC20Transfer({token: TOKEN_A, from: alice, to: address(flow), amount: erc20Amount});

ERC1155Transfer[] memory erc1155Transfers = new ERC1155Transfer[](1);
erc1155Transfers[0] =
ERC1155Transfer({token: TOKEN_C, from: bob, to: alice, id: erc1155TokenId, amount: erc1155Amount});

uint256[] memory stack = LibStackGeneration.generateFlowStack(
Sentinel.unwrap(RAIN_FLOW_SENTINEL),
FlowTransferV1(erc20Transfers, new ERC721Transfer[](0), erc1155Transfers)
);
interpreterEval2MockCall(stack, new uint256[](0));

vm.mockCall(TOKEN_A, abi.encodeWithSelector(IERC20.transferFrom.selector), abi.encode(true));

vm.startPrank(alice);
vm.expectRevert(UnsupportedERC1155Flow.selector);
flow.flow(evaluable, new uint256[](0), new SignedContextV1[](0));
vm.stopPrank();
}
}
Loading