今天要來實作驗證系統的最後一步:將使用者的票卷燒毀,並再鑄造一張新的紀念票卷給使用者。為了維護我們的紀念票卷不被一些有心人士鑄造,同時還需要透過一些 modifier 來維護這個 function 的安全性。
那就事不宜遲,往下看吧!
這邊我們參考 OZ 的 AccessControl.sol
來實作出 onlyRole
的 modifier。
首先我宣告了一個 struct,裏面裝了一個 mapping,另外在用一個 mapping 來映射它。(其實這邊也可用一個 double layered mapping 來實作,只是我覺得這個方式比較容易閱讀)。
struct roleData {
mapping(address => bool) members;
}
mapping (bytes32 => roleData) _roles;
另外用 _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];
}
最後是 _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);
_;
}
最後在我們的合約中引用,並在 contructor 時傳入 verifySuccessed()
上加上 onlyTwoRole()
這個 modifier 便大功告成了!
function verifySuccessed(address user) public onlyTwoRole {
burn(user, 3, 1);
_mint(user, 4, 1, "");
}
這邊想嘗試一個之前在 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()
一張新的紀念票了!
今天算是專案開發的最後一天了!在這三十天的內容中,我學到了很多很多關於 ethereum 的知識與 DApp 開發的眉角,雖然
在途中遇到了超級多問題,但我還是慢慢的將他們解決了!
明天會總結這個專案的技術,並討論一下有哪些地方需要改進,最後會討論我未來可能會走的路!
若有文章內有任何錯誤的地方歡迎指點與討論!非常感謝!
歡迎贊助窮困潦倒大學生
0xd8538ea74825080c0c80B9B175f57e91Ff885Cb4