
49万USDT钱包资产被转走之到底是不是朋友干的?
话题背景wu351256@discord 有谁知道我的代币为什么会在我不知情的情况下下被转走,我查记录上面说是触发了什么智能合约。我是新手有大神带我理解一下嘛? 如果是触发了什么智能合约怎么找到这个合约并且解除合约进入正题@bmlcwenwu 是不是点了什么钓鱼链接?赶快去解除授权 @42069Imtoken里边有教程,先解除授权 @夜与昼 这种币被盗了,报警说不立案,imtoken客服说报警,投诉无门,欲哭无泪 @42069 国内加密货币属于非法,警察不立案。 @夜与昼 对啊,国内,我被盗了49万usdt夜与昼 @42069 这么多U,没想到用冷钱包吗 @夜与昼 我最后一次转账用我朋友的id下载的app,现在就是不知道会不会是他盗的 @42069 什么app? @夜与昼 Imtoken钱包 @42069 不是假的吧 @夜与昼 用他的id后,商城里下载的,登录进去给他转了一笔钱,一个星期后钱包被盗 @42069 登录之后退出id了吗?只是商城登录id没事 夜与昼登录后退出了,登录的时候还双重认证了, *[2:39 PM]*Icloud就会自动登陆 *[2:39 PM]*Iclou...
Etherscan事务交易详细分类
TransferERC21https://cn.etherscan.com/tx/0xc261caa5556785d46fbf5f715f4e5686288304a5a510d34336d763b060dc37b6Swap案例1: https://cn.etherscan.com/tx/0xf09f3b3dcd29552dac6fd14040bb457f3f462ab497dd4a885fcf1bcc026f10ff https://cn.etherscan.com/tx/0x23fdb4a895877af5d2091d541d05f6ae874267c8d82e73bb7f186d8839781c92ApproveERC20ERC721ERC1159https://cn.etherscan.com/tx/0x00ff492f7c93e50b47c1c341d935e5d5cac201455d7d7f8a491fd5f0a7d93746MintERC721MethodID: 0xa0712d68Function: mint(uint256 _mintAmount) MethodI...

