0%

solidity | 一个小而全的合约项目案例

该合约主要实现以下几点

该案例来自于我的 web3 项目 wordslabs 中的一部分。

代码

其主要思路如下

  • ERC721 工厂类
    • 可以创建 ERC721 工厂
  • 验证类
    • 可以进行数字签名

这里直接贴一下代码

  • BookUtil.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
pragma solidity ^0.8.1;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract BookUtil {

address immutable public signer = address(0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2);

function vertify(bytes32 hash, bytes memory signature) public view returns (bool){
return (signer == ECDSA.recover(hash, signature));
}

}
  • GameBook.sol
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
pragma solidity ^0.8.1;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

interface functionContract {
function utilAddress() external pure returns (address utilAddr);
function vertify(bytes32 hash, bytes memory signature) external view returns (bool);
}

interface GameBookRule {

struct Rule {
uint256 amount;
uint256 price;
uint256 time;
bytes32 hash;
bytes signature;
}

}

contract GameBook is ERC721 {

using Address for address;

address public immutable factory;
address public immutable creator;

uint256 public immutable originAmount;
uint256 public immutable originMintPrice;

mapping(address => uint256) public timeRecords;

constructor(uint256 _originAmount, uint256 _price) ERC721("GameBook", "GB") {
factory = msg.sender;
creator = tx.origin;
originAmount = _originAmount;
originMintPrice = _price;
}

function mint(GameBookRule.Rule calldata rule) public payable {
require(!_msgSender().isContract(), "gamebook: call to non-contract");
address utilAddr = functionContract(factory).utilAddress();
require(rule.time != timeRecords[_msgSender()], "gamebook: time is not valid");
require(functionContract(utilAddr).vertify(rule.hash,rule.signature),"gamebook: vertify is fail");
timeRecords[tx.origin] = rule.time;
}

}

contract GameBookFactory is Context, Ownable {

address public utilAddress;

using Address for address;
using EnumerableSet for EnumerableSet.AddressSet;
EnumerableSet.AddressSet private _allGameBookAddrs;
mapping(address => address) public GameBookCreatorInfos;
mapping(address => uint256) public timeRecords;

event eCreateGameBook(address creator, address gameBookAddr);

constructor(address _utilAddress) Ownable(){
utilAddress = _utilAddress;
}

function updateUtilAddr(address addr) public onlyOwner() {
utilAddress = addr;
}

function createGameBook(GameBookRule.Rule calldata rule) external returns (address gameBookAddr) {
require(!_msgSender().isContract(), "gamebook: call to non-contract");
require(rule.time != timeRecords[_msgSender()], "gamebook: time is not valid");
require(functionContract(utilAddress).vertify(rule.hash,rule.signature),"gamebook: vertify is fail");

GameBook gameBook = new GameBook(rule.amount, rule.price);
gameBookAddr = address(gameBook);
if (!_allGameBookAddrs.contains(gameBookAddr)) {
_allGameBookAddrs.add(gameBookAddr);
emit eCreateGameBook(msg.sender, gameBookAddr);
} else {
revert("gamebook: create gamebook fail");
}
GameBookCreatorInfos[gameBookAddr] = _msgSender();
timeRecords[_msgSender()] = rule.time;
}

function getAllGameBooks() public view returns (address[] memory){
return _allGameBookAddrs.values();
}

}

这里说一下,为什么 BookUtil 的验证不直接写在 GameBook 里面,而是采用合约调用的方式。

因为,我怕,万一后端被人攻克后,数字签名就形同虚设了,而这个时候,通过更换 GameBook 里面的数字签名地址,更换验证逻辑。

使用

先部署 BookUtil 得到合约地址为 0xd9145CCE52D386f254917e481eB44e9943F39138

再部署 GameBookFactory 传入合约参数 0xd9145CCE52D386f254917e481eB44e9943F39138,最后部署的合约地址为 0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3

执行 GameBookFactorycreateGameBook,传入参数

["1000","100","10000","0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b","0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c"]

如下图所示

执行完成后,log 输入为

1
2
3
4
5
6
7
8
9
10
11
12
13
[
{
"from": "0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3",
"topic": "0xe91950a1c46aac78a2422f8326cc374e7a6c52b3952ca8f66badb714ced6a30a",
"event": "eCreateGameBook",
"args": {
"0": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"1": "0x568864A892a1B25127018Be020d2AF585Dff6c96",
"creator": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"gameBookAddr": "0x568864A892a1B25127018Be020d2AF585Dff6c96"
}
}
]

说明一个合约地址为 0x568864A892a1B25127018Be020d2AF585Dff6c96ERC721 已经部署成功。

为了能在 remix 中调用 0x568864A892a1B25127018Be020d2AF585Dff6c96 中的方法。

选择 GameBook 合约然后输入合约地址,点击 At Address,就成功出现了。

调用 0x568864A892a1B25127018Be020d2AF585Dff6c96 合约的 mint 传入参数

["1000","100","10000","0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b","0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c"]

最后成功,调用一下各个合约的其他函数,都能正确的返回。

请我喝杯咖啡吧~