diff --git a/models.py b/models.py index 8ff2c11..d5fd811 100644 --- a/models.py +++ b/models.py @@ -71,6 +71,7 @@ class ActiveContracts(SystemBase): id = Column('id', Integer, primary_key=True) contractName = Column('contractName', String) contractAddress = Column('contractAddress', String) + status = Column('status', String) class SystemData(SystemBase): __tablename__ = "systemData" @@ -85,6 +86,7 @@ class ContractParticipantMapping(SystemBase): id = Column('id', Integer, primary_key=True) participantAddress = Column('participantAddress', String) contractName = Column('contractName', String) + contractAddress = Column('contractAddress', String) tokenAmount = Column('tokenAmount', Float) diff --git a/parsing.py b/parsing.py index a0e0083..874d93b 100644 --- a/parsing.py +++ b/parsing.py @@ -6,6 +6,19 @@ operation = None address = None amount = None +months = { 'jan' : 1, +'feb' : 2, +'mar' : 3, +'apr' : 4, +'may' : 5, +'jun' : 6, +'jul' : 7, +'aug' : 8, +'sep' : 9, +'oct' : 10, +'nov' : 11, +'dec' : 12 } + def isTransfer(text): wordlist = ['transfer','send','give'] # keep everything lowercase @@ -145,7 +158,7 @@ def brackets_toNumber(item): return float(item[1:-1]) -def extractContractConditions(text, contracttype, marker): +def extractContractConditions(text, contracttype, marker, blocktime): rulestext = re.split('contract-conditions:\s*', text)[-1] #rulelist = re.split('\d\.\s*', rulestext) rulelist = [] @@ -168,10 +181,31 @@ def extractContractConditions(text, contracttype, marker): if contracttype == 'one-time-event*': extractedRules = {} + + for rule in rulelist: + if rule == '': + continue + elif rule[:10] == 'expirytime': + pattern = re.compile('[^expirytime\s*=\s*].*') + expirytime = pattern.search(rule).group(0).strip() + + try: + expirytime_split = expirytime.split(' ') + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], parsing.months[expirytime_split[1]], expirytime_split[2], expirytime_split[4]) + expirytime_object = arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace(tzinfo=expirytime_split[5]) + blocktime_object = arrow.get(blocktime) + if expirytime_object < blocktime_object: + print('Expirytime of the contract is earlier than the block it is incorporated in. This incorporation will be rejected ') + return None + extractedRules['expirytime'] = expirytime + except: + print('Expiry time not in right format') + return None + for rule in rulelist: if rule=='': continue - elif rule[:14]=='contractamount': + elif rule[:14] == 'contractamount': pattern = re.compile('[^contractamount\s*=\s*].*') searchResult = pattern.search(rule).group(0) contractamount = searchResult.split(marker)[0] @@ -179,20 +213,13 @@ def extractContractConditions(text, contracttype, marker): extractedRules['contractamount'] = float(contractamount) except: print("something is wrong with contract amount entered") - elif rule[:11]=='userchoices': + elif rule[:11] == 'userchoices': pattern = re.compile('[^userchoices\s*=\s*].*') conditions = pattern.search(rule).group(0) conditionlist = conditions.split('|') extractedRules['userchoices'] = {} for idx, condition in enumerate(conditionlist): extractedRules['userchoices'][idx] = condition.strip() - elif rule[:10]=='expirytime': - pattern = re.compile('[^expirytime\s*=\s*].*') - searchResult = pattern.search(rule).group(0).strip() - if searchResult == 'date-time': - continue - else: - extractedRules['expirytime'] = searchResult elif rule[:25] == 'minimumsubscriptionamount': pattern = re.compile('[^minimumsubscriptionamount\s*=\s*].*') searchResult = pattern.search(rule).group(0) @@ -209,9 +236,14 @@ def extractContractConditions(text, contracttype, marker): extractedRules['maximumsubscriptionamount'] = float(maximumsubscriptionamount) except: print("something is wrong with maximum subscription amount entered") + elif rule[:12] == 'payeeAddress': + pattern = re.compile('[^payeeAddress\s*=\s*].*') + searchResult = pattern.search(rule).group(0) + payeeAddress = searchResult.split(marker)[0] + extractedRules['payeeAddress'] = payeeAddress - if 'contractamount' in extractedRules and 'userchoices' in extractedRules: + if len(extractedRules)>1 and 'expirytime' in extractedRules: return extractedRules else: return None @@ -227,7 +259,7 @@ def extractTriggerCondition(text): # Combine test -def parse_flodata(string): +def parse_flodata(string, blockinfo): # todo Rule 20 - remove 'text:' from the start of flodata if it exists if string[0:5] == 'text:': @@ -301,7 +333,7 @@ def parse_flodata(string): elif incorporation and not transfer: contracttype = extractContractType(cleanstring) contractaddress = extractAddress(nospacestring) - contractconditions = extractContractConditions(cleanstring, 'one-time-event*', marker=hashList[0][:-1]) + contractconditions = extractContractConditions(cleanstring, contracttype, marker=hashList[0][:-1], blocktime=blockinfo['time']) if None not in [contracttype, contractaddress, contractconditions]: parsed_data = {'type': 'smartContractIncorporation', 'contractType': contracttype[:-1], @@ -326,7 +358,7 @@ def parse_flodata(string): # todo Rule 36 - Check for only a single @ and the substring "smart contract system says" in flodata, else reject - elif (len(hashList)==0 and len(atList)==1) and 'smart contract system says' in cleanstring: + elif (len(hashList)==0 and len(atList)==1): # Passing the above check means Smart Contract pays | exitcondition triggered from the committee # todo Rule 37 - Extract the trigger condition given by the committee. If its missing, reject triggerCondition = extractTriggerCondition(cleanstring) diff --git a/tracktokens-smartcontracts.py b/tracktokens-smartcontracts.py index 71dde71..db1b5fb 100755 --- a/tracktokens-smartcontracts.py +++ b/tracktokens-smartcontracts.py @@ -14,22 +14,9 @@ from sqlalchemy import create_engine, func, desc from models import SystemData, ActiveTable, ConsumedTable, TransferLogs, TransactionHistory, Base, ContractStructure, ContractBase, ContractParticipants, SystemBase, ActiveContracts, ContractParticipantMapping -months = { 'jan' : 1, -'feb' : 2, -'mar' : 3, -'apr' : 4, -'may' : 5, -'jun' : 6, -'jul' : 7, -'aug' : 8, -'sep' : 9, -'oct' : 10, -'nov' : 11, -'dec' : 12 } - - committeeAddressList = ['oUc4dVvxwK7w5MHUHtev8UawN3eDjiZnNx'] + def transferToken(tokenIdentification, tokenAmount, inputAddress, outputAddress): engine = create_engine('sqlite:///tokens/{}.db'.format(tokenIdentification), echo=True) Base.metadata.create_all(bind=engine) @@ -167,6 +154,125 @@ def transferToken(tokenIdentification, tokenAmount, inputAddress, outputAddress) return 1 +def checkLocaltriggerContracts(blockinfo): + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + # todo : filter activeContracts which only have local triggers + activeContracts = connection.execute('select contractName, contractAddress from activecontracts where status=="active" ').fetchall() + connection.close() + + for contract in activeContracts: + # Check if the contract has blockchain trigger or committee trigger + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(contract[0],contract[1]), echo=True) + connection = engine.connect() + # todo : filter activeContracts which only have local triggers + contractStructure = connection.execute('select * from contractstructure').fetchall() + contractStructure_T = list(zip(*contractstructure)) + + if 'exitconditions' in list(contractStructure_T[1]): + # This is a committee trigger contract + expiryTime = connection.execute('select value from contractstructure where attribute=="expiryTime"').fetchall()[0][0] + expirytime_split = expiryTime.split(' ') + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], parsing.months[expirytime_split[1]], + expirytime_split[2], expirytime_split[4]) + expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace( + tzinfo=expirytime_split[5]) + blocktime_object = parsing.arrow.get(blockinfo['time']) + + if blocktime_object > expirytime_object: + if 'minimumsubscriptionamount' in list(contractStructure_T[1]): + minimumsubscriptionamount = connection.execute('select value from contractstructure where attribute=="minimumsubscriptionamount"').fetchall()[0][0] + tokenAmount_sum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + if tokenAmount_sum < minimumsubscriptionamount: + # Initialize payback to contract participants + contractParticipants = connection.execute('select participantAddress, tokenAmount from contractparticipants').fetchall()[0][0] + + for participant in contractParticipants: + tokenIdentification = connection.execute('select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][0] + contractAddress = connection.execute('select * from contractstructure where attribute="contractAddress"').fetchall()[0][0] + returnval = transferToken(tokenIdentification, participant[1], contractAddress, participant[0]) + if returnval is None: + print("Something went wrong in the token transfer method while doing local Smart Contract Trigger") + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format(contract[0], contract[1])) + connection.close() + + + else: + # This is a blockchain trigger contract + if 'maximumsubscriptionamount' in list(contractStructure_T[1]): + maximumsubscriptionamount = connection.execute('select value from contractstructure where attribute=="maximumsubscriptionamount"').fetchall()[0][0] + tokenAmount_sum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + if tokenAmount_sum >= maximumsubscriptionamount: + # Trigger the contract + payeeAddress = connection.execute('select * from contractstructure where attribute="payeeAddress"').fetchall()[0][0] + tokenIdentification = connection.execute('select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][0] + contractAddress = connection.execute('select * from contractstructure where attribute="contractAddress"').fetchall()[0][0] + returnval = transferToken(tokenIdentification, tokenAmount_sum, contractAddress, payeeAddress) + if returnval is None: + print("Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + contract[0], contract[1])) + connection.close() + + expiryTime = connection.execute('select value from contractstructure where attribute=="expiryTime"').fetchall()[0][0] + expirytime_split = expiryTime.split(' ') + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], parsing.months[expirytime_split[1]], expirytime_split[2], expirytime_split[4]) + expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace( + tzinfo=expirytime_split[5]) + blocktime_object = parsing.arrow.get(blockinfo['time']) + + if blocktime_object > expirytime_object: + if 'minimumsubscriptionamount' in list(contractStructure_T[1]): + minimumsubscriptionamount = connection.execute('select value from contractstructure where attribute=="minimumsubscriptionamount"').fetchall()[0][0] + tokenAmount_sum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + if tokenAmount_sum < minimumsubscriptionamount: + # Initialize payback to contract participants + contractParticipants = connection.execute( + 'select participantAddress, tokenAmount from contractparticipants').fetchall()[0][0] + + for participant in contractParticipants: + tokenIdentification = connection.execute( + 'select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][ + 0] + contractAddress = connection.execute( + 'select * from contractstructure where attribute="contractAddress"').fetchall()[0][0] + returnval = transferToken(tokenIdentification, participant[1], contractAddress, + participant[0]) + if returnval is None: + print( + "Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + contract[0], contract[1])) + connection.close() + + # Trigger the contract + payeeAddress = connection.execute('select * from contractstructure where attribute="payeeAddress"').fetchall()[0][0] + tokenIdentification = connection.execute('select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][0] + contractAddress = connection.execute('select * from contractstructure where attribute="contractAddress"').fetchall()[0][0] + returnval = transferToken(tokenIdentification, tokenAmount_sum, contractAddress, payeeAddress) + if returnval is None: + print("Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + contract[0], contract[1])) + connection.close() + + + def startWorking(transaction_data, parsed_data, blockinfo): # Do the necessary checks for the inputs and outputs @@ -262,24 +368,33 @@ def startWorking(transaction_data, parsed_data, blockinfo): # todo Rule 46 - If the transfer type is smart contract, then call the function transferToken to do sanity checks & lock the balance elif parsed_data['transferType'] == 'smartContract': + # check if the contract is active + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + contractDetails = connection.execute( + 'select contractName, contractAddress from activecontracts where status=="active"').fetchall() + connection.close() + contractList = [] - #Check if the db exists - apppath = os.path.dirname(os.path.realpath(__file__)) - dirpath = os.path.join(apppath, 'smartContracts','{}.db'.format(parsed_data['contractName'])) - if not os.path.exists(dirpath): - print('Smart contract with the given name doesn\'t exist') + counter = 0 + for contract in contractDetails: + if contract[0] == parsed_data['contractName'] and contract[1] == outputlist[0]: + counter = counter + 1 + + if counter != 1: + print('Active Smart contract with the given name doesn\'t exist') return # Check if the contract has expired - engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True) + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) ContractBase.metadata.create_all(bind=engine) session = sessionmaker(bind=engine)() - result = session.query(ContractStructure).filter_by(attribute='expirytime').all() + result = session.query(ContractStructure).filter_by(attribute='expiryTime').all() if result: #now parse the expiry time in python expirytime = result[0].value.strip() expirytime_split = expirytime.split(' ') - parse_string = '{}/{}/{} {}'.format( expirytime_split[3], months[expirytime_split[1]], expirytime_split[2], expirytime_split[4]) + parse_string = '{}/{}/{} {}'.format( expirytime_split[3], parsing.months[expirytime_split[1]], expirytime_split[2], expirytime_split[4]) expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace(tzinfo=expirytime_split[5]) blocktime_object = parsing.arrow.get(blockinfo['time']) @@ -289,53 +404,92 @@ def startWorking(transaction_data, parsed_data, blockinfo): session.close() + # Check if usercondition given is in right format if it exists as part of contractstructure + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + activeContracts = connection.execute('select value from contractstructure where attribute=="exitconditions"').fetchall() + connection.close() + + if parsed_data['userChoice'] is not in list(activeContracts[0]): + print("Wrong userchoice entered\nThis smartContract pariticipation will be rejected") + return + + # Check if contractAmount is part of the contract structure, and enforce it if it is + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + contractAmount = connection.execute('select value from contractstructure where attribute=="contractAmount"').fetchall() + connection.close() + + if contractAmount is not None: + if contractAmount[0][0] != parsed_data['tokenAmount']: + print('Token amount being transferred is not part of the contract structure\nThis transaction will be discarded') + return + + # Check if maximum subscription amount has reached - engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True) + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) ContractBase.metadata.create_all(bind=engine) session = sessionmaker(bind=engine)() result = session.query(ContractStructure).filter_by(attribute='maximumsubscriptionamount').all() - session.close() if result: # now parse the expiry time in python maximumsubscriptionamount = float(result[0].value.strip()) - engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True) - ContractBase.metadata.create_all(bind=engine) - session = sessionmaker(bind=engine)() result = session.query(ContractStructure).filter_by(attribute='maximumsubscriptionamount').all() amountDeposited = session.query(func.sum(ContractParticipants.tokenAmount)).all()[0][0] - session.close() if amountDeposited is None: amountDeposited = 0 if amountDeposited >= maximumsubscriptionamount: - print('Maximum subscription amount reached') + print('Maximum subscription amount reached\n Money will be refunded') return + else: + if parsed_data['tokenAmount']+amountDeposited <= maximumsubscriptionamount: + # Check if the tokenAmount being transferred exists in the address & do the token transfer + returnval = transferToken(parsed_data['tokenIdentification'], parsed_data['tokenAmount'], inputlist[0], outputlist[0]) + if returnval is not None: + # Store participant details in the smart contract's db + session.add(ContractParticipants(participantAddress=inputadd, tokenAmount=parsed_data['tokenAmount'], userChoice=parsed_data['userChoice'])) + session.commit() + session.close() + # Store a mapping of participant address -> Contract participated in + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractParticipantMapping(participantAddress=inputadd, tokenAmount=parsed_data['tokenAmount'], + contractName = parsed_data['contractName'], contractAddress = outputlist[0])) + session.commit() - # Check if the tokenAmount being transferred exists in the address & do the token transfer - returnval = transferToken(parsed_data['tokenIdentification'], parsed_data['tokenAmount'], inputlist[0], outputlist[0]) - if returnval is not None: - # Store participant details in the smart contract's db - engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True) - ContractBase.metadata.create_all(bind=engine) - session = sessionmaker(bind=engine)() - session.add(ContractParticipants(participantAddress=inputadd, tokenAmount=parsed_data['tokenAmount'], userChoice=parsed_data['userChoice'])) - session.commit() - session.close() + else: + print("Something went wrong in the smartcontract token transfer method") + else: + # Transfer only part of the tokens users specified, till the time it reaches maximumamount + returnval = transferToken(parsed_data['tokenIdentification'], maximumsubscriptionamount-amountDeposited, + inputlist[0], outputlist[0]) + if returnval is not None: + # Store participant details in the smart contract's db + session.add(ContractParticipants(participantAddress=inputadd, + tokenAmount=maximumsubscriptionamount-amountDeposited, + userChoice=parsed_data['userChoice'])) + session.commit() + session.close() - # Store a mapping of participant address -> Contract participated in - engine = create_engine('sqlite:///system.db', echo=True) - SystemBase.metadata.create_all(bind=engine) - session = sessionmaker(bind=engine)() - session.add(ContractParticipantMapping(participantAddress=inputadd, tokenAmount=parsed_data['tokenAmount'], - contractName = parsed_data['contractName'])) - session.commit() - session.close() + # Store a mapping of participant address -> Contract participated in + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ContractParticipantMapping(participantAddress=inputadd, + tokenAmount=maximumsubscriptionamount-amountDeposited, + contractName=parsed_data['contractName'], contractAddress = outputlist[0])) + session.commit() + session.close() + else: + print("Something went wrong in the smartcontract token transfer method") + + session.close() - else: - print("Something went wrong in the smartcontract token transfer method") # todo Rule 47 - If the parsed data type is token incorporation, then check if the name hasn't been taken already # if it has been taken then reject the incorporation. Else incorporate it @@ -363,16 +517,34 @@ def startWorking(transaction_data, parsed_data, blockinfo): # todo Rule 48 - If the parsed data type if smart contract incorporation, then check if the name hasn't been taken already # if it has been taken then reject the incorporation. elif parsed_data['type'] == 'smartContractIncorporation': - if not os.path.isfile('./smartContracts/{}.db'.format(parsed_data['contractName'])): + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + contractDetails = connection.execute('select contractName, contractAddress from activecontracts').fetchall() + connection.close() + + counter = 0 + # Check if the combination of contract name and address exists + for contract in contractDetails: + if contract[0] == parsed_data['contractName'] and contract[1] == parsed_data['contractAddress']: + counter = counter + 1 + + + if counter == 0: # todo Rule 49 - If the contract name hasn't been taken before, check if the contract type is an authorized type by the system if parsed_data['contractType'] == 'one-time-event': print("Smart contract is of the type one-time-event") + + # userchoice and payeeAddress conditions cannot come together. Check for it + if 'userchoice' in parsed_data['contractConditions'] and 'payeeAddress' in parsed_data['contractConditions']: + print('Both userchoice and payeeAddress provided as part of the Contract conditions\nIncorporation of Smart Contract with the name {} will be rejected'.format(parsed_data['contractName'])) + return + # todo Rule 50 - Contract address mentioned in flodata field should be same as the receiver FLO address on the output side # henceforth we will not consider any flo private key initiated comment as valid from this address # Unlocking can only be done through smart contract system address if parsed_data['contractAddress'] == inputadd: - print("Hey I have passed the first test for smart contract") - engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True) + dbName = '{}-{}'.format(parsed_data['contractName'], parsed_data['contractAddress']) + engine = create_engine('sqlite:///smartContracts/{}.db'.format(dbName), echo=True) ContractBase.metadata.create_all(bind=engine) session = sessionmaker(bind=engine)() session.add(ContractStructure(attribute='contractType', index=0, value=parsed_data['contractType'])) @@ -385,12 +557,13 @@ def startWorking(transaction_data, parsed_data, blockinfo): ContractStructure(attribute='flodata', index=0, value=parsed_data['flodata'])) session.add( - ContractStructure(attribute='contractamount', index=0, - value=parsed_data['contractConditions']['contractamount'])) - if 'expirytime' in parsed_data['contractConditions']: - session.add( ContractStructure(attribute='expirytime', index=0, value=parsed_data['contractConditions']['expirytime'])) + if 'contractamount' in parsed_data['contractConditions']: + session.add( + ContractStructure(attribute='contractamount', index=0, + value=parsed_data['contractConditions']['contractamount'])) + if 'minimumsubscriptionamount' in parsed_data['contractConditions']: session.add( ContractStructure(attribute='minimumsubscriptionamount', index=0, @@ -399,49 +572,112 @@ def startWorking(transaction_data, parsed_data, blockinfo): session.add( ContractStructure(attribute='maximumsubscriptionamount', index=0, value=parsed_data['contractConditions']['maximumsubscriptionamount'])) - for key, value in parsed_data['contractConditions']['userchoices'].items(): - session.add(ContractStructure(attribute='exitconditions', index=key, value=value)) - session.commit() - session.close() + if 'userchoices' in parsed_data['contractConditions']: + for key, value in parsed_data['contractConditions']['userchoices'].items(): + session.add(ContractStructure(attribute='exitconditions', index=key, value=value)) + # Store smart contract address in system's db, to be ignored during future transfers + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ActiveContracts(contractName=parsed_data['contractName'],contractAddress=parsed_data['contractAddress'], status='active')) + elif 'payeeAddress' in parsed_data['contractConditions']: + # in this case, expirydate( or maximumamount) is the trigger internally. Keep a track of expiry dates + engine = create_engine('sqlite:///system.db', echo=True) + SystemBase.metadata.create_all(bind=engine) + session = sessionmaker(bind=engine)() + session.add(ActiveContracts(contractName=parsed_data['contractName'], contractAddress=parsed_data['contractAddress'], expiryTime=parsed_data['contractConditions']['expirytime'], maximumSubscription=parsed_data['contractConditions']['maximumsubscriptionamount'], status='active')) + else: + print('Neither userchoice nor payeeAddress provided as part of Smart Contract incorporation of the name {}\n This contract incorporation will be rejected'.format(parsed_data['contractName'])) - # Store smart contract address in system's db, to be ignored during future transfers - engine = create_engine('sqlite:///system.db', - echo=True) - SystemBase.metadata.create_all(bind=engine) - session = sessionmaker(bind=engine)() - session.add( - ActiveContracts(contractName=parsed_data['contractName'], contractAddress=parsed_data['contractAddress'])) session.commit() session.close() + else: + print('Contract Incorporation rejected as address in Flodata and input address are different') + else: - print('Transaction rejected as a smartcontract with same name has already been incorporated') + print('Transaction rejected as a smartcontract with same name is active currently') + elif parsed_data['type'] == 'smartContractPays': print('Found a transaction of the type smartContractPays') - # Check if input address is a committee address if inputlist[0] in committeeAddressList: # Check if the output address is an active Smart contract address engine = create_engine('sqlite:///system.db', echo=True) connection = engine.connect() - activeContracts = connection.execute('select * from activecontracts').fetchall() + # todo : Get only activeContracts which have non-local trigger ie. committee triggers them + + contractDetails = connection.execute('select contractName, contractAddress from activecontracts where status=="active"').fetchall() + connection.close() + contractList = [] + + counter = 0 + for contract in contractDetails: + if contract[0] == parsed_data['contractName'] and contract[1] == outputlist[0]: + counter = counter + 1 + + if counter != 1: + print('Active Smart contract with the given name doesn\'t exist\n This committee trigger will be rejected') + return + + # Check if the contract has maximumsubscriptionamount and if it has reached it + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) + connection = engine.connect() + # todo : filter activeContracts which only have local triggers + contractStructure = connection.execute('select * from contractstructure').fetchall() + contractStructure_T = list(zip(*contractstructure)) + + if 'maximumsubscriptionamount' in list(contractStructure_T[1]): + maximumsubscriptionamount = connection.execute('select value from contractstructure where attribute=="maximumsubscriptionamount"').fetchall()[0][0] + tokenAmount_sum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + if tokenAmount_sum >= maximumsubscriptionamount: + # Trigger the contract + contractWinners = connection.execute( + 'select * from contractparticipants where userChoice="{}"'.format( + parsed_data['triggerCondition'])).fetchall() + tokenSum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + winnerSum = connection.execute( + 'select sum(tokenAmount) from contractparticipants where userChoice="{}"'.format( + parsed_data['triggerCondition'])).fetchall() + tokenIdentification = connection.execute( + 'select value from contractstructure where attribute="tokenIdentification"').fetchall()[0][0] + connection.close() + + for winner in contractWinners: + returnval = transferToken(tokenIdentification, (winner[2] / winnerSum) * tokenSum, + outputlist[0], winner[1]) + if returnval is None: + print("CRITICAL ERROR | Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format(parsed_data['contractName'], outputlist[0])) + connection.close() + return + + + # Check if contract has passed expiry time + expiryTime = connection.execute('select value from contractstructure where attibute=="expiryTime"').fetchall()[0][0] + expirytime_split = expiryTime.split(' ') + parse_string = '{}/{}/{} {}'.format(expirytime_split[3], parsing.months[expirytime_split[1]], + expirytime_split[2], expirytime_split[4]) + expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace( + tzinfo=expirytime_split[5]) + blocktime_object = parsing.arrow.get(blockinfo['time']) connection.close() - # Change columns into rows - https://stackoverflow.com/questions/44360162/how-to-access-a-column-in-a-list-of-lists-in-python - activeContracts = list(zip(*activeContracts)) - if outputlist[0] in activeContracts[2] and parsed_data['contractName'] in activeContracts[1]: - + if blocktime_object > expirytime_object: # Check if the minimum subscription amount has been reached if it exists as part of the structure - engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True) + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) ContractBase.metadata.create_all(bind=engine) session = sessionmaker(bind=engine)() result = session.query(ContractStructure).filter_by(attribute='minimumsubscriptionamount').all() session.close() if result: - # now parse the expiry time in python minimumsubscriptionamount = float(result[0].value.strip()) - engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) ContractBase.metadata.create_all(bind=engine) session = sessionmaker(bind=engine)() @@ -453,33 +689,64 @@ def startWorking(transaction_data, parsed_data, blockinfo): amountDeposited = 0 if amountDeposited < minimumsubscriptionamount: - print('Minimum subscription amount reached') + print('Minimum subscription amount hasn\'t been reached\n The token will be returned back') + # Initialize payback to contract participants + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), + echo=True) + connection = engine.connect() + contractParticipants = connection.execute( + 'select participantAddress, tokenAmount from contractparticipants').fetchall()[0][0] + + for participant in contractParticipants: + tokenIdentification = connection.execute( + 'select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][ + 0] + contractAddress = connection.execute( + 'select * from contractstructure where attribute="contractAddress"').fetchall()[0][0] + returnval = transferToken(tokenIdentification, participant[1], contractAddress, + participant[0]) + if returnval is None: + print( + "CRITICAL ERROR | Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + parsed_data['contractName'], outputlist[0])) + connection.close() return - engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True) + engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True) connection = engine.connect() contractWinners = connection.execute('select * from contractparticipants where userChoice="{}"'.format(parsed_data['triggerCondition'])).fetchall() tokenSum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0] + winnerSum = connection.execute('select sum(tokenAmount) from contractparticipants where userChoice="{}"'.format(parsed_data['triggerCondition'])).fetchall() tokenIdentification = connection.execute('select value from contractstructure where attribute="tokenIdentification"').fetchall()[0][0] connection.close() - contractWinners = list(zip(*contractWinners)) - for address in contractWinners[1]: - transferToken(tokenIdentification, tokenSum/len(contractWinners[1]), outputlist[0], address) - - else: - print('This trigger doesn\'t apply to an active contract. It will be discarded') - + for winner in contractWinners: + returnval = transferToken(tokenIdentification, (winner[2]/winnerSum)*tokenSum, outputlist[0], winner[1]) + if returnval is None: + print( + "CRITICAL ERROR | Something went wrong in the token transfer method while doing local Smart Contract Trigger") + return + engine = create_engine('sqlite:///system.db', echo=True) + connection = engine.connect() + connection.execute( + 'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format( + parsed_data['contractName'], outputlist[0])) + connection.close() else: print('Input address is not part of the committee address list. This trigger is rejected') -# todo Rule 1 - Read command line arguments to reset the databases as blank -# Rule 2 - Read config to set testnet/mainnet -# Rule 3 - Set flo blockexplorer location depending on testnet or mainnet -# Rule 4 - Set the local flo-cli path depending on testnet or mainnet -# Rule 5 - Set the block number to scan from +# todo Rule 1 - Read command line arguments to reset the databases as blank +# Rule 2 - Read config to set testnet/mainnet +# Rule 3 - Set flo blockexplorer location depending on testnet or mainnet +# Rule 4 - Set the local flo-cli path depending on testnet or mainnet +# Rule 5 - Set the block number to scan from # Read command line arguments @@ -556,7 +823,7 @@ print("current_block_height : " + str(current_index)) for blockindex in range( startblock, current_index ): print(blockindex) - if blockindex == 590795: + if blockindex == 593777: print('hello') # Scan every block @@ -580,7 +847,7 @@ for blockindex in range( startblock, current_index ): # todo Rule 9 - Reject all noise transactions. Further rules are in parsing.py - parsed_data = parsing.parse_flodata(text) + parsed_data = parsing.parse_flodata(text, blockinfo) if parsed_data['type'] != 'noise': print(blockindex) print(parsed_data['type']) @@ -594,3 +861,6 @@ for blockindex in range( startblock, current_index ): session.commit() session.close() + # Check smartContracts which will be triggered locally, and not by the contract committee + checkLocaltriggerContracts(blockinfo) +