Beispiel #1
0
class StdTx(JsonSerializable, JsonDeserializable):

    __schema__ = S.OBJECT(
        type=S.STRING_WITH_PATTERN(r"^core/StdTx\Z"),
        value=S.OBJECT(
            fee=StdFee.__schema__,
            msg=S.ARRAY(S.ANY(*(mt.__schema__ for mt in MSG_TYPES.values()))),
            signatures=S.ARRAY(StdSignature.__schema__),
            memo=S.STRING,
        ),
    )

    # NOTE: msg is not plural, and is NOT a typo. This may change later for consistency.
    fee: Optional[StdFee] = None
    msg: List[StdMsg] = field(default_factory=list)
    signatures: List[StdSignature] = field(default_factory=list)
    memo: str = ""

    def to_data(self) -> dict:
        return {
            "type": "core/StdTx",
            "value": dict(self.__dict__),
        }

    @classmethod
    def from_data(cls, data: dict) -> StdTx:
        data = data["value"]
        fee = StdFee.from_data(data["fee"])
        # deserialize the messages
        msg = []
        for m in data["msg"]:
            msg_type = MSG_TYPES[m["type"]]
            msg.append(msg_type.from_data(m))
        signatures = [StdSignature.from_data(s) for s in data["signatures"]]
        return cls(fee=fee, msg=msg, signatures=signatures, memo=data["memo"])
Beispiel #2
0
class MsgMultiSend(StdMsg):

    type = "bank/MsgMultiSend"
    action = "multisend"

    Input = Input  # alias
    Output = Output  # alias

    __schema__ = S.OBJECT(
        type=S.STRING_WITH_PATTERN(r"^bank/MsgMultiSend\Z"),
        value=S.OBJECT(inputs=S.ARRAY(Input.__schema__),
                       outputs=S.ARRAY(Output.__schema__)),
    )

    inputs: List[Union[Input, dict]]
    outputs: List[Union[Output, dict]]

    def __init__(
        self,
        inputs: List[Input],
        outputs: List[Output],
    ):
        self.inputs = []
        self.outputs = []

        for i in inputs:
            if isinstance(i, dict):
                self.inputs.append(
                    Input(address=i["address"], coins=i["coins"]))
            elif isinstance(i, Input):
                self.inputs.append(i)
            else:
                raise TypeError(
                    f"invalid item {type(i)} encountered in MsgMultiSend.inputs, can only accept 'Input' or 'dict'."
                )
        for o in outputs:
            if isinstance(o, dict):
                self.outputs.append(
                    Output(address=o["address"], coins=o["coins"]))
            elif isinstance(o, Output):
                self.outputs.append(o)
            else:
                raise TypeError(
                    f"invalid item {type(o)} encountered in MsgMultiSend.outputs, can only accept 'Output' or 'dict'."
                )

    @classmethod
    def from_data(cls, data: dict) -> MsgMultiSend:
        data = data["value"]
        return cls(
            inputs=[Input.from_data(i) for i in data["inputs"]],
            outputs=[Output.from_data(o) for o in data["outputs"]],
        )
Beispiel #3
0
class Block(BlockHeader, JsonDeserializable):

    __schema__ = S.OBJECT(
        block_meta=BlockHeader.__schema__,
        block=S.OBJECT(
            data=S.OBJECT(txs=S.OPTIONAL(S.ARRAY(S.STRING))),
            evidence={},
            last_commit={},
        ),
    )

    evidence: JiguBox  # TODO: improve and document
    last_commit: JiguBox  # TODO: improve and document
    txs: List[
        Union[str, StdTx, TxInfo,
              TxInfosQuery]]  # ordered last for convenience of pretty printing

    @classmethod
    def from_data(cls, data: dict) -> Block:
        header = BlockHeader.from_data(data["block_meta"])
        txs = (data["block"]["data"]["txs"]
               or [])  # these will be Amino-encoded tx strings
        evidence = JiguBox(data["block"].get("evidence"))
        last_commit = JiguBox(data["block"].get("last_commit"))
        return cls(txs=txs,
                   evidence=evidence,
                   last_commit=last_commit,
                   **header.__dict__)
