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

posted on 2023-12-13 17:49  肥兔子爱豆畜子  阅读(220)  评论(0编辑  收藏  举报

导航