以太坊签名验签原理揭秘
签名三大作用讨论密码学中的签名时,我们其实是在讨论所有权、有效性和完整性证明。举例来说,这些签名可以用来:证明你拥有地址的私钥(即认证功能);确保信息(例如,邮件)没有被篡改;验证你下载的 文件是合法有效的。签名基础原理:基于数学公式输入:一个输入消息、一个私钥和一个(通常情况下是秘密的)随机数,就可以得到一串数字作为输出值,也就是签名。 输出:使用另一个数学公式可以进行反向计算,在不知道私钥和随机数的情况下进行验证(译者注:即验证该签名是否出自跟某个公钥对应的私钥)。 这类算法有很多,如 RSA 和 AES,但是以太坊(和比特币)采用的都是椭圆曲线数字签名算法(ECDSA)。请注意,ECDSA 只是签名算法。与 RSA 和 AES 不同,这种算法不能用于加密。以太坊采用的是 secp256k1曲线。 签名方案由哈希算法和签名算法组成。以太坊选择的签名算法是secp256k1,哈希算法选择了keccak256,这是一个从字节串。不可逆计算通过椭圆曲线点乘算法(elliptic curve point manipulation),我们可以使用私钥计算出一个不可逆向计算的值(译者注:...
The infinite past has the present as its destination, and the infinite future has the present as its origin.

49万USDT钱包资产被转走之到底是不是朋友干的?
话题背景wu351256@discord 有谁知道我的代币为什么会在我不知情的情况下下被转走,我查记录上面说是触发了什么智能合约。我是新手有大神带我理解一下嘛? 如果是触发了什么智能合约怎么找到这个合约并且解除合约进入正题@bmlcwenwu 是不是点了什么钓鱼链接?赶快去解除授权 @42069Imtoken里边有教程,先解除授权 @夜与昼 这种币被盗了,报警说不立案,imtoken客服说报警,投诉无门,欲哭无泪 @42069 国内加密货币属于非法,警察不立案。 @夜与昼 对啊,国内,我被盗了49万usdt夜与昼 @42069 这么多U,没想到用冷钱包吗 @夜与昼 我最后一次转账用我朋友的id下载的app,现在就是不知道会不会是他盗的 @42069 什么app? @夜与昼 Imtoken钱包 @42069 不是假的吧 @夜与昼 用他的id后,商城里下载的,登录进去给他转了一笔钱,一个星期后钱包被盗 @42069 登录之后退出id了吗?只是商城登录id没事 夜与昼登录后退出了,登录的时候还双重认证了, *[2:39 PM]*Icloud就会自动登陆 *[2:39 PM]*Iclou...
Etherscan事务交易详细分类
TransferERC21https://cn.etherscan.com/tx/0xc261caa5556785d46fbf5f715f4e5686288304a5a510d34336d763b060dc37b6Swap案例1: https://cn.etherscan.com/tx/0xf09f3b3dcd29552dac6fd14040bb457f3f462ab497dd4a885fcf1bcc026f10ff https://cn.etherscan.com/tx/0x23fdb4a895877af5d2091d541d05f6ae874267c8d82e73bb7f186d8839781c92ApproveERC20ERC721ERC1159https://cn.etherscan.com/tx/0x00ff492f7c93e50b47c1c341d935e5d5cac201455d7d7f8a491fd5f0a7d93746MintERC721MethodID: 0xa0712d68Function: mint(uint256 _mintAmount) MethodI...

以太坊签名验签原理揭秘
签名三大作用讨论密码学中的签名时,我们其实是在讨论所有权、有效性和完整性证明。举例来说,这些签名可以用来:证明你拥有地址的私钥(即认证功能);确保信息(例如,邮件)没有被篡改;验证你下载的 文件是合法有效的。签名基础原理:基于数学公式输入:一个输入消息、一个私钥和一个(通常情况下是秘密的)随机数,就可以得到一串数字作为输出值,也就是签名。 输出:使用另一个数学公式可以进行反向计算,在不知道私钥和随机数的情况下进行验证(译者注:即验证该签名是否出自跟某个公钥对应的私钥)。 这类算法有很多,如 RSA 和 AES,但是以太坊(和比特币)采用的都是椭圆曲线数字签名算法(ECDSA)。请注意,ECDSA 只是签名算法。与 RSA 和 AES 不同,这种算法不能用于加密。以太坊采用的是 secp256k1曲线。 签名方案由哈希算法和签名算法组成。以太坊选择的签名算法是secp256k1,哈希算法选择了keccak256,这是一个从字节串。不可逆计算通过椭圆曲线点乘算法(elliptic curve point manipulation),我们可以使用私钥计算出一个不可逆向计算的值(译者注:...
The infinite past has the present as its destination, and the infinite future has the present as its origin.

Subscribe to Renaissance Labs

Subscribe to Renaissance Labs
Share Dialog
Share Dialog


<100 subscribers
<100 subscribers
合约文件开头需要声明编译器的版本号,目的是为了该合约在未来版本的升级中引入了不兼容的编译器,其语法为:
pragma solidity 版本号
版本号应该遵循“0.x.0”或者“x.0.0”的形式,比如:
// 代表不允许低于0.4.17版本的编译器,也不允许使用高于0.5.0版本的编译器
pragma solidity ^0.4.17
// 代表支持0.4.17以上的版本,但是不能超过0.9.0版本
pragma solidity >=0.4.17 < 0.9.0;
与Java类似,使用双斜杠
//代表单行注释。在双斜杠中间添加星号代表多行注释,比如:
// 单行注释
/*
多行注释
多行注释
...
*/
也可以添加文档注释,其语法为:
/**
* @dev
* @param
* @return
*/
// eg:
pragma solidity >=0.4.17 <0.9.0
contract Calculator {
/**
* @dev 实现两个数的乘法运算
* @param a 乘数
* @param b 被乘数
* @return 返回乘法的结果
*/
function mul(uint a, uint b) public pure returns(uint) {
return a * b;
}
}
布尔类型使用bool关键字。它的值是true或false。布尔类型支持的运算符有:
名称 符号
逻辑与&&
逻辑或||
逻辑非!
等于==
不等于!=
整型使用int或uint表示。uint代表的是无符号整型。它们都支持uint8、uint16,…, uint256(以8位为步长递增)。如果没有指定多少位,默认为int256或uint256。
整型支持的运算符:
名称 符号
比较运算符<= < == != >= >
位运算符& | ^ ~
算术运算符+ - * / ++ -- % ** << >>
注意:如果除数为0或者对0取模都会引发运行时异常。
定长浮点型使用fixed或ufixed表示。但是目前solidity支持声明定长浮点型的变量,不能够对该类型的变量进行赋值。
地址类型的关键字使用address表示,一般用于存储合约或账号,其长度一般为20个字节。地址类型的变量可以使用
> >= < <= == !=运算符。
地址类型的变量还可以使用以下成员变量和成员方法:
成员变量 描述 示例
balance 地址余额 x.balance
transfer 向当前地址转账 x.transfer(10)
send 向当前地址转账,与transfer不同的是,如果执行失败,该方法不会因为异常而终止合约执行 x.send(10)
call、delegatecall 调用合约函数,第一个参数是函数签名。与call不同的是,delegatecall只使用存储在库合约中的代码 x.call(“mul”, 2, 3)
值得注意的是,当一个合约A调用另外一个合约B的时候,就相当于将控制权交给了合约B。合约B也有可能会反过来调用合约A。因此调用结束时,需要对合约A状态变量的改变做好准备。
定长字节数组使用bytes1、bytes2、…、bytes32来表示。如果没有指定长度,默认为bytes1。定长字节数组支持的运算符有:
名称 符号
比较运算符 <= < == != >= >
位运算符 & | ^ ~ << >>
索引访问 x[i]代表访问第i个字节的数据
定长字节数组包含length属性,用于查询字节数组的长度。虽然可以使用byte[]表示字节数组,但是一般推荐使用bytes代替。
地址字面常量
像0x692a70d2e424a56d2c6c27aa97d1a86395877b3a这样通过了地址校验和测试的十六进制字面常量属于address类型。
数值字面常量
solidity支持整数和任意精度小数的字面常量。1.或.1都是有效的小数字面常量。也可以使用科学计数法表示,比如:2e10。如果数值字面常量赋值给了数值类型的变量,就会转换成非字面常量类型。
uint128 a = 2.5 + 1 + 0.5; // 编译成功
uint128 b = 1;
uint128 c = 2.5 + b + 0.5; // 编译报错
上面代码的第二行,将数值字面常量1赋值给了变量b。因为变量b是非字面常量,不同类型的字面常量和非字面常量无法进行运算,所以第三行报错。上面代码的第二行,将数值字面常量1赋值给了变量b。因为变量b是非字面常量,不同类型的字面常量和非字面常量无法进行运算,所以第三行报错。
字符串字面常量
字符串字面常量使用单引号或双引号引起来的一串字符。字符串字面常量可以隐式地转换成bytes类型。比如:
bytes3 b = "abc";
bytes4 b = "abcd";
bytes4 b = "abcde"; // 报错,因为bytes4只能存储4个字节数据
solidity支持将一个函数赋值给一个变量,也可以作为参数传递给其他函数,或者作为函数的返回值。
声明语法:function (参数类型) [internal|external] [pure|constant|view|payable] [returns (返回值类型)]
pragma solidity ^0.4.16;
contract A {
// 声明函数变量,并将mul函数赋给变量func
function (uint, uint) pure returns (uint) func = mul;
// 函数声明
function mul(uint a, uint b) public pure returns(uint) {
return a * b;
}
}
注意事项:
如果函数类型不需要返回,那么需要删除整个returns部分;
如果没有指定internal|external,默认是internal内部函数。内部函数可以在当前合约或子合约中使用;
pragma solidity ^0.4.16;
contract A {
function (uint, uint) pure returns (uint) func;
}
contract B is A {
function mul(uint a, uint b) public pure returns (uint) {
return a * b;
}
function test() public {
// 对父合约的函数变量进行赋值
func = mul;
// 通过父合约函数变量调用mul函数
func(10, 20);
}
}
如果是external外部函数,可以通过函数调用传递,也可以通过函数返回。
pragma solidity ^0.4.16;
contract A {
struct Request {
bytes data;
// 外部函数类型
function (bytes memory) external callback;
}
Request[] requests;
// 该函数的第二个参数类型为外部函数类型
function query(bytes memory data, function (bytes memory) external callback) public {
requests.push(Request(data, callback));
}
}
contract Test {
function getResponse(bytes result) public {
// TODO ...
}
function test() public {
// 创建合约A的实例
A a = new A();
// 调用合约A实例的query函数,第二个参数是函数类型
a.query("abc", this.getResponse);
}
}
如果声明数组的时候指定数组长度,那么数组的大小就固定下来;如果声明数组时候没有指定长度,那么该数组的长度可以发生变化。
// 定义固定长度的数组
uint[3] a;
// 定义长度可变的数组
uint[] a;
uint[] a = new intUnsupported embed;
// 通过字面量定义数组,数组长度也是可变的
uint[] c = [uint(1), 2, 3];
注意事项: 1)如果是状态变量的数组类型,不能手动指定memory或storage,默认为storage数组; 2)如果是局部变量的数组类型,可以指定为memory或storage,但是如果指定为storage数组,那么就必须对数组变量进行初始化;
contract SimpleContract {
int[] aa;
int[3] bb;
int[] cc = new intUnsupported embed;
int[] dd = [uint(1), 2, 3];
public test() public {
int[] memory a1;
int[] storage b1 = aa;
}
}
memory和storage关键字的作用是什么? memory和storage代表数据的存储位置,即数据是保存在内存中还是存储中(这里的存储可以简单理解为电脑的磁盘)。如果是状态变量和局部变量,默认为storage位置;如果是形参,默认位置为memory。另外如果是外部函数参数,其数据位置为calldata,效果跟memory差不多。数据位置的指定非常重要,它会影响着赋值行为。比如说,如果是状态变量赋值给storage的局部变量,实际上传递的是该变量的一个引用。
还有一个地方值得注意的是,定长数组只能赋给定长数组,并且两个数组的大小必须相同;定长数组不能赋值给变长数组.