Beispiel #4
0
class TxBroadcastResult(JsonSerializable, JsonDeserializable):

    __schema__ = S.OBJECT(
        height=S.STRING_INTEGER,
        txhash=S.STRING,
        raw_log=S.STRING,
        logs=S.ARRAY(MsgInfo.__schema__),
        gas_wanted=S.STRING_INTEGER,
        gas_used=S.STRING_INTEGER,
        events=S.ARRAY({}),
    )

    height: int
    txhash: str
    raw_log: str
    # logs: list
    gas_wanted: int
    gas_used: int
    # events: list
    msgs: MsgInfosQuery

    @property
    def pretty_data(self):
        d = dict(self.__dict__)
        d.pop("raw_log")
        return d.items()

    @property
    def events(self):
        return self.msgs.events

    @classmethod
    def from_data(cls, data: dict, tx) -> TxBroadcastResult:
        logs = TxInfo.merge_logs(data, tx)
        if logs:
            logs = MsgInfosQuery(logs)
        return cls(
            height=data.get("height") and int(data["height"]),
            txhash=data.get("txhash"),
            raw_log=data.get("raw_log"),
            gas_wanted=data.get("gas_wanted") and int(data["gas_wanted"]),
            gas_used=data.get("gas_used") and int(data["gas_used"]),
            # events=data.get("events"),
            msgs=logs,
        )
Beispiel #5
0
class UnbondingDelegation(JsonSerializable, JsonDeserializable):

    __schema__ = S.OBJECT(
        delegator_address=S.ACC_ADDRESS,
        validator_address=S.VAL_ADDRESS,
        entries=S.ARRAY(UnbondingEntry.__schema__),
    )

    delegator_address: AccAddress
    validator_address: ValAddress
    entries: List[UnbondingEntry]

    @classmethod
    def from_data(cls, data: Dict[str, Any]) -> UnbondingDelegation:
        entries = [
            UnbondingEntry.from_data(entry) for entry in data["entries"]
        ]
        return cls(
            delegator_address=data["delegator_address"],
            validator_address=data["validator_address"],
            entries=entries,
        )
Beispiel #6
0
class Event(JsonSerializable, JsonDeserializable):
    __schema__ = S.OBJECT(
        type=S.STRING, attributes=S.ARRAY(S.OBJECT(key=S.STRING, value=S.STRING))
    )

    type: str
    attributes: JiguBox[str, List[str]]

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

    def __getitem__(self, item) -> List[str]:
        return self.attributes[item]

    def __getattr__(self, name) -> List[str]:
        if name in self.attributes:
            return self.attributes[name]
        return self.__getattribute__(name)

    @property
    def pretty_data(self):
        d = {"type": self.type, **self.attributes.to_data()}
        return d.items()

    def to_data(self):
        return {
            "type": self.type,
            "attributes": [
                {"key": k, "value": v} for k, vs in self.attributes.items() for v in vs
            ],
        }

    @classmethod
    def from_data(cls, data: dict) -> Event:
        attrs = defaultdict(list)
        for attr in data["attributes"]:
            attrs[attr["key"]].append(attr["value"])
        return cls(type=data["type"], attributes=JiguBox(attrs))
Beispiel #7
0
class PublicKey(JsonSerializable, JsonDeserializable):

    __schema__ = S.OBJECT(
        type=S.STRING,
        value=S.ANY(
            S.STRING,
            S.OBJECT(
                threshold=S.STRING_INTEGER,
                pubkeys=S.ARRAY(S.OBJECT(type=S.STRING, value=S.STRING)),
            ),
        ),
    )

    type: str = "tendermint/PubKeySecp256k1"
    value: Any = None

    @classmethod
    def from_data(cls, data: dict) -> PublicKey:
        # TODO: apply None-coalescing feature to root JsonDeserializable
        # viz: JsonDeserializable
        if data is None:
            return None
        return cls(type=data["type"], value=data["value"])
