Skip to content

Commit 0eb0519

Browse files
committed
Add manage_flow to superfluid.py
1 parent 5fd68b4 commit 0eb0519

File tree

5 files changed

+101
-7
lines changed

5 files changed

+101
-7
lines changed

src/aleph/sdk/chains/ethereum.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@
2222
BALANCEOF_ABI,
2323
MIN_ETH_BALANCE,
2424
MIN_ETH_BALANCE_WEI,
25+
FlowUpdate,
26+
from_wei_token,
2527
get_chain_id,
2628
get_chains_with_super_token,
2729
get_rpc,
2830
get_super_token_address,
2931
get_token_address,
30-
to_human_readable_token,
3132
)
3233
from ..exceptions import BadSignatureError
3334
from ..utils import bytes_from_hex
@@ -107,7 +108,7 @@ def can_transact(self, block=True) -> bool:
107108
if not valid and block:
108109
raise InsufficientFundsError(
109110
required_funds=MIN_ETH_BALANCE,
110-
available_funds=to_human_readable_token(balance),
111+
available_funds=float(from_wei_token(balance)),
111112
)
112113
return valid
113114

@@ -188,6 +189,19 @@ def delete_flow(self, receiver: str) -> Awaitable[str]:
188189
raise ValueError("Superfluid connector is required to delete a flow")
189190
return self.superfluid_connector.delete_flow(receiver=receiver)
190191

192+
def manage_flow(
193+
self,
194+
receiver: str,
195+
flow: Decimal,
196+
update_type: FlowUpdate,
197+
) -> Awaitable[Optional[str]]:
198+
"""Manage the Superfluid flow between this account and the receiver address."""
199+
if not self.superfluid_connector:
200+
raise ValueError("Superfluid connector is required to manage a flow")
201+
return self.superfluid_connector.manage_flow(
202+
receiver=receiver, flow=flow, update_type=update_type
203+
)
204+
191205

192206
def get_fallback_account(
193207
path: Optional[Path] = None, chain: Optional[Chain] = None

src/aleph/sdk/chains/evm.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from aleph_message.models import Chain
66
from eth_account import Account # type: ignore
77

8+
from ..evm_utils import FlowUpdate
89
from .common import get_fallback_private_key
910
from .ethereum import ETHAccount
1011

@@ -41,6 +42,11 @@ def update_flow(self, receiver: str, flow: Decimal) -> Awaitable[str]:
4142
def delete_flow(self, receiver: str) -> Awaitable[str]:
4243
raise ValueError(f"Flow deletion not implemented for this chain {self.CHAIN}")
4344

45+
def manage_flow(
46+
self, receiver: str, flow: Decimal, update_type: FlowUpdate
47+
) -> Awaitable[Optional[str]]:
48+
raise ValueError(f"Flow management not implemented for this chain {self.CHAIN}")
49+
4450

4551
def get_fallback_account(
4652
path: Optional[Path] = None, chain: Optional[Chain] = None

src/aleph/sdk/connectors/superfluid.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
from __future__ import annotations
22

33
from decimal import Decimal
4-
from typing import TYPE_CHECKING
4+
from typing import TYPE_CHECKING, Optional
55

66
from eth_utils import to_normalized_address
77
from superfluid import CFA_V1, Operation, Web3FlowInfo
88

99
from aleph.sdk.exceptions import InsufficientFundsError
1010

11-
from ..evm_utils import get_super_token_address, to_human_readable_token, to_wei_token
11+
from ..evm_utils import (
12+
FlowUpdate,
13+
from_wei_token,
14+
get_super_token_address,
15+
to_wei_token,
16+
)
1217

1318
if TYPE_CHECKING:
1419
from aleph.sdk.chains.ethereum import ETHAccount
@@ -52,7 +57,7 @@ def can_start_flow(self, flow: Decimal, block=True) -> bool:
5257
if not valid and block:
5358
raise InsufficientFundsError(
5459
required_funds=float(MIN_FLOW_4H),
55-
available_funds=to_human_readable_token(balance),
60+
available_funds=float(from_wei_token(balance)),
5661
)
5762
return valid
5863

@@ -96,3 +101,51 @@ async def update_flow(self, receiver: str, flow: Decimal) -> str:
96101
flow_rate=int(to_wei_token(flow)),
97102
),
98103
)
104+
105+
async def manage_flow(
106+
self,
107+
receiver: str,
108+
flow: Decimal,
109+
update_type: FlowUpdate,
110+
) -> Optional[str]:
111+
"""
112+
Update the flow of a Superfluid stream between a sender and receiver.
113+
This function either increases or decreases the flow rate between the sender and receiver,
114+
based on the update_type. If no flow exists and the update type is augmentation, it creates a new flow
115+
with the specified rate. If the update type is reduction and the reduction amount brings the flow to zero
116+
or below, the flow is deleted.
117+
118+
:param receiver: Address of the receiver in hexadecimal format.
119+
:param flow: The flow rate to be added or removed (in ether).
120+
:param update_type: The type of update to perform (augmentation or reduction).
121+
:return: The transaction hash of the executed operation (create, update, or delete flow).
122+
"""
123+
124+
# Retrieve current flow info
125+
flow_info: Web3FlowInfo = await self.account.get_flow(receiver)
126+
127+
current_flow_rate_wei: Decimal = Decimal(flow_info["flowRate"] or "0")
128+
flow_rate_wei: int = int(to_wei_token(flow))
129+
130+
if update_type == FlowUpdate.INCREASE:
131+
if current_flow_rate_wei > 0:
132+
# Update existing flow by increasing the rate
133+
new_flow_rate_wei = current_flow_rate_wei + flow_rate_wei
134+
new_flow_rate_ether = from_wei_token(new_flow_rate_wei)
135+
return await self.account.update_flow(receiver, new_flow_rate_ether)
136+
else:
137+
# Create a new flow if none exists
138+
return await self.account.create_flow(receiver, flow)
139+
else:
140+
if current_flow_rate_wei > 0:
141+
# Reduce the existing flow
142+
new_flow_rate_wei = current_flow_rate_wei - flow_rate_wei
143+
# Ensure to not leave infinitesimal flows
144+
# Often, there were 1-10 wei remaining in the flow rate, which prevented the flow from being deleted
145+
if new_flow_rate_wei > 99:
146+
new_flow_rate_ether = from_wei_token(new_flow_rate_wei)
147+
return await self.account.update_flow(receiver, new_flow_rate_ether)
148+
else:
149+
# Delete the flow if the new flow rate is zero or negative
150+
return await self.account.delete_flow(receiver)
151+
return None

