Skip to content

Commit

Permalink
test,feat!: (1) New events and tx resp abstractions. (2) Cater tests …
Browse files Browse the repository at this point in the history
…to devnet (#180)

* feat: add tests for dex

* test(common): test parse attributes

* feat(pytypes)!: TxResp for easily handling transaction responses
- refactor: move types to pytypes. export all types from nibiru.ptypes module.
- fix: Find a gas config that works better for the E2E tests.
- fix: Get most of the perp tests passing again.

* test: use ueth instead of unibi for pricefeed_test. (2) Bump package dev version

* docs,refactor(network.py): Dynamically load insecure status using the TM endpoint. Add docstring for 'Network' class.

* fix: handle case when the dex pools query returns an empty list

* refactor(common.py): Use dataclass for the TxConfig since it's simpler

* test: use more semantic names + increase pass rate

* (1) dependencies: Use pytest-order to order tests.
- (2) test(dex): Remove uusdc as it does not exist on chain

* test: passing pricefeed tests

* test: passing dex and perp tests

* checkpoint #wip

* test: improve test ordering and separate large websocket test

* test: dex and pricefeed green

* test: fix utils_test.py

* test: add an env var, "USE_LOCALNET" that lets you test against custom
networks

* fix(conftest.py)

* test,docs: (1) Add additional cases for event_test.py (2) More
docstrings

* refactor: address PR comments

Co-authored-by: Matthias <md2022@matrixsystems.co>
  • Loading branch information
Unique-Divine and matthiasmatt authored Dec 9, 2022
1 parent 9f5429e commit 27ff098
Show file tree
Hide file tree
Showing 34 changed files with 1,152 additions and 613 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pytests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,4 @@ jobs:
# run tests
#----------------------------------------------
- name: Run Python SDK tests
run: poetry run pytest -s
run: poetry run pytest -s tests
6 changes: 3 additions & 3 deletions nibiru/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@

ProtobufMessage = google.protobuf.message.Message

import nibiru.common # noqa
import nibiru.msg # noqa
from nibiru.client import GrpcClient # noqa
from nibiru.common import Coin, Direction, PoolAsset, Side, TxConfig, TxType # noqa
import nibiru.pytypes # noqa
from nibiru.grpc_client import GrpcClient # noqa
from nibiru.network import Network # noqa
from nibiru.pytypes import Coin, Direction, PoolAsset, Side, TxConfig, TxType # noqa
from nibiru.sdk import Sdk # noqa
from nibiru.transaction import Transaction # noqa
from nibiru.wallet import Address, PrivateKey, PublicKey # noqa
File renamed without changes.
2 changes: 1 addition & 1 deletion nibiru/msg/bank.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from nibiru_proto.proto.cosmos.distribution.v1beta1 import tx_pb2 as tx_pb
from nibiru_proto.proto.cosmos.staking.v1beta1 import tx_pb2 as staking_pb

from nibiru.common import Coin, PythonMsg
from nibiru.pytypes import Coin, PythonMsg


@dataclasses.dataclass
Expand Down
10 changes: 6 additions & 4 deletions nibiru/msg/dex.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import dataclasses
from typing import List
from typing import List, Union

from nibiru_proto.proto.dex.v1 import pool_pb2 as pool_tx_pb
from nibiru_proto.proto.dex.v1 import tx_pb2 as pb

from nibiru.common import Coin, PoolAsset, PoolType, PythonMsg
from nibiru.pytypes import Coin, PoolAsset, PoolType, PythonMsg


@dataclasses.dataclass
Expand Down Expand Up @@ -62,9 +62,11 @@ class MsgJoinPool(PythonMsg):

sender: str
pool_id: int
tokens: List[Coin]
tokens: Union[Coin, List[Coin]]

def to_pb(self) -> pb.MsgJoinPool:
if isinstance(self.tokens, Coin):
self.tokens = [self.tokens]
return pb.MsgJoinPool(
sender=self.sender,
pool_id=self.pool_id,
Expand All @@ -85,7 +87,7 @@ class MsgExitPool(PythonMsg):

sender: str
pool_id: int
pool_shares: List[Coin]
pool_shares: Coin

def to_pb(self) -> pb.MsgExitPool:
return pb.MsgExitPool(
Expand Down
2 changes: 1 addition & 1 deletion nibiru/msg/perp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from nibiru_proto.proto.perp.v1 import state_pb2 as state_pb
from nibiru_proto.proto.perp.v1 import tx_pb2 as pb

from nibiru.common import Coin, PythonMsg, Side
from nibiru.pytypes import Coin, PythonMsg, Side
from nibiru.utils import to_sdk_dec, to_sdk_int


Expand Down
2 changes: 1 addition & 1 deletion nibiru/msg/pricefeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from nibiru_proto.proto.pricefeed import tx_pb2 as pb

from nibiru.common import PythonMsg
from nibiru.pytypes import PythonMsg
from nibiru.utils import to_sdk_dec, toPbTimestamp


Expand Down
46 changes: 37 additions & 9 deletions nibiru/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,48 @@

@dataclasses.dataclass
class Network:
"""A representation of a Nibiru network based on its Tendermint RPC, gRPC,
and LCD (REST) endpoints. A 'Network' instance enables interactions with a
running blockchain.
Attributes:
lcd_endpoint (str): .
grpc_endpoint (str): .
tendermint_rpc_endpoint (str): .
chain_id (str): .
websocket_endpoint (str): .
env (Optional[str]): TODO docs
fee_denom (Optional[str]): Denom for the coin used to pay gas fees. Defaults to "unibi".
Methods:
customnet: A custom Nibiru network based on environment variables.
Defaults to localnet.
devnet: A development testnet environment that runs the latest release or
pre-release from the nibiru repo. Defaults to 'nibiru-devnet-1'.
localnet: The default local network created by running 'make localnet' in
the nibiru repo.
testnet: A stable testnet environment with public community members.
Think of this as out practice mainnet. Defaults to 'nibiru-testnet-1'.
mainnet: NotImplemented.
Examples:
>>> from nibiru import Network
>>> network = Network.devnet(2)
>>> network.is_insecure
True
"""

lcd_endpoint: str
grpc_endpoint: str
tendermint_rpc_endpoint: str
chain_id: str
env: str
websocket_endpoint: str
env: str = "custom"
fee_denom: str = "unibi"

def __post_init__(self):
"""
Update the env value if the dataclass was created without one.
"""
if self.env == "":
self.env = "custom"
@property
def is_insecure(self) -> bool:
return not ("https" in self.tendermint_rpc_endpoint)

@classmethod
def customnet(cls) -> "Network":
Expand Down Expand Up @@ -74,8 +102,8 @@ def customnet(cls) -> "Network":
@classmethod
def testnet(cls, chain_num: int = 1) -> "Network":
"""
Testnet is a network open to invited validators. It is more stable than devnet and provides a faucet to get some
funds
Testnet is a network open to invited validators. It is more stable than
devnet and provides a faucet to get some funds
Args:
chain_num (int): Testnet number
Expand Down
16 changes: 16 additions & 0 deletions nibiru/pytypes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# These import statements export the types to 'nibiru.pytypes'.

from nibiru.pytypes.common import ( # noqa # TODO move constants to a constants.py file.; noqa
GAS_PRICE,
MAX_MEMO_CHARACTERS,
Coin,
Direction,
PoolAsset,
PoolType,
PythonMsg,
Side,
TxConfig,
TxType,
)
from nibiru.pytypes.event import Event, RawEvent, TxLogEvents # noqa
from nibiru.pytypes.tx_resp import RawTxResp, TxResp # noqa
45 changes: 21 additions & 24 deletions nibiru/common.py → nibiru/pytypes/common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import abc
from dataclasses import dataclass
import dataclasses
from enum import Enum

from nibiru_proto.proto.cosmos.base.v1beta1 import coin_pb2 as cosmos_base_coin_pb
Expand Down Expand Up @@ -52,7 +52,7 @@ class Direction(Enum):
REMOVE = 2


@dataclass
@dataclasses.dataclass
class Coin:
amount: float
denom: str
Expand All @@ -61,35 +61,32 @@ def _generate_proto_object(self):
return cosmos_base_coin_pb.Coin(amount=str(self.amount), denom=self.denom)


@dataclass
@dataclasses.dataclass
class PoolAsset:
token: Coin
weight: float


@dataclasses.dataclass
class TxConfig:
def __init__(
self,
gas_wanted: int = 0,
gas_multiplier: float = 1.25,
gas_price: float = 0,
tx_type: TxType = TxType.ASYNC,
):
"""
The TxConfig object allows to customize the behavior of the Sdk interface when a transaction is sent.
Args:
gas_wanted (int, optional): Set the absolute gas_wanted to be used. Defaults to 0.
gas_multiplier (float, optional): Set the gas multiplier that's being applied to the estimated gas.
Defaults to 0. If gas_wanted is set this property is ignored.
gas_price (float, optional): Set the gas price used to calculate the fee. Defaults to 0.
tx_type (TxType, optional): Configure how to execute the tx. Defaults to TxType.ASYNC.
"""
"""
The TxConfig object allows to customize the behavior of the Sdk interface when a transaction is sent.
Args:
gas_wanted (int, optional): Set the absolute gas_wanted to be used.
Defaults to 0.
gas_multiplier (float, optional): Set the gas multiplier that's being
applied to the estimated gas. If gas_wanted is set, this property
is ignored. Defaults to 0.
gas_price (float, optional): Set the gas price used to calculate the fee.
Defaults to 0.25.
tx_type (TxType, optional): Configure how to execute the tx. Defaults to TxType.ASYNC.
"""

self.gas_multiplier = gas_multiplier
self.gas_wanted = gas_wanted
self.gas_price = gas_price
self.tx_type = tx_type
gas_wanted: int = 0
gas_multiplier: float = 1.25
gas_price: float = 0.25
tx_type: TxType = TxType.ASYNC


class PythonMsg(abc.ABC):
Expand Down
118 changes: 118 additions & 0 deletions nibiru/pytypes/event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import collections
import pprint
from typing import Dict, List


class RawEvent(collections.abc.MutableMapping):
"""Dictionary representing a Tendermint event. In the raw TxOutput of a
successful transaciton, it's the value at
```python
tx_output['rawLog'][0]['events']
```
### Keys (KeyType):
- attributes (List[Dict[str,str]])
- type (str)
### Example:
```python
{'attributes': [
{'key': 'recipient', 'value': 'nibi1uvu52rxwqj5ndmm59y6atvx33mru9xrz6sqekr'},
{'key': 'sender', 'value': 'nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl'},
{'key': 'amount', 'value': '7unibi,70unusd'}],
'type': 'transfer'}
```
"""


class Event:
"""A Tendermint event. An event contains a type and set of attributes.
Events allow application developers to attach additional information to the
'ResponseBeginBlock', 'ResponseEndBlock', 'ResponseCheckTx', and 'ResponseDeliverTx'
functions in the ABCI (application blockchain interface).
In the Tendermint protobuf, the hard definition is:
```proto
message Event {
string type = 1;
repeated EventAttribute attributes = 2;
}
message EventAttribute {
bytes key = 1;
bytes value = 2;
bool index = 3;
}
```
- Ref: [cosmos-sdk/types/events.go](https://github.com/cosmos/cosmos-sdk/blob/93abfdd21d9892550da315b10308519b43fb1775/types/events.go#L221)
- Ref: [tendermint/tendermint/proto/tendermint/abci/types.proto](https://github.com/tendermint/tendermint/blob/a6dd0d270abc3c01f223eedee44d8b285ae273f6/proto/tendermint/abci/types.proto)
"""

type: str
attrs: Dict[str, str]

def __init__(self, raw_event: RawEvent):
self.type = raw_event["type"]
self.attrs = self.parse_attributes(raw_event["attributes"])

@staticmethod
def parse_attributes(raw_attributes: List[Dict[str, str]]) -> Dict[str, str]:
try:
attributes: dict[str, str] = {
kv_dict['key']: kv_dict['value'] for kv_dict in raw_attributes
}
return attributes
except:
raise Exception(
f"failed to parse raw attributes:\n{pprint.pformat(raw_attributes)}"
)

def __repr__(self) -> str:
return f"Event(type={self.type}, attrs={self.attrs})"

def to_dict(self) -> Dict[str, Dict[str, str]]:
return {self.type: self.attrs}


class TxLogEvents:
"""An element of 'TxResp.rawLog'. This object contains events and messages.
Keys (KeyType):
type (str)
attributes (List[EventAttribute])
Args:
events_raw (List[RawEvent])
Attributes:
events (List[Event])
msgs (List[str])
events_raw (List[RawEvent])
event_types (List[str])
"""

events: List[Event]
msgs: List[str]
events_raw: List[RawEvent]
event_types: List[str]

def __init__(self, events_raw: List[RawEvent] = []):
self.events_raw = events_raw
self.events = [Event(raw_event) for raw_event in events_raw]
self.msgs = self.get_msg_types()

def get_msg_types(self) -> List[str]:

msgs = []
self.event_types = []
for event in self.events:
self.event_types.append(event.type)
if event.type == "message":
msgs.append(event.attrs["action"])
return msgs

def __repr__(self) -> str:
self_as_dict = dict(msgs=self.msgs, events=[e.to_dict() for e in self.events])
return pprint.pformat(self_as_dict, indent=2)
Loading

0 comments on commit 27ff098

Please sign in to comment.