Repository: Cog-Creators/Red-DiscordBot

Issue:
[V3 Bank] bank.withdraw/deposit_credits breaks mongodb with floats
# Other bugs

If you give bank.deposit_credits or bank.withdraw_credit a float instead of an int while using MongoDB, the users balance is stuck as a double. Because of this many cogs break because they expect an int, but get a float.

#### What were you trying to do?

I was trying to withdraw credits from a users account with

`await bank.withdraw_credits(ctx.author, X * 0.30)`

although due to a bug I accidentaly ran

```
await bank.withdraw_credits(ctx.author, X * 0.30)
await bank.deposit_credits(ctx.author, X * 0.30)
````

This is most likely unrelated to the bug, but I thought I should mention it anyway.

#### What were you expecting to happen?

I expected red to either throw an error or convert the float to an int 

#### What actually happened?

Red carried on, as if the value was an int. It changed the value type of the balance in mongodb from an int32 to a double. This breaks many other cogs because they expect an int but get a double.

#### How can we reproduce this issue?

1. run the following command

```
await bank.withdraw_credits(ctx.author, X * 0.30)
await bank.deposit_credits(ctx.author, X * 0.30)
```

2. done


Discussion:
Hi! Thanks for reporting this issue. In my opinion it is best to throw an error when a float is passed to `withdraw/deposit_credits`, since a float value for credits doesn't really make any sense. We could implicitly convert it to int as you say, but I don't think it's hard for the cog creator to do that instead, and it saves having to account for implicit behaviour.

Resolving patch:
diff --git a/redbot/core/bank.py b/redbot/core/bank.py
--- a/redbot/core/bank.py
+++ b/redbot/core/bank.py
@@ -214,8 +214,12 @@ async def withdraw_credits(member: discord.Member, amount: int) -> int:
     ValueError
         If the withdrawal amount is invalid or if the account has insufficient
         funds.
+    TypeError
+        If the withdrawal amount is not an `int`.
 
     """
+    if not isinstance(amount, int):
+        raise TypeError("Withdrawal amount must be of type int, not {}.".format(type(amount)))
     if _invalid_amount(amount):
         raise ValueError("Invalid withdrawal amount {} < 0".format(amount))
 
@@ -245,8 +249,12 @@ async def deposit_credits(member: discord.Member, amount: int) -> int:
     ------
     ValueError
         If the deposit amount is invalid.
+    TypeError
+        If the deposit amount is not an `int`.
 
     """
+    if not isinstance(amount, int):
+        raise TypeError("Deposit amount must be of type int, not {}.".format(type(amount)))
     if _invalid_amount(amount):
         raise ValueError("Invalid deposit amount {} <= 0".format(amount))
 
@@ -269,14 +277,18 @@ async def transfer_credits(from_: discord.Member, to: discord.Member, amount: in
     Returns
     -------
     int
-        The new balance.
+        The new balance of the member gaining credits.
 
     Raises
     ------
     ValueError
         If the amount is invalid or if ``from_`` has insufficient funds.
+    TypeError
+        If the amount is not an `int`.
 
     """
+    if not isinstance(amount, int):
+        raise TypeError("Transfer amount must be of type int, not {}.".format(type(amount)))
     if _invalid_amount(amount):
         raise ValueError("Invalid transfer amount {} <= 0".format(amount))
 


Test patch:
diff --git a/tests/cogs/test_economy.py b/tests/cogs/test_economy.py
--- a/tests/cogs/test_economy.py
+++ b/tests/cogs/test_economy.py
@@ -69,3 +69,15 @@ async def test_set_default_balance(bank, guild_factory):
     await bank.set_default_balance(500, guild)
     default_bal = await bank.get_default_balance(guild)
     assert default_bal == 500
+
+
+@pytest.mark.asyncio
+async def test_nonint_transaction_amount(bank, member_factory):
+    mbr1 = member_factory.get()
+    mbr2 = member_factory.get()
+    with pytest.raises(TypeError):
+        await bank.deposit_credits(mbr1, 1.0)
+    with pytest.raises(TypeError):
+        await bank.withdraw_credits(mbr1, 1.0)
+    with pytest.raises(TypeError):
+        await bank.transfer_credits(mbr1, mbr2, 1.0)