0%

Crypto-RacaNft市场挂单script

Pre:

朋友有一批raca potion的nft要出售,尝试用程序去挂单

20211119161018

raca的nft market的合约没开源,自然无法用abi、合约地址去实例化合约对象,然后快速调用合约对应函数。需要自己构造data,与合约交互。


InputData例子解析:

先了解一下InputData是怎么构成的。以下面最简单的合约为例,我们看看用参数 1 调用set(uint x),这个交易附带的数据是什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity ^0.4.0;

contract SimpleStorage {

uint storedData;

function set(uint x) public {
storedData = x;
}

function get() public constant returns (uint) {
return storedData;
}
}

当然第一步需要先把合约部署到以太坊网络上,然后用 “1” 作为参数调用set,如下图:

20211117175500

然后打开etherscan查看交易详情数据, 可以看到其附加数据如下图:

20211117175532

这个数据就是ABI的编码数据:
0x60fe47b10000000000000000000000000000000000000000000000000000000000000001

把上面交易的附加数据拷贝出来分析一下,这个数据可以分成两个子部分:

函数选择器(4字节)

  • 0x60fe47b1

第一个参数(32字节)

  • 00000000000000000000000000000000000000000000000000000000000000001

函数选择器值 实际是对函数签名字符串进行sha3(keccak256哈希运算之后,取前4个字节,用代码表示就是:

1
bytes4(sha3(“set(uint256)”)) == 0x60fe47b1

参数部分则是使用对应的16进制数。现在就好理解 附加数据怎么转化为对应的函数调用。

实验:

刚好自己有在练手solidity,有部署到ropsten测试网上。可以尝试能不能构造出已知交易的input_data

调用的合约函数:

1
2
3
function createZoombie(string name) public {
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def mint_zoombie(self):
# function createZoombie(string name) public{}
func_abi = {
"inputs": [
{"name": "abc", "type": "string"}, # 参数类型
],
"name": "createZoombie", # 函数名
}

fn_selector = '0x' + encode_hex(function_abi_to_4byte_selector(func_abi))

args_data = encode_hex(encode_single('string', 'haha')) # encode_abi(['string'], ['haha'])

print(fn_selector)
print(args_data)
total_data = fn_selector + args_data
print(total_data)

实际的input_data:

1
2
0x3dca7430 # 方法名
6861686100000000000000000000000000000000000000000000000000000000 # 参数:haha

构造出的input_data:

1
2
3
4
0x3dca7430 # 方法名
0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000004
6861686100000000000000000000000000000000000000000000000000000000 # 参数:haha

没有构造成功,经过多个tx,观察发现中间多出来的数据是固定。但一时不知道是如何generate出来的,只能暂时先放弃了。

data的组成规则:

关于data,前八位为方法名以及参数类型的hash,只要方法名,参数个数,参数顺序以及参数类型确定,方法名hash就确定

hash往后64位进行分割,每一段就是方法的参数,不足64前面补零凑64位

参数中对于数组类型要注意,解析会有点特殊,一般会先以位数序号进行占位,然后到指定位序才是真正数组数据的起点,数组数据起点会先表明下面数据有多少位,然后才是数组数据的依次排列

raca nft挂单卖出:

授权:

授权这个data比较简单,找到多几个tx比较一下。

20211119155832

可知他的data是固定的,直接用就行了。

挂单卖出:

依然是找到多几个tx比较一下,按照data的组成规则分开观察。

1
2
3
4
5
6
7
8
0x467f963d                                                         # function selector
00000000000000000000000051353799f8550c9010a8b0cbfe6c02ca96e026e2 # usm potion药水地址
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000001 # 数量
00000000000000000000000012bb890508c125661e03b09ec06e404bc9289040 # Raca token 地址
0000000000000000000000000000000000000000000002f6d546136addf80000 # 价格
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000

可知,主要是要修改数量和价格的数据,其他数据不变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 构造卖单的data
def create_potion_sell_order_input_data(self, price):
logger.debug("[*] set price: {}".format(price))
price = self.bsc_ins.w3.toWei(price, 'ether') # 转换成Wei

price_hex_str = self.bsc_ins.w3.toHex(price) # 转换成16进制
price_hex_str = price_hex_str.replace("0x", "") # 去掉开头的0x字符串
full_price_hex_str = add_pre_zero(price_hex_str) # 不足64位,前面补0
# print(full_price_hex_str)
last_input_data = "0x467f963d00000000000000000000000051353799f8550c9010a8b0cbfe6c02ca96e026e20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000012bb890508c125661e03b09ec06e404bc9289040{}00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".format(
full_price_hex_str)

return last_input_data

# 构造tx的参数
def get_tx_params(self, to_address, input_data, gas):
now_nonce = self.bsc_ins.w3.eth.get_transaction_count(self.wallet_addr)
to_address = Web3.toChecksumAddress(to_address)
tx_params = {
'nonce': now_nonce,
'to': to_address,
'value': self.bsc_ins.w3.toWei(0, 'ether'),
'gas': gas,
'gasPrice': self.bsc_ins.w3.toWei('5', 'gwei'),
'data': input_data, # here
}
return tx_params

将tx用私钥签名,广播交易就可以了。然后检测对应账户里的raca数量就可以知道有没卖出成功。

Summary:

  1. data的组成规则:前八位为方法名以及参数类型的hash,hash往后64位进行分割,每一段就是方法的参数,不足64前面补零凑64位

  2. 多找相关的tx比较,找规律

Refs: