Skip to content

Commit

Permalink
Fix
Browse files Browse the repository at this point in the history
  • Loading branch information
rohan-agarwal-coinbase committed Nov 22, 2024
1 parent f52ad57 commit c556401
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 55 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
### Fixed
- Fix bug in `Asset.from_model` where passed in asset ID was not used when creating a gwei or wei asset.

### Added

- Add `FundOperation` and `FundQuote` classes to support wallet funding.

## [0.10.3] - 2024-11-07

### Added
Expand Down
16 changes: 16 additions & 0 deletions tests/test_fund_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,22 @@ def test_fund_operation_create_with_quote(
)


@patch("cdp.Cdp.api_clients")
def test_list_fund_operations(mock_api_clients, fund_operation_factory):
"""Test the listing of fund operations."""
mock_list_fund_operations = Mock()
mock_list_fund_operations.return_value = Mock(
data=[fund_operation_factory()._model], has_more=False
)
mock_api_clients.fund.list_fund_operations = mock_list_fund_operations
fund_operations = FundOperation.list("test-wallet-id", "0xaddressid")
assert len(list(fund_operations)) == 1
assert all(isinstance(f, FundOperation) for f in fund_operations)
mock_list_fund_operations.assert_called_once_with(
wallet_id="test-wallet-id", address_id="0xaddressid", limit=100, page=None
)


@patch("cdp.Cdp.api_clients")
def test_fund_operation_reload(mock_api_clients, fund_operation_factory):
"""Test the reloading of a FundOperation object."""
Expand Down
123 changes: 68 additions & 55 deletions tests/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
from unittest.mock import ANY, Mock, PropertyMock, call, patch

import pytest
from bip_utils import Bip32Slip10Secp256k1
from eth_account import Account

from cdp.client.models.create_address_request import CreateAddressRequest
from cdp.client.models.create_wallet_request import CreateWalletRequest, CreateWalletRequestWallet
from cdp.client.models.create_wallet_webhook_request import CreateWalletWebhookRequest
from cdp.contract_invocation import ContractInvocation
from cdp.fund_operation import FundOperation
from cdp.fund_quote import FundQuote
from cdp.payload_signature import PayloadSignature
from cdp.smart_contract import SmartContract
from cdp.trade import Trade
Expand Down Expand Up @@ -51,60 +52,6 @@ def test_wallet_initialization_with_server_signer(wallet_factory):
assert not wallet.can_sign


@patch("cdp.Cdp.use_server_signer", False)
@patch("cdp.wallet.Account")
@patch("cdp.wallet.Bip32Slip10Secp256k1")
@patch("cdp.Cdp.api_clients")
def test_wallet_addresses(
mock_api_clients, mock_bip32, mock_account, wallet_factory, address_model_factory
):
"""Test Wallet addresses method."""
wallet = wallet_factory()
mock_list_addresses = Mock()
mock_list_addresses.return_value.data = [
address_model_factory(address_id="0x1234"),
address_model_factory(address_id="0x5678"),
]
mock_api_clients.addresses.list_addresses = mock_list_addresses

mock_from_key = Mock(
side_effect=[Mock(spec=Account, address="0x1234"), Mock(spec=Account, address="0x5678")]
)
mock_account.from_key = mock_from_key

mock_derive_path = Mock(spec=Bip32Slip10Secp256k1)
mock_bip32.DerivePath = mock_derive_path

addresses = wallet.addresses

assert len(addresses) == 2
assert all(isinstance(addr, WalletAddress) for addr in addresses)
assert addresses[0].address_id == "0x1234"
assert addresses[1].address_id == "0x5678"


@patch("cdp.Cdp.use_server_signer", True)
@patch("cdp.Cdp.api_clients")
def test_wallet_addresses_with_server_signer(
mock_api_clients, wallet_factory, address_model_factory
):
"""Test Wallet addresses method with server-signer."""
wallet = wallet_factory()
mock_list_addresses = Mock()
mock_list_addresses.return_value.data = [
address_model_factory(address_id="0x1234"),
address_model_factory(address_id="0x5678"),
]
mock_api_clients.addresses.list_addresses = mock_list_addresses

addresses = wallet.addresses

assert len(addresses) == 2
assert all(isinstance(addr, WalletAddress) for addr in addresses)
assert addresses[0].address_id == "0x1234"
assert addresses[1].address_id == "0x5678"


@patch("cdp.Cdp.use_server_signer", False)
@patch("cdp.Cdp.api_clients")
@patch("cdp.wallet.Bip32Slip10Secp256k1")
Expand Down Expand Up @@ -645,3 +592,69 @@ def test_create_webhook(mock_api_clients, wallet_factory, webhook_factory):

# Additional assertions to check the returned webhook object
assert webhook.notification_uri == notification_uri


@patch("cdp.Cdp.use_server_signer", True)
def test_wallet_fund(wallet_factory):
"""Test the fund method of a Wallet."""
wallet = wallet_factory()
mock_default_address = Mock(spec=WalletAddress)
mock_fund_operation = Mock(spec=FundOperation)
mock_default_address.fund.return_value = mock_fund_operation

with patch.object(
Wallet, "default_address", new_callable=PropertyMock
) as mock_default_address_prop:
mock_default_address_prop.return_value = mock_default_address

