iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0
Web 3

Web3 X 公共財系列 第 27

Day 27 - PCO -1

  • 分享至 

  • xImage
  •  

Partial Common Ownership by another name: SALSA (Self-Assessed Licenses Sold at Auction), COST (Common Ownership Self-assessed Tax), Harberger taxes, or depreciating licenses.

721labs PCO token standards

從ERC721+ lease(ILease + Taxation(ITaxation, Valuation, ERC721, Remittance, Beneficiary))來
以下探討各Interface為主

Ilease

interface ILease {
  /// @notice 接管某個代幣的租約。當前擁有者會根據當前估值收到款項,且訊息中包含的所有超額值都會被加到押金中。
  /// @param tokenId_ 買家希望購買的代幣ID。
  /// @param newValuation_ 買家對代幣的新估值。必須大於或等於當前估值。
  /// @param currentValuation_ 為了防止前置攻擊,必須提供當前的估值。
  /// 買家只會在同意的估值下完成接管。這可以防止惡意的第二個買家在第一筆交易完成之前購買代幣,更改估值,
  /// 並吞噬第一個買家的押金。
  function takeoverLease(
    uint256 tokenId_,
    uint256 newValuation_,
    uint256 currentValuation_
  ) external payable;

  /// @notice 允許擁有者自我評估代幣的價值。
  /// @param tokenId_ 代幣ID。
  /// @param newValuation_ 以Wei為單位的新估值。
  function selfAssess(uint256 tokenId_, uint256 newValuation_) external;
}

ITaxation

interface ITaxation {
  //////////////////////////////
  /// 外部方法
  //////////////////////////////

  /// 允許擁有者提取他們存款的一部分。
  function withdrawDeposit(uint256 tokenId_, uint256 wei_) external;

  ///  允許擁有者提取他們的全部存款。
  function exit(uint256 tokenId_) external;

  /// 以`msg.value` Wei增加擁有者的存款。
  function deposit(uint256 tokenId_) external payable;

  //////////////////////////////
  /// 稅收查詢方法
  //////////////////////////////

  ///返回自代幣上次轉移以來收集的稅金數量。
  function taxCollectedSinceLastTransferOf(uint256 tokenId_)
    external
    view
    returns (uint256);

  ///獲取給定代幣的稅率
  function taxRateOf(uint256 tokenId_) external view returns (uint256);

  ///獲取給定代幣的稅收周期
  function collectionFrequencyOf(uint256 tokenId_)
    external
    view
    returns (uint256);

  /// 定現在和過去某一時間之間累積的應稅金額。
  /// @param tokenId_ 要查詢的代幣ID。
  /// @param time_ Unix時間戳。
  /// @return taxDue 以Wei為單位的應稅金額。
  function taxOwedSince(uint256 tokenId_, uint256 time_)
    external
    view
    returns (uint256 taxDue);

  /// 公開方法查詢應稅金額。返回當前時間。用於計算預期的稅收義務。
  /// @param tokenId_ 要查詢的代幣ID。
  /// @return amount 以Wei為單位的應稅金額。
  /// @return timestamp 現在的Unix時間戳。
  function taxOwed(uint256 tokenId_)
    external
    view
    returns (uint256 amount, uint256 timestamp);

  /// 最後一次收稅時間的查詢方法。
  /// @param tokenId_ 要查詢的代幣ID。
  /// @return Unix時間戳。
  function lastCollectionTimeOf(uint256 tokenId_)
    external
    view
    returns (uint256);

  //////////////////////////////
  /// 存款查詢方法
  //////////////////////////////

  /// 獲取給定代幣ID的當前存款。
  function depositOf(uint256 tokenId_) external view returns (uint256);

  /// 可提取的存款金額,即任何存款金額大於應稅金額的部分。
  function withdrawableDeposit(uint256 tokenId_)
    external
    view
    returns (uint256);

  //////////////////////////////
  /// 拍賣查詢
  //////////////////////////////

  /// 應稅金額是否超過存款?如果是,代幣應該被合約“拍賣”。估值應為零,任何人都可以以瓦斯費用接管代幣的租約。
  /// @dev 當估值應為零時,這是一個有用的輔助函數,但合約還沒有反映出來,因為還沒有調用`#_forecloseIfNecessary`。@param tokenId_ 請求拍賣狀態的代幣ID。
  /// @return 返回一個布林值,表示合約是否已被拍賣。
  function foreclosed(uint256 tokenId_) external view returns (bool);

