最近正在研究如何在Ethereum上用token支付一些合约业务,其实Ethereum主页的众筹或者DAO都有例子,但是并没有实质性的内容。
一般来说,大家用的接口是这样的:
首先,在代币合约中用代币持有人告知将会有一个_recipient来调用自己持有的代币,限额为_value,_extraData是额外信息。
function approveAndCall(address _recipient, uint256 _value, bytes _extraData) {
approve(_recipient, _value);
TokenRecipient(_recipient).receiveApproval(msg.sender, _value, address(this), _extraData);
}
接着,另外一个智能合约作为_recipient需要提供一个对应的接口进行转帐操作。除了确认代币地址和转帐之外,还可以处理_extraData内容取值的问题。
function receiveApproval(address _sender, uint256 _value, TokenContract _tokenContract, bytes _extraData) {
require(_tokenContract == tokenContract);
require(tokenContract.transferFrom(_sender, address(this), 1));
uint256 payloadSize;
uint256 payload;
assembly {
payloadSize := mload(_extraData)
payload := mload(add(_extraData, 0x20))
}
payload = payload >> 8*(32 - payloadSize);
info[sender] = payload;
}
以上代码来自于https://medium.com/@jgm.orinoco/ethereum-smart-service-payment-with-tokens-60894a79f75c ,他评价了一些接口逻辑,说得还是蛮不错的,但我实际试了一下,发现_extraData是个啥玩意啊在receiveApproval里完全读不出来啊。
而官方的例子……我得说它比上面这个博客提供的内容还让人绝望。
function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData){
Token t = Token(_token);
require(t.transferFrom(_from, this, _value));
receivedTokens(_from, _value, _token, _extraData);
}
一脸懵逼.jpg
直接就传进Log里了???exo me??
言归正传,无论是用官方和博客的代码,我经过了JVM本地测试和Rinkeby测试网测试,发现了两个核心问题:
_extraData加入log的时候是有内容的,但是无论数据怎样变化,显示的都是一串完全相同的bytes32数据。
-
我在receiveApproval中直接调用_extraData.length,数值为0。
插入一下上面提到的几个数据类型的介绍:
bytes32 -> 一串16位进制的数据,长度为32个,如果当成简单string看长度为64
bytesX -> X可为1-32, 依然是16位进制的数据,就是长度不同
bytes -> 一个不限长度的bytes1的array,可看成["0x12", "0x11", "0x33", "........"]
于是我开始想,这应该是有点问题的,不是我的问题,是编译传输的时候发生的问题。
跟各位读者进一步介绍一下智能合约代码在放到Ethereum上面后是如何跟外部产生互动的:
- 网页app需要记录智能合约的地址与function接口数据,并且写好调用逻辑(如:点击按钮1会触发某个合约approveAndCall的function)
- 运用metamask插件帮助用户用私钥封装好调用的function和对应数据
- 把封装好的调用命令发布到区块链网络中
所以接下来的步骤就是要分析封装数据了,在调用approveAndCall(address _recipient, uint256 _value, bytes _extraData)函数的时候,交易数据是这样的:
{"nonce":"0x3d",
"gasPrice":"0x098bca5a00",
"gasLimit":"0x01c9c380",
"to":"0xdbA90346047B7ecC4E78C392d0BC368D6cc5B75f",
"value":"0x00",
"data":"0xcae9ca51"
"0000000000000000000000002aa58a03122764cbbc94ada363620283e8ba76b6",
"0000000000000000000000000000000000000000000000000de0b6b3a7640000",
"0000000000000000000000000000000000000000000000000000000000000060",
"0000000000000000000000000000000000000000000000000000000000000003",
"1234560000000000000000000000000000000000000000000000000000000000",
"chainId":4}
//上面的data字节其实是连在一起的,为了方便我们自己看才用逗号断开
//chainID=4的意思是Rinkeby测试网
这段交易数据对应的是智能合约里一个global object叫msg,我们这里只看msg.data:
approveAndCall | msg.data | 解释 |
---|---|---|
0xcae9ca51 | function取hash后的前四个字节 | |
address _recipient | 0x2aa58a03122764cbbc94ada363620283e8ba76b6 | |
uint256 _value | 0xde0b6b3a7640000 | 代币数额的16进制化 |
bytes _extraData | (三条bytes32数据) | 第一条为EVM虚拟机内存pointer,第二条为数据长度,第三条为数据内容 |
这样看起来是没什么问题的,我在代币合约里面调用_extraData也没啥问题,于是关键就在于进入receiveApproval (address _from, uint256 _value, address _token, bytes _extraData)时发生的问题了。经过我的多番测试,EVM在合约二次外部调用生成新stack的时候msg.data变成了这样(详细怎么测试就不写了):
"data":"0x8f4ffcb1"
"00000000000000000000000023d541a02b2764cbbc94ada363620283e8ba76b6",
"0000000000000000000000000000000000000000000000000de0b6b3a7640000",
"000000000000000000000000dbA90346047B7ecC4E78C392d0BC368D6cc5B75f",
"1234560000000000000000000000000000000000000000000000000000000000",
只传了数据内容没有传数据结构,所以无论如何,在使用正常方法的情况下数据是读不出来的!!
解决方案
- 在函数内部强行读取msg.data的字节数据再整合出来。
- 让官方改EVM吧……远目