Skip to content

Commit 3c842d1

Browse files
authored
feat: add token transfer builder and encoder (#178)
1 parent b5d01ae commit 3c842d1

8 files changed

Lines changed: 227 additions & 0 deletions

File tree

crypto/enums/abi_function.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ class AbiFunction(Enum):
88
USERNAME_RESIGNATION = 'resignUsername'
99
VALIDATOR_REGISTRATION = 'registerValidator'
1010
VALIDATOR_RESIGNATION = 'resignValidator'
11+
UPDATE_VALIDATOR = 'updateValidator'
12+
TRANSFER = 'transfer'
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from crypto.enums.abi_function import AbiFunction
2+
from crypto.enums.contract_abi_type import ContractAbiType
3+
from crypto.transactions.builder.abstract_transaction_builder import (
4+
AbstractTransactionBuilder,
5+
)
6+
from crypto.transactions.types.evm_call import EvmCall
7+
from crypto.utils.abi_encoder import AbiEncoder
8+
from crypto.utils.transaction_utils import TransactionUtils
9+
10+
11+
class TokenTransferBuilder(AbstractTransactionBuilder):
12+
def contract_address(self, address):
13+
self.transaction.data['to'] = address
14+
return self
15+
16+
def recipient(self, address, amount):
17+
encoder = AbiEncoder(ContractAbiType.TOKEN)
18+
payload = encoder.encode_function_call(
19+
AbiFunction.TRANSFER.value, [address, amount]
20+
)
21+
self.transaction.data['data'] = TransactionUtils.parse_hex_from_str(
22+
payload
23+
)
24+
return self
25+
26+
def get_transaction_instance(self, data):
27+
return EvmCall(data)

crypto/utils/abi_encoder.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ def encode_function_call(self, function_name, args=[]):
1313
}
1414
return self.encode_function_data(parameters)
1515

