contract ArbitrageBot is AccessControl {
    using SafeMath for uint256;

    IBPool public bPool;
    address public safeAccount;

    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    bytes32 public constant WITHDRAWAL_ROLE = keccak256("WITHDRAWAL_ROLE");

    mapping(address => ERC20) public tokens;

    string private constant UNAUTHORIZED_USER = "The Caller is Unauthorized";
    string private constant UNSET_SAFE_ACCOUNT = "First set the safe account";
    string private constant TRANSFER_ERROR = "Token transfer failed";
    string private constant INVALID_ADDRESS = "Invalid address";

    event Swapped(address tokenIn, address tokenOut, uint256 tokenAmountIn, uint256 minTokenAmountOut);
    event SafeAccountSet(address safeAccount);
    event ReclaimedToken(address token, uint256 amount, address safeAccount);
    event TokensUpdated(address token);

    address public constant _bPool = 0x1Dc2948B6dB34E38291090B825518C1E8346938B;
    address public constant _deus = 0x3b62F3820e0B035cc4aD602dECe6d796BC325325;
    address public constant _dea = 0x80aB141F324C3d6F2b18b030f1C4E95d4d658778;
    address public constant _sdea = 0xd8C33488B76D4a2C06D5cCB75574f10F6ccaC3D7;
    address public constant _suni_dd = 0x2EdE9CB92a6dE0916889E5936B1aAd0e99ddf242;
    address public constant _suni_de = 0x670431fCdAf39280deE488C6D8277B9865E22d08;
    address public constant _suni_du = 0xB7b52c3523Af9c237817a49D17E656283cC59678;
    address public constant _sdeus = 0xc586AeA83A96d57764A431B9F4e2E84844075a01;


    /**
     * @dev sets the pool and the tokens addresses
     */
    constructor() public {
        bPool = IBPool(_bPool);
        tokens[_deus] = ERC20(_deus);
        tokens[_dea] = ERC20(_dea);
        tokens[_sdea] = ERC20(_sdea);
        tokens[_suni_dd] = ERC20(_suni_dd);
        tokens[_suni_de] = ERC20(_suni_de);
        tokens[_suni_du] = ERC20(_suni_du);
        tokens[_sdeus] = ERC20(_sdeus);
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
    }

    /**
     *@notice swap base token to quote token
     * @param tokenIn address of the base token
     * @param tokenOut address of the quote token
     * @param tokenAmountIn amount of the base token
     * @param incrementalPercentage The incremental percentage
     */
    function _trade(address tokenIn, address tokenOut, uint256 tokenAmountIn, uint256 incrementalPercentage)
        internal
    {
        uint maxPrice = bPool.getSpotPrice(tokenIn, tokenOut).mul(incrementalPercentage).div(100);
        uint minTokenAmountOut = tokenAmountIn.div(maxPrice);
        tokens[tokenIn].approve(address(bPool), tokenAmountIn);
        emit Swapped(tokenIn, tokenOut, tokenAmountIn, minTokenAmountOut);
        bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, minTokenAmountOut, maxPrice);
    }

    /**
     *@notice create a batch transaction to swap multiple pairs on the Balancer pool to balance the pool
     * @param tokensIn The list of the base tokens
     * @param tokensOut The list of the quote tokens
     * @param tokensAmountIn The amount of list of the base tokens
     * @param incrementalPercentages list of the incremental percentage
     */
    function makeBalance(
        address[] memory tokensIn,
        address[] memory tokensOut,
        uint256[] memory tokensAmountIn,
        uint256[] memory incrementalPercentages
    )
        external
    {
        require(hasRole(OPERATOR_ROLE, _msgSender()), UNAUTHORIZED_USER);

        for (uint8 i = 0; i < tokensIn.length; i++) {
            _trade(tokensIn[i], tokensOut[i], tokensAmountIn[i], incrementalPercentages[i]);
        }
    }

    /**
     * @notice get the the pair's current price
     * @param tokenIn address of the base token
     * @param tokenOut address of the quote token
     */
    function getSpotPrice(address tokenIn, address tokenOut)
        external
        view
        returns(uint)
    {
        return bPool.getSpotPrice(tokenIn, tokenOut);
    }

    /**
     * @notice Set a safe address where reclaimed tokens will go
     * @param _safeAccount a safe address where reclaimed tokens will go
     */
    function setSafeAccount(address _safeAccount)
        external
    {
        require(hasRole(WITHDRAWAL_ROLE, _msgSender()), UNAUTHORIZED_USER);

        safeAccount = _safeAccount;
        emit SafeAccountSet(_safeAccount);
    }

    /**
    * @notice Reclaim tokens of the specified type sent to the safe account
     * @param token The token address
     * @param amount Withdrawal amount
     */
    function reclaimTokens(address token, uint256 amount)
        external
    {
        require(hasRole(WITHDRAWAL_ROLE, _msgSender()), UNAUTHORIZED_USER);

        require(safeAccount != address(0), UNSET_SAFE_ACCOUNT);

        require(tokens[token].transfer(safeAccount, amount), TRANSFER_ERROR);
        emit ReclaimedToken(token, amount, safeAccount);
    }

    /**
    * @notice Update pool tokens
     * @param token The new token address
     */
    function updateTokens(address token)
        external
    {
        require(hasRole(WITHDRAWAL_ROLE, _msgSender()), UNAUTHORIZED_USER);

        require(token != address(0), INVALID_ADDRESS);
        tokens[token] = ERC20(token);

        emit TokensUpdated(token);
    }
}
/**
 * @title Arbitrage bot contract
 */
function _trade(address tokenIn, address tokenOut, uint256 tokenAmountIn, uint256 incrementalPercentage)
    internal
{
    uint maxPrice = bPool.getSpotPrice(tokenIn, tokenOut).mul(incrementalPercentage).div(100);
    uint minTokenAmountOut = tokenAmountIn.div(maxPrice);
    tokens[tokenIn].approve(address(bPool), tokenAmountIn);
    emit Swapped(tokenIn, tokenOut, tokenAmountIn, minTokenAmountOut);
    bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, minTokenAmountOut, maxPrice);
}

/**
 *@notice swap base token to quote token
 * @param tokenIn address of the base token
 * @param tokenOut address of the quote token
 * @param tokenAmountIn amount of the base token
 * @param incrementalPercentage The incremental percentage
 */