Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crypto/enums/abi_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ class AbiFunction(Enum):
USERNAME_RESIGNATION = 'resignUsername'
VALIDATOR_REGISTRATION = 'registerValidator'
VALIDATOR_RESIGNATION = 'resignValidator'
UPDATE_VALIDATOR = 'updateValidator'
TRANSFER = 'transfer'
1 change: 1 addition & 0 deletions crypto/enums/contract_abi_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ class ContractAbiType(Enum):
CUSTOM = 'custom'
CONSENSUS = 'consensus'
MULTIPAYMENT = 'multipayment'
TOKEN = 'token'
USERNAMES = 'usernames'
27 changes: 27 additions & 0 deletions crypto/transactions/builder/token_transfer_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from crypto.enums.abi_function import AbiFunction
from crypto.enums.contract_abi_type import ContractAbiType
from crypto.transactions.builder.abstract_transaction_builder import (
AbstractTransactionBuilder,
)
from crypto.transactions.types.evm_call import EvmCall
from crypto.utils.abi_encoder import AbiEncoder
from crypto.utils.transaction_utils import TransactionUtils


class TokenTransferBuilder(AbstractTransactionBuilder):
def contract_address(self, address):
self.transaction.data['to'] = address
return self

def recipient(self, address, amount):
encoder = AbiEncoder(ContractAbiType.TOKEN)
payload = encoder.encode_function_call(
AbiFunction.TRANSFER.value, [address, amount]
)
self.transaction.data['data'] = TransactionUtils.parse_hex_from_str(
payload
)
return self

def get_transaction_instance(self, data):
return EvmCall(data)
345 changes: 345 additions & 0 deletions crypto/utils/abi/json/Abi.Token.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions crypto/utils/abi_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ def __contract_abi_path(self, abi_type: ContractAbiType, path: Optional[str] = N
if abi_type == ContractAbiType.MULTIPAYMENT:
return os.path.join(os.path.dirname(__file__), 'abi/json', 'Abi.Multipayment.json')

if abi_type == ContractAbiType.TOKEN:
return os.path.join(os.path.dirname(__file__), 'abi/json', 'Abi.Token.json')

if abi_type == ContractAbiType.USERNAMES:
return os.path.join(os.path.dirname(__file__), 'abi/json', 'Abi.Usernames.json')

Expand Down
6 changes: 6 additions & 0 deletions crypto/utils/abi_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ def encode_function_call(self, function_name, args=[]):
}
return self.encode_function_data(parameters)

def encode_function_call_hex(self, function_name, args=[]):
result = self.encode_function_call(function_name, args)
if not result.startswith('0x'):
return '0x' + result
return result

def encode_function_data(self, parameters):
args = parameters.get('args', [])

Expand Down
65 changes: 65 additions & 0 deletions crypto/utils/transaction_data_encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from crypto.enums.abi_function import AbiFunction
from crypto.enums.contract_abi_type import ContractAbiType
from crypto.utils.abi_encoder import AbiEncoder


class TransactionDataEncoder:
@staticmethod
def multi_payment(recipients, amounts):
return AbiEncoder(ContractAbiType.MULTIPAYMENT).encode_function_call_hex(
AbiFunction.MULTIPAYMENT.value, [recipients, amounts]
)

@staticmethod
def update_validator(validator_public_key):
key = validator_public_key
if not key.startswith('0x'):
key = '0x' + key
return AbiEncoder(ContractAbiType.CONSENSUS).encode_function_call_hex(
AbiFunction.UPDATE_VALIDATOR.value, [key]
)

@staticmethod
def username_registration(username):
return AbiEncoder(ContractAbiType.USERNAMES).encode_function_call_hex(
AbiFunction.USERNAME_REGISTRATION.value, [username]
)

@staticmethod
def username_resignation():
return AbiEncoder(ContractAbiType.USERNAMES).encode_function_call_hex(
AbiFunction.USERNAME_RESIGNATION.value, []
)

@staticmethod
def validator_registration(validator_public_key):
key = validator_public_key
if not key.startswith('0x'):
key = '0x' + key
return AbiEncoder(ContractAbiType.CONSENSUS).encode_function_call_hex(
AbiFunction.VALIDATOR_REGISTRATION.value, [key]
)

@staticmethod
def validator_resignation():
return AbiEncoder(ContractAbiType.CONSENSUS).encode_function_call_hex(
AbiFunction.VALIDATOR_RESIGNATION.value, []
)

