【主要內容】
今天嘗試解決交易NFT時的購買操作不能完成,在昨天已經找到根源問題在NFT資產授權處理上的基礎上,開始修改智能合約中關於函數修改器與授權相關的部分內容,共耗時32分鐘。
(此外整理作筆記花費了約36分鐘)
詳細學習過程見文末學習過程屏幕錄像。
【solidity0.7.0學習筆記(今天沒有新增內容)】
https://learnblockchain.cn/docs/solidity/layout-of-source-files.html
一、版本標識Pragmas
認真閱讀官方文檔,才明白過來,^符號在版本標識中意思 只是,在當前二級版本號範圍內有效。
如:
pragma solidity ^0.5.2;
這樣,源文件將既不允許低於 0.5.2 版本的編譯器編譯, 也不允許高於(包含) 0.6.0 版本的編譯器編譯(第二個條件因使用 ^ 被添加)。 這種做法的考慮是,編譯器在 0.6.0 版本之前不會有重大變更,所以可確保源代碼始終按預期被編譯。 上面例子中不固定編譯器的具體版本號,因此編譯器的補丁版也可以使用。
不是我之前理解 的那樣,只要版本高於0.5.2就可以,原來是有上限的(到0.6之下)
二、ABI增強測試功能標識 ABIEncoderV2
新的 ABI 編碼器可以用來編碼和解碼嵌套的數組和結構體,當然這部分代碼還在優化之中,他沒有像之前 ABI 編碼器 那樣經過嚴格的測試,我們可以使用下面的語法來啓用它 pragma experimental ABIEncoderV2; 。
三、文件引用 import
今天才發現solidity的文件引用其實非常的豐富,完全參照 了javascript語言,甚至我個人感覺與python語言也非常類似。
因爲缺乏實踐,所以簡單引用以下筆記:
“
Solidity 支持的導入語句來模塊化代碼,其語法跟 JavaScript(從 ES6 起)非常類似。 儘管 Solidity 不支持 default export 。
註解
ES6 即 ECMAScript 6.0,ES6是 JavaScript 語言的下一代標準,已經在 2015 年 6 月正式發佈。 ——譯者注
在全局層面上,可使用如下格式的導入語句:
import "filename";
此語句將從 “filename” 中**導入所有的全局符號到當前全局作用域**中(不同於 ES6,Solidity 是向後兼容的)。
這種形式已經不建議使用,因爲它會無法預測地污染當前命名空間。 如果在“filename”中添加新的符號,則會自動添加出現在所有導入 “filename” 的文件中。 更好的方式是明確導入的具體 符號。
向下面這樣,創建了新的 symbolName 全局符號,他的成員都來自與導入的 "filename" 文件中的全局符號,如:
import * as symbolName from "filename";
然後所有全局符號都以``symbolName.symbol``格式提供。 此語法的變體不屬於ES6,但可能有用:
import "filename" as symbolName;
它等價於 import * as symbolName from "filename";。
如果存在命名衝突,則可以在導入時重命名符號。例如,下面的代碼創建了新的全局符號 alias 和 symbol2 ,引用的 symbol1 和 symbol2 來自 “filename” 。
import {symbol1 as alias, symbol2} from "filename";
路徑
上文中的 filename 總是會按路徑來處理,以 / 作爲目錄分割符、以 . 標示當前目錄、以 .. 表示父目錄。 當 . 或 .. 後面跟隨的字符是 / 時,它們才能被當做當前目錄或父目錄。 只有路徑以當前目錄 . 或父目錄 .. 開頭時,才能被視爲相對路徑。
用 import "./filename" as symbolName; 語句導入當前源文件同目錄下的文件 filename 。 如果用 import "filename" as symbolName; 代替,可能會引入不同的(如在全局 include directory 中)文件。
最終導入哪個文件取決於編譯器(見下文 在實際的編譯器中使用)到底是怎樣解析路徑的。 通常,目錄層次不必嚴格映射到本地文件系統, 它也可以映射到能通過諸如 ipfs,http 或者 git 發現的資源。
註解
通常使用相對引用 import "./filename.sol"; 並且避免使用 .. ,後面這種方式可以使用全局路徑並設置映射
”
【修改了erc721base.sol文件中的一些函數修改器及授權操作的代碼】
自己嘗試去修改了erc721base.sol文件中的一些函數修改器與授權操作的代碼,思路還沒有理清楚,特別是在安全性限制上仍然一頭霧水。
現在的erc721base.sol文件的內容如下:
```
pragma solidity ^0.4.18;
import './SafeMath.sol'; //這是一個library
import './AssetRegistryStorage.sol'; //資產登記所需要的基類全約,其中聲明瞭各種需要登記到區塊中的(變量類型爲storage)mapping表變量
import './IERC721Base.sol'; //這兒聲明瞭erc721的interface
import './IERC721Receiver.sol'; //在這個文件中聲明瞭一個Interface,聲明瞭讓合約可以接收nft的函數onERC721Received
import './ERC165.sol'; //erc165的interface
//--這個合約也不是最子級的合約,雖然這個合約基本完善地完成了NFT資產所需要的各種方法
//FullAssetRegistry.SOL文件中的FullAssetRegistry合約還會繼承這個合約生成新的子合約
contract ERC721Base is AssetRegistryStorage, IERC721Base, ERC165 {
//作爲基類合約的IERC721Base和ERC165中聲明的只是一個interface,但仍然可以被當作基類合約使用。
using SafeMath for uint256; //這兒使用了庫 SafeMath.sol中的內容
// Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
bytes4 private constant ERC721_RECEIVED = 0x150b7a02; //ERC165接口需要的常量,表示 合約是否可以接收 NFT資產的標識
bytes4 private constant InterfaceId_ERC165 = 0x01ffc9a7; //ERC165需要的常量,表示合約是否支持erc165接口
/*
* 0x01ffc9a7 ===
* bytes4(keccak256('supportsInterface(bytes4)'))
*/
bytes4 private constant Old_InterfaceId_ERC721 = 0x7c0633c6;
bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; //ERC721需要的常量,表示合約是否支持erc721接口
/*
* 0x80ac58cd ===
* bytes4(keccak256('balanceOf(address)')) ^
* bytes4(keccak256('ownerOf(uint256)')) ^
* bytes4(keccak256('approve(address,uint256)')) ^
* bytes4(keccak256('getApproved(uint256)')) ^
* bytes4(keccak256('setApprovalForAll(address,bool)')) ^
* bytes4(keccak256('isApprovedForAll(address,address)')) ^
* bytes4(keccak256('transferFrom(address,address,uint256)')) ^
* bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^
* bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)'))
*/
//
// Global Getters
//
/**
* @dev Gets the total amount of assets stored by the contract 獲取當前合約管理着多少個NFT的總數量
* @return uint256 representing the total amount of assets
*/
function totalSupply() external view returns (uint256) {
return _totalSupply(); //通過調用下一個函數到獲取
}
function _totalSupply() internal view returns (uint256) {
return _count; //狀態變量(全局變量)_count是在sol文件AssetRegistryStorage.sol中聲明的
}
//注意到,這兒的每一個方法,都進行了安全隔離,對外,採用external關鍵字修飾,對內(包括對下級合約——也就是把當前合約當作父級合約的子合約)使用internal關鍵字。
/*
public與private
對於public和private,相信學過其他主流語言的人都能明白:
public修飾的變量和函數,任何用戶或者合約都能調用和訪問。
private修飾的變量和函數,只能在其所在的合約中調用和訪問,即使是其子合約也沒有權限訪問。
external和internal
除 public 和 private 屬性之外,Solidity 還使用了另外兩個描述函數可見性的修飾詞:internal(內部) 和 external(外部)。
internal 和 private 類似,不過, 如果某個合約繼承自其父合約,這個合約即可以訪問父合約中定義的“內部”函數。
external 與public 類似,只不過這些函數只能在合約之外調用 - 它們不能被合約內的其他函數調用。
internal、private、external、public這4種關鍵字都是可見性修飾符,互不共存(也就是說,同一時間只能使用這四個修飾字中的一個)
————————————————
版權聲明:本文爲CSDN博主「黃嘉成」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_33829547/java/article/details/80460013
*/
//
// Asset-centric getter functions 查詢有關NFT的所有者等相關信息的操作
//
/**
* @dev Queries what address owns an asset. This method does not throw. 傳入NFT的ID值:assetId,以查詢得到此NFT資產的擁有人是誰。
* In order to check if the asset exists, use the `exists` function or check if the
* return value of this call is `0`.
* @return uint256 the assetId
*/
function ownerOf(uint256 assetId) external view returns (address) {
return _ownerOf(assetId);
}
function _ownerOf(uint256 assetId) internal view returns (address) {
return _holderOf[assetId]; //狀態變量(全局變量)_holderOf是在sol文件AssetRegistryStorage.sol中聲明的
}
//
// Holder-centric getter functions
//
/**
* @dev Gets the balance of the specified address 查看一個節點(形參 owner指定)擁有總共多少個NFT
* @param owner address to query the balance of
* @return uint256 representing the amount owned by the passed address
*/
function balanceOf(address owner) external view returns (uint256) {
return _balanceOf(owner);
}
function _balanceOf(address owner) internal view returns (uint256) {
return _assetsOf[owner].length; //狀態變量(全局變量)_assetsOf是在sol文件AssetRegistryStorage.sol中聲明的
}
//
// Authorization getters 獲取當前資產授權狀態的函數方法
//
/**
* @dev Query whether an address has been authorized to move any assets on behalf of someone else 查詢 形參 assetHolder 節點 是否已經把 它的所有NFT資產都授權給了 形參 operator 節點,允許 形參 operator 節點 全權處理(包括移動)這些NFT資產。
* @param operator the address that might be authorized
* @param assetHolder the address that provided the authorization
* @return bool true if the operator has been authorized to move any assets
*/
function isApprovedForAll(address assetHolder, address operator)
external view returns (bool)
{
return _isApprovedForAll(assetHolder, operator);
}
function _isApprovedForAll(address assetHolder, address operator)
internal view returns (bool)
{
return _operators[assetHolder][operator]; //狀態變量(全局變量)_operators是在sol文件AssetRegistryStorage.sol中聲明的
}
/**
* @dev Query what address has been particularly authorized to move an asset 查詢指定ID的NFT資產(形參 assetId 指定) 當前 已經被 授權給哪個 節點 處理。
* @param assetId the asset to be queried for
* @return bool true if the asset has been approved by the holder
*/
function getApproved(uint256 assetId) external view returns (address) {
return _getApprovedAddress(assetId);
}
function getApprovedAddress(uint256 assetId) external view returns (address) {
return _getApprovedAddress(assetId);
}
function _getApprovedAddress(uint256 assetId) internal view returns (address) {
return _approval[assetId];
}
/**
* @dev Query if an operator can move an asset. 查詢 一個 節點(形參 operator 指定)是不是已經 被 授權處理 指定ID的NFT(形參 assetId)
* @param operator the address that might be authorized
* @param assetId the asset that has been `approved` for transfer
* @return bool true if the asset has been approved by the holder
*/
function isAuthorized(address operator, uint256 assetId) external view returns (bool) {
return _isAuthorized(operator, assetId);
}
function _isAuthorized(address operator, uint256 assetId) internal view returns (bool)
{
require(operator != 0); //檢查 節點地址是否爲空
address owner = _ownerOf(assetId); //得到指定ID的NFT本身是屬於哪個節點地址的
if (operator == owner) { //如果指定ID的NFT的歸屬節點就是當前 要查詢 的operator 那麼,就直接返回TRUE
return true;
}
return _isApprovedForAll(owner, operator) || _getApprovedAddress(assetId) == operator;
//雙豎線左邊是檢查節點owner是否已經授權節點operator處理它的全部NFT資產,雙豎線右邊是獲取指定id的assetId現在的授權處理節點地址是否等於Operator
//如果以上兩個條件二者之一符合,則會返回TRUE,否則 返回FALSE
}
//
// Authorization 執行授權等操作的函數方法區域
//
/**
* @dev Authorize a third party operator to manage (send) msg.sender's asset 當前調用合約的節點(msg.sender)調用此方法來向指定的節點(形參 operator 指定)授權允許其 操作自己的所有NFT資產,或撤消這個授權——是發起授權還是撤消授權由形參 authorized 的BOOL值決定
* @param operator address to be approved
* @param authorized bool set to true to authorize, false to withdraw authorization
*/
function setApprovalForAll(address operator, bool authorized) external {
return _setApprovalForAll(operator, authorized);
}
function _setApprovalForAll(address operator, bool authorized) internal {
if (authorized) { //如果是發起授權
require(!_isApprovedForAll(msg.sender, operator));
_addAuthorization(operator, msg.sender); //這個方法在本合約稍後定義的,直接操縱 映射表 _operators 來完成
} else { //如果是撤消授權
require(_isApprovedForAll(msg.sender, operator));
_clearAuthorization(operator, msg.sender); //這個方法在本合約稍後定義的,直接操縱 映射表 _operators 來完成
}
emit ApprovalForAll(msg.sender, operator, authorized); //廣播這個事件,事件的定義在sol文件IERC721Base.sol中聲明的
}
/**
* @dev Authorize a third party operator to manage one particular asset //這個函數是授權節點Operator可以操作調用合約節點(msg.sender)的指定ID的一個nft資產。
* @param operator address to be approved
* @param assetId asset to approve
*/
function approve(address operator, uint256 assetId) external {
address holder = _ownerOf(assetId); //獲取要操作的指定ID的NFT(這兒就是形參assetId)當前的所有者節點的地址
require(msg.sender == holder || _isApprovedForAll(holder,msg.sender)==true || _getApprovedAddress(assetId)==msg.sender);
//上一個語句,左邊是檢查當前合約調用節點是不是就是此ID的NFT資產的所有人,右邊檢查,當前調用合約的節點是不是已經把自己的所有NFT資產的操作權授權給了當前操作的指定ID的NFT資產的所有人(現在是變量holder表示)。
//第三個or語句檢測的是,如果當前調用者獲得了此單個NFT資產的授權
//只要兩者有其一是true ,語句將會繼續 執行,如果兩者都是false,則語句就不再繼續往下執行。
require(operator != holder); //如果要被授權的節點就是holder節點,那麼就沒有意義了,所以這兒要檢測一下。
//於是下一句話進行了進一步的檢查,如果要被授權的節點operator已經是指定id的Nft資產可操作者,那就不必要進行重複授權操作了。
if (_getApprovedAddress(assetId) != operator) {
_approval[assetId] = operator; //直接將映射表_approval中指定ID的NFT資產的被授權操作節點修改爲operator就可以了。
emit Approval(holder, operator, assetId); //廣播這個更改映射表的事件。
}
}
//--下面這個函數 是爲函數 _setApprovalForAll 準備的子函數
function _addAuthorization(address operator, address holder) private {
_operators[holder][operator] = true;
}
//--下面這個函數 是爲函數 _setApprovalForAll 準備的子函數
function _clearAuthorization(address operator, address holder) private {
_operators[holder][operator] = false;
}
//
// Internal Operations
//
//下面這個函數把指定ID的NFT資產(形參assetId指定)的 所有人 指定爲 to這個節點 (變更資產所有人)
function _addAssetTo(address to, uint256 assetId) internal {
_holderOf[assetId] = to; //在資產所有人的映射表中,變更 此資產的所有人爲to節點
uint256 length = _balanceOf(to); //記錄下to這個節點現在有多少個NFT資產
_assetsOf[to].push(assetId); //向映射表_assetsOf中的to節點下添加新的NFT資產的ID
_indexOfAsset[assetId] = length; //更改映射表 _indexOfAsset中指定的當前 ID的 NFT資產在to節點自己的資產登記表_assetOf中的Index值,現在發現,這個Index值 是從1開始的,而不是從零開始的。
_count = _count.add(1); //這兒沒有理解爲什麼_count需要增加1——現在知道 了,因爲 把一個指定 ID的nft資產從某個 節點中移除時,這個變量是減少了1的,所以重新 登記 時,這個變量就要加上1
}
//下面這個函數把指定 ID的NFT資產(形參assetId指定)從原 擁有者(形參from指定 的節點)中刪除掉,操作之後,這個NFT資產的歸屬節點地址 爲0
function _removeAssetFrom(address from, uint256 assetId) internal {
uint256 assetIndex = _indexOfAsset[assetId]; //獲取指定ID的NFT資產的在其擁有者的所有資產中的index值
uint256 lastAssetIndex = _balanceOf(from).sub(1); //查詢from節點名下有多少個nft資產,這兒減掉1的意思是使Index是從0開始計數的,這和上一個函數的理解是否有衝突
uint256 lastAssetId = _assetsOf[from][lastAssetIndex]; //獲取到指定節點from名下的最後一個nft資產的id值,index的問題,現在我的理解 是,從映射表 _indexOfAsset獲取 到的Index值都要減去1才能用於映射表_assetsOf
_holderOf[assetId] = 0; //將指定ID的NFT的歸屬節點地址設置爲空,意味着,這個NFT資產從原擁有者的名下被刪除掉了(它現在不再屬於任何一個節點)
// Insert the last asset into the position previously occupied by the asset to be removed
//將最後一個資產插入要移除的資產先前佔用的位置__把from節點所有的資產列表中_assetsOf[from]原本在最後一項的資產的ID值lastAssetId記錄到被刪除的那個 資產_assetsOf[from][assetIndex] 的位置那兒——也就是原Index爲assetIndex值的那個資產位置現在記錄的是原來 最後一個資產的ID
_assetsOf[from][assetIndex] = lastAssetId;
// Resize the array
_assetsOf[from][lastAssetIndex] = 0; //現在from節點的所有資產清單中的index爲最後一項資產記錄的資產ID值被設置 爲 0
_assetsOf[from].length--; //這兒居然直接將映射表_assetOf[from]的元素長度減少了一
// Remove the array if no more assets are owned to prevent pollution
if (_assetsOf[from].length == 0) { //如果from節點名下現在已經沒有任何NFT資產了,
delete _assetsOf[from]; //可以直接刪除映射表中指定的一項
}
// Update the index of positions for the asset
_indexOfAsset[assetId] = 0; //現在被從原擁有者名下 刪除掉的指定ID的NFT資產的index記錄被設置成了0
_indexOfAsset[lastAssetId] = assetIndex; //因爲from節點所擁有的全部NFT資產記錄清單中的最後一項資產的index值已經改變,所以要在映射表_indexOfAsset中也作修改。
_count = _count.sub(1); //這兒記錄下整個合約記錄的所有NFT資產的總數也減少了1(在重新把這個指定 ID的NFT資產登記 給另一個節點 的時候 ,COUNT值又會加上1)
}
//撤消對指定ID的NFT資產(形參assetId指定)可代爲操作權的授權聲明,是函數approve的逆過程,這個函數僅供本合約內部調用
function _clearApproval(address holder, uint256 assetId) internal {
if (_ownerOf(assetId) == holder && _approval[assetId] != 0) { //檢查Holder節點是不是此NFT資產的所有人,同時檢查 當前NFT資產量不是有必要更改授權
//不過我發現 ,這兒是直接 傳入 一個參數Holder作爲地址,意味着並沒有 檢查 當前調用 合約的節點msg.sender是不是holder這個節點 ,當然這個函數呢使用的關鍵字是:internal
//當前函數只能供本合約內部調用 ,那我的理解 是,這個函數是供別的公開函數調用 的子函數
_approval[assetId] = 0; //現在這個指定 ID的NFT資產被授權給節點地址 0 可以代爲操作,意味着之前的授權被撤消了
emit Approval(holder, 0, assetId); //廣播 授權的變更,只是這一次變更爲授權給地址爲0的節點
}
}
//
// Supply-altering functions
//
//增加一個方法來獲取assetId的下一個可用id序號
function _getnewfreeassetid() internal view returns (uint256){
uint256 rr = 1;
if(_assetsIds.length > 0){
uint256 c = _assetsIds.length;
for(uint256 i = 0;i < c;i++){
if(rr < _assetsIds[i]){
rr = _assetsIds[i];
}
}
rr = rr+1; //前面的循環只能取出 最大的一個ID,這兒要取得新 的ID ,所以得加上1
}
return rr;
}
//實現讓 節點beneficiary 獲取指定ID的NFT資產(assetId這個形參指明瞭是哪個NFT資產),本函數僅供本合約內部調用
function _generate(uint256 assetId, address beneficiary) internal {
require(_holderOf[assetId] == 0); //首先檢查一下,指定的NFT是否是 沒有擁有者 的狀態,如果是屬於沒有歸屬者的NFT纔可以執行後續操作。
_addAssetTo(beneficiary, assetId); //然後用上面定義過的本合約專用的內部函數 把這個NFT轉移給節點:beneficiary
bool isnewid = true;
if(_assetsIds.length > 0){
uint256 c = _assetsIds.length;
for(uint256 i = 0;i < c;i++){
if(assetId == _assetsIds[i]){
isnewid = false;
break;
}
}
}else{
isnewid = true;
}
if(isnewid == true){
_assetsIds.push(assetId);
}
emit Transfer(0, beneficiary, assetId); //廣播這一資產轉移 的事件,從節點地址爲0的節點轉移給beneficiary代表的節點
}
//
// Transaction related operations
//下面定義了六個函數修改器
//
//這個函數修改器用於限制當前調用合約的節點,要執行指定函數操作的節點必須是要操作的NFT資產的擁有節點
modifier onlyHolder(uint256 assetId) {
require(_ownerOf(assetId) == msg.sender);
_;
}
//這個函數修改器用於限制針對當前要操作的NFT資產,當前調用合約的節點是不是已經取得了對此NFT資產的操作權(這個節點被授權操作這個NFT了嗎?)
modifier onlyAuthorized(uint256 assetId) {
require(_isAuthorized(msg.sender, assetId)); //使用了前面定義的函數來檢查指定節點這兒是msg.sender是不是已經取得了這個NFT的操作權。
_;
}
//這個函數修改器的作用時,限制from節點是不是assetId對應的NFT資產的實際擁有者
modifier isCurrentOwner(address from, uint256 assetId) {
require(_ownerOf(assetId) == from);
_;
}
//新增的函數修改器
modifier isContractHadApprovedOrIsCurrentOwner(address from,uint256 assetId){
require(_ownerOf(assetId) == from || _isApprovedForAll(_ownerOf(assetId),from)==true || _getApprovedAddress(assetId)==from || _getApprovedAddress(assetId)==this || _isApprovedForAll(_ownerOf(assetId),this)==true);
_;
//第一個OR條件檢測的是:from是不是當前NFT資產的實際擁有節點
//第二個or條件檢測的是:from是不是已經獲得了當前NFT資產實際擁有節點對其全部資產的授權
//第三個or條件檢測的是:from是不是已經獲得了當前NFT資產的操作授權
//第四個or語句檢測的是,當前合約地址是否已經得到了此單個nft資產的授權
//第五個or語句檢測的是,當前合約地址是否已經得到了出售方所有NFT資產的授權
}
//這個函數修改器用於限制指定的節點destinatary是不是一個空地址(地址爲0)
modifier isDestinataryDefined(address destinatary) {
require(destinatary != 0); //只有在其地址不爲0的情況下,纔會繼續執行後面的語句
_;
}
//這個函數修改器的作用時,防止to節點就是指定ID的NFT資產(這兒由assetId指定)的擁有者
modifier destinataryIsNotHolder(uint256 assetId, address to) {
require(_ownerOf(assetId) != to); //只有這個Nft資產的擁有者節點不是to節點時,後面的代碼纔會執行
_;
}
//下面這個函數修改器是後面添加的,用以檢查當前調用合約 的節點是不是合約的部署節點(合約的擁有者節點)
modifier iscontractowner(){
require(msg.sender==addowner);
_;
}
//銷燬資產(針對資產的擁有者來說),應當定義另一個方法來讓合約擁有者可以恢復被擁有者自己刪除的NFT資產
//讓指定ID的NFT資產(由形參assetId指定)從現在的擁有在手中 脫離(轉移開),就是使其現擁有者不再擁有這個NFT資產,本函數僅供本合約內部調用
//只能由當前資產的擁有者節點(isCurrentOwner(msg.sender, assetId)函數修改器用以檢查)來執行指定ID的資產的銷燬----------------------------
function _destroy(uint256 assetId) isCurrentOwner(msg.sender, assetId) internal {
address holder = _holderOf[assetId]; //首先獲取指定NFT資產當前 的實際擁有人
require(holder != 0); //檢查此NFT資產當前 的實際擁有地址是否爲0
_removeAssetFrom(holder, assetId); //調用上面定義好的函數,從原擁有人者的資產列表中刪除掉這個NFT資產,這一步執行完成後,這個NFT資產的歸屬節點地址就是0
emit Transfer(holder, 0, assetId); //廣播這一資產轉移 的事件,從原節點地址轉移給節點地址爲0的節點
}
//合約的擁有者可以讓被銷燬的資產重新分配給另一個節點地址
function _recovery(address newadd,uint256 assetId) iscontractowner() internal {
address holder = _holderOf[assetId]; //首先獲取指定NFT資產當前 的實際擁有人
require(holder == 0); //檢查此NFT資產當前 的實際擁有地址是否爲0 ,是0說明者 一個之前被註銷過的資產
require(newadd != 0); //檢查要接收此資產的新地址是否爲0,不能是0
_addAssetTo(newadd, assetId); //將這個NFT資產登記到to這個節點名下
emit Transfer(holder, newadd, assetId); //廣播這次完整的資產轉移過程的事件
}
//下面的轉賬函數有點問題並不能完全保證,授權狀態下轉移 第三方節點的資產
//主要問題是:_moveToken函數使用函數修改器的問題,未經確認。
/**
* @dev Alias of `safeTransferFrom(from, to, assetId, '')`
* 這是安全進行資產轉移 的函數
* @param from address that currently owns an asset
* @param to address to receive the ownership of the asset
* @param assetId uint256 ID of the asset to be transferred
*/
//這個函數進行資產轉移 ,把assetId代表的NFT資產從節點from轉移給節點to,這個方法是隻能由外部調用合約者使用的方法
function safeTransferFrom(address from, address to, uint256 assetId) external {
return _doTransferFrom(from, to, assetId, '', true); //具體過程調用了下面緊接着定義的一個僅供本合約內部調用的函數
}
/**
* @dev Securely transfers the ownership of a given asset from one address to
* another address, calling the method `onNFTReceived` on the target address if
* there's code associated with it
* 這是上一個函數的另一個版本,支持添加額外交易數據
* @param from address that currently owns an asset
* @param to address to receive the ownership of the asset
* @param assetId uint256 ID of the asset to be transferred
* @param userData bytes arbitrary user information to attach to this transfer
*/
function safeTransferFrom(address from, address to, uint256 assetId, bytes userData) external {
return _doTransferFrom(from, to, assetId, userData, true);
}
/**
* @dev Transfers the ownership of a given asset from one address to another address
* Warning! This function does not attempt to verify that the target address can send
* tokens.
* 這是個常規的進行資產轉移 的函數(不過調用的是和上面兩個函數調用的相同的內部函數)
* @param from address sending the asset
* @param to address to receive the ownership of the asset
* @param assetId uint256 ID of the asset to be transferred
*/
function transferFrom(address from, address to, uint256 assetId) external {
return _doTransferFrom(from, to, assetId, '', false);
}
//這個僅供合約內部使用的函數纔是真正的實現NFT資產轉移 的函數過程
function _doTransferFrom(
address from,
address to,
uint256 assetId,
bytes userData,
bool doCheck
)
onlyAuthorized(assetId) //v添加了函數修改器,必須保證調用合約者msg.sender擁有對當下操作的NFT資產的操作權(被授權過)
internal
{
_moveToken(from, to, assetId, userData, doCheck); //進一步調用下級子函數來實現。
}
function _moveToken(
address from,
address to,
uint256 assetId,
bytes userData,
bool doCheck
)
isDestinataryDefined(to) //這兒添加了三個函數修改器,加強檢查,首先確保to節點的地址不爲0
destinataryIsNotHolder(assetId, to) //這兒確保to節點不是assetId代表的NFT資產的所有人
isContractHadApprovedOrIsCurrentOwner(from, assetId) //這兒確保from節點(而不是調用合約的msg.sender節點)是assetId代表的NFT資產的實際擁有人(現在明白過來,與上級函數的是否有權處理指定ID的資產的函數修改器不衝突)
private //這個函數是私有的,只能本合約調用,連子合約都不能調用
{
address holder = _holderOf[assetId]; //取得NFT資產原有的所有者節點
_clearApproval(holder, assetId); //清除這個nft資產相關的全部授權信息
_removeAssetFrom(holder, assetId); //從原所有者節點的資產表中刪除這個NFT資產
_addAssetTo(to, assetId); //將這個NFT資產登記到to這個節點名下
emit Transfer(holder, to, assetId); //廣播這次完整的資產轉移過程的事件
if (doCheck && _isContract(to)) { //如果需要執行檢查,檢查什麼?第二個條件是在檢查 to 這個節點是一個智能合約地址嗎?
// Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))
//如果to這個節點是一個智能合約所在的節點地址,那麼執行下面的代碼
require(
IERC721Receiver(to).onERC721Received(
msg.sender, holder, assetId, userData
) == ERC721_RECEIVED
);
//初步理解 上一句話就是 檢查 to這個節點上的智能合約是否支持 接收nft資產,就是看其中有沒有定義過一個方法onERC721Received()
}
}
/**
* Internal function that moves an asset from one holder to another
*/
/**
* @dev Returns `true` if the contract implements `interfaceID` and `interfaceID` is not 0xffffffff, `false` otherwise
* @param _interfaceID The interface identifier, as specified in ERC-165
*/
//這個只供外界調用的函數方法,用於檢測本合約是否實現了erc165及erc721合約接口,初步理解
function supportsInterface(bytes4 _interfaceID) external view returns (bool) {
if (_interfaceID == 0xffffffff) { //就是要求合約一定不能支持0xffffffff接口,這到底是什麼接口規範?
return false;
}
return _interfaceID == InterfaceId_ERC165 || _interfaceID == Old_InterfaceId_ERC721 || _interfaceID == InterfaceId_ERC721;
}
//
// Utilities 下面這個函數用於合約內部檢查一個節點地址是否是一個智能合約所在的節點地址。
//
function _isContract(address addr) internal view returns (bool) {
uint size;
assembly { size := extcodesize(addr) } //在大括號中的彙編代碼,extcodesize指令是求出節點地址addr所在的智能合約的代碼的size
return size > 0; //如果size大於0,就說明這個節點地址是一個智能合約所在的節點,普通的以太坊節點上沒有存放智能合約 代碼,因此 size=0,而有智能合約駐存的節點地址,上面的智能合約代碼,因此 這個size就會>0
}
}
```
【歡迎大家加入[就是要學]社羣】
如今,這個世界的變化與科技的發展就像一個機器猛獸,它跑得越來越快,跑得越來越快,在我們身後追趕着我們。
很多人很早就放棄了成長,也就放棄了繼續奔跑,多數人保持終身不變的樣子,原地不動,成爲那猛獸的肚中餐——當然那也不錯,在猛獸的逼迫下,機械的重複着自我感覺還良好地穩定工作與生活——而且多半感覺不到這有什麼不正常的地方,因爲在猛獸肚子裏的是大多數人,就好像大多數人都在一個大坑裏,也就感覺不出來這是一個大坑了,反而坑外的世界顯得有些不大正常。
爲什麼我們不要做坑裏的大多數人?
因爲真正的人生,應當有百萬種可能 ;因爲真正的一生可以有好多輩子組成,每一輩子都可以做自己喜歡的事情;因爲真正的人生,應當有無數種可以選擇的權利,而不是總覺得自己別無選擇。因爲我們要成爲一九法則中爲數不多的那個一;因爲我們要成爲自己人生的導演而不是被迫成爲別人安排的戲目中的演員。
【請注意】
就是要學社羣並不會告訴你怎樣一夜暴富!也不會告訴你怎樣不經努力就實現夢想!
【請注意】
就是要學社羣並沒有任何可以應付未來一切變化的獨門絕技,也沒有值得吹噓的所謂價值連城的成功學方法論!
【請注意】
社羣只會互相幫助,讓每個人都看清自己在哪兒,自己是怎樣的,重新看見心中的夢想,喚醒各自內心中的那個英雄,然後勇往直前,成爲自己想要成爲的樣子!
期待與你並肩奔赴未來!
QQ羣:646854445 (【就是要學】終身成長)
【原文地址】
https://www.941xue.com/content.aspx?k=941XUENQVUDR18515350912427270016
【同步語音筆記】
https://www.ximalaya.com/keji/19103006/369910994
【學習過程屏幕錄屏】
https://www.bilibili.com/video/BV1Hv411x7Zg/
筆記合集在github上:
https://github.com/lhghroom/Self-learning-blockchain-from-scratch