// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/// @title  TransferOnion
/// @notice TransferOnion is a hash onion for distributing tokens. The shell commits
///         to an ordered list of the token transfers and can be permissionlessly
///         unwrapped in order. The SENDER must `approve` this contract as
///         `transferFrom` is used to move the token balances.
contract TransferOnion is ReentrancyGuard {
    using SafeERC20 for ERC20;

    /// @notice Struct representing a layer of the onion.
    struct Layer {
        address recipient;
        uint256 amount;
        bytes32 shell;
    }

    /// @notice Address of the token to distribute.
    ERC20 public immutable TOKEN;

    /// @notice Address of the account to distribute tokens from.
    address public immutable SENDER;

    /// @notice Current shell hash.
    bytes32 public shell;

    /// @notice Constructs a new TransferOnion.
    /// @param _token  Address of the token to distribute.
    /// @param _sender Address of the sender to distribute from.
    /// @param _shell  Initial shell of the onion.
    constructor(
        ERC20 _token,
        address _sender,
        bytes32 _shell
    ) {
        TOKEN = _token;
        SENDER = _sender;
        shell = _shell;
    }

    /// @notice Peels layers from the onion and distributes tokens.
    /// @param _layers Array of onion layers to peel.
    function peel(Layer[] memory _layers) public nonReentrant {
        bytes32 tempShell = shell;
        uint256 length = _layers.length;
        for (uint256 i = 0; i < length; ) {
            Layer memory layer = _layers[i];

            // Confirm that the onion layer is correct.
            require(
                keccak256(abi.encode(layer.recipient, layer.amount, layer.shell)) == tempShell,
                "TransferOnion: what are you doing in my swamp?"
            );

            // Update the onion layer.
            tempShell = layer.shell;

            // Transfer the tokens.
            // slither-disable-next-line arbitrary-send-erc20
            TOKEN.safeTransferFrom(SENDER, layer.recipient, layer.amount);

            // Unchecked increment to save some gas.
            unchecked {
                ++i;
            }
        }

        shell = tempShell;
    }
}
