Skip to content

Commit

Permalink
Sequential commit (#569)
Browse files Browse the repository at this point in the history
* 1st iteration

* buy order

* fulfilSellOrder

* releaseFundsToIntermediateSellers

* Bump eslint-config-prettier from 8.5.0 to 8.6.0 (#547)

Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 8.5.0 to 8.6.0.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md)
- [Commits](prettier/eslint-config-prettier@v8.5.0...v8.6.0)

---
updated-dependencies:
- dependency-name: eslint-config-prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Protocol Initalization Handler Facet documentation (#538)

* Initalizer Facet documentation

* Apply suggestions from code review

Co-authored-by: Ana Julia Bittencourt  <anajuliabit@gmail.com>

* Expand initialization description

* Review fixes

* upgrade configs

* Update docs/tasks.md

Co-authored-by: Ana Julia Bittencourt  <anajuliabit@gmail.com>

Co-authored-by: Ana Julia Bittencourt  <anajuliabit@gmail.com>

* chore: add forwarder address in deploy suite script

* chore: add forwarder address in .env.example

* Rename operator to assistant (#556)

* operator->assistant

* remove console.log

* diagram update

* Revert assistant back to operator for ERC721/ERC1155

* PriceDIscovery domain test

* sequential commits test, first batch

* revert reasons

* escrow amount

* Return overpaid to buyer

* sell order

* releaseFunds - COMPLETED

* releaseFunds - REVOKED

* releaseFunds - CANCELED

* releaseFunds - RETRACTED

* releaseFunds - DISPUTED - RESOLVED

* releaseFunds - ESCALATED

* releaseFunds - REFUSED

* changing fee + royalties

* Refactor

* refactor into separate facet

* sequentialCommitToOffer tests

* verify incoming voucher

* Fix failing tests

* Protocol Diamond diagram

* review fixes

* additional tests

* remove RoyaltyRecipient + fix typos

* Fix typo SequentialCommitHandler.sol

* New chunks

* correctly toggle preprocessing

* SequentialCommitHandlerFacet constructor arg in deployment

* fix unit tests

* test isEligibleToCommit

* review fixes

* add events for encumbering/releasing funds

* add tests for events for encumbering/releasing funds

* use safeerc20 for approval

* extend test timeout

* Bump code coverage

* refactor + disable viaIR compilation

* Price discovery (#578)

* AMM integration tests (WIP)

* Make hardhat task work with submodules which uses foundry

* Update hardhat config

* Fix diamond deploy

* Renaming folders

* Continue AMM POC

* Add price type to offer struct and continue sudoswap poc

* Fix interface ids

* Moving lssvm submodule

* Make boson voucher compatible with price discovery offers

* Continue POC work

* Finalize AMM POC

* Finalize sudoswap POC

* Seaport: Create offer with criteria (WIP)

* Seaport: advance order with criteria

* Move test files

* Fix sudoswap tests

* Fix seaport tests

* Make commitToOffer and commitToPriceDiscoveryOffer a single method

* Price discovery POC: auction

* Implement new voucher transfer and protocol communication design (WIP)

* Removing logs

* Fixing compile

* Finish new voucher transfer and protocol communication design

* SneakAuction (WIP)

* Supports bid commits that requires pre-holding the NFT

* Token id is mandatory on bid commits

* Continue work on PD

* Fix submodules hardhat compilation

* Finish refactor

* Remove SneakyAuction test and add Zora test

* Offer can't be price discovery and be transfer externally at the sime time

* Add new validations to fulfilBidOrder

* Decoupling bid function

* Add natspec comments

* Fix set committed

* Split commitToOffer and commitToPreMintedOffer

* Review changes

* Apply more review changes

* Owner must be priceDiscoveryContract when caller is not voucher owner

* Add some others verifications

* Fix Zora integration test without wrapper

* Fix stack too deep issue by enabling Yul via iR

* Split commitToPriceDiscovery in a new PriceDiscoveryFacet

* Add weth to protocol protocol addresses storage

* Fix interface ids

* Creating SudoswapWrraper

* Update SudoswapWrapper contract to allow wrapping multiple tokens at once and modify integration test accordingly

* Update SudoswapWrapper contract to allow wrapping by sender

* Continue SudoswapWrapper

* Finalise sudoswap wrapper poc

* Adding missing natspec comments

* Upgrade hardhat config and minors changes

* Fix pre-minted voucher transfer to check for offer price type and transaction origin

* update integration tests

* update inegration test - sudoswap

* Extend priceDiscovery struct

* deprecate commitToPremintedOffer

* Fix fundsHandler tests

* Fix metaTransactionsHandler tests

* Fix Offer domain script

* Fix sequentialCommitHandler tests

* review fixes 1st iter

* Correct resellerId in initial exchangeCosts for price discovery

* review fixes 2nd iter

* More minore fixes in tests/contracts

* PriceDiscoveryHandler unit tests

* onPremintedVoucherTransferred test + other minor fixes

* Zora wrapper (#598)

* initial wrapper

* Zora auction PoC

* code cleanup, add comments

* additional auction tests

* Review fixes

* Remove ownable

* review fixes

* wrapper tests

* custom mock auction

* handling wrapper another type of "Side"

* Improve wrapper handling

---------

Co-authored-by: zajck <klemen@impressieve.com>
Co-authored-by: Klemen <64400885+zajck@users.noreply.github.com>

* New chunks + bump line coverage

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ana Julia Bittencourt <anajuliabit@gmail.com>
Co-authored-by: Albert Folch <albertfolch@redeemeum.com>
  • Loading branch information
4 people authored Nov 21, 2023
1 parent 9cc931b commit d331983
Show file tree
Hide file tree
Showing 82 changed files with 10,640 additions and 434 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "submodules/seaport"]
path = submodules/seaport
url = git@github.com:ProjectOpenSea/seaport.git
[submodule "submodules/lssvm"]
path = submodules/lssvm
url = https://github.com/sudoswap/lssvm
20 changes: 20 additions & 0 deletions contracts/domain/BosonConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ string constant EXCHANGE_IS_NOT_IN_A_FINAL_STATE = "Exchange is not in a final s
string constant EXCHANGE_ALREADY_EXISTS = "Exchange already exists";
string constant INVALID_RANGE_LENGTH = "Range length is too large or zero";

// Revert Reasons: Sequential commit related
string constant UNEXPECTED_ERC721_RECEIVED = "Unexpected ERC721 received";
string constant FEE_AMOUNT_TOO_HIGH = "Fee amount is too high";
string constant VOUCHER_NOT_RECEIVED = "Voucher not received";
string constant NEGATIVE_PRICE_NOT_ALLOWED = "Negative price not allowed";

// Revert Reasons: Twin related
uint256 constant SINGLE_TWIN_RESERVED_GAS = 160000;
uint256 constant MINIMAL_RESIDUAL_GAS = 230000;
Expand Down Expand Up @@ -157,6 +163,7 @@ string constant TOKEN_TRANSFER_FAILED = "Token transfer failed";
string constant INSUFFICIENT_VALUE_RECEIVED = "Insufficient value received";
string constant INSUFFICIENT_AVAILABLE_FUNDS = "Insufficient available funds";
string constant NATIVE_NOT_ALLOWED = "Transfer of native currency not allowed";
string constant ZERO_DEPOSIT_NOT_ALLOWED = "Zero deposit not allowed";

// Revert Reasons: Meta-Transactions related
string constant NONCE_USED_ALREADY = "Nonce used already";
Expand Down Expand Up @@ -250,3 +257,16 @@ string constant RETRACT_DISPUTE = "retractDispute(uint256)";
string constant RAISE_DISPUTE = "raiseDispute(uint256)";
string constant ESCALATE_DISPUTE = "escalateDispute(uint256)";
string constant RESOLVE_DISPUTE = "resolveDispute(uint256,uint256,bytes32,bytes32,uint8)";

// Price discovery related
string constant PRICE_TOO_HIGH = "Price discovery returned a price that is too high";
string constant PRICE_TOO_LOW = "Price discovery returned a price that is too low";
string constant TOKEN_ID_MANDATORY = "Token id is mandatory for bid orders";
string constant TOKEN_ID_MISMATCH = "Token id mismatch";
string constant INVALID_PRICE_TYPE = "Invalid price type";
string constant INVALID_PRICE_DISCOVERY = "Invalid price discovery argument";
string constant INCOMING_VOUCHER_ALREADY_SET = "Incoming voucher already set";
string constant NEW_OWNER_AND_BUYER_MUST_MATCH = "New owner and buyer must match";
string constant PRICE_DISCOVERY_CONTRACTS_NOT_SET = "PriceDiscoveryContract and Conduit must be set";
string constant TOKEN_ID_NOT_SET = "Token id not set";
string constant VOUCHER_TRANSFER_NOT_ALLOWED = "Voucher transfer not allowed";
27 changes: 27 additions & 0 deletions contracts/domain/BosonTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ contract BosonTypes {
Clerk // Deprecated.
}

enum PriceType {
Static, // Default should always be at index 0. Never change this value.
Discovery
}

struct AuthToken {
uint256 tokenId;
AuthTokenType tokenType;
Expand Down Expand Up @@ -152,6 +157,7 @@ contract BosonTypes {
string metadataHash;
bool voided;
uint256 collectionIndex;
PriceType priceType;
}

struct OfferDates {
Expand Down Expand Up @@ -192,6 +198,13 @@ contract BosonTypes {
ExchangeState state;
}

struct ExchangeCosts {
uint256 resellerId;
uint256 price;
uint256 protocolFeeAmount;
uint256 royaltyAmount;
}

struct Voucher {
uint256 committedDate;
uint256 validUntilDate;
Expand Down Expand Up @@ -300,4 +313,18 @@ contract BosonTypes {
address collectionAddress;
string externalId;
}

struct PriceDiscovery {
uint256 price;
Side side;
address priceDiscoveryContract;
address conduit;
bytes priceDiscoveryData;
}

enum Side {
Ask,
Bid,
Wrapper // Side is not relevant from the protocol perspective
}
}
2 changes: 1 addition & 1 deletion contracts/example/SnapshotGate/SnapshotGate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IBosonOfferHandler } from "../../interfaces/handlers/IBosonOfferHandler
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { BosonTypes } from "../../domain/BosonTypes.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC721 } from "./support/ERC721.sol";
import { ERC721 } from "./../support/ERC721.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
Expand Down
257 changes: 257 additions & 0 deletions contracts/example/Sudoswap/SudoswapWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.9;
import { IBosonExchangeHandler } from "../../interfaces/handlers/IBosonExchangeHandler.sol";
import { IBosonOfferHandler } from "../../interfaces/handlers/IBosonOfferHandler.sol";
import { DAIAliases as DAI } from "../../interfaces/DAIAliases.sol";
import { BosonTypes } from "../../domain/BosonTypes.sol";
import { ERC721 } from "./../support/ERC721.sol";
import { IERC721Metadata } from "./../support/IERC721Metadata.sol";
import { IERC165 } from "../../interfaces/IERC165.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IPool {
function swapTokenForSpecificNFTs(
uint256[] calldata nftIds,
uint256 maxExpectedTokenInput,
address nftRecipient,
bool isRouter,
address routerCaller
) external payable returns (uint256 inputAmount);
}

/**
* @title SudoswapWrapper
* @notice Wraps Boson Vouchers so they can be used with Sudoswap.
*
* Features:
* - Wraps vouchers into ERC721 tokens that can be used with Sudoswap.
* - Tracks the price agreed in Sudoswap.
* - Allows to unwrap the voucher and sends funds to the protocol.
* - Owner of wrapped voucher has the right to receive the true corresponding Boson voucher
*
* Out-of-band setup:
* - Create a seller in Boson Protocol and get the Boson Voucher address.
* - Deploy a SudoswapWrapper contract and pass the Boson Voucher address.
* - Approve SudoswapWrapper to transfer Boson Vouchers on behalf of the seller.
*
* Usage:
* - Seller wraps a voucher by calling `wrap` function.
* - Seller calls Sudoswap method `createAuction` with the wrapped voucher address.
* - Auction proceeds normally and either finishes with `endAuction` or `cancelAuction`.
* - If auction finishes with `endAuction`:
* - Bidder gets wrapped voucher and this contract gets the price.
* - `unwrap` must be executed via the Boson Protocol `commitToOffer` method.
* - If auction finishes with `cancelAuction`:
* - This contract gets wrapped voucher back and the bidder gets the price.
* - `unwrap` can be executed by the owner of the wrapped voucher.
*
* N.B. Although Sudoswap can send ethers, it's preffered to receive
* WETH instead. For that reason `receive` is not implemented, so it automatically sends WETH.
*/
contract SudoswapWrapper is BosonTypes, Ownable, ERC721 {
// Add safeTransferFrom to IERC20
using SafeERC20 for IERC20;

// Contract addresses
address private immutable voucherAddress;
address private poolAddress;
address private immutable factoryAddress;
address private immutable protocolAddress;
address private immutable wethAddress;

// 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;

/**
* @notice Constructor
*
* @param _voucherAddress The address of the voucher that are wrapped by this contract.
* @param _factoryAddress The address of the Sudoswap factory.
* @param _protocolAddress The address of the Boson Protocol.
* @param _wethAddress The address of the WETH token.
*/
constructor(
address _voucherAddress,
address _factoryAddress,
address _protocolAddress,
address _wethAddress
) ERC721(getVoucherName(_voucherAddress), getVoucherSymbol(_voucherAddress)) {
voucherAddress = _voucherAddress;
factoryAddress = _factoryAddress;
protocolAddress = _protocolAddress;
wethAddress = _wethAddress;

// Approve pool to transfer wrapped vouchers
_setApprovalForAll(address(this), _factoryAddress, 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 vouchers, transfer true vouchers to this contract and mint wrapped vouchers
*
* Reverts if:
* - caller is not the contract owner
*
* @param _tokenIds The token ids.
*/
function wrap(uint256[] memory _tokenIds) external onlyOwner {
for (uint256 i = 0; i < _tokenIds.length; i++) {
uint256 tokenId = _tokenIds[i];

// Transfer vouchers 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 caller, so it can be used with Sudoswap
_mint(msg.sender, tokenId);
}
}

/**
* @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);

// Either contract owner or protocol can unwrap
// If contract owner is unwrapping, this is equivalent to removing the voucher from the pool
require(
msg.sender == protocolAddress || wrappedVoucherOwner == msg.sender,
"SudoswapWrapper: Only owner or protocol can unwrap"
);

uint256 priceToPay = price[_tokenId];

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

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

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

delete cachedExchangeToken[_tokenId]; // gas refund

// Burn wrapped voucher
_burn(_tokenId);
}

/**
* @notice Set the pool address
*
* @param _poolAddress The pool address
*/
function setPoolAddress(address _poolAddress) external onlyOwner {
poolAddress = _poolAddress;
}

/**
* @notice swap token for specific NFT
*
* @param _tokenId - the token id
* @param _maxPrice - the max price
*/
function swapTokenForSpecificNFT(uint256 _tokenId, uint256 _maxPrice) external {
(address exchangeToken, uint256 balanceBefore) = getCurrentBalance(_tokenId);

uint256[] memory tokenIds = new uint256[](1);
tokenIds[0] = _tokenId;

IERC20(exchangeToken).safeTransferFrom(msg.sender, address(this), _maxPrice);
IERC20(exchangeToken).forceApprove(poolAddress, _maxPrice);

IPool(poolAddress).swapTokenForSpecificNFTs(tokenIds, _maxPrice, msg.sender, false, address(0));

(, uint256 balanceAfter) = getCurrentBalance(_tokenId);

uint256 actualPrice = balanceAfter - balanceBefore;
require(actualPrice <= _maxPrice, "SudoswapWrapper: Price too high");

price[_tokenId] = actualPrice;
}

/**
* @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 (address exchangeToken, uint256 balance) {
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;
}

balance = 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);
}
}
Loading

0 comments on commit d331983

Please sign in to comment.