olas-operate-middleware 0.1.0rc59__py3-none-any.whl → 0.13.2__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.
- olas_operate_middleware-0.13.2.dist-info/METADATA +75 -0
- olas_operate_middleware-0.13.2.dist-info/RECORD +101 -0
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/WHEEL +1 -1
- operate/__init__.py +17 -0
- operate/account/user.py +35 -9
- operate/bridge/bridge_manager.py +470 -0
- operate/bridge/providers/lifi_provider.py +377 -0
- operate/bridge/providers/native_bridge_provider.py +677 -0
- operate/bridge/providers/provider.py +469 -0
- operate/bridge/providers/relay_provider.py +457 -0
- operate/cli.py +1565 -417
- operate/constants.py +60 -12
- operate/data/README.md +19 -0
- operate/data/contracts/{service_staking_token → dual_staking_token}/__init__.py +2 -2
- operate/data/contracts/dual_staking_token/build/DualStakingToken.json +443 -0
- operate/data/contracts/dual_staking_token/contract.py +132 -0
- operate/data/contracts/dual_staking_token/contract.yaml +23 -0
- operate/{ledger/base.py → data/contracts/foreign_omnibridge/__init__.py} +2 -19
- operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +1372 -0
- operate/data/contracts/foreign_omnibridge/contract.py +130 -0
- operate/data/contracts/foreign_omnibridge/contract.yaml +23 -0
- operate/{ledger/solana.py → data/contracts/home_omnibridge/__init__.py} +2 -20
- operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +1421 -0
- operate/data/contracts/home_omnibridge/contract.py +80 -0
- operate/data/contracts/home_omnibridge/contract.yaml +23 -0
- operate/data/contracts/l1_standard_bridge/__init__.py +20 -0
- operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +831 -0
- operate/data/contracts/l1_standard_bridge/contract.py +158 -0
- operate/data/contracts/l1_standard_bridge/contract.yaml +23 -0
- operate/data/contracts/l2_standard_bridge/__init__.py +20 -0
- operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +626 -0
- operate/data/contracts/l2_standard_bridge/contract.py +130 -0
- operate/data/contracts/l2_standard_bridge/contract.yaml +23 -0
- operate/data/contracts/mech_activity/__init__.py +20 -0
- operate/data/contracts/mech_activity/build/MechActivity.json +111 -0
- operate/data/contracts/mech_activity/contract.py +44 -0
- operate/data/contracts/mech_activity/contract.yaml +23 -0
- operate/data/contracts/optimism_mintable_erc20/__init__.py +20 -0
- operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +491 -0
- operate/data/contracts/optimism_mintable_erc20/contract.py +45 -0
- operate/data/contracts/optimism_mintable_erc20/contract.yaml +23 -0
- operate/data/contracts/recovery_module/__init__.py +20 -0
- operate/data/contracts/recovery_module/build/RecoveryModule.json +811 -0
- operate/data/contracts/recovery_module/contract.py +61 -0
- operate/data/contracts/recovery_module/contract.yaml +23 -0
- operate/data/contracts/requester_activity_checker/__init__.py +20 -0
- operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +111 -0
- operate/data/contracts/requester_activity_checker/contract.py +33 -0
- operate/data/contracts/requester_activity_checker/contract.yaml +23 -0
- operate/data/contracts/staking_token/__init__.py +20 -0
- operate/data/contracts/staking_token/build/StakingToken.json +1336 -0
- operate/data/contracts/{service_staking_token → staking_token}/contract.py +27 -13
- operate/data/contracts/staking_token/contract.yaml +23 -0
- operate/data/contracts/uniswap_v2_erc20/contract.yaml +3 -1
- operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +20 -0
- operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +363 -0
- operate/keys.py +118 -33
- operate/ledger/__init__.py +159 -56
- operate/ledger/profiles.py +321 -18
- operate/migration.py +555 -0
- operate/{http → operate_http}/__init__.py +3 -2
- operate/{http → operate_http}/exceptions.py +6 -4
- operate/operate_types.py +544 -0
- operate/pearl.py +13 -1
- operate/quickstart/analyse_logs.py +118 -0
- operate/quickstart/claim_staking_rewards.py +104 -0
- operate/quickstart/reset_configs.py +106 -0
- operate/quickstart/reset_password.py +70 -0
- operate/quickstart/reset_staking.py +145 -0
- operate/quickstart/run_service.py +726 -0
- operate/quickstart/stop_service.py +72 -0
- operate/quickstart/terminate_on_chain_service.py +83 -0
- operate/quickstart/utils.py +298 -0
- operate/resource.py +62 -3
- operate/services/agent_runner.py +202 -0
- operate/services/deployment_runner.py +868 -0
- operate/services/funding_manager.py +929 -0
- operate/services/health_checker.py +280 -0
- operate/services/manage.py +2356 -620
- operate/services/protocol.py +1246 -340
- operate/services/service.py +756 -391
- operate/services/utils/mech.py +103 -0
- operate/services/utils/tendermint.py +86 -12
- operate/settings.py +70 -0
- operate/utils/__init__.py +135 -0
- operate/utils/gnosis.py +407 -80
- operate/utils/single_instance.py +226 -0
- operate/utils/ssl.py +133 -0
- operate/wallet/master.py +708 -123
- operate/wallet/wallet_recovery_manager.py +507 -0
- olas_operate_middleware-0.1.0rc59.dist-info/METADATA +0 -304
- olas_operate_middleware-0.1.0rc59.dist-info/RECORD +0 -41
- operate/data/contracts/service_staking_token/build/ServiceStakingToken.json +0 -1273
- operate/data/contracts/service_staking_token/contract.yaml +0 -23
- operate/ledger/ethereum.py +0 -48
- operate/types.py +0 -260
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/entry_points.txt +0 -0
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info/licenses}/LICENSE +0 -0
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
#
|
|
18
18
|
# ------------------------------------------------------------------------------
|
|
19
19
|
|
|
20
|
-
"""This module contains the class to connect to the `
|
|
20
|
+
"""This module contains the class to connect to the `StakingToken` contract."""
|
|
21
21
|
|
|
22
22
|
from enum import Enum
|
|
23
23
|
|
|
@@ -27,18 +27,10 @@ from aea.contracts.base import Contract
|
|
|
27
27
|
from aea.crypto.base import LedgerApi
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class
|
|
31
|
-
"""Staking
|
|
30
|
+
class StakingTokenContract(Contract):
|
|
31
|
+
"""The Staking Token contract."""
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
STAKED = 1
|
|
35
|
-
EVICTED = 2
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class ServiceStakingTokenContract(Contract):
|
|
39
|
-
"""The Service Staking contract."""
|
|
40
|
-
|
|
41
|
-
contract_id = PublicId.from_str("valory/service_staking_token:0.1.0")
|
|
33
|
+
contract_id = PublicId.from_str("valory/staking_token:0.1.0")
|
|
42
34
|
|
|
43
35
|
@classmethod
|
|
44
36
|
def get_service_staking_state(
|
|
@@ -50,7 +42,7 @@ class ServiceStakingTokenContract(Contract):
|
|
|
50
42
|
"""Check whether the service is staked."""
|
|
51
43
|
contract_instance = cls.get_instance(ledger_api, contract_address)
|
|
52
44
|
res = contract_instance.functions.getStakingState(service_id).call()
|
|
53
|
-
return dict(data=
|
|
45
|
+
return dict(data=res)
|
|
54
46
|
|
|
55
47
|
@classmethod
|
|
56
48
|
def build_stake_tx(
|
|
@@ -121,6 +113,28 @@ class ServiceStakingTokenContract(Contract):
|
|
|
121
113
|
ts = contract.functions.getNextRewardCheckpointTimestamp().call()
|
|
122
114
|
return dict(data=ts)
|
|
123
115
|
|
|
116
|
+
@classmethod
|
|
117
|
+
def ts_checkpoint(
|
|
118
|
+
cls,
|
|
119
|
+
ledger_api: LedgerApi,
|
|
120
|
+
contract_address: str,
|
|
121
|
+
) -> JSONLike:
|
|
122
|
+
"""Retrieve the checkpoint's timestamp."""
|
|
123
|
+
contract = cls.get_instance(ledger_api, contract_address)
|
|
124
|
+
ts_checkpoint = contract.functions.tsCheckpoint().call()
|
|
125
|
+
return dict(data=ts_checkpoint)
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def liveness_ratio(
|
|
129
|
+
cls,
|
|
130
|
+
ledger_api: LedgerApi,
|
|
131
|
+
contract_address: str,
|
|
132
|
+
) -> JSONLike:
|
|
133
|
+
"""Retrieve the liveness ratio."""
|
|
134
|
+
contract = cls.get_instance(ledger_api, contract_address)
|
|
135
|
+
liveness_ratio = contract.functions.livenessRatio().call()
|
|
136
|
+
return dict(data=liveness_ratio)
|
|
137
|
+
|
|
124
138
|
@classmethod
|
|
125
139
|
def get_liveness_period(
|
|
126
140
|
cls,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: staking_token
|
|
2
|
+
author: valory
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
type: contract
|
|
5
|
+
description: Service staking token contract
|
|
6
|
+
license: Apache-2.0
|
|
7
|
+
aea_version: '>=1.0.0, <2.0.0'
|
|
8
|
+
fingerprint:
|
|
9
|
+
__init__.py: bafybeicmgkagyhgwn2ktcdjbprijalbdyj26cvza4d3b7uvmehvy4mmr3i
|
|
10
|
+
build/StakingToken.json: bafybeibhcwyawq377innrpq4ytpw5kotufjqo7cyd2rjhyit34mnbks5b4
|
|
11
|
+
contract.py: bafybeigjt7a2biiorp4vmj4d3qkm3xbbohnawoetywojye5akhavlvkrxm
|
|
12
|
+
fingerprint_ignore_patterns: []
|
|
13
|
+
contracts: []
|
|
14
|
+
class_name: StakingTokenContract
|
|
15
|
+
contract_interface_paths:
|
|
16
|
+
ethereum: build/StakingToken.json
|
|
17
|
+
dependencies:
|
|
18
|
+
open-aea-ledger-ethereum:
|
|
19
|
+
version: <2,>=1.53.0
|
|
20
|
+
open-aea-test-autonomy:
|
|
21
|
+
version: <1,>=0.14.14.post1
|
|
22
|
+
web3:
|
|
23
|
+
version: <7,>=6.0.0
|
|
@@ -8,7 +8,9 @@ aea_version: '>=1.0.0, <2.0.0'
|
|
|
8
8
|
fingerprint:
|
|
9
9
|
__init__.py: bafybeia75xbvf6zogcloppdcu2zrwcl6d46ebj2zmm6dkge6eno3k6ysw4
|
|
10
10
|
build/IUniswapV2ERC20.json: bafybeid45gy6x5ah5ub5p2obdhph36skpoy5qdhikwwfso7f3655iqnpc4
|
|
11
|
-
contract.py:
|
|
11
|
+
contract.py: bafybeidxnb72n5xz4fvvxogbk2xk2vz4jvtsqa3berolr34v7cug3snism
|
|
12
|
+
tests/__init__.py: bafybeigj3ngmfmyy4nw4xipxbqnx4nd42aqidqmmduwpx6hurssjqqkirq
|
|
13
|
+
tests/test_contract.py: bafybeigaf7kyhuy4qn5dww2r62hffhytab3uhmjummsaiazzvqetvomyi4
|
|
12
14
|
fingerprint_ignore_patterns: []
|
|
13
15
|
contracts: []
|
|
14
16
|
class_name: UniswapV2ERC20Contract
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ------------------------------------------------------------------------------
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2021-2022 Valory AG
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
# ------------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
"""Tests package for valory/uniswap_v2_erc20 contract."""
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ------------------------------------------------------------------------------
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2021-2022 Valory AG
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
# ------------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
"""Tests for valory/uniswap_v2_erc20 contract."""
|
|
21
|
+
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Dict
|
|
24
|
+
from unittest import mock
|
|
25
|
+
|
|
26
|
+
from aea.test_tools.test_contract import BaseContractTestCase
|
|
27
|
+
|
|
28
|
+
from operate.data.contracts.uniswap_v2_erc20.contract import UniswapV2ERC20Contract
|
|
29
|
+
|
|
30
|
+
PACKAGE_DIR = Path(__file__).parent.parent
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
CONTRACT_ADDRESS = "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2"
|
|
34
|
+
ADDRESS_ONE = "0x46F415F7BF30f4227F98def9d2B22ff62738fD68"
|
|
35
|
+
ADDRESS_TWO = "0x7A1236d5195e31f1F573AD618b2b6FEFC85C5Ce6"
|
|
36
|
+
ADDRESS_THREE = "0x7A1236d5195e31f1F573AD618b2b6FEFC85C5Ce6"
|
|
37
|
+
NONCE = 0
|
|
38
|
+
CHAIN_ID = 1
|
|
39
|
+
MAX_ALLOWANCE = 2**256 - 1
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestUniswapV2ERC20Contract(BaseContractTestCase):
|
|
43
|
+
"""Test TestUniswapV2ERC20Contract."""
|
|
44
|
+
|
|
45
|
+
path_to_contract = PACKAGE_DIR
|
|
46
|
+
ledger_identifier = "ethereum"
|
|
47
|
+
contract: UniswapV2ERC20Contract
|
|
48
|
+
owner_address = ADDRESS_ONE
|
|
49
|
+
sender_address = ADDRESS_TWO
|
|
50
|
+
gas_price = 100
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def finish_contract_deployment(cls) -> str:
|
|
54
|
+
"""Finish the contract deployment."""
|
|
55
|
+
contract_address = CONTRACT_ADDRESS
|
|
56
|
+
return contract_address
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def _deploy_contract(cls, contract, ledger_api, deployer_crypto, gas) -> Dict: # type: ignore
|
|
60
|
+
"""Deploy contract."""
|
|
61
|
+
return {}
|
|
62
|
+
|
|
63
|
+
def test_approve(self) -> None:
|
|
64
|
+
"""Test approve."""
|
|
65
|
+
eth_value = 0
|
|
66
|
+
gas = 100
|
|
67
|
+
spender_address = ADDRESS_THREE
|
|
68
|
+
approval_value = 100
|
|
69
|
+
data = self.contract.get_instance(
|
|
70
|
+
self.ledger_api, self.contract_address
|
|
71
|
+
).encodeABI(fn_name="approve", args=[spender_address, approval_value])
|
|
72
|
+
with mock.patch.object(
|
|
73
|
+
self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
|
|
74
|
+
):
|
|
75
|
+
with mock.patch.object(
|
|
76
|
+
self.ledger_api.api.manager, "request_blocking", return_value=CHAIN_ID
|
|
77
|
+
):
|
|
78
|
+
result = self.contract.approve(
|
|
79
|
+
self.ledger_api,
|
|
80
|
+
self.contract_address,
|
|
81
|
+
spender_address,
|
|
82
|
+
approval_value,
|
|
83
|
+
sender_address=self.sender_address,
|
|
84
|
+
gas=gas,
|
|
85
|
+
gasPrice=self.gas_price,
|
|
86
|
+
)
|
|
87
|
+
assert result == {
|
|
88
|
+
"chainId": CHAIN_ID,
|
|
89
|
+
"data": data,
|
|
90
|
+
"gas": gas,
|
|
91
|
+
"gasPrice": self.gas_price,
|
|
92
|
+
"nonce": NONCE,
|
|
93
|
+
"to": CONTRACT_ADDRESS,
|
|
94
|
+
"value": eth_value,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
def test_transfer(self) -> None:
|
|
98
|
+
"""Test transfer."""
|
|
99
|
+
eth_value = 0
|
|
100
|
+
gas = 100
|
|
101
|
+
spender_address = ADDRESS_THREE
|
|
102
|
+
value = 100
|
|
103
|
+
data = self.contract.get_instance(
|
|
104
|
+
self.ledger_api, self.contract_address
|
|
105
|
+
).encodeABI(fn_name="transfer", args=[spender_address, value])
|
|
106
|
+
with mock.patch.object(
|
|
107
|
+
self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
|
|
108
|
+
):
|
|
109
|
+
with mock.patch.object(
|
|
110
|
+
self.ledger_api.api.manager, "request_blocking", return_value=CHAIN_ID
|
|
111
|
+
):
|
|
112
|
+
result = self.contract.transfer(
|
|
113
|
+
self.ledger_api,
|
|
114
|
+
self.contract_address,
|
|
115
|
+
spender_address,
|
|
116
|
+
value,
|
|
117
|
+
sender_address=self.sender_address,
|
|
118
|
+
gas=gas,
|
|
119
|
+
gasPrice=self.gas_price,
|
|
120
|
+
)
|
|
121
|
+
assert result == {
|
|
122
|
+
"chainId": CHAIN_ID,
|
|
123
|
+
"data": data,
|
|
124
|
+
"gas": gas,
|
|
125
|
+
"gasPrice": self.gas_price,
|
|
126
|
+
"nonce": NONCE,
|
|
127
|
+
"to": CONTRACT_ADDRESS,
|
|
128
|
+
"value": eth_value,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
def test_transfer_from(self) -> None:
|
|
132
|
+
"""Test transfer_from."""
|
|
133
|
+
eth_value = 0
|
|
134
|
+
gas = 100
|
|
135
|
+
from_address = ADDRESS_THREE
|
|
136
|
+
to_address = ADDRESS_ONE
|
|
137
|
+
value = 100
|
|
138
|
+
data = self.contract.get_instance(
|
|
139
|
+
self.ledger_api, self.contract_address
|
|
140
|
+
).encodeABI(fn_name="transferFrom", args=[from_address, to_address, value])
|
|
141
|
+
with mock.patch.object(
|
|
142
|
+
self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
|
|
143
|
+
):
|
|
144
|
+
with mock.patch.object(
|
|
145
|
+
self.ledger_api.api.manager, "request_blocking", return_value=CHAIN_ID
|
|
146
|
+
):
|
|
147
|
+
result = self.contract.transfer_from(
|
|
148
|
+
self.ledger_api,
|
|
149
|
+
self.contract_address,
|
|
150
|
+
from_address,
|
|
151
|
+
to_address,
|
|
152
|
+
value,
|
|
153
|
+
sender_address=self.sender_address,
|
|
154
|
+
gas=gas,
|
|
155
|
+
gasPrice=self.gas_price,
|
|
156
|
+
)
|
|
157
|
+
assert result == {
|
|
158
|
+
"chainId": CHAIN_ID,
|
|
159
|
+
"data": data,
|
|
160
|
+
"gas": gas,
|
|
161
|
+
"gasPrice": self.gas_price,
|
|
162
|
+
"nonce": NONCE,
|
|
163
|
+
"to": CONTRACT_ADDRESS,
|
|
164
|
+
"value": eth_value,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def test_permit(self) -> None:
|
|
168
|
+
"""Test permit."""
|
|
169
|
+
eth_value = 0
|
|
170
|
+
gas = 100
|
|
171
|
+
owner_address = ADDRESS_THREE
|
|
172
|
+
spender_address = CONTRACT_ADDRESS
|
|
173
|
+
value = 100
|
|
174
|
+
deadline = 10
|
|
175
|
+
v = 10
|
|
176
|
+
r = b""
|
|
177
|
+
s = b""
|
|
178
|
+
data = self.contract.get_instance(
|
|
179
|
+
self.ledger_api, self.contract_address
|
|
180
|
+
).encodeABI(
|
|
181
|
+
fn_name="permit",
|
|
182
|
+
args=[owner_address, spender_address, value, deadline, v, r, s],
|
|
183
|
+
)
|
|
184
|
+
with mock.patch.object(
|
|
185
|
+
self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
|
|
186
|
+
):
|
|
187
|
+
with mock.patch.object(
|
|
188
|
+
self.ledger_api.api.manager, "request_blocking", return_value=CHAIN_ID
|
|
189
|
+
):
|
|
190
|
+
result = self.contract.permit(
|
|
191
|
+
self.ledger_api,
|
|
192
|
+
self.contract_address,
|
|
193
|
+
owner_address,
|
|
194
|
+
spender_address,
|
|
195
|
+
value,
|
|
196
|
+
deadline,
|
|
197
|
+
v,
|
|
198
|
+
r,
|
|
199
|
+
s,
|
|
200
|
+
sender_address=self.sender_address,
|
|
201
|
+
gas=gas,
|
|
202
|
+
gasPrice=self.gas_price,
|
|
203
|
+
)
|
|
204
|
+
assert result == {
|
|
205
|
+
"chainId": CHAIN_ID,
|
|
206
|
+
"data": data,
|
|
207
|
+
"gas": gas,
|
|
208
|
+
"gasPrice": self.gas_price,
|
|
209
|
+
"nonce": NONCE,
|
|
210
|
+
"to": CONTRACT_ADDRESS,
|
|
211
|
+
"value": eth_value,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
def test_allowance(self) -> None:
|
|
215
|
+
"""Test allowance."""
|
|
216
|
+
owner_address = ADDRESS_ONE
|
|
217
|
+
spender_address = ADDRESS_THREE
|
|
218
|
+
with mock.patch.object(
|
|
219
|
+
self.ledger_api.api.manager,
|
|
220
|
+
"request_blocking",
|
|
221
|
+
return_value="0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
222
|
+
):
|
|
223
|
+
result = self.contract.allowance(
|
|
224
|
+
self.ledger_api,
|
|
225
|
+
self.contract_address,
|
|
226
|
+
owner_address,
|
|
227
|
+
spender_address,
|
|
228
|
+
)
|
|
229
|
+
assert result == 0
|
|
230
|
+
|
|
231
|
+
def test_balance_of(self) -> None:
|
|
232
|
+
"""Test balance_of."""
|
|
233
|
+
owner_address = ADDRESS_THREE
|
|
234
|
+
with mock.patch.object(
|
|
235
|
+
self.ledger_api.api.manager,
|
|
236
|
+
"request_blocking",
|
|
237
|
+
return_value="0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
238
|
+
):
|
|
239
|
+
result = self.contract.balance_of(
|
|
240
|
+
self.ledger_api, self.contract_address, owner_address
|
|
241
|
+
)
|
|
242
|
+
assert result == 0
|
|
243
|
+
|
|
244
|
+
def test_get_approve_data(self) -> None:
|
|
245
|
+
"""Test get_method_data with approve."""
|
|
246
|
+
|
|
247
|
+
result = self.contract.get_method_data(
|
|
248
|
+
ledger_api=self.ledger_api,
|
|
249
|
+
contract_address=self.contract_address,
|
|
250
|
+
method_name="approve",
|
|
251
|
+
spender=ADDRESS_ONE,
|
|
252
|
+
value=MAX_ALLOWANCE,
|
|
253
|
+
)
|
|
254
|
+
assert result == {
|
|
255
|
+
"data": b"\t^\xa7\xb3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
256
|
+
b"F\xf4\x15\xf7\xbf0\xf4\"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh"
|
|
257
|
+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
|
258
|
+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
def test_get_transfer_data(self) -> None:
|
|
262
|
+
"""Test get_method_data with transfer."""
|
|
263
|
+
|
|
264
|
+
result = self.contract.get_method_data(
|
|
265
|
+
ledger_api=self.ledger_api,
|
|
266
|
+
contract_address=self.contract_address,
|
|
267
|
+
method_name="transfer",
|
|
268
|
+
to=ADDRESS_ONE,
|
|
269
|
+
value=1,
|
|
270
|
+
)
|
|
271
|
+
assert result == {
|
|
272
|
+
"data": b"\xa9\x05\x9c\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
273
|
+
b"F\xf4\x15\xf7\xbf0\xf4\"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh"
|
|
274
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
275
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
def test_get_transfer_from_data(self) -> None:
|
|
279
|
+
"""Test get_method_data with transfer_from."""
|
|
280
|
+
|
|
281
|
+
# "from" is a reserved word, so must be passed as kwargs
|
|
282
|
+
kwargs = {"from": ADDRESS_TWO, "to": ADDRESS_ONE, "value": 1}
|
|
283
|
+
|
|
284
|
+
result = self.contract.get_method_data(
|
|
285
|
+
ledger_api=self.ledger_api,
|
|
286
|
+
contract_address=self.contract_address,
|
|
287
|
+
method_name="transfer_from",
|
|
288
|
+
**kwargs
|
|
289
|
+
)
|
|
290
|
+
assert result == {
|
|
291
|
+
"data": b"#\xb8r\xdd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00z\x126\xd5"
|
|
292
|
+
b"\x19^1\xf1\xf5s\xada\x8b+o\xef\xc8\\\\\xe6\x00\x00\x00\x00"
|
|
293
|
+
b'\x00\x00\x00\x00\x00\x00\x00\x00F\xf4\x15\xf7\xbf0\xf4"'
|
|
294
|
+
b"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
295
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
296
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x01"
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
def test_get_permit_data(self) -> None:
|
|
300
|
+
"""Test get_method_data with permit."""
|
|
301
|
+
|
|
302
|
+
result = self.contract.get_method_data(
|
|
303
|
+
ledger_api=self.ledger_api,
|
|
304
|
+
contract_address=self.contract_address,
|
|
305
|
+
method_name="permit",
|
|
306
|
+
owner=ADDRESS_ONE,
|
|
307
|
+
spender=ADDRESS_TWO,
|
|
308
|
+
value=1,
|
|
309
|
+
deadline=300,
|
|
310
|
+
v=0,
|
|
311
|
+
r=b"0",
|
|
312
|
+
s=b"0",
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
assert result == {
|
|
316
|
+
"data": b"\xd5\x05\xac\xcf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
317
|
+
b"F\xf4\x15\xf7\xbf0\xf4\"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh"
|
|
318
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00z\x126\xd5\x19^1\xf1"
|
|
319
|
+
b"\xf5s\xada\x8b+o\xef\xc8\\\\\xe6\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
320
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
321
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
322
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
323
|
+
b"\x00\x00\x00\x00\x00\x00\x01,\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
324
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
325
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00"
|
|
326
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
327
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00"
|
|
328
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
329
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
def test_get_allowance_data(self) -> None:
|
|
333
|
+
"""Test get_method_data with allowance."""
|
|
334
|
+
|
|
335
|
+
result = self.contract.get_method_data(
|
|
336
|
+
ledger_api=self.ledger_api,
|
|
337
|
+
contract_address=self.contract_address,
|
|
338
|
+
method_name="allowance",
|
|
339
|
+
owner=ADDRESS_ONE,
|
|
340
|
+
spender=ADDRESS_TWO,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
assert result == {
|
|
344
|
+
"data": b"\xddb\xed>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
345
|
+
b"F\xf4\x15\xf7\xbf0\xf4\"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh"
|
|
346
|
+
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00z\x126\xd5\x19^1\xf1"
|
|
347
|
+
b"\xf5s\xada\x8b+o\xef\xc8\\\\\xe6"
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
def test_get_balance_of_data(self) -> None:
|
|
351
|
+
"""Test get_method_data with balance_of."""
|
|
352
|
+
|
|
353
|
+
result = self.contract.get_method_data(
|
|
354
|
+
ledger_api=self.ledger_api,
|
|
355
|
+
contract_address=self.contract_address,
|
|
356
|
+
method_name="balance_of",
|
|
357
|
+
owner=ADDRESS_ONE,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
assert result == {
|
|
361
|
+
"data": b"p\xa0\x821\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
362
|
+
b"F\xf4\x15\xf7\xbf0\xf4\"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh"
|
|
363
|
+
}
|
operate/keys.py
CHANGED
|
@@ -20,17 +20,18 @@
|
|
|
20
20
|
"""Keys manager."""
|
|
21
21
|
|
|
22
22
|
import json
|
|
23
|
-
import logging
|
|
24
23
|
import os
|
|
25
|
-
import
|
|
24
|
+
import tempfile
|
|
26
25
|
from dataclasses import dataclass
|
|
26
|
+
from logging import Logger
|
|
27
27
|
from pathlib import Path
|
|
28
|
+
from typing import Any, Optional
|
|
28
29
|
|
|
29
|
-
from aea.helpers.logging import setup_logger
|
|
30
30
|
from aea_ledger_ethereum.ethereum import EthereumCrypto
|
|
31
31
|
|
|
32
|
+
from operate.operate_types import LedgerType
|
|
32
33
|
from operate.resource import LocalResource
|
|
33
|
-
from operate.
|
|
34
|
+
from operate.utils import unrecoverable_delete
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
@dataclass
|
|
@@ -41,35 +42,66 @@ class Key(LocalResource):
|
|
|
41
42
|
address: str
|
|
42
43
|
private_key: str
|
|
43
44
|
|
|
45
|
+
def get_decrypted_json(self, password: str) -> dict:
|
|
46
|
+
"""Get decrypted key json."""
|
|
47
|
+
return {
|
|
48
|
+
"ledger": self.ledger.value,
|
|
49
|
+
"address": self.address,
|
|
50
|
+
"private_key": "0x"
|
|
51
|
+
+ EthereumCrypto.decrypt(self.private_key, password=password),
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
@classmethod
|
|
45
55
|
def load(cls, path: Path) -> "Key":
|
|
46
56
|
"""Load a service"""
|
|
47
57
|
return super().load(path) # type: ignore
|
|
48
58
|
|
|
49
59
|
|
|
50
|
-
Keys = t.List[Key]
|
|
51
|
-
|
|
52
|
-
|
|
53
60
|
class KeysManager:
|
|
54
61
|
"""Keys manager."""
|
|
55
62
|
|
|
56
|
-
def __init__(
|
|
57
|
-
self,
|
|
58
|
-
path: Path,
|
|
59
|
-
logger: t.Optional[logging.Logger] = None,
|
|
60
|
-
) -> None:
|
|
63
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
61
64
|
"""
|
|
62
65
|
Initialize keys manager
|
|
63
66
|
|
|
64
67
|
:param path: Path to keys storage.
|
|
65
68
|
:param logger: logging.Logger object.
|
|
66
69
|
"""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self.
|
|
70
|
+
if "path" not in kwargs:
|
|
71
|
+
raise ValueError("Path must be provided for KeysManager")
|
|
72
|
+
|
|
73
|
+
self.path: Path = kwargs["path"]
|
|
74
|
+
self.logger: Logger = kwargs["logger"]
|
|
75
|
+
self.password: Optional[str] = kwargs.get("password")
|
|
76
|
+
self.path.mkdir(exist_ok=True, parents=True)
|
|
77
|
+
|
|
78
|
+
def private_key_to_crypto(
|
|
79
|
+
self, private_key: str, password: Optional[str]
|
|
80
|
+
) -> EthereumCrypto:
|
|
81
|
+
"""Convert private key string to EthereumCrypto instance."""
|
|
82
|
+
with tempfile.NamedTemporaryFile(
|
|
83
|
+
dir=self.path,
|
|
84
|
+
mode="w",
|
|
85
|
+
suffix=".txt",
|
|
86
|
+
delete=False, # Handle cleanup manually
|
|
87
|
+
) as temp_file:
|
|
88
|
+
temp_file_name = temp_file.name
|
|
89
|
+
temp_file.write(private_key)
|
|
90
|
+
temp_file.flush()
|
|
91
|
+
temp_file.close() # Close the file before reading
|
|
92
|
+
|
|
93
|
+
# Set proper file permissions (readable by owner only)
|
|
94
|
+
os.chmod(temp_file_name, 0o600)
|
|
95
|
+
crypto = EthereumCrypto(private_key_path=temp_file_name, password=password)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
unrecoverable_delete(
|
|
99
|
+
Path(temp_file.name)
|
|
100
|
+
) # Clean up the temporary file
|
|
101
|
+
except OSError as e:
|
|
102
|
+
self.logger.error(f"Failed to delete temp file {temp_file.name}: {e}")
|
|
103
|
+
|
|
104
|
+
return crypto
|
|
73
105
|
|
|
74
106
|
def get(self, key: str) -> Key:
|
|
75
107
|
"""Get key object."""
|
|
@@ -81,26 +113,79 @@ class KeysManager:
|
|
|
81
113
|
)
|
|
82
114
|
)
|
|
83
115
|
|
|
116
|
+
def get_decrypted(self, key: str) -> dict:
|
|
117
|
+
"""Get key json."""
|
|
118
|
+
if self.password is not None:
|
|
119
|
+
return self.get(key).get_decrypted_json(self.password)
|
|
120
|
+
return self.get(key).json
|
|
121
|
+
|
|
122
|
+
def get_private_key_file(self, address: str) -> Path:
|
|
123
|
+
"""Get the path to the private key file for the given address."""
|
|
124
|
+
path = self.path / f"{address}_private_key"
|
|
125
|
+
|
|
126
|
+
if path.is_file():
|
|
127
|
+
return path
|
|
128
|
+
|
|
129
|
+
key = self.get(address)
|
|
130
|
+
private_key = key.private_key
|
|
131
|
+
path.write_text(private_key, encoding="utf-8")
|
|
132
|
+
os.chmod(path, 0o600)
|
|
133
|
+
return path
|
|
134
|
+
|
|
135
|
+
def get_crypto_instance(self, address: str) -> EthereumCrypto:
|
|
136
|
+
"""Get EthereumCrypto instance for the given address."""
|
|
137
|
+
key: Key = self.get(address)
|
|
138
|
+
return self.private_key_to_crypto(key.private_key, self.password)
|
|
139
|
+
|
|
84
140
|
def create(self) -> str:
|
|
85
141
|
"""Creates new key."""
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
ledger=LedgerType.ETHEREUM,
|
|
95
|
-
address=crypto.address,
|
|
96
|
-
private_key=crypto.private_key,
|
|
97
|
-
).json,
|
|
98
|
-
indent=4,
|
|
99
|
-
),
|
|
100
|
-
encoding="utf-8",
|
|
142
|
+
self.path.mkdir(exist_ok=True, parents=True)
|
|
143
|
+
crypto = EthereumCrypto(password=self.password)
|
|
144
|
+
key = Key(
|
|
145
|
+
ledger=LedgerType.ETHEREUM,
|
|
146
|
+
address=crypto.address,
|
|
147
|
+
private_key=crypto.encrypt(password=self.password)
|
|
148
|
+
if self.password is not None
|
|
149
|
+
else crypto.private_key,
|
|
101
150
|
)
|
|
151
|
+
for path in (
|
|
152
|
+
self.path / f"{crypto.address}.bak",
|
|
153
|
+
self.path / crypto.address,
|
|
154
|
+
):
|
|
155
|
+
if path.is_file():
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
path.write_text(
|
|
159
|
+
json.dumps(
|
|
160
|
+
key.json,
|
|
161
|
+
indent=2,
|
|
162
|
+
),
|
|
163
|
+
encoding="utf-8",
|
|
164
|
+
)
|
|
165
|
+
|
|
102
166
|
return crypto.address
|
|
103
167
|
|
|
104
168
|
def delete(self, key: str) -> None:
|
|
105
169
|
"""Delete key."""
|
|
106
170
|
os.remove(self.path / key)
|
|
171
|
+
|
|
172
|
+
def update_password(self, new_password: str) -> None:
|
|
173
|
+
"""Update password for all keys."""
|
|
174
|
+
for key_file in self.path.iterdir():
|
|
175
|
+
if not key_file.is_file() or key_file.suffix == ".bak":
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
key = self.get(key_file.name)
|
|
179
|
+
crypto = self.get_crypto_instance(key_file.name)
|
|
180
|
+
encrypted_private_key = crypto.encrypt(password=new_password)
|
|
181
|
+
key.private_key = encrypted_private_key
|
|
182
|
+
key.path = self.path / key_file.name
|
|
183
|
+
key.store()
|
|
184
|
+
|
|
185
|
+
backup_path = self.path / f"{key.address}.bak"
|
|
186
|
+
backup_path.write_text(
|
|
187
|
+
json.dumps(key.json, indent=2),
|
|
188
|
+
encoding="utf-8",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
self.password = new_password
|