编译报错:
对于storage动态数组,可以通过它的length属性改变数组的大小,所以下面第15行代码编译通过。但是对于memory动态数组,则无法通过length属性改变数组的大小,所以下面第14行代码报错:

上面第14行代码会提示TypeError: Expression has to be an lvalue.。lvalue可以理解为等号左边的值。上面错误提示信息提示我们,等号左边的值a1.length必须是一个lvalue,即可以被修改的值。明显a1.length不是一个lvalue。
添加数组元素可以通过下标方式添加,比如说:
function test() public pure {
int[] memory arr;
arr[0] = 100;
}
如果是变长的storage数组以及bytes类型,也可以通过push方法添加数组元素,该方法会返回数组的最新长度。
contract SimpleContract {
int[] aa; // Define an array of variable length
function test() public {
int[] memory bb;
bb[0] = 100;
// 因为aa是一个变长的storage数组,因此可以通过push方法添加元素
aa.push(100);
// 下面代码报错,因为bb的位置不是storage
bb.push(100);
}
}
另外一个值得注意的地方是,由于EVM限制,solidity不支持通过外部函数调用返回动态内容。比如下面代码:
function test() public pure returns(int[] memory) {
int[] memory arr;
arr[0] = 100;
return arr;
}
EVM编译合约时候不会报错,但是当我们调用该合约示例的test函数时候,提示下面错误信息call to SimpleContract.test errored: VM error: invalid opcode.。

