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)