// 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;

    /**
     * @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.
            TOKEN.safeTransferFrom(SENDER, layer.recipient, layer.amount);

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

        shell = tempShell;
    }
}
