Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0209ff4
update fixtures
alexbarnsley Feb 19, 2025
9214b03
keccak integration
alexbarnsley Feb 19, 2025
ab8ce08
handle different contracts when deserializing
alexbarnsley Feb 19, 2025
79a35c3
better handling of stripping 0x from data
alexbarnsley Feb 19, 2025
feecff0
scrub
alexbarnsley Feb 19, 2025
15e922c
update other fixture data
alexbarnsley Feb 19, 2025
029b9be
fix wif test
alexbarnsley Feb 19, 2025
abe3f34
move hardcoded tx values to fixtures
alexbarnsley Feb 19, 2025
3348de0
deserialize test other contract transactions
alexbarnsley Feb 19, 2025
a1064a1
update rpl decoder expectations
alexbarnsley Feb 19, 2025
b144d17
remove unused transaction class
alexbarnsley Feb 19, 2025
a8a17a6
remove additional lstrips
alexbarnsley Feb 19, 2025
ee8ea60
made AbstractTransaction decode_payload method static
alexbarnsley Feb 19, 2025
5655d2d
pass payload to tx type constructor to prevent double-up of decoding …
alexbarnsley Feb 21, 2025
3aa900c
update multipayment address
alexbarnsley Feb 21, 2025
ec8f0e0
fix check for valid address
alexbarnsley Feb 21, 2025
9cab1f8
add multipayment abi
alexbarnsley Feb 21, 2025
10c1f0c
test validating addresses
alexbarnsley Feb 21, 2025
e9c37bb
scrub
alexbarnsley Feb 21, 2025
448ed28
fix parameter decoder for arrays
alexbarnsley Feb 21, 2025
8c981d6
feat: integrate multipayments
alexbarnsley Feb 21, 2025
003755c
test
alexbarnsley Feb 21, 2025
976afbc
rename multipayment builder file
alexbarnsley Feb 21, 2025
64ff3ac
update import for test
alexbarnsley Feb 21, 2025
03fc19d
Merge remote-tracking branch 'origin/feat/mainsail' into feat/integra…
alexbarnsley Feb 24, 2025
a475b1f
adjust test with new fixture data
alexbarnsley Feb 24, 2025
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
1 change: 1 addition & 0 deletions crypto/enums/abi_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
class AbiFunction(Enum):
VOTE = 'vote'
UNVOTE = 'unvote'
MULTIPAYMENT = 'pay'
USERNAME_REGISTRATION = 'registerUsername'
USERNAME_RESIGNATION = 'resignUsername'
VALIDATOR_REGISTRATION = 'registerValidator'
Expand Down
1 change: 1 addition & 0 deletions crypto/enums/contract_abi_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
class ContractAbiType(Enum):
CUSTOM = 'custom'
CONSENSUS = 'consensus'
MULTIPAYMENT = 'multipayment'
USERNAMES = 'usernames'
2 changes: 1 addition & 1 deletion crypto/enums/contract_addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

class ContractAddresses(Enum):
CONSENSUS = '0x535B3D7A252fa034Ed71F0C53ec0C6F784cB64E1'
MULTIPAYMENT = '0x83769BeEB7e5405ef0B7dc3C66C43E3a51A6d27f'
MULTIPAYMENT = '0x00EFd0D4639191C49908A7BddbB9A11A994A8527'
USERNAMES = '0x2c1DE3b4Dbb4aDebEbB5dcECAe825bE2a9fc6eb6'
26 changes: 26 additions & 0 deletions crypto/transactions/builder/multipayment_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Optional
from crypto.enums.contract_addresses import ContractAddresses
from crypto.transactions.builder.base import AbstractTransactionBuilder
from crypto.transactions.types.multipayment import Multipayment

class MultipaymentBuilder(AbstractTransactionBuilder):
def __init__(self, data: Optional[dict] = None):
super().__init__(data)

self.transaction.data['pay'] = [[], []]

self.recipient_address(ContractAddresses.MULTIPAYMENT.value)
self.transaction.refresh_payload_data()

