Skip to content

Commit 38c9a5d

Browse files
authored
Merge pull request #2959 from humanprotocol/develop
Release 20241223
2 parents 1aa5975 + 32bfe10 commit 38c9a5d

File tree

634 files changed

+31954
-18844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

634 files changed

+31954
-18844
lines changed

.github/workflows/cd-deploy-contracts.yaml

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@ on:
99
escrowFactory:
1010
description: 'deploy escrow factory'
1111
required: true
12-
staking:
13-
description: 'deploy staking'
14-
required: true
15-
rewardPool:
16-
description: 'deploy reward pool'
17-
required: true
1812

1913
jobs:
2014
deploy-contracts:
@@ -51,63 +45,43 @@ jobs:
5145
run: |
5246
case ${{ github.event.inputs.network }} in
5347
"sepolia")
54-
echo "escrow_factory=0xD6D347ba6987519B4e42EcED43dF98eFf5465a23" >> $GITHUB_OUTPUT
55-
echo "staking=0x2B9C5EC6220BA8Ad08CB51A60FFdbC6a6235B203" >> $GITHUB_OUTPUT
56-
echo "reward_pool=0xAFf5a986A530ff839d49325A5dF69F96627E8D29" >> $GITHUB_OUTPUT
48+
echo "escrow_factory=0x5987A5558d961ee674efe4A8c8eB7B1b5495D3bf" >> $GITHUB_OUTPUT
5749
echo "private_key=TESTNET_PRIVATE_KEY" >> $GITHUB_OUTPUT
5850
;;
5951
"polygon")
6052
echo "escrow_factory=0xBDBfD2cC708199C5640C6ECdf3B0F4A4C67AdfcB" >> $GITHUB_OUTPUT
61-
echo "staking=0xcbAd56bE3f504E98bd70875823d3CC0242B7bB29" >> $GITHUB_OUTPUT
62-
echo "reward_pool=0x1371057BAec59944B924A7963F2EeCF43ff94CE4" >> $GITHUB_OUTPUT
6353
echo "private_key=MAINNET_PRIVATE_KEY" >> $GITHUB_OUTPUT
6454
;;
6555
"polygonAmoy")
6656
echo "escrow_factory=0xAFf5a986A530ff839d49325A5dF69F96627E8D29" >> $GITHUB_OUTPUT
67-
echo "staking=0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60" >> $GITHUB_OUTPUT
68-
echo "reward_pool=0xd866bCEFf6D0F77E1c3EAE28230AE6C79b03fDa7" >> $GITHUB_OUTPUT
6957
echo "private_key=TESTNET_PRIVATE_KEY" >> $GITHUB_OUTPUT
7058
;;
7159
"bsc")
7260
echo "escrow_factory=0x92FD968AcBd521c232f5fB8c33b342923cC72714" >> $GITHUB_OUTPUT
73-
echo "staking=0xdFbB79dC35a3A53741be54a2C9b587d6BafAbd1C" >> $GITHUB_OUTPUT
74-
echo "reward_pool=0xf376443BCc6d4d4D63eeC086bc4A9E4a83878e0e" >> $GITHUB_OUTPUT
7561
echo "private_key=MAINNET_PRIVATE_KEY" >> $GITHUB_OUTPUT
7662
;;
7763
"bscTestnet")
7864
echo "escrow_factory=0x2bfA592DBDaF434DDcbb893B1916120d181DAD18" >> $GITHUB_OUTPUT
79-
echo "staking=0x5517fE916Fe9F8dB15B0DDc76ebDf0BdDCd4ed18" >> $GITHUB_OUTPUT
80-
echo "reward_pool=0xB0A0500103eCEc431b73F6BAd923F0a2774E6e29" >> $GITHUB_OUTPUT
8165
echo "private_key=TESTNET_PRIVATE_KEY" >> $GITHUB_OUTPUT
8266
;;
8367
"moonbeam")
8468
echo "escrow_factory=0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a" >> $GITHUB_OUTPUT
85-
echo "staking=0x05398211bA2046E296fBc9a9D3EB49e3F15C3123" >> $GITHUB_OUTPUT
86-
echo "reward_pool=0x4A5963Dd6792692e9147EdC7659936b96251917a" >> $GITHUB_OUTPUT
8769
echo "private_key=MAINNET_PRIVATE_KEY" >> $GITHUB_OUTPUT
8870
;;
8971
"moonbaseAlpha")
9072
echo "escrow_factory=0x5e622FF522D81aa426f082bDD95210BC25fCA7Ed" >> $GITHUB_OUTPUT
91-
echo "staking=0xBFC7009F3371F93F3B54DdC8caCd02914a37495c" >> $GITHUB_OUTPUT
92-
echo "reward_pool=0xf46B45Df3d956369726d8Bd93Ba33963Ab692920" >> $GITHUB_OUTPUT
9373
echo "private_key=TESTNET_PRIVATE_KEY" >> $GITHUB_OUTPUT
9474
;;
9575
"mainnet")
9676
echo "escrow_factory=0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a" >> $GITHUB_OUTPUT
97-
echo "staking=0x05398211bA2046E296fBc9a9D3EB49e3F15C3123" >> $GITHUB_OUTPUT
98-
echo "reward_pool=0x4A5963Dd6792692e9147EdC7659936b96251917a" >> $GITHUB_OUTPUT
9977
echo "private_key=MAINNET_PRIVATE_KEY" >> $GITHUB_OUTPUT
10078
;;
10179
"avalanche")
10280
echo "escrow_factory=0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a" >> $GITHUB_OUTPUT
103-
echo "staking=0x05398211bA2046E296fBc9a9D3EB49e3F15C3123" >> $GITHUB_OUTPUT
104-
echo "reward_pool=0x4A5963Dd6792692e9147EdC7659936b96251917a" >> $GITHUB_OUTPUT
10581
echo "private_key=MAINNET_PRIVATE_KEY" >> $GITHUB_OUTPUT
10682
;;
10783
"avalancheFujiTestnet")
10884
echo "escrow_factory=0x56C2ba540726ED4f46E7a134b6b9Ee9C867FcF92" >> $GITHUB_OUTPUT
109-
echo "staking=0x9890473B0b93E24d6D1a8Dfb739D577C6f25FFd3" >> $GITHUB_OUTPUT
110-
echo "reward_pool=0x5517fE916Fe9F8dB15B0DDc76ebDf0BdDCd4ed18" >> $GITHUB_OUTPUT
11185
echo "private_key=TESTNET_PRIVATE_KEY" >> $GITHUB_OUTPUT
11286
;;
11387
*)
@@ -116,30 +90,18 @@ jobs:
11690
;;
11791
esac
11892
- name: Upgrade Proxies
119-
if: github.event.inputs.escrowFactory == 'true' || github.event.inputs.staking == 'true' || github.event.inputs.rewardPool== 'true'
93+
if: github.event.inputs.escrowFactory == 'true'
12094
run: yarn upgrade:proxy --network ${{ github.event.inputs.network }}
12195
working-directory: ./packages/core
12296
env:
12397
PRIVATE_KEY: ${{ secrets[steps.networks.outputs.private_key] }}
12498
DEPLOY_ESCROW_FACTORY: ${{ github.event.inputs.escrowFactory }}
125-
DEPLOY_STAKING: ${{ github.event.inputs.staking }}
126-
DEPLOY_REWARD_POOL: ${{ github.event.inputs.rewardPool }}
12799
ESCROW_FACTORY_ADDRESS: ${{ steps.networks.outputs.escrow_factory }}
128-
STAKING_ADDRESS: ${{ steps.networks.outputs.staking }}
129-
REWARD_POOL_ADDRESS: ${{ steps.networks.outputs.reward_pool }}
130100

