From fa662d8fe82ed4374a8b1121287c92b4482f3613 Mon Sep 17 00:00:00 2001 From: Yao Date: Wed, 6 Aug 2025 17:03:00 -0700 Subject: [PATCH 1/6] feat: add integration tests for WIP's transferFrom and approve methods - Add the 3rd test wallet to be the receiver - Add comprehensive test coverage for WIP token approval functionality - Add integration tests for transferFrom with various scenarios (basic, insufficient allowance, insufficient balance, invalid inputs) - Fix import issue with wallet_address_3 in setup_for_integration.py --- tests/integration/config/test_config.py | 5 + tests/integration/conftest.py | 8 + tests/integration/setup_for_integration.py | 6 + tests/integration/test_integration_wip.py | 577 ++++++++++++++++++++- 4 files changed, 595 insertions(+), 1 deletion(-) diff --git a/tests/integration/config/test_config.py b/tests/integration/config/test_config.py index 8b3248dd..96ad470c 100644 --- a/tests/integration/config/test_config.py +++ b/tests/integration/config/test_config.py @@ -7,9 +7,11 @@ load_dotenv(override=True) private_key = os.getenv("WALLET_PRIVATE_KEY") private_key_2 = os.getenv("WALLET_PRIVATE_KEY_2") +private_key_3 = os.getenv("WALLET_PRIVATE_KEY_3") rpc_url = os.getenv("RPC_PROVIDER_URL") wallet_address = os.getenv("WALLET_ADDRESS") wallet_address_2 = os.getenv("WALLET_ADDRESS_2") +wallet_address_3 = os.getenv("WALLET_ADDRESS_3") if not private_key: raise ValueError("WALLET_PRIVATE_KEY environment variable is not set") @@ -24,6 +26,7 @@ # Set up the account with the private key account = web3.eth.account.from_key(private_key) account_2 = web3.eth.account.from_key(private_key_2) +account_3 = web3.eth.account.from_key(private_key_3) # Export all configuration __all__ = [ @@ -32,6 +35,8 @@ "account_2", "wallet_address", "wallet_address_2", + "wallet_address_3", "private_key", "private_key_2", + "private_key_3", ] diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 5df41ba0..e91d4440 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -14,6 +14,8 @@ from tests.integration.config.test_config import account, account_2, web3 from tests.integration.config.utils import MockERC20, approve, get_story_client from tests.integration.setup_for_integration import PIL_LICENSE_TEMPLATE +from tests.integration.config.test_config import account, account_2, account_3, web3 +from tests.integration.config.utils import get_story_client @pytest.fixture(scope="session") @@ -124,3 +126,9 @@ def mint_and_approve_license_token( ) return license_token_ids + + +@pytest.fixture(scope="session") +def story_client_3() -> StoryClient: + """Fixture to provide the secondary story client""" + return get_story_client(web3, account_3) diff --git a/tests/integration/setup_for_integration.py b/tests/integration/setup_for_integration.py index c25f05e0..cf6f0923 100644 --- a/tests/integration/setup_for_integration.py +++ b/tests/integration/setup_for_integration.py @@ -2,10 +2,13 @@ from tests.integration.config.test_config import ( account, account_2, + account_3, private_key, private_key_2, + private_key_3, wallet_address, wallet_address_2, + wallet_address_3, web3, ) @@ -38,10 +41,13 @@ "web3", "account", "account_2", + "account_3", "wallet_address", "wallet_address_2", + "wallet_address_3", "private_key", "private_key_2", + "private_key_3", "get_story_client", "get_token_id", "mint_tokens", diff --git a/tests/integration/test_integration_wip.py b/tests/integration/test_integration_wip.py index 87ada03c..71fef910 100644 --- a/tests/integration/test_integration_wip.py +++ b/tests/integration/test_integration_wip.py @@ -1,16 +1,35 @@ +import logging +import sys + +import pytest from eth_typing import Hash32 from story_protocol_python_sdk.story_client import StoryClient -from .setup_for_integration import wallet_address, wallet_address_2, web3 +from .setup_for_integration import ( + wallet_address, + wallet_address_2, + wallet_address_3, + web3, +) + +# Configure logging for tests +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + stream=sys.stdout, # Ensure output goes to stdout +) +logger = logging.getLogger(__name__) # Type assertions to ensure wallet addresses are strings assert wallet_address is not None, "wallet_address is required" assert wallet_address_2 is not None, "wallet_address_2 is required" +assert wallet_address_3 is not None, "wallet_address_3 is required" # Type cast to satisfy mypy wallet_address_str: str = wallet_address wallet_address_2_str: str = wallet_address_2 +wallet_address_3_str: str = wallet_address_3 class TestWIPDeposit: @@ -77,6 +96,562 @@ def test_transfer(self, story_client: StoryClient): # and the TypeScript test also skips this test for the same reason +class TestWIPApprove: + """Test WIP approval functionality""" + + def setup_method(self): + """Setup method called before each test""" + logger.info("Setting up WIP approve test") + + def teardown_method(self): + """Teardown method called after each test""" + logger.info("Cleaning up WIP approve test") + + def test_logging_demo(self): + """Demo test to show logging vs print output""" + print("🔵 PRINT: This will show with -s flag") + print("🔵 PRINT: Without -s, this is captured by pytest") + + logger.debug("🐛 DEBUG: This won't show (level too low)") + logger.info("📝 INFO: This should show with proper logging config") + logger.warning("⚠️ WARNING: This should also show") + logger.error("❌ ERROR: This should definitely show") + + # This test always passes + assert True + + def test_approve_basic(self, story_client: StoryClient): + """Test basic approve functionality""" + print("🔵 PRINT: Starting basic approve test") # This will show with -s + logger.info("📝 LOGGER: Testing basic WIP approve functionality") + + approve_amount = web3.to_wei("0.1", "ether") + + try: + # Get initial allowance + initial_allowance = story_client.WIP.allowance( + owner=wallet_address_str, spender=wallet_address_2_str + ) + logger.info( + f"Initial allowance: {web3.from_wei(initial_allowance, 'ether')} ETH" + ) + + # Approve wallet_address_2 to spend WIP + logger.info( + f"Approving {web3.from_wei(approve_amount, 'ether')} ETH for {wallet_address_2_str}" + ) + response = story_client.WIP.approve( + spender=wallet_address_2_str, + amount=approve_amount, + tx_options={"waitForTransaction": True}, + ) + + # Verify transaction hash + assert isinstance( + response, dict + ), f"Expected dict response, got {type(response)}" + assert "tx_hash" in response, f"Response missing tx_hash: {response}" + assert isinstance( + response["tx_hash"], str + ), f"Invalid tx_hash type: {type(response['tx_hash'])}" + + tx_hash = response["tx_hash"] + logger.info(f"Approve transaction submitted: {tx_hash}") + + # Wait for transaction confirmation + tx_receipt = web3.eth.wait_for_transaction_receipt( + Hash32(bytes.fromhex(tx_hash)), timeout=300 + ) + + if tx_receipt["status"] == 0: + logger.error(f"Approve transaction failed: {tx_hash}") + raise AssertionError( + f"Approve transaction failed with status 0: {tx_hash}" + ) + + logger.info( + f"Approve transaction confirmed in block {tx_receipt['blockNumber']}" + ) + + # Verify allowance was set correctly + final_allowance = story_client.WIP.allowance( + owner=wallet_address_str, spender=wallet_address_2_str + ) + logger.info( + f"Final allowance: {web3.from_wei(final_allowance, 'ether')} ETH" + ) + + assert final_allowance == approve_amount, ( + f"Allowance mismatch. Expected: {web3.from_wei(approve_amount, 'ether')} ETH, " + f"Got: {web3.from_wei(final_allowance, 'ether')} ETH" + ) + + logger.info("Basic approve test completed successfully!") + + except Exception as e: + logger.error(f"Basic approve test failed: {str(e)}") + raise + + def test_approve_zero_amount(self, story_client: StoryClient): + """Test approving zero amount (should be rejected by implementation)""" + logger.info("Testing approve with zero amount") + + try: + # First approve some amount + initial_approve = web3.to_wei("0.05", "ether") + story_client.WIP.approve( + spender=wallet_address_2_str, + amount=initial_approve, + tx_options={"waitForTransaction": True}, + ) + + # Verify initial allowance + initial_allowance = story_client.WIP.allowance( + owner=wallet_address_str, spender=wallet_address_2_str + ) + assert initial_allowance == initial_approve + + # Try to approve zero amount - should be rejected + logger.info("Attempting to approve zero amount (should be rejected)") + with pytest.raises( + ValueError, match="WIP approve amount must be greater than 0" + ): + story_client.WIP.approve( + spender=wallet_address_2_str, + amount=0, + tx_options={"waitForTransaction": True}, + ) + + # Verify allowance remains unchanged + final_allowance = story_client.WIP.allowance( + owner=wallet_address_str, spender=wallet_address_2_str + ) + + assert ( + final_allowance == initial_allowance + ), f"Allowance should remain unchanged, got: {final_allowance}" + logger.info("Zero amount approve test completed successfully!") + + except Exception as e: + logger.error(f"Zero amount approve test failed: {str(e)}") + raise + + def test_approve_negative_amount(self, story_client: StoryClient): + """Test approving negative amount (should be rejected)""" + logger.info("Testing approve with negative amount") + + with pytest.raises( + ValueError, match="WIP approve amount must be greater than 0" + ): + story_client.WIP.approve( + spender=wallet_address_2_str, + amount=-1, + tx_options={"waitForTransaction": True}, + ) + + logger.info("Negative amount approve test completed successfully!") + + def test_approve_max_uint256(self, story_client: StoryClient): + """Test approving maximum uint256 value""" + logger.info("Testing approve with maximum uint256 value") + + max_uint256 = 2**256 - 1 + + try: + response = story_client.WIP.approve( + spender=wallet_address_2_str, + amount=max_uint256, + tx_options={"waitForTransaction": True}, + ) + + # Verify transaction + assert isinstance(response["tx_hash"], str) + + # Wait for confirmation + web3.eth.wait_for_transaction_receipt( + Hash32(bytes.fromhex(response["tx_hash"])), timeout=300 + ) + + # Verify allowance is set to max uint256 + final_allowance = story_client.WIP.allowance( + owner=wallet_address_str, spender=wallet_address_2_str + ) + + assert ( + final_allowance == max_uint256 + ), f"Allowance should be max uint256, got: {final_allowance}" + logger.info("Max uint256 approve test completed successfully!") + + except Exception as e: + logger.error(f"Max uint256 approve test failed: {str(e)}") + raise + + @pytest.mark.parametrize( + "invalid_address", + [ + "0x123", # Too short + "invalid_address", # Invalid format + "", # Empty string + ], + ) + def test_approve_invalid_spender( + self, story_client: StoryClient, invalid_address: str + ): + """Test approve with invalid spender addresses""" + logger.info(f"Testing approve with invalid spender: {invalid_address}") + + with pytest.raises(ValueError, match="not valid"): + story_client.WIP.approve( + spender=invalid_address, amount=web3.to_wei("0.01", "ether") + ) + + logger.info(f"Invalid spender test passed for: {invalid_address}") + + def test_approve_zero_address(self, story_client: StoryClient): + """Test approve with zero address (should be rejected)""" + logger.info("Testing approve with zero address") + + zero_address = "0x0000000000000000000000000000000000000000" + + # The current implementation allows zero address, but we can test it + # If the implementation changes to reject zero address, this test will catch it + try: + response = story_client.WIP.approve( + spender=zero_address, + amount=web3.to_wei("0.01", "ether"), + tx_options={"waitForTransaction": True}, + ) + + # If it succeeds, verify the transaction + assert isinstance(response["tx_hash"], str) + logger.info(f"Zero address approve succeeded: {response['tx_hash']}") + + except ValueError as e: + # If it fails, that's also acceptable + logger.info(f"Zero address approve failed as expected: {str(e)}") + + logger.info("Zero address approve test completed") + + +class TestWIPTransferFrom: + """Test WIP transferFrom functionality""" + + def setup_method(self): + """Setup method called before each test""" + logger.info("Setting up WIP transferFrom test") + + def teardown_method(self): + """Teardown method called after each test""" + logger.info("Cleaning up WIP transferFrom test") + + def test_transfer_from_basic( + self, story_client: StoryClient, story_client_2: StoryClient + ): + """Test basic transferFrom functionality""" + logger.info("Testing basic WIP transferFrom functionality") + + transfer_amount = web3.to_wei("0.01", "ether") + + try: + # Ensure wallet_address has enough WIP balance + owner_balance = story_client.WIP.balance_of(wallet_address_str) + if owner_balance < transfer_amount: + logger.info( + f"Insufficient balance ({web3.from_wei(owner_balance, 'ether')} ETH), depositing more WIP" + ) + deposit_amount = ( + transfer_amount - owner_balance + web3.to_wei("0.1", "ether") + ) + story_client.WIP.deposit( + amount=deposit_amount, tx_options={"waitForTransaction": True} + ) + + # Get initial balances + owner_wip_before = story_client.WIP.balance_of(wallet_address_str) + spender_wip_before = story_client.WIP.balance_of(wallet_address_2_str) + receiver_wip_before = story_client.WIP.balance_of(wallet_address_3_str) + + logger.info( + f"Owner WIP balance before: {web3.from_wei(owner_wip_before, 'ether')} ETH" + ) + logger.info( + f"Spender WIP balance before: {web3.from_wei(spender_wip_before, 'ether')} ETH" + ) + logger.info( + f"Receiver WIP balance before: {web3.from_wei(receiver_wip_before, 'ether')} ETH" + ) + + # Approve spender to transfer from owner + logger.info("Approving spender to transfer from owner") + approve_response = story_client.WIP.approve( + spender=wallet_address_2_str, + amount=transfer_amount, + tx_options={"waitForTransaction": True}, + ) + + # Wait for approve transaction + web3.eth.wait_for_transaction_receipt( + Hash32(bytes.fromhex(approve_response["tx_hash"])), timeout=300 + ) + + # Verify allowance + allowance = story_client.WIP.allowance( + owner=wallet_address_str, spender=wallet_address_2_str + ) + assert allowance >= transfer_amount, f"Insufficient allowance: {allowance}" + logger.info(f"Allowance verified: {web3.from_wei(allowance, 'ether')} ETH") + + # Execute transferFrom using spender's account + logger.info("Executing transferFrom transaction") + transfer_response = story_client_2.WIP.transfer_from( + from_address=wallet_address_str, + to=wallet_address_3_str, + amount=transfer_amount, + tx_options={"waitForTransaction": True}, + ) + + # Verify transaction hash + assert isinstance( + transfer_response, dict + ), f"Expected dict response, got {type(transfer_response)}" + assert ( + "tx_hash" in transfer_response + ), f"Response missing tx_hash: {transfer_response}" + assert isinstance( + transfer_response["tx_hash"], str + ), f"Invalid tx_hash type: {type(transfer_response['tx_hash'])}" + + tx_hash = transfer_response["tx_hash"] + logger.info(f"TransferFrom transaction submitted: {tx_hash}") + + # Wait for transaction confirmation + tx_receipt = web3.eth.wait_for_transaction_receipt( + Hash32(bytes.fromhex(tx_hash)), timeout=300 + ) + + if tx_receipt["status"] == 0: + logger.error(f"TransferFrom transaction failed: {tx_hash}") + raise AssertionError( + f"TransferFrom transaction failed with status 0: {tx_hash}" + ) + + logger.info( + f"TransferFrom transaction confirmed in block {tx_receipt['blockNumber']}" + ) + + # Get final balances + owner_wip_after = story_client.WIP.balance_of(wallet_address_str) + spender_wip_after = story_client.WIP.balance_of(wallet_address_2_str) + receiver_wip_after = story_client.WIP.balance_of(wallet_address_3_str) + + logger.info( + f"Owner WIP balance after: {web3.from_wei(owner_wip_after, 'ether')} ETH" + ) + logger.info( + f"Spender WIP balance after: {web3.from_wei(spender_wip_after, 'ether')} ETH" + ) + logger.info( + f"Receiver WIP balance after: {web3.from_wei(receiver_wip_after, 'ether')} ETH" + ) + + # Verify balances + assert ( + owner_wip_after == owner_wip_before - transfer_amount + ), f"Owner balance should decrease by {web3.from_wei(transfer_amount, 'ether')} ETH" + + assert ( + receiver_wip_after == receiver_wip_before + transfer_amount + ), f"Receiver balance should increase by {web3.from_wei(transfer_amount, 'ether')} ETH" + + # Verify spender balance remains the same (they're just the intermediary) + assert ( + spender_wip_after == spender_wip_before + ), "Spender balance should remain unchanged" + + # Verify allowance was reduced + final_allowance = story_client.WIP.allowance( + owner=wallet_address_str, spender=wallet_address_2_str + ) + expected_allowance = allowance - transfer_amount + assert final_allowance == expected_allowance, ( + f"Allowance should be reduced by transfer amount. " + f"Expected: {web3.from_wei(expected_allowance, 'ether')} ETH, " + f"Got: {web3.from_wei(final_allowance, 'ether')} ETH" + ) + + logger.info("Basic transferFrom test completed successfully!") + + except Exception as e: + logger.error(f"Basic transferFrom test failed: {str(e)}") + # Log current balances for debugging + try: + logger.error( + f"Current owner balance: {web3.from_wei(story_client.WIP.balance_of(wallet_address_str), 'ether')} ETH" + ) + logger.error( + f"Current spender balance: {web3.from_wei(story_client_2.WIP.balance_of(wallet_address_2_str), 'ether')} ETH" + ) + logger.error( + f"Current allowance: {web3.from_wei(story_client.WIP.allowance(wallet_address_str, wallet_address_2_str), 'ether')} ETH" + ) + except Exception as logging_error: + logger.warning(f"Failed to log additional debug info: {logging_error}") + raise + + def test_transfer_from_insufficient_allowance( + self, story_client: StoryClient, story_client_2: StoryClient + ): + """Test transferFrom with insufficient allowance""" + logger.info("Testing transferFrom with insufficient allowance") + + transfer_amount = web3.to_wei("0.01", "ether") + + try: + # Ensure wallet_address has enough WIP balance + owner_balance = story_client.WIP.balance_of(wallet_address_str) + if owner_balance < transfer_amount: + logger.info("Depositing WIP to ensure sufficient balance") + story_client.WIP.deposit( + amount=transfer_amount, tx_options={"waitForTransaction": True} + ) + + # Approve less than transfer amount + insufficient_allowance = transfer_amount - web3.to_wei("0.001", "ether") + story_client.WIP.approve( + spender=wallet_address_2_str, + amount=insufficient_allowance, + tx_options={"waitForTransaction": True}, + ) + + # Try to transfer more than allowed + with pytest.raises(Exception): # Should raise an exception + story_client_2.WIP.transfer_from( + from_address=wallet_address_str, + to=wallet_address_2_str, + amount=transfer_amount, + tx_options={"waitForTransaction": True}, + ) + + logger.info("Insufficient allowance test completed successfully!") + + except Exception as e: + logger.error(f"Insufficient allowance test failed: {str(e)}") + raise + + def test_transfer_from_insufficient_balance( + self, story_client: StoryClient, story_client_2: StoryClient + ): + """Test transferFrom with insufficient balance""" + logger.info("Testing transferFrom with insufficient balance") + + transfer_amount = web3.to_wei("1000", "ether") # Large amount + + try: + # Get current balance + current_balance = story_client.WIP.balance_of(wallet_address_str) + logger.info( + f"Current balance: {web3.from_wei(current_balance, 'ether')} ETH" + ) + + # Approve large amount + story_client.WIP.approve( + spender=wallet_address_2_str, + amount=transfer_amount, + tx_options={"waitForTransaction": True}, + ) + + # Try to transfer more than available balance + with pytest.raises(Exception): # Should raise an exception + story_client_2.WIP.transfer_from( + from_address=wallet_address_str, + to=wallet_address_2_str, + amount=transfer_amount, + tx_options={"waitForTransaction": True}, + ) + + logger.info("Insufficient balance test completed successfully!") + + except Exception as e: + logger.error(f"Insufficient balance test failed: {str(e)}") + raise + + def test_transfer_from_negative_amount(self, story_client: StoryClient): + """Test transferFrom with negative amount (should be rejected)""" + logger.info("Testing transferFrom with negative amount") + + with pytest.raises( + ValueError, match="WIP transfer amount must be greater than 0" + ): + story_client.WIP.transfer_from( + from_address=wallet_address_str, + to=wallet_address_2_str, + amount=-1, + tx_options={"waitForTransaction": True}, + ) + + logger.info("Negative amount transferFrom test completed successfully!") + + @pytest.mark.parametrize( + "invalid_address", + [ + "0x123", # Too short + "invalid_address", # Invalid format + "", # Empty string + ], + ) + def test_transfer_from_invalid_addresses( + self, story_client: StoryClient, invalid_address: str + ): + """Test transferFrom with invalid addresses""" + logger.info(f"Testing transferFrom with invalid address: {invalid_address}") + + with pytest.raises(ValueError, match="not valid"): + story_client.WIP.transfer_from( + from_address=invalid_address, + to=wallet_address_2_str, + amount=web3.to_wei("0.01", "ether"), + ) + + with pytest.raises(ValueError, match="not valid"): + story_client.WIP.transfer_from( + from_address=wallet_address_str, + to=invalid_address, + amount=web3.to_wei("0.01", "ether"), + ) + + logger.info(f"Invalid address test passed for: {invalid_address}") + + def test_transfer_from_zero_addresses(self, story_client: StoryClient): + """Test transferFrom with zero addresses""" + logger.info("Testing transferFrom with zero addresses") + + zero_address = "0x0000000000000000000000000000000000000000" + + # Test zero address as from_address + try: + story_client.WIP.transfer_from( + from_address=zero_address, + to=wallet_address_2_str, + amount=web3.to_wei("0.01", "ether"), + ) + logger.info("Zero address as from_address succeeded") + except Exception as e: + logger.info(f"Zero address as from_address failed as expected: {str(e)}") + + # Test zero address as to_address + try: + story_client.WIP.transfer_from( + from_address=wallet_address_str, + to=zero_address, + amount=web3.to_wei("0.01", "ether"), + ) + logger.info("Zero address as to_address succeeded") + except Exception as e: + logger.info(f"Zero address as to_address failed as expected: {str(e)}") + + logger.info("Zero address transferFrom test completed") + + class TestWIPWithdraw: def test_withdraw(self, story_client: StoryClient): """Test withdrawing WIP to IP""" From 916cc2aae9821a7604d2c46b87789222a6b5acde Mon Sep 17 00:00:00 2001 From: Yao Date: Wed, 6 Aug 2025 17:38:11 -0700 Subject: [PATCH 2/6] chore: polish the WIPApprove test cases --- tests/integration/test_integration_wip.py | 82 ++++++++++++++--------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/tests/integration/test_integration_wip.py b/tests/integration/test_integration_wip.py index 71fef910..2ac831ae 100644 --- a/tests/integration/test_integration_wip.py +++ b/tests/integration/test_integration_wip.py @@ -107,23 +107,39 @@ def teardown_method(self): """Teardown method called after each test""" logger.info("Cleaning up WIP approve test") - def test_logging_demo(self): - """Demo test to show logging vs print output""" - print("🔵 PRINT: This will show with -s flag") - print("🔵 PRINT: Without -s, this is captured by pytest") + # Reset allowance to a reasonable value to avoid affecting other tests + try: + from tests.integration.config.test_config import account, web3 + from tests.integration.config.utils import get_story_client + + story_client = get_story_client(web3, account) - logger.debug("🐛 DEBUG: This won't show (level too low)") - logger.info("📝 INFO: This should show with proper logging config") - logger.warning("⚠️ WARNING: This should also show") - logger.error("❌ ERROR: This should definitely show") + # Check current allowance + current_allowance = story_client.WIP.allowance( + owner=wallet_address_str, spender=wallet_address_2_str + ) + target_allowance = web3.to_wei("0.01", "ether") - # This test always passes - assert True + # Only reset if current allowance is not already 0.01 IP + if current_allowance != target_allowance: + story_client.WIP.approve( + spender=wallet_address_2_str, + amount=target_allowance, + tx_options={"waitForTransaction": True}, + ) + logger.info( + f"Reset allowance from {web3.from_wei(current_allowance, 'ether')} to {web3.from_wei(target_allowance, 'ether')} IP" + ) + else: + logger.info( + f"Allowance already at target value: {web3.from_wei(target_allowance, 'ether')} IP" + ) + except Exception as e: + logger.warning(f"Failed to reset allowance in teardown: {e}") def test_approve_basic(self, story_client: StoryClient): """Test basic approve functionality""" - print("🔵 PRINT: Starting basic approve test") # This will show with -s - logger.info("📝 LOGGER: Testing basic WIP approve functionality") + logger.info("Testing basic WIP approve functionality") approve_amount = web3.to_wei("0.1", "ether") @@ -133,12 +149,12 @@ def test_approve_basic(self, story_client: StoryClient): owner=wallet_address_str, spender=wallet_address_2_str ) logger.info( - f"Initial allowance: {web3.from_wei(initial_allowance, 'ether')} ETH" + f"Initial allowance: {web3.from_wei(initial_allowance, 'ether')} IP" ) # Approve wallet_address_2 to spend WIP logger.info( - f"Approving {web3.from_wei(approve_amount, 'ether')} ETH for {wallet_address_2_str}" + f"Approving {web3.from_wei(approve_amount, 'ether')} IP for {wallet_address_2_str}" ) response = story_client.WIP.approve( spender=wallet_address_2_str, @@ -178,12 +194,12 @@ def test_approve_basic(self, story_client: StoryClient): owner=wallet_address_str, spender=wallet_address_2_str ) logger.info( - f"Final allowance: {web3.from_wei(final_allowance, 'ether')} ETH" + f"Final allowance: {web3.from_wei(final_allowance, 'ether')} IP" ) assert final_allowance == approve_amount, ( - f"Allowance mismatch. Expected: {web3.from_wei(approve_amount, 'ether')} ETH, " - f"Got: {web3.from_wei(final_allowance, 'ether')} ETH" + f"Allowance mismatch. Expected: {web3.from_wei(approve_amount, 'ether')} IP, " + f"Got: {web3.from_wei(final_allowance, 'ether')} IP" ) logger.info("Basic approve test completed successfully!") @@ -357,7 +373,7 @@ def test_transfer_from_basic( owner_balance = story_client.WIP.balance_of(wallet_address_str) if owner_balance < transfer_amount: logger.info( - f"Insufficient balance ({web3.from_wei(owner_balance, 'ether')} ETH), depositing more WIP" + f"Insufficient balance ({web3.from_wei(owner_balance, 'ether')} IP), depositing more WIP" ) deposit_amount = ( transfer_amount - owner_balance + web3.to_wei("0.1", "ether") @@ -372,13 +388,13 @@ def test_transfer_from_basic( receiver_wip_before = story_client.WIP.balance_of(wallet_address_3_str) logger.info( - f"Owner WIP balance before: {web3.from_wei(owner_wip_before, 'ether')} ETH" + f"Owner WIP balance before: {web3.from_wei(owner_wip_before, 'ether')} IP" ) logger.info( - f"Spender WIP balance before: {web3.from_wei(spender_wip_before, 'ether')} ETH" + f"Spender WIP balance before: {web3.from_wei(spender_wip_before, 'ether')} IP" ) logger.info( - f"Receiver WIP balance before: {web3.from_wei(receiver_wip_before, 'ether')} ETH" + f"Receiver WIP balance before: {web3.from_wei(receiver_wip_before, 'ether')} IP" ) # Approve spender to transfer from owner @@ -399,7 +415,7 @@ def test_transfer_from_basic( owner=wallet_address_str, spender=wallet_address_2_str ) assert allowance >= transfer_amount, f"Insufficient allowance: {allowance}" - logger.info(f"Allowance verified: {web3.from_wei(allowance, 'ether')} ETH") + logger.info(f"Allowance verified: {web3.from_wei(allowance, 'ether')} IP") # Execute transferFrom using spender's account logger.info("Executing transferFrom transaction") @@ -445,23 +461,23 @@ def test_transfer_from_basic( receiver_wip_after = story_client.WIP.balance_of(wallet_address_3_str) logger.info( - f"Owner WIP balance after: {web3.from_wei(owner_wip_after, 'ether')} ETH" + f"Owner WIP balance after: {web3.from_wei(owner_wip_after, 'ether')} IP" ) logger.info( - f"Spender WIP balance after: {web3.from_wei(spender_wip_after, 'ether')} ETH" + f"Spender WIP balance after: {web3.from_wei(spender_wip_after, 'ether')} IP" ) logger.info( - f"Receiver WIP balance after: {web3.from_wei(receiver_wip_after, 'ether')} ETH" + f"Receiver WIP balance after: {web3.from_wei(receiver_wip_after, 'ether')} IP" ) # Verify balances assert ( owner_wip_after == owner_wip_before - transfer_amount - ), f"Owner balance should decrease by {web3.from_wei(transfer_amount, 'ether')} ETH" + ), f"Owner balance should decrease by {web3.from_wei(transfer_amount, 'ether')} IP" assert ( receiver_wip_after == receiver_wip_before + transfer_amount - ), f"Receiver balance should increase by {web3.from_wei(transfer_amount, 'ether')} ETH" + ), f"Receiver balance should increase by {web3.from_wei(transfer_amount, 'ether')} IP" # Verify spender balance remains the same (they're just the intermediary) assert ( @@ -475,8 +491,8 @@ def test_transfer_from_basic( expected_allowance = allowance - transfer_amount assert final_allowance == expected_allowance, ( f"Allowance should be reduced by transfer amount. " - f"Expected: {web3.from_wei(expected_allowance, 'ether')} ETH, " - f"Got: {web3.from_wei(final_allowance, 'ether')} ETH" + f"Expected: {web3.from_wei(expected_allowance, 'ether')} IP, " + f"Got: {web3.from_wei(final_allowance, 'ether')} IP" ) logger.info("Basic transferFrom test completed successfully!") @@ -486,13 +502,13 @@ def test_transfer_from_basic( # Log current balances for debugging try: logger.error( - f"Current owner balance: {web3.from_wei(story_client.WIP.balance_of(wallet_address_str), 'ether')} ETH" + f"Current owner balance: {web3.from_wei(story_client.WIP.balance_of(wallet_address_str), 'ether')} IP" ) logger.error( - f"Current spender balance: {web3.from_wei(story_client_2.WIP.balance_of(wallet_address_2_str), 'ether')} ETH" + f"Current spender balance: {web3.from_wei(story_client_2.WIP.balance_of(wallet_address_2_str), 'ether')} IP" ) logger.error( - f"Current allowance: {web3.from_wei(story_client.WIP.allowance(wallet_address_str, wallet_address_2_str), 'ether')} ETH" + f"Current allowance: {web3.from_wei(story_client.WIP.allowance(wallet_address_str, wallet_address_2_str), 'ether')} IP" ) except Exception as logging_error: logger.warning(f"Failed to log additional debug info: {logging_error}") @@ -550,7 +566,7 @@ def test_transfer_from_insufficient_balance( # Get current balance current_balance = story_client.WIP.balance_of(wallet_address_str) logger.info( - f"Current balance: {web3.from_wei(current_balance, 'ether')} ETH" + f"Current balance: {web3.from_wei(current_balance, 'ether')} IP" ) # Approve large amount From 5bac040eacf7fffda11874e0a328f90da73760a2 Mon Sep 17 00:00:00 2001 From: Yao Date: Thu, 7 Aug 2025 11:16:49 -0700 Subject: [PATCH 3/6] wip: update WIP module and integration tests chore chore --- .../resources/WIP.py | 22 +- tests/integration/test_integration_wip.py | 293 +++++++----------- 2 files changed, 134 insertions(+), 181 deletions(-) diff --git a/src/story_protocol_python_sdk/resources/WIP.py b/src/story_protocol_python_sdk/resources/WIP.py index 37d7c2f1..8cbdbfaa 100644 --- a/src/story_protocol_python_sdk/resources/WIP.py +++ b/src/story_protocol_python_sdk/resources/WIP.py @@ -55,7 +55,7 @@ def deposit(self, amount: int, tx_options: dict | None = None) -> dict: return {"tx_hash": response["tx_hash"]} except Exception as e: - raise ValueError(f"Failed to deposit IP for WIP: {str(e)}") + raise ValueError(str(e)) def withdraw(self, amount: int, tx_options: dict | None = None) -> dict: """ @@ -80,7 +80,7 @@ def withdraw(self, amount: int, tx_options: dict | None = None) -> dict: return {"tx_hash": response["tx_hash"]} except Exception as e: - raise ValueError(f"Failed to withdraw WIP: {str(e)}") + raise ValueError(str(e)) def approve( self, spender: str, amount: int, tx_options: dict | None = None @@ -102,6 +102,8 @@ def approve( spender = self.web3.to_checksum_address(spender) + # Note: Contract handles self-approval validation with custom error ERC20InvalidSpender + response = build_and_send_transaction( self.web3, self.account, @@ -114,7 +116,7 @@ def approve( return {"tx_hash": response["tx_hash"]} except Exception as e: - raise ValueError(f"Failed to approve WIP: {str(e)}") + raise ValueError(str(e)) def balance_of(self, address: str) -> int: """ @@ -131,7 +133,7 @@ def balance_of(self, address: str) -> int: return self.wip_client.balanceOf(address) except Exception as e: - raise ValueError(f"Failed to get WIP balance: {str(e)}") + raise ValueError(str(e)) def transfer(self, to: str, amount: int, tx_options: dict | None = None) -> dict: """ @@ -151,6 +153,9 @@ def transfer(self, to: str, amount: int, tx_options: dict | None = None) -> dict to = self.web3.to_checksum_address(to) + # Note: Contract handles zero address and contract address validation + # with custom errors ERC20InvalidReceiver + response = build_and_send_transaction( self.web3, self.account, @@ -163,7 +168,7 @@ def transfer(self, to: str, amount: int, tx_options: dict | None = None) -> dict return {"tx_hash": response["tx_hash"]} except Exception as e: - raise ValueError(f"Failed to transfer WIP: {str(e)}") + raise ValueError(str(e)) def transfer_from( self, from_address: str, to: str, amount: int, tx_options: dict | None = None @@ -190,6 +195,9 @@ def transfer_from( from_address = self.web3.to_checksum_address(from_address) to = self.web3.to_checksum_address(to) + # Note: Contract handles zero address and contract address validation + # with custom errors ERC20InvalidReceiver + response = build_and_send_transaction( self.web3, self.account, @@ -203,7 +211,7 @@ def transfer_from( return {"tx_hash": response["tx_hash"]} except Exception as e: - raise ValueError(f"Failed to transfer WIP from another address: {str(e)}") + raise ValueError(str(e)) def allowance(self, owner: str, spender: str) -> int: """ @@ -226,4 +234,4 @@ def allowance(self, owner: str, spender: str) -> int: return self.wip_client.allowance(owner, spender) except Exception as e: - raise ValueError(f"Failed to get allowance: {str(e)}") + raise ValueError(str(e)) diff --git a/tests/integration/test_integration_wip.py b/tests/integration/test_integration_wip.py index 2ac831ae..cbe59d32 100644 --- a/tests/integration/test_integration_wip.py +++ b/tests/integration/test_integration_wip.py @@ -1,5 +1,4 @@ -import logging -import sys +# tests/integration/test_integration_wip.py import pytest from eth_typing import Hash32 @@ -13,14 +12,6 @@ web3, ) -# Configure logging for tests -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - stream=sys.stdout, # Ensure output goes to stdout -) -logger = logging.getLogger(__name__) - # Type assertions to ensure wallet addresses are strings assert wallet_address is not None, "wallet_address is required" assert wallet_address_2 is not None, "wallet_address_2 is required" @@ -65,6 +56,8 @@ def test_deposit(self, story_client: StoryClient): class TestWIPTransfer: + """Test WIP transfer functionality""" + def test_transfer(self, story_client: StoryClient): """Test transferring WIP""" transfer_amount = web3.to_wei("0.01", "ether") @@ -73,6 +66,15 @@ def test_transfer(self, story_client: StoryClient): sender_wip_before = story_client.WIP.balance_of(wallet_address_str) receiver_wip_before = story_client.WIP.balance_of(wallet_address_2_str) + # Ensure sender has enough WIP balance + if sender_wip_before < transfer_amount: + story_client.WIP.deposit( + amount=transfer_amount - sender_wip_before, + tx_options={"waitForTransaction": True}, + ) + + sender_wip_before = story_client.WIP.balance_of(wallet_address_str) + # Transfer WIP to wallet_address_2 response = story_client.WIP.transfer( to=wallet_address_2_str, @@ -95,18 +97,49 @@ def test_transfer(self, story_client: StoryClient): # Note: We're not testing transferFrom here as it requires approval # and the TypeScript test also skips this test for the same reason + def test_transfer_zero_address(self, story_client: StoryClient): + """Test transfer with zero address (should be rejected by contract with custom error)""" + zero_address = "0x0000000000000000000000000000000000000000" + + # The contract should reject this with a custom error ERC20InvalidReceiver + with pytest.raises(Exception) as exc_info: + story_client.WIP.transfer( + to=zero_address, + amount=web3.to_wei("0.01", "ether"), + tx_options={"waitForTransaction": True}, + ) + + # Verify the transaction failed due to contract validation + # The SDK should provide the direct contract error message + assert "0xec442f05" in str(exc_info.value) + + def test_transfer_to_contract_address(self, story_client: StoryClient): + """Test transfer to WIP contract address (should be rejected by contract with custom error)""" + # Get the WIP contract address + contract_address = story_client.WIP.wip_client.contract.address + + # The contract should reject this with a custom error ERC20InvalidReceiver + with pytest.raises(Exception) as exc_info: + story_client.WIP.transfer( + to=contract_address, + amount=web3.to_wei("0.01", "ether"), + tx_options={"waitForTransaction": True}, + ) + + # Verify the transaction failed due to contract validation + # The SDK should provide the direct contract error message + assert "0xec442f05" in str(exc_info.value) + class TestWIPApprove: """Test WIP approval functionality""" def setup_method(self): """Setup method called before each test""" - logger.info("Setting up WIP approve test") + pass def teardown_method(self): """Teardown method called after each test""" - logger.info("Cleaning up WIP approve test") - # Reset allowance to a reasonable value to avoid affecting other tests try: from tests.integration.config.test_config import account, web3 @@ -127,35 +160,22 @@ def teardown_method(self): amount=target_allowance, tx_options={"waitForTransaction": True}, ) - logger.info( - f"Reset allowance from {web3.from_wei(current_allowance, 'ether')} to {web3.from_wei(target_allowance, 'ether')} IP" - ) else: - logger.info( - f"Allowance already at target value: {web3.from_wei(target_allowance, 'ether')} IP" - ) - except Exception as e: - logger.warning(f"Failed to reset allowance in teardown: {e}") + pass + except Exception: + raise def test_approve_basic(self, story_client: StoryClient): """Test basic approve functionality""" - logger.info("Testing basic WIP approve functionality") - approve_amount = web3.to_wei("0.1", "ether") try: # Get initial allowance - initial_allowance = story_client.WIP.allowance( + story_client.WIP.allowance( owner=wallet_address_str, spender=wallet_address_2_str ) - logger.info( - f"Initial allowance: {web3.from_wei(initial_allowance, 'ether')} IP" - ) # Approve wallet_address_2 to spend WIP - logger.info( - f"Approving {web3.from_wei(approve_amount, 'ether')} IP for {wallet_address_2_str}" - ) response = story_client.WIP.approve( spender=wallet_address_2_str, amount=approve_amount, @@ -172,7 +192,6 @@ def test_approve_basic(self, story_client: StoryClient): ), f"Invalid tx_hash type: {type(response['tx_hash'])}" tx_hash = response["tx_hash"] - logger.info(f"Approve transaction submitted: {tx_hash}") # Wait for transaction confirmation tx_receipt = web3.eth.wait_for_transaction_receipt( @@ -180,38 +199,25 @@ def test_approve_basic(self, story_client: StoryClient): ) if tx_receipt["status"] == 0: - logger.error(f"Approve transaction failed: {tx_hash}") raise AssertionError( f"Approve transaction failed with status 0: {tx_hash}" ) - logger.info( - f"Approve transaction confirmed in block {tx_receipt['blockNumber']}" - ) - # Verify allowance was set correctly final_allowance = story_client.WIP.allowance( owner=wallet_address_str, spender=wallet_address_2_str ) - logger.info( - f"Final allowance: {web3.from_wei(final_allowance, 'ether')} IP" - ) assert final_allowance == approve_amount, ( f"Allowance mismatch. Expected: {web3.from_wei(approve_amount, 'ether')} IP, " f"Got: {web3.from_wei(final_allowance, 'ether')} IP" ) - logger.info("Basic approve test completed successfully!") - - except Exception as e: - logger.error(f"Basic approve test failed: {str(e)}") + except Exception: raise def test_approve_zero_amount(self, story_client: StoryClient): """Test approving zero amount (should be rejected by implementation)""" - logger.info("Testing approve with zero amount") - try: # First approve some amount initial_approve = web3.to_wei("0.05", "ether") @@ -228,7 +234,6 @@ def test_approve_zero_amount(self, story_client: StoryClient): assert initial_allowance == initial_approve # Try to approve zero amount - should be rejected - logger.info("Attempting to approve zero amount (should be rejected)") with pytest.raises( ValueError, match="WIP approve amount must be greater than 0" ): @@ -246,16 +251,12 @@ def test_approve_zero_amount(self, story_client: StoryClient): assert ( final_allowance == initial_allowance ), f"Allowance should remain unchanged, got: {final_allowance}" - logger.info("Zero amount approve test completed successfully!") - except Exception as e: - logger.error(f"Zero amount approve test failed: {str(e)}") + except Exception: raise def test_approve_negative_amount(self, story_client: StoryClient): """Test approving negative amount (should be rejected)""" - logger.info("Testing approve with negative amount") - with pytest.raises( ValueError, match="WIP approve amount must be greater than 0" ): @@ -265,12 +266,8 @@ def test_approve_negative_amount(self, story_client: StoryClient): tx_options={"waitForTransaction": True}, ) - logger.info("Negative amount approve test completed successfully!") - def test_approve_max_uint256(self, story_client: StoryClient): """Test approving maximum uint256 value""" - logger.info("Testing approve with maximum uint256 value") - max_uint256 = 2**256 - 1 try: @@ -296,10 +293,8 @@ def test_approve_max_uint256(self, story_client: StoryClient): assert ( final_allowance == max_uint256 ), f"Allowance should be max uint256, got: {final_allowance}" - logger.info("Max uint256 approve test completed successfully!") - except Exception as e: - logger.error(f"Max uint256 approve test failed: {str(e)}") + except Exception: raise @pytest.mark.parametrize( @@ -314,39 +309,43 @@ def test_approve_invalid_spender( self, story_client: StoryClient, invalid_address: str ): """Test approve with invalid spender addresses""" - logger.info(f"Testing approve with invalid spender: {invalid_address}") - with pytest.raises(ValueError, match="not valid"): story_client.WIP.approve( spender=invalid_address, amount=web3.to_wei("0.01", "ether") ) - logger.info(f"Invalid spender test passed for: {invalid_address}") - def test_approve_zero_address(self, story_client: StoryClient): - """Test approve with zero address (should be rejected)""" - logger.info("Testing approve with zero address") - + """Test approve with zero address (current implementation accepts it)""" zero_address = "0x0000000000000000000000000000000000000000" - # The current implementation allows zero address, but we can test it - # If the implementation changes to reject zero address, this test will catch it - try: - response = story_client.WIP.approve( - spender=zero_address, + # The current implementation accepts zero address, so we test that it works + response = story_client.WIP.approve( + spender=zero_address, + amount=web3.to_wei("0.01", "ether"), + tx_options={"waitForTransaction": True}, + ) + + # Verify the transaction succeeded + assert isinstance(response["tx_hash"], str) + + # Verify the allowance was set + allowance = story_client.WIP.allowance( + owner=wallet_address_str, spender=zero_address + ) + assert allowance == web3.to_wei("0.01", "ether") + + def test_approve_self(self, story_client: StoryClient): + """Test approve with self as spender (should be rejected by contract with custom error)""" + with pytest.raises(Exception) as exc_info: + story_client.WIP.approve( + spender=wallet_address_str, amount=web3.to_wei("0.01", "ether"), tx_options={"waitForTransaction": True}, ) - # If it succeeds, verify the transaction - assert isinstance(response["tx_hash"], str) - logger.info(f"Zero address approve succeeded: {response['tx_hash']}") - - except ValueError as e: - # If it fails, that's also acceptable - logger.info(f"Zero address approve failed as expected: {str(e)}") - - logger.info("Zero address approve test completed") + # Verify the transaction failed due to contract validation + # The SDK should provide the direct contract error message + assert "0x94280d62" in str(exc_info.value) class TestWIPTransferFrom: @@ -354,27 +353,22 @@ class TestWIPTransferFrom: def setup_method(self): """Setup method called before each test""" - logger.info("Setting up WIP transferFrom test") + pass def teardown_method(self): """Teardown method called after each test""" - logger.info("Cleaning up WIP transferFrom test") + pass def test_transfer_from_basic( self, story_client: StoryClient, story_client_2: StoryClient ): """Test basic transferFrom functionality""" - logger.info("Testing basic WIP transferFrom functionality") - transfer_amount = web3.to_wei("0.01", "ether") try: # Ensure wallet_address has enough WIP balance owner_balance = story_client.WIP.balance_of(wallet_address_str) if owner_balance < transfer_amount: - logger.info( - f"Insufficient balance ({web3.from_wei(owner_balance, 'ether')} IP), depositing more WIP" - ) deposit_amount = ( transfer_amount - owner_balance + web3.to_wei("0.1", "ether") ) @@ -387,18 +381,7 @@ def test_transfer_from_basic( spender_wip_before = story_client.WIP.balance_of(wallet_address_2_str) receiver_wip_before = story_client.WIP.balance_of(wallet_address_3_str) - logger.info( - f"Owner WIP balance before: {web3.from_wei(owner_wip_before, 'ether')} IP" - ) - logger.info( - f"Spender WIP balance before: {web3.from_wei(spender_wip_before, 'ether')} IP" - ) - logger.info( - f"Receiver WIP balance before: {web3.from_wei(receiver_wip_before, 'ether')} IP" - ) - # Approve spender to transfer from owner - logger.info("Approving spender to transfer from owner") approve_response = story_client.WIP.approve( spender=wallet_address_2_str, amount=transfer_amount, @@ -415,10 +398,8 @@ def test_transfer_from_basic( owner=wallet_address_str, spender=wallet_address_2_str ) assert allowance >= transfer_amount, f"Insufficient allowance: {allowance}" - logger.info(f"Allowance verified: {web3.from_wei(allowance, 'ether')} IP") # Execute transferFrom using spender's account - logger.info("Executing transferFrom transaction") transfer_response = story_client_2.WIP.transfer_from( from_address=wallet_address_str, to=wallet_address_3_str, @@ -438,7 +419,6 @@ def test_transfer_from_basic( ), f"Invalid tx_hash type: {type(transfer_response['tx_hash'])}" tx_hash = transfer_response["tx_hash"] - logger.info(f"TransferFrom transaction submitted: {tx_hash}") # Wait for transaction confirmation tx_receipt = web3.eth.wait_for_transaction_receipt( @@ -446,30 +426,15 @@ def test_transfer_from_basic( ) if tx_receipt["status"] == 0: - logger.error(f"TransferFrom transaction failed: {tx_hash}") raise AssertionError( f"TransferFrom transaction failed with status 0: {tx_hash}" ) - logger.info( - f"TransferFrom transaction confirmed in block {tx_receipt['blockNumber']}" - ) - # Get final balances owner_wip_after = story_client.WIP.balance_of(wallet_address_str) spender_wip_after = story_client.WIP.balance_of(wallet_address_2_str) receiver_wip_after = story_client.WIP.balance_of(wallet_address_3_str) - logger.info( - f"Owner WIP balance after: {web3.from_wei(owner_wip_after, 'ether')} IP" - ) - logger.info( - f"Spender WIP balance after: {web3.from_wei(spender_wip_after, 'ether')} IP" - ) - logger.info( - f"Receiver WIP balance after: {web3.from_wei(receiver_wip_after, 'ether')} IP" - ) - # Verify balances assert ( owner_wip_after == owner_wip_before - transfer_amount @@ -495,38 +460,19 @@ def test_transfer_from_basic( f"Got: {web3.from_wei(final_allowance, 'ether')} IP" ) - logger.info("Basic transferFrom test completed successfully!") - - except Exception as e: - logger.error(f"Basic transferFrom test failed: {str(e)}") - # Log current balances for debugging - try: - logger.error( - f"Current owner balance: {web3.from_wei(story_client.WIP.balance_of(wallet_address_str), 'ether')} IP" - ) - logger.error( - f"Current spender balance: {web3.from_wei(story_client_2.WIP.balance_of(wallet_address_2_str), 'ether')} IP" - ) - logger.error( - f"Current allowance: {web3.from_wei(story_client.WIP.allowance(wallet_address_str, wallet_address_2_str), 'ether')} IP" - ) - except Exception as logging_error: - logger.warning(f"Failed to log additional debug info: {logging_error}") + except Exception: raise def test_transfer_from_insufficient_allowance( self, story_client: StoryClient, story_client_2: StoryClient ): """Test transferFrom with insufficient allowance""" - logger.info("Testing transferFrom with insufficient allowance") - transfer_amount = web3.to_wei("0.01", "ether") try: # Ensure wallet_address has enough WIP balance owner_balance = story_client.WIP.balance_of(wallet_address_str) if owner_balance < transfer_amount: - logger.info("Depositing WIP to ensure sufficient balance") story_client.WIP.deposit( amount=transfer_amount, tx_options={"waitForTransaction": True} ) @@ -548,26 +494,18 @@ def test_transfer_from_insufficient_allowance( tx_options={"waitForTransaction": True}, ) - logger.info("Insufficient allowance test completed successfully!") - - except Exception as e: - logger.error(f"Insufficient allowance test failed: {str(e)}") + except Exception: raise def test_transfer_from_insufficient_balance( self, story_client: StoryClient, story_client_2: StoryClient ): """Test transferFrom with insufficient balance""" - logger.info("Testing transferFrom with insufficient balance") - transfer_amount = web3.to_wei("1000", "ether") # Large amount try: # Get current balance - current_balance = story_client.WIP.balance_of(wallet_address_str) - logger.info( - f"Current balance: {web3.from_wei(current_balance, 'ether')} IP" - ) + story_client.WIP.balance_of(wallet_address_str) # Approve large amount story_client.WIP.approve( @@ -585,16 +523,11 @@ def test_transfer_from_insufficient_balance( tx_options={"waitForTransaction": True}, ) - logger.info("Insufficient balance test completed successfully!") - - except Exception as e: - logger.error(f"Insufficient balance test failed: {str(e)}") + except Exception: raise def test_transfer_from_negative_amount(self, story_client: StoryClient): """Test transferFrom with negative amount (should be rejected)""" - logger.info("Testing transferFrom with negative amount") - with pytest.raises( ValueError, match="WIP transfer amount must be greater than 0" ): @@ -605,8 +538,6 @@ def test_transfer_from_negative_amount(self, story_client: StoryClient): tx_options={"waitForTransaction": True}, ) - logger.info("Negative amount transferFrom test completed successfully!") - @pytest.mark.parametrize( "invalid_address", [ @@ -619,8 +550,6 @@ def test_transfer_from_invalid_addresses( self, story_client: StoryClient, invalid_address: str ): """Test transferFrom with invalid addresses""" - logger.info(f"Testing transferFrom with invalid address: {invalid_address}") - with pytest.raises(ValueError, match="not valid"): story_client.WIP.transfer_from( from_address=invalid_address, @@ -635,37 +564,53 @@ def test_transfer_from_invalid_addresses( amount=web3.to_wei("0.01", "ether"), ) - logger.info(f"Invalid address test passed for: {invalid_address}") - def test_transfer_from_zero_addresses(self, story_client: StoryClient): - """Test transferFrom with zero addresses""" - logger.info("Testing transferFrom with zero addresses") - + """Test transferFrom with zero addresses (should be rejected by contract with custom error)""" zero_address = "0x0000000000000000000000000000000000000000" - # Test zero address as from_address - try: + # Test zero address as to_address - should be rejected by contract validation + # Note: We need to set up approval first for a valid from_address + story_client.WIP.approve( + spender=wallet_address_2_str, + amount=web3.to_wei("0.1", "ether"), + tx_options={"waitForTransaction": True}, + ) + + with pytest.raises(Exception) as exc_info: story_client.WIP.transfer_from( - from_address=zero_address, - to=wallet_address_2_str, + from_address=wallet_address_str, + to=zero_address, amount=web3.to_wei("0.01", "ether"), + tx_options={"waitForTransaction": True}, ) - logger.info("Zero address as from_address succeeded") - except Exception as e: - logger.info(f"Zero address as from_address failed as expected: {str(e)}") - # Test zero address as to_address - try: + # Verify the transaction failed due to contract validation + # The SDK should provide the direct contract error message + assert "0xec442f05" in str(exc_info.value) + + def test_transfer_from_to_contract_address(self, story_client: StoryClient): + """Test transferFrom to WIP contract address (should be rejected by contract with custom error)""" + # Get the WIP contract address + contract_address = story_client.WIP.wip_client.contract.address + + # Set up approval first + story_client.WIP.approve( + spender=wallet_address_2_str, + amount=web3.to_wei("0.1", "ether"), + tx_options={"waitForTransaction": True}, + ) + + with pytest.raises(Exception) as exc_info: story_client.WIP.transfer_from( from_address=wallet_address_str, - to=zero_address, + to=contract_address, amount=web3.to_wei("0.01", "ether"), + tx_options={"waitForTransaction": True}, ) - logger.info("Zero address as to_address succeeded") - except Exception as e: - logger.info(f"Zero address as to_address failed as expected: {str(e)}") - logger.info("Zero address transferFrom test completed") + # Verify the transaction failed due to contract validation + # The SDK should provide the direct contract error message + assert "0xec442f05" in str(exc_info.value) class TestWIPWithdraw: From 24ed6f632014c1519dafdea06b395a4ead8d73cd Mon Sep 17 00:00:00 2001 From: Yao Date: Fri, 5 Sep 2025 17:28:13 +0800 Subject: [PATCH 4/6] chore --- .../resources/WIP.py | 14 +- tests/integration/config/test_config.py | 5 - tests/integration/conftest.py | 8 - tests/integration/setup_for_integration.py | 6 - tests/integration/test_integration_wip.py | 531 ++---------------- 5 files changed, 45 insertions(+), 519 deletions(-) diff --git a/src/story_protocol_python_sdk/resources/WIP.py b/src/story_protocol_python_sdk/resources/WIP.py index 8cbdbfaa..cec06866 100644 --- a/src/story_protocol_python_sdk/resources/WIP.py +++ b/src/story_protocol_python_sdk/resources/WIP.py @@ -55,7 +55,7 @@ def deposit(self, amount: int, tx_options: dict | None = None) -> dict: return {"tx_hash": response["tx_hash"]} except Exception as e: - raise ValueError(str(e)) + raise ValueError(f"Failed to deposit IP for WIP: {str(e)}") def withdraw(self, amount: int, tx_options: dict | None = None) -> dict: """ @@ -80,7 +80,7 @@ def withdraw(self, amount: int, tx_options: dict | None = None) -> dict: return {"tx_hash": response["tx_hash"]} except Exception as e: - raise ValueError(str(e)) + raise ValueError(f"Failed to withdraw WIP: {str(e)}") def approve( self, spender: str, amount: int, tx_options: dict | None = None @@ -116,7 +116,7 @@ def approve( return {"tx_hash": response["tx_hash"]} except Exception as e: - raise ValueError(str(e)) + raise ValueError(f"Failed to approve WIP: {str(e)}") def balance_of(self, address: str) -> int: """ @@ -133,7 +133,7 @@ def balance_of(self, address: str) -> int: return self.wip_client.balanceOf(address) except Exception as e: - raise ValueError(str(e)) + raise ValueError(f"Failed to get WIP balance: {str(e)}") def transfer(self, to: str, amount: int, tx_options: dict | None = None) -> dict: """ @@ -168,7 +168,7 @@ def transfer(self, to: str, amount: int, tx_options: dict | None = None) -> dict return {"tx_hash": response["tx_hash"]} except Exception as e: - raise ValueError(str(e)) + raise ValueError(f"Failed to transfer WIP: {str(e)}") def transfer_from( self, from_address: str, to: str, amount: int, tx_options: dict | None = None @@ -211,7 +211,7 @@ def transfer_from( return {"tx_hash": response["tx_hash"]} except Exception as e: - raise ValueError(str(e)) + raise ValueError(f"Failed to transfer WIP from another address: {str(e)}") def allowance(self, owner: str, spender: str) -> int: """ @@ -234,4 +234,4 @@ def allowance(self, owner: str, spender: str) -> int: return self.wip_client.allowance(owner, spender) except Exception as e: - raise ValueError(str(e)) + raise ValueError(f"Failed to get allowance: {str(e)}") diff --git a/tests/integration/config/test_config.py b/tests/integration/config/test_config.py index 96ad470c..8b3248dd 100644 --- a/tests/integration/config/test_config.py +++ b/tests/integration/config/test_config.py @@ -7,11 +7,9 @@ load_dotenv(override=True) private_key = os.getenv("WALLET_PRIVATE_KEY") private_key_2 = os.getenv("WALLET_PRIVATE_KEY_2") -private_key_3 = os.getenv("WALLET_PRIVATE_KEY_3") rpc_url = os.getenv("RPC_PROVIDER_URL") wallet_address = os.getenv("WALLET_ADDRESS") wallet_address_2 = os.getenv("WALLET_ADDRESS_2") -wallet_address_3 = os.getenv("WALLET_ADDRESS_3") if not private_key: raise ValueError("WALLET_PRIVATE_KEY environment variable is not set") @@ -26,7 +24,6 @@ # Set up the account with the private key account = web3.eth.account.from_key(private_key) account_2 = web3.eth.account.from_key(private_key_2) -account_3 = web3.eth.account.from_key(private_key_3) # Export all configuration __all__ = [ @@ -35,8 +32,6 @@ "account_2", "wallet_address", "wallet_address_2", - "wallet_address_3", "private_key", "private_key_2", - "private_key_3", ] diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index e91d4440..5df41ba0 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -14,8 +14,6 @@ from tests.integration.config.test_config import account, account_2, web3 from tests.integration.config.utils import MockERC20, approve, get_story_client from tests.integration.setup_for_integration import PIL_LICENSE_TEMPLATE -from tests.integration.config.test_config import account, account_2, account_3, web3 -from tests.integration.config.utils import get_story_client @pytest.fixture(scope="session") @@ -126,9 +124,3 @@ def mint_and_approve_license_token( ) return license_token_ids - - -@pytest.fixture(scope="session") -def story_client_3() -> StoryClient: - """Fixture to provide the secondary story client""" - return get_story_client(web3, account_3) diff --git a/tests/integration/setup_for_integration.py b/tests/integration/setup_for_integration.py index cf6f0923..c25f05e0 100644 --- a/tests/integration/setup_for_integration.py +++ b/tests/integration/setup_for_integration.py @@ -2,13 +2,10 @@ from tests.integration.config.test_config import ( account, account_2, - account_3, private_key, private_key_2, - private_key_3, wallet_address, wallet_address_2, - wallet_address_3, web3, ) @@ -41,13 +38,10 @@ "web3", "account", "account_2", - "account_3", "wallet_address", "wallet_address_2", - "wallet_address_3", "private_key", "private_key_2", - "private_key_3", "get_story_client", "get_token_id", "mint_tokens", diff --git a/tests/integration/test_integration_wip.py b/tests/integration/test_integration_wip.py index cbe59d32..db08cc2f 100644 --- a/tests/integration/test_integration_wip.py +++ b/tests/integration/test_integration_wip.py @@ -1,26 +1,18 @@ # tests/integration/test_integration_wip.py -import pytest from eth_typing import Hash32 from story_protocol_python_sdk.story_client import StoryClient -from .setup_for_integration import ( - wallet_address, - wallet_address_2, - wallet_address_3, - web3, -) +from .setup_for_integration import wallet_address, wallet_address_2, web3 # Type assertions to ensure wallet addresses are strings assert wallet_address is not None, "wallet_address is required" assert wallet_address_2 is not None, "wallet_address_2 is required" -assert wallet_address_3 is not None, "wallet_address_3 is required" # Type cast to satisfy mypy wallet_address_str: str = wallet_address wallet_address_2_str: str = wallet_address_2 -wallet_address_3_str: str = wallet_address_3 class TestWIPDeposit: @@ -97,520 +89,73 @@ def test_transfer(self, story_client: StoryClient): # Note: We're not testing transferFrom here as it requires approval # and the TypeScript test also skips this test for the same reason - def test_transfer_zero_address(self, story_client: StoryClient): - """Test transfer with zero address (should be rejected by contract with custom error)""" - zero_address = "0x0000000000000000000000000000000000000000" - - # The contract should reject this with a custom error ERC20InvalidReceiver - with pytest.raises(Exception) as exc_info: - story_client.WIP.transfer( - to=zero_address, - amount=web3.to_wei("0.01", "ether"), - tx_options={"waitForTransaction": True}, - ) - - # Verify the transaction failed due to contract validation - # The SDK should provide the direct contract error message - assert "0xec442f05" in str(exc_info.value) - - def test_transfer_to_contract_address(self, story_client: StoryClient): - """Test transfer to WIP contract address (should be rejected by contract with custom error)""" - # Get the WIP contract address - contract_address = story_client.WIP.wip_client.contract.address - - # The contract should reject this with a custom error ERC20InvalidReceiver - with pytest.raises(Exception) as exc_info: - story_client.WIP.transfer( - to=contract_address, - amount=web3.to_wei("0.01", "ether"), - tx_options={"waitForTransaction": True}, - ) - - # Verify the transaction failed due to contract validation - # The SDK should provide the direct contract error message - assert "0xec442f05" in str(exc_info.value) - class TestWIPApprove: """Test WIP approval functionality""" - def setup_method(self): - """Setup method called before each test""" - pass - - def teardown_method(self): - """Teardown method called after each test""" - # Reset allowance to a reasonable value to avoid affecting other tests - try: - from tests.integration.config.test_config import account, web3 - from tests.integration.config.utils import get_story_client - - story_client = get_story_client(web3, account) - - # Check current allowance - current_allowance = story_client.WIP.allowance( - owner=wallet_address_str, spender=wallet_address_2_str - ) - target_allowance = web3.to_wei("0.01", "ether") - - # Only reset if current allowance is not already 0.01 IP - if current_allowance != target_allowance: - story_client.WIP.approve( - spender=wallet_address_2_str, - amount=target_allowance, - tx_options={"waitForTransaction": True}, - ) - else: - pass - except Exception: - raise - - def test_approve_basic(self, story_client: StoryClient): + def test_approve(self, story_client: StoryClient): """Test basic approve functionality""" - approve_amount = web3.to_wei("0.1", "ether") + approve_amount = web3.to_wei(1, "wei") - try: - # Get initial allowance - story_client.WIP.allowance( - owner=wallet_address_str, spender=wallet_address_2_str - ) - - # Approve wallet_address_2 to spend WIP - response = story_client.WIP.approve( - spender=wallet_address_2_str, - amount=approve_amount, - tx_options={"waitForTransaction": True}, - ) - - # Verify transaction hash - assert isinstance( - response, dict - ), f"Expected dict response, got {type(response)}" - assert "tx_hash" in response, f"Response missing tx_hash: {response}" - assert isinstance( - response["tx_hash"], str - ), f"Invalid tx_hash type: {type(response['tx_hash'])}" - - tx_hash = response["tx_hash"] - - # Wait for transaction confirmation - tx_receipt = web3.eth.wait_for_transaction_receipt( - Hash32(bytes.fromhex(tx_hash)), timeout=300 - ) - - if tx_receipt["status"] == 0: - raise AssertionError( - f"Approve transaction failed with status 0: {tx_hash}" - ) - - # Verify allowance was set correctly - final_allowance = story_client.WIP.allowance( - owner=wallet_address_str, spender=wallet_address_2_str - ) - - assert final_allowance == approve_amount, ( - f"Allowance mismatch. Expected: {web3.from_wei(approve_amount, 'ether')} IP, " - f"Got: {web3.from_wei(final_allowance, 'ether')} IP" - ) - - except Exception: - raise - - def test_approve_zero_amount(self, story_client: StoryClient): - """Test approving zero amount (should be rejected by implementation)""" - try: - # First approve some amount - initial_approve = web3.to_wei("0.05", "ether") - story_client.WIP.approve( - spender=wallet_address_2_str, - amount=initial_approve, - tx_options={"waitForTransaction": True}, - ) - - # Verify initial allowance - initial_allowance = story_client.WIP.allowance( - owner=wallet_address_str, spender=wallet_address_2_str - ) - assert initial_allowance == initial_approve - - # Try to approve zero amount - should be rejected - with pytest.raises( - ValueError, match="WIP approve amount must be greater than 0" - ): - story_client.WIP.approve( - spender=wallet_address_2_str, - amount=0, - tx_options={"waitForTransaction": True}, - ) - - # Verify allowance remains unchanged - final_allowance = story_client.WIP.allowance( - owner=wallet_address_str, spender=wallet_address_2_str - ) - - assert ( - final_allowance == initial_allowance - ), f"Allowance should remain unchanged, got: {final_allowance}" - - except Exception: - raise - - def test_approve_negative_amount(self, story_client: StoryClient): - """Test approving negative amount (should be rejected)""" - with pytest.raises( - ValueError, match="WIP approve amount must be greater than 0" - ): - story_client.WIP.approve( - spender=wallet_address_2_str, - amount=-1, - tx_options={"waitForTransaction": True}, - ) - - def test_approve_max_uint256(self, story_client: StoryClient): - """Test approving maximum uint256 value""" - max_uint256 = 2**256 - 1 - - try: - response = story_client.WIP.approve( - spender=wallet_address_2_str, - amount=max_uint256, - tx_options={"waitForTransaction": True}, - ) - - # Verify transaction - assert isinstance(response["tx_hash"], str) - - # Wait for confirmation - web3.eth.wait_for_transaction_receipt( - Hash32(bytes.fromhex(response["tx_hash"])), timeout=300 - ) - - # Verify allowance is set to max uint256 - final_allowance = story_client.WIP.allowance( - owner=wallet_address_str, spender=wallet_address_2_str - ) - - assert ( - final_allowance == max_uint256 - ), f"Allowance should be max uint256, got: {final_allowance}" - - except Exception: - raise - - @pytest.mark.parametrize( - "invalid_address", - [ - "0x123", # Too short - "invalid_address", # Invalid format - "", # Empty string - ], - ) - def test_approve_invalid_spender( - self, story_client: StoryClient, invalid_address: str - ): - """Test approve with invalid spender addresses""" - with pytest.raises(ValueError, match="not valid"): - story_client.WIP.approve( - spender=invalid_address, amount=web3.to_wei("0.01", "ether") - ) - - def test_approve_zero_address(self, story_client: StoryClient): - """Test approve with zero address (current implementation accepts it)""" - zero_address = "0x0000000000000000000000000000000000000000" - - # The current implementation accepts zero address, so we test that it works response = story_client.WIP.approve( - spender=zero_address, - amount=web3.to_wei("0.01", "ether"), - tx_options={"waitForTransaction": True}, + spender=wallet_address_2_str, + amount=approve_amount, ) - # Verify the transaction succeeded + assert response is not None + assert "tx_hash" in response assert isinstance(response["tx_hash"], str) - # Verify the allowance was set - allowance = story_client.WIP.allowance( - owner=wallet_address_str, spender=zero_address + # Verify allowance was set correctly + final_allowance = story_client.WIP.allowance( + owner=wallet_address_str, spender=wallet_address_2_str ) - assert allowance == web3.to_wei("0.01", "ether") - - def test_approve_self(self, story_client: StoryClient): - """Test approve with self as spender (should be rejected by contract with custom error)""" - with pytest.raises(Exception) as exc_info: - story_client.WIP.approve( - spender=wallet_address_str, - amount=web3.to_wei("0.01", "ether"), - tx_options={"waitForTransaction": True}, - ) - - # Verify the transaction failed due to contract validation - # The SDK should provide the direct contract error message - assert "0x94280d62" in str(exc_info.value) + assert final_allowance == approve_amount class TestWIPTransferFrom: """Test WIP transferFrom functionality""" - def setup_method(self): - """Setup method called before each test""" - pass - - def teardown_method(self): - """Teardown method called after each test""" - pass - - def test_transfer_from_basic( + def test_transfer_from( self, story_client: StoryClient, story_client_2: StoryClient ): """Test basic transferFrom functionality""" - transfer_amount = web3.to_wei("0.01", "ether") - - try: - # Ensure wallet_address has enough WIP balance - owner_balance = story_client.WIP.balance_of(wallet_address_str) - if owner_balance < transfer_amount: - deposit_amount = ( - transfer_amount - owner_balance + web3.to_wei("0.1", "ether") - ) - story_client.WIP.deposit( - amount=deposit_amount, tx_options={"waitForTransaction": True} - ) - - # Get initial balances - owner_wip_before = story_client.WIP.balance_of(wallet_address_str) - spender_wip_before = story_client.WIP.balance_of(wallet_address_2_str) - receiver_wip_before = story_client.WIP.balance_of(wallet_address_3_str) - - # Approve spender to transfer from owner - approve_response = story_client.WIP.approve( - spender=wallet_address_2_str, - amount=transfer_amount, - tx_options={"waitForTransaction": True}, - ) - - # Wait for approve transaction - web3.eth.wait_for_transaction_receipt( - Hash32(bytes.fromhex(approve_response["tx_hash"])), timeout=300 - ) - - # Verify allowance - allowance = story_client.WIP.allowance( - owner=wallet_address_str, spender=wallet_address_2_str - ) - assert allowance >= transfer_amount, f"Insufficient allowance: {allowance}" - - # Execute transferFrom using spender's account - transfer_response = story_client_2.WIP.transfer_from( - from_address=wallet_address_str, - to=wallet_address_3_str, - amount=transfer_amount, - tx_options={"waitForTransaction": True}, - ) - - # Verify transaction hash - assert isinstance( - transfer_response, dict - ), f"Expected dict response, got {type(transfer_response)}" - assert ( - "tx_hash" in transfer_response - ), f"Response missing tx_hash: {transfer_response}" - assert isinstance( - transfer_response["tx_hash"], str - ), f"Invalid tx_hash type: {type(transfer_response['tx_hash'])}" - - tx_hash = transfer_response["tx_hash"] - - # Wait for transaction confirmation - tx_receipt = web3.eth.wait_for_transaction_receipt( - Hash32(bytes.fromhex(tx_hash)), timeout=300 - ) - - if tx_receipt["status"] == 0: - raise AssertionError( - f"TransferFrom transaction failed with status 0: {tx_hash}" - ) - - # Get final balances - owner_wip_after = story_client.WIP.balance_of(wallet_address_str) - spender_wip_after = story_client.WIP.balance_of(wallet_address_2_str) - receiver_wip_after = story_client.WIP.balance_of(wallet_address_3_str) - - # Verify balances - assert ( - owner_wip_after == owner_wip_before - transfer_amount - ), f"Owner balance should decrease by {web3.from_wei(transfer_amount, 'ether')} IP" - - assert ( - receiver_wip_after == receiver_wip_before + transfer_amount - ), f"Receiver balance should increase by {web3.from_wei(transfer_amount, 'ether')} IP" - - # Verify spender balance remains the same (they're just the intermediary) - assert ( - spender_wip_after == spender_wip_before - ), "Spender balance should remain unchanged" - - # Verify allowance was reduced - final_allowance = story_client.WIP.allowance( - owner=wallet_address_str, spender=wallet_address_2_str - ) - expected_allowance = allowance - transfer_amount - assert final_allowance == expected_allowance, ( - f"Allowance should be reduced by transfer amount. " - f"Expected: {web3.from_wei(expected_allowance, 'ether')} IP, " - f"Got: {web3.from_wei(final_allowance, 'ether')} IP" - ) - - except Exception: - raise - - def test_transfer_from_insufficient_allowance( - self, story_client: StoryClient, story_client_2: StoryClient - ): - """Test transferFrom with insufficient allowance""" - transfer_amount = web3.to_wei("0.01", "ether") - - try: - # Ensure wallet_address has enough WIP balance - owner_balance = story_client.WIP.balance_of(wallet_address_str) - if owner_balance < transfer_amount: - story_client.WIP.deposit( - amount=transfer_amount, tx_options={"waitForTransaction": True} - ) - - # Approve less than transfer amount - insufficient_allowance = transfer_amount - web3.to_wei("0.001", "ether") - story_client.WIP.approve( - spender=wallet_address_2_str, - amount=insufficient_allowance, - tx_options={"waitForTransaction": True}, - ) - - # Try to transfer more than allowed - with pytest.raises(Exception): # Should raise an exception - story_client_2.WIP.transfer_from( - from_address=wallet_address_str, - to=wallet_address_2_str, - amount=transfer_amount, - tx_options={"waitForTransaction": True}, - ) - - except Exception: - raise - - def test_transfer_from_insufficient_balance( - self, story_client: StoryClient, story_client_2: StoryClient - ): - """Test transferFrom with insufficient balance""" - transfer_amount = web3.to_wei("1000", "ether") # Large amount - - try: - # Get current balance - story_client.WIP.balance_of(wallet_address_str) - - # Approve large amount - story_client.WIP.approve( - spender=wallet_address_2_str, - amount=transfer_amount, - tx_options={"waitForTransaction": True}, - ) - - # Try to transfer more than available balance - with pytest.raises(Exception): # Should raise an exception - story_client_2.WIP.transfer_from( - from_address=wallet_address_str, - to=wallet_address_2_str, - amount=transfer_amount, - tx_options={"waitForTransaction": True}, - ) - - except Exception: - raise - - def test_transfer_from_negative_amount(self, story_client: StoryClient): - """Test transferFrom with negative amount (should be rejected)""" - with pytest.raises( - ValueError, match="WIP transfer amount must be greater than 0" - ): - story_client.WIP.transfer_from( - from_address=wallet_address_str, - to=wallet_address_2_str, - amount=-1, - tx_options={"waitForTransaction": True}, - ) + transfer_amount = web3.to_wei("1", "wei") - @pytest.mark.parametrize( - "invalid_address", - [ - "0x123", # Too short - "invalid_address", # Invalid format - "", # Empty string - ], - ) - def test_transfer_from_invalid_addresses( - self, story_client: StoryClient, invalid_address: str - ): - """Test transferFrom with invalid addresses""" - with pytest.raises(ValueError, match="not valid"): - story_client.WIP.transfer_from( - from_address=invalid_address, - to=wallet_address_2_str, - amount=web3.to_wei("0.01", "ether"), - ) + # Ensure wallet_address has enough WIP balance + owner_balance = story_client.WIP.balance_of(wallet_address_str) + if owner_balance < transfer_amount: + deposit_amount = transfer_amount - owner_balance + web3.to_wei("1", "wei") + story_client.WIP.deposit(amount=deposit_amount) - with pytest.raises(ValueError, match="not valid"): - story_client.WIP.transfer_from( - from_address=wallet_address_str, - to=invalid_address, - amount=web3.to_wei("0.01", "ether"), - ) + # Get initial balances + owner_wip_before = story_client.WIP.balance_of(wallet_address_str) + spender_wip_before = story_client.WIP.balance_of(wallet_address_2_str) - def test_transfer_from_zero_addresses(self, story_client: StoryClient): - """Test transferFrom with zero addresses (should be rejected by contract with custom error)""" - zero_address = "0x0000000000000000000000000000000000000000" - - # Test zero address as to_address - should be rejected by contract validation - # Note: We need to set up approval first for a valid from_address + # Approve spender to transfer from owner story_client.WIP.approve( spender=wallet_address_2_str, - amount=web3.to_wei("0.1", "ether"), - tx_options={"waitForTransaction": True}, + amount=transfer_amount, ) - with pytest.raises(Exception) as exc_info: - story_client.WIP.transfer_from( - from_address=wallet_address_str, - to=zero_address, - amount=web3.to_wei("0.01", "ether"), - tx_options={"waitForTransaction": True}, - ) - - # Verify the transaction failed due to contract validation - # The SDK should provide the direct contract error message - assert "0xec442f05" in str(exc_info.value) - - def test_transfer_from_to_contract_address(self, story_client: StoryClient): - """Test transferFrom to WIP contract address (should be rejected by contract with custom error)""" - # Get the WIP contract address - contract_address = story_client.WIP.wip_client.contract.address - - # Set up approval first - story_client.WIP.approve( - spender=wallet_address_2_str, - amount=web3.to_wei("0.1", "ether"), - tx_options={"waitForTransaction": True}, + # Execute transferFrom using spender's account + response = story_client_2.WIP.transfer_from( + from_address=wallet_address_str, + to=wallet_address_2_str, + amount=transfer_amount, ) - with pytest.raises(Exception) as exc_info: - story_client.WIP.transfer_from( - from_address=wallet_address_str, - to=contract_address, - amount=web3.to_wei("0.01", "ether"), - tx_options={"waitForTransaction": True}, - ) + assert response is not None + assert "tx_hash" in response + assert isinstance(response["tx_hash"], str) + + # Get final balances + owner_wip_after = story_client.WIP.balance_of(wallet_address_str) + spender_wip_after = story_client.WIP.balance_of(wallet_address_2_str) - # Verify the transaction failed due to contract validation - # The SDK should provide the direct contract error message - assert "0xec442f05" in str(exc_info.value) + # Verify balances + assert owner_wip_after == owner_wip_before - transfer_amount + assert spender_wip_after == spender_wip_before + transfer_amount class TestWIPWithdraw: From 11f59a40a6035106d9633ccd899b371eb3a9d1ee Mon Sep 17 00:00:00 2001 From: Yao Date: Fri, 5 Sep 2025 17:31:32 +0800 Subject: [PATCH 5/6] Apply suggestions from code review --- src/story_protocol_python_sdk/resources/WIP.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/story_protocol_python_sdk/resources/WIP.py b/src/story_protocol_python_sdk/resources/WIP.py index cec06866..37d7c2f1 100644 --- a/src/story_protocol_python_sdk/resources/WIP.py +++ b/src/story_protocol_python_sdk/resources/WIP.py @@ -102,8 +102,6 @@ def approve( spender = self.web3.to_checksum_address(spender) - # Note: Contract handles self-approval validation with custom error ERC20InvalidSpender - response = build_and_send_transaction( self.web3, self.account, @@ -153,9 +151,6 @@ def transfer(self, to: str, amount: int, tx_options: dict | None = None) -> dict to = self.web3.to_checksum_address(to) - # Note: Contract handles zero address and contract address validation - # with custom errors ERC20InvalidReceiver - response = build_and_send_transaction( self.web3, self.account, @@ -195,9 +190,6 @@ def transfer_from( from_address = self.web3.to_checksum_address(from_address) to = self.web3.to_checksum_address(to) - # Note: Contract handles zero address and contract address validation - # with custom errors ERC20InvalidReceiver - response = build_and_send_transaction( self.web3, self.account, From 63fea38c23c7b53e975db0bb20474fbfc7bbed4c Mon Sep 17 00:00:00 2001 From: Yao Date: Fri, 5 Sep 2025 17:35:58 +0800 Subject: [PATCH 6/6] Apply suggestions from code review --- tests/integration/test_integration_wip.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/integration/test_integration_wip.py b/tests/integration/test_integration_wip.py index db08cc2f..e6c7ed17 100644 --- a/tests/integration/test_integration_wip.py +++ b/tests/integration/test_integration_wip.py @@ -52,7 +52,7 @@ class TestWIPTransfer: def test_transfer(self, story_client: StoryClient): """Test transferring WIP""" - transfer_amount = web3.to_wei("0.01", "ether") + transfer_amount = web3.to_wei(1, "wei") # Get balances before transfer sender_wip_before = story_client.WIP.balance_of(wallet_address_str) @@ -62,7 +62,6 @@ def test_transfer(self, story_client: StoryClient): if sender_wip_before < transfer_amount: story_client.WIP.deposit( amount=transfer_amount - sender_wip_before, - tx_options={"waitForTransaction": True}, ) sender_wip_before = story_client.WIP.balance_of(wallet_address_str)