筹备
随着区块链技术的逐步推广,区块链平安也逐步成为钻研的热点。在其中,又以智能智能合约平安最为突出。Ethernaut 正是入门钻研区块链智能合约平安的好工具。
- 首先,应确保装置 Metamask,如果能够应用 Google Extension 能够间接装置,否则能够应用 FireFox 装置
- 新建账号,并连贯到 RinkeBy Test Network(须要在 Setting – Advanced 里启用 Show test networks,并在网络中进行切换)
- 拜访 Faucet 并获取测试币,每天都有 0.1Eth 的额度
当初就能够开始 Ethernaut 的探索之旅了!
0. Hello Ethernaut
本节比较简单,所以我将更关注整体过程,介绍 Ethernaut 的实例创立等等,本人也梳理一下,所以会更具体一些。
筹备工作
进入 Hello Ethernaut,会主动提醒连贯 Metamask 钱包,连贯后,示意图如下:
按 F12 关上开发者工具,在 console 界面就能够进行智能合约的交互。
创立实例并剖析
单击 Get New Instance 以创立新的合约实例。
能够看出咱们实际上是通过与合约 0xD991431D8b033ddCb84dAD257f4821E9d5b38C33
交互以创立实例。在辅导参数中,调用 0xdfc86b17
办法,附带地址为 0x4e73b858fd5d7a5fc1c3455061de52a53f35d966
作为参数。实际上,所有关卡创立实例时都会向0xD991431D8b033ddCb84dAD257f4821E9d5b38C33
,附带的地址则是用来表明所处的关卡,如本例 URL 地址也为https://ethernaut.openzeppelin.com/level/0x4E73b858fD5D7A5fc1c3455061dE52a53F35d966
。
实例曾经胜利生成,主合约交易截图如下:
进入交易详情,查看外部交易,发现合约之间产生了调用。第一笔是由主合约调用关卡合约,第二笔是由关卡合约创立合约实例,其中实例地址为0x87DeA53b8cbF340FAa77C833B92612F49fE3B822
。
回到页面来看,能够确认生成实例确实为 0x87DeA53b8cbF340FAa77C833B92612F49fE3B822
上面咱们将进行合约的交互以实现本关卡。
合约交互
此时,在 console 界面能够通过 player
和contract
别离查看用户以后账户和被创立合约实例。player
代表用户钱包账户地址,而 contract
则蕴含合约实例abi
、address
、以及办法信息。
依照提醒要求输出await contract.info()
, 失去后果'You will find what you need in info1().'
。
输出await contract.info1()
, 失去后果'Try info2(), but with"hello"as a parameter.'
。
输出 await contract.info2('hello')
, 失去后果'The property infoNum holds the number of the next info method to call.
。
输出 await contract.infoNum()
, 失去 infoNum 参数值为42
(Word 中的首位)。这就是下一步要调用的函数(info42
)。
输出await contract.info42()
, 失去后果'theMethodName is the name of the next method.
,即下一步该当调用theMethodName
。
输出await contract.theMethodName()
, 失去后果'The method name is method7123949.
。
输出 await contract.method7123949()
, 失去后果'If you know the password, submit it to authenticate().
。
所以通过 password()
能够获取明码 ethernaut0
,并将其提交到authenticate(string)
。
留神当在进行 authenticate()
函数时,Metamask 会弹出交易确认,这是因为该函数扭转了合约外部的状态(以实现对关卡胜利的查看工作),而其余先前调用的函数却没有(为 View)。
此时,本关卡曾经实现。能够抉择 Sumbit Instance 进行提交,同样要签名实现交易
在此之后,Console 页面弹出胜利提醒,本关卡实现!
总结
本题比较简单,更多的是要相熟 ethernaut 的操作和原理。
1. Fallback
创立实例并剖析
依据先前的步骤,创立合约实例,其合约地址为 0xe0D053252d87F16F7f080E545ef2F3C157EA8d0E
。
本关卡要求 取得合约的所有权并清空余额 。
察看其源代码,找到合约所有权变更的入口。找到两个,别离是 contribute()
及receive()
,其代码如下:
function contribute() public payable {require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {owner = msg.sender;}
}
receive() external payable {require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
依照 contribute()
的逻辑,当用户随调用发送小于 0.001 ether
, 其总奉献额超过了 owner
,即可取得合约的所有权。这个过程看似简略,然而通过以下 constructor() 函数能够看出,在创立时,owner
的创立额为1000 ether
,所以这种办法不是很实用。
constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
再思考 receive()
函数,依据其逻辑,当用户发送任意 ether
,且在此之前已有奉献(已调用过contribute()
函数),即可取得合约所有权。receive()
相似于 fallback()
,当用户发送代币但没有指定函数对应时(如sendTransaction()
),会调用该办法。
在获取所有权后,再调用 withdraw
函数既能够清空合约余额。
合约交互
应用 contract
命令,查看合约 abi 及对外函数状况。
调用await contract.contribute({value:1})
,向合约发送 1 单位 Wei。
此时,调用 await contract.getContribution()
查看用户奉献,发现贡献度为 1,满足调用 receiver()
默认函数的最低要求。
应用 await contract.sendTransaction({value:1})
结构转账交易发送给合约,
调用 await contract.owner() === player
确认合约所有者曾经变更。
最初调用 await contract.withdraw()
取出余额。
提交实例,显示关卡胜利!
总结
本关卡也算比较简单,次要须要剖析代码外部的逻辑,了解 fallback()
及receive
的原理。