  /// 確定代幣擁有者直到拍賣還有多長時間。
  function foreclosureTime(uint256 tokenId_) external view returns (uint256);
}

Taxation.sol

重要功能

function collectTax(uint256 tokenId_) public {
    uint256 valuation = valuationOf(tokenId_);

    // There's no tax to be collected on an unvalued token.
    if (valuation == 0) return;

    // If valuation > 0, contract has not foreclosed.
    uint256 owed = _taxOwed(tokenId_);

    // Owed will be 0 when the token is owned by its beneficiary.
    // i.e. no tax is owed.
    if (owed == 0) return;

    // If foreclosure should have occured in the past, last collection time will be
    // backdated to when the tax was last paid for.
    if (foreclosed(tokenId_)) {
      _setLastCollectionTime(tokenId_, _backdatedForeclosureTime(tokenId_));
      // Set remaining deposit to be collected.
      owed = depositOf(tokenId_);
    } else {
      _setLastCollectionTime(tokenId_, block.timestamp);
    }

    // Normal collection
    _setDeposit(tokenId_, depositOf(tokenId_) - owed);
    taxationCollected[tokenId_] += owed;
    _setTaxCollectedSinceLastTransfer(
      tokenId_,
      taxCollectedSinceLastTransferOf(tokenId_) + owed
    );

    emit LogCollection(tokenId_, owed);

    /// Remit taxation to beneficiary.
    _remit(beneficiaryOf(tokenId_), owed, RemittanceTriggers.TaxCollection);

    _forecloseIfNecessary(tokenId_);
  }

其他

interface IRemittance {
  /// @notice 允許先前的擁有者提取未成功發送的匯款。
  /// @dev 為了降低複雜性,提取的資金與當前的押金完全分開。
  function withdrawOutstandingRemittance() external;
}
interface IValuation {
  /// @notice 返回代幣的自我評估估值。
  /// @param tokenId_ 代幣ID。
  /// @return 以Wei為單位的估值。如果代幣沒有設定估值,則返回0。
  function valuationOf(uint256 tokenId_) external view returns (uint256);
}

interface IBeneficiary {
  /// @notice 為給定的代幣設定受益人。
  /// @dev 只應由受益人呼叫。
  /// @param tokenId_ 要設定受益人的代幣。
  /// @param beneficiary_ 受益人的地址。
  function setBeneficiary(uint256 tokenId_, address payable beneficiary_)
    external;

  /// @notice 獲取給定代幣的受益人
  /// @param tokenId_ 要查詢的代幣ID。
  /// @return 受益人地址
  function beneficiaryOf(uint256 tokenId_) external view returns (address);
}

PartialCommonOwnership.sol

僅有兩個內部function: _mint() and _burn()整理上述

 /// @notice Mints a new token.
  /// @param tokenId_ Token's ID.
  /// @param leasee_ Token's leasee.
  /// @param deposit_ Token's deposit.
  /// @param valuation_ Leasee's self assessed valuation of the token.
  /// @param beneficiary_ Beneficiary of the token's taxation.
  /// @param taxRate_ Tax rate (numerator).
  /// @param collectionFrequency_ Tax collection frequency.
  function _mint(
    uint256 tokenId_,
    address leasee_,
    uint256 deposit_,
    uint256 valuation_,
    address payable beneficiary_,
    uint256 taxRate_,
    uint256 collectionFrequency_
  ) internal {
    _safeMint(leasee_, tokenId_);
    _setDeposit(tokenId_, deposit_);
    _setValuation(tokenId_, valuation_);
    _setBeneficiary(tokenId_, beneficiary_);
    _setTaxRate(tokenId_, taxRate_);
    _setCollectionFrequency(tokenId_, collectionFrequency_);
  }

  /// @notice Burns a token.
  /// @param tokenId_ ID of token to burn.
  function _burn(uint256 tokenId_) internal override _collectTax(tokenId_) {
    // Return the current owner's deposit.
    _withdrawDeposit(tokenId_, depositOf(tokenId_));

    // Burn token
    ERC721._burn(tokenId_);

    // Delete state
    delete _beneficiaries[tokenId_];
    delete _valuations[tokenId_];
    delete _taxNumerators[tokenId_];
    delete _collectionFrequencies[tokenId_];
    delete _locked[tokenId_];
  }
}

