Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes for new pricing system #199

Merged
merged 16 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/aleph/sdk/chains/ethereum.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
BALANCEOF_ABI,
MIN_ETH_BALANCE,
MIN_ETH_BALANCE_WEI,
FlowUpdate,
from_wei_token,
get_chain_id,
get_chains_with_super_token,
get_rpc,
get_super_token_address,
get_token_address,
to_human_readable_token,
)
from ..exceptions import BadSignatureError
from ..utils import bytes_from_hex
Expand Down Expand Up @@ -107,7 +108,7 @@ def can_transact(self, block=True) -> bool:
if not valid and block:
raise InsufficientFundsError(
required_funds=MIN_ETH_BALANCE,
available_funds=to_human_readable_token(balance),
available_funds=float(from_wei_token(balance)),
)
return valid

Expand Down Expand Up @@ -162,6 +163,12 @@ def get_super_token_balance(self) -> Decimal:
return Decimal(contract.functions.balanceOf(self.get_address()).call())
return Decimal(0)

def can_start_flow(self, flow: Decimal) -> bool:
"""Check if the account has enough funds to start a Superfluid flow of the given size."""
if not self.superfluid_connector:
raise ValueError("Superfluid connector is required to check a flow")
return self.superfluid_connector.can_start_flow(flow)

def create_flow(self, receiver: str, flow: Decimal) -> Awaitable[str]:
"""Creat a Superfluid flow between this account and the receiver address."""
if not self.superfluid_connector:
Expand All @@ -188,6 +195,19 @@ def delete_flow(self, receiver: str) -> Awaitable[str]:
raise ValueError("Superfluid connector is required to delete a flow")
return self.superfluid_connector.delete_flow(receiver=receiver)

def manage_flow(
self,
receiver: str,
flow: Decimal,
update_type: FlowUpdate,
) -> Awaitable[Optional[str]]:
"""Manage the Superfluid flow between this account and the receiver address."""
if not self.superfluid_connector:
raise ValueError("Superfluid connector is required to manage a flow")
return self.superfluid_connector.manage_flow(
receiver=receiver, flow=flow, update_type=update_type
)


def get_fallback_account(
path: Optional[Path] = None, chain: Optional[Chain] = None
Expand Down
9 changes: 9 additions & 0 deletions src/aleph/sdk/chains/evm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from aleph_message.models import Chain
from eth_account import Account # type: ignore

from ..evm_utils import FlowUpdate
from .common import get_fallback_private_key
from .ethereum import ETHAccount

Expand All @@ -29,6 +30,9 @@ def get_token_balance(self) -> Decimal:
def get_super_token_balance(self) -> Decimal:
raise ValueError(f"Super token not implemented for this chain {self.CHAIN}")

def can_start_flow(self, flow: Decimal) -> bool:
raise ValueError(f"Flow checking not implemented for this chain {self.CHAIN}")

def create_flow(self, receiver: str, flow: Decimal) -> Awaitable[str]:
raise ValueError(f"Flow creation not implemented for this chain {self.CHAIN}")

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

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


def get_fallback_account(
path: Optional[Path] = None, chain: Optional[Chain] = None
Expand Down
60 changes: 57 additions & 3 deletions src/aleph/sdk/connectors/superfluid.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from __future__ import annotations

from decimal import Decimal
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional

from eth_utils import to_normalized_address
from superfluid import CFA_V1, Operation, Web3FlowInfo

from aleph.sdk.exceptions import InsufficientFundsError

from ..evm_utils import get_super_token_address, to_human_readable_token, to_wei_token
from ..evm_utils import (
FlowUpdate,
from_wei_token,
get_super_token_address,
to_wei_token,
)

if TYPE_CHECKING:
from aleph.sdk.chains.ethereum import ETHAccount
Expand Down Expand Up @@ -44,6 +49,7 @@ async def _execute_operation_with_account(self, operation: Operation) -> str:
return await self.account._sign_and_send_transaction(populated_transaction)

def can_start_flow(self, flow: Decimal, block=True) -> bool:
"""Check if the account has enough funds to start a Superfluid flow of the given size."""
valid = False
if self.account.can_transact(block=block):
balance = self.account.get_super_token_balance()
Expand All @@ -52,7 +58,7 @@ def can_start_flow(self, flow: Decimal, block=True) -> bool:
if not valid and block:
raise InsufficientFundsError(
required_funds=float(MIN_FLOW_4H),
available_funds=to_human_readable_token(balance),
available_funds=float(from_wei_token(balance)),
)
return valid

Expand Down Expand Up @@ -96,3 +102,51 @@ async def update_flow(self, receiver: str, flow: Decimal) -> str:
flow_rate=int(to_wei_token(flow)),
),
)

