Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 25 additions & 2 deletions packages/examples/cvat/exchange-oracle/src/chain/escrow.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import json
from decimal import Decimal
from functools import partial

import httpx2
from human_protocol_sdk.constants import ChainId, Status
from human_protocol_sdk.encryption import Encryption, EncryptionUtils
from human_protocol_sdk.escrow import EscrowData, EscrowUtils
from human_protocol_sdk.escrow import EscrowClient, EscrowData, EscrowUtils
from human_protocol_sdk.utils import validate_url

from src.chain.web3 import get_token_symbol
from src.chain.web3 import get_token_decimals, get_token_symbol, get_web3
from src.core.config import Config
from src.core.types import OracleWebhookTypes
from src.services.cache import Cache
Expand Down Expand Up @@ -108,3 +109,25 @@ def get_escrow_fund_token_symbol(chain_id: int, escrow_address: str) -> str:
token_address=escrow.token,
set_callback=partial(get_token_symbol, chain_id, escrow.token),
)


def get_escrow_fund_token_decimals(chain_id: int, escrow_address: str) -> int:
"""
ERC-20 decimals: divide the raw token amount by 10**decimals for the user representation
(https://eips.ethereum.org/EIPS/eip-20).
"""

escrow = get_escrow(chain_id, escrow_address)
return get_token_decimals(chain_id, escrow.token)


def get_remaining_escrow_funds(chain_id: int, escrow_address: str) -> Decimal | None:
web3 = get_web3(chain_id)
client = EscrowClient(web3)

remaining_funds = client.get_remaining_funds(escrow_address)
if remaining_funds is None:
return None

token_decimals = get_escrow_fund_token_decimals(chain_id, escrow_address)
return Decimal(remaining_funds) / Decimal(10**token_decimals)
16 changes: 16 additions & 0 deletions packages/examples/cvat/exchange-oracle/src/chain/web3.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
}
] # ABI for fetching token symbol

decimals_abi = [
{
"constant": True,
"inputs": [],
"name": "decimals",
"outputs": [{"name": "", "type": "uint8"}],
"type": "function",
}
] # ABI for fetching token decimals (ERC-20 optional method; https://eips.ethereum.org/EIPS/eip-20)


def get_web3(chain_id: int | Networks):
match chain_id:
Expand Down Expand Up @@ -83,3 +93,9 @@ def get_token_symbol(chain_id: int, token_address: str) -> str:
w3 = get_web3(chain_id)
contract = w3.eth.contract(address=w3.to_checksum_address(token_address), abi=symbol_abi)
return contract.functions.symbol().call()


def get_token_decimals(chain_id: int, token_address: str) -> int:
w3 = get_web3(chain_id)
contract = w3.eth.contract(address=w3.to_checksum_address(token_address), abi=decimals_abi)
return contract.functions.decimals().call()
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from argparse import ArgumentParser
from collections.abc import Generator
from contextlib import ExitStack, contextmanager
from decimal import Decimal
from logging import Logger
from pathlib import Path, PurePosixPath
from typing import Any
Expand Down Expand Up @@ -118,6 +119,9 @@ def patched_get_escrow(chain_id: int, escrow_address: str) -> EscrowData:
mock.patch.object(EscrowUtils, "get_escrow", patched_get_escrow),
mock.patch("src.chain.escrow.get_token_symbol", return_value="HMT"),
mock.patch("src.chain.web3.get_token_symbol", return_value="HMT"),
mock.patch(
"src.handlers.job_creation.utils.get_remaining_escrow_funds", return_value=Decimal(100)
),
):
yield

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from __future__ import annotations

import os
from decimal import Decimal
from typing import TYPE_CHECKING, TypeVar

from datumaro.util.image import IMAGE_EXTENSIONS

import src.cvat.api_calls as cvat_api
from src.chain.escrow import get_escrow
from src.chain.escrow import get_remaining_escrow_funds
from src.core.tasks import TaskTypes
from src.services.cloud import CloudProviders

Expand Down Expand Up @@ -54,12 +53,14 @@ def get_assignment_bounty(
def _get_assignment_bounty_from_escrow(
escrow_address: str, chain_id: int, job_count: int
) -> str | None:
# TODO: refine (fees, already-paid amount, token decimals). Rough split of the escrow's total
# funds across the jobs.
if not job_count:
return None
escrow = get_escrow(chain_id, escrow_address)
return str(Decimal(escrow.total_funded_amount) / job_count)

funds = get_remaining_escrow_funds(chain_id, escrow_address)
if funds is None:
return None

return str(funds / job_count)


def is_image(path: str) -> bool:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import subprocess
from decimal import Decimal
from pathlib import Path
from unittest.mock import Mock, patch

Expand Down Expand Up @@ -143,16 +144,18 @@ def test_create_audio_transcription_task(fxt_audio_transcription_input):
manifest, ds_rois_tsv, gt_tsv = fxt_audio_transcription_input

cvat_api = _make_cvat_api_mock()
escrow = Mock(total_funded_amount="40")
with (
patch.object(handlers, "get_escrow_manifest", return_value=manifest),
patch("src.handlers.job_creation.builders.audio.transcription.cvat_api", cvat_api),
patch("src.handlers.job_creation.utils.get_escrow", return_value=escrow) as mock_get_escrow,
patch(
"src.handlers.job_creation.utils.get_remaining_escrow_funds",
return_value=Decimal(40),
) as mock_remaining_funds,
):
handlers.create_task(ESCROW_ADDRESS, CHAIN_ID)

# v2 manifest → per-assignment bounty derived from escrow funds / job count
mock_get_escrow.assert_called_once_with(CHAIN_ID, ESCROW_ADDRESS)
mock_remaining_funds.assert_called_once_with(CHAIN_ID, ESCROW_ADDRESS)

# Check CVAT API calls
cvat_api.create_project.assert_called_once()
Expand All @@ -172,7 +175,7 @@ def test_create_audio_transcription_task(fxt_audio_transcription_input):
assert project.cvat_cloudstorage_id == 100
assert project.job_type == TaskTypes.audio_transcription.value
assert project.chain_id == CHAIN_ID
assert project.assignment_bounty == "10" # 40 escrow funds / 4 assignments
assert project.assignment_bounty == "10" # 40 tokens remaining / 4 assignments

tasks = db_service.get_tasks_by_cvat_project_id(session, project.cvat_id)
assert len(tasks) == assignment_count
Expand Down