iT邦幫忙

2022 iThome 鐵人賽

DAY 29
1
Web 3

Road Map To DApp Developer系列 第 29

【DAY29】Verification System (Final Cut)

  • 分享至 

  • xImage
  •  

Preface

今天要來實作驗證系統的最後一步:將使用者的票卷燒毀,並再鑄造一張新的紀念票卷給使用者。為了維護我們的紀念票卷不被一些有心人士鑄造,同時還需要透過一些 modifier 來維護這個 function 的安全性

那就事不宜遲,往下看吧!

OnlyRole

這邊我們參考 OZ 的 AccessControl.sol 來實作出 onlyRole 的 modifier。

Struct and Mapping

首先我宣告了一個 struct,裏面裝了一個 mapping,另外在用一個 mapping 來映射它。(其實這邊也可用一個 double layered mapping 來實作,只是我覺得這個方式比較容易閱讀)。

struct roleData {
    mapping(address => bool) members;   
}
mapping (bytes32 => roleData) _roles;

GrantRole

另外用 _grantRoleInBatch() 這個函式幫助我們在一開始傳入驗證者所需要的身份認證,我使用類似 ERC1155 mintInBatch() 的方式。

並設計這個函式只有 owner 可以呼叫,以防別人透過這個函式取得身份來執行我們有限制的函式

constructor() {
    _owner = msg.sender;
}

modifier _onlyOwner {
    require(_owner == msg.sender, "Not Owner.");
    _;
}

event GrantRole(address, bytes32, address);
function _grantRoleInBatch(bytes32[] memory role, address[] memory addr) internal virtual _onlyOwner{
    for (uint i = 0; i < role.length; i++) {
        if (!hasRole(role[i], addr[i])) {
            _roles[role[i]].members[addr[i]] = true;
            emit GrantRole(addr[i], role[i], msg.sender);
        }
    }
}

function hasRole(bytes32 role, address addr) public view virtual returns (bool) {
    return _roles[role].members[addr];
}

Check and modifier

最後是 _checkRole(),由於我們的 verifySuccessed() 需要一次驗證兩種身份,因此我使用 function overloading 的方式來完成,若需要其他驗證單一身份的 function 則可以選擇性使用。

function _checkRole(bytes32 role1, bytes32 role2, address addr) internal view {
    if (!hasRole(role1, addr)) {
        revert (
            string("do not have the access")
        );
    }
    if (!hasRole(role2, addr)) {
        revert (
            string("do not have the access")
        );
    }
}

function _checkRole(bytes32 role, address addr) internal view {
    if (!hasRole(role, addr)) {
        revert (
            string("do not have the access")
        );
    }
}

modifier onlyTwoRole {
    bytes32 role1 = keccak256("MINTER_ROLE");
    bytes32 role2 = keccak256("BURNER_ROLE");
    _checkRole(role1, role2, msg.sender);
    _;
}

modifier onlySingleRole {
    bytes32 role = keccak256("MINTER_ROLE"); 
    // or change your rule if you want
    _checkRole(role, msg.sender);
    _;
}

Completed

最後在我們的合約中引用,並在 contructor 時傳入 verifySuccessed() 上加上 onlyTwoRole() 這個 modifier 便大功告成了!

function verifySuccessed(address user) public onlyTwoRole { 
    burn(user, 3, 1);
    _mint(user, 4, 1, "");
}

Deploy on chain

這邊想嘗試一個之前在 web3js 中沒試過的功能,就是透過 web3js 來部署到區塊鏈上。

但是實際操作下來,與我們在鏈上呼叫 contract 中的函式差不多,只是要先轉換 contract 的 byteCode 與預估所需要的 gas fee 而已。

步驟一:取得 provider 與獲取 signer。

const network = process.env.REACT_APP_ETHEREUM_NETWORK;
  const web3 = new Web3(
      new Web3.providers.HttpProvider(
          `https://${network}.infura.io/v3/${process.env.REACT_APP_INFURA_API_KEY}`
      )
  );
  // Creating a signing account from a private key
  const signer = web3.eth.accounts.privateKeyToAccount(
      process.env.REACT_APP_SIGNER_PRIVATE_KEY
  );
  web3.eth.accounts.wallet.add(signer);

步驟二:利用 contract abi 建立一個 Contract Object,利用這個 Object 計算部署所需要的 gas fee。

同時記得傳入 constructor 所需要的 parameters 與 bytecode:

bytecode 我是複製在 remix 中的資料,並用 import bytecode from... 來取得他,最後 deploy 所需要的資料在 bytecode.object 中。

const ticketContract = new web3.eth.Contract(abi);

const mintRole = buf2hex(keccak256("MINTER_ROLE"));
const burnRole = buf2hex(keccak256("BURNER_ROLE"));
const address = "0xaB50035F25F96dd3DEf1A7a9cC4E1C81AD6a7651";

ticketContract.deploy({
    data: bytecode.object, 
    arguments: [
      [mintRole, burnRole],
      [address, address]
    ]
  })
  .estimateGas((err, gas) => {
    console.log(gas)
  })

這邊計算出來得到 3908308

步驟三:最後則是透過 contract.deploy() 這個 method 成功部署合約到鏈上。

ticketContract.deploy({
    data: bytecode.object,
    arguments: [
      [mintRole, burnRole],
      [address, address]
    ]
  })
  .send({
      from: address,
      gas: 4000000,
  }, (error, transactionHash) => {

  })
  .on('error', error => { 
    console.log(error);
   })
  .on('transactionHash', transactionHash => { 
    console.log(transactionHash);
   })
  .on('receipt', receipt => {
    console.log(receipt.contractAddress) // contains the new contract address
  })
  .then( (newContractInstance) => {
      console.log(newContractInstance.options.address) // instance with the new contract address
  });

最後成功的部署到 goerli 測試鏈上!

Contract.methods.send()

接下來就是讓 Verifier 可以透過先填入私鑰在 .env 中,轉換成 signer.address 避免直接揭露他們的帳號在前端的程式碼中。

而前面呼叫 provider 的方式跟上面一樣,便不再贅述。

const ticketContract = new web3.eth.Contract(abi, contractAddress);
ticketContract.methods.verifySuccessed(address).send({
  from: address,
  gas: 5000000
})
.on('error', (error, receipt) => {
  console.log(receipt);
  console.log(error);
  return;
})
.on('transactionHash', (hash) => {
  console.log(hash);
});

最後我們成功的幫使用者 burn() 了原本的票和 _mint() 一張新的紀念票了!

Closing

今天算是專案開發的最後一天了!在這三十天的內容中,我學到了很多很多關於 ethereum 的知識與 DApp 開發的眉角,雖然
在途中遇到了超級多問題,但我還是慢慢的將他們解決了!

明天會總結這個專案的技術,並討論一下有哪些地方需要改進,最後會討論我未來可能會走的路!

Ref:


若有文章內有任何錯誤的地方歡迎指點與討論!非常感謝!

歡迎贊助窮困潦倒大學生
0xd8538ea74825080c0c80B9B175f57e91Ff885Cb4


上一篇
【DAY28】Verification System (Smart Contract II - Use Infura to send tx)
下一篇
【DAY30】結語與未來
系列文
Road Map To DApp Developer30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言