Skip to content

Commit

Permalink
Fix bug where to_wei didn't handle decimals correctly (#421)
Browse files Browse the repository at this point in the history
* Fix bug where to_wei didn't handle decimals correctly
* Fix pytest github action

Co-authored-by: Calina Cenan <[email protected]>
  • Loading branch information
David Hunt-Mateo and calina-c authored Apr 20, 2022
1 parent 22b2a15 commit b9eb303
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 36 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ jobs:
- name: Run Barge
working-directory: ${{ github.workspace }}/barge
env:
OPERATOR_SERVICE_VERSION: refactor_signatures
CONTRACTS_VERSION: v1.0.0-alpha.28
AQUARIUS_VERSION: refactor_signatures
PROVIDER_VERSION: refactor_signatures
run: |
bash -x start_ocean.sh --no-dashboard 2>&1 --with-rbac --with-provider2 --with-c2d > start_ocean.log &
for i in $(seq 1 150); do
Expand Down
47 changes: 31 additions & 16 deletions ocean_provider/utils/currency.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from decimal import ROUND_DOWN, Context, Decimal
from decimal import ROUND_DOWN, Context, Decimal, localcontext
from typing import Union

from web3.main import Web3
from eth_utils.currency import units

"""The maximum uint256 value."""
MAX_UINT256 = 2**256 - 1
Expand Down Expand Up @@ -32,28 +32,43 @@
MAX_ETHER = Decimal(MAX_WEI).scaleb(-18, context=ETHEREUM_DECIMAL_CONTEXT)