但是,如果是通过web3调用test函数,它会返回一些动态内容。
Tips:可以把bytes看作是byte的数组形式,也可以将bytes当作string类型使用。但是,string无法通过length或索引来访问字符串中的每个字符,如果需要访问字符串中的某个字符,可以先把字符串转换成bytes形式,比如:uint size = bytes(“123”).length; byte c = bytes(“123”)[0]。
可以使用结构体用来存储复杂的数据结构。其定义格式:
struct 结构体名称 {
变量类型 变量名;
...
}
结构体中变量类型可以是任意类型,但是它不能够包含自身。

如果函数中将结构体类型数据赋给一个局部变量,这个过程并没有创建这个结构体对象的副本,而且将该结构体对象的引用赋给了局部变量(即引用传递)。
与结构体类似的是,枚举也是solidity中的一种自定义类型,其定义格式为:
enum 类型名称 {
枚举值1,
枚举值2,
...
}
枚举类型中至少要包含一个枚举值。枚举值可以是任意有效的标识符(比如说:必须是字母或下划线开头)。
enum Week {
Monday,
Tuesday,
...
}
枚举值的类型默认为uint8。第一个枚举值会被解析成0,第二个枚举值会被解析成1,以此类推。枚举类型无法与其他类型进行隐式转换,只能进行显示转换。
Weekend weekend = Weekend(0);
上面代码将数值0转换成枚举类型,对应Weekend.Monday枚举值。如果数值超过了最大枚举值,则运行合约时显式转换报错。
与Java的Map集合类型类似,映射类型用于存储的是一对有关系的数据,其定义格式为:
mapping(_keyType => _valueType) 变量名;
_keyType不可以是映射、变长数组、枚举、结构体类型;_valueType可以是任意类型。
在映射中,实际上存储的是key的keccak256哈希值。映射没有长度,也没有key和value的集合概念。
如果局部变量使用mapping类型,那么该变量的数据位置必须是storage,并且必须要对该变量进行初始化。
contract SimpleContract {
mapping(string => string) m1;
function test() public {
mapping(string => string) storage m2 = m1;
}
}
元组类似于定长数组,它是一个元素数量固定的对象列表。但是与数组不同的是,元组中元素类型可以不一样。一般来说,可以在函数中通过元组返回多个值,使用元素类型的变量来接收函数的返回值。
function f() public pure returns(uint, bool) {
return (10, false);
}
function test() public pure {
(uint a, bool b) = f();
}
元组之间可以进行赋值。
function test() public pure {
uint x = 10;
uint y = 20;
(x, y) = (y, x);
}
上面代码通过元组之间赋值交互x和y的值。
如果元组只有一个元素,那么元素后的逗号不能省略。
function f() public pure returns(uint, bool) {
return (10, false);
}
function test() public pure {
(uint a, ) = f();
}
算数运算符:+ - * / % ++ --
逻辑运算符:&& || !
比较运算符:> < >= <= == !=
位运算符:& | ^(异或) ~(取反) <<(左移) >>(右移) >>>(右移后左边补0)
赋值运算符:= += -= *= /= %=
三目运算符:condition expression ? value1 : value2
左值变量lvalue,即一个可以赋值给它的变量。如果表达式中包含左值变量,其运算符都可以简写。比如:
function test() public {
int a = 10;
int b = 5;
a += b;a -= b;a *= b;a /= b;
a++;a--;
}
与左值紧密相关的一个关键字delete。比如说delete a,如果a是一个整型,那么delete a代表将a的值恢复成初始状态;如果a是动态数组,那么delete a相当于将数组的长度length设置为0;如果a是静态数组,那么delete a会将数组中每个元素进行重置;如果a是结构体类型,delete a会将结构体中每个属性进行重置。注意:delete对映射无效。
如果两个相同类型但精度不同的变量进行运算,那么低精度会自动转换成高精度的类型。
function test() public {
int8 a = 10;
int b = 20;
int c = a + b;
}
上面代码中变量a的类型是int8,变量b的类型是int256,因此它们两个变量相加,低精度会自动转换成高精度,因此得到的结果是int256。同样地,低精度变量也可以赋给高精度变量,所以下面代码也是没问题的。
function test() public {
int8 a = 10;
int b = a;
}
但是如果需要将高精度转换成低精度的类型,那么就需要强制类型转换,如下所示:
function test() public {
int a = 10;
int8 b = int8(a);
}
需要注意的是,显式类型转换可能会导致一些无法预料的结果,比如说:
function test() public {
int a1 = -10;
uint a2 = uint(a); // 115792089237316195423570985008687907853269984665640564039457584007913129639926
uint32 b1 = 0x12345678;
uint16 b2 = uint16(b1); // 22136,即0x5678的十进制表示形式
}
运行上面代码,最终结果与我们的预期不一样。因此如果使用显式类型转换时候,必须要清楚转换的过程。
与javascript类似,定义变量时候也可以使用var关键字,比如说:
int a = 10;
var b = a;
上面代码中,因为变量a的类型为int256,因此变量b的类型也是int256。而且一旦变量b的类型确定后,它的类型就不会再发生改变。因此下面程序编译失败:
bool c = false;
b = c;
另外,solidity不支持对函数形参或反参使用var关键字。

