def test_validate_erc20_transfer_data_ng_tip_value_greater_than_maximum(self): method = 'a9059cbb' to_address = format(10, '064x') tip_value = format(10 ** 24 + 1, '064x') test_tx = method + to_address + tip_value with self.assertRaises(ValidationError) as e: PrivateChainUtil.validate_erc20_transfer_data(test_tx, '0x' + to_address[24:]) self.assertEqual( e.exception.args[0], '1000000000000000000000001 is greater than the maximum of 1000000000000000000000000' )
def test_validate_message_signature_ng(self): web3 = Web3(HTTPProvider('http://localhost:8584')) test_account = web3.eth.account.create() test_message = 'hogepiyo' failure_signature = '0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' \ '0123456789abcdef0123456789abcdef0123456789ab' with self.assertRaises(BadSignature) as e: PrivateChainUtil.validate_message_signature( test_message, failure_signature, test_account.address ) self.assertEqual(e.exception.args[0], 'Invalid signature')
def validate_params(self): # single validate(self.params, self.get_schema()) # relational PrivateChainUtil.validate_message_signature( self.event['requestContext']['authorizer']['claims']['cognito:username'], self.params['signature'], self.params['wallet_address'] ) # 既に登録済みの場合は処理中断 if UserUtil.exists_private_eth_address(self.dynamodb, self.event['requestContext']['authorizer']['claims']['cognito:username']): raise ValidationError('private_eth_address is exists.')
def test_validate_raw_transaction_signature_ok(self): web3 = Web3(HTTPProvider('http://localhost:8584')) test_account = web3.eth.account.create() transaction = { 'to': '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', 'value': 1000000000, 'gas': 2000000, 'gasPrice': 234567897654321, 'nonce': 0, 'chainId': 1 } signed = web3.eth.account.sign_transaction(transaction, test_account.key) # 例外が発生しないこと PrivateChainUtil.validate_raw_transaction_signature(signed.rawTransaction.hex(), test_account.address)
def test_validate_message_signature_ok(self): web3 = Web3(HTTPProvider('http://localhost:8584')) test_account = web3.eth.account.create() test_message = 'hogepiyo' sign_message = web3.eth.account.sign_message( encode_defunct(text=test_message), private_key=test_account.key.hex() ) # 例外が発生しないこと PrivateChainUtil.validate_message_signature( test_message, sign_message['signature'].hex(), test_account.address )
def __get_timestamp_by_block_number(self, block_number): url = 'https://' + os.environ[ 'PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/eth/get_block_by_number' payload_dict = { 'block_num': block_number, } return PrivateChainUtil.send_transaction( request_url=url, payload_dict=payload_dict).get('timestamp')
def __is_burnable_user(eth_address, tip_value, burn_value): # get_balance token = PrivateChainUtil.get_balance(eth_address) # return result if int(token, 16) >= tip_value + burn_value: return True return False
def __polling_to_private_chain(self, purchase_transaction): try: if PrivateChainUtil.is_transaction_completed(purchase_transaction): return 'done' return 'doing' except (SendTransactionError, ReceiptError) as e: logging.info(e) return 'fail'
def __get_apply_relay_events_specified_block_range(self, from_block, to_block, user_eth_address): url = 'https://' + os.environ['PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/wallet/apply_relay_events' payload_dict = { 'from_block': from_block, 'to_block': to_block, 'recipient_eth_address': user_eth_address[2:], } return PrivateChainUtil.send_transaction(request_url=url, payload_dict=payload_dict)
def test_get_data_from_raw_transaction_ng_failure_v(self): web3 = Web3(HTTPProvider('http://localhost:8584')) test_account = web3.eth.account.create() test_data = '0xa9059cbb' nonce = 10 transaction = { 'nonce': nonce, 'gasPrice': 0, 'gas': 100000, 'to': web3.toChecksumAddress(os.environ['PRIVATE_CHAIN_ALIS_TOKEN_ADDRESS']), 'value': 0, 'data': test_data, 'chainId': 8994 } signed = web3.eth.account.sign_transaction(transaction, test_account.key) with self.assertRaises(ValidationError) as e: PrivateChainUtil.get_data_from_raw_transaction(signed.rawTransaction.hex(), format(nonce, '#x')) self.assertEqual(e.exception.args[0], 'v is invalid')
def test_get_data_from_raw_transaction_ng_failure_to(self): web3 = Web3(HTTPProvider('http://localhost:8584')) test_account = web3.eth.account.create() test_data = '0xa9059cbb' nonce = 10 transaction = { 'nonce': nonce, 'gasPrice': 0, 'gas': 100000, 'to': test_account.address, 'value': 0, 'data': test_data, 'chainId': 8995 } signed = web3.eth.account.sign_transaction(transaction, test_account.key) with self.assertRaises(ValidationError) as e: PrivateChainUtil.get_data_from_raw_transaction(signed.rawTransaction.hex(), format(nonce, '#x')) self.assertEqual(e.exception.args[0], 'private_chain_alis_token_address is invalid')
def __validate_has_not_token(self, params): address = params.get('custom:private_eth_address') if address is not None: url = 'https://' + os.environ[ 'PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/wallet/balance' payload = {'private_eth_address': address[2:]} token = PrivateChainUtil.send_transaction(request_url=url, payload_dict=payload) if token is not None and token != '0x0000000000000000000000000000000000000000000000000000000000000000': raise ValidationError("Do not allow phone number updates")
def test_validate_raw_transaction_signature_ng(self): web3 = Web3(HTTPProvider('http://localhost:8584')) test_account = web3.eth.account.create() transaction = { 'to': '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', 'value': 1000000000, 'gas': 2000000, 'gasPrice': 234567897654321, 'nonce': 0, 'chainId': 1 } signed = web3.eth.account.sign_transaction(transaction, test_account.key) failure_address = '0x123456789a123456789a123456789a123456789a' with self.assertRaises(ValidationError) as e: PrivateChainUtil.validate_raw_transaction_signature( signed.rawTransaction.hex(), failure_address ) self.assertEqual(e.exception.args[0], 'Signature is invalid')
def __get_allowance(from_user_eth_address): payload = { 'from_user_eth_address': from_user_eth_address, 'owner_eth_address': from_user_eth_address[2:], 'spender_eth_address': os.environ['PRIVATE_CHAIN_BRIDGE_ADDRESS'][2:] } url = 'https://' + os.environ[ 'PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/wallet/allowance' return PrivateChainUtil.send_transaction(request_url=url, payload_dict=payload)
def exec_main_proc(self): address = self.event['requestContext']['authorizer']['claims'].get( 'custom:private_eth_address') # nonce を取得 if address is None: # まだウォレットアドレスを作成していないユーザには 0 を返す nonce = '0x0' else: nonce = PrivateChainUtil.get_transaction_count(address) return {'statusCode': 200, 'body': json.dumps({'nonce': nonce})}
def validate_params(self): # 認証・ウォレット情報が登録済であること UserUtil.verified_phone_and_email(self.event) UserUtil.validate_private_eth_address( self.dynamodb, self.event['requestContext']['authorizer']['claims'] ['cognito:username']) # single validate(self.params, self.get_schema()) # 署名が正しいこと if self.params.get('init_approve_signed_transaction') is not None: PrivateChainUtil.validate_raw_transaction_signature( self.params['init_approve_signed_transaction'], self.event['requestContext']['authorizer']['claims'] ['custom:private_eth_address']) PrivateChainUtil.validate_raw_transaction_signature( self.params['approve_signed_transaction'], self.event['requestContext']['authorizer']['claims'] ['custom:private_eth_address']) PrivateChainUtil.validate_raw_transaction_signature( self.params['relay_signed_transaction'], self.event['requestContext']['authorizer']['claims'] ['custom:private_eth_address']) # pinコードを検証 self.__validate_pin_code(self.params['access_token'], self.params['pin_code'])
def test_is_transaction_completed_ok_last(self): with patch('requests.post') as mock_post: mock_post.side_effect = [ FakeResponse(status_code=200, text='{}'), FakeResponse(status_code=200, text='{}'), FakeResponse(status_code=200, text='{}'), FakeResponse(status_code=200, text='{}'), FakeResponse(status_code=200, text='{"result": {"logs": [{"type": "mined"}]}}') ] tran = '0x1234567890123456789012345678901234567890' response = PrivateChainUtil.is_transaction_completed(transaction=tran) self.assertEqual(response, True) self.assertEqual(mock_post.call_count, settings.TRANSACTION_CONFIRM_COUNT)
def test_send_raw_transaction_ok(self): test_raw_transaction = '0xabcdef0123456789' test_url = 'https://' + os.environ['PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/eth/send_raw_transaction' magic_lib = MagicMock(return_value='0x10') with patch('private_chain_util.PrivateChainUtil.send_transaction', magic_lib): result = PrivateChainUtil.send_raw_transaction(test_raw_transaction) self.assertEqual('0x10', result) _, kwargs = magic_lib.call_args expect_payload = { 'raw_transaction': test_raw_transaction } self.assertEqual(test_url, kwargs['request_url']) self.assertEqual(expect_payload, kwargs['payload_dict'])
def __relay(from_user_eth_address, recipient_eth_address, send_value, nonce): payload = { 'from_user_eth_address': from_user_eth_address, 'recipient_eth_address': recipient_eth_address[2:], 'nonce': nonce, 'amount': format(send_value, '064x') } url = 'https://' + os.environ[ 'PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/wallet/relay' # relay 実施 result = PrivateChainUtil.send_transaction(request_url=url, payload_dict=payload) return result
def exec_main_proc(self): from_user_eth_address = self.event['requestContext']['authorizer']['claims'].get('custom:private_eth_address') # allowance を取得 # まだウォレットアドレスを作成していないユーザには 0 を返す if from_user_eth_address is None: allowance = '0x0' else: allowance = PrivateChainUtil.get_allowance(from_user_eth_address) return { 'statusCode': 200, 'body': json.dumps({'allowance': allowance}) }
def validate_params(self): # 認証・ウォレット情報が登録済であること UserUtil.verified_phone_and_email(self.event) UserUtil.validate_private_eth_address( self.dynamodb, self.event['requestContext']['authorizer']['claims'] ['cognito:username']) # single validate(self.params, self.get_schema()) # 署名が正しいこと PrivateChainUtil.validate_raw_transaction_signature( self.params['tip_signed_transaction'], self.event['requestContext'] ['authorizer']['claims']['custom:private_eth_address']) PrivateChainUtil.validate_raw_transaction_signature( self.params['burn_signed_transaction'], self.event['requestContext']['authorizer']['claims'] ['custom:private_eth_address']) # relation # 公開されている記事であること DBUtil.validate_article_existence(self.dynamodb, self.params['article_id'], status='public')
def __approve(from_user_eth_address, send_value, nonce): payload = { 'from_user_eth_address': from_user_eth_address, 'spender_eth_address': os.environ['PRIVATE_CHAIN_BRIDGE_ADDRESS'][2:], 'nonce': nonce, 'value': format(send_value, '064x') } url = 'https://' + os.environ[ 'PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/wallet/approve' # approve 実施 result = PrivateChainUtil.send_transaction(request_url=url, payload_dict=payload) return result
def test_get_transaction_count_ok(self): test_address = '0x401BA17D89D795B3C6e373c5062F1C3F8979e73B' test_url = 'https://' + os.environ['PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/eth/get_transaction_count' test_count = '0x10' magic_lib = MagicMock(return_value=test_count) with patch('private_chain_util.PrivateChainUtil.send_transaction', magic_lib): result = PrivateChainUtil.get_transaction_count(test_address) self.assertEqual(test_count, result) _, kwargs = magic_lib.call_args expect_payload = { 'from_user_eth_address': test_address } self.assertEqual(test_url, kwargs['request_url']) self.assertEqual(expect_payload, kwargs['payload_dict'])
def exec_main_proc(self): user_id = self.event['requestContext']['authorizer']['claims'][ 'cognito:username'] address = self.event['requestContext']['authorizer']['claims'].get( 'custom:private_eth_address') # 現在のトークン量を取得 # まだウォレットアドレスを作成していないユーザには 0 を返す if address is None: balance = '0x0' elif not UserUtil.exists_private_eth_address(self.dynamodb, user_id): # Cognito上にprivate_eth_addressは存在するが、カストディ規制のウォレット移行が完了していないユーザにも0を返す balance = '0x0' else: balance = PrivateChainUtil.get_balance(address) return {'statusCode': 200, 'body': json.dumps({'result': balance})}
def test_get_allowance_ok(self): test_address = '0x401BA17D89D795B3C6e373c5062F1C3F8979e73B' test_url = 'https://' + os.environ['PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/wallet/allowance' test_allowance = '0x10' magic_lib = MagicMock(return_value=test_allowance) with patch('private_chain_util.PrivateChainUtil.send_transaction', magic_lib): result = PrivateChainUtil.get_allowance(test_address) self.assertEqual(test_allowance, result) _, kwargs = magic_lib.call_args expect_payload = { 'from_user_eth_address': test_address, 'owner_eth_address': test_address[2:], 'spender_eth_address': os.environ['PRIVATE_CHAIN_BRIDGE_ADDRESS'][2:] } self.assertEqual(test_url, kwargs['request_url']) self.assertEqual(expect_payload, kwargs['payload_dict'])
def test_get_data_from_raw_transaction_ok_with_nonce_zero(self): web3 = Web3(HTTPProvider('http://localhost:8584')) test_account = web3.eth.account.create() test_data = '0xa9059cbb' nonce = 0 transaction = { 'nonce': nonce, 'gasPrice': 0, 'gas': 100000, 'to': web3.toChecksumAddress(os.environ['PRIVATE_CHAIN_ALIS_TOKEN_ADDRESS']), 'value': 0, 'data': test_data, 'chainId': 8995 } signed = web3.eth.account.sign_transaction(transaction, test_account.key) actual = PrivateChainUtil.get_data_from_raw_transaction(signed.rawTransaction.hex(), format(nonce, '#x')) self.assertEqual(test_data[2:], actual)
def test_main_ok_call_validate_methods(self): test_tip_value = 10 to_address = format(10, '064x') burn_value = int(test_tip_value / Decimal(10)) raw_transactions = self.create_singed_transactions( to_address, test_tip_value, burn_value) with patch('me_wallet_tip.UserUtil.get_private_eth_address') as mock_get_private_eth_address, \ patch('me_wallet_tip.UserUtil.verified_phone_and_email') as mock_verified_phone_and_email, \ patch('me_wallet_tip.UserUtil.validate_private_eth_address') as mock_validate_private_eth_address, \ patch('me_wallet_tip.PrivateChainUtil.validate_raw_transaction_signature') as mock_validate_signature, \ patch('me_wallet_tip.PrivateChainUtil.validate_erc20_transfer_data') \ as mock_validate_erc20_transfer_data: mock_get_private_eth_address.return_value = '0x' + to_address[24:] target_article_id = self.article_info_table_items[0]['article_id'] event = { 'body': { 'article_id': target_article_id, 'tip_signed_transaction': raw_transactions['tip'].rawTransaction.hex(), 'burn_signed_transaction': raw_transactions['burn'].rawTransaction.hex() }, 'requestContext': { 'authorizer': { 'claims': { 'cognito:username': '******', 'custom:private_eth_address': self.test_account.address, 'phone_number_verified': 'true', 'email_verified': 'true' } } } } event['body'] = json.dumps(event['body']) response = MeWalletTip(event, {}, self.dynamodb, cognito=None).main() self.assertEqual(response['statusCode'], 200) # verified_phone_and_email args, _ = mock_verified_phone_and_email.call_args self.assertEqual(event, args[0]) # validate_private_eth_address args, _ = mock_validate_private_eth_address.call_args self.assertEqual(self.dynamodb, args[0]) self.assertEqual('act_user_01', args[1]) # validate_raw_transaction_signature args, _ = mock_validate_signature.call_args_list[0] self.assertEqual(raw_transactions['tip'].rawTransaction.hex(), args[0]) self.assertEqual(self.test_account.address, args[1]) args, _ = mock_validate_signature.call_args_list[1] self.assertEqual(raw_transactions['burn'].rawTransaction.hex(), args[0]) self.assertEqual(self.test_account.address, args[1]) # validate_erc20_transfer_data args, _ = mock_validate_erc20_transfer_data.call_args_list[0] tip_data = PrivateChainUtil.get_data_from_raw_transaction( raw_transactions['tip'].rawTransaction.hex(), '0x5') self.assertEqual(tip_data, args[0]) self.assertEqual('0x' + to_address[24:], args[1]) args, _ = mock_validate_erc20_transfer_data.call_args_list[1] burn_data = PrivateChainUtil.get_data_from_raw_transaction( raw_transactions['burn'].rawTransaction.hex(), '0x6') self.assertEqual(burn_data, args[0]) self.assertEqual('0x' + os.environ['BURN_ADDRESS'], args[1])
def exec_main_proc(self): ################ # get parameter ################ sort_key = TimeUtil.generate_sort_key() from_user_eth_address = self.event['requestContext']['authorizer'][ 'claims']['custom:private_eth_address'] user_id = self.event['requestContext']['authorizer']['claims'][ 'cognito:username'] allowance = PrivateChainUtil.get_allowance(from_user_eth_address) transaction_count = PrivateChainUtil.get_transaction_count( from_user_eth_address) ################ # validation ################ # validate raw_transaction # init_approve_signed_transaction if int(allowance, 16) != 0: # allowance が設定されている場合は必須 if self.params.get('init_approve_signed_transaction') is None: raise ValidationError( 'init_approve_signed_transaction is invalid.') # data init_approve_data = PrivateChainUtil.get_data_from_raw_transaction( self.params['init_approve_signed_transaction'], transaction_count) PrivateChainUtil.validate_erc20_approve_data(init_approve_data) if int(init_approve_data[72:], 16) != 0: raise ValidationError('Value of init_approve is invalid.') transaction_count = PrivateChainUtil.increment_transaction_count( transaction_count) # approve_signed_transaction approve_data = PrivateChainUtil.get_data_from_raw_transaction( self.params['approve_signed_transaction'], transaction_count) PrivateChainUtil.validate_erc20_approve_data(approve_data) # 日次の限度額を超えていた場合は例外 sum_price = self.__get_token_send_value_today(user_id) if Decimal(os.environ['DAILY_LIMIT_TOKEN_SEND_VALUE'] ) < sum_price + Decimal(int(approve_data[72:], 16)): raise ValidationError('Token withdrawal limit has been exceeded.') transaction_count = PrivateChainUtil.increment_transaction_count( transaction_count) # relay_signed_transaction relay_data = PrivateChainUtil.get_data_from_raw_transaction( self.params['relay_signed_transaction'], transaction_count) PrivateChainUtil.validate_erc20_relay_data(relay_data) # approve と relay の value が同一であること approve_value = int(approve_data[72:], 16) relay_value = int(relay_data[72:], 16) if approve_value != relay_value: raise ValidationError('approve and relay values do not match.') ####################### # send_raw_transaction ####################### # 既に approve されている場合(allowance の戻り値が 0 ではない場合)、該当の approve を削除する(0 で更新) if int(allowance, 16) != 0: PrivateChainUtil.send_raw_transaction( self.params.get('init_approve_signed_transaction')) # approve 実施 approve_transaction_hash = PrivateChainUtil.send_raw_transaction( self.params.get('approve_signed_transaction')) self.__create_send_info_with_approve_transaction_hash( sort_key, user_id, approve_transaction_hash, relay_value) # token_send_table への書き込み完了後に出金関連の例外が発生した場合は、token_send_table のステータスを fail に更新する try: # relay 実施 relay_transaction_hash = PrivateChainUtil.send_raw_transaction( self.params.get('relay_signed_transaction')) self.__update_send_info_with_relay_transaction_hash( sort_key, user_id, relay_transaction_hash) # transaction の完了を確認 is_completed = PrivateChainUtil.is_transaction_completed( relay_transaction_hash) except SendTransactionError as e: # ステータスを fail に更新し中断 self.__update_send_info_with_send_status(sort_key, user_id, 'fail') raise e except ReceiptError: # send_value の値が残高を超えた場合や、処理最小・最大値の範囲に収まっていない場合に ReceiptError が発生するため # ValidationError として処理を中断する # ステータスを fail に更新 self.__update_send_info_with_send_status(sort_key, user_id, 'fail') raise ValidationError('send_value') # transaction が完了していた場合、ステータスを done に更新 if is_completed: self.__update_send_info_with_send_status(sort_key, user_id, 'done') return { 'statusCode': 200, 'body': json.dumps({'is_completed': is_completed}) }
def __get_relay_paused(): return PrivateChainUtil.send_transaction( 'https://' + os.environ['PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/wallet/relay_paused')
def __get_min_single_relay_amount(): return PrivateChainUtil.send_transaction( 'https://' + os.environ['PRIVATE_CHAIN_EXECUTE_API_HOST'] + '/production/wallet/min_single_relay_amount')