Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

task[3]: pandapls #1402

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions members/pandapls/task3/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
node_modules
.env

# Hardhat files
/cache
/artifacts

# TypeChain files
/typechain
/typechain-types

# solidity-coverage files
/coverage
/coverage.json

# Hardhat Ignition default folder for deployments against a local node
ignition/deployments/chain-31337
22 changes: 22 additions & 0 deletions members/pandapls/task3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Sample Hardhat Project

// 该hardhat 测试用例正常跑,部署异常
// 最后使用remix进行合约部署并进行测试

```shell
npm run compile
npm run test
npm run deploy:sepolia
```

最终合约相关地址:
ERC20 - MyToken: 0x4e5623b716081424e6f645b56ec05cad1b499af0
ERC721 - MyNFTToken: 0xd069460acf2daba36c3b740934514ae1fc962441
NFTMarket - NFTMarket: 0xf0ab8066330e181be5359f0740cb2182e7608045

上架NFT交易哈希: 0xc47ee1ba4bf948df51ac924964273dd5da3636ddb6c247b00b8a6662ac4e8835
购买NFT交易哈希:0xefcd744c33223e648ba8a9684740ec50e1cc29ef7ace71898511288233bdf46f

![Alt text](image.png)

![Alt text](image-1.png)
23 changes: 23 additions & 0 deletions members/pandapls/task3/contracts/MyNFTToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract MyNFTToken is ERC721 {
uint256 private _nextTokenId;

// 修改构造函数,传递名称和符号给 ERC721
constructor() ERC721("POPNFT", "PNFT") {
_nextTokenId = 0;
}

function mint() external returns (uint256) {
uint256 tokenId = _nextTokenId;

_safeMint(msg.sender, tokenId);

_nextTokenId++;

return tokenId;
}
}
10 changes: 10 additions & 0 deletions members/pandapls/task3/contracts/MyToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("Popul Panda", "POP") {
_mint(msg.sender, initialSupply);
}
}
109 changes: 109 additions & 0 deletions members/pandapls/task3/contracts/NFTMarket.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

contract NFTMarket {

struct NFTItem {
address nftContract; // NFT 合约地址
uint256 tokenId; // NFT 的token Id
address seller; // 卖家地址
uint256 price; // NFT 的价格
}

// 卖家地址 => 卖家卖出的NFT => NFT信息
mapping(address => mapping(uint256 => NFTItem)) private nftMap;

// 支付的代币 - 只能用erc20协议的代币
IERC20 private _paymentToken;


// 存储所有上架的 NFT 的 tokenId,用于遍历 (nftContract => tokenIds)
mapping(address => uint256[]) private nftTokens;


event NFTListed(
address indexed seller,
address indexed nftContract,
uint256 indexed tokenId,
uint256 price
);

event NFTPurchased(
address indexed seller,
address indexed nftContract,
uint256 indexed tokenId,
uint256 price
);

// 初始化 ERC20 支付代币合约
constructor(address paymentToken) {
_paymentToken = IERC20(paymentToken);
}
function token() external view returns (address) {
return address(_paymentToken);
}
// 上架NFT
function listNFT(address nftContract, uint256 tokenId, uint256 price) external {
IERC721 nft = IERC721(nftContract);
// 确保 NFT 合约地址不是 0
require(nft.ownerOf(tokenId) == msg.sender, "not the owner of the contract");
// 确保价格大于 0
require(price > 0, "Price must be greater than zero");

// 确保市场合约有权转移该 NFT
require(nft.isApprovedForAll(msg.sender, address(this)), "the contract not be approved");


nftMap[nftContract][tokenId] = NFTItem(nftContract, tokenId, msg.sender, price);
// 将 tokenId 添加到 nftTokens 数组中,方便后续遍历
nftTokens[nftContract].push(tokenId);

emit NFTListed(msg.sender, nftContract, tokenId, price);
}

// 购买NFT
function purchaseNFT(address nftContract, uint256 tokenId) external {
NFTItem memory listedItem = nftMap[nftContract][tokenId];
require(listedItem.price > 0, 'this nft is not for sale');

bool success = _paymentToken.transferFrom(msg.sender, listedItem.seller, listedItem.price);
// 将ERC20代币从买家转移到卖家
require(success, "Paent failed!");

// 将 NFT 从卖家转移给买家
IERC721(nftContract).safeTransferFrom(listedItem.seller, msg.sender, tokenId);
// 删除已售出的 NFT 上架信息
delete nftMap[nftContract][tokenId];

// 删除 nftTokens 中的该 tokenId
uint256[] storage tokens = nftTokens[nftContract];
for (uint256 i = 0; i < tokens.length; i++) {
if (tokens[i] == tokenId) {
tokens[i] = tokens[tokens.length - 1]; // 用最后一个 tokenId 替换要删除的
tokens.pop(); // 删除最后一个元素
break;
}
}

emit NFTPurchased(msg.sender, nftContract, tokenId, listedItem.price);
}

// 获取指定合约的所有上架 NFT 信息
function getAllNFTItems(address nftContract) external view returns (NFTItem[] memory) {
uint256 totalItems = nftTokens[nftContract].length;
NFTItem[] memory items = new NFTItem[](totalItems);

// 遍历 nftTokens 数组并获取上架的 NFT 信息
for (uint256 i = 0; i < totalItems; i++) {
uint256 tokenId = nftTokens[nftContract][i];
items[i] = nftMap[nftContract][tokenId];
}

return items;
}

}
33 changes: 33 additions & 0 deletions members/pandapls/task3/hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-ignition");
require('dotenv').config();