solidity支持javascript的大部分语法,比如if…else、while…do、do…while、for等等。
pragma solidity ^0.4.16;
contract SimpleContract {
function test1(int week) public pure returns(string) {
if (week == 1) {
return "Monday";
} else if (week == 2) {
return "Tuesday";
} else if (week == 3) {
return "Wednesday";
} else if (week == 4) {
return "Thursday";
} else if (week == 5) {
return "Friday";
} else if (week == 6) {
return "Saturday";
} else if (week == 7) {
return "Sunday";
} else {
return "invalid week";
}
}
function test2() public pure returns(int) {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
function test3() public pure returns(int) {
int i = 0;
int sum = 0;
while (i < 100) {
sum += i;
i++;
}
return sum;
}
function test4() public pure returns(int) {
int i = 0;
int sum = 0;
do {
if (i % 2 == 0) continue;
sum += i;
i++;
} while(i < 100);
return sum;
}
}
合约文件开头需要声明编译器的版本号,目的是为了该合约在未来版本的升级中引入了不兼容的编译器,其语法为:
pragma solidity 版本号
版本号应该遵循“0.x.0”或者“x.0.0”的形式,比如:
// 代表不允许低于0.4.17版本的编译器,也不允许使用高于0.5.0版本的编译器
pragma solidity ^0.4.17
// 代表支持0.4.17以上的版本,但是不能超过0.9.0版本
pragma solidity >=0.4.17 < 0.9.0;
与Java类似,使用双斜杠
//代表单行注释。在双斜杠中间添加星号代表多行注释,比如:
// 单行注释
/*
多行注释
多行注释
...
*/
也可以添加文档注释,其语法为:
/**
* @dev
* @param
* @return
*/
// eg:
pragma solidity >=0.4.17 <0.9.0
contract Calculator {
/**
* @dev 实现两个数的乘法运算
* @param a 乘数
* @param b 被乘数
* @return 返回乘法的结果
*/
function mul(uint a, uint b) public pure returns(uint) {
return a * b;
}
}
布尔类型使用bool关键字。它的值是true或false。布尔类型支持的运算符有:
名称 符号
逻辑与&&
逻辑或||
逻辑非!
等于==
不等于!=
整型使用int或uint表示。uint代表的是无符号整型。它们都支持uint8、uint16,…, uint256(以8位为步长递增)。如果没有指定多少位,默认为int256或uint256。
整型支持的运算符:
名称 符号
比较运算符<= < == != >= >
位运算符& | ^ ~
算术运算符+ - * / ++ -- % ** << >>
注意:如果除数为0或者对0取模都会引发运行时异常。
定长浮点型使用fixed或ufixed表示。但是目前solidity支持声明定长浮点型的变量,不能够对该类型的变量进行赋值。
地址类型的关键字使用address表示,一般用于存储合约或账号,其长度一般为20个字节。地址类型的变量可以使用
> >= < <= == !=运算符。
地址类型的变量还可以使用以下成员变量和成员方法:
成员变量 描述 示例
balance 地址余额 x.balance
transfer 向当前地址转账 x.transfer(10)
send 向当前地址转账,与transfer不同的是,如果执行失败,该方法不会因为异常而终止合约执行 x.send(10)
call、delegatecall 调用合约函数,第一个参数是函数签名。与call不同的是,delegatecall只使用存储在库合约中的代码 x.call(“mul”, 2, 3)
值得注意的是,当一个合约A调用另外一个合约B的时候,就相当于将控制权交给了合约B。合约B也有可能会反过来调用合约A。因此调用结束时,需要对合约A状态变量的改变做好准备。
定长字节数组使用bytes1、bytes2、…、bytes32来表示。如果没有指定长度,默认为bytes1。定长字节数组支持的运算符有:
名称 符号
比较运算符 <= < == != >= >
位运算符 & | ^ ~ << >>
索引访问 x[i]代表访问第i个字节的数据
定长字节数组包含length属性,用于查询字节数组的长度。虽然可以使用byte[]表示字节数组,但是一般推荐使用bytes代替。
地址字面常量
像0x692a70d2e424a56d2c6c27aa97d1a86395877b3a这样通过了地址校验和测试的十六进制字面常量属于address类型。
数值字面常量
solidity支持整数和任意精度小数的字面常量。1.或.1都是有效的小数字面常量。也可以使用科学计数法表示,比如:2e10。如果数值字面常量赋值给了数值类型的变量,就会转换成非字面常量类型。
uint128 a = 2.5 + 1 + 0.5; // 编译成功
uint128 b = 1;
uint128 c = 2.5 + b + 0.5; // 编译报错
上面代码的第二行,将数值字面常量1赋值给了变量b。因为变量b是非字面常量,不同类型的字面常量和非字面常量无法进行运算,所以第三行报错。上面代码的第二行,将数值字面常量1赋值给了变量b。因为变量b是非字面常量,不同类型的字面常量和非字面常量无法进行运算,所以第三行报错。
字符串字面常量
字符串字面常量使用单引号或双引号引起来的一串字符。字符串字面常量可以隐式地转换成bytes类型。比如:
bytes3 b = "abc";
bytes4 b = "abcd";
bytes4 b = "abcde"; // 报错,因为bytes4只能存储4个字节数据
solidity支持将一个函数赋值给一个变量,也可以作为参数传递给其他函数,或者作为函数的返回值。
声明语法:function (参数类型) [internal|external] [pure|constant|view|payable] [returns (返回值类型)]
pragma solidity ^0.4.16;
contract A {
// 声明函数变量,并将mul函数赋给变量func
function (uint, uint) pure returns (uint) func = mul;
// 函数声明
function mul(uint a, uint b) public pure returns(uint) {
return a * b;
}
}
注意事项:
如果函数类型不需要返回,那么需要删除整个returns部分;
如果没有指定internal|external,默认是internal内部函数。内部函数可以在当前合约或子合约中使用;
pragma solidity ^0.4.16;
contract A {
function (uint, uint) pure returns (uint) func;
}
contract B is A {
function mul(uint a, uint b) public pure returns (uint) {
return a * b;
}
function test() public {
// 对父合约的函数变量进行赋值
func = mul;
// 通过父合约函数变量调用mul函数
func(10, 20);
}
}
如果是external外部函数,可以通过函数调用传递,也可以通过函数返回。
pragma solidity ^0.4.16;
contract A {
struct Request {
bytes data;
// 外部函数类型
function (bytes memory) external callback;
}
Request[] requests;
// 该函数的第二个参数类型为外部函数类型
function query(bytes memory data, function (bytes memory) external callback) public {
requests.push(Request(data, callback));
}
}
contract Test {
function getResponse(bytes result) public {
// TODO ...
}
function test() public {
// 创建合约A的实例
A a = new A();
// 调用合约A实例的query函数,第二个参数是函数类型
a.query("abc", this.getResponse);
}
}
如果声明数组的时候指定数组长度,那么数组的大小就固定下来;如果声明数组时候没有指定长度,那么该数组的长度可以发生变化。
// 定义固定长度的数组
uint[3] a;
// 定义长度可变的数组
uint[] a;
uint[] a = new intUnsupported embed;
// 通过字面量定义数组,数组长度也是可变的
uint[] c = [uint(1), 2, 3];
注意事项: 1)如果是状态变量的数组类型,不能手动指定memory或storage,默认为storage数组; 2)如果是局部变量的数组类型,可以指定为memory或storage,但是如果指定为storage数组,那么就必须对数组变量进行初始化;
contract SimpleContract {
int[] aa;
int[3] bb;
int[] cc = new intUnsupported embed;
int[] dd = [uint(1), 2, 3];
public test() public {
int[] memory a1;
int[] storage b1 = aa;
}
}
memory和storage关键字的作用是什么? memory和storage代表数据的存储位置,即数据是保存在内存中还是存储中(这里的存储可以简单理解为电脑的磁盘)。如果是状态变量和局部变量,默认为storage位置;如果是形参,默认位置为memory。另外如果是外部函数参数,其数据位置为calldata,效果跟memory差不多。数据位置的指定非常重要,它会影响着赋值行为。比如说,如果是状态变量赋值给storage的局部变量,实际上传递的是该变量的一个引用。
还有一个地方值得注意的是,定长数组只能赋给定长数组,并且两个数组的大小必须相同;定长数组不能赋值给变长数组.

