def get_asset_balance(cls, contract_name): contract = cls.erc20_contracts.get(contract_name, None) if contract == None: raise Exception('Not supported contract name for swap:%s' % contract_name) concise = ConciseContract(contract) return cls.Wei2Satoshi(concise.balanceOf(config.ETHEREUM_SCAN_ADDRESS), concise.decimals())[0]
class ContractInterface: ''' This python interface mimics the solidity smart contract interface deployed on the blockchain and its methods. This way, it's much simpler and easier on the eyes the interaction with the smart contract functions from the client side ''' CONTRACT_PATH = path.dirname( path.abspath(__file__)) + "/../contracts/token.sol" COMPILED_CONTRACT_PATH = path.dirname( path.abspath(__file__)) + "/../contracts/contract_compiled.pickle" # Client number is only for tests def __init__(self, account=None, ip_address=None, mac_address=None, w3=None, client_number: int = None): ''' Default constructor of the interface class, with parameters that make the code much cleaner in our client API . For now the only parameter is the client number which identifies which of the default accounts provided by ganache-cli the client sets as its default one {1 - 9} since 0 is for the server and owner of the contract ''' # In case there's no w3 given we initialize to the default server if w3 is None: self.w3 = Web3(HTTPProvider("http://localhost:8545")) #self.w3 = Web3(HTTPProvider("http://192.168.0.29:8545")) else: self.w3 = w3 # Regular register (not from proxy) if ip_address is None and mac_address is None: self.IP = utils.getIP() self.MAC = utils.getMAC() else: self.IP = ip_address self.MAC = mac_address self.contract = self._getContract(self.w3) #self.contract = self._load_contract(self.w3) # The ConciseContract class is much better for reaidng variables straight away. # If our goal is not transact something, we'll use the Concise version of the contract self.contractConcise = ConciseContract(self.contract) # set the default account for the client, if it's None we'll register and we'll get a new one # Only for tests if client_number is not None: self.w3.eth.defaultAccount = self.w3.eth.accounts[client_number] self.account = self.w3.eth.defaultAccount else: # Usamos el w3 del proxy en ese caso # The client's own address is in the account variable self.account = account # The default account is however the one of the server # DATA FOR LEASES: # The dictionary is of the shape { grant_id : amount } so for the total memory we # just add the values self.remoteStorage = {} # The dictionary is of the shape { grant_id : amount } where amount is a number from # 0 to 100. 100 would mean that the amount is equal to all the available storage on # the remote server self.remoteCPU = {} # Load the reservations self._load_reservations() def register(self): ''' Registers the client in the server's contract, and sends the useful information so that the server can recognize each client ''' tx_hash = None print('Registering with', self.account) # Regular register tx_hash = self.contract.functions.register(self.account, self.IP, self.MAC).transact() self.w3.eth.waitForTransactionReceipt(tx_hash) # build a listener so we can get the server's response server_answer_filter = self.contract.events.RegisterResponse.createFilter( fromBlock=0, toBlock='latest', argument_filters={'account': self.account}) print('Created filter to wait for server register response') while True: response = server_answer_filter.get_new_entries() if len(response) != 0: for grant in response: args = grant['args'] if args['accepted']: print('Accepted request') # If we don't have an account we stablish it if self.account is None: self.account = args['account'] self.w3.eth.defaultAccount = self.account self._pickle_address() print('Address pickled') else: print('Grant rejected') break else: print('No answer yet') time.sleep(1) print('Balance after register:', self.contractConcise.balanceOf(self.account), self.contractConcise.symbol()) del server_answer_filter def transfer(self, to: str, amount: int): ''' Transfers to the specified ethereum account (expressed as a hexadecimal string) the specified amount of IoT (tokens) ''' print('Balances before: {}, {}'.format( self.contractConcise.balanceOf(self.account), self.contractConcise.balanceOf(to))) tx_hash = self.contract.functions.transfer(self.account, to, amount).transact() self.w3.eth.waitForTransactionReceipt(tx_hash) print('Balances after: {}, {}'.format( self.contractConcise.balanceOf(self.account), self.contractConcise.balanceOf(to))) def request_storage(self, amount: int): ''' Requests storage from the server ''' print('Asking for {} for account {}'.format(amount, self.account)) tx_hash = self.contract.functions.getStorage(self.account, amount).transact() self.w3.eth.waitForTransactionReceipt(tx_hash) # Wait for the storage grant and filter just the ones for us petitionFilter = self.contract.events.StorageResponse.createFilter( fromBlock=0, toBlock='latest', argument_filters={'account': self.account}) print('Created filter for storage grants') while True: response = petitionFilter.get_new_entries() if len(response) != 0: for grant in response: args = grant['args'] if args['accepted']: print('Accepted request') self.remoteStorage[args['grantID']] = int( args['amount']) print('new dict:', self.remoteStorage) else: print('Grant rejected') break else: print('No answer yet') time.sleep(1) # We no longer need the filter del petitionFilter self._update_reservations() def free_storage(self, id): ''' Free the storage acquired from the server ''' print('Removing item with id=', id) tx_hash = self.contract.functions.freeStorage(self.account, id).transact() self.w3.eth.waitForTransactionReceipt(tx_hash) print('freed storage') # Pop the item self.remoteStorage.pop(id) self._update_reservations() def request_computing_power(self, amount: int): """ Request computing power from the server """ print('Asking for {}% of cpu for account {}'.format( amount, self.account)) # Transact and wait for the transaction receipt tx_hash = self.contract.functions.getComputingPower( self.account, amount).transact() self.w3.eth.waitForTransactionReceipt(tx_hash) # Filter the answers to get if our request was granted petition_filter = self.contract.events.CPUResponse.createFilter( fromBlock=0, toBlock='latest', argument_filters={'account': self.account}) print('Created computing power filter for account', self.account) # Wait for our response while True: response = petition_filter.get_new_entries() if len(response) != 0: for answer in response: args = answer['args'] if args['accepted']: print('Our computing power request was granted') self.remoteCPU[args['grantID']] = int(args['amount']) print('New dict: ', self.remoteCPU) else: print('Grant rejected') break else: print('No answer to cpu request yet') time.sleep(1) del petition_filter self._update_reservations() def free_computing_power(self, id): ''' Free the cpu reservation ''' print('Removing cpu reservation with id', id) tx_hash = self.contract.functions.freeComputingPower(self.account, id).transact() self.w3.eth.waitForTransactionReceipt(tx_hash) print('Freed cpu storage') # Pop item self.remoteCPU.pop(id) self._update_reservations() def force_error(self): """ Method to test whether the onlyOwner modifier works properly """ print('Trying to force an error') tx_hash = self.contract.functions._freeStorage(self.account, 500).transact() self.w3.eth.waitForTransactionReceipt(tx_hash) print('Received transaction hash') def _pickle_address(self): """ Saves the device's address in a pickle file """ with open(ADDRESS_PICKLE_FILE, 'wb') as pickle_file: # dump the address pickle.dump(self.account, pickle_file) def _unpickle_address(self): """ Gets this device's address from the file """ if os.path.exists(ADDRESS_PICKLE_FILE): with open(ADDRESS_PICKLE_FILE, 'rb') as pickle_file: self.account = pickle.load(pickle_file) self.w3.eth.defaultAccount = self.account #self.w3.eth.defaultAccount = self.w3.eth.accounts[client_number] #self.account = self.w3.eth.defaultAccount def _update_reservations(self): """ Updates the reservations in the pickle file after a reservation is made or freed """ with open(RESERVATIONS_PICKLE_FILE, 'wb') as pickle_file: # save the dictionaries pickle.dump(self.remoteStorage, pickle_file) pickle.dump(self.remoteCPU, pickle_file) def _load_reservations(self): """ Use pickle to load the remote storage and computing power reservations. This way we make them persistent in case of a reboot of the service. The variables inside the pickle file keep the same name """ if os.path.exists(RESERVATIONS_PICKLE_FILE): with open(RESERVATIONS_PICKLE_FILE, 'rb') as pickle_file: # restore the dictionaries self.remoteStorage = pickle.load(pickle_file) self.remoteCPU = pickle.load(pickle_file) print('Restored storage reservations:\n', self.remoteStorage) print('Restored cpu reservations:\n', self.remoteCPU) def _load_contract(self, w3: Web3): with open(self.COMPILED_CONTRACT_PATH, 'rb') as pickle_file: interface = pickle.load(pickle_file) contract = w3.eth.contract(abi=interface['abi'], address=CONTRACT_ADDRESS) return contract def _getContract(self, w3: Web3): with open(self.CONTRACT_PATH, "r") as contract_file: global CONTRACT_ADDRESS source_code = contract_file.read() compiled = compile_source(source_code) interface = compiled['<stdin>:IoToken'] contract = w3.eth.contract(abi=interface['abi'], address=CONTRACT_ADDRESS) return contract
generated_price = web3.eth.generateGasPrice() c_contract = ConciseContract(contract) t_contract = ImplicitContract(contract) print('Network gas price:', web3.fromWei(web3.eth.gasPrice, 'Gwei')) print('Computed gas price:', web3.fromWei(generated_price, 'Gwei')) # print(web3.eth.accounts) # print(web3.txpool.content) params = {'from': OWNER, 'gasPrice': generated_price} # mint(t_contract, web3.eth.accounts[0], web3.toWei(1, 'Ether'), tx_p=params) # mint(t_contract, '0x8862cE71FDCDC386D5a9b6BB5640a8FefD6DDAd0', web3.toWei(100000, 'Ether'), tx_p=params) # mint(t_contract, '0xfF989e7D397e6fF1026429A87d7A5eF7c6B09c27', web3.toWei(100000, 'Ether'), tx_p=params) print('OWNER', c_contract.balanceOf(OWNER)) print('RECIPIENT', c_contract.balanceOf(RECIPIENT)) print('FEE_RECIPIENT', c_contract.balanceOf(FEE_RECIPIENT)) print('starting transaction...') receipt = start_escrow(t_contract, 11223344, RECIPIENT, web3.toWei(50, 'Gwei'), tx_p=params) print(receipt) print('releasing transaction...') receipt = release_escrow(t_contract, 11223344, FEE_RECIPIENT, tx_p=params) print(receipt) print('OWNER', c_contract.balanceOf(OWNER))
def test_deploy( pack_deploy, instantiate, accounts, assert_tx_failed, pack_types, pack_prices, get_receipt, get_logs_for_event, w3, #Parameters bundle_sizes, bundle_caps): BLOCK_GAS_LIMIT = 8e6 # Well over double this now, but a possible concern. # Deploy an oracle and a Link Token (pack, processor, referrals, cards, vault, pack_r, processor_r, referrals_r, cards_r, vault_r) = pack_deploy() receipts = { "pack": (pack, pack_r), "processor": (processor, processor_r), "referrals": (referrals, referrals_r), "cards": (cards, cards_r), "vault": (vault, vault_r) } if DEBUG: print("") # new line for name, r in receipts.items(): if DEBUG: print("Gas used to deploy {}:\t{}".format(name, r[1]['gasUsed'])) assert r[1]['gasUsed'] < BLOCK_GAS_LIMIT, "{} uses more gas than block_gas_limit!".format(name) assert r[0].address != 0, "Address returned not expected for {}".format(name) addPack = cards.functions.addPack(pack.address) assert_tx_failed(addPack, {'from': accounts[1]}) addPack.transact({'from': accounts[0]}) tokens = {} # Now each of the bundle tokens need to be deployed # For each, the pack has to be set to point to the bundle and stipulate it's price and size. for i, (rarity, rarity_value) in enumerate(pack_types.items()): setPack = pack.functions.setPack( rarity_value, pack_prices[rarity], "Genesis {} Bundle".format(rarity), "BND{}".format(rarity[0]), bundle_sizes[i], bundle_caps[i], ) assert_tx_failed(setPack, {'from': accounts[1]}) tx_hash = setPack.transact({'from': accounts[0]}) rcpt = get_receipt(tx_hash) assert len(rcpt['logs']) == 1, "Should have one event emitted." assert rcpt['gasUsed'] < BLOCK_GAS_LIMIT, "Too much gas for block!" logs = get_logs_for_event(pack.events.PackAdded, tx_hash) tokens[rarity] = instantiate(logs[0]['args']['bundle'], abi=None, contract="Bundle") processor.functions.setCanSell(tokens[rarity].address, True).transact({'from': accounts[0]}) if DEBUG: print("Created {} Bundles: Size: {}, Cap: {}, Gas: {}".format( rarity, bundle_sizes[i], bundle_caps[i], rcpt['gasUsed']) ) print("Approval Statuses:") for address in accounts[:5]: print("{}: {}".format(address, processor.functions.approvedSellers(accounts[0]).call())) # For each of the tokens, check their variables and try to purchase a bunch of them, # making sure we can't purchase too many for i, (rarity, token) in enumerate(tokens.items()): c = ConciseContract(token) assert c.totalSupply() == 0, "{} tokens should start with supply of zero".format(rarity) assert c.balanceOf(accounts[0]) == 0, "No user should have balance before mint." assert c.balanceOf(accounts[1]) == 0, "No user should have balance before mint." assert c.name() == "Genesis {} Bundle".format(rarity), "Incorrect token name" assert c.symbol() == "BND{}".format(rarity[0]), "Incorrect token symbol" assert c.decimals() == 0, "Incorrect token decimals" assert c.cap() == bundle_caps[i], "Incorrect cap for token" assert c.packType() == pack_types[rarity], "Incorrect packType for token" BUNDLES_TO_BUY = 6 for _ in range(bundle_caps[i] if bundle_caps[i] > 0 else BUNDLES_TO_BUY): # Shouldn't be able to buy if we send no money assert_tx_failed( token.functions.purchaseFor(accounts[0], 1, accounts[1]), {'from': accounts[0]} ) assert processor.functions.approvedSellers(token.address).call(), "Bundle should be approved to sell." tx_hash = token.functions.purchaseFor(accounts[0], 1, accounts[1])\ .transact({'from': accounts[0], 'value': pack_prices[rarity] * bundle_sizes[i]}) rcpt = get_receipt(tx_hash) assert rcpt['gasUsed'] < BLOCK_GAS_LIMIT, "Too much gas for block!" if DEBUG: print("Account0 {} tokens: {}, vault: {} ({})".format( rarity, token.functions.balanceOf(accounts[0]).call(), (vault.functions.total().call())/10**18, w3.eth.getBalance(vault.address)/10**18)) # Now there shouldn't be any more bundles available to buy if bundle_caps[i] > 0: assert_tx_failed( token.functions.purchaseFor(accounts[0], 1, accounts[1]), {'from': accounts[0]} )
def cpu_allocator(contract, available_cpu: Value, lock: Lock, fromBlock=0, toBlock='latest'): """ Similar to the storage allocator, this method allocates cpu computing power to a certain client after it's requested, returning an id for said reservation """ print('[x] cpu_allocator: started') # Create the filter necessary cpu_allocator_filter = contract.events.CPUPetition.createFilter( fromBlock=fromBlock, toBlock=toBlock) time.sleep(1) # Concise contract to access the balances in a much more direct way concise_contract = ConciseContract(contract) try: # Main loop of the receiver while True: new_cpu_petitions = cpu_allocator_filter.get_new_entries() if len(new_cpu_petitions) != 0: # Open mysql connection try: connection = _getConnection() cursor = connection.cursor() with lock: for petition in new_cpu_petitions: args = petition['args'] print('Cpu_petition: {} -> {}'.format( args['account'], args['amount'])) # Check to see if rejecting and accepting works, we need # to change it after if available_cpu.value - args['amount'] >= 0: cpu_id = uuid.uuid4() price = BlockchainServer.calculate_price_of_request( Resource.computing_power, int(args['amount']), available_cpu, args['account']) # Get balance of the account requesting storage balance = concise_contract.balanceOf( args['account']) print('Balance is {} and price is {}'.format( balance, price)) if balance >= price: # Decrease the balance print('Accepting Request') # Update balance, False = decrement contract.functions.updateBalance( args['account'], int(price), False).transact() contract.functions.answerComputingPowerRequest( args['account'], args['amount'], str(cpu_id), True).transact() available_cpu.value -= args['amount'] # 1) Insert cpu allocation in the database cursor.execute(""" INSERT INTO cpu_allocations(id, account_address, amount) value ('{}', '{}', {})""".format( str(cpu_id), args['account'], args['amount'])) # 2) Update client balance in the database cursor.execute( "UPDATE clients set coin_balance = coin_balance - {} where account_address = '{}'" .format(price, args['account'])) # 3) Insert new transaction pending of charge cursor.execute( "INSERT INTO pending_charges(id, account_address, charge) value ('{}','{}',{})" .format(cpu_id, args['account'], price)) # Commit changes connection.commit() new_balance = concise_contract.balanceOf( args['account']) print('New balance', new_balance) else: # Client does not have enough funds print('Not enough funds') print('Rejecting request') contract.functions.answerComputingPowerRequest( args['account'], args['amount'], '', False).transact() else: # Not enough spare CPU print( 'Rejecting request... number {} is not even' .format(args['amount'])) contract.functions.answerComputingPowerRequest( args['account'], args['amount'], '', False).transact() except mysql.connector.Error as error: connection.rollback() print('Error in mysql: {}'.format(error)) finally: cursor.close() connection.close() # Sleep until the next comprobation time.sleep(1) except KeyboardInterrupt: print("[*] cpu_allocator: exiting...") sys.exit(0)
def storage_allocator(contract, available_storage: Value, lock: Lock, fromBlock=0, toBlock='latest'): ''' Process that decides whether a storage request is granted or not. Most of the requirement checking is done directly by the smart contract, so here the main task of this method is to generate a unique id for the reservation - with the uuid4 module - and store all the data of the reservation in the database One of the requirements to be able to allocate memory is being reggistered in the system. If not, the call will fail ''' print('[x] storage_allocator: started') # Create filters for all events related to storage petitionFilter = contract.events.StoragePetition.createFilter( fromBlock=fromBlock, toBlock=toBlock) time.sleep(1) # Concise contract to access the balances in a much more direct way concise_contract = ConciseContract(contract) try: while True: newStoragePetitions = petitionFilter.get_new_entries() if len(newStoragePetitions) != 0: try: connection = _getConnection() cursor = connection.cursor() # Acquire lock to change the reservations with lock: for petition in newStoragePetitions: args = petition['args'] print('Args: {}, {}'.format( args['account'], args['amount'])) # Make the appropriate comprobations if available_storage.value - args['amount'] >= 0: st_id = uuid.uuid4() price = BlockchainServer.calculate_price_of_request( Resource.storage, int(args['amount']), available_storage, args['account']) print('Price of the request:', price, 'IoTokens') # Get balance of the account requesting storage balance = concise_contract.balanceOf( args['account']) print('Balance is {} and price is {}'.format( balance, price)) if balance >= price: # Decrease the balance print('Accepting Request') # Update balance, False = decrement contract.functions.updateBalance( args['account'], int(price), False).transact() contract.functions.answerStorageRequest( args['account'], args['amount'], str(st_id), True).transact() # Add storage reservation to the database cursor.execute(''' INSERT INTO storage_allocations(id, account_address, amount) value ('{}','{}',{})'''.format( str(st_id), args['account'], args['amount'])) # Update client balance in the database cursor.execute( "UPDATE clients set coin_balance = coin_balance - {} where account_address = '{}'" .format(price, args['account'])) # Insert new transaction pending of charge cursor.execute( "INSERT INTO pending_charges(id, account_address, charge) value ('{}','{}',{})" .format(st_id, args['account'], price)) connection.commit() available_storage.value -= args['amount'] print('Storage available:', available_storage.value, 'MB') new_balance = concise_contract.balanceOf( args['account']) print('New balance', new_balance) else: # Not enough balance in the client's account print('Not enough funds') print('Rejecting request') contract.functions.answerStorageRequest( args['account'], args['amount'], '', False).transact() else: # Not enough available storage print('Rejecting request') contract.functions.answerStorageRequest( args['account'], args['amount'], '', False).transact() except mysql.connector.Error as error: connection.rollback() print('Failed inserting new record into table: {}'.format( error)) finally: cursor.close() connection.close() time.sleep(1) except KeyboardInterrupt: print("[*] storage_allocator: exiting...") sys.exit(0)