Beispiel #8
0
class Redelegation(JsonSerializable, JsonDeserializable):

    __schema__ = S.OBJECT(
        delegator_address=S.ACC_ADDRESS,
        validator_src_address=S.VAL_ADDRESS,
        validator_dst_address=S.VAL_ADDRESS,
        entries=S.ARRAY(RedelegationEntry.__schema__),
    )

    delegator_address: AccAddress
    validator_src_address: ValAddress
    validator_dst_address: ValAddress
    entries: List[RedelegationEntry]

    @classmethod
    def from_data(cls, data: Dict[str, Any]) -> Redelegation:
        entries = [RedelegationEntry.from_data(re) for re in data["entries"]]
        return cls(
            delegator_address=data["delegator_address"],
            validator_src_address=data["validator_src_address"],
            validator_dst_address=data["validator_dst_address"],
            entries=entries,
        )
Beispiel #9
0
class ParamChanges(JsonSerializable, JsonDeserializable):

    __schema__ = S.ARRAY(ParamChangeSchema)

    def __init__(self, changes: dict):
        for k, v in changes.items():
            m = symbol.match(k)
            if not m:
                raise InvalidParamChange(
                    f"Parameter change subspace could not be parsed: {k}")
            if not isinstance(v, dict):
                raise InvalidParamChange(
                    f"Parameter change value should be a dict but got: '{type(v)}' for {k}"
                )
            for sk, sv in v.items():
                sm = symbol.match(sk)
                if not sm:
                    raise InvalidParamChange(
                        f"Parameter change key could not be parsed - {k}: '{sk}'"
                    )
        self.changes = JiguBox(changes)

    def __repr__(self) -> str:
        return f"<ParamChanges {self.changes!r}>"

    def __getattr__(self, name: str) -> JiguBox:
        return self.changes[name]

    def __getitem__(self, item) -> JiguBox:
        return self.changes[item]

    @staticmethod
    def _get_key(subspace, key, inverse=False):
        """Each parameter has a special key in the ParamStore that might not correspond
        to the parameter's JSON-name. We use this function to look up the ParamStore key
        for the JSON-name, which we are familiar with.

        Set `inverse` to `True` to look up from the opposite direction for deserialization.
        """
        try:
            table = PARAMSTORE_KEY_LOOKUP_TABLE[subspace]
            if inverse:
                table = table.inverse
            return table[key]
        except KeyError:
            return key

    @staticmethod
    def _marshal_value(subspace, key, value):
        """Formats the value for param change. Terra node expects all the values to be
        JSON-encoded, and Amino:JSON int/int64/uint/uint64 expects quoted values for
        JavaScript numeric support, and int16/uint16 need `int`.
        """
        try:
            if key not in PARAM_DEFNS[subspace]:  # if not JSON-name
                # try paramstore name
                key = ParamChanges._get_key(subspace, key, inverse=True)
            if len(PARAM_DEFNS[subspace][key]) == 3:
                value = PARAM_DEFNS[subspace][key][2](value)
        except KeyError:
            pass
        return serialize_to_json(value)

    @staticmethod
    def _unmarshal_value(subspace, key, value):
        """Looks up the correct type to decode the right type for the parameter change."""
        if subspace in DES_LOOKUP_TABLE and key in DES_LOOKUP_TABLE[subspace]:
            if DES_LOOKUP_TABLE[subspace][key] is not None:
                return DES_LOOKUP_TABLE[subspace][key](value)
        return value

    @property
    def pretty_data(self):
        return self.changes.items()

    def to_data(self) -> list:
        param_changes = []
        for subspace, v in self.changes.items():
            for key, value in v.items():
                param_changes.append({
                    "subspace":
                    subspace,
                    "key":
                    self._get_key(subspace, key),
                    "value":
                    self._marshal_value(subspace, key, value),
                })
        return param_changes

    @classmethod
    def from_data(cls, data: list) -> ParamChanges:
        changes = JiguBox(default_box=True)
        for p in data:
            subspace = p["subspace"]
            key = cls._get_key(
                subspace, p["key"], inverse=True
            )  # p["key"] is paramstore key, we are using json-name keys inside Jigu
            value = cls._unmarshal_value(subspace, p["key"],
                                         json.loads(p["value"]))
            changes[subspace][key] = value
        return cls(changes)
