library BoringERC20 {
	bytes4 private constant SIG_SYMBOL = 0x95d89b41; // symbol()
	bytes4 private constant SIG_NAME = 0x06fdde03; // name()
	bytes4 private constant SIG_DECIMALS = 0x313ce567; // decimals()
	bytes4 private constant SIG_TRANSFER = 0xa9059cbb; // transfer(address,uint256)
	bytes4 private constant SIG_TRANSFER_FROM = 0x23b872dd; // transferFrom(address,address,uint256)

	function returnDataToString(bytes memory data) internal pure returns (string memory) {
		if (data.length >= 64) {
			return abi.decode(data, (string));
		} else if (data.length == 32) {
			uint8 i = 0;
			while (i < 32 && data[i] != 0) {
				i++;
			}
			bytes memory bytesArray = new bytes(i);
			for (i = 0; i < 32 && data[i] != 0; i++) {
				bytesArray[i] = data[i];
			}
			return string(bytesArray);
		} else {
			return "???";
		}
	}

	/// @notice Provides a safe ERC20.symbol version which returns '???' as fallback string.
	/// @param token The address of the ERC-20 token contract.
	/// @return (string) Token symbol.
	function safeSymbol(IERC20 token) internal view returns (string memory) {
		(bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_SYMBOL));
		return success ? returnDataToString(data) : "???";
	}

	/// @notice Provides a safe ERC20.name version which returns '???' as fallback string.
	/// @param token The address of the ERC-20 token contract.
	/// @return (string) Token name.
	function safeName(IERC20 token) internal view returns (string memory) {
		(bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_NAME));
		return success ? returnDataToString(data) : "???";
	}

	/// @notice Provides a safe ERC20.decimals version which returns '18' as fallback value.
	/// @param token The address of the ERC-20 token contract.
	/// @return (uint8) Token decimals.
	function safeDecimals(IERC20 token) internal view returns (uint8) {
		(bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_DECIMALS));
		return success && data.length == 32 ? abi.decode(data, (uint8)) : 18;
	}

	/// @notice Provides a safe ERC20.transfer version for different ERC-20 implementations.
	/// Reverts on a failed transfer.
	/// @param token The address of the ERC-20 token.
	/// @param to Transfer tokens to.
	/// @param amount The token amount.
	function safeTransfer(
		IERC20 token,
		address to,
		uint256 amount
	) internal {
		(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(SIG_TRANSFER, to, amount));
		require(success && (data.length == 0 || abi.decode(data, (bool))), "BoringERC20::safeTransfer: transfer failed");
	}

	/// @notice Provides a safe ERC20.transferFrom version for different ERC-20 implementations.
	/// Reverts on a failed transfer.
	/// @param token The address of the ERC-20 token.
	/// @param from Transfer tokens from.
	/// @param to Transfer tokens to.
	/// @param amount The token amount.
	function safeTransferFrom(
		IERC20 token,
		address from,
		address to,
		uint256 amount
	) internal {
		(bool success, bytes memory data) =
			address(token).call(abi.encodeWithSelector(SIG_TRANSFER_FROM, from, to, amount));
		require(
			success && (data.length == 0 || abi.decode(data, (bool))),
			"BoringERC20::safeTransferFrom: transfer failed"
		);
	}
}
// solhint-disable avoid-low-level-calls
function safeTransferFrom(
	IERC20 token,
	address from,
	address to,
	uint256 amount
) internal {
	(bool success, bytes memory data) =
		address(token).call(abi.encodeWithSelector(SIG_TRANSFER_FROM, from, to, amount));
	require(
		success && (data.length == 0 || abi.decode(data, (bool))),
		"BoringERC20::safeTransferFrom: transfer failed"
	);
}

/// @notice Provides a safe ERC20.transferFrom version for different ERC-20 implementations.
/// Reverts on a failed transfer.
/// @param token The address of the ERC-20 token.
/// @param from Transfer tokens from.
/// @param to Transfer tokens to.
/// @param amount The token amount.