zkSyncにSolidity製のdappをデプロイする
リソース
公式Docs。zkSyncは2.0が最新なので1.0系のDocsと間違えないように気をつける。
Want to dive directly into the code? Head straight to the Developer guide.
zkSyncのテストネットのExplorer
EthereumのテストネットのGoerliのExplorer
一番最新のdapp on zkSyncの実践解説。
メモ
.gitignoreにartifacts-zkとcache-zkを入れた
Goeliにデプロイしたらエラーが出た
TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator)) at Deployer.deploy
const deployed = await deployer.deploy(artifact);こんな感じに実装してたけど、この第二引数に空の引数を指定しないとダメだった
デプロイの仕組み
How deploying contracts works on Ethereum. To deploy a contract, a user sends a transaction to the zero address (0x000...000) with the data field of the transaction equal to the contract bytecode concatenated with the constructor parameters.
How deploying contracts works on zkSync. To deploy a contract, a user sends a transaction to the zero address (0x000...000) with the data field for the transaction equal to the contract bytecode hash concatenated with the constructor parameters. The contract bytecode itself is supplied in the factory_deps field of the EIP712 transactions. If the contract is a factory (i.e. it can deploy other contracts), these contracts' bytecodes should be included in the factory_deps as well.
デプロイしたがどこにコントラクトがあるかわからない...
デプロイしたコード
code:hardhat.config.js
require("@matterlabs/hardhat-zksync-deploy");
require("@matterlabs/hardhat-zksync-solc");
module.exports = {
zksolc: {
version: "0.1.0",
compilerSource: "docker",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
experimental: {
dockerImage: "matterlabs/zksolc",
},
},
},
zkSyncDeploy: {
ethNetwork: "goerli",
},
solidity: {
version: "0.8.12",
},
networks: {
hardhat: {
zksync: true,
},
},
};
code:js
const { ethers, upgrades } = require("hardhat");
const { utils, Wallet } = require("zksync-web3");
const { Deployer } = require("@matterlabs/hardhat-zksync-deploy");
const functionName = "TwitterV1";
module.exports = async function (hre) {
console.log("Start deploy!");
const wallet = new Wallet(process.env.PRIVATE_KEY);
const deployer = new Deployer(hre, wallet);
const artifact = await deployer.loadArtifact(functionName);
const depositAmount = ethers.utils.parseEther("0.001");
const depositHandle = await deployer.zkWallet.deposit({
to: deployer.zkWallet.address,
token: utils.ETH_ADDRESS,
amount: depositAmount,
});
await depositHandle.wait();
const deployed = await deployer.deploy(artifact, []);
const contractAddress = deployed.address;
console.log(${functionName} deployed to:, contractAddress);
// Copying ABIs to frontend.
const fs = require("fs");
const contractDir = __dirname + "/../../frontend/src/resources";
const compiledArtifact = artifacts.readArtifactSync(functionName);
fs.writeFileSync(
contractDir + "/contract-abi.json",
JSON.stringify(compiledArtifact, null, 2)
);
};
あった。zkSyncのテストネットのExplorerで検索できた
ストレージのデータはどこにあるの...
while computation and storage are performed off-chain.
L1のみ使ってると混乱しがちだが、zkSyncを使うときはL1とL2の両方を利用するということを念頭においておく。
具体的にはデータはL1、コントラクトはL2にある。
フロントエンドはMetamaskはGoerliだがプロバイダーはzkSyncを使う必要がある。設定を迷うことがある。
出来たこと
既存のSolidityのコントラクトをzksolcでコンパイルしてzkSyncのテストネット(バックエンドはGoerli)にデプロイ
フロントエンド側でzkSyncにデプロイしたコントラクトにツイートのトランザクションを発生させてGoerliに保存
-> なんかできてなかったっぽい...。GoerliのWalletでGoerliにtransactionを通しただけだった...
getFollowingsなどのシンプルなデータをリターンするview関数はエラーなく返ってくる
しかしsetTweetなどの書き込み系は全滅。一部の処理を含むview系の関数も動かない
コントラクトを書くときの違い
As you can see, developing on zkSync 2.0 isn’t that different from developing on any other EVM chain, but it has its nuances like the ability to pay transaction fees in ERC20 tokens. Another thing to watch out is, that ETH is an ERC20 token as well. So any payable functions in the smart contracts will not work.
setTweetをした時に出るエラー
Cannot estimate transaction: VM execution resulted in a revert: VM Error.
似たようなエラーに遭遇した人いた
うまく行かなすぎて諦めた
GWに時間が取れそうなのでまた動かすチャレンジを始めた
ローカルにzkSyncの環境を作り、コントラクトのテストを一行ずつデバッグして何が動かない原因なのか調べる
_safeMintでtransaction may fail or may require manual gas limitのエラーが出る
Discordに_safeMintはまだサポートされてないと書いてあった。リンク code:txt
For NFT project, we've been asked several times about minting not working on our testnet. If you use _safeMint, use _mint instead.
We will update our docs to be more explicit about it.
Hardhat + chaiのTypescriptテストはこのリポジトリを参考にした
code:sh
yarn add -D @nomiclabs/hardhat-waffle chai-as-promised @nomiclabs/hardhat-ethers @types/chai-as-promised
code:hardhat.config.ts
import { config as dotEnvConfig } from "dotenv";
dotEnvConfig();
import "@nomiclabs/hardhat-waffle";
import "@matterlabs/hardhat-zksync-deploy";
import "@matterlabs/hardhat-zksync-solc";
console.log(
NODE_ENV: ${process.env.NODE_ENV}, RICH_WALLET_PK: ${process.env.RICH_WALLET_PK}
);
const zkSyncDeploy =
process.env.NODE_ENV == "test"
? {
}
: {
ethNetwork: "goerli",
};
module.exports = {
zksolc: {
version: "0.1.0",
compilerSource: "docker",
settings: {
optimizer: {
enabled: true,
},
experimental: {
dockerImage: "matterlabs/zksolc",
},
},
},
zkSyncDeploy,
solidity: {
version: "0.8.10",
},
networks: {
// To compile with zksolc, this must be the default network.
hardhat: {
zksync: true,
},
},
};
code:test.ts
import * as hre from "hardhat";
import chai from "chai";
import chaiAsPromised from "chai-as-promised";
import { Wallet, Provider } from "zksync-web3";
import { Deployer } from "@matterlabs/hardhat-zksync-deploy";
chai.use(chaiAsPromised);
const { expect } = chai;
const RICH_WALLET_PK =
"0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110";
async function setup() {
const provider = Provider.getDefaultProvider();
const wallet = new Wallet(RICH_WALLET_PK, provider);
const deployer = new Deployer(hre, wallet);
const artifact = await deployer.loadArtifact("TwitterV1");
const deployed = await deployer.deploy(artifact, []);
return { twitter: deployed, owner: wallet };
}
describe("Twitter", function () {
describe("setTweet", function () {
it("Should return error", async function () {
const { twitter } = await setup();
expect(twitter.setTweet(" ")).to.eventually.be.rejected;
});
});
describe("getTimeline", function () {
it("Should return the tweet", async function () {
const { twitter, owner } = await setup();
const tx = await twitter.setTweet("Hello, world!", "");
await tx.wait();
const tweets = await twitter.getTimeline(0, 10);
expect(tweet.content).to.equal("Hello, world!");
expect(tweet.author).to.equal(owner.address);
});
});
describe("getUserTweets", function () {
it("Should return the tweets order by timestamp desc", async function () {
const { twitter, owner } = await setup();
let tx = await twitter.setTweet("Hello, world!", "");
await tx.wait();
tx = await twitter.setTweet("Hello, new world!", "");
await tx.wait();
const tweets = await twitter.getUserTweets(owner.address);
expect(tweet.content).to.equal("Hello, new world!");
expect(tweet.author).to.equal(owner.address);
});
it("Should return the tweet", async function () {
const { twitter, owner } = await setup();
let tx = await twitter.setTweet(
"Hello, world!",
"data:image/png;base64,XXXX"
);
await tx.wait();
const tweets = await twitter.getUserTweets(owner.address);
expect(tweet.content).to.equal("Hello, world!");
expect(tweet.author).to.equal(owner.address);
expect(tweet.attachment).to.equal("data:image/png;base64,XXXX");
});
});
describe("getTweet", function () {
it("Should return the tweet", async function () {
const { twitter, owner } = await setup();
let tx = await twitter.setTweet("Hello, world!", "");
await tx.wait();
const tweet = await twitter.getTweet(1);
expect(tweet.content).to.equal("Hello, world!");
expect(tweet.author).to.equal(owner.address);
});
});
describe("follow", function () {
it("Should follow user", async function () {
const { twitter, owner } = await setup();
const user = await hre.ethers.getSigners();
let tx = await twitter.follow(user.address);
await tx.wait();
const followings = await twitter.getFollowings(owner.address);
const following = followings0; expect(following.id).to.equal(user.address);
const followers = await twitter.getFollowers(user.address);
const follower = followers0; expect(follower.id).to.equal(owner.address);
});
});
describe("getFollowings", function () {
it("Should unfollow user", async function () {
const { twitter, owner } = await setup();
const user, user2 = await hre.ethers.getSigners();
let tx = await twitter.follow(user.address);
await tx.wait();
tx = await twitter.follow(user2.address);
await tx.wait();
let followings = await twitter.getFollowings(owner.address);
expect(followings.length).to.equal(2);
let followers = await twitter.getFollowers(user.address);
expect(followers.length).to.equal(1);
followers = await twitter.getFollowers(user2.address);
expect(followers.length).to.equal(1);
tx = await twitter.unfollow(user.address);
await tx.wait();
followings = await twitter.getFollowings(owner.address);
expect(followings.length).to.equal(1);
followers = await twitter.getFollowers(user.address);
expect(followers.length).to.equal(0);
followers = await twitter.getFollowers(user2.address);
expect(followers.length).to.equal(1);
});
});
describe("isFollowing", function () {
it("Should true if follow the address", async function () {
const { twitter, owner } = await setup();
const user = await hre.ethers.getSigners();
let tx = await twitter.follow(user.address);
await tx.wait();
const following = await twitter.isFollowing(user.address);
expect(following).to.equal(true);
});
});
describe("addLike", function () {
it("Should add a like to the tweet", async function () {
const { twitter, owner } = await setup();
let tx = await twitter.setTweet("Hello, world!", "");
await tx.wait();
let tweets = await twitter.getUserTweets(owner.address);
expect(tweet.likes.includes(owner.address)).to.be.false;
tx = await twitter.addLike(tweet.tokenId);
await tx.wait();
tweets = await twitter.getUserTweets(owner.address);
expect(tweet.likes.includes(owner.address)).to.be.true;
});
});
describe("getLikes", function () {
it("Should return liked tweets", async function () {
const { twitter, owner } = await setup();
let tx = await twitter.setTweet("Hello, world!", "");
await tx.wait();
let tweets = await twitter.getLikes(owner.address);
expect(tweets.length).to.equal(0);
tweets = await twitter.getUserTweets(owner.address);
tx = await twitter.addLike(tweet.tokenId);
await tx.wait();
tweets = await twitter.getLikes(owner.address);
expect(tweet.likes.includes(owner.address)).to.be.true;
});
});
describe("changeIconUrl/getUserIcon", function () {
it("Should change icon url", async function () {
const { twitter, owner } = await setup();
let url = await twitter.getUserIcon(owner.address);
expect(url).to.equal("");
await tx.wait();
url = await twitter.getUserIcon(owner.address);
});
});
describe("setComment/getComments", function () {
it("Should add the comment", async function () {
const { twitter, owner } = await setup();
let tx = await twitter.setTweet("Hello, world!", "");
await tx.wait();
tx = await twitter.setComment("Hello, comment!", 1);
await tx.wait();
const comments = await twitter.getComments(1);
const comment = comments0; expect(comment.content).to.equal("Hello, comment!");
expect(comment.author).to.equal(owner.address);
});
});
describe("addRetweet", function () {
it("Should add the rt", async function () {
const { twitter, owner } = await setup();
let tx = await twitter.setTweet("Hello, world!", "");
await tx.wait();
tx = await twitter.addRetweet(1);
await tx.wait();
let tweets = await twitter.getTimeline(0, 2);
expect(tweets1.retweets.includes(owner.address)).to.be.true; expect(tweets1.retweetedBy).to.eq( "0x0000000000000000000000000000000000000000"
);
expect(tweets0.retweets.includes(owner.address)).to.be.true; expect(tweets0.retweetedBy).to.eq(owner.address); });
});
describe("tokenURI", function () {
it("Should return base64 encoded string", async function () {
const { twitter, owner } = await setup();
let tx = await twitter.setTweet("Hello, world!", "");
await tx.wait();
const tokenURI = await twitter.tokenURI(1);
expect(tokenURI).to.eq(
"data:application/json;base64,eyJuYW1lIjoiVHdlZXQgIzEiLCAiZGVzY3JpcHRpb24iOiJIZWxsbywgd29ybGQhIiwgImltYWdlIjogImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIQnlaWE5sY25abFFYTndaV04wVW1GMGFXODlJbmhOYVc1WlRXbHVJRzFsWlhRaUlIWnBaWGRDYjNnOUlqQWdNQ0F6TlRBZ016VXdJajQ4Y21WamRDQjNhV1IwYUQwaU1UQXdKU0lnYUdWcFoyaDBQU0l4TURBbElpQm1hV3hzUFNJallXRmlPR015SWo0OEwzSmxZM1ErUEhOM2FYUmphRDQ4Wm05eVpXbG5iazlpYW1WamRDQjRQU0l3SWlCNVBTSXdJaUIzYVdSMGFEMGlNVEF3SlNJZ2FHVnBaMmgwUFNJeE1EQWxJajQ4Y0NCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TVRrNU9TOTRhSFJ0YkNJZ1ptOXVkQzF6YVhwbFBTSXhNbkI0SWlCemRIbHNaVDBpWm05dWRDMXphWHBsT2pFd2NIZzdjR0ZrWkdsdVp6bzFjSGc3SWo1VWQyVmxkQ014UEdKeUx6NUlaV3hzYnl3Z2QyOXliR1FoUEdKeUx6NDhhVzFuSUhOeVl6MGlJaTgrUEM5d1Bqd3ZabTl5WldsbmJrOWlhbVZqZEQ0OEwzTjNhWFJqYUQ0OEwzTjJaejQ9In0="
);
});
});
});
動いた!!!!