def pay(self, address: str, amount: str):
self.transaction.data['pay'][0].append(address)
self.transaction.data['pay'][1].append(amount)

self.transaction.refresh_payload_data()

self.transaction.data['value'] = str(int(self.transaction.data['value']) + int(amount))

return self

def get_transaction_instance(self, data: dict):
return Multipayment(data)
13 changes: 10 additions & 3 deletions crypto/transactions/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from crypto.enums.constants import Constants
from crypto.enums.contract_abi_type import ContractAbiType
from crypto.transactions.types.abstract_transaction import AbstractTransaction
from crypto.transactions.types.multipayment import Multipayment
from crypto.transactions.types.transfer import Transfer
from crypto.transactions.types.evm_call import EvmCall
from crypto.transactions.types.username_registration import UsernameRegistration
Expand Down Expand Up @@ -59,20 +60,26 @@ def deserialize(self) -> AbstractTransaction:
return transaction

def guess_transaction_from_data(self, data: dict) -> AbstractTransaction:
multipayment_payload_data = self.decode_payload(data, ContractAbiType.MULTIPAYMENT)
if multipayment_payload_data is not None:
function_name = multipayment_payload_data.get('functionName')
if function_name == AbiFunction.MULTIPAYMENT.value:
return Multipayment(data, multipayment_payload_data)

if data['value'] != '0':
return Transfer(data)

consensus_payload_data = self.decode_payload(data)
if consensus_payload_data is not None:
function_name = consensus_payload_data.get('functionName')
if function_name == AbiFunction.VOTE.value:
return Vote(data)
return Vote(data, consensus_payload_data)

if function_name == AbiFunction.UNVOTE.value:
return Unvote(data)

if function_name == AbiFunction.VALIDATOR_REGISTRATION.value:
return ValidatorRegistration(data)
return ValidatorRegistration(data, consensus_payload_data)

if function_name == AbiFunction.VALIDATOR_RESIGNATION.value:
return ValidatorResignation(data)
Expand All @@ -81,7 +88,7 @@ def guess_transaction_from_data(self, data: dict) -> AbstractTransaction:
if username_payload_data is not None:
function_name = username_payload_data.get('functionName')
if function_name == AbiFunction.USERNAME_REGISTRATION.value:
return UsernameRegistration(data)
return UsernameRegistration(data, username_payload_data)

if function_name == AbiFunction.USERNAME_RESIGNATION.value:
return UsernameResignation(data)
Expand Down
24 changes: 24 additions & 0 deletions crypto/transactions/types/multipayment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import Optional
from crypto.enums.contract_abi_type import ContractAbiType
from crypto.transactions.types.abstract_transaction import AbstractTransaction
from crypto.utils.abi_encoder import AbiEncoder
from crypto.enums.abi_function import AbiFunction

class Multipayment(AbstractTransaction):
def __init__(self, data: Optional[dict] = None, payload: Optional[dict] = None):
data = data or {}
if payload is None:
payload = self.decode_payload(data, ContractAbiType.MULTIPAYMENT)

if payload:
data['pay'] = payload.get('args', [])

super().__init__(data)

def get_payload(self) -> str:
if 'pay' not in self.data:
return ''

encoder = AbiEncoder(ContractAbiType.MULTIPAYMENT)

return encoder.encode_function_call(AbiFunction.MULTIPAYMENT.value, self.data['pay'])
6 changes: 4 additions & 2 deletions crypto/transactions/types/username_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
from crypto.enums.abi_function import AbiFunction

class UsernameRegistration(AbstractTransaction):
def __init__(self, data: Optional[dict] = None):
def __init__(self, data: Optional[dict] = None, payload: Optional[dict] = None):
data = data or {}
payload = self.decode_payload(data, ContractAbiType.USERNAMES)
if payload is None:
payload = self.decode_payload(data, ContractAbiType.USERNAMES)

if payload:
data['username'] = payload.get('args', [None])[0] if payload.get('args') else None

Expand Down
7 changes: 5 additions & 2 deletions crypto/transactions/types/validator_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
from crypto.utils.transaction_utils import TransactionUtils