Beispiel #10
0
class MsgInfo(wrapt.ObjectProxy):

    __schema__ = S.OBJECT(
        msg_index=S.INTEGER,
        success=S.BOOLEAN,
        log=S.STRING,
        events=S.ARRAY(
            S.OBJECT(
                type=S.STRING,
                attributes=S.ARRAY(S.OBJECT(key=S.STRING, value=S.STRING)),
            )),
    )

    def __init__(self, msg: StdMsg, success: bool, log: dict,
                 events: EventsQuery):
        wrapt.ObjectProxy.__init__(self, msg)
        try:
            log = json.loads(log)
            if type(log) == dict:
                log = JiguBox(log)
        except:
            log = None
        self._self_success = success
        self._self_log = log
        self._self_events = events
        self._self_pretty_data = None

    @property
    def success(self):
        return self._self_success

    @property
    def log(self):
        return self._self_log

    @property
    def events(self):
        return self._self_events

    def __eq__(self, other):
        return (isinstance(other, MsgInfo) and self.success == other.success
                and self.log == other.log and self.events == other.events)

    def __ne__(self, other):
        # we have to do this because objectproxy uses the underlying wrapped's __neq__
        return not self == other

    @property
    def pretty_data(self):
        d = dict(self.__dict__)
        items = list(d.items())
        items.append(("success", self.success))
        if self.log:
            items.append(("log", self.log))
        items.append(("events", self.events))
        return items

    def _pretty_output(self, path: str = ""):
        return PrettyPrintable._pretty_output(self, path)

    def _pretty_repr_(self, path: str = "") -> str:
        return PrettyPrintable._pretty_repr_(self, path)

    @property
    def _pp(self):
        """Shortcut for seeing pretty-printing output."""
        see(self)
        return None
Beispiel #11
0
class LazyGradedVestingAccount(Account):

    __schema__ = S.OBJECT(
        type=S.STRING_WITH_PATTERN(r"^core/LazyGradedVestingAccount\Z"),
        value=S.OBJECT(
            BaseVestingAccount=S.OBJECT(
                BaseAccount=Account.__schema__["properties"]["value"],
                original_vesting=Coins.__schema__,
                delegated_free=Coins.__schema__,
                delegated_vesting=Coins.__schema__,
                end_time=S.STRING_INTEGER,
            ),
            vesting_schedules=S.ARRAY(
                S.OBJECT(denom=S.STRING,
                         schedules=S.ARRAY(VestingScheduleEntry.__schema__))),
        ),
    )

    original_vesting: Coins
    delegated_free: Coins
    delegated_vesting: Coins
    end_time: int
    vesting_schedules: Dict[str, List[VestingScheduleEntry]]

    def __init__(
        self,
        base_account: Account,
        original_vesting: Coins,
        delegated_free: Coins,
        delegated_vesting: Coins,
        end_time: int,
        vesting_schedules: Dict[str, List[VestingScheduleEntry]],
    ):
        Account.__init__(
            self,
            address=base_account.address,
            coins=base_account.coins,
            public_key=base_account.public_key,
            account_number=base_account.account_number,
            sequence=base_account.sequence,
        )
        self._base_account = base_account
        self.original_vesting = original_vesting
        self.delegated_free = delegated_free
        self.delegated_vesting = delegated_vesting
        self.end_time = end_time
        self.vesting_schedules = vesting_schedules

    def to_data(self):
        return {
            "type": "core/LazyGradedVestingAccount",
            "value": {
                "BaseVestingAccount": {
                    "BaseAccount":
                    Account.to_data(self._base_account)["value"],
                    "original_vesting": self.original_vesting,
                    "delegated_free": self.delegated_free,
                    "delegated_vesting": self.delegated_vesting,
                    "end_time": str(self.end_time),
                },
                "vesting_schedules": [{
                    "denom":
                    denom,
                    "schedules":
                    self.vesting_schedules[denom]
                } for denom in self.vesting_schedules],
            },
        }

    @property
    def pretty_data(self):
        d = dict(self.__dict__)
        d.pop("_base_account")
        return d.items()

    @classmethod
    def from_data(cls, data: dict) -> LazyGradedVestingAccount:
        if "type" in data:
            data = data["value"]  # disregard type
        bva = data[
            "BaseVestingAccount"]  # value = { bva { ... } vesting_schedules { } }
        original_vesting = Coins.from_data(bva["original_vesting"])
        delegated_free = Coins.from_data(bva["delegated_free"])
        delegated_vesting = Coins.from_data(bva["delegated_vesting"])
        vesting_schedules = JiguBox({})
        for s in data["vesting_schedules"]:
            vesting_schedules[s["denom"]] = [
                VestingScheduleEntry.deserialize(e) for e in s["schedules"]
            ]
        return cls(
            base_account=Account.from_data(bva["BaseAccount"]),
            original_vesting=original_vesting,
            delegated_free=delegated_free,
            delegated_vesting=delegated_vesting,
            end_time=int(bva["end_time"]),
            vesting_schedules=vesting_schedules,
        )