src/aleph/sdk/evm_utils.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from decimal import Decimal
2+
from enum import Enum
23
from typing import List, Optional, Union
34

45
from aleph_message.models import Chain
@@ -21,11 +22,18 @@
2122
}]"""
2223

2324

24-
def to_human_readable_token(amount: Decimal) -> float:
25-
return float(amount / (Decimal(10) ** Decimal(settings.TOKEN_DECIMALS)))
25+
class FlowUpdate(str, Enum):
26+
REDUCE = "reduce"
27+
INCREASE = "increase"
28+
29+
30+
def from_wei_token(amount: Decimal) -> Decimal:
31+
"""Converts the given wei value to ether."""
32+
return amount / Decimal(10) ** Decimal(settings.TOKEN_DECIMALS)
2633

2734

2835
def to_wei_token(amount: Decimal) -> Decimal:
36+
"""Converts the given ether value to wei."""
2937
return amount * Decimal(10) ** Decimal(settings.TOKEN_DECIMALS)
3038

3139

tests/unit/test_superfluid.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from eth_utils import to_checksum_address
88

99
from aleph.sdk.chains.ethereum import ETHAccount
10+
from aleph.sdk.evm_utils import FlowUpdate
1011

1112

1213
def generate_fake_eth_address():
@@ -24,6 +25,7 @@ def mock_superfluid():
2425
mock_superfluid.create_flow = AsyncMock(return_value="0xTransactionHash")
2526
mock_superfluid.delete_flow = AsyncMock(return_value="0xTransactionHash")
2627
mock_superfluid.update_flow = AsyncMock(return_value="0xTransactionHash")
28+
mock_superfluid.manage_flow = AsyncMock(return_value="0xTransactionHash")
2729

2830
# Mock get_flow to return a mock Web3FlowInfo
2931
mock_flow_info = {"timestamp": 0, "flowRate": 0, "deposit": 0, "owedDeposit": 0}
@@ -98,3 +100,14 @@ async def test_get_flow(eth_account, mock_superfluid):
98100
assert flow_info["flowRate"] == 0
99101
assert flow_info["deposit"] == 0
100102
assert flow_info["owedDeposit"] == 0
103+
104+
105+
@pytest.mark.asyncio
106+
async def test_manage_flow(eth_account, mock_superfluid):
107+
receiver = generate_fake_eth_address()
108+
flow = Decimal("0.005")
109+
110+
tx_hash = await eth_account.manage_flow(receiver, flow, FlowUpdate.INCREASE)
111+
112+
assert tx_hash == "0xTransactionHash"
113+
mock_superfluid.manage_flow.assert_awaited_once()

0 commit comments

Comments
 (0)