class ValidatorRegistration(AbstractTransaction):
def __init__(self, data: Optional[dict] = None):
def __init__(self, data: Optional[dict] = None, payload: Optional[dict] = None):
data = data or {}
payload = self.decode_payload(data)

if payload is None:
payload = self.decode_payload(data)

if payload:
data['validatorPublicKey'] = TransactionUtils.parse_hex_from_str(payload.get('args', [None])[0]) if payload.get('args') else None
super().__init__(data)
Expand Down
6 changes: 4 additions & 2 deletions crypto/transactions/types/vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
from crypto.enums.abi_function import AbiFunction

class Vote(AbstractTransaction):
def __init__(self, data: Optional[dict] = None):
def __init__(self, data: Optional[dict] = None, payload: Optional[dict] = None):
data = data or {}
payload = self.decode_payload(data)
if payload is None:
payload = self.decode_payload(data)

if payload:
data['vote'] = payload.get('args', [None])[0] if payload.get('args') else None

Expand Down
110 changes: 110 additions & 0 deletions crypto/utils/abi/json/Abi.Multipayment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{
"abi": [
{
"type": "function",
"name": "pay",
"inputs": [
{
"name": "recipients",
"type": "address[]",
"internalType": "address payable[]"
},
{
"name": "amounts",
"type": "uint256[]",
"internalType": "uint256[]"
}
],
"outputs": [],
"stateMutability": "payable"
},
{ "type": "error", "name": "FailedToSendEther", "inputs": [] },
{ "type": "error", "name": "InvalidValue", "inputs": [] },
{
"type": "error",
"name": "RecipientsAndAmountsMismatch",
"inputs": []
}
],
"bytecode": {
"object": "0x6080604052348015600e575f5ffd5b506102c98061001c5f395ff3fe60806040526004361061001d575f3560e01c8063084ce70814610021575b5f5ffd5b61003461002f3660046101c1565b610036565b005b828114610056576040516366d5293b60e11b815260040160405180910390fd5b5f805b8281101561008f578383828181106100735761007361022d565b90506020020135826100859190610241565b9150600101610059565b508034146100b057604051632a9ffab760e21b815260040160405180910390fd5b5f5b84811015610171575f8686838181106100cd576100cd61022d565b90506020020160208101906100e29190610266565b6001600160a01b03168585848181106100fd576100fd61022d565b905060200201356040515f6040518083038185875af1925050503d805f8114610141576040519150601f19603f3d011682016040523d82523d5f602084013e610146565b606091505b505090508061016857604051630dcf35db60e41b815260040160405180910390fd5b506001016100b2565b505050505050565b5f5f83601f840112610189575f5ffd5b50813567ffffffffffffffff8111156101a0575f5ffd5b6020830191508360208260051b85010111156101ba575f5ffd5b9250929050565b5f5f5f5f604085870312156101d4575f5ffd5b843567ffffffffffffffff8111156101ea575f5ffd5b6101f687828801610179565b909550935050602085013567ffffffffffffffff811115610215575f5ffd5b61022187828801610179565b95989497509550505050565b634e487b7160e01b5f52603260045260245ffd5b8082018082111561026057634e487b7160e01b5f52601160045260245ffd5b92915050565b5f60208284031215610276575f5ffd5b81356001600160a01b038116811461028c575f5ffd5b939250505056fea2646970667358221220bfee9113d4628767f3e4ea5baeb21f9c3bd88ea4c440d0a915dae090d37cd9a664736f6c634300081b0033",
"sourceMap": "81:836:23:-:0;;;;;;;;;;;;;;;;;;;",
"linkReferences": {}
},
"deployedBytecode": {
"object": "0x60806040526004361061001d575f3560e01c8063084ce70814610021575b5f5ffd5b61003461002f3660046101c1565b610036565b005b828114610056576040516366d5293b60e11b815260040160405180910390fd5b5f805b8281101561008f578383828181106100735761007361022d565b90506020020135826100859190610241565b9150600101610059565b508034146100b057604051632a9ffab760e21b815260040160405180910390fd5b5f5b84811015610171575f8686838181106100cd576100cd61022d565b90506020020160208101906100e29190610266565b6001600160a01b03168585848181106100fd576100fd61022d565b905060200201356040515f6040518083038185875af1925050503d805f8114610141576040519150601f19603f3d011682016040523d82523d5f602084013e610146565b606091505b505090508061016857604051630dcf35db60e41b815260040160405180910390fd5b506001016100b2565b505050505050565b5f5f83601f840112610189575f5ffd5b50813567ffffffffffffffff8111156101a0575f5ffd5b6020830191508360208260051b85010111156101ba575f5ffd5b9250929050565b5f5f5f5f604085870312156101d4575f5ffd5b843567ffffffffffffffff8111156101ea575f5ffd5b6101f687828801610179565b909550935050602085013567ffffffffffffffff811115610215575f5ffd5b61022187828801610179565b95989497509550505050565b634e487b7160e01b5f52603260045260245ffd5b8082018082111561026057634e487b7160e01b5f52601160045260245ffd5b92915050565b5f60208284031215610276575f5ffd5b81356001600160a01b038116811461028c575f5ffd5b939250505056fea2646970667358221220bfee9113d4628767f3e4ea5baeb21f9c3bd88ea4c440d0a915dae090d37cd9a664736f6c634300081b0033",
"sourceMap": "81:836:23:-:0;;;;;;;;;;;;;;;;;;;;;209:706;;;;;;:::i;:::-;;:::i;:::-;;;318:35;;;314:103;;376:30;;-1:-1:-1;;;376:30:23;;;;;;;;;;;314:103;492:13;;519:89;539:18;;;519:89;;;587:7;;595:1;587:10;;;;;;;:::i;:::-;;;;;;;578:19;;;;;:::i;:::-;;-1:-1:-1;559:3:23;;519:89;;;;634:5;621:9;:18;617:70;;662:14;;-1:-1:-1;;;662:14:23;;;;;;;;;;;617:70;702:9;697:212;717:21;;;697:212;;;760:9;774:10;;785:1;774:13;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;774:18:23;800:7;;808:1;800:10;;;;;;;:::i;:::-;;;;;;;774:41;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;759:56;;;834:4;829:70;;865:19;;-1:-1:-1;;;865:19:23;;;;;;;;;;;829:70;-1:-1:-1;740:3:23;;697:212;;;;304:611;209:706;;;;:::o;14:375:25:-;85:8;95:6;149:3;142:4;134:6;130:17;126:27;116:55;;167:1;164;157:12;116:55;-1:-1:-1;190:20:25;;233:18;222:30;;219:50;;;265:1;262;255:12;219:50;302:4;294:6;290:17;278:29;;362:3;355:4;345:6;342:1;338:14;330:6;326:27;322:38;319:47;316:67;;;379:1;376;369:12;316:67;14:375;;;;;:::o;394:792::-;524:6;532;540;548;601:2;589:9;580:7;576:23;572:32;569:52;;;617:1;614;607:12;569:52;657:9;644:23;690:18;682:6;679:30;676:50;;;722:1;719;712:12;676:50;761:78;831:7;822:6;811:9;807:22;761:78;:::i;:::-;858:8;;-1:-1:-1;735:104:25;-1:-1:-1;;946:2:25;931:18;;918:32;975:18;962:32;;959:52;;;1007:1;1004;997:12;959:52;1046:80;1118:7;1107:8;1096:9;1092:24;1046:80;:::i;:::-;394:792;;;;-1:-1:-1;1145:8:25;-1:-1:-1;;;;394:792:25:o;1191:127::-;1252:10;1247:3;1243:20;1240:1;1233:31;1283:4;1280:1;1273:15;1307:4;1304:1;1297:15;1323:222;1388:9;;;1409:10;;;1406:133;;;1461:10;1456:3;1452:20;1449:1;1442:31;1496:4;1493:1;1486:15;1524:4;1521:1;1514:15;1406:133;1323:222;;;;:::o;1550:294::-;1617:6;1670:2;1658:9;1649:7;1645:23;1641:32;1638:52;;;1686:1;1683;1676:12;1638:52;1712:23;;-1:-1:-1;;;;;1764:31:25;;1754:42;;1744:70;;1810:1;1807;1800:12;1744:70;1833:5;1550:294;-1:-1:-1;;;1550:294:25:o",
"linkReferences": {}
},
"methodIdentifiers": { "pay(address[],uint256[])": "084ce708" },
"rawMetadata": "{\"compiler\":{\"version\":\"0.8.27+commit.40a35a09\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"FailedToSendEther\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidValue\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RecipientsAndAmountsMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address payable[]\",\"name\":\"recipients\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"}],\"name\":\"pay\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/multi-payment/MultiPayment.sol\":\"MultiPayment\"},\"evmVersion\":\"shanghai\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@contracts/=src/\",\":@forge-std/=lib/forge-std/src/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\",\":solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/\"]},\"sources\":{\"src/multi-payment/MultiPayment.sol\":{\"keccak256\":\"0x804a5823ffdb1866bc5f42a0fe3688c8e153da92e279c7b79aa3d73472a1ce35\",\"license\":\"GNU GENERAL PUBLIC LICENSE\",\"urls\":[\"bzz-raw://43df2e2ee0c0a76dd4ee8bd6cc81d65c80fb1aaada5e925d129a91de434d2c52\",\"dweb:/ipfs/QmRj4GTC5R6QTcaBxmghrH3ABuDXXwvHpi4yYjd1e8VduW\"]}},\"version\":1}",
"metadata": {
"compiler": { "version": "0.8.27+commit.40a35a09" },
"language": "Solidity",
"output": {
"abi": [
{ "inputs": [], "type": "error", "name": "FailedToSendEther" },
{ "inputs": [], "type": "error", "name": "InvalidValue" },
{
"inputs": [],
"type": "error",
"name": "RecipientsAndAmountsMismatch"
},
{
"inputs": [
{
"internalType": "address payable[]",
"name": "recipients",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"stateMutability": "payable",
"type": "function",
"name": "pay"
}
],
"devdoc": { "kind": "dev", "methods": {}, "version": 1 },
"userdoc": { "kind": "user", "methods": {}, "version": 1 }
},
"settings": {
"remappings": [
"@contracts/=src/",
"@forge-std/=lib/forge-std/src/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/",
"ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
"forge-std/=lib/forge-std/src/",
"halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/",
"openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/",
"solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/"
],
"optimizer": { "enabled": true, "runs": 200 },
"metadata": { "bytecodeHash": "ipfs" },
"compilationTarget": {
"src/multi-payment/MultiPayment.sol": "MultiPayment"
},
"evmVersion": "shanghai",
"libraries": {}
},
"sources": {
"src/multi-payment/MultiPayment.sol": {
"keccak256": "0x804a5823ffdb1866bc5f42a0fe3688c8e153da92e279c7b79aa3d73472a1ce35",
"urls": [
"bzz-raw://43df2e2ee0c0a76dd4ee8bd6cc81d65c80fb1aaada5e925d129a91de434d2c52",
"dweb:/ipfs/QmRj4GTC5R6QTcaBxmghrH3ABuDXXwvHpi4yYjd1e8VduW"
],
"license": "GNU GENERAL PUBLIC LICENSE"
}
},
"version": 1
},
"id": 23
}
6 changes: 5 additions & 1 deletion crypto/utils/abi_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ def strip_hex_prefix(self, hex_str):
def is_valid_address(self, address):
# Compute the checksum address and compare
computed_checksum_address = get_checksum_address(address.lower())
return address == computed_checksum_address

return address.lower() == computed_checksum_address.lower()

def keccak256(self, input_str):
k = keccak.new(digest_bits=256)
Expand All @@ -62,6 +63,9 @@ def __contract_abi_path(self, abi_type: ContractAbiType, path: Optional[str] = N
if abi_type == ContractAbiType.CONSENSUS:
return os.path.join(os.path.dirname(__file__), 'abi/json', 'Abi.Consensus.json')

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

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

Expand Down
Loading