def to_wei(
amount_in_ether: Union[Decimal, str, int], decimals: int = DECIMALS_18
def to_wei(amount_in_ether: Union[Decimal, str, int]) -> int:
return parse_units(amount_in_ether, DECIMALS_18)


def parse_units(
amount: Union[Decimal, str, int], unit_name: Union[str, int] = DECIMALS_18
) -> int:
"""
Convert token amount to wei from ether, quantized to the specified number of decimal places
Convert token amount from a formatted unit to an EVM-compatible integer.
float input is purposfully not supported
"""
amount_in_ether = normalize_and_validate_ether(amount_in_ether)
decimal_places = Decimal(10) ** -abs(decimals)
return Web3.toWei(
amount_in_ether.quantize(decimal_places, context=ETHEREUM_DECIMAL_CONTEXT),
"ether",
num_decimals = (
int(units[unit_name].log10()) if isinstance(unit_name, str) else unit_name
)

decimal_amount = normalize_and_validate_unit(amount, num_decimals)

if decimal_amount == Decimal(0):
return 0

unit_value = Decimal(10) ** num_decimals

with localcontext(ETHEREUM_DECIMAL_CONTEXT):
return int(decimal_amount * unit_value)


def normalize_and_validate_ether(amount_in_ether: Union[Decimal, str, int]) -> Decimal:
def normalize_and_validate_unit(
amount: Union[Decimal, str, int], decimals: int = DECIMALS_18
) -> Decimal:
"""Returns an amount in ether, encoded as a Decimal
Takes Decimal, str, or int as input. Purposefully does not support float."""
if isinstance(amount_in_ether, str) or isinstance(amount_in_ether, int):
amount_in_ether = Decimal(amount_in_ether)
if isinstance(amount, str) or isinstance(amount, int):
amount = Decimal(amount)

if abs(amount_in_ether) > MAX_ETHER:
raise ValueError("Token abs(amount_in_ether) exceeds MAX_ETHER.")
if abs(amount) > Decimal(MAX_WEI).scaleb(
-decimals, context=ETHEREUM_DECIMAL_CONTEXT
):
raise ValueError("Token amount exceeds maximum.")

return amount_in_ether
return amount
6 changes: 3 additions & 3 deletions ocean_provider/utils/provider_fees.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from datetime import datetime
import json
import logging
import os
from datetime import datetime
from typing import Any, Dict, List

from eth_keys import KeyAPI
from eth_keys.backends import NativeECCBackend
from ocean_provider.requests_session import get_requests_session
from ocean_provider.utils.basics import LocalFileAdapter, get_provider_wallet, get_web3
from ocean_provider.utils.currency import to_wei
from ocean_provider.utils.currency import parse_units
from ocean_provider.utils.datatoken import get_datatoken_contract
from ocean_provider.utils.services import Service
from ocean_provider.utils.util import get_compute_environments_endpoint, get_environment
Expand Down Expand Up @@ -107,4 +107,4 @@ def get_provider_fee_amount(valid_until, compute_env, web3, provider_fee_token):
dt = get_datatoken_contract(web3, provider_fee_token)
decimals = dt.caller.decimals()

return to_wei(str(provider_fee_amount), decimals)
return parse_units(str(provider_fee_amount), decimals)
60 changes: 46 additions & 14 deletions ocean_provider/utils/test/test_currency.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@
from decimal import Decimal, localcontext

import pytest

from ocean_provider.utils.currency import (
ETHEREUM_DECIMAL_CONTEXT,
MAX_ETHER,
MAX_WEI,
MIN_ETHER,
MIN_WEI,
parse_units,
to_wei,
)

USDT_DECIMALS = 6
MIN_USDT = Decimal("0.000001")
MAX_USDT = Decimal(MAX_WEI).scaleb(-USDT_DECIMALS, context=ETHEREUM_DECIMAL_CONTEXT)

SEVEN_DECIMALS = 7
MIN_SEVEN = Decimal("0.0000001")
MAX_SEVEN = Decimal(MAX_WEI).scaleb(-SEVEN_DECIMALS, context=ETHEREUM_DECIMAL_CONTEXT)


@pytest.mark.unit
def test_to_wei():
Expand Down Expand Up @@ -47,18 +55,42 @@ def test_to_wei():
to_wei(MAX_ETHER) == MAX_WEI
), "Conversion from maximum ether to maximum wei failed."

with pytest.raises(ValueError):
# Use ETHEREUM_DECIMAL_CONTEXT when performing arithmetic on MAX_ETHER
with localcontext(ETHEREUM_DECIMAL_CONTEXT):
# Use ETHEREUM_DECIMAL_CONTEXT when performing arithmetic on MAX_ETHER
with localcontext(ETHEREUM_DECIMAL_CONTEXT):
with pytest.raises(ValueError):
to_wei(MAX_ETHER + 1)

USDT_DECIMALS = 6
assert (
to_wei("0", USDT_DECIMALS) == 0
), "Zero ether of USDT should equal zero wei of USDT"
assert (
to_wei("0.123456789123456789", USDT_DECIMALS) == 123456000_000000000
), "Conversion from ether to wei using decimals failed"
assert (
to_wei("1.123456789123456789", USDT_DECIMALS) == 1_123456000_000000000
), "Conversion from ether to wei using decimals failed"

@pytest.mark.unit
def test_parse_units():
"""Test the parse_units function"""
assert parse_units("0", USDT_DECIMALS) == 0
assert parse_units("0.123456789123456789", USDT_DECIMALS) == 123456
assert parse_units("1.123456789123456789", USDT_DECIMALS) == 1_123456
assert parse_units("5278.02", USDT_DECIMALS) == 5278_020000
assert parse_units(MIN_USDT, USDT_DECIMALS) == MIN_WEI
assert parse_units(MAX_USDT, USDT_DECIMALS) == MAX_WEI

# Use ETHEREUM_DECIMAL_CONTEXT when performing arithmetic on MAX_USDT
with localcontext(ETHEREUM_DECIMAL_CONTEXT):
with pytest.raises(ValueError):
parse_units(MAX_USDT + 1, USDT_DECIMALS)

assert parse_units("0", "mwei") == 0
assert parse_units("0.123456789123456789", "mwei") == 123456
assert parse_units("1.123456789123456789", "mwei") == 1_123456
assert parse_units("5278.02", "mwei") == 5278_020000
assert parse_units(MIN_USDT, "mwei") == MIN_WEI
assert parse_units(MAX_USDT, "mwei") == MAX_WEI

# Use ETHEREUM_DECIMAL_CONTEXT when performing arithmetic on MAX_USDT
with localcontext(ETHEREUM_DECIMAL_CONTEXT):
with pytest.raises(ValueError):
parse_units(MAX_USDT + 1, "mwei")

assert parse_units("0", SEVEN_DECIMALS) == 0
assert parse_units("0.123456789", SEVEN_DECIMALS) == 1234567
assert parse_units("1.123456789", SEVEN_DECIMALS) == 1_1234567
assert parse_units("5278.02", SEVEN_DECIMALS) == 5278_0200000
assert parse_units(MIN_SEVEN, SEVEN_DECIMALS) == MIN_WEI
assert parse_units(MAX_SEVEN, SEVEN_DECIMALS) == MAX_WEI

0 comments on commit b9eb303

Please sign in to comment.