Skip to content

Commit

Permalink
feat: align vote transaction with core
Browse files Browse the repository at this point in the history
  • Loading branch information
c0nsol3 committed Jun 26, 2022
1 parent 55414ce commit 1e1d252
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 39 deletions.
2 changes: 1 addition & 1 deletion solar_crypto/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

SOLAR_TRANSACTION_FEES = {
SOLAR_TRANSACTION_BURN: 0,
SOLAR_TRANSACTION_VOTE: 100000000,
SOLAR_TRANSACTION_VOTE: 9000000,
}


Expand Down
38 changes: 34 additions & 4 deletions solar_crypto/transactions/builder/vote.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import re
import typing
from decimal import Decimal
from functools import cmp_to_key
from math import trunc

from solar_crypto.constants import SOLAR_TRANSACTION_VOTE, TRANSACTION_TYPE_GROUP
from solar_crypto.exceptions import SolarInvalidTransaction
from solar_crypto.transactions.builder.base import BaseTransactionBuilder


Expand All @@ -19,7 +21,9 @@ def __init__(self):

def set_votes(
self,
votes: typing.Union[typing.List[str], typing.Dict[str, typing.Union[int, float]]] = dict,
votes: typing.Union[
typing.List[str], typing.Dict[str, typing.Union[int, float, Decimal]]
] = dict,
):
"""Set votes
Expand All @@ -30,24 +34,34 @@ def set_votes(

if isinstance(votes, list):
vote_list = filter(lambda vote: not vote.startswith("-"), votes)
vote_list = list(map(lambda vote: vote[1:], vote_list))
vote_list = list(
map(lambda vote: vote[1:] if vote.startswith("+") else vote, vote_list)
)

if len(vote_list) > 53:
raise SolarInvalidTransaction("Unable to vote for more than 53 delegates")

if len(vote_list) == 0:
self.transaction.asset["votes"] = {}
return

weight = round((trunc((100 / len(vote_list)) * 100) / 100) * 100)
weight = trunc(((((100 / len(vote_list))) * 100) / 100) * 100)
remainder = 10000

for vote in vote_list:
vote_object[vote] = weight / 100
remainder -= weight

for index in range(remainder):
for index in range(int(remainder)):
key = list(vote_object.keys())[index]
vote_object[key] = round((vote_object[key] + 0.01) * 100) / 100

votes = vote_object
else:
for key, val in votes.items():
votes[key] = val

validate(votes)

if votes:
nr_of_votes = len(votes.keys())
Expand All @@ -57,6 +71,22 @@ def set_votes(
self.transaction.asset["votes"] = votes


def validate(votes):
for value in votes.values():
if not valid_precision(value):
raise SolarInvalidTransaction("Only two decimal places are allowed.")


def valid_precision(value, max_precision=2):
if isinstance(value, Decimal):
if abs(value.as_tuple().exponent) <= max_precision:
return True
elif isinstance(value, float) or isinstance(value, str):
if str(value)[::-1].find(".") <= max_precision:
return True
return False


def cmp(a: typing.List[typing.Union[int, str]], b: typing.List[typing.Union[int, str]]):
"""
Compare two alphanum keys
Expand Down
10 changes: 5 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,11 @@ def transaction_type_vote():
"type": 2,
"nonce": 1,
"senderPublicKey": "037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c",
"fee": 100000000,
"asset": {"votes": {"deadlock": 50.33}},
"signature": "4b3f1fc10a11302109b123decceadcf5601053079919f90f7e0c28d502771a0e19553b8019579762373a957eb7876474fcfc5d12d6ea898205f54e62ce3f2c7c", # noqa
"fee": 9000000,
"asset": {"votes": {"deadlock": 64.7, "fun": 35.3}},
"signature": "b1b484cb3b781b0af8f82f8639c52f25f637100d8adadcdde380c07924ada1cc1c1f5b6107cce1ff86b87ab66806ca1a8ae716e3555198cc7fa324a70f697b54", # noqa
"amount": 0,
"id": "a7789f2eb62bae9782db053b045ed35a4475147c0b9bab5f964d69af2422a9ac",
"serialized": "ff031e0200000002000100000000000000037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c00e1f50500000000000108646561646c6f636ba9134b3f1fc10a11302109b123decceadcf5601053079919f90f7e0c28d502771a0e19553b8019579762373a957eb7876474fcfc5d12d6ea898205f54e62ce3f2c7c", # noqa
"id": "73a9a7a58c1060b8dd0c92cfe5c9bf305cfd9c387888aab92ae4e30770c5ff68",
"serialized": "ff031e0200000002000100000000000000037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c4054890000000000000208646561646c6f636b46190366756eca0db1b484cb3b781b0af8f82f8639c52f25f637100d8adadcdde380c07924ada1cc1c1f5b6107cce1ff86b87ab66806ca1a8ae716e3555198cc7fa324a70f697b54", # noqa
}
return data
46 changes: 34 additions & 12 deletions tests/transactions/builder/test_vote.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
from collections import OrderedDict

import pytest

from solar_crypto.configuration.network import set_network
from solar_crypto.constants import SOLAR_TRANSACTION_VOTE, TRANSACTION_TYPE_GROUP
from solar_crypto.exceptions import SolarInvalidTransaction
from solar_crypto.networks.testnet import Testnet
from solar_crypto.transactions.builder.vote import Vote, sort_votes

set_network(Testnet)


def test_vote_transaction():
def test_vote_transaction_input_error():
vote = {"deadlock": 50.3333}