编译报错:
对于storage动态数组,可以通过它的length属性改变数组的大小,所以下面第15行代码编译通过。但是对于memory动态数组,则无法通过length属性改变数组的大小,所以下面第14行代码报错:

上面第14行代码会提示TypeError: Expression has to be an lvalue.。lvalue可以理解为等号左边的值。上面错误提示信息提示我们,等号左边的值a1.length必须是一个lvalue,即可以被修改的值。明显a1.length不是一个lvalue。
添加数组元素可以通过下标方式添加,比如说:
function test() public pure {
int[] memory arr;
arr[0] = 100;
}
如果是变长的storage数组以及bytes类型,也可以通过push方法添加数组元素,该方法会返回数组的最新长度。
contract SimpleContract {
int[] aa; // Define an array of variable length
function test() public {
int[] memory bb;
bb[0] = 100;
// 因为aa是一个变长的storage数组,因此可以通过push方法添加元素
aa.push(100);
// 下面代码报错,因为bb的位置不是storage
bb.push(100);
}
}
另外一个值得注意的地方是,由于EVM限制,solidity不支持通过外部函数调用返回动态内容。比如下面代码:
function test() public pure returns(int[] memory) {
int[] memory arr;
arr[0] = 100;
return arr;
}
EVM编译合约时候不会报错,但是当我们调用该合约示例的test函数时候,提示下面错误信息call to SimpleContract.test errored: VM error: invalid opcode.。

