{"content":{"title":"bodhi菩提项目合约代码详解","body":"> 官网地址：https://bodhi.wtf/\r\n> 合约地址：https://optimistic.etherscan.io/address/0x2ad82a4e39bac43a54ddfe6f94980aaf0d1409ef#code\r\n                  \r\n# 1.ERC1155\r\n`import {ERC1155} from \"solmate/src/tokens/ERC1155.sol\";`\r\n合约调用ERC1155.sol标准合约，与标准合约ERC721不一样的地方是，721类型NFT每个ID仅此有一个，1155类型NFT是相同的ID可以有多个数量\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/04/WUINgBW866221a52e115f.png)\r\n\r\n举个例子，这里有ID为11的NFT，点击buy Share的话，相当于在ERC1155这一系列的NFT里面的第11号ID购买了Share，可多人持有该ID的Share，而ERC721中同一ID只能一人独有\r\n\r\n# 2.日志记录\r\n\r\n```\r\nevent Create(uint256 indexed assetId, address indexed sender, string arTxId);\r\nevent Remove(uint256 indexed assetId, address indexed sender);\r\nevent Trade(\r\nTradeType indexed tradeType,\r\nuint256 indexed assetId,\r\naddress indexed sender,\r\nuint256 tokenAmount,\r\nuint256 ethAmount,\r\nuint256 creatorFee\r\n);\r\n```\r\nevent类型Create、Remove、Trade三个是用于日志的一个记录，允许外部程序监听合约中的状态变化\r\n\r\n# 3.Assert\r\n\r\n```\r\nstruct Asset {\r\nuint256 id;\r\nstring arTxId; // arweave transaction id\r\naddress creator;\r\n}\r\n```\r\n每篇文章都有的三个信息：id、arTxId、creator\r\n1.id为文章的id\r\n2.arTxId是指 Arweave 上的交易 ID，Arweave 是一个永久性的去中心化存储网络，它专注于存储数据。arTxId作用是将菩提上的文章存储在Arweave上，即Arweave存储空间的ID。只要在Arweave上输入arTxId就能获得相对应的贴文内容。\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/04/D1gbheOG66221fd38209b.png)\r\n3.creator为文章的创造者\r\n\r\n# 4.方便前端调取的变量\r\n`uint256 public assetIndex;`\r\n\r\n\r\nassetIndex：表示当前已创建的贴文数量，它是一个计数器。通过观察合约信息，可以得知目前菩提有13989篇贴文（第一篇序号为0）\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/04/v55KkMPh662269b5afbba.png)\r\n`mapping(uint256 => Asset) public assets;`\r\n\r\n映射：输入一个uint256类型的ID，就返回整个Asset内容\r\n`mapping(address => uint256[]) public userAssets;`\r\n\r\n映射：输入一个address地址，返回用户所创建的Assert ID。应用场景：My Holdings界面，“我”创建了哪些贴文的ID，方便用户查询自己的贴文\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/04/dmC82k3166226c9083977.png)\r\n\r\n`mapping(bytes32 => uint256) public txToAssetId;`\r\n\r\n映射：传入哈希值（bytes32类型）返回一个贴文的ID号（uint256类型），这用来在创建贴文时用于检查贴文是否已经存在，以避免重复创建相同哈希值的贴文\r\n\r\n`mapping(uint256 => uint256) public totalSupply;`\r\n\r\ntotalSupply总供应量：贴文目前总共的购买share的数量，每有人buy，totalSupply就会增加\r\n`mapping(uint256 => uint256) public pool;`\r\n\r\npool 被用来记录每个资产的池（pool）总额，其中贴文的唯一标识符（assetId）用作键，而池的总额用作相应的值，当进行买卖交易时，买家支付的 ETH 会被添加到相应贴文的池中，这样卖家就可以从池中提取相应的 ETH\r\n`uint256 public constant CREATOR_PREMINT = 1 ether; // 1e18`\r\n\r\nCREATOR_PREMINT：用于精确计算，比如3除以2，在区块链上是等于1，因为区块链上只能整数运算，与1.5差距较大，用3*CREATOR_PREMINT=3乘10的18次方再除以2，把数字放大再进行加减乘除，这样计算就增强了精确性。\r\n`uint256 public constant CREATOR_FEE_PERCENT = 0.05 ether; // 5%`\r\n\r\nCREATOR_FEE_PERCENT：购买贴文Share收取的手续费百分比，手续费会转给创建贴文的用户\r\n\r\n\r\n\r\n```\r\nenum TradeType {\r\nMint,\r\nBuy,\r\nSell\r\n} // = 0, 1, 2\r\n```\r\n枚举类型（enum）允许定义一组命名的常量值，这里TradeType是一个枚举类型，它定义了三个可能的交易类型：Mint（铸造）、Buy（购买）和Sell（出售），可以使用TradeType.Mint来表示铸造交易类型，TradeType.Buy表示购买交易类型，TradeType.Sell表示出售交易类型\r\n\r\n# 5.函数function\r\n\r\n`function create(string calldata arTxId) public`\r\n\r\n传入arTxId，使用create函数就能创建贴文\r\n`bytes32 txHash = keccak256(abi.encodePacked(arTxId));`\r\n\r\n将arTxId用abi.encodePacked()进行编码后方便keccak256()进行哈希运算，得到txHash这个变量\r\n`require(txToAssetId[txHash] == 0, \"Asset already exists\");`\r\n\r\n如果txHash对应的资产ID不为0，那么说明这个哈希值已经存在于txToAssetId映射中，即已经有一个相同的资产使用了这个哈希值，用来确保用户不会重复创建相同的贴文\r\n\r\n`uint256 newAssetId = assetIndex;`\r\n\r\n将新的可用的贴文ID分配给了新创建的贴文，下方图片左上角的0就是贴文ID，新的贴文就依次加一\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/04/sy2MllMh662385ba3a0a4.png)\r\n\r\n`assets[newAssetId] = Asset(newAssetId, arTxId, msg.sender);`\r\n\r\n将创建的新贴文存储在assets映射中，存储的键为newAssetId，存储的键对应值为newAssetId, arTxId, msg.sender（创建新贴文用户的钱包地址）\r\n\r\n`userAssets[msg.sender].push(newAssetId);`\r\n\r\n将新创建的贴文ID添加到当前用户（msg.sender）的userAssets数组中\r\n\r\n`txToAssetId[txHash] = newAssetId;`\r\n\r\n将新创建贴文的newAssetId与arTxId的哈希值txHash关联起来，存入txToAssetId映射中\r\n\r\n`totalSupply[newAssetId] += CREATOR_PREMINT;`\r\n\r\n新贴文的初始总供应量设置为CREATOR_PREMINT（10的18次方），方便小数点的计算\r\n\r\n`assetIndex = newAssetId + 1;`\r\n\r\n这是跟上方`assets[newAssetId] = Asset(newAssetId, arTxId, msg.sender);`配合在一起用的，使创建的新贴文ID是没有用过的最新ID\r\n\r\n`_mint(msg.sender, newAssetId, CREATOR_PREMINT, \"\");`\r\n\r\n_mint方法是ERC1155标准合约中定义好的函数，用来铸造NFT，每创建新贴文就是在铸造新NFT。CREATOR_PREMINT为新帖文的初始供应量。\"\" 是一个空字符串，用于传递额外的数据。\r\n\r\n\r\n\r\n```\r\nemit Create(newAssetId, msg.sender, arTxId);\r\nemit Trade(TradeType.Mint, newAssetId, msg.sender, CREATOR_PREMINT, 0, 0);\r\n```\r\n每创建新贴文，都会有Create和Trade事件触发，记录日志\r\n\r\n\r\n\r\n\r\n\r\n```\r\nfunction getAssetIdsByAddress(address addr) public view returns (uint256[] memory) {\r\nreturn userAssets[addr];\r\n}\r\n```\r\n\r\ngetAssetIdsByAddress：传入用户的钱包地址address返回用户所创建的所有贴文ID数组\r\n\r\n\r\n```\r\nfunction _curve(uint256 x) private pure returns (uint256) {\r\nreturn x <= CREATOR_PREMINT ? 0 : ((x - CREATOR_PREMINT) * (x - CREATOR_PREMINT) * (x - CREATOR_PREMINT));\r\n}\r\n```\r\n\r\n这个函数用于计算贴文价格曲线，根据贴文的供应量来确定资产的价格。函数意思是如果 x（贴文供应量）小于或等于预分配给创建者的数量 CREATOR_PREMINT，则返回0，否则返回 (x - CREATOR_PREMINT) * (x - CREATOR_PREMINT) * (x - CREATOR_PREMINT) 的计算结果。\r\n\r\n\r\n```\r\nfunction getPrice(uint256 supply, uint256 amount) public pure returns (uint256) {\r\nreturn (_curve(supply + amount) - _curve(supply)) / 1 ether / 1 ether / 50_000;\r\n}\r\n```\r\n\r\n这个函数主要用于计算基于贴文供应量和交易数量的价格。(_curve(supply + amount) - _curve(supply)) 计算了交易后资产价格曲线的变化量。/ 1 ether / 1 ether / 50_000 对价格曲线的变化量进行了一系列除法操作，以确定最终的价格。\r\n\r\n\r\n```\r\nfunction getBuyPrice(uint256 assetId, uint256 amount) public view returns (uint256) {\r\nreturn getPrice(totalSupply[assetId], amount);\r\n}\r\n```\r\n\r\n函数用于获取购买指定share的贴文的价格，其计算方式依赖于 getPrice 函数的实现，并根据资产的总供应量和购买的share来确定价格。这跟上方的getPrice区别在于：第一个函数用于内部计算资产价格曲线的变化，而第二个函数则用于外部获取购买资产的价格。\r\n\r\n\r\n```\r\nfunction getSellPrice(uint256 assetId, uint256 amount) public view returns (uint256) {\r\nreturn getPrice(totalSupply[assetId] - amount, amount);\r\n}\r\n```\r\n函数用于获取销售指定share的贴文的价格，其计算方式依赖于 getPrice 函数的实现，并根据贴文的总供应量和销售的share来确定价格。\r\n\r\n\r\n```\r\nfunction getBuyPriceAfterFee(uint256 assetId, uint256 amount) public view returns (uint256) {\r\nuint256 price = getBuyPrice(assetId, amount);\r\nuint256 creatorFee = (price * CREATOR_FEE_PERCENT) / 1 ether;\r\nreturn price + creatorFee;\r\n}\r\n```\r\n\r\n函数用来计算用户实际购买贴文share的价格，等于贴文share价格加上手续费，手续费是价格的5%\r\n\r\n\r\n```\r\nfunction getSellPriceAfterFee(uint256 assetId, uint256 amount) public view returns (uint256) {\r\nuint256 price = getSellPrice(assetId, amount);\r\nuint256 creatorFee = (price * CREATOR_FEE_PERCENT) / 1 ether;\r\nreturn price - creatorFee;\r\n}\r\n```\r\n函数用来计算用户实际售卖贴文share的价格，等于贴文share价格减去手续费\r\n\r\n\r\n```\r\nfunction buy(uint256 assetId, uint256 amount) public payable {\r\nrequire(assetId < assetIndex, \"Asset does not exist\");\r\n```\r\n\r\n贴文ID要小于计数器assetId，限制用户不能提前购买还未创建的贴文Share。例如，目前有8000条贴文，我购买assetId为9000的贴文的Share，目前还没有assetId为9000的贴文，所以要做限制\r\n\r\n\r\n```\r\nuint256 price = getBuyPrice(assetId, amount);\r\nuint256 creatorFee = (price * CREATOR_FEE_PERCENT) / 1 ether;\r\n```\r\n\r\n获取价格和手续费\r\n\r\n`require(msg.value >= price + creatorFee, \"Insufficient payment\");`\r\n\r\n用户购买付出的金额要大于等于价格与手续费的和\r\n\r\n`totalSupply[assetId] += amount;`\r\n\r\n用户购买多少Share，该贴文的总供应量就增加多少\r\n\r\n`pool[assetId] += price;`\r\n\r\n用户购买贴文Share后，pool池子中增加相应的价格\r\n\r\n`_mint(msg.sender, assetId, amount, \"\");`\r\n\r\n用户购买贴文Share后，利用ERC1155 合约的mint函数铸造该贴文相应数量的NFT\r\n\r\n\r\n```\r\nemit Trade(TradeType.Buy, assetId, msg.sender, amount, price, creatorFee);\r\n```\r\n\r\n\r\n\r\n触发Trade事件，记录交易细节，包括交易类型 (tradeType)、贴文ID (assetId)、用户钱包地址(msg.sender)、代币数量 (amount)、价格(price) 和手续费 (creatorFee)\r\n\r\n\r\n```\r\n(bool creatorFeeSent, ) = payable(assets[assetId].creator).call{value: creatorFee}(\"\");\r\n```\r\n将用户购买所付的手续费发送给创作者的钱包的地址，并记录是否发送成功。\r\n1.(bool creatorFeeSent, ) = ...：这是一个解构语法，将 call 函数的返回值中的第一个返回值（表示是否成功发送以太币）分配给 creatorFeeSent 变量，而第二个返回值被忽略。\r\n2.assets[assetId].creator：获取贴文的创建者钱包地址。\r\n3.payable(assets[assetId].creator)：将地址转换为 payable 类型，以便进行以太币转账。\r\n4..call{value: creatorFee}(\"\")：以太币调用，使用 call 函数向合约地址发送以太币，其中 {value: creatorFee} 表示要发送的以太币数量为 creatorFee。空字符串 \"\" 是传递给被调用合约的数据，因为这里没有需要传递的额外数据。\r\n\r\n`require(creatorFeeSent, \"Failed to send Ether\");`\r\n\r\n如果尝试向贴文的创建者发送费用的操作失败（即 creatorFeeSent 为 false），则函数执行会被中止，并返回错误消息 \"Failed to send Ether\"\r\n\r\n\r\n```\r\nfunction sell(uint256 assetId, uint256 amount) public {\r\nrequire(assetId < assetIndex, \"Asset does not exist\");\r\nrequire(balanceOf[msg.sender][assetId] >= amount, \"Insufficient balance\");\r\nuint256 supply = totalSupply[assetId];\r\nrequire(supply - amount >= CREATOR_PREMINT, \"Supply not allowed below premint amount\");\r\nuint256 price = getSellPrice(assetId, amount);\r\nuint256 creatorFee = (price * CREATOR_FEE_PERCENT) / 1 ether;\r\n_burn(msg.sender, assetId, amount);\r\ntotalSupply[assetId] = supply - amount;\r\npool[assetId] -= price;\r\nemit Trade(TradeType.Sell, assetId, msg.sender, amount, price, creatorFee);\r\n(bool sent, ) = payable(msg.sender).call{value: price - creatorFee}(\"\");\r\n(bool creatorFeeSent, ) = payable(assets[assetId].creator).call{value: creatorFee}(\"\");\r\nrequire(sent && creatorFeeSent, \"Failed to send Ether\");\r\n}\r\n```\r\n\r\nsell函数与buy函数的逻辑基本相同，下面介绍几个不同的地方\r\n\r\n\r\n```\r\nrequire(balanceOf[msg.sender][assetId] >= amount, \"Insufficient balance\");\r\n```\r\n\r\n用户卖的数量不能超过拥有的数量\r\n1.balanceOf是ERC1155合约中定义的映射，函数定义：\r\nmapping(address => mapping(uint256 => uint256)) public balanceOf;\r\n2.这个映射将账户地址映射到一个包含贴文ID和余额之间关系的映射\r\n3.balanceOf[msg.sender][assetId] 就表示了账户 msg.sender 在贴文assetId 上的余额。\r\n\r\n\r\n```\r\nrequire(supply - amount >= CREATOR_PREMINT, \"Supply not allowed below premint amount\");\r\n```\r\n\r\n确保从资产的总供应量中减去用户卖的数量amount 后，剩余的供应量不会低于预先铸造的数量 CREATOR_PREMINT\r\n\r\n` _burn(msg.sender, assetId, amount);`\r\n\r\n用户售卖后，销毁相应数量的NFT\r\n\r\n\r\n```\r\n(bool sent, ) = payable(msg.sender).call{value: price - creatorFee}(\"\");\r\n```\r\n\r\n用户售卖后，向用户转账金额，金额为价格减去手续费\r\n\r\n\r\n```\r\nfunction uri(uint256 id) public view override returns (string memory) {\r\nreturn assets[id].arTxId;\r\n}\r\n```\r\nuri：传入文章ID，返回存储在Arweave中文章的arTxId"},"author":{"user":"https://learnblockchain.cn/people/18946","address":"0xed922c25702ca34d2ab1a2a4ccf576a9d5922f07"},"history":"bafkreigccrr6amgy5svlwupqcvic3tiilzeeqacacrunoivnofwiuuczeq","timestamp":1714633298,"version":1}