@staticmethod
def token_transfer(recipient_address, amount):
return AbiEncoder(ContractAbiType.TOKEN).encode_function_call_hex(
AbiFunction.TRANSFER.value, [recipient_address, amount]
)

@staticmethod
def vote(vote_address):
return AbiEncoder(ContractAbiType.CONSENSUS).encode_function_call_hex(
AbiFunction.VOTE.value, [vote_address]
)

@staticmethod
def unvote():
return AbiEncoder(ContractAbiType.CONSENSUS).encode_function_call_hex(
AbiFunction.UNVOTE.value, []
)
16 changes: 16 additions & 0 deletions tests/fixtures/transactions/token-transfer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"data": {
"value": "0",
"senderPublicKey": "0243333347c8cbf4e3cbc7a96964181d02a2b0c854faa2fef86b4b8d92afcf473d",
"gasPrice": "5000000000",
"gasLimit": "21000",
"nonce": "1",
"data": "a9059cbb0000000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb220000000000000000000000000000000000000000000000000000000000000000",
"to": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22",
"v": 0,
"r": "ca5516c582647a1626bfeb4ccf62314dae02c8c561dbff690bdbda89d181e6f7",
"s": "1b4ddf55e432e532c8e8d1a854f21e0c80e35a35fe4557899fb3f485d071f1e1",
"hash": "4bb567e6ffd6716b99865fc6917eadf0f5b6cbccdd6999a592db6143a7f24d81"
},
"serialized": "f8ab0185012a05f200825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb2280b844a9059cbb0000000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb220000000000000000000000000000000000000000000000000000000000000000825c6ba0ca5516c582647a1626bfeb4ccf62314dae02c8c561dbff690bdbda89d181e6f7a01b4ddf55e432e532c8e8d1a854f21e0c80e35a35fe4557899fb3f485d071f1e1"
}
15 changes: 15 additions & 0 deletions tests/fixtures/transactions/transaction-data-encoder.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"Username": "test",
"Address": "0xA5cc0BfEB09742C5e4C610f2EBaaB82Eb142Ca10",
"ValidatorPublicKey": "b209f4a7454ae17c5808991dffbf204c747b851f351d2ce72a6e18903d0e2f609e0328ebbc3fb97cd4d3660b4bc156f1",
"Amount": "1000000000000",
"Encoded": {
"UpdateValidator": "0x5a8eed7300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030b209f4a7454ae17c5808991dffbf204c747b851f351d2ce72a6e18903d0e2f609e0328ebbc3fb97cd4d3660b4bc156f100000000000000000000000000000000",
"UsernameRegistration": "0x36a94134000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000",
"UsernameResignation": "0xebed6dab",
"ValidatorRegistration": "0x602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030b209f4a7454ae17c5808991dffbf204c747b851f351d2ce72a6e18903d0e2f609e0328ebbc3fb97cd4d3660b4bc156f100000000000000000000000000000000",
"ValidatorResignation": "0xb85f5da2",
"TokenTransfer": "0xa9059cbb000000000000000000000000a5cc0bfeb09742c5e4c610f2ebaab82eb142ca10000000000000000000000000000000000000000000000000000000e8d4a51000",
"MultiPayment": "0x084ce708000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a5cc0bfeb09742c5e4c610f2ebaab82eb142ca10000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e8"
}
}
53 changes: 53 additions & 0 deletions tests/transactions/builder/test_token_transfer_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from crypto.transactions.builder.token_transfer_builder import (
TokenTransferBuilder,
)


def test_token_transfer_transaction(passphrase, load_transaction_fixture):
fixture = load_transaction_fixture('transactions/token-transfer')

builder = (
TokenTransferBuilder
.new()
.recipient(fixture['data']['to'], int(fixture['data']['value']))
.to(fixture['data']['to'])
.nonce(fixture['data']['nonce'])
.gas_price(fixture['data']['gasPrice'])
.gas_limit(fixture['data']['gasLimit'])
.sign(passphrase)
)

assert builder.transaction.data['gasPrice'] == int(
fixture['data']['gasPrice']
)
assert builder.transaction.data['gasLimit'] == int(
fixture['data']['gasLimit']
)
assert builder.transaction.data['nonce'] == fixture['data']['nonce']
assert builder.transaction.data['to'].lower() == (
fixture['data']['to'].lower()
)
assert builder.transaction.data['v'] == fixture['data']['v']
assert builder.transaction.data['r'] == fixture['data']['r']
assert builder.transaction.data['s'] == fixture['data']['s']
assert builder.transaction.data['hash'] == fixture['data']['hash']

assert builder.verify()