131101
- name: Verify Escrow Factory Proxy
132102
if: always() && github.event.inputs.escrowFactory == 'true'
133103
run: npx hardhat verify --network ${{ github.event.inputs.network }} ${{ steps.networks.outputs.escrow_factory }}
134104
working-directory: ./packages/core
135-
- name: Verify Staking Proxy
136-
if: always() && github.event.inputs.staking == 'true'
137-
run: npx hardhat verify --network ${{ github.event.inputs.network }} ${{ steps.networks.outputs.staking }}
138-
working-directory: ./packages/core
139-
- name: Verify Reward Pool Proxy
140-
if: always() && github.event.inputs.rewardPool== 'true'
141-
run: npx hardhat verify --network ${{ github.event.inputs.network }} ${{ steps.networks.outputs.reward_pool }}
142-
working-directory: ./packages/core
143105
#Commit changes to develop
144106
- name: Check for Changes
145107
if: always()

.github/workflows/ci-dependency-review.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ jobs:
1111
- name: "Checkout Repository"
1212
uses: actions/[email protected]
1313
- name: "Dependency Review"
14-
uses: actions/dependency-review-action@v4.4.0
14+
uses: actions/dependency-review-action@v4.5.0

docs/sdk/python/human_protocol_sdk.encryption.encryption.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Decrypts a message using the private key.
6060
* **message** (`str`) – Armored message to decrypt
6161
* **public_key** (`Optional`[`str`]) – Armored public key used for signature verification. Defaults to None.
6262
* **Return type:**
63-
`str`
63+
`bytes`
6464
* **Returns:**
6565
Decrypted message
6666
* **Example:**
@@ -94,7 +94,7 @@ Decrypts a message using the private key.
9494
Signs a message using the private key.
9595

