opinion-clob-sdk 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opinion-clob-sdk might be problematic. Click here for more details.
- opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/opinion_clob_sdk/__init__.py +26 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/contract_caller.py +390 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/contracts/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/contracts/conditional_tokens.py +707 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/contracts/erc20.py +111 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/exception.py +11 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/base_builder.py +41 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/exception.py +2 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/order_builder.py +90 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/order_builder_test.py +40 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/constants.py +2 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/order.py +254 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/order_type.py +9 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/sides.py +8 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/signatures.py +8 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/signer.py +20 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +109 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/constants.py +19 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/eip712/__init__.py +176 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/enums.py +6 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/exceptions.py +94 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/multisend.py +347 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe.py +141 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/compatibility_fallback_handler_v1_3_0.py +327 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/multisend_v1_3_0.py +22 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/safe_v1_3_0.py +1035 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/utils.py +26 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_signature.py +364 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_test.py +37 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_tx.py +437 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/signatures.py +63 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/typing.py +17 -0
- opinion_clob_sdk/opinion_clob_sdk/chain/safe/utils.py +218 -0
- opinion_clob_sdk/opinion_clob_sdk/config.py +4 -0
- opinion_clob_sdk/opinion_clob_sdk/model.py +19 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +26 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contract_caller.py +390 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contracts/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contracts/conditional_tokens.py +707 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contracts/erc20.py +111 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/exception.py +11 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/base_builder.py +41 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/exception.py +2 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/order_builder.py +90 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/order_builder_test.py +40 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/constants.py +2 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/order.py +254 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/order_type.py +9 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/sides.py +8 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/signatures.py +8 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/signer.py +20 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +109 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/constants.py +19 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/eip712/__init__.py +176 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/enums.py +6 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/exceptions.py +94 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/multisend.py +347 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe.py +141 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/compatibility_fallback_handler_v1_3_0.py +327 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/multisend_v1_3_0.py +22 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/safe_v1_3_0.py +1035 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/utils.py +26 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_signature.py +364 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_test.py +37 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_tx.py +437 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/signatures.py +63 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/typing.py +17 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/utils.py +218 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/config.py +4 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/model.py +19 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/sdk.py +947 -0
- opinion_clob_sdk/opinion_clob_sdk/sdk.py +947 -0
- opinion_clob_sdk/sdk.py +24 -17
- {opinion_clob_sdk-0.1.1.dist-info → opinion_clob_sdk-0.1.3.dist-info}/METADATA +1 -1
- opinion_clob_sdk-0.1.3.dist-info/RECORD +130 -0
- opinion_clob_sdk-0.1.1.dist-info/RECORD +0 -46
- {opinion_clob_sdk-0.1.1.dist-info → opinion_clob_sdk-0.1.3.dist-info}/WHEEL +0 -0
- {opinion_clob_sdk-0.1.1.dist-info → opinion_clob_sdk-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from eth_typing import ChecksumAddress
|
|
2
|
+
from web3 import Web3
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_safe_contract(w3: Web3, address: ChecksumAddress):
|
|
6
|
+
from .safe_v1_3_0 import abi
|
|
7
|
+
return w3.eth.contract(
|
|
8
|
+
address=address,
|
|
9
|
+
abi=abi
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_compatibility_fallback_handler_contract(w3: Web3, address: ChecksumAddress):
|
|
14
|
+
from .compatibility_fallback_handler_v1_3_0 import abi
|
|
15
|
+
return w3.eth.contract(
|
|
16
|
+
address=address,
|
|
17
|
+
abi=abi
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_multi_send_contract(w3: Web3, address: ChecksumAddress):
|
|
22
|
+
from .multisend_v1_3_0 import abi
|
|
23
|
+
return w3.eth.contract(
|
|
24
|
+
address=address,
|
|
25
|
+
abi=abi
|
|
26
|
+
)
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from logging import getLogger
|
|
4
|
+
from typing import List, Optional, Sequence, Union
|
|
5
|
+
|
|
6
|
+
from eth_abi import decode as decode_abi
|
|
7
|
+
from eth_abi import encode as encode_abi
|
|
8
|
+
from eth_abi.exceptions import DecodingError
|
|
9
|
+
from eth_account.messages import defunct_hash_message
|
|
10
|
+
from eth_typing import BlockIdentifier, ChecksumAddress, HexAddress, HexStr
|
|
11
|
+
from web3 import Web3
|
|
12
|
+
|
|
13
|
+
from .safe_contracts.utils import get_safe_contract, get_compatibility_fallback_handler_contract
|
|
14
|
+
from .utils import fast_to_checksum_address
|
|
15
|
+
from hexbytes import HexBytes
|
|
16
|
+
from web3.exceptions import Web3Exception
|
|
17
|
+
|
|
18
|
+
from .signatures import (
|
|
19
|
+
get_signing_address,
|
|
20
|
+
signature_split,
|
|
21
|
+
signature_to_bytes,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
EthereumBytes = Union[bytes, str]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SafeSignatureException(Exception):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CannotCheckEIP1271ContractSignature(SafeSignatureException):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SafeSignatureType(Enum):
|
|
39
|
+
CONTRACT_SIGNATURE = 0
|
|
40
|
+
APPROVED_HASH = 1
|
|
41
|
+
EOA = 2
|
|
42
|
+
ETH_SIGN = 3
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def from_v(v: int):
|
|
46
|
+
if v == 0:
|
|
47
|
+
return SafeSignatureType.CONTRACT_SIGNATURE
|
|
48
|
+
elif v == 1:
|
|
49
|
+
return SafeSignatureType.APPROVED_HASH
|
|
50
|
+
elif v > 30:
|
|
51
|
+
return SafeSignatureType.ETH_SIGN
|
|
52
|
+
else:
|
|
53
|
+
return SafeSignatureType.EOA
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def uint_to_address(value: int) -> ChecksumAddress:
|
|
57
|
+
"""
|
|
58
|
+
Convert a Solidity `uint` value to a checksummed `address`, removing
|
|
59
|
+
invalid padding bytes if present
|
|
60
|
+
|
|
61
|
+
:return: Checksummed address
|
|
62
|
+
"""
|
|
63
|
+
encoded = encode_abi(["uint"], [value])
|
|
64
|
+
# Remove padding bytes, as Solidity will ignore it but `eth_abi` will not
|
|
65
|
+
encoded_without_padding_bytes = b"\x00" * 12 + encoded[-20:]
|
|
66
|
+
return fast_to_checksum_address(
|
|
67
|
+
decode_abi(["address"], encoded_without_padding_bytes)[0]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class SafeSignature(ABC):
|
|
72
|
+
def __init__(self, signature: EthereumBytes, safe_hash: EthereumBytes):
|
|
73
|
+
"""
|
|
74
|
+
:param signature: Owner signature
|
|
75
|
+
:param safe_hash: Signed hash for the Safe (message or transaction)
|
|
76
|
+
"""
|
|
77
|
+
self.signature = HexBytes(signature)
|
|
78
|
+
self.safe_hash = HexBytes(safe_hash)
|
|
79
|
+
self.v, self.r, self.s = signature_split(self.signature)
|
|
80
|
+
|
|
81
|
+
def __str__(self):
|
|
82
|
+
return f"SafeSignature type={self.signature_type.name} owner={self.owner}"
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def parse_signature(
|
|
86
|
+
cls,
|
|
87
|
+
signatures: EthereumBytes,
|
|
88
|
+
safe_hash: EthereumBytes,
|
|
89
|
+
safe_hash_preimage: Optional[EthereumBytes] = None,
|
|
90
|
+
ignore_trailing: bool = True,
|
|
91
|
+
) -> List["SafeSignature"]:
|
|
92
|
+
"""
|
|
93
|
+
:param signatures: One or more signatures appended. EIP1271 data at the end is supported.
|
|
94
|
+
:param safe_hash: Signed hash for the Safe (message or transaction)
|
|
95
|
+
:param safe_hash_preimage: ``safe_hash`` preimage for EIP1271 validation
|
|
96
|
+
:param ignore_trailing: Ignore trailing data on the signature. Some libraries pad it and add some zeroes at
|
|
97
|
+
the end
|
|
98
|
+
:return: List of SafeSignatures decoded
|
|
99
|
+
"""
|
|
100
|
+
if not signatures:
|
|
101
|
+
return []
|
|
102
|
+
elif isinstance(signatures, str):
|
|
103
|
+
signatures = HexBytes(signatures)
|
|
104
|
+
|
|
105
|
+
signature_size = 65 # For contract signatures there'll be some data at the end
|
|
106
|
+
data_position = len(
|
|
107
|
+
signatures
|
|
108
|
+
) # For contract signatures, to stop parsing at data position
|
|
109
|
+
|
|
110
|
+
safe_signatures = []
|
|
111
|
+
for i in range(0, len(signatures), signature_size):
|
|
112
|
+
if (
|
|
113
|
+
i >= data_position
|
|
114
|
+
): # If contract signature data position is reached, stop
|
|
115
|
+
break
|
|
116
|
+
|
|
117
|
+
signature = signatures[i : i + signature_size]
|
|
118
|
+
if ignore_trailing and len(signature) < 65:
|
|
119
|
+
# Trailing stuff
|
|
120
|
+
break
|
|
121
|
+
v, r, s = signature_split(signature)
|
|
122
|
+
signature_type = SafeSignatureType.from_v(v)
|
|
123
|
+
safe_signature: "SafeSignature"
|
|
124
|
+
if signature_type == SafeSignatureType.CONTRACT_SIGNATURE:
|
|
125
|
+
if s < data_position:
|
|
126
|
+
data_position = s
|
|
127
|
+
contract_signature_len = int.from_bytes(
|
|
128
|
+
signatures[s : s + 32], "big"
|
|
129
|
+
) # Len size is 32 bytes
|
|
130
|
+
contract_signature = signatures[
|
|
131
|
+
s + 32 : s + 32 + contract_signature_len
|
|
132
|
+
] # Skip array size (32 bytes)
|
|
133
|
+
safe_signature = SafeSignatureContract(
|
|
134
|
+
signature,
|
|
135
|
+
safe_hash,
|
|
136
|
+
safe_hash_preimage or safe_hash,
|
|
137
|
+
contract_signature,
|
|
138
|
+
)
|
|
139
|
+
elif signature_type == SafeSignatureType.APPROVED_HASH:
|
|
140
|
+
safe_signature = SafeSignatureApprovedHash(signature, safe_hash)
|
|
141
|
+
elif signature_type == SafeSignatureType.EOA:
|
|
142
|
+
safe_signature = SafeSignatureEOA(signature, safe_hash)
|
|
143
|
+
elif signature_type == SafeSignatureType.ETH_SIGN:
|
|
144
|
+
safe_signature = SafeSignatureEthSign(signature, safe_hash)
|
|
145
|
+
|
|
146
|
+
safe_signatures.append(safe_signature)
|
|
147
|
+
return safe_signatures
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def export_signatures(cls, safe_signatures: Sequence["SafeSignature"]) -> HexBytes:
|
|
151
|
+
"""
|
|
152
|
+
Takes a list of SafeSignature objects and exports them as a valid signature for the contract
|
|
153
|
+
|
|
154
|
+
:param safe_signatures:
|
|
155
|
+
:return: Valid signature for the Safe contract
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
signature = b""
|
|
159
|
+
dynamic_part = b""
|
|
160
|
+
dynamic_offset = len(safe_signatures) * 65
|
|
161
|
+
# Signatures must be sorted by owner
|
|
162
|
+
for safe_signature in sorted(safe_signatures, key=lambda s: s.owner.lower()):
|
|
163
|
+
if isinstance(safe_signature, SafeSignatureContract):
|
|
164
|
+
signature += signature_to_bytes(
|
|
165
|
+
safe_signature.v, safe_signature.r, dynamic_offset
|
|
166
|
+
)
|
|
167
|
+
# encode_abi adds {32 bytes offset}{32 bytes size}. We don't need offset
|
|
168
|
+
contract_signature_padded = encode_abi(
|
|
169
|
+
["bytes"], [safe_signature.contract_signature]
|
|
170
|
+
)[32:]
|
|
171
|
+
contract_signature = contract_signature_padded[
|
|
172
|
+
: 32 + len(safe_signature.contract_signature)
|
|
173
|
+
]
|
|
174
|
+
dynamic_part += contract_signature
|
|
175
|
+
dynamic_offset += len(contract_signature)
|
|
176
|
+
else:
|
|
177
|
+
signature += safe_signature.export_signature()
|
|
178
|
+
return HexBytes(signature + dynamic_part)
|
|
179
|
+
|
|
180
|
+
def export_signature(self) -> HexBytes:
|
|
181
|
+
"""
|
|
182
|
+
Exports signature in a format that's valid individually. That's important for contract signatures, as it
|
|
183
|
+
will fix the offset
|
|
184
|
+
|
|
185
|
+
:return:
|
|
186
|
+
"""
|
|
187
|
+
return self.signature
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
@abstractmethod
|
|
191
|
+
def owner(self):
|
|
192
|
+
"""
|
|
193
|
+
:return: Decode owner from signature, without any further validation (signature can be not valid)
|
|
194
|
+
"""
|
|
195
|
+
raise NotImplementedError
|
|
196
|
+
|
|
197
|
+
@abstractmethod
|
|
198
|
+
def is_valid(self, w3: Web3, safe_address: str) -> bool:
|
|
199
|
+
"""
|
|
200
|
+
:param w3: Web3 instance
|
|
201
|
+
:param safe_address: Required for Approved Hash check
|
|
202
|
+
:return: `True` if signature is valid, `False` otherwise
|
|
203
|
+
"""
|
|
204
|
+
raise NotImplementedError
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
@abstractmethod
|
|
208
|
+
def signature_type(self) -> SafeSignatureType:
|
|
209
|
+
raise NotImplementedError
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class SafeSignatureContract(SafeSignature):
|
|
213
|
+
EIP1271_MAGIC_VALUE = HexBytes(0x20C13B0B)
|
|
214
|
+
EIP1271_MAGIC_VALUE_UPDATED = HexBytes(0x1626BA7E)
|
|
215
|
+
|
|
216
|
+
def __init__(
|
|
217
|
+
self,
|
|
218
|
+
signature: EthereumBytes,
|
|
219
|
+
safe_hash: EthereumBytes,
|
|
220
|
+
safe_hash_preimage: EthereumBytes,
|
|
221
|
+
contract_signature: EthereumBytes,
|
|
222
|
+
):
|
|
223
|
+
"""
|
|
224
|
+
:param signature:
|
|
225
|
+
:param safe_hash: Signed hash for the Safe (message or transaction)
|
|
226
|
+
:param safe_hash_preimage: ``safe_hash`` preimage for EIP1271 validation
|
|
227
|
+
:param contract_signature:
|
|
228
|
+
"""
|
|
229
|
+
super().__init__(signature, safe_hash)
|
|
230
|
+
self.safe_hash_preimage = HexBytes(safe_hash_preimage)
|
|
231
|
+
self.contract_signature = HexBytes(contract_signature)
|
|
232
|
+
|
|
233
|
+
@classmethod
|
|
234
|
+
def from_values(
|
|
235
|
+
cls,
|
|
236
|
+
safe_owner: ChecksumAddress,
|
|
237
|
+
safe_hash: EthereumBytes,
|
|
238
|
+
safe_hash_preimage: EthereumBytes,
|
|
239
|
+
contract_signature: EthereumBytes,
|
|
240
|
+
) -> "SafeSignatureContract":
|
|
241
|
+
signature = signature_to_bytes(
|
|
242
|
+
0, int.from_bytes(HexBytes(safe_owner), byteorder="big"), 65
|
|
243
|
+
)
|
|
244
|
+
return cls(signature, safe_hash, safe_hash_preimage, contract_signature)
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def owner(self) -> ChecksumAddress:
|
|
248
|
+
"""
|
|
249
|
+
:return: Address of contract signing. No further checks to get the owner are needed,
|
|
250
|
+
but it could be a non-existing contract
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
return uint_to_address(self.r)
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def signature_type(self) -> SafeSignatureType:
|
|
257
|
+
return SafeSignatureType.CONTRACT_SIGNATURE
|
|
258
|
+
|
|
259
|
+
def export_signature(self) -> HexBytes:
|
|
260
|
+
"""
|
|
261
|
+
Fix offset (s) and append `contract_signature` at the end of the signature
|
|
262
|
+
|
|
263
|
+
:return:
|
|
264
|
+
"""
|
|
265
|
+
# encode_abi adds {32 bytes offset}{32 bytes size}. We don't need offset
|
|
266
|
+
contract_signature_padded = encode_abi(["bytes"], [self.contract_signature])[
|
|
267
|
+
32:
|
|
268
|
+
]
|
|
269
|
+
contract_signature = contract_signature_padded[
|
|
270
|
+
: 32 + len(self.contract_signature)
|
|
271
|
+
]
|
|
272
|
+
dynamic_offset = 65
|
|
273
|
+
|
|
274
|
+
return HexBytes(
|
|
275
|
+
signature_to_bytes(self.v, self.r, dynamic_offset) + contract_signature
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
def is_valid(self, w3: Web3, *args) -> bool:
|
|
279
|
+
compatibility_fallback_handler = get_compatibility_fallback_handler_contract(
|
|
280
|
+
w3, self.owner
|
|
281
|
+
)
|
|
282
|
+
is_valid_signature_fn = (
|
|
283
|
+
compatibility_fallback_handler.get_function_by_signature(
|
|
284
|
+
"isValidSignature(bytes,bytes)"
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
try:
|
|
288
|
+
return is_valid_signature_fn(
|
|
289
|
+
self.safe_hash_preimage, self.contract_signature
|
|
290
|
+
).call() in (
|
|
291
|
+
self.EIP1271_MAGIC_VALUE,
|
|
292
|
+
self.EIP1271_MAGIC_VALUE_UPDATED,
|
|
293
|
+
)
|
|
294
|
+
except (Web3Exception, DecodingError, ValueError):
|
|
295
|
+
# Error using `pending` block identifier or contract does not exist
|
|
296
|
+
logger.warning(
|
|
297
|
+
"Cannot check EIP1271 signature from contract %s", self.owner
|
|
298
|
+
)
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class SafeSignatureApprovedHash(SafeSignature):
|
|
303
|
+
@property
|
|
304
|
+
def owner(self):
|
|
305
|
+
return uint_to_address(self.r)
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def signature_type(self):
|
|
309
|
+
return SafeSignatureType.APPROVED_HASH
|
|
310
|
+
|
|
311
|
+
@classmethod
|
|
312
|
+
def build_for_owner(cls, owner: str, safe_hash: str) -> "SafeSignatureApprovedHash":
|
|
313
|
+
r = owner.lower().replace("0x", "").rjust(64, "0")
|
|
314
|
+
s = "0" * 64
|
|
315
|
+
v = "01"
|
|
316
|
+
return cls(HexBytes(r + s + v), safe_hash)
|
|
317
|
+
|
|
318
|
+
def is_valid(self, w3: Web3, safe_address: str) -> bool:
|
|
319
|
+
safe_contract = get_safe_contract(
|
|
320
|
+
w3, ChecksumAddress(HexAddress(HexStr(safe_address)))
|
|
321
|
+
)
|
|
322
|
+
exception: Exception
|
|
323
|
+
|
|
324
|
+
block_identifiers: List[BlockIdentifier] = ["pending", "latest"]
|
|
325
|
+
for block_identifier in block_identifiers:
|
|
326
|
+
try:
|
|
327
|
+
return (
|
|
328
|
+
safe_contract.functions.approvedHashes(
|
|
329
|
+
self.owner, self.safe_hash
|
|
330
|
+
).call(block_identifier=block_identifier)
|
|
331
|
+
== 1
|
|
332
|
+
)
|
|
333
|
+
except (Web3Exception, DecodingError, ValueError) as e:
|
|
334
|
+
# Error using `pending` block identifier
|
|
335
|
+
exception = e
|
|
336
|
+
raise exception # This should never happen
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class SafeSignatureEthSign(SafeSignature):
|
|
340
|
+
@property
|
|
341
|
+
def owner(self):
|
|
342
|
+
# defunct_hash_message prepends `\x19Ethereum Signed Message:\n32`
|
|
343
|
+
message_hash = defunct_hash_message(primitive=self.safe_hash)
|
|
344
|
+
return get_signing_address(message_hash, self.v - 4, self.r, self.s)
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def signature_type(self):
|
|
348
|
+
return SafeSignatureType.ETH_SIGN
|
|
349
|
+
|
|
350
|
+
def is_valid(self, *args) -> bool:
|
|
351
|
+
return True
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class SafeSignatureEOA(SafeSignature):
|
|
355
|
+
@property
|
|
356
|
+
def owner(self):
|
|
357
|
+
return get_signing_address(self.safe_hash, self.v, self.r, self.s)
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def signature_type(self):
|
|
361
|
+
return SafeSignatureType.EOA
|
|
362
|
+
|
|
363
|
+
def is_valid(self, *args) -> bool:
|
|
364
|
+
return True
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from web3 import Web3
|
|
3
|
+
from web3.providers import HTTPProvider
|
|
4
|
+
|
|
5
|
+
from .constants import NULL_ADDRESS
|
|
6
|
+
from .enums import SafeOperationEnum
|
|
7
|
+
from .safe import Safe
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestSafeRx(unittest.TestCase):
|
|
11
|
+
def setUp(self) -> None:
|
|
12
|
+
self.private_key = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
|
|
13
|
+
self.safe_address = '0x8F58a1ab58e18Bb3f8ACf5E14c046D4F7add824a'
|
|
14
|
+
self.compatibility_handler_address = '0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4'
|
|
15
|
+
self.rpc_url = 'https://mainnet.base.org'
|
|
16
|
+
self.w3 = Web3(HTTPProvider(self.rpc_url))
|
|
17
|
+
|
|
18
|
+
def test_sign_multi_sig_tx(self):
|
|
19
|
+
# self.w3.keccak(text='hash')
|
|
20
|
+
|
|
21
|
+
safe = Safe(self.w3, self.private_key, self.safe_address, '')
|
|
22
|
+
safe_tx = safe.build_multisig_tx(
|
|
23
|
+
to='',
|
|
24
|
+
value=0,
|
|
25
|
+
data=b'',
|
|
26
|
+
operation=SafeOperationEnum.CALL.value,
|
|
27
|
+
safe_tx_gas=0,
|
|
28
|
+
base_gas=0,
|
|
29
|
+
gas_price=0,
|
|
30
|
+
gas_token=NULL_ADDRESS,
|
|
31
|
+
refund_receiver=NULL_ADDRESS,
|
|
32
|
+
safe_nonce=0,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
signatures = safe_tx.sign(self.private_key)
|
|
36
|
+
expected_sig = '2e83d2137e103b2cd5bfbf624d796acd7aec0ca8ea007f3a8093e3844a0df12360493c35f4c49b42b55b17c2290b91ed9a9b787641f1433268552ebcb14edd2c1b'
|
|
37
|
+
self.assertEqual(expected_sig, signatures.hex(), 'invalid signature')
|