This repository has been archived by the owner on Feb 16, 2021. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from terra-project/hotfix_1.1
v1.1
- Loading branch information
Showing
15 changed files
with
22,269 additions
and
741 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,278 @@ | ||
from __future__ import annotations | ||
|
||
import json | ||
import re | ||
from dataclasses import dataclass, field | ||
from typing import List | ||
|
||
from bidict import bidict | ||
|
||
from jigu.core.proposal import Content | ||
from jigu.util.serdes import JiguBox | ||
from jigu.core.sdk import Coins, Dec | ||
from jigu.core.treasury import PolicyConstraints | ||
from jigu.error import InvalidParamChange | ||
from jigu.util.serdes import ( | ||
JiguBox, | ||
JsonDeserializable, | ||
JsonSerializable, | ||
serialize_to_json, | ||
) | ||
from jigu.util.validation import Schemas as S | ||
|
||
__all__ = ["ParamChanges", "ParameterChangeProposal"] | ||
|
||
symbol = re.compile(r"^[a-zA-Z_][a-zA-Z_0-9]*$") | ||
|
||
ParamChangeSchema = S.OBJECT(subspace=S.STRING, key=S.STRING, value=S.STRING) | ||
|
||
__all__ = ["ParameterChangeProposal"] | ||
# For each subspace, map JSON-param key to (ParamStore key, deserializing-fn, serialiazing-fn) | ||
# Serializing function is necessary due to weird ways Cosmos's params module treats integers. | ||
|
||
# TODO: This file could use some work. | ||
|
||
# Define the deserialization and serialization functions for governance params | ||
def deserialize_deposit(data: dict): | ||
if data.get("min_deposit"): | ||
data["min_deposit"] = Coins.deserialize(data["min_deposit"]) | ||
if data.get("max_deposit_period"): | ||
data["max_deposit_period"] = int(data["max_deposit_period"]) | ||
return data | ||
|
||
|
||
def serialize_deposit(data: dict): | ||
if data.get("max_deposit_period"): | ||
data["max_deposit_period"] = str(int(data["max_deposit_period"])) | ||
return data | ||
|
||
|
||
def deserialize_voting(data: dict): | ||
if data.get("voting_period"): | ||
data["voting_period"] = int(data["voting_period"]) | ||
return data | ||
|
||
|
||
def serialize_voting(data: dict): | ||
if data.get("voting_period"): | ||
data["voting_period"] = str(int(data["voting_period"])) | ||
return data | ||
|
||
|
||
def deserialize_tally(data: dict): | ||
if data.get("quorum"): | ||
data["quorum"] = Dec(data["quorum"]) | ||
if data.get("threshold"): | ||
data["threshold"] = Dec(data["threshold"]) | ||
if data.get("veto"): | ||
data["veto"] = Dec(data["veto"]) | ||
return data | ||
|
||
|
||
def serialize_tally(data: dict): | ||
return data | ||
|
||
|
||
# TODO: this could be refactored into XXXModuleParams class in core, with keys | ||
# as attributes, and API requests for Module Params give you an instance of | ||
# ModuleParams with all keys loaded with their current values. Then, to build | ||
# param changes, you would instantiate an empty ModuleParams class with the | ||
# values initialized to ModuleParams.EMPTY and then set each one. | ||
|
||
PARAM_DEFNS = { | ||
"distribution": { | ||
"community_tax": ("communitytax", Dec), | ||
"base_proposer_reward": ("baseproposerreward", Dec), | ||
"bonus_proposer_reward": ("bonusproposerreward", Dec), | ||
"withdraw_addr_enabled": ("withdrawaddrenabled", bool), | ||
}, | ||
"staking": { | ||
"unbonding_time": ("UnbondingTime", int, str), | ||
"max_validators": ("MaxValidators", int, int), | ||
"max_entries": ("KeyMaxEntries", int, int), | ||
"bond_denom": ("BondDenom", str), | ||
}, | ||
"slashing": { | ||
"max_evidence_age": ("MaxEvidenceAge", int, str), # no longer in cosmos master/ | ||
"signed_blocks_window": ("SignedBlocksWindow", int, str), | ||
"min_signed_per_window": ("MinSignedPerWindow", Dec), | ||
"downtime_jail_duration": ("DowntimeJailDuration", int, str), | ||
"slash_fraction_double_sign": ("SlashFractionDoubleSign", Dec), | ||
"slash_fraction_downtime": ("SlashFractionDowntime", Dec), | ||
}, | ||
"oracle": { | ||
"vote_period": ("voteperiod", int, str), | ||
"vote_threshold": ("votethreshold", Dec), | ||
"reward_band": ("rewardband", Dec), | ||
"reward_distribution_window": ("rewarddistributionwindow", int, str), | ||
"whitelist": ("whitelist", None), | ||
"slash_fraction": ("slashfraction", Dec), | ||
"slash_window": ("slashwindow", int, str), | ||
"min_valid_per_window": ("minvalidperwindow", Dec), | ||
}, | ||
"market": { | ||
"pool_recovery_period": ("poolrecoveryperiod", int, str), | ||
"base_pool": ("basepool", Dec), | ||
"min_spread": ("minspread", Dec), | ||
"illiquid_tobin_tax_list": ("illiquidtobintaxlist", None), | ||
}, | ||
"treasury": { | ||
"tax_policy": ("taxpolicy", PolicyConstraints.deserialize), | ||
"reward_policy": ("rewardpolicy", PolicyConstraints.deserialize), | ||
"min_spread": ("minspread", Dec), | ||
"seigniorage_burden_target": ("seigniorageburdentarget", Dec), | ||
"mining_increment": ("miningincrement", Dec), | ||
"window_short": ("windowshort", int, str), | ||
"window_long": ("windowlong", int, str), | ||
"window_probation": ("windowprobation", int, str), | ||
}, | ||
"gov": { | ||
"deposit_params": ("depositparams", deserialize_deposit, serialize_deposit), | ||
"voting_params": ("votingparams", deserialize_voting, serialize_voting), | ||
"tally_params": ("tallyparams", deserialize_tally, serialize_tally), | ||
}, | ||
} | ||
|
||
# create lookup table for deserialization | ||
DES_LOOKUP_TABLE = {} | ||
for subspace, keys in PARAM_DEFNS.items(): | ||
DES_LOOKUP_TABLE[subspace] = {d[0]: d[1] for k, d in keys.items()} | ||
|
||
# DES_LOOKUP_TABLE[subspace][paramkey aka d[0]] = JSON param key | ||
# DES_LOOKUP_TABLE["treasury"]["windowlong"] -> "window_long" | ||
|
||
# create lookup table for serialization | ||
PARAMSTORE_KEY_LOOKUP_TABLE = {} | ||
for subspace, keys in PARAM_DEFNS.items(): | ||
PARAMSTORE_KEY_LOOKUP_TABLE[subspace] = bidict({k: d[0] for k, d in keys.items()}) | ||
|
||
|
||
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) | ||
|
||
|
||
@dataclass | ||
class ParameterChangeProposal(Content): | ||
|
||
type = "params/ParameterChangeProposal" | ||
ParamChanges = ParamChanges # alias for easy access | ||
|
||
__schema__ = S.OBJECT( | ||
type=S.STRING_WITH_PATTERN(r"^params/ParameterChangeProposal\Z"), | ||
value=S.OBJECT( | ||
title=S.STRING, description=S.STRING, changes=S.ARRAY(ParamChangeSchema), | ||
title=S.STRING, description=S.STRING, changes=ParamChanges.__schema__, | ||
), | ||
) | ||
|
||
title: str | ||
description: str | ||
changes: List[JiguBox] = field(default_factory=list) | ||
changes: ParamChanges = field(default_factory=ParamChanges) | ||
|
||
def __post_init__(self): | ||
# validation checks for key | ||
if isinstance(self.changes, dict): | ||
self.changes = ParamChanges(self.changes) | ||
|
||
@classmethod | ||
def from_data(cls, data: dict) -> ParameterChangeProposal: | ||
data = data["value"] | ||
return cls( | ||
title=data["title"], | ||
description=data["description"], | ||
changes=[JiguBox(change) for change in data["changes"]], | ||
changes=ParamChanges.from_data(data["changes"]), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.