该合约主要实现以下几点
该案例来自于我的 web3
项目 wordslabs 中的一部分。
代码 其主要思路如下
这里直接贴一下代码
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)); } }
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
执行 GameBookFactory
的 createGameBook
,传入参数
["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" } } ]
说明一个合约地址为 0x568864A892a1B25127018Be020d2AF585Dff6c96
的 ERC721
已经部署成功。
为了能在 remix
中调用 0x568864A892a1B25127018Be020d2AF585Dff6c96
中的方法。
选择 GameBook
合约然后输入合约地址,点击 At Address
,就成功出现了。
调用 0x568864A892a1B25127018Be020d2AF585Dff6c96
合约的 mint
传入参数
["1000","100","10000","0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b","0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c"]
最后成功,调用一下各个合约的其他函数,都能正确的返回。