Skip to content

Commit

Permalink
Merge branch 'main' into audit_v2_4_0_flb_02c
Browse files Browse the repository at this point in the history
  • Loading branch information
zajck authored Jan 9, 2024
2 parents dd990b0 + b13ec66 commit 5fec245
Show file tree
Hide file tree
Showing 20 changed files with 704 additions and 58 deletions.
4 changes: 4 additions & 0 deletions contracts/domain/BosonErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ interface BosonErrors {
error ExchangeAlreadyExists();
// Range length is 0, is more than quantity available or it would cause an overflow
error InvalidRangeLength();
// Exchange is being finalized into an invalid state
error InvalidTargeExchangeState();

// Twin related
// Twin does not exist
Expand Down Expand Up @@ -300,6 +302,8 @@ interface BosonErrors {
error InvalidDisputeTimeout();
// Absolute zero offers cannot be escalated
error EscalationNotAllowed();
// Dispute is being finalized into an invalid state
error InvalidTargeDisputeState();

// Config related
// Percentage exceeds 100%
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ interface IBosonSequentialCommitHandler is BosonErrors, IBosonExchangeEvents, IB
* - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer)
* - Received ERC20 token amount differs from the expected value
* - Protocol does not receive the voucher
* - Transfer of voucher to the buyer fails for some reasong (e.g. buyer is contract that doesn't accept voucher)
* - Transfer of voucher to the buyer fails for some reason (e.g. buyer is contract that doesn't accept voucher)
* - Reseller did not approve protocol to transfer exchange token in escrow
* - Call to price discovery contract fails
* - Protocol fee and royalties combined exceed the secondary price
Expand Down
40 changes: 40 additions & 0 deletions contracts/mock/BuyerContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

import { IERC721Receiver } from "../interfaces/IERC721Receiver.sol";

/**
* @title BuyerContract
*
* @notice Contract that acts as a buyer for testing purposes
*/
contract BuyerContract is IERC721Receiver {
enum FailType {
None,
Revert,
ReturnWrongSelector
}

FailType public failType;

/**
* @dev Set fail type
*/
function setFailType(FailType _failType) external {
failType = _failType;
}

/**
* @dev Return wrong selector to test revert
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4) {
if (failType == FailType.Revert) revert("BuyerContract: revert");
if (failType == FailType.ReturnWrongSelector) return 0x12345678;
return IERC721Receiver.onERC721Received.selector;
}
}
23 changes: 23 additions & 0 deletions contracts/mock/MockDisputeHandlerFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

import { DisputeHandlerFacet } from "../protocol/facets/DisputeHandlerFacet.sol";

/**
* @title TestDisputeHandlerFacet
*
* @notice Extended DisputeHandlerFacet with additional external functions for testing
*/
contract TestDisputeHandlerFacet is DisputeHandlerFacet {
/**
* @notice Test function to test invalid final dispute state
*
* @param _exchangeId - the id of the associated exchange
* @param _targetState - target final state
*/
function finalizeDispute(uint256 _exchangeId, DisputeState _targetState) external {
(, Exchange storage exchange) = fetchExchange(_exchangeId);
(, Dispute storage dispute, DisputeDates storage disputeDates) = fetchDispute(_exchangeId);
finalizeDispute(_exchangeId, exchange, dispute, disputeDates, _targetState, 1000);
}
}
22 changes: 22 additions & 0 deletions contracts/mock/MockExchangeHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { DisputeBase } from "../protocol/bases/DisputeBase.sol";
import { FundsLib } from "../protocol/libs/FundsLib.sol";
import "../domain/BosonConstants.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import { ExchangeHandlerFacet } from "../protocol/facets/ExchangeHandlerFacet.sol";

/**
* @title MockExchangeHandlerFacet
Expand Down Expand Up @@ -537,3 +538,24 @@ contract MockExchangeHandlerFacetWithDefect is MockExchangeHandlerFacet {
emit VoucherCanceled2(exchange.offerId, _exchangeId, msgSender());
}
}

/**
* @title TestExchangeHandlerFacet
*
* @notice Extended ExchangeHandlerFacet with additional external functions for testing
*/
contract TestExchangeHandlerFacet is ExchangeHandlerFacet {
//solhint-disable-next-line
constructor(uint256 _firstExchangeId2_2_0) ExchangeHandlerFacet(_firstExchangeId2_2_0) {}

/**
* @notice Test function to test invalid final exchange state
*
* @param _exchangeId - the id of the exchange to finalize
* @param _targetState - the target state to which the exchange should be transitioned
*/
function finalizeExchange(uint256 _exchangeId, ExchangeState _targetState) external {
(, Exchange storage exchange) = fetchExchange(_exchangeId);
finalizeExchange(exchange, _targetState);
}
}
239 changes: 239 additions & 0 deletions contracts/mock/MockWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.9;

import { IBosonOfferHandler } from "../interfaces/handlers/IBosonOfferHandler.sol";
import { IBosonExchangeHandler } from "../interfaces/handlers/IBosonExchangeHandler.sol";
import { BosonTypes } from "../domain/BosonTypes.sol";
import { ERC721 } from "../example/support/ERC721.sol";
import { IERC721Metadata } from "../example/support/IERC721Metadata.sol";
import { IERC721 } from "../interfaces/IERC721.sol";
import { IERC165 } from "../interfaces/IERC165.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC721Receiver } from "../interfaces/IERC721Receiver.sol";

/**
* @title MockWrapper
* @notice Wrapper contract used in tests
*
*/
contract MockWrapper is BosonTypes, ERC721, IERC721Receiver {
// Add safeTransferFrom to IERC20
using SafeERC20 for IERC20;

// Contract addresses
address private immutable voucherAddress;
address private immutable mockAuctionAddress;
address private immutable protocolAddress;
address private immutable wethAddress;

// Token ID for which the price is not yet known
uint256 private pendingTokenId;

// Mapping from token ID to price. If pendingTokenId == tokenId, this is not the final price.
mapping(uint256 => uint256) private price;

// Mapping to cache exchange token address, so costly call to the protocol is not needed every time.
mapping(uint256 => address) private cachedExchangeToken;

mapping(uint256 => address) private wrapper;

/**
* @notice Constructor
*
* @param _voucherAddress The address of the voucher that are wrapped by this contract.
* @param _mockAuctionAddress The address of Mock Auction.
*/
constructor(
address _voucherAddress,
address _mockAuctionAddress,
address _protocolAddress,
address _wethAddress
) ERC721(getVoucherName(_voucherAddress), getVoucherSymbol(_voucherAddress)) {
voucherAddress = _voucherAddress;
mockAuctionAddress = _mockAuctionAddress;
protocolAddress = _protocolAddress;
wethAddress = _wethAddress;

// Approve Mock Auction to transfer wrapped vouchers
_setApprovalForAll(address(this), _mockAuctionAddress, true);
}

/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*/
function supportsInterface(bytes4 _interfaceId) public view virtual override(ERC721) returns (bool) {
return (_interfaceId == type(IERC721).interfaceId || _interfaceId == type(IERC165).interfaceId);
}

/**
* @notice Wraps the voucher, transfer true voucher to itself and approves the contract owner to operate on it.
*
* Reverts if:
* - caller is not the contract owner
*
* @param _tokenId The token id.
*/
function wrap(uint256 _tokenId) external {
// Transfer voucher to this contract
// Instead of msg.sender it could be voucherAddress, if vouchers were preminted to contract itself
// Not using safeTransferFrom since this contract is the recipient and we are sure it can handle the vouchers
IERC721(voucherAddress).transferFrom(msg.sender, address(this), _tokenId);

// Mint to itself, so it can be used with Mock Auction
_mint(address(this), _tokenId); // why not sender instead of address(this)?

// Approves original token owner to operate on wrapped token
_approve(msg.sender, _tokenId);

wrapper[_tokenId] = msg.sender;
}

/**
* @notice Unwraps the voucher, transfer true voucher to owner and funds to the protocol.
*
* Reverts if:
* - caller is neither protocol nor voucher owner
*
* @param _tokenId The token id.
*/
function unwrap(uint256 _tokenId) external {
address wrappedVoucherOwner = ownerOf(_tokenId);
if (wrappedVoucherOwner == address(this)) wrappedVoucherOwner = wrapper[_tokenId];

// Either contract owner or protocol can unwrap
// If contract owner is unwrapping, this is equivalent to canceled auction
require(
msg.sender == protocolAddress || wrappedVoucherOwner == msg.sender,
"MockWrapper: Only owner or protocol can unwrap"
);

// If some token price is not know yet, update it now
if (pendingTokenId != 0) updatePendingTokenPrice();

uint256 priceToPay = price[_tokenId];

// Delete price and pendingTokenId to prevent reentrancy
delete price[_tokenId];
delete pendingTokenId;

// transfer voucher to voucher owner
IERC721(voucherAddress).safeTransferFrom(address(this), wrappedVoucherOwner, _tokenId);

// Transfer token to protocol
if (priceToPay > 0) {
IERC20(cachedExchangeToken[_tokenId]).safeTransfer(protocolAddress, priceToPay);
}

delete cachedExchangeToken[_tokenId]; // gas refund
delete wrapper[_tokenId];

// Burn wrapped voucher
_burn(_tokenId);

// Send funds to protocol (testing purposes)
payable(msg.sender).transfer(address(this).balance);
}

/**
* @notice Handle transfers out of Mock Auction.
*
* @param _from The address of the sender.
* @param _to The address of the recipient.
* @param _tokenId The token id.
*/
function _beforeTokenTransfer(address _from, address _to, uint256 _tokenId) internal virtual override(ERC721) {
if (_from == mockAuctionAddress && _to != address(this)) {
// Auction is over, and wrapped voucher is being transferred to voucher owner
// If recipient is address(this), it means the auction was canceled and price updating can be skipped

// If some token price is not know yet, update it now
if (pendingTokenId != 0) updatePendingTokenPrice();

// Store current balance and set the pending token id
price[_tokenId] = getCurrentBalance(_tokenId);
pendingTokenId = _tokenId;
}

super._beforeTokenTransfer(_from, _to, _tokenId);
}

function updatePendingTokenPrice() internal {
uint256 tokenId = pendingTokenId;
price[tokenId] = getCurrentBalance(tokenId) - price[tokenId];
}

/**
* @notice Gets own token balance for the exchange token, associated with the token ID.
*
* @dev If the exchange token is not known, it is fetched from the protocol and cached for future use.
*
* @param _tokenId The token id.
*/
function getCurrentBalance(uint256 _tokenId) internal returns (uint256) {
address exchangeToken = cachedExchangeToken[_tokenId];

// If exchange token is not known, get it from the protocol.
if (exchangeToken == address(0)) {
uint256 offerId = _tokenId >> 128; // OfferId is the first 128 bits of the token ID.

if (offerId == 0) {
// pre v2.2.0. Token does not have offerId, so we need to get it from the protocol.
// Get Boson exchange. Don't explicitly check if the exchange exists, since existance of the token implies it does.
uint256 exchangeId = _tokenId & type(uint128).max; // ExchangeId is the last 128 bits of the token ID.
(, BosonTypes.Exchange memory exchange, ) = IBosonExchangeHandler(protocolAddress).getExchange(
exchangeId
);
offerId = exchange.offerId;
}

// Get Boson offer. Don't explicitly check if the offer exists, since existance of the token implies it does.
(, BosonTypes.Offer memory offer, , , , ) = IBosonOfferHandler(protocolAddress).getOffer(offerId);
exchangeToken = offer.exchangeToken;

// If exchange token is 0, it means native token is used. In that case, use WETH.
if (exchangeToken == address(0)) exchangeToken = wethAddress;
cachedExchangeToken[_tokenId] = exchangeToken;
}

return IERC20(exchangeToken).balanceOf(address(this));
}

/**
* @notice Gets the Boson Voucher token name and adds "Wrapped" prefix.
*
* @dev Used only in the constructor.
*
* @param _voucherAddress Boson Voucher address
*/
function getVoucherName(address _voucherAddress) internal view returns (string memory) {
string memory name = IERC721Metadata(_voucherAddress).name();
return string.concat("Wrapped ", name);
}

/**
* @notice Gets the the Boson Voucher symbol and adds "W" prefix.
*
* @dev Used only in the constructor.
*
* @param _voucherAddress Boson Voucher address
*/
function getVoucherSymbol(address _voucherAddress) internal view returns (string memory) {
string memory symbol = IERC721Metadata(_voucherAddress).symbol();
return string.concat("W", symbol);
}

/**
* @dev See {IERC721Receiver-onERC721Received}.
*
* Always returns `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}

function topUp() external payable {}
}
Loading

0 comments on commit 5fec245

Please sign in to comment.