如何调用以太坊智能合约
First and foremost
在CTF的智能合约题目里,一个很大的瓶颈(对于我自己🙁)就是不知道该如何调用合约,尤其是无源码的合约逆向。学习了一些文章,大概掌握了一些方法,因此再造个轮子记忆一下。可能还不是很全,学到新的再去补充。
需要用到的工具就是Remix IDE和MetaMask插件钱包。
通过部署源码来调用
当然,自己写的合约放在Remix里编译部署后就可以直接调用;如果是别人的合约,并且我们能够拿到合约地址和源码,依然可以使用Remix来调用。
这里以一道题目举例:
合约代码:
pragma solidity ^0.4.21;
contract CallMeChallenge {
bool public isComplete = false;
function callme() public {
isComplete = true;
}
}
在Ropsten测试网络下,题目给出了合约源码及其地址,要求调用callme()函数。
首先编译合约源码:
因为本题环境是在Ropsten测试网络下,Environment应选择“Injected Web3”,并且MetaMask是需要登录的。
然后在“At Address”栏里填入合约地址并点击该按钮部署合约:
之后会出现部署好的合约及其函数:
然后点击callme按钮就能调用callme函数,等待交易完成后,点击isComplete就可以看到结果为true了:
在调用合约的时候我尝试更改一下代码里的函数名称,发现和原代码不一样的话,部署后的合约函数无法正常调用,或许只能与原代码相同的前提下才可以调用成功。
这是最简单的合约调用方式,但也只能满足手动的交互。
通过Web3.js调用合约
浏览器里执行
浏览器安装了MetaMask插件后,在浏览器里按F12打开js控制台,就可以直接使用web3的库,这样的话可以直接在浏览器执行js代码调用合约。
还是通过一道题目举例:
题目源码:
pragma solidity ^0.4.21;
// Relevant part of the CaptureTheEther contract.
contract CaptureTheEther {
mapping (address => bytes32) public nicknameOf;
function setNickname(bytes32 nickname) public {
nicknameOf[msg.sender] = nickname;
}
}
// Challenge contract. You don't need to do anything with this; it just verifies
// that you set a nickname for yourself.
contract NicknameChallenge {
CaptureTheEther cte = CaptureTheEther(msg.sender);
address player;
// Your address gets passed in as a constructor parameter.
function NicknameChallenge(address _player) public {
player = _player;
}
// Check that the first character is not null.
function isComplete() public view returns (bool) {
return cte.nicknameOf(player)[0] != 0;
}
}
CaptureTheEther合约的作用是给账户添加一个昵称,并且已经部署在了Ropsten上,地址为0x71c46Ed333C35e4E6c62D32dc7C8F00D125b4fee。我们需要调用该合约给自己账户添加昵称。
因为以太坊分主网和测试网络之分,个人实验了一下,如何在其它页面调用web3可能只是在主网上,如果以Ropsten网络账户登录下的Remix页面,则调用web3时是在Ropsten网络上操作。
把合约源码放入Remix中编译,可以得到合约的ABI:
这里的Web3Deploy是可以直接使用的JS代码,其中第一行就是合约ABI,类似JSON数据:
下面就是调用合约的JavaScript代码,在Remix页面下按F12进入js控制台里执行,并支付交易费(你在执行这段代码的时候,就是用的你此时的账户)。
var abiDefinition = [{"constant":false,"inputs":[{"name":"nickname","type":"bytes32"}],"name":"setNickname","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"nicknameOf","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"}];
var CaptureTheEther = web3.eth.contract(abiDefinition);
// load from deployed contract. replace the address with yours
var cte = CaptureTheEther.at("0x71c46Ed333C35e4E6c62D32dc7C8F00D125b4fee");
// call `setNickname` function
cte.setNickname("your nickname", console.log);
接下来用题目生成的地址部署NicknameChallenge合约,将自己账户地址传入构造函数,点击deploy,调用isComplete函数看到结果是true,就说明昵称添加成功。
nodejs执行
npm安装的web3库有很多问题,为了避免环境带来的问题(npm安装的报错),这里选择安装web3@0.20.1,ethereumjs-tx@1.3.7版本的库。
npm install web3@^0.20.1
npm install ethereumjs-tx@^1.3.7
js脚本如下:
let Web3 = require("web3");
let Tx = require("ethereumjs-tx");
//合约地址
let contractAddr = "0x35...2827";
//创建web3对象
let web3 = new Web3;
//连接到ropsten
let INFURA_api = "https://ropsten.infura.io/v3/1b...4b0";
web3.setProvider(new Web3.providers.HttpProvider(INFURA_api));
//账户地址
let fromAddr = "0x97...e05B7";
let count = web3.eth.getTransactionCount(fromAddr);
let gasPrice = web3.eth.gasPrice;
let gasLimit = 90000;
let rawTransaction = {
"from": fromAddr,
"nonce": web3.toHex(count),
"gasPrice": web3.toHex(gasPrice),
"gasLimit": web3.toHex(gasLimit),
"to": contractAddr,
"value": "0x0",
"data": "0xa3c8e393" //函数名callme取keccak后的bytes4
};
//账户私钥,不包含'0x'字符
let priv_key = new Buffer.from("bb...623ef3ed3c", "hex");
let tx = new Tx(rawTransaction);
//用私钥签名交易信息
tx.sign(priv_key);
let serializedTx = tx.serialize();
//发送交易
web3.eth.sendRawTransaction('0x'+serializedTx.toString('hex'),
function(err, hash) {
if (!err)
console.log(hash);
else
console.log(err);
});
运行此脚本node web3test.js
会返回交易哈希,到ropsten.etherscan.io里可以搜到此交易信息。
通过Web3.py调用合约
pip安装Web3.pypip install web3
。
下面是一个调用合约函数的python脚本:
# -*- coding:utf-8 -*-
from web3 import Web3, HTTPProvider
true = True
false = False
config = {
"abi":[ # 合约ABI
{
"constant": false,
"inputs": [],
"name": "callme",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "isComplete",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
],
"address":"0x35***27" # 合约地址
}
INFURA_api = "https://ropsten.infura.io/***" # INFURA的ropsten API地址
web3 = Web3(HTTPProvider(INFURA_api))
contract_instance = web3.eth.contract(address=config['address'], abi=config['abi'])
my_addr = "0x97***1d" # 账户地址
priv_key = "0xb***c" # 账户私钥
def SendTxn(txn):
signed_txn = web3.eth.account.signTransaction(txn,private_key=priv_key)
res = web3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
txn_receipt = web3.eth.waitForTransactionReceipt(res)
print(res)
return txn_receipt
txn = contract_instance.functions.callme().buildTransaction(
{
'chainId':3,
'nonce':web3.eth.getTransactionCount(my_addr),
'gas':7600000,
'value':Web3.toWei(0,'ether'),
'gasPrice':web3.eth.gasPrice,
}
)
print(SendTxn(txn))
实例化合约需要合约的ABI和地址,以及调用合约的账户及私钥。
Remix里可直接复制到合约ABI,和地址:
这里借助INFURA(使用方法可参考之前的文章)的API连接到以太坊Ropsten网络,来监听交易。
通过创建交易的方式来调用合约函数,再将交易发送至网络。
运行脚本会输出交易信息。
并且在https://ropsten.etherscan.io也可以查看到交易: