Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def __init__(self, web3: Web3):
abi = json.load(abi_file)
self.contract = self.web3.eth.contract(address=contract_address, abi=abi)

def disputeIdToAssertionId(self, disputeId):
return self.contract.functions.disputeIdToAssertionId(disputeId).call()

def maxBonds(self, token):
return self.contract.functions.maxBonds(token).call()

Expand All @@ -30,5 +33,8 @@ def maxLiveness(self):

def minLiveness(self):
return self.contract.functions.minLiveness().call()

def oov3(self):
return self.contract.functions.oov3().call()


3 changes: 3 additions & 0 deletions src/story_protocol_python_sdk/abi/WIP/WIP_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ def withdraw(self, value):
def build_withdraw_transaction(self, value, tx_params):
return self.contract.functions.withdraw(value).build_transaction(tx_params)

def allowance(self, owner, spender):
return self.contract.functions.allowance(owner, spender).call()

def balanceOf(self, owner):
return self.contract.functions.balanceOf(owner).call()

Expand Down
142 changes: 142 additions & 0 deletions src/story_protocol_python_sdk/abi/jsons/ASSERTION_ABI.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
[
{
"inputs": [
{
"internalType": "bytes32",
"name": "assertionId",
"type": "bytes32"
}
],
"name": "getAssertion",
"outputs": [
{
"components": [
{
"components": [
{
"internalType": "bool",
"name": "arbitrateViaEscalationManager",
"type": "bool"
},
{
"internalType": "bool",
"name": "discardOracle",
"type": "bool"
},
{
"internalType": "bool",
"name": "validateDisputers",
"type": "bool"
},
{
"internalType": "address",
"name": "assertingCaller",
"type": "address"
},
{
"internalType": "address",
"name": "escalationManager",
"type": "address"
}
],
"internalType": "struct OptimisticOracleV3Interface.EscalationManagerSettings",
"name": "escalationManagerSettings",
"type": "tuple"
},
{
"internalType": "address",
"name": "asserter",
"type": "address"
},
{
"internalType": "uint64",
"name": "assertionTime",
"type": "uint64"
},
{
"internalType": "bool",
"name": "settled",
"type": "bool"
},
{
"internalType": "contract IERC20",
"name": "currency",
"type": "address"
},
{
"internalType": "uint64",
"name": "expirationTime",
"type": "uint64"
},
{
"internalType": "bool",
"name": "settlementResolution",
"type": "bool"
},
{
"internalType": "bytes32",
"name": "domainId",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "identifier",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "bond",
"type": "uint256"
},
{
"internalType": "address",
"name": "callbackRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "disputer",
"type": "address"
}
],
"internalType": "struct OptimisticOracleV3Interface.Assertion",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "assertionId",
"type": "bytes32"
}
],
"name": "settleAssertion",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "currency",
"type": "address"
}
],
"name": "getMinimumBond",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
143 changes: 142 additions & 1 deletion src/story_protocol_python_sdk/resources/Dispute.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
from story_protocol_python_sdk.resources.WIP import WIP
from story_protocol_python_sdk.abi.DisputeModule.DisputeModule_client import DisputeModuleClient
from story_protocol_python_sdk.abi.ArbitrationPolicyUMA.ArbitrationPolicyUMA_client import ArbitrationPolicyUMAClient
from story_protocol_python_sdk.abi.IPAccountImpl.IPAccountImpl_client import IPAccountImplClient
from story_protocol_python_sdk.abi.WIP.WIP_client import WIPClient

from story_protocol_python_sdk.utils.transaction_utils import build_and_send_transaction
from story_protocol_python_sdk.utils.ipfs import convert_cid_to_hash_ipfs
from story_protocol_python_sdk.utils.constants import ZERO_ADDRESS
from story_protocol_python_sdk.utils.oov3 import get_assertion_bond

class Dispute:
"""
Expand All @@ -25,7 +28,8 @@ def __init__(self, web3: Web3, account, chain_id: int):
self.dispute_module_client = DisputeModuleClient(web3)
self.arbitration_policy_uma_client = ArbitrationPolicyUMAClient(web3)
self.wip = WIP(web3, account, chain_id)

self.wip_client = WIPClient(web3)

def _validate_address(self, address: str) -> str:
"""
Validates if a string is a valid Ethereum address.
Expand Down Expand Up @@ -214,3 +218,140 @@ def _parse_tx_dispute_raised_event(self, tx_receipt: dict) -> int:
dispute_id = int.from_bytes(data[:32], byteorder='big')
return dispute_id
return None

def dispute_assertion(self, assertion_id: str, counter_evidence_cid: str, ip_id: str, tx_options: dict = None) -> dict:
"""
Counters a dispute that was raised by another party on an IP using counter evidence.
The counter evidence (e.g., documents, images) should be uploaded to IPFS,
and its corresponding CID is converted to a hash for the request.

The liveness period is split in two parts:
- the first part of the liveness period in which only the IP's owner can be called the method.
- a second part in which any address can be called the method.

If you only have a disputeId, call dispute_id_to_assertion_id to get the assertionId needed here.

:param assertion_id str: The ID of the assertion to dispute.
:param counter_evidence_cid str: The IPFS CID of the counter evidence.
:param ip_id str: The IP ID related to the dispute.
:param tx_options dict: [Optional] Transaction options.
:return dict: Transaction response containing tx_hash and receipt.
"""
try:
# Validate IP ID
ip_id = self._validate_address(ip_id)