9696
* **Parameters:**
97-
**message** (`str`) – Message to sign
97+
**message** (`Union`[`str`, `bytes`]) – Message to sign
9898
* **Return type:**
9999
`str`
100100
* **Returns:**
@@ -130,7 +130,7 @@ Signs a message using the private key.
130130
Signs and encrypts a message using the private key and recipient’s public keys.
131131

132132
* **Parameters:**
133-
* **message** (`str`) – Message to sign and encrypt
133+
* **message** (`Union`[`str`, `bytes`]) – Message to sign and encrypt
134134
* **public_keys** (`List`[`str`]) – List of armored public keys of the recipients
135135
* **Return type:**
136136
`str`

docs/sdk/python/human_protocol_sdk.escrow.escrow_client.md

Lines changed: 61 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -73,45 +73,6 @@ Initializes a Escrow instance.
7373
* **Parameters:**
7474
**web3** (`Web3`) – The Web3 object
7575

76-
#### abort(escrow_address, tx_options=None)
77-
78-
Cancels the specified escrow,
79-
sends the balance to the canceler and selfdestructs the escrow contract.
80-
81-
* **Parameters:**
82-
* **escrow_address** (`str`) – Address of the escrow to abort
83-
* **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters
84-
* **Return type:**
85-
`None`
86-
* **Returns:**
87-
None
88-
* **Raises:**
89-
[**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an error occurs while checking the parameters
90-
* **Example:**
91-
```python
92-
from eth_typing import URI
93-
from web3 import Web3
94-
from web3.middleware import construct_sign_and_send_raw_middleware
95-
from web3.providers.auto import load_provider_from_uri
96-
97-
from human_protocol_sdk.escrow import EscrowClient
98-
99-
def get_w3_with_priv_key(priv_key: str):
100-
w3 = Web3(load_provider_from_uri(URI("http://localhost:8545")))
101-
gas_payer = w3.eth.account.from_key(priv_key)
102-
w3.eth.default_account = gas_payer.address
103-
w3.middleware_onion.add(
104-
construct_sign_and_send_raw_middleware(gas_payer),
105-
"construct_sign_and_send_raw_middleware",
106-
)
107-
return (w3, gas_payer)
108-
109-
(w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY')
110-
escrow_client = EscrowClient(w3)
111-
112-
escrow_client.abort("0x62dD51230A30401C455c8398d06F85e4EaB6309f")
113-
```
114-
11576
#### add_trusted_handlers(escrow_address, handlers, tx_options=None)
11677

11778
Adds an array of addresses to the trusted handlers list.
@@ -158,7 +119,7 @@ Adds an array of addresses to the trusted handlers list.
158119
)
159120
```
160121

161-
#### bulk_payout(escrow_address, recipients, amounts, final_results_url, final_results_hash, txId, tx_options=None)
122+
#### bulk_payout(escrow_address, recipients, amounts, final_results_url, final_results_hash, txId, force_complete=False, tx_options=None)
162123

163124
Pays out the amounts specified to the workers and sets the URL of the final results file.
164125

@@ -169,6 +130,7 @@ Pays out the amounts specified to the workers and sets the URL of the final resu
169130
* **final_results_url** (`str`) – Final results file url
170131
* **final_results_hash** (`str`) – Final results file hash
171132
* **txId** (`Decimal`) – Serial number of the bulks
133+
* **force_complete** (`Optional`[`bool`]) – (Optional) Indicates if remaining balance should be transferred to the escrow creator
172134
* **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters
173135
* **Return type:**
174136
`None`
@@ -301,67 +263,6 @@ Sets the status of an escrow to completed.
301263
escrow_client.complete("0x62dD51230A30401C455c8398d06F85e4EaB6309f")
302264
```
303265

