Skip to content

Commit

Permalink
fix(ListOnOpenseaProposal): handle cancelled Zora auction
Browse files Browse the repository at this point in the history
  • Loading branch information
0xble committed Sep 22, 2022
1 parent 559c27e commit 7a76a2b
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 8 deletions.
16 changes: 13 additions & 3 deletions contracts/proposals/ListOnOpenseaProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,19 @@ abstract contract ListOnOpenseaProposal is ZoraHelpers {
abi.decode(params.progressData, (uint8, ZoraProgressData));
// Try to settle the Zora auction. This will revert if the auction
// is still ongoing.
if (_settleZoraAuction(zpd.auctionId, zpd.minExpiry, data.token, data.tokenId)) {
// Auction sold. Nothing left to do. Return empty progress data
// to indicate there are no more steps to execute.
ZoraAuctionStatus statusCode = _settleZoraAuction(
zpd.auctionId,
zpd.minExpiry,
data.token,
data.tokenId
);
if (
statusCode == ZoraAuctionStatus.Sold ||
statusCode == ZoraAuctionStatus.Cancelled
) {
// Auction sold or was cancelled. Nothing left to do. Return
// empty progress data to indicate there are no more steps to
// execute.
return "";
}
// The auction simply expired before anyone bid on it. We have the NFT
Expand Down
8 changes: 4 additions & 4 deletions contracts/proposals/ListOnZoraProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ contract ListOnZoraProposal is ZoraHelpers {
)
internal
override
returns (bool sold)
returns (ZoraAuctionStatus statusCode)
{
// Getting the state of an auction is super expensive so it seems
// cheaper to just let `endAuction()` fail and react to the error.
Expand All @@ -179,7 +179,7 @@ contract ListOnZoraProposal is ZoraHelpers {
// settlement by seeing if we now possess the NFT.
if (token.safeOwnerOf(tokenId) == address(this)) {
emit ZoraAuctionFailed(auctionId);
return false;
return ZoraAuctionStatus.Cancelled;
}
} catch (bytes memory errData) {
bytes32 errHash = keccak256(errData);
Expand All @@ -191,7 +191,7 @@ contract ListOnZoraProposal is ZoraHelpers {
}
ZORA.cancelAuction(auctionId);
emit ZoraAuctionExpired(auctionId, minExpiry);
return false;
return ZoraAuctionStatus.Expired;
} else if (errHash != AUCTION_DOESNT_EXIST_ERROR_HASH) {
// Otherwise, we should get an auction doesn't exist error,
// because someone else must have called `endAuction()`.
Expand All @@ -201,6 +201,6 @@ contract ListOnZoraProposal is ZoraHelpers {
// Already ended by someone else. Nothing to do.
}
emit ZoraAuctionSold(auctionId);
return true;
return ZoraAuctionStatus.Sold;
}
}
8 changes: 7 additions & 1 deletion contracts/proposals/ZoraHelpers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ abstract contract ZoraHelpers {
uint40 minExpiry;
}

enum ZoraAuctionStatus {
Sold,
Expired,
Cancelled
}

// Transfer and create a Zora auction for the token + tokenId.
function _createZoraAuction(
// The minimum bid.
Expand All @@ -39,5 +45,5 @@ abstract contract ZoraHelpers {
)
internal
virtual
returns (bool sold);
returns (ZoraAuctionStatus statusCode);
}
63 changes: 63 additions & 0 deletions sol-tests/proposals/ListOnOpenseaProposalForked.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ contract ListOnOpenseaProposalForkedTest is
);
event ZoraAuctionExpired(uint256 auctionId, uint256 expiry);
event ZoraAuctionSold(uint256 auctionId);
event ZoraAuctionFailed(uint256 auctionId);

uint256 constant ZORA_AUCTION_DURATION = 0.5 days;
uint256 constant ZORA_AUCTION_TIMEOUT = 1 days;
Expand Down Expand Up @@ -644,6 +645,68 @@ contract ListOnOpenseaProposalForkedTest is
assertEq(address(impl).balance, LIST_PRICE);
}

// Test a proposal where the zora listing is cancelled.
function testForked_Execution_BoughtOnZora_Cancelled() public onlyForked {
// We will cancel the auction because the buyer cannot receive the NFT.
address buyer = address(this);
uint256 listPrice = 1e18;
uint40 listDuration = 7 days;
(IERC721 token, uint256 tokenId) = _randomPreciousToken();
(
,
IProposalExecutionEngine.ExecuteProposalParams memory executeParams
) = _createTestProposal(
token,
tokenId,
listPrice,
listDuration,
new uint256[](0),
new address payable[](0)
);
// This will list on zora because the proposal was not passed unanimously.
uint256 auctionId = _getNextZoraAuctionId();
vm.expectEmit(false, false, false, true);
emit ZoraAuctionCreated(
auctionId,
token,
tokenId,
listPrice,
uint40(ZORA_AUCTION_DURATION),
uint40(block.timestamp) + uint40(ZORA_AUCTION_TIMEOUT)
);
executeParams.progressData = impl.executeListOnOpensea(executeParams);
{
(, ZoraHelpers.ZoraProgressData memory progressData) =
abi.decode(executeParams.progressData, (
ListOnOpenseaProposal.ListOnOpenseaStep,
ZoraHelpers.ZoraProgressData
));
assertEq(progressData.auctionId, auctionId);
}
// Try to advance the proposal before the zora auction has timed out (fail).
skip(ZORA_AUCTION_TIMEOUT- 1);
vm.expectRevert(abi.encodeWithSelector(
ListOnZoraProposal.ZoraListingNotExpired.selector,
auctionId,
block.timestamp + 1
));
impl.executeListOnOpensea(executeParams);

// Bid on the zora auction.
_bidOnZoraListing(auctionId, buyer, listPrice);
// The auction will be now extended by ZORA_AUCTION_DURATION.

// Skip past the end of the auction.
skip(ZORA_AUCTION_DURATION);
// Advance the proposal, finalizing the zora auction.
vm.expectEmit(false, false, false, true);
emit ZoraAuctionFailed(auctionId);
executeParams.progressData = impl.executeListOnOpensea(executeParams);
// Listing cancelled because the buyer could not receive the NFT. The
// proposal should be done.
assertEq(executeParams.progressData.length, 0);
}

// Test a proposal where the zora listing is bought and finalized externally.
function testForked_Execution_BoughtOnZora_settledExternally() public onlyForked {
address buyer = _randomAddress();
Expand Down

0 comments on commit 7a76a2b

Please sign in to comment.