Wrapper.sol

最終在這裡實現,兩個重要功能


  /// 接管給定的代幣,創建一個符合部分共同所有權的"包裝"版本。新代幣將返回給擁有者。
  /// @dev 注意`#safeTransferFrom`首先要求合約地址被`msg.sender`批准。
  /// @param tokenContractAddress_ 發行代幣的合約地址。
  /// @param tokenId_ 要被包裝的代幣ID。
  /// @param valuation_ 代幣的自我評估估值。
  /// @param beneficiary_ 參見`PCO._beneficiaries`。
  /// @param taxRate_ 參見`PCO._taxNumerators`。
  /// @param collectionFrequency_ 參見`PCO._taxPeriods`。
  function wrap(
    address tokenContractAddress_,
    uint256 tokenId_,
    uint256 valuation_,
    address payable beneficiary_,
    uint256 taxRate_,
    uint256 collectionFrequency_
  ) public payable {

    require(valuation_ > 0, "估值必須大於0");
    require(beneficiary_ != address(0), "受益人地址不能為零");
    require(taxRate_ > 0, "稅率必須大於0");
    require(collectionFrequency_ > 0, "稅收頻率必須大於0");

    IERC721 tokenContract = IERC721(tokenContractAddress_);

    if (msg.sender == beneficiary_) {
      // 如果發送者是受益人,確保他們沒有發送存款
      require(msg.value == 0, "不需要存款");
    } else {
      require(msg.value > 0, "需要存款");
    }

    // 將代幣的所有權轉移給此合約。
    tokenContract.safeTransferFrom(msg.sender, address(this), tokenId_);

    uint256 _wrappedTokenId = wrappedTokenId(tokenContractAddress_, tokenId_);
    _wrappedTokenMap[_wrappedTokenId] = WrappedToken({
      contractAddress: tokenContractAddress_,
      tokenId: tokenId_,
      operatorAddress: msg.sender
    });

    _mint(
      _wrappedTokenId,
      msg.sender,
      msg.value,
      valuation_,
      beneficiary_,
      taxRate_,
      collectionFrequency_
    );

    emit LogTokenWrapped(tokenContractAddress_, tokenId_, _wrappedTokenId);
  }

  ///@notice 解除包裝給定的代幣。只能由最初包裝代幣的地址調用。銷毀包裝的代幣並將基礎代幣轉移到包裝代幣的最後一個擁有者。
  /// @param tokenId_ 包裝代幣的ID。
  function unwrap(uint256 tokenId_) public _tokenMinted(tokenId_) {
    WrappedToken memory token = _wrappedTokenMap[tokenId_];

    require(token.operatorAddress == msg.sender, "只有包裝的原始創建者可以操作");

    // 在燃燒之前獲取當前擁有者的地址。
    address owner = ownerOf(tokenId_);

    // 刪除包裝狀態
    delete _wrappedTokenMap[tokenId_];

    // 燃燒代幣
    _burn(tokenId_);

    // 將基礎代幣的所有權轉移到當前擁有者
    IERC721 tokenContract = IERC721(token.contractAddress);
    tokenContract.safeTransferFrom(address(this), owner, token.tokenId);
  }

  /// @notice 查詢包裝代幣的URI。
  /// @param tokenId_ 參見IERC721
  /// @return 代幣URI字符串。
  function tokenURI(uint256 tokenId_) public view returns (string memory) {
    require(
      _exists(tokenId_),
      "ERC721Metadata: 非存在代幣的URI查詢"
    );

    WrappedToken memory wrappedToken = _wrappedTokenMap[tokenId_];
    IERC721Metadata metadata = IERC721Metadata(wrappedToken.contractAddress);
    return metadata.tokenURI(wrappedToken.tokenId);
  }

深入研究可參考這邊

重點文章推薦

721labs協助的 https://partialcommonownership.com/


上一篇
Day 26 - RetroPFG / PCO
下一篇
Day 28 - PCO-2: the space / thisartworkisalwaysonsale / fxhash ticket
系列文
Web3 X 公共財30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言