304-
#### create_and_setup_escrow(token_address, trusted_handlers, job_requester_id, escrow_config)
305-
306-
Creates and sets up an escrow.
307-
308-
* **Parameters:**
309-
* **token_address** (`str`) – Token to use for pay outs
310-
* **trusted_handlers** (`List`[`str`]) – Array of addresses that can perform actions on the contract
311-
* **job_requester_id** (`str`) – The id of the job requester
312-
* **escrow_config** ([`EscrowConfig`](#human_protocol_sdk.escrow.escrow_client.EscrowConfig)) – Object containing all the necessary information to setup an escrow
313-
* **Return type:**
314-
`str`
315-
* **Returns:**
316-
The address of the escrow created
317-
* **Raises:**
318-
[**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an error occurs while checking the parameters
319-
* **Example:**
320-
```python
321-
from eth_typing import URI
322-
from web3 import Web3
323-
from web3.middleware import construct_sign_and_send_raw_middleware
324-
from web3.providers.auto import load_provider_from_uri
325-
326-
from human_protocol_sdk.escrow import EscrowClient
327-
328-
def get_w3_with_priv_key(priv_key: str):
329-
w3 = Web3(load_provider_from_uri(URI("http://localhost:8545")))
330-
gas_payer = w3.eth.account.from_key(priv_key)
331-
w3.eth.default_account = gas_payer.address
332-
w3.middleware_onion.add(
333-
construct_sign_and_send_raw_middleware(gas_payer),
334-
"construct_sign_and_send_raw_middleware",
335-
)
336-
return (w3, gas_payer)
337-
338-
(w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY')
339-
escrow_client = EscrowClient(w3)
340-
341-
token_address = '0x0376D26246Eb35FF4F9924cF13E6C05fd0bD7Fb4'
342-
trusted_handlers = [
343-
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
344-
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
345-
]
346-
job_requester_id = 'job-requester'
347-
escrow_config = EscrowConfig(
348-
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
349-
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
350-
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
351-
10,
352-
10,
353-
10,
354-
"htttp://localhost/manifest.json",
355-
"b5dad76bf6772c0f07fd5e048f6e75a5f86ee079",
356-
)
357-
escrow_address = escrow_client.create_and_setup_escrow(
358-
token_address,
359-
trusted_handlers,
360-
job_requester_id,
361-
escrow_config
362-
)
363-
```
364-
365266
#### create_escrow(token_address, trusted_handlers, job_requester_id, tx_options=None)
366267

367268
Creates an escrow contract that uses the token passed to pay oracle fees and reward workers.
@@ -882,6 +783,52 @@ Stores the results url.
882783
)
883784
```
884785

786+
#### withdraw(escrow_address, token_address, tx_options=None)
787+
788+
Withdraws additional tokens in the escrow to the canceler.
789+
790+
* **Parameters:**
791+
* **escrow_address** (`str`) – Address of the escrow to withdraw
792+
* **token_address** (`str`) – Address of the token to withdraw
793+
* **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters
794+
* **Return type:**
795+
[`EscrowWithdraw`](#human_protocol_sdk.escrow.escrow_client.EscrowWithdraw)
796+
* **Returns:**
797+
EscrowWithdraw:
798+
An instance of the EscrowWithdraw class containing details of the withdrawal transaction,
799+
including the transaction hash and the token address and amount withdrawn.
800+
* **Raises:**
801+
* [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an error occurs while checking the parameters
802+
* [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If the transfer event associated with the withdrawal
803+
is not found in the transaction logs
804+
* **Example:**
805+
```python
806+
from eth_typing import URI
807+
from web3 import Web3
808+
from web3.middleware import construct_sign_and_send_raw_middleware
809+
from web3.providers.auto import load_provider_from_uri
810+
811+
from human_protocol_sdk.escrow import EscrowClient
812+
813+
def get_w3_with_priv_key(priv_key: str):
814+
w3 = Web3(load_provider_from_uri(URI("http://localhost:8545")))
815+
gas_payer = w3.eth.account.from_key(priv_key)
816+
w3.eth.default_account = gas_payer.address
817+
w3.middleware_onion.add(
818+
construct_sign_and_send_raw_middleware(gas_payer),
819+
"construct_sign_and_send_raw_middleware",
820+
)
821+
return (w3, gas_payer)
822+
823+
(w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY')
824+
escrow_client = EscrowClient(w3)
825+
826+
escrow_cancel_data = escrow_client.withdraw(
827+
"0x62dD51230A30401C455c8398d06F85e4EaB6309f",
828+
"0x0376D26246Eb35FF4F9924cF13E6C05fd0bD7Fb4"
829+
)
830+
```
831+
885832
### *exception* human_protocol_sdk.escrow.escrow_client.EscrowClientError
886833

887834
Bases: `Exception`
@@ -905,3 +852,16 @@ Initializes a Escrow instance.
905852
* **reputation_oracle_fee** (`Decimal`) – Fee percentage of the Reputation Oracle
906853
* **manifest_url** (`str`) – Manifest file url
907854
* **hash** (`str`) – Manifest file hash
855+
856+
### *class* human_protocol_sdk.escrow.escrow_client.EscrowWithdraw(tx_hash, token_address, amount_withdrawn)
857+
858+
Bases: `object`
859+
860+
#### \_\_init_\_(tx_hash, token_address, amount_withdrawn)
861+
862+
Represents the result of an escrow cancellation transaction.
863+
864+
* **Parameters:**
865+
* **tx_hash** (`str`) – The hash of the transaction associated with the escrow withdrawal.
866+
* **token_address** (`str`) – The address of the token used for the withdrawal.
867+
* **amount_withdrawn** (`any`) – The amount withdrawn from the escrow.

docs/sdk/python/human_protocol_sdk.escrow.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,10 @@ obtain information from both the contracts and subgraph.
1212
* [`EscrowCancel.__init__()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowCancel.__init__)
1313
* [`EscrowClient`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient)
1414
* [`EscrowClient.__init__()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.__init__)
15-
* [`EscrowClient.abort()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.abort)
1615
* [`EscrowClient.add_trusted_handlers()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.add_trusted_handlers)
1716
* [`EscrowClient.bulk_payout()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.bulk_payout)
1817
* [`EscrowClient.cancel()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.cancel)
1918
* [`EscrowClient.complete()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.complete)
20-
* [`EscrowClient.create_and_setup_escrow()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.create_and_setup_escrow)
2119
* [`EscrowClient.create_escrow()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.create_escrow)
2220
* [`EscrowClient.fund()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.fund)
2321
* [`EscrowClient.get_balance()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.get_balance)
@@ -34,9 +32,12 @@ obtain information from both the contracts and subgraph.
3432
* [`EscrowClient.get_token_address()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.get_token_address)
3533
* [`EscrowClient.setup()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.setup)
3634
* [`EscrowClient.store_results()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.store_results)
35+
* [`EscrowClient.withdraw()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClient.withdraw)
3736
* [`EscrowClientError`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClientError)
3837
* [`EscrowConfig`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowConfig)
3938
* [`EscrowConfig.__init__()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowConfig.__init__)
39+
* [`EscrowWithdraw`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowWithdraw)
40+
* [`EscrowWithdraw.__init__()`](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowWithdraw.__init__)
4041
* [human_protocol_sdk.escrow.escrow_utils module](human_protocol_sdk.escrow.escrow_utils.md)
4142
* [Code Example](human_protocol_sdk.escrow.escrow_utils.md#code-example)
4243
* [Module](human_protocol_sdk.escrow.escrow_utils.md#module)

0 commit comments

Comments
 (0)