# Create IP Account client
ip_account = IPAccountImplClient(self.web3, contract_address=ip_id)

# Get assertion details to determine bond amount
bond = get_assertion_bond(self.web3, self.arbitration_policy_uma_client, assertion_id)

# Check if user has enough WIP tokens
user_balance = self.wip.balance_of(address=self.account.address)

if user_balance < bond:
raise ValueError(f"Insufficient WIP balance. Required: {bond}, Available: {user_balance}")

# Convert CID to IPFS hash
counter_evidence_hash = convert_cid_to_hash_ipfs(counter_evidence_cid)

# Get encoded data for dispute assertion
encoded_data = self.arbitration_policy_uma_client.contract.encode_abi(
abi_element_identifier="disputeAssertion",
args= [
assertion_id,
counter_evidence_hash
]
)

# Check allowance
allowance = self.wip.allowance(
owner=self.account.address,
spender=ip_account.contract.address
)

# Approve IP Account to transfer WrappedIP tokens if needed
if allowance < bond:
approve_tx = self.wip.approve(
spender=ip_account.contract.address,
amount=2**256 - 1 # maxUint256
)

# Prepare calls for executeBatch
calls = []

if bond > 0:
# Transfer tokens from wallet to IP Account
transfer_data = self.wip_client.contract.encode_abi(
abi_element_identifier="transferFrom",
args=[
self.account.address,
ip_account.contract.address,
bond
]
)
calls.append({
'target': self.wip_client.contract.address,
'value': 0,
'data': transfer_data
})

# Approve arbitration policy to spend tokens
approve_data = self.wip_client.contract.encode_abi(
abi_element_identifier="approve",
args=[
self.arbitration_policy_uma_client.contract.address,
2**256 - 1 # maxUint256
]
)
calls.append({
'target': self.wip_client.contract.address,
'value': 0,
'data': approve_data
})

# Add dispute assertion call
calls.append({
'target': self.arbitration_policy_uma_client.contract.address,
'value': 0,
'data': encoded_data
})

# Execute batch transaction
response = build_and_send_transaction(
self.web3,
self.account,
ip_account.build_executeBatch_transaction,
calls,
0,
tx_options=tx_options
)

return {
'tx_hash': response['tx_hash'],
'receipt': response.get('tx_receipt')
}

except Exception as e:
raise ValueError(f"Failed to dispute assertion: {str(e)}")

def dispute_id_to_assertion_id(self, dispute_id: int) -> str:
"""
Converts a dispute ID to its corresponding assertion ID.

:param dispute_id int: The dispute ID to convert.
:return str: The corresponding assertion ID as a hex string.
:raises ValueError: If there is an error during the conversion.
"""
try:
assertion_id = self.arbitration_policy_uma_client.disputeIdToAssertionId(dispute_id)
return assertion_id
except Exception as e:
raise ValueError(f"Failed to convert dispute ID to assertion ID: {str(e)}")

def get_assertion_bond(self, assertion_id: str) -> int:
"""
Get the bond amount for a given assertion ID.
"""
return get_assertion_bond(self.web3, self.arbitration_policy_uma_client, assertion_id)
23 changes: 23 additions & 0 deletions src/story_protocol_python_sdk/resources/WIP.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,26 @@ def transfer_from(self, from_address: str, to: str, amount: int, tx_options: dic

except Exception as e:
raise ValueError(f"Failed to transfer WIP from another address: {str(e)}")

def allowance(self, owner: str, spender: str) -> int:
"""
Returns the amount of WIP tokens that `spender` is allowed to spend on behalf of `owner`.

:param owner str: The address of the token owner.
:param spender str: The address of the spender.
:return int: The amount of WIP tokens the spender is allowed to spend.
"""
try:
if not self.web3.is_address(owner):
raise ValueError(f"The owner address {owner} is not valid.")

if not self.web3.is_address(spender):
raise ValueError(f"The spender address {spender} is not valid.")

owner = self.web3.to_checksum_address(owner)
spender = self.web3.to_checksum_address(spender)

return self.wip_client.allowance(owner, spender)

except Exception as e:
raise ValueError(f"Failed to get allowance: {str(e)}")
7 changes: 5 additions & 2 deletions src/story_protocol_python_sdk/scripts/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@
"functions": [
"minLiveness",
"maxLiveness",
"maxBonds"
"maxBonds",
"disputeIdToAssertionId",
"oov3"
]
},
{
Expand All @@ -239,7 +241,8 @@
"approve",
"balanceOf",
"transfer",
"transferFrom"
"transferFrom",
"allowance"
]
},
{
Expand Down
Loading