深入分析NFT合约源码——以Surge Women为例

深入分析NFT合约源码——以Surge Women为例

github地址:https://github.com/youngqqcn/mynft

问题1:NFT(non-fungible token),即非同质化代币,如何理解“非同质化”?在代码层面如何实现的?

答:fungible中文意思是“可互换的”,可互换的东西是没有特殊性的,如果是独一无二的东西则具有了“不可互换的”属性。例如,1元钱的硬币和1元钱的纸钞则可以互换,虽然在形态上不同,但是在作为货币的属性上本质相同,都是代表1元。

至于如何编码实现,前面说了non-fungible的东西必须具备“独一无二”的属性,在编程领域什么东西独一无二呢?
答案很简单,就是唯一的id,用一个整数即可,在solidity中uint256能够表示的整数完全够用。

问题2:NFT的图片(或者音频、视频等)是怎样和智能中tokenId一一对应起来的?

图片等资源文件放在IPFS,智能合约中只存储每个token对应的IPFS上的URI即可。每个NFT项目有一个目录,目录下可以放很多资源文件,在构造合约的时候将目录在IPFS上的URI设置为baseURI,那么每个token的资源文件在IPFS的URI就确定了。例如,某个NFT项目在IPFS上总目录的URI为ipfs://QmYVsw73haPgm9jK9BopsuKtzuxLANjYn75xeHLpht13D5,tokenId为1802的token在IPFS上的URI则为ipfs://QmYVsw73haPgm9jK9BopsuKtzuxLANjYn75xeHLpht13D5/1802

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from web3 import Web3
import json

# https://etherscan.io/tx/0xbede5e44cc631303a22d066cc269f989469742b5bb6d9a74185e146dab9211e4
# https://mainnet.infura.io/v3/8a264f274fd94de48eb290d35db030ab
# contract address is 0x0632aDCab8F12edD3b06F99Dc6078FE1FEDD32B0

from web3 import Web3
my_provider = Web3.HTTPProvider('https://mainnet.infura.io/v3/8a264f274fd94de48eb290d35db030ab')
w3 = Web3(my_provider)

def main():

contract_address = '0x0632aDCab8F12edD3b06F99Dc6078FE1FEDD32B0'
contract_abi = json.load(open('surge.abi', 'r'))
# print(contract_abi)

mycontract = w3.eth.contract(address=contract_address, abi=contract_abi)
name = mycontract.functions.name().call()
print(name)

symbol = mycontract.functions.symbol().call()
print(symbol)

tokenURI = mycontract.functions.tokenURI(1802).call()
print(tokenURI)

pass

if __name__ == '__main__':
main()

运行打印的结果是:

1
2
3
Surge Women Passport
SURGE
ipfs://QmYVsw73haPgm9jK9BopsuKtzuxLANjYn75xeHLpht13D5/1802

项目在IPFS的总目录:
https://ipfs.io/ipfs/QmYVsw73haPgm9jK9BopsuKtzuxLANjYn75xeHLpht13D5

https://tth-ipfs.com/ipfs/QmYVsw73haPgm9jK9BopsuKtzuxLANjYn75xeHLpht13D5
ipfs浏览器中的链接:ipfs/Qmaseu2BbetLjA6eU7mQ2THEkjdBum5wq1EfuLAY2AoiEA/1802.png

分析tokenURI函数的代码

1
2
3
4
5
6
7
8
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (!_exists(tokenId)) revert URIQueryForNonexistentToken();

// baseURI是目录的URI
string memory baseURI = _baseURI();
// 将目录的URI和tokenId拼接在一起就是token的URI
return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : '';
}

而 _baseURI由 Surge合约重写了父合约的_baseURI函数。Surge合约在构造函数中设置了baseURI,也就是在构造合约时已经设置了baseURI

1
2
3
4
5
6
7
8
9
10
11
12
constructor(
string memory _name,
string memory _symbol,
string memory _baseTokenURI,
uint128 _price,
address _receiver,
uint256 _royalties
) payable ERC721A(_name, _symbol) {
setBaseURI(_baseTokenURI);
setPrice(_price);
setRoyalties(_receiver, _royalties);
}

