nft royalty edge cases (#14789)

* nft royalty edge cases

* making tests faster

* Update tests/wallet/nft_wallet/test_nft_1_offers.py

Co-authored-by: Matt Hauff <quexington@gmail.com>

* flake

---------

Co-authored-by: Matt Hauff <quexington@gmail.com>
This commit is contained in:
Sebastjan Trepca 2023-03-15 02:27:15 +01:00 committed by GitHub
parent 766c33cd33
commit a6f423522b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 24 deletions

View File

@ -347,6 +347,11 @@ class NFTWallet:
# For a DID enabled NFT wallet it cannot mint NFT0. Mint NFT1 instead.
did_id = self.did_id
amount = uint64(1)
# ensure percentage is uint16
try:
percentage = uint16(percentage)
except ValueError:
raise ValueError("Percentage must be lower than 655%")
coins = await self.standard_wallet.select_coins(uint64(amount + fee))
if coins is None:
return None
@ -663,7 +668,6 @@ class NFTWallet:
payments.append(Payment(puzhash, amount, memos_with_hint))
payment_sum = sum([p.amount for p in payments])
unsigned_spend_bundle, chia_tx = await self.generate_unsigned_spendbundle(
payments,
fee,
@ -869,16 +873,23 @@ class NFTWallet:
for asset, amount in royalty_nft_asset_dict.items(): # royalty enabled NFTs
transfer_info = driver_dict[asset].also().also() # type: ignore
assert isinstance(transfer_info, PuzzleInfo)
royalty_percentage_raw = transfer_info["transfer_program"]["royalty_percentage"]
assert royalty_percentage_raw is not None
# clvm encodes large ints as bytes
if isinstance(royalty_percentage_raw, bytes):
royalty_percentage = int_from_bytes(royalty_percentage_raw)
else:
royalty_percentage = int(royalty_percentage_raw)
if amount > 0:
required_royalty_info.append(
(
asset,
bytes32(transfer_info["transfer_program"]["royalty_address"]),
uint16(transfer_info["transfer_program"]["royalty_percentage"]),
uint16(royalty_percentage),
)
)
else:
offered_royalty_percentages[asset] = uint16(transfer_info["transfer_program"]["royalty_percentage"])
offered_royalty_percentages[asset] = uint16(royalty_percentage)
royalty_payments: Dict[Optional[bytes32], List[Tuple[bytes32, Payment]]] = {}
for asset, amount in fungible_asset_dict.items(): # offered fungible items
@ -950,6 +961,7 @@ class NFTWallet:
additional_bundles: List[SpendBundle] = []
# standard pays the fee if possible
fee_left_to_pay: uint64 = uint64(0) if None in offer_dict and offer_dict[None] < 0 else fee
for asset, amount in offer_dict.items():
if amount < 0:
if asset is None:

View File

@ -3,7 +3,6 @@ from __future__ import annotations
import dataclasses
import logging
import time
import traceback
from typing import Any, Dict, List, Optional, Set, Tuple, Union
from typing_extensions import Literal
@ -597,8 +596,7 @@ class TradeManager:
return True, offer, None
except Exception as e:
tb = traceback.format_exc()
self.log.error(f"Error with creating trade offer: {type(e)}{tb}")
self.log.exception("Error creating trade offer")
return False, None, str(e)
async def maybe_create_wallets_for_offer(self, offer: Offer) -> None:

View File

@ -1272,7 +1272,14 @@ async def test_nft_offer_sell_cancel_in_batch(self_hostname: str, two_wallet_nod
)
@pytest.mark.parametrize(
"forwards_compat,royalty_pts",
[(True, (200, 500, 500)), (False, (200, 500, 500)), (False, (0, 0, 0))],
[
(True, (200, 500, 500)),
(False, (200, 500, 500)),
(False, (0, 0, 0)), # test that we can have 0 royalty
(False, (65000, 65534, 65535)), # test that we can reach max royalty
(False, (10000, 10001, 10005)), # tests 100% royalty is not allowed
(False, (100000, 10001, 10005)), # 1000% shouldn't work
],
)
@pytest.mark.asyncio
# @pytest.mark.skip
@ -1316,12 +1323,30 @@ async def test_complex_nft_offer(
for i in range(0, 2):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_maker))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_taker))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_taker))
if royalty_pts[0] > 60000:
blocks_needed = 9
else:
blocks_needed = 3
if not forwards_compat:
for i in range(blocks_needed):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_taker))
else:
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_taker))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token))
await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=30)
funds_maker = sum([calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, 3)])
funds_taker = sum([calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, 4)])
if forwards_compat:
funds_taker = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, 4)]
)
else:
funds_taker = sum(
[
calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i))
for i in range(1, 3 + blocks_needed)
]
)
await time_out_assert(30, wallet_maker.get_unconfirmed_balance, funds_maker)
await time_out_assert(30, wallet_maker.get_confirmed_balance, funds_maker)
@ -1391,7 +1416,7 @@ async def test_complex_nft_offer(
royalty_puzhash_maker = ph_maker
royalty_puzhash_taker = ph_taker
royalty_basis_pts_maker, royalty_basis_pts_taker_1, royalty_basis_pts_taker_2 = (
uint16(royalty_pts[0]),
royalty_pts[0],
uint16(royalty_pts[1]),
uint16(royalty_pts[2]),
)
@ -1408,14 +1433,25 @@ async def test_complex_nft_offer(
("h", "0xD4584AD463139FA8C0D9F68F4B59F185"),
]
)
if royalty_basis_pts_maker > 65535:
with pytest.raises(ValueError):
await nft_wallet_maker.generate_new_nft(
metadata,
target_puzhash_maker,
royalty_puzhash_maker,
royalty_basis_pts_maker, # type: ignore
did_id_maker,
)
return
else:
sb_maker = await nft_wallet_maker.generate_new_nft(
metadata,
target_puzhash_maker,
royalty_puzhash_maker,
uint16(royalty_basis_pts_maker),
did_id_maker,
)
sb_maker = await nft_wallet_maker.generate_new_nft(
metadata,
target_puzhash_maker,
royalty_puzhash_maker,
royalty_basis_pts_maker,
did_id_maker,
)
sb_taker_1 = await nft_wallet_taker.generate_new_nft(
metadata,
target_puzhash_taker,
@ -1508,12 +1544,21 @@ async def test_complex_nft_offer(
assert error is None
assert success
assert trade_make is not None
trade_take, tx_records = await trade_manager_taker.respond_to_offer(
old_maker_offer if forwards_compat else Offer.from_bytes(trade_make.offer),
wallet_node_taker.get_full_node_peer(),
fee=FEE,
)
if royalty_basis_pts_maker == 10000:
with pytest.raises(ValueError):
trade_take, tx_records = await trade_manager_taker.respond_to_offer(
old_maker_offer if forwards_compat else Offer.from_bytes(trade_make.offer),
wallet_node_taker.get_full_node_peer(),
fee=FEE,
)
# all done for this test
return
else:
trade_take, tx_records = await trade_manager_taker.respond_to_offer(
old_maker_offer if forwards_compat else Offer.from_bytes(trade_make.offer),
wallet_node_taker.get_full_node_peer(),
fee=FEE,
)
assert trade_take is not None
assert tx_records is not None
await full_node_api.process_transaction_records(records=tx_records)
@ -1521,7 +1566,7 @@ async def test_complex_nft_offer(
# Now let's make sure the final wallet state is correct
maker_royalty_summary = NFTWallet.royalty_calculation(
{
nft_to_offer_asset_id_maker: (royalty_puzhash_maker, royalty_basis_pts_maker),
nft_to_offer_asset_id_maker: (royalty_puzhash_maker, uint16(royalty_basis_pts_maker)),
},
{
None: uint64(XCH_REQUESTED),