但是,如果是通过web3调用test函数,它会返回一些动态内容。
Tips:可以把bytes看作是byte的数组形式,也可以将bytes当作string类型使用。但是,string无法通过length或索引来访问字符串中的每个字符,如果需要访问字符串中的某个字符,可以先把字符串转换成bytes形式,比如:uint size = bytes(“123”).length; byte c = bytes(“123”)[0]。
可以使用结构体用来存储复杂的数据结构。其定义格式:
struct 结构体名称 {
变量类型 变量名;
...
}
结构体中变量类型可以是任意类型,但是它不能够包含自身。

如果函数中将结构体类型数据赋给一个局部变量,这个过程并没有创建这个结构体对象的副本,而且将该结构体对象的引用赋给了局部变量(即引用传递)。
与结构体类似的是,枚举也是solidity中的一种自定义类型,其定义格式为:
enum 类型名称 {
枚举值1,
枚举值2,
...
}
枚举类型中至少要包含一个枚举值。枚举值可以是任意有效的标识符(比如说:必须是字母或下划线开头)。
enum Week {
Monday,
Tuesday,
...
}
枚举值的类型默认为uint8。第一个枚举值会被解析成0,第二个枚举值会被解析成1,以此类推。枚举类型无法与其他类型进行隐式转换,只能进行显示转换。
Weekend weekend = Weekend(0);
上面代码将数值0转换成枚举类型,对应Weekend.Monday枚举值。如果数值超过了最大枚举值,则运行合约时显式转换报错。
与Java的Map集合类型类似,映射类型用于存储的是一对有关系的数据,其定义格式为:
mapping(_keyType => _valueType) 变量名;
_keyType不可以是映射、变长数组、枚举、结构体类型;_valueType可以是任意类型。
在映射中,实际上存储的是key的keccak256哈希值。映射没有长度,也没有key和value的集合概念。
如果局部变量使用mapping类型,那么该变量的数据位置必须是storage,并且必须要对该变量进行初始化。
contract SimpleContract {
mapping(string => string) m1;
function test() public {
mapping(string => string) storage m2 = m1;
}
}
元组类似于定长数组,它是一个元素数量固定的对象列表。但是与数组不同的是,元组中元素类型可以不一样。一般来说,可以在函数中通过元组返回多个值,使用元素类型的变量来接收函数的返回值。
function f() public pure returns(uint, bool) {
return (10, false);
}
function test() public pure {
(uint a, bool b) = f();
}
元组之间可以进行赋值。
function test() public pure {
uint x = 10;
uint y = 20;
(x, y) = (y, x);
}
上面代码通过元组之间赋值交互x和y的值。
如果元组只有一个元素,那么元素后的逗号不能省略。
function f() public pure returns(uint, bool) {
return (10, false);
}
function test() public pure {
(uint a, ) = f();
}
算数运算符:+ - * / % ++ --
逻辑运算符:&& || !
比较运算符:> < >= <= == !=
位运算符:& | ^(异或) ~(取反) <<(左移) >>(右移) >>>(右移后左边补0)
赋值运算符:= += -= *= /= %=
三目运算符:condition expression ? value1 : value2
左值变量lvalue,即一个可以赋值给它的变量。如果表达式中包含左值变量,其运算符都可以简写。比如:
function test() public {
int a = 10;
int b = 5;
a += b;a -= b;a *= b;a /= b;
a++;a--;
}
与左值紧密相关的一个关键字delete。比如说delete a,如果a是一个整型,那么delete a代表将a的值恢复成初始状态;如果a是动态数组,那么delete a相当于将数组的长度length设置为0;如果a是静态数组,那么delete a会将数组中每个元素进行重置;如果a是结构体类型,delete a会将结构体中每个属性进行重置。注意:delete对映射无效。
如果两个相同类型但精度不同的变量进行运算,那么低精度会自动转换成高精度的类型。
function test() public {
int8 a = 10;
int b = 20;
int c = a + b;
}
上面代码中变量a的类型是int8,变量b的类型是int256,因此它们两个变量相加,低精度会自动转换成高精度,因此得到的结果是int256。同样地,低精度变量也可以赋给高精度变量,所以下面代码也是没问题的。
function test() public {
int8 a = 10;
int b = a;
}
但是如果需要将高精度转换成低精度的类型,那么就需要强制类型转换,如下所示:
function test() public {
int a = 10;
int8 b = int8(a);
}
需要注意的是,显式类型转换可能会导致一些无法预料的结果,比如说:
function test() public {
int a1 = -10;
uint a2 = uint(a); // 115792089237316195423570985008687907853269984665640564039457584007913129639926
uint32 b1 = 0x12345678;
uint16 b2 = uint16(b1); // 22136,即0x5678的十进制表示形式
}
运行上面代码,最终结果与我们的预期不一样。因此如果使用显式类型转换时候,必须要清楚转换的过程。
与javascript类似,定义变量时候也可以使用var关键字,比如说:
int a = 10;
var b = a;
上面代码中,因为变量a的类型为int256,因此变量b的类型也是int256。而且一旦变量b的类型确定后,它的类型就不会再发生改变。因此下面程序编译失败:
bool c = false;
b = c;
另外,solidity不支持对函数形参或反参使用var关键字。

solidity支持javascript的大部分语法,比如if…else、while…do、do…while、for等等。
pragma solidity ^0.4.16;
contract SimpleContract {
function test1(int week) public pure returns(string) {
if (week == 1) {
return "Monday";
} else if (week == 2) {
return "Tuesday";
} else if (week == 3) {
return "Wednesday";
} else if (week == 4) {
return "Thursday";
} else if (week == 5) {
return "Friday";
} else if (week == 6) {
return "Saturday";
} else if (week == 7) {
return "Sunday";
} else {
return "invalid week";
}
}
function test2() public pure returns(int) {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
function test3() public pure returns(int) {
int i = 0;
int sum = 0;
while (i < 100) {
sum += i;
i++;
}
return sum;
}
function test4() public pure returns(int) {
int i = 0;
int sum = 0;
do {
if (i % 2 == 0) continue;
sum += i;
i++;
} while(i < 100);
return sum;
}
}
No activity yet