presaleMint为什么要用到merkleProof?

项目方做了预售,对所有参加预售的地址构造了一棵merkle tree,并将merkle root填入智能合约,调用presale的地址必须在merkle tree中。

使用merkle tree可以隐藏了具体地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// @notice Presale minting verifies callers address is in Merkle Root
/// @param _amountOfTokens Amount of tokens to mint
/// @param _merkleProof Hash of the callers address used to verify the location of that address in the Merkle Root
function presaleMint(uint256 _amountOfTokens, bytes32[] calldata _merkleProof)
external
payable
verifyMaxPerUser(msg.sender, _amountOfTokens)
verifyMaxSupply(_amountOfTokens)
isEnoughEth(_amountOfTokens)
{
require(status == SaleStatus.Presale, "Presale not active");

bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
require(MerkleProof.verify(_merkleProof, merkleRoot, leaf), "Not in presale list");

_mintedAmount[msg.sender] += _amountOfTokens;
_safeMint(msg.sender, _amountOfTokens);
}

设置merkle root
https://etherscan.io/tx/0x4d6e0c07516115b8a803f77fe3067d52091c8d888eecb8f60fe897a68501ea27

1
2
3
4
5
/// @notice Set Presale Merkle Root
/// @param _merkleRoot Merkle Root hash
function setMerkleRoot(bytes32 _merkleRoot) public onlyOwner {
merkleRoot = _merkleRoot;
}

presale

https://etherscan.io/tx/0x387dd09362758758b52d56dd2093724039fbd5592b13613cc347a2c1a216b581

同一个地址2次调用presale,那么它提供的merkle proof两次肯定是一样的。
https://etherscan.io/tx/0x5c76c3e78933ccc9f50e3a6f979226c02b9ab96ed320cbd68d4fbf3361c2b366
https://etherscan.io/tx/0xe64591ba680b9fb18f3bac61a20b7343801f03a9905d1f260df4d945089a056e

认知世界的经济学(笔记)

认知世界的经济学(笔记)

第1章 经济学是什么

理论课1——经济学世界观

经济学研究什么

  • 个体的选择

经济学和金融学的区别

  • 钱的选择

经济学的前提假设

  • 资源永远稀缺的
  • 人是理性的

取舍

课程地图

  • 第一模块:经济学核心原理
  • 第二模块:消费者如何选择
  • 第三模块:企业如何决策
  • 第四模块:市场如何运行
  • 第五模块:金钱如何分配

经济学的历史观

  • 现在:现在的经济制度存在系统性漏洞——就业问题和贫富差距增大
  • 未来:凯恩斯主义、货币主义

理论课3——经济学道德观

第2章 经济学的基本方法

1、个体分析
2、群体分析
3、模型与数据检验

机会成本

什么是机会成本?

选择中放弃选择最高价值

她那时还太年轻,不知道命运馈赠的礼物,早已暗中标好了价格。
——斯蒂芬·茨威格《断头皇后》

机会成本有何用?

沉没成本

  • 沉没成本:人们在决定是否去做一件事情的时候,不仅是看这件事对自己有没有好处,而且也看过去是不是已经在这件事情上有过投入。我们把这些已经发生不可收回的支出,如时间、金钱、精力等称为“沉没成本”(Sunk Cost)。

边际分析

  • 边际成本:增加一单位的产量随即而产生的成本增加量即称为边际成
  • 边际收益:是指增加一单位产品的销售所增加的收益,即最后一单位产品的售出所取得的收益。

存量与增量


个体选项优化

1、预算约束与可行选项

2、权衡选项:成本收益分析

充分考虑影响因素、准确估值

3、激励

群体均衡

  • 群体分析:

  • 均衡:

  • 合成谬误:

  • 搭便车:

模型与数据检验

步骤

  • 1、设立假说
  • 2、解释假说
  • 3、数据检验
  • 4、查看结果

归因谬误

  • 把先后发生的事情当作因果关系
  • 把同事发生的两类事情当成有因果关系

