Solidity智能合约例子:存证合约
一、合约编写
感谢b站上的UP老哥冲少,这里参考了他的视频。一共是4个合约,1个是权限控制,1个存证、1个存证申请(是否可以存证有投票机制)、还一个入口合约。
Authentication.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
contract Authentication {
address public _owner;
mapping (address => bool) private _acl;
constructor() {
_owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == _owner, "Not Admin");
_;
}
modifier auth(){
require(msg.sender==_owner || _acl[msg.sender]==true, "Not Authenticated");
_;
}
function allow(address addr) public onlyOwner{
_acl[addr] = true;
}
function deny(address addr) public onlyOwner{
_acl[addr] = false;
}
}
EvidenceRepository.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "./Authentication.sol";
contract EvidenceRepository is Authentication{
struct EvidenceData{
bytes32 hash;
address owner;
uint timestamp;
}
mapping(bytes32=>EvidenceData) private _evidences; //所有存证数据,用data hash进行索引
function setData(bytes32 hash, address owner, uint timestamp) public auth{
_evidences[hash].hash = hash;
_evidences[hash].owner = owner;
_evidences[hash].timestamp = timestamp;
}
function getData(bytes32 hash) public view returns(bytes32, address, uint) {
EvidenceData storage evidence = _evidences[hash];
require(evidence.hash == hash, "Evidence not exist");
return (evidence.hash, evidence.owner, evidence.timestamp);
}
}
RequestRepository.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "./Authentication.sol";
contract RequestRepository is Authentication {
struct SaveRequest{
bytes32 hash; //data hash
address creator; //存证创建人
uint8 voted; //投了多少票了
bytes desc;
mapping(address=>bool) status; //投票人是否针对可以存证进行了投票
}
uint8 public _threshold; //达到多少票算通过,可以存证
mapping(bytes32=>SaveRequest) private _saveRequests;
mapping(address=>bool) private _voters;
constructor(uint8 threshold, address[] memory voterArray) {
_threshold = threshold;
for(uint i=0; i<voterArray.length; i++){
_voters[voterArray[i]] = true;
}
}
function createSaveRequest(bytes32 hash, address owner, bytes memory desc) public auth{
require(_saveRequests[hash].hash == 0, "request already existed");
_saveRequests[hash].hash = hash;
_saveRequests[hash].creator = owner;
_saveRequests[hash].desc = desc;
}
function voteSaveRequest(bytes32 hash, address voter) public auth returns (bool) {
require(_voters[voter] == true, "Not allowed to vote");
require(_saveRequests[hash].hash == hash, "request not found");
SaveRequest storage request = _saveRequests[hash];
require(request.status[voter] == false, "Voter already voted");
request.status[voter] = true;
request.voted++;
return true;
}
function getRequestData(bytes32 hash) public view
returns(bytes32, address creator, bytes memory desc, uint8 voted, uint8 threshold) {
SaveRequest storage request = _saveRequests[hash];
require(_saveRequests[hash].hash==hash, "request not found");
return (hash, request.creator, request.desc, request.voted, _threshold);
}
function deleteSaveRequest(bytes32 hash) public auth{
require(_saveRequests[hash].hash==hash, "request not found");
delete _saveRequests[hash];
}
}
EvidenceController.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "./EvidenceRepository.sol";
import "./RequestRepository.sol";
contract EvidenceController {
EvidenceRepository public _evidenceRepo;
RequestRepository public _requestRepo;
event CreateSaveRequestEvent(bytes32 indexed hash, address creator);
event VoteSaveRequestEvent(bytes32 indexed hash, address voter, bool complete);
event EvidenceSavedEvent(bytes32 indexed hash);
constructor(uint8 threshold, address[] memory voterArray) {
_evidenceRepo = new EvidenceRepository();
_requestRepo = new RequestRepository(threshold, voterArray);
}
modifier validDataHash(bytes32 hash){
require(hash != 0, "Not valid hash");
_;
}
function createSaveRequest(bytes32 hash, bytes memory ext) public validDataHash(hash) {
_requestRepo.createSaveRequest(hash, msg.sender, ext);
emit CreateSaveRequestEvent(hash, msg.sender);
}
function voteSaveRequest(bytes32 hash) public validDataHash(hash) returns (bool) {
bool b = _requestRepo.voteSaveRequest(hash, msg.sender);
if(!b){
return false;
}
(bytes32 h, address creator, bytes memory ext, uint8 voted, uint8 threshold)
= _requestRepo.getRequestData(hash);
bool passed = voted >= threshold;
emit VoteSaveRequestEvent(hash, msg.sender, passed);
if(passed){
_evidenceRepo.setData(hash, creator, block.timestamp);
_requestRepo.deleteSaveRequest(hash);
emit EvidenceSavedEvent(hash);
}
return true;
}
function getRequestData(bytes32 hash) public view
returns (bytes32, address creator, bytes memory ext, uint8 voted, uint8 threshold) {
return _requestRepo.getRequestData(hash);
}
function getEvidence(bytes32 hash) public view returns (bytes32, address, uint) {
return _evidenceRepo.getData(hash);
}
}
二、部署与测试
部署脚本deploy.js
const { ethers } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the Account:", deployer.address);
const votersWhiteList = ["0xF7A1938Fecc594aaF126d46fd173cE74A659ad9A",
"0x72BbA0fE9D0dA19b27705BCdDAb783BeED24bbDb",
"0x5e4105b9d7C6a5D7a9ce282F36018E95e76Cf95C"];
const EvidenceController = await ethers.deployContract("EvidenceController" , [2, votersWhiteList] );
console.log("EvidenceController Address:", await EvidenceController.getAddress());
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
部署到ganache:
>npx hardhat run scripts/deploy.js --network ganache
Deploying contracts with the Account: 0xF7A1938Fecc594aaF126d46fd173cE74A659ad9A
EvidenceController Address: 0xCC6075edc208F8531BbE62751590C48390B65e18
测试脚本call.js
const { ethers } = require("hardhat");
const contractABI = [
{
"inputs": [
{
"internalType": "uint8",
"name": "threshold",
"type": "uint8"
},
{
"internalType": "address[]",
"name": "voterArray",
"type": "address[]"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "hash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address",
"name": "creator",
"type": "address"
}
],
"name": "CreateSaveRequestEvent",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "hash",
"type": "bytes32"
}
],
"name": "EvidenceSavedEvent",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "hash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address",
"name": "voter",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "complete",
"type": "bool"
}
],
"name": "VoteSaveRequestEvent",
"type": "event"
},
{
"inputs": [],
"name": "_evidenceRepo",
"outputs": [
{
"internalType": "contract EvidenceRepository",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "_requestRepo",
"outputs": [
{
"internalType": "contract RequestRepository",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "hash",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "ext",
"type": "bytes"
}
],
"name": "createSaveRequest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "hash",
"type": "bytes32"
}
],
"name": "getEvidence",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "hash",
"type": "bytes32"
}
],
"name": "getRequestData",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "bytes",
"name": "ext",
"type": "bytes"
},
{
"internalType": "uint8",
"name": "voted",
"type": "uint8"
},
{
"internalType": "uint8",
"name": "threshold",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "hash",
"type": "bytes32"
}
],
"name": "voteSaveRequest",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
];
async function main(){
const contractAddress = "0xCC6075edc208F8531BbE62751590C48390B65e18";
const provider = ethers.getDefaultProvider("http://localhost:7545/");
//获取地址有多少ETH,获取当前区块高度
const queryAddress = "0xF7A1938Fecc594aaF126d46fd173cE74A659ad9A";
let ethBalance = await provider.getBalance(queryAddress);
console.log("地址%s有%s个ETH", queryAddress, ethBalance);
let blockNumber = await provider.getBlockNumber();
console.log("当前链上区块高度%s", blockNumber);
//调用合约方法
//查询
let evidenceContract = await new ethers.Contract(contractAddress, contractABI, provider);
//写(调用合约新建存证方法)
const sha256hash = "0x4d633e712a045622a49398476ddd54ee0ffdb7e8881757724918b795fc5ea06c";
let signer = await new ethers.Wallet("0x624cfb2cb9b9c3212abfa2800fd8de8e2501c84500336007f4459c50198c69f7", provider);
let evidenceContractConnected = await evidenceContract.connect(signer);
//创建一个存证请求
console.log("==================>新建存证请求");
const ext = ethers.toUtf8Bytes("feituzi ai douchuzi");
let txn = await evidenceContractConnected.createSaveRequest(sha256hash, ext);
console.log("创建存证请求, 交易结果: %s", txn);
console.log("==================>查询存证请求");
let request = await evidenceContractConnected.getRequestData(sha256hash);
//bytes32, address creator, bytes memory ext, uint8 voted, uint8 threshold
console.log("存证请求内容, datahash:%s, 创建人:%s, 附加信息:%s, 已投票:%s, 共需投票:%s",
request[0], request[1], request[2], request[3], request[4]);
//存证请求需要两个以上地址vote,第一个投票
console.log("==================>存证请求需要两个以上地址vote");
let result = await evidenceContractConnected.voteSaveRequest(sha256hash);
console.log("%s对存证请求的投票结果%s", await signer.getAddress(), result);
//第二个投票
signer = await new ethers.Wallet("0x68ef8f95e6c8bc2411a6f4ca995d5ffbe73d146a43f829c40002f1fe57fe305d", provider);
evidenceContractConnected = await evidenceContract.connect(signer);
result = await evidenceContractConnected.voteSaveRequest(sha256hash);
console.log("%s对存证请求的投票结果%s", await signer.getAddress(), result);
console.log("==================>查询存证结果");
let evidence = await evidenceContractConnected.getEvidence(sha256hash);
console.log("存证内容DataHash:%s, 存证人Address:%s, 存证时间戳:%s", evidence[0], evidence[1], evidence[2]);
}
main()
.then(() => {
process.exit(0);
})
.catch((error) => {
console.error(error);
process.exit(1);
});
输出:
>npx hardhat run scripts/call.js --network ganache
地址0xF7A1938Fecc594aaF126d46fd173cE74A659ad9A有99979908610956548638n个ETH
当前链上区块高度16
==================>新建存证请求
创建存证请求, 交易结果: ContractTransactionResponse {
provider: JsonRpcProvider {},
blockNumber: null,
blockHash: null,
index: undefined,
hash: '0x4878174613c432e93ee101a4537c4e06de250cc77fa788ef6a574e2b0b904a0b',
type: 2,
to: '0xCC6075edc208F8531BbE62751590C48390B65e18',
from: '0xF7A1938Fecc594aaF126d46fd173cE74A659ad9A',
nonce: 15,
gasLimit: 101692n,
gasPrice: undefined,
maxPriorityFeePerGas: 1000000000n,
maxFeePerGas: 1420328120n,
data: '0x8c7536af4d633e712a045622a49398476ddd54ee0ffdb7e8881757724918b795fc5ea06c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001366656974757a6920616920646f756368757a6900000000000000000000000000',
value: 0n,
chainId: 1337n,
signature: Signature { r: "0xa7ee9a9892bcb9d3e84336bfcd435c7bb5aa7462c5f96c1475fbca01e3a13c40", s: "0x6077dba0e1554f172a7f38ac7c4e0db54b792c8c2a4ef740870be77becb5b3ed", yParity: 1, networkV: null },
accessList: []
}
==================>查询存证请求
存证请求内容, datahash:0x4d633e712a045622a49398476ddd54ee0ffdb7e8881757724918b795fc5ea06c, 创建人:0xF7A1938Fecc594aaF126d46fd173cE74A659ad9A, 附加信息:0x66656974757a6920616920646f756368757a69, 已投票:0n, 共需投票:2n
==================>存证请求需要两个以上地址vote
0xF7A1938Fecc594aaF126d46fd173cE74A659ad9A对存证请求的投票结果ContractTransactionResponse {
provider: JsonRpcProvider {},
blockNumber: null,
blockHash: null,
index: undefined,
hash: '0xbb8a1acf8993ff091a8fed2304086b478af3321441936d934d7eee11d9bdfee0',
type: 2,
to: '0xCC6075edc208F8531BbE62751590C48390B65e18',
from: '0xF7A1938Fecc594aaF126d46fd173cE74A659ad9A',
nonce: 16,
gasLimit: 74712n,
gasPrice: undefined,
maxPriorityFeePerGas: 1000000000n,
maxFeePerGas: 1369376818n,
data: '0xab9beda74d633e712a045622a49398476ddd54ee0ffdb7e8881757724918b795fc5ea06c',
value: 0n,
chainId: 1337n,
signature: Signature { r: "0xdf53f19910951b0e909dcdd0019fc0be14c593559225fbdd80b0474cb3927135", s: "0x29e7c9ec820132a504978e3a3f2634c307332768d6bb8f2df6a5cc43fa6d2b08", yParity: 1, networkV: null },
accessList: []
}
0x5e4105b9d7C6a5D7a9ce282F36018E95e76Cf95C对存证请求的投票结果ContractTransactionResponse {
provider: JsonRpcProvider {},
blockNumber: null,
blockHash: null,
index: undefined,
hash: '0x53445ece5e25cc99557b169274f6f45a1b2705dee1c735e28934ecb700aa1251',
type: 2,
to: '0xCC6075edc208F8531BbE62751590C48390B65e18',
from: '0x5e4105b9d7C6a5D7a9ce282F36018E95e76Cf95C',
nonce: 1,
gasLimit: 159052n,
gasPrice: undefined,
maxPriorityFeePerGas: 1000000000n,
maxFeePerGas: 1324601728n,
data: '0xab9beda74d633e712a045622a49398476ddd54ee0ffdb7e8881757724918b795fc5ea06c',
value: 0n,
chainId: 1337n,
signature: Signature { r: "0xd48914d918cb84f5ef267ede0eda21ba4212928b4c9bc07411e8dc5faf22f14e", s: "0x6965f60c9d2858c091be6a028d942fd6b66e9e1271ec47757e99581ea2a4564d", yParity: 1, networkV: null },
accessList: []
}
==================>查询存证结果
存证内容DataHash:0x4d633e712a045622a49398476ddd54ee0ffdb7e8881757724918b795fc5ea06c, 存证人Address:0xF7A1938Fecc594aaF126d46fd173cE74A659ad9A, 存证时间戳:1702459055n
根据txHash查询交易和存证内容
const contractAddress = "0xCC6075edc208F8531BbE62751590C48390B65e18";
const provider = ethers.getDefaultProvider("http://localhost:7545/");
let evidenceContract = await new ethers.Contract(contractAddress, contractABI, provider);
const txHash = "0x4878174613c432e93ee101a4537c4e06de250cc77fa788ef6a574e2b0b904a0b";
let transactinResponse = await provider.getTransaction(txHash);
console.log("根据交易hash:%s 查询交易结果%s", txHash, transactinResponse);
输出
根据交易hash:0x4878174613c432e93ee101a4537c4e06de250cc77fa788ef6a574e2b0b904a0b 查询交易结果
TransactionResponse {
provider: JsonRpcProvider {},
blockNumber: 17,
blockHash: '0x720f023c40936ce4c869d7e7642d9ef8a97e3710d46fe54e44d5d90d68b2975e',
index: undefined,
hash: '0x4878174613c432e93ee101a4537c4e06de250cc77fa788ef6a574e2b0b904a0b',
type: 2,
to: '0xCC6075edc208F8531BbE62751590C48390B65e18',
from: '0xF7A1938Fecc594aaF126d46fd173cE74A659ad9A',
nonce: 15,
gasLimit: 101692n,
gasPrice: 1184688409n,
maxPriorityFeePerGas: 1000000000n,
maxFeePerGas: 1420328120n,
data: '0x8c7536af4d633e712a045622a49398476ddd54ee0ffdb7e8881757724918b795fc5ea06c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001366656974757a6920616920646f756368757a6900000000000000000000000000',
value: 0n,
chainId: 1337n,
signature: Signature { r: "0xa7ee9a9892bcb9d3e84336bfcd435c7bb5aa7462c5f96c1475fbca01e3a13c40", s: "0x6077dba0e1554f172a7f38ac7c4e0db54b792c8c2a4ef740870be77becb5b3ed", yParity: 1, networkV: null },
accessList: []
}
如上,data字段中包含8c7536af
是合约方法createSaveRequest(bytes32,bytes)
的函数接口名,以及2个实际传入的参数。
Keccak256(createSaveRequest(bytes32,bytes))再取前8个字符(32位)就是8c7536af
那么可以获取到传入的datahash:0x4d633e712a045622a49398476ddd54ee0ffdb7e8881757724918b795fc5ea06c
,再根据datahash去调用合约方法getEvidence(bytes32 hash)
就可以查询存证内容了。
参考
https://learnblockchain.cn/2018/01/04/understanding-smart-contracts/
https://learnblockchain.cn/article/2517
https://docs.ethers.org/v6/api/providers/#Provider-getTransaction