transaction = Vote()
with pytest.raises(SolarInvalidTransaction) as e:
transaction.set_votes(vote)
assert str(e.value) == "Only two decimal places are allowed."


def test_vote_transaction_multiple_votes():
vote = {"fun": 35.3, "deadlock": 64.7}

transaction = Vote()
transaction.set_votes(vote)
transaction.set_nonce(1)
Expand All @@ -22,47 +34,57 @@ def test_vote_transaction():
assert transaction_dict["asset"]["votes"] == vote
assert transaction_dict["type"] is SOLAR_TRANSACTION_VOTE
assert transaction_dict["typeGroup"] == TRANSACTION_TYPE_GROUP.SOLAR.value
assert transaction_dict["fee"] == 100000000
assert transaction_dict["fee"] == 9000000

transaction.verify() # if no exception is raised, it means the transaction is valid


def test_vote_transaction_multiple_votes():
vote = {"fun": 35.3, "deadlock": 64.7}

def test_vote_transaction_using_empty_list():
transaction = Vote()
transaction.set_votes(vote)
transaction.set_votes(["-awesome"])
transaction.set_nonce(1)
transaction.sign("testing")
transaction_dict = transaction.to_dict()

assert transaction_dict["nonce"] == 1
assert transaction_dict["signature"]
assert transaction_dict["asset"]["votes"] == vote
assert transaction_dict["asset"]["votes"] == {}
assert transaction_dict["type"] is SOLAR_TRANSACTION_VOTE
assert transaction_dict["typeGroup"] == TRANSACTION_TYPE_GROUP.SOLAR.value
assert transaction_dict["fee"] == 100000000
assert transaction_dict["fee"] == 9000000

transaction.verify() # if no exception is raised, it means the transaction is valid


def test_vote_transaction_using_list():
def test_vote_transaction_using_list_ok():
transaction = Vote()
transaction.set_votes(["-awesome"])
transaction.set_votes(["awesome", "possom", "flossom"])
transaction.set_nonce(1)
transaction.sign("testing")
transaction_dict = transaction.to_dict()

assert transaction_dict["nonce"] == 1
assert transaction_dict["signature"]
assert transaction_dict["asset"]["votes"] == {}
assert OrderedDict(transaction_dict["asset"]["votes"]) == OrderedDict({
"awesome": 33.34,
"flossom": 33.33,
"possom": 33.33,
})
assert transaction_dict["type"] is SOLAR_TRANSACTION_VOTE
assert transaction_dict["typeGroup"] == TRANSACTION_TYPE_GROUP.SOLAR.value
assert transaction_dict["fee"] == 100000000
assert transaction_dict["fee"] == 9000000

transaction.verify() # if no exception is raised, it means the transaction is valid


def test_vote_transaction_using_list_more_than_53():
transaction = Vote()

with pytest.raises(SolarInvalidTransaction) as e:
transaction.set_votes([str(x) for x in range(55)])
assert str(e.value) == "Unable to vote for more than 53 delegates"


def test_sort_votes():
votes = {
"ddd": 50,
Expand Down
21 changes: 4 additions & 17 deletions tests/transactions/deserializers/test_vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


def test_vote_deserializer():
serialized = "ff031e0200000002000100000000000000037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c00e1f50500000000000108646561646c6f636ba9138ee49974901f9818d56459d1817c41fc2d51204abc07cea05b72cae00cffaf314057f06b009ce6a2f99c8774af171f209fac1a9ce15abb429648ea22321765cb" # noqa
serialized = "ff031e0200000002000100000000000000037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c4054890000000000000208646561646c6f636b46190366756eca0db1b484cb3b781b0af8f82f8639c52f25f637100d8adadcdde380c07924ada1cc1c1f5b6107cce1ff86b87ab66806ca1a8ae716e3555198cc7fa324a70f697b54" # noqa

deserializer = Deserializer(serialized)
actual = deserializer.deserialize()
Expand All @@ -11,29 +11,16 @@ def test_vote_deserializer():
assert actual.typeGroup == 2
assert actual.type == 2
assert actual.amount == 0
assert actual.fee == 100000000
assert actual.fee == 9000000
assert actual.nonce == 1
assert actual.asset["votes"] == {"deadlock": 50.33}
assert actual.asset["votes"] == {"deadlock": 64.7, "fun": 35.3}
assert (
actual.senderPublicKey
== "037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c"
) # noqa
assert (
actual.signature
== "8ee49974901f9818d56459d1817c41fc2d51204abc07cea05b72cae00cffaf314057f06b009ce6a2f99c8774af171f209fac1a9ce15abb429648ea22321765cb"
== "b1b484cb3b781b0af8f82f8639c52f25f637100d8adadcdde380c07924ada1cc1c1f5b6107cce1ff86b87ab66806ca1a8ae716e3555198cc7fa324a70f697b54"
)

actual.verify()


def test_vote_deserializer_multiple_votes():
serialized = "ff031e0200000002000100000000000000037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c00e1f5050000000000020366756eca0d08646561646c6f636b46196300f3ff5eff1a2e083ab31762089ad95bebb3de8ee451442a5fd945c2d7773cfe5dfc1528d0c03533677b88cd9e4421395716f78e9f7519343d2fb43ef1ca8d"

deserializer = Deserializer(serialized)
actual = deserializer.deserialize()

assert actual.asset["votes"] == {"fun": 35.3, "deadlock": 64.7}
assert (
actual.senderPublicKey
== "037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c"
) # noqa

0 comments on commit 1e1d252

Please sign in to comment.