有相关关系,却没有因果关系?

  • 遗漏因素
  • 反向因果关系
  • 巧合

马斯洛的需求层次理论

1
2
3
4
5
6
    /理想人生价值\   自我实现需求
/ 尊重需求 \ (自尊、他尊)
/ 社交需求 \ (归属和爱的需求)
/ 安全需求 \ (人身、健康、职业安全等)
/ 生理需求 \ (食物、水、睡眠)

第三章—经济学的核心逻辑

需求

需求量:在某个价格下,消费者愿意且能够购买的某种商品或服务的数量

  • 购买意愿
  • 购买能力

需求定律:价格与需求之间呈反向关系:

  • 替代效应
  • 收入效应

需求曲线移动:

  • 收入变化
  • 替代品
  • 互补品
  • 人的变化
  • 消费者对未来的预期
  • 消费者的喜好、偏好的变化

供给

供给量:在某个价格下,卖家愿意且能够出售的某种商品或服务的数量

  • 供给意愿
  • 供给能力

供给定律:价格与供给呈正相关关系

供给曲线:

均衡/价格


PPI(Producer Price Index):生产价格指数

PMI(Purchasing Manager’s Index):采购经理指数


第二模块——消费者如何选择的?

  • 消费者的偏好
  • 商品或服务的价格
  • 消费者的收入

第4章——消费者选择模型

效应

边际效用递减规律

基数效用论
序数效用论

无差异曲线

预算线

网路外部性:

用户转换成本:


案例——婚姻与恋爱关系

  • 付出与回报
  • 处理矛盾,解决问题,不要冷战
  • 新鲜感降低

第5章——时间与风险

风险补偿

风险:不确定性

风险的计算:

时间补偿:

通胀

利率:时间补偿+风险补偿

利率计算:

  • 年利率 = 月利率 * 12
  • 月利率 = 日利率 * 30

单利、复利

复利计算:m * (1 + r)^n

折现率:

贴现率:

第六章——消费者是如何选择的?

过度自信:

框架效应:

锚定效应:

人们更偏向于维持现状:

压力线:

支撑线:

峰终定律:如果在一段体验的高峰处结尾,体验是愉悦的,那么对整个体验的感受就是愉悦的。

心理账户:

第七章——

需求弹性

交叉弹性:

消费者剩余:

第八章——企业

企业的目的:利润最大化

企业组织:

成本分析

显性成本:

隐性成本:没有产生会计成本(例如,资金成本,老板自己的人力成本,企业自己拥有的办公楼,小店自己的店铺,企业的专利)。隐形成本需要用机会成本来度量。

会计成本:

会计利润、经济利润

显性成本=会计成本

显性成本 + 隐性成本 = 经济成本 = 机会成本的总和 = 生产成本

固定成本:

可变成本:

总成本:

平均总成本 = 平均固定成本 + 平均可变成本

边际产量:

边际报酬递减:固定其他资源,只改变单一资源

边际成本:
平均成本:

企业短期成本:

边际成本:

只要 增加成本 > 产出 ,就可以增加投入

规模经济、规模不经济

利润:

边际成本大于市场价格,增加的产量带来的是亏损

通过平均成本计算利润:

定价:

消费者剩余与生产者剩余

供给弹性:

  • 企业的库存情况
  • 商品类目的大小
  • 生产的难度、门槛、资源的耗费
  • 时间长短

NFT学习

NFT学习

ERC721官网:http://erc721.org/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IERC721 /* is ERC165 */ {
// 事件:转移token
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

//事件: 授权approved管理owner的tokenId
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

//事件: 授权operator管理所有资产
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

// 获取token的数量
function balanceOf(address owner) external view returns (uint256 balance);

// 获取token所有者
function ownerOf(uint256 tokenId) external view returns (address owner);

// 安全转移,首先会检查合约是否能够识别ERC721协议,以防止转进区的NFT被锁住
// from, to 都不能是零地址
// 如果调用者不是from,那么,在此之前必须已经被授权
// 如果to是智能合约,那么,to必须实现IERC721Receiver-onERC721Received
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;


