Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言。这门语言受到了 C++,Python 和 Javascript 语言的影响,设计的目的是能在以太坊虚拟机(EVM)上运行。Solidity 是静态类型语言,支持继承、库和复杂的用户定义类型等特性。
因受到C++,Python 和 Javascript 语言的影响,因此编码风格基本上和c++大同小异,学习solidty并不难,只是需要去理解一些相关特性和用法。
接触solidty语言,就一定义会接触solidity的应用二进制编码接口,此博客主要是用c++去实现编码。
接口说明:https://solidity-cn.readthedocs.io/zh/develop/abi-spec.html
解码代码:
string Function::functionEncode(list<EthType*> datalist)
{
string encodeStr = getMethodId();//①append methodId
if (datalist.empty())
return encodeStr;
//格式:type:value
int dynamicIndex = 0;
vector<string> vecDynamic;//存储动态类型的数据
vector<int*> vecLocation;//存储动态类型的起始位置和数据大小
int location = 0;//存储动态类型的起始位置
int dataNum = 0;//数据大小
list<EthType*>::iterator iter = datalist.begin();
while (iter != datalist.end())
{
ValueType type = (*iter)->getType();
string val = "";
switch (type)
{
case nullValue:
break;
case intValue:
{
if ((*iter)->isInt())
{
val = m_pOperation->intToHexStr((*iter)->asInt());
}
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
break;
}
case uintValue:
{
val = m_pOperation->intToHexStr((*iter)->asUint());
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
break;
}
case longlongValue:
{
val = m_pOperation->longlongToHexStr((*iter)->asInt64());
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
break;
}
case ulonglongValue:
{
val = m_pOperation->longlongToHexStr((*iter)->asUint64());
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT, 64);
break;
}
case realValue:
{
break;
}
case stringValue:
{
if (dynamicIndex == 0)
{
location = datalist.size() * 32;
val = m_pOperation->intToHexStr(location);//数据部分起始位置的偏移量,参数个数*32 字节,正好是头部的大小
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
}
else
{
//第二个动态参数 = 第一个动态参数的数据部分起始位置的偏移量 + 第一个动态参数的数据部分的长度 = 4*32 + 3*32
int cc = vecLocation[dynamicIndex - 1][0] + vecLocation[dynamicIndex - 1][1];
location = cc;
val = m_pOperation->intToHexStr(location);
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
}
//存储数据部分hex
//1.解析+编码
string tempHex = "";
string cc = m_pOperation->stringUTF8((*iter)->asString());
dataNum = cc.length();
tempHex += m_pOperation->toHexStringWithPrefixZeroPadded(m_pOperation->intToHexStr(dataNum), enumDataAlignment::ALIGN_LEFT,64);
//补位
int chushu = dataNum / 32;
int yushu = dataNum % 32;
if (yushu != 0)
chushu += 1;
string hexData = m_pOperation->stringToHexStr(cc);
for (int i = 0; i < chushu; i++)
{
tempHex += m_pOperation->toHexStringWithPrefixZeroPadded(hexData.substr(i*64,64), enumDataAlignment::ALIGN_RIGHT,64);
}
vecDynamic.push_back(tempHex);
int arr[2] = { 0,0 };
arr[0] = location;
arr[1] = (chushu + 1) * 32;
vecLocation.push_back(arr);
dynamicIndex++;
break;
}
case booleanValue:
{
if ((*iter)->asBool())
val = "1"/*intToHexStr(1)*/;
else
val = "0"/*intToHexStr(0)*/;
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
break;
}
case ListStringValue:
{
if (dynamicIndex == 0)
{
location = datalist.size() * 32;
val = m_pOperation->intToHexStr(location);//数据部分起始位置的偏移量,参数个数*32 字节,正好是头部的大小
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
}
else
{
//第二个动态参数 = 第一个动态参数的数据部分起始位置的偏移量 + 第一个动态参数的数据部分的长度 = 4*32 + 3*32
int cc = vecLocation[dynamicIndex - 1][0] + vecLocation[dynamicIndex - 1][1];
location = cc;
val = m_pOperation->intToHexStr(location);
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
}
//存储数据部分hex
//1.解析+编码
string tempHex = "";
listStr cc = (*iter)->asListString();
dataNum = cc.size();
tempHex += m_pOperation->toHexStringWithPrefixZeroPadded(m_pOperation->intToHexStr(dataNum) , enumDataAlignment::ALIGN_LEFT,64);
for (auto va : cc)
tempHex += m_pOperation->toHexStringWithPrefixZeroPadded(m_pOperation->stringToHexStr(va), enumDataAlignment::ALIGN_RIGHT,64);
vecDynamic.push_back(tempHex);
int arr[2] = { 0,0 };
arr[0] = location;
arr[1] = (dataNum+1)*32;
vecLocation.push_back(arr);
dynamicIndex++;
break;
}
case ListDoubleValue:
break;
case ListIntValue:
{
if (dynamicIndex == 0)
{
location = datalist.size() * 32;
val = m_pOperation->intToHexStr(location);//数据部分起始位置的偏移量,参数个数*32 字节,正好是头部的大小
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
}
else
{
//第二个动态参数 = 第一个动态参数的数据部分起始位置的偏移量 + 第一个动态参数的数据部分的长度 = 4*32 + 3*32
int cc = vecLocation[dynamicIndex - 1][0] + vecLocation[dynamicIndex - 1][1];
location = cc;
val = m_pOperation->intToHexStr(location);
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
}
//存储数据部分hex
//1.解析+编码
string tempHex = "";
list<int> cc = (*iter)->asListint();
dataNum = cc.size();
tempHex += m_pOperation->toHexStringWithPrefixZeroPadded(m_pOperation->intToHexStr(dataNum), enumDataAlignment::ALIGN_LEFT,64);
for (auto va : cc)
tempHex += m_pOperation->toHexStringWithPrefixZeroPadded(m_pOperation->intToHexStr(va), enumDataAlignment::ALIGN_LEFT,64);
vecDynamic.push_back(tempHex);
int arr[2] = { 0,0 };
arr[0] = location;
arr[1] = (dataNum+1)*32;
vecLocation.push_back(arr);
dynamicIndex++;
break;
}
case ByteValue:
{
if (dynamicIndex == 0)
{
location = datalist.size() * 32;
val = m_pOperation->intToHexStr(location);//数据部分起始位置的偏移量,参数个数*32 字节,正好是头部的大小
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
}
else
{
//第二个动态参数 = 第一个动态参数的数据部分起始位置的偏移量 + 第一个动态参数的数据部分的长度 = 4*32 + 3*32
int cc = vecLocation[dynamicIndex - 1][0] + vecLocation[dynamicIndex - 1][1];
location = cc;
val = m_pOperation->intToHexStr(location);
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT,64);
}
//存储数据部分hex
//1.解析+编码
string tempHex = "";
char *cc = (char *)(*iter)->asBytes();
dataNum = strlen((char *)cc)+1;
tempHex += m_pOperation->toHexStringWithPrefixZeroPadded(m_pOperation->intToHexStr(dataNum), enumDataAlignment::ALIGN_LEFT,64);
tempHex += m_pOperation->toHexStringWithPrefixZeroPadded(m_pOperation->stringToHexStr(cc), enumDataAlignment::ALIGN_RIGHT,64);
vecDynamic.push_back(tempHex);
int arr[2] = { 0,0 };
arr[0] = location;
arr[1] = 2 * 32;
vecLocation.push_back(arr);
dynamicIndex++;
break;
}
case AddressValue:
{
Address *cc = (*iter)->asAddress();
val = m_pOperation->toHexStringWithPrefixZeroPadded(cc->getValue().substr(2,40), enumDataAlignment::ALIGN_LEFT,64);
break;
}
case ListAddressValue:
{
if (dynamicIndex == 0)
{
location = datalist.size() * 32;
val = m_pOperation->intToHexStr(location);//数据部分起始位置的偏移量,参数个数*32 字节,正好是头部的大小
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT, 64);
}
else
{
//第二个动态参数 = 第一个动态参数的数据部分起始位置的偏移量 + 第一个动态参数的数据部分的长度 = 4*32 + 3*32
int cc = vecLocation[dynamicIndex - 1][0] + vecLocation[dynamicIndex - 1][1];
location = cc;
val = m_pOperation->intToHexStr(location);
val = m_pOperation->toHexStringWithPrefixZeroPadded(val, enumDataAlignment::ALIGN_LEFT, 64);
}
//存储数据部分hex
//1.解析+编码
string tempHex = "";
listAddress cc = (*iter)->asListAddress();
dataNum = cc.size();
tempHex += m_pOperation->toHexStringWithPrefixZeroPadded(m_pOperation->intToHexStr(dataNum), enumDataAlignment::ALIGN_LEFT, 64);
for (auto va : cc)
tempHex += m_pOperation->toHexStringWithPrefixZeroPadded(va->getValue().substr(2,40), enumDataAlignment::ALIGN_LEFT, 64);
vecDynamic.push_back(tempHex);
int arr[2] = { 0,0 };
arr[0] = location;
arr[1] = (dataNum + 1) * 32;
vecLocation.push_back(arr);
dynamicIndex++;
break;
val = m_pOperation->toHexStringWithPrefixZeroPadded(val.substr(2, 40), enumDataAlignment::ALIGN_LEFT, 64);
break;
}
default:
break;
}
encodeStr += val;//②append data Offset position
iter++;
}
for (auto var : vecDynamic)//③iterator append dynamic data encoder
{
encodeStr += var;
}
return encodeStr;
}
此处主要是c++对solidity不同类型进行不同的编码过程,主要是静态和动态类型的编码位置区分:
静态:直接传入
动态:数据起始偏移量+数据长度+数据内容
根据应用二进制接口说明进行静态和动态的编码(函数名编码"方法ID"+参数编码"数据")
对齐方式区分:左对齐和右对齐(补充32字节)
注意事项:对应汉字 需要区分编码格式,在vs汉字中的编码格式(默认是GB2312)是一个汉字两个字节表示,而utf-8则是3个字节表示一个汉字,JAVA传输的汉字和solidity中的汉字都是utf-8编码(一个汉字三个字节表示)因此需要必要的转换编码格式:
string stringUTF8(const string& str)
{
int nwLen = ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
wchar_t* pwBuf = new wchar_t[nwLen + 1];
ZeroMemory(pwBuf, nwLen * 2 + 2);
::MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), pwBuf, nwLen);
int nLen = ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
char* pBuf = new char[nLen + 1];
ZeroMemory(pBuf, nLen + 1);
::WideCharToMultiByte(CP_UTF8, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
std::string retStr(pBuf);
delete[]pwBuf;
delete[]pBuf;
pwBuf = NULL;
pBuf = NULL;
return retStr;
}
在linux上就不需要进行转换,一个汉字使用3个字节表示,默认是UTF-8格式。
解码过程:
void Function::decodeResult(string result, list<EthType*> &cc)
{
//剔除前缀0x
string data = result.substr(2);
list<EthType*> listdata;
int num = cc.size();
int count = 0;
list<EthType*>::iterator iter = cc.begin();
while (iter != cc.end())
{
ValueType type = (*iter)->getType();
switch (type)
{
case nullValue:
break;
case intValue:
{
string hexInt = data.substr(count * 64, 64);
size_t value = m_pOperation->hexStrToUint(hexInt.c_str(),16);
(*iter)->setIntValue(value);
break;
}
case uintValue:
{
string hexInt = data.substr(count * 64, 64);
unsigned int value = m_pOperation->hexStrToUint(hexInt, 16);
(*iter)->setUIntValue(value);
break;
}
case longlongValue:
{
string hexlong = data.substr(count * 64, 64);
int64_t value = m_pOperation->hexStrToLonglong(hexlong, 16);
(*iter)->setInt64Value(value);
break;
}
case ulonglongValue:
{
string hexlong = data.substr(count * 64, 64);
uint64_t value = m_pOperation->hexStrToLonglong(hexlong, 16);
(*iter)->setUInt64Value(value);
break;
}
case realValue:
break;
case stringValue:
{
//①获取编译位置
int index = m_pOperation->hexStrToUint(data.substr(count * 64, 64).c_str(),16);//获取数据长度*32
//②获取数据位置offset(区分数据长度和数据部分)
int dataLen = m_pOperation->hexStrToUint(data.substr(index * 2, 64).c_str(),16);//获取数据长度
//③计算数据的位长度,如果不满64的倍数,则补齐,反之
int val = dataLen * 2 / 64;
int bitnum = 0;
if (dataLen * 2 % 64 != 0)
bitnum = (val + 1) * 64;
else
bitnum = val * 64;
//③数据部分(index*2+64指数据位置处+长度位置64)
string hexdata = data.substr(index * 2 + 64, bitnum);
//④解析数据hex to string
string str = m_pOperation->hexStringToString(hexdata, hexdata.size());
(*iter)->setStringValue(str);
break;
}
case booleanValue:
{
string hexbool = data.substr(count * 64, 64);
bool value = m_pOperation->hexStrToUint(hexbool, 16);
//EthType boolV(value);
//listdata.push_back(&boolV);
(*iter)->setBoolValue(value);
break;
}
case ListStringValue:
{
//①获取编译位置
int index = m_pOperation->hexStrToUint(data.substr(count * 64, 64).c_str(),16);//获取数据长度*32
//②计算数组个数
int numS = m_pOperation->hexStrToUint(data.substr(index * 2, 64).c_str(),16);
listStr *listStr1 = new listStr;
int offset = index * 2 + 64;
for (int i = 0; i < numS; i++)
{
//③获取数据位置offset(区分数据长度和数据部分)
int dataLen = m_pOperation->hexStrToUint(data.substr(offset, 64).c_str(),16);//获取数据长度
//④计算数据的位长度,如果不满64的倍数,则补齐,反之
int val = dataLen * 2 / 64;
int bitnum = 0;
if (dataLen * 2 % 64 != 0)
bitnum = (val + 1) * 64;
else
bitnum = val * 64;
//③数据部分(index*2+64指数据位置处+长度位置64)
string hexdata = data.substr(offset+64, bitnum);
//偏移位置
offset += bitnum + 64; //得到下一个元素的位置
listStr1->push_back(m_pOperation->hexStringToString(hexdata, hexdata.size()));
}
//④解析数据hex to string
(*iter)->setListStringValue(listStr1);
break;
}
case AddressValue:
{
string hexInt = data.substr(count * 64, 64);
string result = m_pOperation->spiltAddress(hexInt);
Address *cc = new Address(result);
(*iter)->setAddressValue(cc);
break;
}
case ListAddressValue:
{
//①获取编译位置
int index = m_pOperation->hexStrToUint(data.substr(count * 64, 64).c_str(), 16);//获取数据长度*32
//②获取数据个数
int numArr = m_pOperation->hexStrToUint(data.substr(index * 2, 64).c_str(), 16);
int offset = index * 2 + 64;
string intData = "";
listAddress *tempValue = new list<Address *>;
//③解析数据
for (int i = 0; i < numArr; i++)
{
intData = data.substr(offset, 64);
tempValue->push_back(new Address("0x" + intData.substr(64 - 40)));
offset += 64;
}
(*iter)->setListAddressValue(tempValue);
break;
}
case ListDoubleValue:
break;
case ListIntValue:
{
//①获取编译位置
int index = m_pOperation->hexStrToUint(data.substr(count * 64, 64).c_str(),16);//获取数据长度*32
//②获取数据个数
int numArr = m_pOperation->hexStrToUint(data.substr(index * 2, 64).c_str(),16);
int offset = index * 2+64;
string intData = "";
list<int> *tempValue = new list<int>;
//③解析数据
for (int i = 0; i < numArr; i++)
{
intData = data.substr(offset, 64);
tempValue->push_back(atoi(intData.c_str()));
offset += 64;
}
(*iter)->setListIntValue(tempValue);
break;
}
case ByteValue://组成:偏移量+bytes长度+数据
{
//①获取编译位置
int index = m_pOperation->hexStrToUint(data.substr(count * 64, 64).c_str(),16);//获取数据长度*32
//②获取数据长度
int dataLen = m_pOperation->hexStrToUint(data.substr(index * 2, 64).c_str(),16);
//③计算数据的位长度,如果不满64的倍数,则补齐,反之
int val = dataLen * 2 / 64;
int bitnum = 0;
if (dataLen * 2 % 64 != 0)
bitnum = (val + 1) * 64;
else
bitnum = val * 64;
//③数据部分(index*2+64指数据位置处+长度位置64)
string hexdata = data.substr(index * 2 + 64, bitnum);
byte* bytedata = (byte*)m_pOperation->hexStringToString(hexdata,hexdata.size()).c_str();
(*iter)->setBytesValue(bytedata);
break;
}
default:
break;
}
count++;
iter++;
}
}
此处解码还不完全,因为只是解码了相对的一个参数的数据代码 ,还未完善返回多个参数情况下的数据解码,写得比较急,因此忘记了后面会补上。解码也是根据编码的方式去进行对应数据解码(数据起始偏移位置+数据长度+数据内容)。
以上代码可以不断的优化使其变得简洁明了不这么复杂,笔者比较懒,编码习惯不好。
总结:
①编写的代码要严谨,一定要做异常处理,不然后面调试和联调的过程会出现很多bug,也会不断的添加异常,因此不如一开始就严谨一点,既节约时间也能提升自己的编码习惯。
②调用第三库的过程中应尽量保持对应的变量类型不可随意的定义类似的变量类型,造成强制转换等。
例子:size_t int 如获取长度的过程中区别还是很多的,别随意习惯int;
还有curl库中的info参数;
//int info=0; 此类型的变量可能windows运行正常,但linux上就段错误
long info = 0;
curl_easy_getinfo(hnd, CURLINFO_RESPONSE_CODE, &info);
这个错误当时我找了很久才找到,容易忽略,linux上调试过程中段错误并不是指向这儿,而是其他地方,这就特别很坑。毕竟linux上对编码以及指针更加严格。
③写代码过程中,一定要不断的去优化代码,能优化的当时就优化出来,不可拖到后面整体完成了再去优化(人都是懒惰的,一看优化要修改很多位置,就会懒得去优化的)。