16+
def encode_function_call_hex(self, function_name, args=[]):
17+
result = self.encode_function_call(function_name, args)
18+
if not result.startswith('0x'):
19+
return '0x' + result
20+
return result
21+
1622
def encode_function_data(self, parameters):
1723
args = parameters.get('args', [])
1824

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from crypto.enums.abi_function import AbiFunction
2+
from crypto.enums.contract_abi_type import ContractAbiType
3+
from crypto.utils.abi_encoder import AbiEncoder
4+
5+
6+
class TransactionDataEncoder:
7+
@staticmethod
8+
def multi_payment(recipients, amounts):
9+
return AbiEncoder(ContractAbiType.MULTIPAYMENT).encode_function_call_hex(
10+
AbiFunction.MULTIPAYMENT.value, [recipients, amounts]
11+
)
12+
13+
@staticmethod
14+
def update_validator(validator_public_key):
15+
key = validator_public_key
16+
if not key.startswith('0x'):
17+
key = '0x' + key
18+
return AbiEncoder(ContractAbiType.CONSENSUS).encode_function_call_hex(
19+
AbiFunction.UPDATE_VALIDATOR.value, [key]
20+
)
21+
22+
@staticmethod
23+
def username_registration(username):
24+
return AbiEncoder(ContractAbiType.USERNAMES).encode_function_call_hex(
25+
AbiFunction.USERNAME_REGISTRATION.value, [username]
26+
)
27+
28+
@staticmethod
29+
def username_resignation():
30+
return AbiEncoder(ContractAbiType.USERNAMES).encode_function_call_hex(
31+
AbiFunction.USERNAME_RESIGNATION.value, []
32+
)
33+
34+
@staticmethod
35+
def validator_registration(validator_public_key):
36+
key = validator_public_key
37+
if not key.startswith('0x'):
38+
key = '0x' + key
39+
return AbiEncoder(ContractAbiType.CONSENSUS).encode_function_call_hex(
40+
AbiFunction.VALIDATOR_REGISTRATION.value, [key]
41+
)
42+
43+
@staticmethod
44+
def validator_resignation():
45+
return AbiEncoder(ContractAbiType.CONSENSUS).encode_function_call_hex(
46+
AbiFunction.VALIDATOR_RESIGNATION.value, []
47+
)
48+
49+
@staticmethod
50+
def token_transfer(recipient_address, amount):
51+
return AbiEncoder(ContractAbiType.TOKEN).encode_function_call_hex(
52+
AbiFunction.TRANSFER.value, [recipient_address, amount]
53+
)
54+
55+
@staticmethod
56+
def vote(vote_address):
57+
return AbiEncoder(ContractAbiType.CONSENSUS).encode_function_call_hex(
58+
AbiFunction.VOTE.value, [vote_address]
59+
)
60+
61+
@staticmethod
62+
def unvote():
63+
return AbiEncoder(ContractAbiType.CONSENSUS).encode_function_call_hex(
64+
AbiFunction.UNVOTE.value, []
65+
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"data": {
3+
"value": "0",
4+
"senderPublicKey": "0243333347c8cbf4e3cbc7a96964181d02a2b0c854faa2fef86b4b8d92afcf473d",
5+
"gasPrice": "5000000000",
6+
"gasLimit": "21000",
7+
"nonce": "1",
8+
"data": "a9059cbb0000000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb220000000000000000000000000000000000000000000000000000000000000000",
9+
"to": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22",
10+
"v": 0,
11+
"r": "ca5516c582647a1626bfeb4ccf62314dae02c8c561dbff690bdbda89d181e6f7",
12+
"s": "1b4ddf55e432e532c8e8d1a854f21e0c80e35a35fe4557899fb3f485d071f1e1",
13+
"hash": "4bb567e6ffd6716b99865fc6917eadf0f5b6cbccdd6999a592db6143a7f24d81"
14+
},
15+
"serialized": "f8ab0185012a05f200825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb2280b844a9059cbb0000000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb220000000000000000000000000000000000000000000000000000000000000000825c6ba0ca5516c582647a1626bfeb4ccf62314dae02c8c561dbff690bdbda89d181e6f7a01b4ddf55e432e532c8e8d1a854f21e0c80e35a35fe4557899fb3f485d071f1e1"
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"Username": "test",
3+
"Address": "0xA5cc0BfEB09742C5e4C610f2EBaaB82Eb142Ca10",
4+
"ValidatorPublicKey": "b209f4a7454ae17c5808991dffbf204c747b851f351d2ce72a6e18903d0e2f609e0328ebbc3fb97cd4d3660b4bc156f1",
5+
"Amount": "1000000000000",
6+
"Encoded": {
7+
"UpdateValidator": "0x5a8eed7300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030b209f4a7454ae17c5808991dffbf204c747b851f351d2ce72a6e18903d0e2f609e0328ebbc3fb97cd4d3660b4bc156f100000000000000000000000000000000",
8+
"UsernameRegistration": "0x36a94134000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000",
9+
"UsernameResignation": "0xebed6dab",
10+
"ValidatorRegistration": "0x602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030b209f4a7454ae17c5808991dffbf204c747b851f351d2ce72a6e18903d0e2f609e0328ebbc3fb97cd4d3660b4bc156f100000000000000000000000000000000",
11+
"ValidatorResignation": "0xb85f5da2",
12+
"TokenTransfer": "0xa9059cbb000000000000000000000000a5cc0bfeb09742c5e4c610f2ebaab82eb142ca10000000000000000000000000000000000000000000000000000000e8d4a51000",
13+
"MultiPayment": "0x084ce708000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a5cc0bfeb09742c5e4c610f2ebaab82eb142ca10000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e8"
14+
}
15+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from crypto.transactions.builder.token_transfer_builder import (
2+
TokenTransferBuilder,
3+
)
4+
5+
6+
def test_token_transfer_transaction(passphrase, load_transaction_fixture):
7+
fixture = load_transaction_fixture('transactions/token-transfer')
8+
9+
builder = (
10+
TokenTransferBuilder
11+
.new()
12+
.recipient(fixture['data']['to'], int(fixture['data']['value']))
13+
.to(fixture['data']['to'])
14+
.nonce(fixture['data']['nonce'])
15+
.gas_price(fixture['data']['gasPrice'])
16+
.gas_limit(fixture['data']['gasLimit'])
17+
.sign(passphrase)
18+
)
19+
20+
assert builder.transaction.data['gasPrice'] == int(
21+
fixture['data']['gasPrice']
22+
)
23+
assert builder.transaction.data['gasLimit'] == int(
24+
fixture['data']['gasLimit']
25+
)
26+
assert builder.transaction.data['nonce'] == fixture['data']['nonce']
27+
assert builder.transaction.data['to'].lower() == (
28+
fixture['data']['to'].lower()
29+
)
30+
assert builder.transaction.data['v'] == fixture['data']['v']
31+
assert builder.transaction.data['r'] == fixture['data']['r']
32+
assert builder.transaction.data['s'] == fixture['data']['s']
33+
assert builder.transaction.data['hash'] == fixture['data']['hash']
34+
35+
assert builder.verify()
36+
37+
38+
def test_token_transfer_serialization(passphrase, load_transaction_fixture):
39+
fixture = load_transaction_fixture('transactions/token-transfer')
40+
41+
builder = (
42+
TokenTransferBuilder
43+
.new()
44+
.recipient(fixture['data']['to'], int(fixture['data']['value']))
45+
.to(fixture['data']['to'])
46+
.nonce(fixture['data']['nonce'])
47+
.gas_price(fixture['data']['gasPrice'])
48+
.gas_limit(fixture['data']['gasLimit'])
49+
.sign(passphrase)
50+
)
51+
52+
serialized = builder.transaction.serialize().hex()
53+
assert serialized == fixture['serialized']
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from crypto.utils.transaction_data_encoder import TransactionDataEncoder
2+
3+
4+
def test_encode_token_transfer(load_transaction_fixture):
5+
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
6+
encoded = TransactionDataEncoder.token_transfer(
7+
fixture['Address'], fixture['Amount']
8+
)
9+
assert encoded == fixture['Encoded']['TokenTransfer']
10+
11+
12+
def test_encode_username_registration(load_transaction_fixture):
13+
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
14+
encoded = TransactionDataEncoder.username_registration(fixture['Username'])
15+
assert encoded == fixture['Encoded']['UsernameRegistration']
16+
17+
18+
def test_encode_username_resignation(load_transaction_fixture):
19+
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
20+
encoded = TransactionDataEncoder.username_resignation()
21+
assert encoded == fixture['Encoded']['UsernameResignation']
22+
23+
24+
def test_encode_validator_registration(load_transaction_fixture):
25+
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
26+
encoded = TransactionDataEncoder.validator_registration(
27+
fixture['ValidatorPublicKey']
28+
)
29+
assert encoded == fixture['Encoded']['ValidatorRegistration']
30+
31+
32+
def test_encode_validator_resignation(load_transaction_fixture):
33+
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
34+
encoded = TransactionDataEncoder.validator_resignation()
35+
assert encoded == fixture['Encoded']['ValidatorResignation']
36+
37+
38+
def test_encode_multi_payment(load_transaction_fixture):
39+
fixture = load_transaction_fixture('transactions/transaction-data-encoder')
40+
encoded = TransactionDataEncoder.multi_payment(
41+
[fixture['Address']], ['1000']
42+
)
43+
assert encoded == fixture['Encoded']['MultiPayment']

0 commit comments

Comments
 (0)