def test_token_transfer_serialization(passphrase, load_transaction_fixture):
fixture = load_transaction_fixture('transactions/token-transfer')

builder = (
TokenTransferBuilder
.new()
.recipient(fixture['data']['to'], int(fixture['data']['value']))
.to(fixture['data']['to'])
.nonce(fixture['data']['nonce'])
.gas_price(fixture['data']['gasPrice'])
.gas_limit(fixture['data']['gasLimit'])
.sign(passphrase)
)

serialized = builder.transaction.serialize().hex()
assert serialized == fixture['serialized']
13 changes: 13 additions & 0 deletions tests/utils/test_abi_base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
from crypto.enums.contract_abi_type import ContractAbiType
from crypto.utils.abi_base import AbiBase


def test_it_should_load_token_abi():
abi_base = AbiBase(ContractAbiType.TOKEN)
function_names = [
item['name'] for item in abi_base.abi if item.get('type') == 'function'
]
assert 'transfer' in function_names
assert 'approve' in function_names
assert 'balanceOf' in function_names
assert 'totalSupply' in function_names
assert 'allowance' in function_names


def test_it_should_validate_uppercase_addresses():
assert AbiBase().is_valid_address('0xC3bBE9B1CeE1ff85Ad72b87414B0E9B7F2366763') is True

Expand Down
16 changes: 16 additions & 0 deletions tests/utils/test_abi_decoder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from crypto.enums.contract_abi_type import ContractAbiType
from crypto.utils.abi_decoder import AbiDecoder


Expand All @@ -13,3 +14,18 @@ def test_decode_vote_payload():
'functionName': function_name,
'args': args,
}


def test_decode_token_transfer():
decoder = AbiDecoder(ContractAbiType.TOKEN)
data = (
'0xa9059cbb'
'000000000000000000000000c3bbe9b1cee1ff85ad72b87414b0e9b7f2366763'
'00000000000000000000000000000000000000000000000000000000000003e8'
)
decoded = decoder.decode_function_data(data)
assert decoded['functionName'] == 'transfer'
assert decoded['args'] == [
'0xC3bBE9B1CeE1ff85Ad72b87414B0E9B7F2366763',
1000,
]
15 changes: 15 additions & 0 deletions tests/utils/test_abi_encoder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from crypto.enums.contract_abi_type import ContractAbiType
from crypto.utils.abi_encoder import AbiEncoder


Expand All @@ -22,3 +23,17 @@ def test_encode_address():
'dynamic': False,
'encoded': '0x000000000000000000000000c3bbe9b1cee1ff85ad72b87414b0e9b7f2366763',
}


def test_encode_token_transfer():
encoder = AbiEncoder(ContractAbiType.TOKEN)
encoded = encoder.encode_function_call(
'transfer',
['0xC3bBE9B1CeE1ff85Ad72b87414B0E9B7F2366763', 1000]
)
expected = (
'0xa9059cbb'
'000000000000000000000000c3bbe9b1cee1ff85ad72b87414b0e9b7f2366763'
'00000000000000000000000000000000000000000000000000000000000003e8'
)
assert encoded == expected
43 changes: 43 additions & 0 deletions tests/utils/test_transaction_data_encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from crypto.utils.transaction_data_encoder import TransactionDataEncoder


def test_encode_token_transfer(load_transaction_fixture):
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
encoded = TransactionDataEncoder.token_transfer(
fixture['Address'], fixture['Amount']
)
assert encoded == fixture['Encoded']['TokenTransfer']


def test_encode_username_registration(load_transaction_fixture):
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
encoded = TransactionDataEncoder.username_registration(fixture['Username'])
assert encoded == fixture['Encoded']['UsernameRegistration']


def test_encode_username_resignation(load_transaction_fixture):
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
encoded = TransactionDataEncoder.username_resignation()
assert encoded == fixture['Encoded']['UsernameResignation']


def test_encode_validator_registration(load_transaction_fixture):
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
encoded = TransactionDataEncoder.validator_registration(
fixture['ValidatorPublicKey']
)
assert encoded == fixture['Encoded']['ValidatorRegistration']


def test_encode_validator_resignation(load_transaction_fixture):
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
encoded = TransactionDataEncoder.validator_resignation()
assert encoded == fixture['Encoded']['ValidatorResignation']


def test_encode_multi_payment(load_transaction_fixture):
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
encoded = TransactionDataEncoder.multi_payment(
[fixture['Address']], ['1000']
)
assert encoded == fixture['Encoded']['MultiPayment']