evm-tokenvote

Voting machine using ERC20 tokens as votes.
Log | Files | Refs | README

voter.py (15303B)


      1 # standard imports
      2 import logging
      3 import os
      4 import enum
      5 
      6 # external imports
      7 from chainlib.eth.constant import ZERO_ADDRESS
      8 from chainlib.eth.contract import (
      9     ABIContractEncoder,
     10     ABIContractDecoder,
     11     ABIContractType,
     12     abi_decode_single,
     13 )
     14 from chainlib.eth.jsonrpc import to_blockheight_param
     15 from chainlib.eth.error import RequestMismatchException
     16 from chainlib.eth.tx import (
     17     TxFactory,
     18     TxFormat,
     19 )
     20 from chainlib.jsonrpc import JSONRPCRequest
     21 from chainlib.block import BlockSpec
     22 from hexathon import (
     23     add_0x,
     24     strip_0x,
     25 )
     26 from chainlib.eth.cli.encode import CLIEncoder
     27 
     28 # local imports
     29 from evm_tokenvote.data import data_dir
     30 
     31 logg = logging.getLogger()
     32 
     33 
     34 class ProposalState(enum.IntEnum):
     35     INIT = 1
     36     FINAL = 2
     37     SCANNED = 4
     38     INSUFFICIENT = 8
     39     TIED = 16
     40     SUPPLYCHANGE = 32
     41     IMMEDIATE = 64
     42     CANCELLED = 128
     43 
     44 
     45 class Proposal:
     46    
     47     def __init__(self, description_digest, *args, **kwargs):
     48         self.description_digest = description_digest
     49         self.supply = kwargs.get('supply')
     50         self.total = kwargs.get('total')
     51         self.block_deadline = kwargs.get('block_deadline')
     52         self.target_vote_ppm = kwargs.get('target_vote_ppm')
     53         self.cancel_votes = kwargs.get('cancel_votes')
     54         self.proposer = kwargs.get('proposer')
     55         self.state = kwargs.get('state')
     56         self.serial = kwargs.get('serial')
     57 
     58 
     59     def __str__(self):
     60         return "proposal description {} total {} supply {}".format(self.description_digest, self.total, self.supply)
     61 
     62 
     63 class Voter(TxFactory):
     64 
     65     __abi = None
     66     __bytecode = None
     67 
     68     def constructor(self, sender_address, token_address, protect_supply=False, voter_registry=None, proposer_registry=None, tx_format=TxFormat.JSONRPC, version=None):
     69         code = self.cargs(token_address, protect_supply=protect_supply, voter_registry=voter_registry, proposer_registry=proposer_registry, version=version)
     70         tx = self.template(sender_address, None, use_nonce=True)
     71         tx = self.set_code(tx, code)
     72         return self.finalize(tx, tx_format)
     73 
     74 
     75     @staticmethod
     76     def cargs(token_address, protect_supply=False, voter_registry=None, proposer_registry=None, version=None):
     77         if voter_registry == None:
     78             voter_registry = ZERO_ADDRESS
     79         if proposer_registry == None:
     80             proposer_registry = ZERO_ADDRESS
     81         if token_address == None:
     82             raise ValueError("token address cannot be zero address")
     83         code = Voter.bytecode(version=version)
     84         enc = ABIContractEncoder()
     85         enc.address(token_address)
     86         enc.bool(protect_supply)
     87         enc.address(voter_registry)
     88         enc.address(proposer_registry)
     89         args = enc.get()
     90         code += args
     91         logg.debug('constructor code: ' + args)
     92         return code
     93 
     94 
     95     @staticmethod
     96     def gas(code=None):
     97         return 4000000
     98 
     99 
    100     @staticmethod
    101     def abi():
    102         if Voter.__abi == None:
    103             f = open(os.path.join(data_dir, 'Voter.json'), 'r')
    104             Voter.__abi = json.load(f)
    105             f.close()
    106         return Voter.__abi
    107 
    108 
    109     @staticmethod
    110     def bytecode(version=None):
    111         if Voter.__bytecode == None:
    112             f = open(os.path.join(data_dir, 'Voter.bin'))
    113             Voter.__bytecode = f.read()
    114             f.close()
    115         return Voter.__bytecode
    116 
    117 
    118     def propose(self, contract_address, sender_address, description, block_deadline, target_vote_ppm=500000, tx_format=TxFormat.JSONRPC, id_generator=None):
    119         enc = ABIContractEncoder()
    120         enc.method('propose')
    121         enc.typ(ABIContractType.BYTES32)
    122         enc.typ(ABIContractType.UINT256)
    123         enc.typ_literal('uint24')
    124         enc.bytes32(description)
    125         enc.uint256(block_deadline)
    126         enc.uintn(target_vote_ppm, 24)
    127         data = add_0x(enc.get())
    128         tx = self.template(sender_address, contract_address, use_nonce=True)
    129         tx = self.set_code(tx, data)
    130         tx = self.finalize(tx, tx_format, id_generator=id_generator)
    131         return tx
    132 
    133     
    134     def propose_blockwait(self, contract_address, sender_address, blockwait, block_deadline, target_vote_ppm=500000, tx_format=TxFormat.JSONRPC, id_generator=None):
    135         enc = ABIContractEncoder()
    136         enc.method('proposeInternal')
    137         enc.typ(ABIContractType.BYTES32)
    138         enc.typ(ABIContractType.BYTES32)
    139         enc.typ(ABIContractType.UINT256)
    140         enc.typ_literal('uint24')
    141         enc.bytes32('67ca084db32598c571e2ad2dc8b95679c3fa14c63213935dfd8f0a158ff65c57')
    142         blockwait_bytes = blockwait.to_bytes(length=32, byteorder='big')
    143         enc.bytes32(blockwait_bytes)
    144         enc.uint256(block_deadline)
    145         enc.uintn(target_vote_ppm, 24)
    146         data = add_0x(enc.get())
    147         tx = self.template(sender_address, contract_address, use_nonce=True)
    148         tx = self.set_code(tx, data)
    149         tx = self.finalize(tx, tx_format, id_generator=id_generator)
    150         return tx
    151 
    152 
    153     def add_option(self, contract_address, sender_address, proposal_idx, description, tx_format=TxFormat.JSONRPC, id_generator=None):
    154         enc = ABIContractEncoder()
    155         enc.method('addOption')
    156         enc.typ(ABIContractType.UINT256)
    157         enc.typ(ABIContractType.BYTES32)
    158         enc.uint256(proposal_idx)
    159         enc.bytes32(description)
    160         data = add_0x(enc.get())
    161         tx = self.template(sender_address, contract_address, use_nonce=True)
    162         tx = self.set_code(tx, data)
    163         tx = self.finalize(tx, tx_format, id_generator=id_generator)
    164         return tx
    165 
    166 
    167 #    def propose(self, contract_address, sender_address, description, block_deadline, target_vote_ppm=500000, options=[], tx_format=TxFormat.JSONRPC, id_generator=None):
    168 #        enc = ABIContractEncoder()
    169 #        if len(options) == 0: 
    170 #            enc.method('propose')
    171 #        else:
    172 #            enc.method('proposeMulti')
    173 #        enc.typ(ABIContractType.BYTES32)
    174 #        if len(options) > 0: 
    175 #            enc.typ_literal('bytes32[]')
    176 #        enc.typ(ABIContractType.UINT256)
    177 #        enc.typ_literal('uint24')
    178 #        enc.bytes32(description)
    179 #        if len(options) > 0: 
    180 #            enc.uint256(32*4)
    181 #        enc.uint256(block_deadline)
    182 #        enc.uintn(target_vote_ppm, 24)
    183 #        if len(options) > 0: 
    184 #            enc.uint256(len(options))
    185 #            for v in options:
    186 #                enc.bytes32(v)
    187 #        data = add_0x(enc.get())
    188 #        tx = self.template(sender_address, contract_address, use_nonce=True)
    189 #        tx = self.set_code(tx, data)
    190 #        tx = self.finalize(tx, tx_format, id_generator=id_generator)
    191 #        return tx
    192 
    193 
    194     def vote(self, contract_address, sender_address, value, option=None, tx_format=TxFormat.JSONRPC, id_generator=None):
    195         enc = ABIContractEncoder()
    196         if option == None:
    197             enc.method('vote')
    198             enc.typ(ABIContractType.UINT256)
    199         else:
    200             enc.method('voteOption')
    201             enc.typ(ABIContractType.UINT256)
    202             enc.typ(ABIContractType.UINT256)
    203         if option != None:
    204             enc.uint256(option)
    205         enc.uint256(value)
    206         data = add_0x(enc.get())
    207         tx = self.template(sender_address, contract_address, use_nonce=True)
    208         tx = self.set_code(tx, data)
    209         tx = self.finalize(tx, tx_format, id_generator=id_generator)
    210         return tx
    211 
    212 
    213     def vote_cancel(self, contract_address, sender_address, value, tx_format=TxFormat.JSONRPC, id_generator=None):
    214         enc = ABIContractEncoder()
    215         enc.method('voteCancel')
    216         enc.typ(ABIContractType.UINT256)
    217         enc.uint256(value)
    218         data = add_0x(enc.get())
    219         tx = self.template(sender_address, contract_address, use_nonce=True)
    220         tx = self.set_code(tx, data)
    221         tx = self.finalize(tx, tx_format, id_generator=id_generator)
    222         return tx
    223 
    224 
    225     def scan(self, contract_address, sender_address, proposal_index, count, tx_format=TxFormat.JSONRPC, id_generator=None):
    226         enc = ABIContractEncoder()
    227         enc.method('scan')
    228         enc.typ(ABIContractType.UINT256)
    229         enc.typ(ABIContractType.UINT8)
    230         enc.uint256(proposal_index)
    231         enc.uintn(count, 8)
    232         data = add_0x(enc.get())
    233         tx = self.template(sender_address, contract_address, use_nonce=True)
    234         tx = self.set_code(tx, data)
    235         tx = self.finalize(tx, tx_format, id_generator=id_generator)
    236         return tx
    237 
    238 
    239     def finalize_vote(self, contract_address, sender_address, tx_format=TxFormat.JSONRPC, id_generator=None):
    240         enc = ABIContractEncoder()
    241         enc.method('finalize')
    242         data = add_0x(enc.get())
    243         tx = self.template(sender_address, contract_address, use_nonce=True)
    244         tx = self.set_code(tx, data)
    245         tx = self.finalize(tx, tx_format, id_generator=id_generator)
    246         return tx
    247 
    248 
    249     def withdraw(self, contract_address, sender_address, tx_format=TxFormat.JSONRPC, id_generator=None):
    250         enc = ABIContractEncoder()
    251         enc.method('withdraw')
    252         data = add_0x(enc.get())
    253         tx = self.template(sender_address, contract_address, use_nonce=True)
    254         tx = self.set_code(tx, data)
    255         tx = self.finalize(tx, tx_format, id_generator=id_generator)
    256         return tx
    257 
    258 
    259     def get_proposal(self, contract_address, proposal_idx, sender_address=ZERO_ADDRESS, id_generator=None):
    260         j = JSONRPCRequest(id_generator)
    261         o = j.template()
    262         o['method'] = 'eth_call'
    263         enc = ABIContractEncoder()
    264         enc.method('getProposal')
    265         enc.typ(ABIContractType.UINT256)
    266         enc.uint256(proposal_idx)
    267         data = add_0x(enc.get())
    268         tx = self.template(sender_address, contract_address)
    269         tx = self.set_code(tx, data)
    270         o['params'].append(self.normalize(tx))
    271         o['params'].append('latest')
    272         o = j.finalize(o)
    273         return o
    274 
    275 
    276     def get_option(self, contract_address, proposal_idx, option_idx, sender_address=ZERO_ADDRESS, id_generator=None):
    277         j = JSONRPCRequest(id_generator)
    278         o = j.template()
    279         o['method'] = 'eth_call'
    280         enc = ABIContractEncoder()
    281         enc.method('getOption')
    282         enc.typ(ABIContractType.UINT256)
    283         enc.typ(ABIContractType.UINT256)
    284         enc.uint256(proposal_idx)
    285         enc.uint256(option_idx)
    286         data = add_0x(enc.get())
    287         tx = self.template(sender_address, contract_address)
    288         tx = self.set_code(tx, data)
    289         o['params'].append(self.normalize(tx))
    290         o['params'].append('latest')
    291         o = j.finalize(o)
    292         return o
    293 
    294 
    295     def option_count(self, contract_address, proposal_idx, sender_address=ZERO_ADDRESS, id_generator=None):
    296         j = JSONRPCRequest(id_generator)
    297         o = j.template()
    298         o['method'] = 'eth_call'
    299         enc = ABIContractEncoder()
    300         enc.method('optionCount')
    301         enc.typ(ABIContractType.UINT256)
    302         enc.uint256(proposal_idx)
    303         data = add_0x(enc.get())
    304         tx = self.template(sender_address, contract_address)
    305         tx = self.set_code(tx, data)
    306         o['params'].append(self.normalize(tx))
    307         o['params'].append('latest')
    308         o = j.finalize(o)
    309         return o
    310 
    311 
    312     def vote_count(self, contract_address, proposal_idx, option_idx=0, sender_address=ZERO_ADDRESS, id_generator=None):
    313         j = JSONRPCRequest(id_generator)
    314         o = j.template()
    315         o['method'] = 'eth_call'
    316         enc = ABIContractEncoder()
    317         enc.method('voteCount')
    318         enc.typ(ABIContractType.UINT256)
    319         enc.typ(ABIContractType.UINT256)
    320         enc.uint256(proposal_idx)
    321         enc.uint256(option_idx)
    322         data = add_0x(enc.get())
    323         tx = self.template(sender_address, contract_address)
    324         tx = self.set_code(tx, data)
    325         o['params'].append(self.normalize(tx))
    326         o['params'].append('latest')
    327         o = j.finalize(o)
    328         return o
    329 
    330 
    331     def block_wait_limit(self, contract_address, sender_address=ZERO_ADDRESS, id_generator=None):
    332         j = JSONRPCRequest(id_generator)
    333         o = j.template()
    334         o['method'] = 'eth_call'
    335         enc = ABIContractEncoder()
    336         enc.method('blockWaitLimit')
    337         data = add_0x(enc.get())
    338         tx = self.template(sender_address, contract_address)
    339         tx = self.set_code(tx, data)
    340         o['params'].append(self.normalize(tx))
    341         o['params'].append('latest')
    342         o = j.finalize(o)
    343         return o
    344 
    345 
    346     def current_proposal(self, contract_address, sender_address=ZERO_ADDRESS, id_generator=None):
    347         j = JSONRPCRequest(id_generator)
    348         o = j.template()
    349         o['method'] = 'eth_call'
    350         enc = ABIContractEncoder()
    351         enc.method('getCurrentProposal')
    352         data = add_0x(enc.get())
    353         tx = self.template(sender_address, contract_address)
    354         tx = self.set_code(tx, data)
    355         o['params'].append(self.normalize(tx))
    356         o['params'].append('latest')
    357         o = j.finalize(o)
    358         return o
    359 
    360 
    361 
    362     @classmethod
    363     def parse_proposal(self, v, serial=None):
    364         v = strip_0x(v)
    365         logg.debug("proposal {}".format(v))
    366 
    367         cursor = 64
    368         dec = ABIContractDecoder()
    369         dec.typ(ABIContractType.BYTES32)
    370         dec.typ(ABIContractType.UINT256)
    371         dec.typ(ABIContractType.UINT256)
    372         dec.typ(ABIContractType.UINT256)
    373         dec.typ(ABIContractType.UINT256)
    374         dec.typ(ABIContractType.UINT256) # actually uint24
    375         dec.typ(ABIContractType.ADDRESS)
    376         dec.typ(ABIContractType.UINT8)
    377 
    378         dec.val(v[cursor:cursor+64]) # description
    379         cursor += 64 # options pos
    380         cursor += 64 # optionsvotes pos
    381         cursor += 64
    382         dec.val(v[cursor:cursor+64])
    383         cursor += 64
    384         dec.val(v[cursor:cursor+64])
    385         cursor += 64
    386         dec.val(v[cursor:cursor+64])
    387         cursor += 64
    388         dec.val(v[cursor:cursor+64])
    389         cursor += 64
    390         dec.val(v[cursor:cursor+64])
    391         cursor += 64
    392         dec.val(v[cursor:cursor+64])
    393         cursor += 64
    394         dec.val(v[cursor:cursor+64])
    395         cursor += 64
    396 
    397         r = dec.get()
    398         o = Proposal(r[0],
    399                      cancelVotes=r[1],
    400                      supply=r[2],
    401                      total=r[3],
    402                      block_deadline=r[4],
    403                      target_vote_ppm=r[5],
    404                      proposer=r[6],
    405                      state=r[7],
    406                      serial=serial,
    407                      )
    408         return o
    409 
    410 
    411 def bytecode(**kwargs):
    412     return Voter.bytecode(version=kwargs.get('version'))
    413 
    414 
    415 def create(**kwargs):
    416     enc = CLIEncoder()
    417     (typ, token_address) = enc.translate('a', strip_0x(kwargs['token_address']))
    418     voter_registry = kwargs.get('voter_registry')
    419     if voter_registry != None:
    420         (typ, voter_registry) = enc.translate('a', strip_0x(voter_registry))
    421     proposer_registry = kwargs.get('proposer_registry')
    422     if proposer_registry != None:
    423         (typ, proposer_registry) = enc.translate('a', strip_0x(proposer_registry))
    424     return Voter.cargs(token_address=token_address, protect_supply=kwargs.get('protect_supply'), voter_registry=voter_registry, proposer_registry=proposer_registry, version=kwargs.get('version'))
    425 
    426 
    427 def args(v):
    428     if v == 'create':
    429         return (['token_address'], ['protect_supply', 'voter_registry', 'propose_registry'],)
    430     elif v == 'default' or v == 'bytecode':
    431         return ([], ['version'],)
    432     raise ValueError('unknown command: ' + v)