这是读研整的第一个活,要求如下
调研一下编译器solang,从solidity 到wasm,是不是真的好用。
得找一些例子,下载一些solidity合约代码,先用以太坊官方编译器编译执行能跑。然后试试solang是否能成功编译。
这个工具在开发状态,可能不够好用。
整活的前提是充分理解自己要干的事。在我自己对区块链了解几乎一无所知的前提下,从要求的描述中,应当将它解析成以下几个小任务:
那么接下来就按步骤依次解决吧。
什么是Solidity?
以太坊Solidity是一种面向智能合约的高级语言,其语法与JavaScript类似。solidity是用于生成在EVM上执行的机器级代码的工具。solidity编译器获取高级代码并将其分解为更简单的指令。Solidity代码封装在Contracts中。
Solidity 是静态类型语言,支持继承、库和复杂的用户定义类型等特性。
使用 Solidity 语言,可以为投票、众筹、秘密竞价(盲拍)、多重签名的钱包以及其他应用创建合约。
参考链接: solidity教程:solidity语言入门.
参考链接: solidity官方文档.
什么是wasm?
WebAssembly/wasm WebAssembly 或者 wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式。
WebAssembly(WASM)是一种新的编码方式,可以在现代的网络浏览器中运行,它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C/C++/Rust 等语言提供一个编译目标,以便它们可以在 Web 上运行。WebAssembly 的开发团队分别来自 Mozilla、Google、Microsoft、Apple 等公司,标准由 W3C 组织制定,目前 WebAssembly 在以太坊下一代虚拟机(EWASM)以及 EOS、Dfinity 项目中被使用。
参考链接: wasm官方文档.
参考链接: WebAssembly完全入门——了解wasm的前世今身.
目前,在我看来,solidity是一个在区块链上写应用的语言,而wasm是一个类似但超越js的一种格式。这乍一看没啥关系啊,怎么能从solidity转化为wasm呢?(可能不正确)
首先,我们关注一下solang网址中的内容。
欢迎使用Solang,这是一种新的用rust编写的Solidity编译器,它使用llvm作为编译器后端。 其结果是仅编译器前端需要被编写为rust。
Solang的目标是Substrate, ewasm, and Sawtooth。
Solang目前正在积极开发中,实现的同时也在不断记录。 请查看我们的文档。
尽管我不知道这个Substrate, ewasm, and Sawtooth都是啥,但是这个ewasm有点类似我们想要的wasm。
于是去简单查阅一下,在以太坊WebAssembly(ewasm)文档中我们发现了它。
以太坊WebAssembly是使用WebAssembly的确定性子集对以太坊智能合约执行层进行的重新设计。
将WebAssembly用作智能合约的格式可获得多种好处,以下列出了其中的一些好处:
-智能合约的执行速度接近于本地
-可以使用许多传统的编程语言(例如C, C ++和Rust)
-访问庞大的开发人员社区和WebAssembly周围的工具链
虽然我还是不太了解ewasm是干啥的,但是起码ewasm和solidity是差不多一回事了,都用作智能合约的格式。(可能不正确)
在以太坊WebAssembly(ewasm)官方文档中,我们发现文档提供了多种构建solang的方法,如下:
这里采用第一种方法,即使用hyperledgerlabs / solang docker hub映像安装solang。
首先要安装docker,我们根据文章:Windows Docker 安装的方法安装docker。但是在此文基于的windows版本是专业版,而我使用的是家庭中文版,于是根据文章:win10 家庭中文版没有Hyper-V,这样安装一步搞定的方法安装Hyper-V。
在安装docker后,启动docker,在命令行输入docker pull hyperledgerlabs/solang
,docker便自动拉取solang的内容。
在此之后,要确认是否安装了LLVM,版本8.0以上,我下载了 LLVM下载页中的Windows(64位) (.sig) 安装时不要忘了添加 LLVM 到 path。
这个,虽然官方要求使用代码(如下)验证LLVM是否安装成功,但是我这反正没正确显示。
llvm-config --version
llc --version
llvm-config --link-static --libs
首先从docker hub提取最后一个Solang映像 :
docker pull hyperledgerlabs/solang
然后运行Solang:
docker run --rm -it hyperledgerlabs/solang --version
如果要编译某些Solidity文件,则源文件需要在容器内可用。您可以通过-v命令行执行此操作。在此示例中,/local/path应将其替换为您的Solidity文件的绝对路径:
docker run --rm -it -v /local/path:/sources hyperledgerlabs/solang -o /sources /sources/flipper.sol
这里,我把/local/path替换为了自己的路径e:\code\solidity,注意,这里的磁盘符e一定是小写,而不是大写。其结果是,会在当前路径下生成两个文件:flipper.json、flipper.wasm。这里我采用的flipper.sol文件是官网里的实例,具体如下:
contract flipper {
bool private value;
/// Constructor that initializes the `bool` value to the given `init_value`.
constructor(bool initvalue) public {
value = initvalue;
}
/// A message that can be called on instantiated contracts.
/// This one flips the value of the stored `bool` from `true`
/// to `false` and vice versa.
function flip() public {
value = !value;
}
/// Simply returns the current value of our `bool`.
function get() public view returns (bool) {
return value;
}
}
看起来貌似的转换成功了,从solidity到wasm。但是wasm具体怎么用,还需要进一步了解。
从solang的文档中,我们了解到wasm的运行可以使用Polkadot UI来运行,具体运行操作如网址:Polkadot UI文档。
为了可以正确使用wasm,需要对Polkadot UI的环境加以配置,由于我的电脑是windows 10系统,所以根据Substrate-Windows入门来进行配置。具体操作如下:
1. 下载并安装“用于Visual Studio的构建工具:”
您可以通过以下链接获取它:https://aka.ms/buildtools。
运行安装文件:vs_buildtools.exe。
在安装Visual C ++生成工具时,请确保包含“ Windows 10 SDK”组件。
重启你的电脑。
2. 安装Rust:
%USERPROFILE%\.cargo\bin
)。这会使将来的应用程序将自动具有正确的环境,但是您可能需要重新启动当前的Shell。 这里我和文档中有些不一样,安装rust后,其自动设置好了环境变量,我没有主动去设置。
如果不设置环境变量,会在安装A Substrate Node时报错,错误大概类似这样:
error: failed to run custom build command for `polkadot-runtime v0.6.0 (polkadot/runtime)`
Caused by:
process didn't exit successfully: `polkadot/target/debug/build/polkadot-runtime-8e31e940c712ba37/build-script-build` (exit code: 1)
--- stderr
Compiling wasm-build-runner-impl v1.0.0 (polkadot/target/debug/build/polkadot-runtime-eec245fcb90ce820/out/wasm_build_runner)
Finished dev [unoptimized + debuginfo] target(s) in 0.90s
Running `polkadot/target/debug/build/polkadot-runtime-eec245fcb90ce820/out/wasm_build_runner/target/debug/wasm-build-runner-impl`
Rust WASM toolchain not installed, please install it!
因此一定要记得在系统环境变量的path中添加%USERPROFILE%\.cargo\bin
。
3. 在命令提示符(CMD)中运行以下命令,以设置Wasm构建环境:
rustup update nightly
rustup update stable
rustup target add wasm32-unknown-unknown --toolchain nightly
4. 安装LLVM:https://releases.llvm.org/download.html
5. 使用以下命令安装OpenSSL vcpkg:
mkdir C:\Tools
cd C:\Tools
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg.exe install openssl:x64-windows-static
6. 使用PowerShell将OpenSSL添加到您的系统变量中:
$env:OPENSSL_DIR = 'C:\Tools\vcpkg\installed\x64-windows-static'
$env:OPENSSL_STATIC = 'Yes'
[System.Environment]::SetEnvironmentVariable('OPENSSL_DIR', $env:OPENSSL_DIR, [System.EnvironmentVariableTarget]::User)
[System.Environment]::SetEnvironmentVariable('OPENSSL_STATIC', $env:OPENSSL_STATIC, [System.EnvironmentVariableTarget]::User)
7. 最后,安装cmake
个人迷惑:这个cmake安装来干啥呢?
在前面漫长的安装后,还有两样东西等着我们安装。
我们需要使用带有内置Contracts模块的Substrate节点。在本研讨会中,我们将使用预先设计的Substrate节点客户端。
cargo install node-cli --git https://github.com/paritytech/substrate.git --tag v2.0.0-rc4 --force
这个的安装时间可能超级长,而且我在半夜安装时安不动,白天就速度飞快,难道是外国人白天都睡觉不用网了吗?
我们将要安装的最终工具是墨水!命令行实用程序,将使设置底物智能合约项目更加容易。
您可以使用Cargo通过以下方式安装实用程序:
cargo install cargo-contract --vers 0.6.1 --force
然后,您可以cargo contract --help用来开始探索提供给您的命令。
成功安装后substrate,可以通过运行以下命令启动本地开发链:
substrate --dev
注意:如果您过去曾经运行过此命令,则可能要清除链条,使用命令substrate purge-chain --dev
以使本教程顺利进行。
运行成功结果如图:
由于我运行成功后,其一直在生成block,命令行一直跳,使用没有截下类似的图,所以使用官方图。
此时打开Polkadot UI与节点进行交互。
注意:您将需要使用基于Chromium的浏览器(Google Chrome)来使该站点与本地节点进行交互。Polkadot UI托管在安全服务器上,而本地节点则不在托管服务器上,这可能会导致Firefox的兼容性问题。另一个选择是在本地克隆和运行Polkadot UI。
操作如图所示:
一切顺利的话,会在左侧浏览菜单看到正在生成的区块。
现在我们已经从源代码生成了Wasm二进制文件并启动了Substrate节点,我们希望将此合同部署到我们的Substrate区块链上。
Substrate上的智能合约部署与传统智能合约区块链上的部署有些不同。
每次您在其他平台上推送合同时,都会部署一个全新的智能合同源代码块,而Substrate选择优化此行为。例如,标准ERC20令牌已部署到以太坊数千次,有时仅更改初始配置(通过Solidity constructor功能)。这些实例中的每一个都在区块链上占用相当于合同源代码大小的空间,即使实际上没有更改任何代码。
在Substrate中,合同部署过程分为两个部分:
1.将您的代码放在区块链上
2.创建合同实例
通过这种模式,可以将诸如ERC20标准之类的合同代码一次性放置在区块链上,但是可以实例化任意次。无需持续上传相同的源代码并浪费区块链上的空间。
个人理解:大概意思是Substrate和别人不一样,处理contract时先上传,后实例化,这样会省去多次上传所用的成本。(不一定正确)
具体操作如下:
打开用户界面的特殊设计的“合约”部分。
在“code”部分中,选择“上传Wasm”。
在弹出窗口中,选择一个具有一定帐户余额的部署帐户,例如ferdie。在已编译合同WASM中,选择flipper.wasm我们生成的文件。对于合同的元数据,选择JSON文件。
在按下“上传”并“签名并提交”之后,将形成一个新块,并使用发出系统事件contracts.PutCode
。如果交易成功,您将收到一个system.ExtrinsicSuccess
事件,您的WASM合同将存储在您的Substrate区块链上!
智能合约作为区块链上账户系统的扩展而存在。因此,创建此合同的实例将创建一个新合同,该合同将AccountId存储由智能合同管理的任何余额并允许我们与合同进行交互。
您会在“code”选项卡上注意到一个新对象,它代表我们的智能合约。现在,我们需要部署智能合约来创建实例。按下鳍状肢合同上的“部署”按钮。
为了实例我们的合同,我们只需要给这个合同帐户捐赠的10 Units
,以支付租金的存储和重新设置允许的最大气体价值(1,000,000
):
注意:如前所述,合同创建涉及创建新帐户。因此,您必须确保至少为合同账户提供区块链定义的存入保证金。我们还需要能够支付合同的租金(endowment)。如果我们用完所有这笔定金,合同将无效。我们总是可以补充合同的余额并保持其链上状态。
按下部署时,应该会看到一系列事件,包括创建新帐户(system.NewAccount)和合同的实例化(contracts.instantiate),如图:
现在您的合同已被完全部署,我们可以开始与它进行交互了!Flipper只有两个功能,因此我们将向您展示同时使用这两个功能的感觉。
如果您回顾一下我们合同的on_deploy()
功能,我们会将Flipper
合同的初始值设置为false
。让我们检查一下是否是这种情况。
在“contract
”部分中,按“执行”按钮:
注意:您可能想知道:“为什么从合同中读取值时我们需要指定气体?”
如果您注意到“调用”按钮的正上方,则有一个切换按钮,使您可以“将事务发送为交易”或“作为RPC调用发送”。对于这样的只读请求,我们可以简单地使用RPC调用来模拟事务,但实际上不将任何内容存储在链上。因此,您仍然需要指定正确的汽油量来支付“虚拟费用”,但是不用担心,以这种方式拨打电话不会收取任何费用。
这次我们使用filp(),将maximum gas allowed 设置为 1,000,000.
此时单击调用,右侧会有事件消息弹窗。
如果成功,那么我们通过get(),将返回一个true。
漫长的安装和配置环境完成之后,终于完成了基础部分,接下来就是寻找solidity代码,在官方编译器中跑跑,再用solang编译一下,再去Substrate中跑跑。感觉整个过程找资料、安装、等下载费时最多。
以太坊编译器有很多,这里采用的是网页版的remix,
左侧栏从上到下,依次是文件管理、编译、部署等…
首先我们单击编译栏,开启自动编译。
我们这里跑一下盲拍的代码。
pragma solidity >0.4.23 <0.5.0;
contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint deposit;
}
address public beneficiary;
uint public biddingEnd;
uint public revealEnd;
bool public ended;
mapping(address => Bid[]) public bids;
address public highestBidder;
uint public highestBid;
// 可以取回的之前的出价
mapping(address => uint) pendingReturns;
event AuctionEnded(address winner, uint highestBid);
/// 使用 modifier 可以更便捷的校验函数的入参。
/// `onlyBefore` 会被用于后面的 `bid` 函数:
/// 新的函数体是由 modifier 本身的函数体,并用原函数体替换 `_;` 语句来组成的。
modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { require(now > _time); _; }
constructor(
uint _biddingTime,
uint _revealTime,
address _beneficiary
) public {
beneficiary = _beneficiary;
biddingEnd = now + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}
/// 可以通过 `_blindedBid` = keccak256(value, fake, secret)
/// 设置一个秘密竞拍。
/// 只有在出价披露阶段被正确披露,已发送的以太币才会被退还。
/// 如果与出价一起发送的以太币至少为 “value” 且 “fake” 不为真,则出价有效。
/// 将 “fake” 设置为 true ,然后发送满足订金金额但又不与出价相同的金额是隐藏实际出价的方法。
/// 同一个地址可以放置多个出价。
function bid(bytes32 _blindedBid)
public
payable
onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({
blindedBid: _blindedBid,
deposit: msg.value
}));
}
/// 披露你的秘密竞拍出价。
/// 对于所有正确披露的无效出价以及除最高出价以外的所有出价,你都将获得退款。
function reveal(
uint[] _values,
bool[] _fake,
bytes32[] _secret
)
public
onlyAfter(biddingEnd)
onlyBefore(revealEnd)
{
uint length = bids[msg.sender].length;
require(_values.length == length);
require(_fake.length == length);
require(_secret.length == length);
uint refund;
for (uint i = 0; i < length; i++) {
Bid storage bid = bids[msg.sender][i];
(uint value, bool fake, bytes32 secret) =
(_values[i], _fake[i], _secret[i]);
if (bid.blindedBid != keccak256(value, fake, secret)) {
// 出价未能正确披露
// 不返还订金
continue;
}
refund += bid.deposit;
if (!fake && bid.deposit >= value) {
if (placeBid(msg.sender, value))
refund -= value;
}
// 使发送者不可能再次认领同一笔订金
bid.blindedBid = bytes32(0);
}
msg.sender.transfer(refund);
}
// 这是一个 "internal" 函数, 意味着它只能在本合约(或继承合约)内被调用
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
// 返还之前的最高出价
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
/// 取回出价(当该出价已被超越)
function withdraw() public {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// 这里很重要,首先要设零值。
// 因为,作为接收调用的一部分,
// 接收者可以在 `transfer` 返回之前重新调用该函数。(可查看上面关于‘条件 -> 影响 -> 交互’的标注)
pendingReturns[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
/// 结束拍卖,并把最高的出价发送给受益人
function auctionEnd()
public
onlyAfter(revealEnd)
{
require(!ended);
emit AuctionEnded(highestBidder, highestBid);
ended = true;
beneficiary.transfer(highestBid);
}
}
具体的代码就不解释了,操作如下:
这里可以自己随便切换账户进行出价、撤回等操作,待设定的拍卖时间到期,即可单击auctionEnd结束拍卖。(出价时记得修改出的价格,不然会报错)
从测试结果看来,solidity文档给的例子运行起来还是比较舒服的。
这里按照之前的方法,使用solang编译sol文件,但是却报错了。
第一个错误是sol文件中不能有中文。
第二个错误还没搞明白,截图如下:
按照solidity官方文档的描述,msg、now等应该是专有变量啊,为什么solang认不出来呢?
我去solang的github页面提交了这个issue,得到了开发者的答复,他们确实目前还没有支持这些关键字,计划在7月25日之前加入支持。具体如下:
大概意思是:
没错,这尚未执行。但是,我目前正在7月25日之前实现当前里程碑的这些功能:https://github.com/hyperledger-labs/solang/milestone/3
第三个错误还没搞明白,截图如下:
大概意思是solang不认识constant,但是这个不是官方的函数返回值类型吗?
官方函数写法:
function () {internal|external} [pure|constant|view|payable] [returns ()]
代码中函数的写法:
function greet() public constant returns (string) {
return greeting;
}
不懂。把constant改为pure后,倒是不报错了。