Skip to content

Commit 3859e0c

Browse files
authored
Feature fix base unit conversion (#94)
* Fix conversions to BaseUnit * Updates
1 parent 2221d3b commit 3859e0c

File tree

3 files changed

+192
-5
lines changed

3 files changed

+192
-5
lines changed

alphaswarm/core/token.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import NewType, Union
55

66
from eth_typing import ChecksumAddress
7-
from pydantic.dataclasses import dataclass
7+
from pydantic import BaseModel
88
from web3 import Web3
99
from web3.types import Wei
1010

@@ -55,16 +55,15 @@ def base_units(self) -> BaseUnit:
5555
return self.token_info.convert_to_base_units(self.value)
5656

5757

58-
@dataclass
59-
class TokenInfo:
58+
class TokenInfo(BaseModel):
6059
symbol: str
6160
address: str
6261
decimals: int
6362
chain: str
6463
is_native: bool = False
6564

6665
def convert_to_base_units(self, amount: Decimal) -> BaseUnit:
67-
return BaseUnit(amount * (10**self.decimals))
66+
return BaseUnit(int(amount * (10**self.decimals)))
6867

6968
def convert_from_base_units(self, amount: Union[BaseUnit, Wei]) -> Decimal:
7069
return Decimal(amount) / (10**self.decimals)

alphaswarm/services/exchanges/jupiter/jupiter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class JupiterQuote(BaseModel):
4141

4242
@property
4343
def out_amount(self) -> BaseUnit:
44-
return BaseUnit(self.quote["outAmount"])
44+
return BaseUnit(int(self.quote["outAmount"]))
4545

4646

4747
class JupiterSwapTransaction:

tests/unit/core/test_token.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
from decimal import Decimal
2+
3+
import pytest
4+
from web3.types import Wei
5+
6+
from alphaswarm.core.token import BaseUnit, TokenAmount, TokenInfo
7+
8+
9+
@pytest.fixture
10+
def eth_token() -> TokenInfo:
11+
return TokenInfo(
12+
symbol="ETH",
13+
address="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
14+
decimals=18,
15+
chain="ethereum",
16+
is_native=True,
17+
)
18+
19+
20+
@pytest.fixture
21+
def usdc_token() -> TokenInfo:
22+
return TokenInfo(symbol="USDC", address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", decimals=6, chain="ethereum")
23+
24+
25+
def test_token_amount_init(eth_token: TokenInfo) -> None:
26+
amount = TokenAmount(eth_token, Decimal("1.5"))
27+
assert amount.token_info == eth_token
28+
assert amount.value == Decimal("1.5")
29+
30+
31+
def test_token_amount_is_zero(eth_token: TokenInfo) -> None:
32+
zero_amount = TokenAmount(eth_token, Decimal("0"))
33+
non_zero_amount = TokenAmount(eth_token, Decimal("1.5"))
34+
35+
assert zero_amount.is_zero is True
36+
assert non_zero_amount.is_zero is False
37+
38+
39+
def test_token_amount_str_formatting(eth_token: TokenInfo) -> None:
40+
amount = TokenAmount(eth_token, Decimal("1.23456789"))
41+
assert str(amount) == "1.23456789 ETH"
42+
43+
large_amount = TokenAmount(eth_token, Decimal("1234567.89"))
44+
assert str(large_amount) == "1,234,567.89000000 ETH"
45+
46+
47+
def test_token_amount_equality(eth_token: TokenInfo, usdc_token: TokenInfo) -> None:
48+
amount1 = TokenAmount(eth_token, Decimal("1.5"))
49+
amount2 = TokenAmount(eth_token, Decimal("1.5"))
50+
different_token = TokenAmount(usdc_token, Decimal("1.5"))
51+
different_amount = TokenAmount(eth_token, Decimal("2.0"))
52+
53+
assert amount1 == amount2
54+
assert amount1 != different_token
55+
assert amount1 != different_amount
56+
assert amount1 != "not a token amount"
57+
58+
59+
def test_token_amount_comparison(eth_token: TokenInfo) -> None:
60+
amount1 = TokenAmount(eth_token, Decimal("1.5"))
61+
amount2 = TokenAmount(eth_token, Decimal("2.0"))
62+
amount3 = TokenAmount(eth_token, Decimal("1.5"))
63+
64+
assert amount1 < amount2
65+
assert amount1 <= amount2
66+
assert amount2 > amount1
67+
assert amount2 >= amount1
68+
assert amount1 <= amount3
69+
assert amount1 >= amount3
70+
71+
72+
def test_token_amount_comparison_different_tokens(eth_token: TokenInfo, usdc_token: TokenInfo) -> None:
73+
eth_amount = TokenAmount(eth_token, Decimal("1.5"))
74+
usdc_amount = TokenAmount(usdc_token, Decimal("1.5"))
75+
76+
with pytest.raises(ValueError, match="Cannot compare different tokens"):
77+
_ = eth_amount < usdc_amount
78+
79+
with pytest.raises(ValueError, match="Cannot compare different tokens"):
80+
_ = eth_amount > usdc_amount
81+
82+
83+
def test_token_amount_base_units(eth_token: TokenInfo, usdc_token: TokenInfo) -> None:
84+
# Test ETH (18 decimals)
85+
eth_amount = TokenAmount(eth_token, Decimal("1.5"))
86+
assert eth_amount.base_units == BaseUnit(1500000000000000000)
87+
88+
# Test USDC (6 decimals)
89+
usdc_amount = TokenAmount(usdc_token, Decimal("1.5"))
90+
assert usdc_amount.base_units == BaseUnit(1500000)
91+
92+
93+
def test_token_info_convert_base_units(eth_token: TokenInfo) -> None:
94+
# Test converting to base units
95+
base_units = eth_token.convert_to_base_units(Decimal("1.5"))
96+
assert base_units == BaseUnit(1500000000000000000)
97+
98+
# Test converting from base units
99+
wei_amount = Wei(1500000000000000000)
100+
decimal_amount = eth_token.convert_from_base_units(wei_amount)
101+
assert decimal_amount == Decimal("1.5")
102+
103+
104+
def test_token_info_amount_creation(eth_token: TokenInfo) -> None:
105+
# Test creating amount from decimal
106+
amount1 = eth_token.to_amount(Decimal("1.5"))
107+
assert amount1.value == Decimal("1.5")
108+
assert amount1.token_info == eth_token
109+
110+
# Test creating zero amount
111+
zero_amount = eth_token.to_zero_amount()
112+
assert zero_amount.value == Decimal("0")
113+
assert zero_amount.is_zero is True
114+
115+
# Test creating amount from base units
116+
base_amount = eth_token.to_amount_from_base_units(Wei(1500000000000000000))
117+
assert base_amount.value == Decimal("1.5")
118+
119+
120+
def test_token_info_equality(eth_token: TokenInfo) -> None:
121+
same_token = TokenInfo(symbol="ETH", address=eth_token.address, decimals=18, chain="ethereum", is_native=True)
122+
different_chain = TokenInfo(symbol="ETH", address=eth_token.address, decimals=18, chain="base", is_native=True)
123+
different_address = TokenInfo(
124+
symbol="ETH",
125+
address="0x1234567890123456789012345678901234567890",
126+
decimals=18,
127+
chain="ethereum",
128+
is_native=True,
129+
)
130+
131+
assert eth_token == same_token
132+
assert eth_token != different_chain
133+
assert eth_token != different_address
134+
assert eth_token != "not a token info"
135+
136+
137+
def test_token_info_ethereum_factory() -> None:
138+
eth = TokenInfo.Ethereum()
139+
assert eth.symbol == "ETH"
140+
assert eth.decimals == 18
141+
assert eth.is_native is True
142+
assert eth.chain == "ethereum"
143+
assert eth.address == ""
144+
145+
146+
def test_token_info_address_to_path(eth_token: TokenInfo, usdc_token: TokenInfo) -> None:
147+
# Test normal address
148+
assert usdc_token.address_to_path() == "A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
149+
150+
# Test address with 0x prefix
151+
token = TokenInfo(symbol="TEST", address="0x1234", decimals=18, chain="ethereum")
152+
assert token.address_to_path() == "0000000000000000000000000000000000001234"
153+
154+
# Test empty address
155+
empty_addr_token = TokenInfo(symbol="TEST", address="", decimals=18, chain="ethereum")
156+
assert empty_addr_token.address_to_path() == "0" * 40
157+
158+
159+
def test_token_info_checksum_address(usdc_token: TokenInfo) -> None:
160+
# Test converting to checksum address
161+
assert usdc_token.checksum_address == "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
162+
163+
# Test with lowercase address
164+
token = TokenInfo(
165+
symbol="TEST", address="0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", decimals=18, chain="ethereum"
166+
)
167+
assert token.checksum_address == "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
168+
169+
170+
def test_token_info_validation() -> None:
171+
# Test valid token creation
172+
token = TokenInfo(
173+
symbol="TEST", address="0x1234567890123456789012345678901234567890", decimals=18, chain="ethereum"
174+
)
175+
assert token.symbol == "TEST"
176+
assert token.decimals == 18
177+
178+
# Test with minimum required fields
179+
min_token = TokenInfo(symbol="MIN", address="", decimals=6, chain="ethereum")
180+
assert min_token.is_native is False
181+
182+
183+
def test_token_info_str_representation(eth_token: TokenInfo) -> None:
184+
# Test string representation includes key information
185+
token_str = str(eth_token)
186+
assert eth_token.symbol in token_str
187+
assert eth_token.address in token_str
188+
assert eth_token.chain in token_str

0 commit comments

Comments
 (0)