// 合约验证本地网络异常需要加以下配置
// const { ProxyAgent, setGlobalDispatcher } = require("undici");
// const proxyAgent = new ProxyAgent("http://127.0.0.1:7890");
// setGlobalDispatcher(proxyAgent);
const { INFURA_ID, WALLET_PRIVATE_KEY, ETHERS_API_HARDHAT_KEY } = process.env;

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.26",
networks: {
sepolia: {
url: `https://sepolia.infura.io/v3/${INFURA_ID}`,
accounts: [`0x${WALLET_PRIVATE_KEY}`],
},
},
verify: {
etherscan: {
apiKey: ETHERS_API_HARDHAT_KEY
}
},
// 合约验证
etherscan: {
apiKey: ETHERS_API_HARDHAT_KEY
},
sourcify: {
enabled: true
},

};
Binary file added members/pandapls/task3/image-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added members/pandapls/task3/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions members/pandapls/task3/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "hardhat-project",
"scripts": {
"compile": "npx hardhat compile",
"verify:sepolia": "npx hardhat verify --network sepolia",
"deploy:sepolia": "npx hardhat run scripts/deploy.js --network sepolia",
"test": "npx hardhat test"
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@nomiclabs/hardhat-etherscan": "^3.1.8",
"ethers": "^6.13.3",
"hardhat": "^2.22.12",
"undici": "^6.19.8"
},
"dependencies": {
"@openzeppelin/contracts": "^5.0.2",
"dotenv": "^16.4.5"
}
}
54 changes: 54 additions & 0 deletions members/pandapls/task3/scripts/deploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const hre = require("hardhat");

async function main() {
console.log('deploy start --->');
// 获取合约部署者的地址
const [deployer] = await ethers.getSigners();
// 获取部署者的 ETH 余额
const balance = await ethers.provider.getBalance(deployer.address);
console.log(`Deployer's balance: ${ethers.formatEther(balance)} ETH`);
// 部署erc20 代币合约
const myTokenFactory = await hre.ethers.getContractFactory('MyToken');
const myToken = await myTokenFactory.deploy(ethers.parseEther('1'));
const myTokenAddress = await myToken.target;
await verify(myTokenAddress, [ethers.parseEther('1')])
console.log(`MyToken deployed to: ${myTokenAddress}`);

// 部署NFT token合约
const myNFTTokenFactory = await ethers.getContractFactory('MyNFTToken');
const myNFTToken = await myNFTTokenFactory.deploy();
const myNFTTokenAddress = await myNFTToken.getAddress();
await verify(myNFTTokenAddress, [])
console.log(`MyNFTToken deployed to: ${myNFTTokenAddress}`);

// 部署NFTMarket合约
const NFTMarketFactory = await ethers.getContractFactory('NFTMarket');
const NFTMarket = await NFTMarketFactory.deploy(myTokenAddress);
const NFTMarketAddress = await NFTMarket.getAddress();
await verify(myNFTTokenAddress, [myTokenAddress])
console.log(`NFTMarket deployed to: ${NFTMarketAddress}`);

console.log('<---- deploy end ');
}
async function verify(contractAddress, args) {
console.log("Verifying contract...")
try {
await run("verify:verify", {
address: contractAddress,
constructorArgsParams: args,
})
} catch (e) {
if (e.message.toLowerCase().includes("already verified")) {
console.log("Already Verified!")
} else {
console.log(e)
}
}
}
// 运行部署脚本
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Loading