fund_operation = wallet.fund(amount="1.0", asset_id="eth")

assert isinstance(fund_operation, FundOperation)
mock_default_address.fund.assert_called_once_with("1.0", "eth")


@patch("cdp.Cdp.use_server_signer", True)
def test_wallet_fund_no_default_address(wallet_factory):
"""Test the fund method of a Wallet with no default address."""
wallet = wallet_factory()

with patch.object(
Wallet, "default_address", new_callable=PropertyMock
) as mock_default_address_prop:
mock_default_address_prop.return_value = None

with pytest.raises(ValueError, match="Default address does not exist"):
wallet.fund(amount="1.0", asset_id="eth")


@patch("cdp.Cdp.use_server_signer", True)
def test_wallet_quote_fund(wallet_factory):
"""Test the quote_fund method of a Wallet."""
wallet = wallet_factory()
mock_default_address = Mock(spec=WalletAddress)
mock_fund_quote = Mock(spec=FundQuote)
mock_default_address.quote_fund.return_value = mock_fund_quote

with patch.object(
Wallet, "default_address", new_callable=PropertyMock
) as mock_default_address_prop:
mock_default_address_prop.return_value = mock_default_address

fund_quote = wallet.quote_fund(amount="1.0", asset_id="eth")

assert isinstance(fund_quote, FundQuote)
mock_default_address.quote_fund.assert_called_once_with("1.0", "eth")


@patch("cdp.Cdp.use_server_signer", True)
def test_wallet_quote_fund_no_default_address(wallet_factory):
"""Test the quote_fund method of a Wallet with no default address."""
wallet = wallet_factory()

with patch.object(
Wallet, "default_address", new_callable=PropertyMock
) as mock_default_address_prop:
mock_default_address_prop.return_value = None

with pytest.raises(ValueError, match="Default address does not exist"):
wallet.quote_fund(amount="1.0", asset_id="eth")
80 changes: 80 additions & 0 deletions tests/test_wallet_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

from cdp.contract_invocation import ContractInvocation
from cdp.errors import InsufficientFundsError
from cdp.fund_operation import FundOperation
from cdp.fund_quote import FundQuote
from cdp.payload_signature import PayloadSignature
from cdp.smart_contract import SmartContract
from cdp.trade import Trade
Expand Down Expand Up @@ -1020,3 +1022,81 @@ def test_ensure_sufficient_balance_sufficient_full_amount(
mock_get_balance.assert_called_once_with(
network_id=wallet_address.network_id, address_id=wallet_address.address_id, asset_id="eth"
)


@patch("cdp.wallet_address.FundOperation")
def test_fund(mock_fund_operation, wallet_address_factory):
"""Test the fund method."""
wallet_address = wallet_address_factory()

mock_fund_operation_instance = Mock(spec=FundOperation)
mock_fund_operation.create.return_value = mock_fund_operation_instance

fund_operation = wallet_address.fund(amount="1.0", asset_id="eth")

assert isinstance(fund_operation, FundOperation)
mock_fund_operation.create.assert_called_once_with(
address_id=wallet_address.address_id,
amount=Decimal("1.0"),
asset_id="eth",
network_id=wallet_address.network_id,
wallet_id=wallet_address.wallet_id,
)


@patch("cdp.wallet_address.FundOperation")
def test_fund_api_error(mock_fund_operation, wallet_address_factory):
"""Test the fund method raises an error when the API call fails."""
wallet_address = wallet_address_factory()

mock_fund_operation.create.side_effect = Exception("API Error")

with pytest.raises(Exception, match="API Error"):
wallet_address.fund(amount="1.0", asset_id="eth")

mock_fund_operation.create.assert_called_once_with(
address_id=wallet_address.address_id,
amount=Decimal("1.0"),
asset_id="eth",
network_id=wallet_address.network_id,
wallet_id=wallet_address.wallet_id,
)


@patch("cdp.wallet_address.FundQuote")
def test_quote_fund(mock_fund_quote, wallet_address_factory):
"""Test the quote_fund method."""
wallet_address = wallet_address_factory()

mock_fund_quote_instance = Mock(spec=FundQuote)
mock_fund_quote.create.return_value = mock_fund_quote_instance

fund_quote = wallet_address.quote_fund(amount="1.0", asset_id="eth")

assert isinstance(fund_quote, FundQuote)
mock_fund_quote.create.assert_called_once_with(
address_id=wallet_address.address_id,
amount="1.0",
asset_id="eth",
network_id=wallet_address.network_id,
wallet_id=wallet_address.wallet_id,
)


@patch("cdp.wallet_address.FundQuote")
def test_quote_fund_api_error(mock_fund_quote, wallet_address_factory):
"""Test the quote_fund method raises an error when the API call fails."""
wallet_address = wallet_address_factory()

mock_fund_quote.create.side_effect = Exception("API Error")

with pytest.raises(Exception, match="API Error"):
wallet_address.quote_fund(amount="1.0", asset_id="eth")

mock_fund_quote.create.assert_called_once_with(
address_id=wallet_address.address_id,
amount="1.0",
asset_id="eth",
network_id=wallet_address.network_id,
wallet_id=wallet_address.wallet_id,
)

0 comments on commit c556401

Please sign in to comment.