Beispiel #12
0
class Coins(JsonSerializable, JsonDeserializable, Generic[T]):

    __schema__ = S.ARRAY(Coin.__schema__)

    def __init__(self, coins: Iterable[Coin] = None, **denoms):
        if coins is None:
            coins = []
        coins = list(coins) + [Coin(d, a) for d, a in denoms.items()]
        self._cd = dict()
        for coin in list(coins):
            if self._cd.get(coin.denom, None) is None:
                self._cd[coin.denom] = Coin(coin.denom, coin.amount)
            else:
                self._cd[coin.denom] = coin + self._cd[coin.denom]

    def __repr__(self) -> str:
        rstr = ", ".join(f"{c.denom}={c.amount!r}" for c in self.coins)
        return f"Coins({rstr})"

    def __str__(self) -> str:
        return ", ".join(str(coin) for coin in self.coins)

    def to_data(self) -> List[Dict[str, str]]:
        return [coin.to_data() for coin in self.coins]

    def _pretty_repr_(self, path: str = "") -> str:
        d = JiguBox({coin.denom: coin.amount for coin in self.coins})
        return d._pretty_repr_()

    def __add__(self, other: Union[Coin, Coins]) -> Coins:
        if other == 0:
            return Coins(self.coins)
        elif isinstance(other, Coins):
            return Coins(self.coins + other.coins)
        elif isinstance(other, Coin):
            return Coins(self.coins + [other])
        else:
            raise TypeError(
                f"unsupported operand types for +: 'Coins' and '{type(other)}'"
            )

    def __radd__(self, other):
        return self + other

    def __mul__(self, other: Union[int, float, Decimal, Dec]) -> Coins:
        return Coins([coin * other for coin in self.coins])

    def __rmul__(self, other) -> Coins:
        return self * other

    def __truediv__(self, other: Union[int, float, Decimal, Dec]) -> Coins:
        return Coins([coin / other for coin in self.coins])

    def __floordiv__(self, other: Union[int, float, Decimal, Dec]) -> Coins:
        return Coins([coin / other for coin in self.coins])

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Coins):
            return JsonSerializable.__eq__(self, other)
        elif isinstance(other, list):
            try:
                return JsonSerializable.__eq__(self, Coins(other))
            except AttributeError:
                return False
        else:
            return False

    @classmethod
    def from_data(cls, data: List[Dict[str, str]]) -> Coins:
        coins = map(Coin.from_data, data)
        return cls(coins)

    @property
    def denoms(self) -> List[str]:
        return sorted(self._cd.keys())

    @property
    def coins(self) -> List[Coin]:
        return sorted(self._cd.values(), key=lambda c: c.denom)

    @property
    def dec_coins(self) -> Coins:
        return Coins(c.dec_coin for c in self.coins)

    @property
    def int_coins(self) -> Coins:
        return Coins(c.int_coin for c in self.coins)

    def filter(self, predicate: Callable[[Coin], bool]) -> Coins:
        return Coins(c for c in self.coins if predicate(c))

    def __iter__(self):
        return iter(self.coins)

    def __contains__(self, denom: str) -> bool:
        return denom in self._cd

    def __getitem__(self, denom: str) -> Coin:
        return self._cd[denom]

    def __getattr__(self, name: str) -> Coin:
        if name in self.denoms:
            return self[name]
        return self.__getattribute__(name)