// 转移函数
function transferFrom(
address from,
address to,
uint256 tokenId
) external;


// 授权函数
function approve(address to, uint256 tokenId) external;


// 获取某个token授权的账户
function getApproved(uint256 tokenId) external view returns (address operator);


// 授权operator管理所有资产
function setApprovalForAll(address operator, bool _approved) external;


// 检查是否有授权
function isApprovedForAll(address owner, address operator) external view returns (bool);


// 同上,多了一个data参数
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
}

interface ERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}


//用于接收ERC721的智能合约必须实现此 `IERC721Receiver` 接口
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
*/
// 当智能合约收到ERC721 token时,此函数就会被调用,并返回一个selector以确认收到了token
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}

所谓NFT(Non-Fungible Token)就是保证tokenId唯一即可,关于tokenId生成,有很多方式。CryptoKitty中是用数组的index

1
2
3
4
5

Kitty[] kitties;

uint256 newKittenId = kitties.push(_kitty) - 1; // push 会返回数组的长度,用元素的index作为token的ID

小王子

最近看完了《小王子》,非常经典的童话。如作者所写,这是写给大人们看的童话,因为每个大人都曾经是小孩子,可是很多大人忘记了这一点。

每个人心理都有一个小王子或曾经拥有过,在成长过程中,慢慢地成了“大人”,大人们都喜欢把东西说的清清楚楚,总是在他们自己的星球沉醉于他们自己事情。

《小王子》中经典的句子太多了,我想我们不应该执迷于寻章摘句,而是应该用心品味原著才,“重要的东西是看不到的”。

愿我们在夜晚仰望星空时,拥有一颗会笑的星星。

关于写作

坚持每周写点东西。

今天是2021年6月8号,2021高考第二天。今天讨论一下关于写作的问题,当然,这里所指的写作不是文学创作那种,而是指文字组织和表述能力。

为什么要坚持每周写点东西呢?

  • 第一,可以作为一周的总结,记录这一周值得记录和思考的事情,可以包括很多方面,生活,锻炼,摄影,美食,旅行,阅读,音乐,技术,等等;
  • 第二,作为一种激励方法,让自己保持发现生活中的美好心态,坚持一些好的习惯,比如阅读和锻炼身体。有效输入才能保证有效输出。

如果是为了做某事而做某事,那样可能就会忘记初衷是什么。阅读的目的不是单纯的阅读,也是一种思考,正所谓“学而不思则罔,死而不学则殆”。所以,也不要为了写点东西而写点东西,一定要有自己的东西,要有自己的思考。

其实,不论是学习一门技能还是阅读一本小说,如果是缺少了“思”,那么就很难领悟本质。写作是一种输出,写作的过程就是将已有的知识进行整理,然后以文字方式输出。

想简单讨论一下“费曼学习法“, 其精髓在于“以教促学”或“以教为学”,是一种快速深度学习的方法。可以概括为“主动输入+主动加工+主动输出”,即有效的输入与有效的输出相结合,有效的输出正反馈于有效输入,形成一个良性循环。简而言之,如果我需要快速掌握某些内容,那么就让自己成为这门课的“老师”,教授他人这些内容,这样你在“备课”的过程中学习,在学习中整理知识消化知识。这种“以教促学“和软件工程领域的”测试驱动(TDD)“的思想非常相似,都是以“输出”驱动“输入”,可以大大提高学习效率。写作也是一种教学方式,在这个过程中会重新整理和重新发现。

最后,大致介绍一下“费曼学习法”几个要点,后面我会单独写一篇文章记录自己利用这个方法学习的实践经验。

  • 1.明确目标: 你想学习的概念,内容
  • 2.以教促学: 写出自己的理解
  • 3.化整为零: 将目标内容分解为一个个基础知识点
  • 4.总结提炼: 合并内容,将复杂的内容简化并提炼核心内容,再重复上述几步
  • Copyrights © 2021-2024 youngqqcn

请我喝杯咖啡吧~