async def manage_flow(
self,
receiver: str,
flow: Decimal,
update_type: FlowUpdate,
) -> Optional[str]:
"""
Update the flow of a Superfluid stream between a sender and receiver.
This function either increases or decreases the flow rate between the sender and receiver,
based on the update_type. If no flow exists and the update type is augmentation, it creates a new flow
with the specified rate. If the update type is reduction and the reduction amount brings the flow to zero
or below, the flow is deleted.

:param receiver: Address of the receiver in hexadecimal format.
:param flow: The flow rate to be added or removed (in ether).
:param update_type: The type of update to perform (augmentation or reduction).
:return: The transaction hash of the executed operation (create, update, or delete flow).
"""

# Retrieve current flow info
flow_info: Web3FlowInfo = await self.account.get_flow(receiver)

current_flow_rate_wei: Decimal = Decimal(flow_info["flowRate"] or 0)
flow_rate_wei: int = int(to_wei_token(flow))

if update_type == FlowUpdate.INCREASE:
if current_flow_rate_wei > 0:
# Update existing flow by increasing the rate
new_flow_rate_wei = current_flow_rate_wei + flow_rate_wei
new_flow_rate_ether = from_wei_token(new_flow_rate_wei)
return await self.account.update_flow(receiver, new_flow_rate_ether)
else:
# Create a new flow if none exists
return await self.account.create_flow(receiver, flow)
else:
if current_flow_rate_wei > 0:
# Reduce the existing flow
new_flow_rate_wei = current_flow_rate_wei - flow_rate_wei
# Ensure to not leave infinitesimal flows
# Often, there were 1-10 wei remaining in the flow rate, which prevented the flow from being deleted
if new_flow_rate_wei > 99:
new_flow_rate_ether = from_wei_token(new_flow_rate_wei)
return await self.account.update_flow(receiver, new_flow_rate_ether)
else:
# Delete the flow if the new flow rate is zero or negative
return await self.account.delete_flow(receiver)
return None
12 changes: 10 additions & 2 deletions src/aleph/sdk/evm_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from decimal import Decimal
from enum import Enum
from typing import List, Optional, Union

from aleph_message.models import Chain
Expand All @@ -21,11 +22,18 @@
}]"""


def to_human_readable_token(amount: Decimal) -> float:
return float(amount / (Decimal(10) ** Decimal(settings.TOKEN_DECIMALS)))
class FlowUpdate(str, Enum):
REDUCE = "reduce"
INCREASE = "increase"


def from_wei_token(amount: Decimal) -> Decimal:
"""Converts the given wei value to ether."""
return amount / Decimal(10) ** Decimal(settings.TOKEN_DECIMALS)


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


Expand Down
13 changes: 13 additions & 0 deletions tests/unit/test_superfluid.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from eth_utils import to_checksum_address

from aleph.sdk.chains.ethereum import ETHAccount
from aleph.sdk.evm_utils import FlowUpdate


def generate_fake_eth_address():
Expand All @@ -24,6 +25,7 @@ def mock_superfluid():
mock_superfluid.create_flow = AsyncMock(return_value="0xTransactionHash")
mock_superfluid.delete_flow = AsyncMock(return_value="0xTransactionHash")
mock_superfluid.update_flow = AsyncMock(return_value="0xTransactionHash")
mock_superfluid.manage_flow = AsyncMock(return_value="0xTransactionHash")

# Mock get_flow to return a mock Web3FlowInfo
mock_flow_info = {"timestamp": 0, "flowRate": 0, "deposit": 0, "owedDeposit": 0}
Expand Down Expand Up @@ -98,3 +100,14 @@ async def test_get_flow(eth_account, mock_superfluid):
assert flow_info["flowRate"] == 0
assert flow_info["deposit"] == 0
assert flow_info["owedDeposit"] == 0


@pytest.mark.asyncio
async def test_manage_flow(eth_account, mock_superfluid):
receiver = generate_fake_eth_address()
flow = Decimal("0.005")

tx_hash = await eth_account.manage_flow(receiver, flow, FlowUpdate.INCREASE)

assert tx_hash == "0xTransactionHash"
mock_superfluid.manage_flow.assert_awaited_once()
Loading