2020-11-02 Solidity学习

2020-11-02 Solidity学习
参考博客
https://blog.csdn.net/zmrlinux/article/details/106551733
https://remix.ethereum.org/
https://www.qikegu.com/docs/4953

引用类型/复合数据类型
1 数组 (字符串与bytes是特殊的数组,所以也是引用类型)
2 struct (结构体)
3 map (映射)
这些类型涉及到的数据量较大,复制它们可能要消耗大量Gas,非常昂贵,所以使用它们时,必须考虑存储位置,例如,是保存在内存中,还是在EVM存储区中。

数据位置(data location)
在合约中声明和使用的变量都有一个数据位置,指明变量值应该存储在哪里。合约变量的数据位置将会影响Gas消耗量。

Solidity 提供4种类型的数据位置。
Storage:存储区数据位置的成本最高。该存储位置存储永久数据,这意味着该数据可以被合约中的所有函数访问。可以把它视为计算机的硬盘数据,所有数据都永久存储

pragma solidity ^0.5.0;  

contract DataLocation {  

   // storage     
   uint stateVariable;  
   uint[] stateArray;  
}  

Memory:内存位置是临时数据,比存储位置便宜。它只能在函数中访问。通常,内存数据用于保存临时变量,以便在函数执行期间进行计算。一旦函数执行完毕,它的内容就会被丢弃。你可以把它想象成每个单独函数的内存(RAM)。

pragma solidity ^0.5.0;  

contract DataLocation {  

   // storage     
   uint stateVariable;  
   uint[] stateArray;  

   function calculate(uint num1, uint num2) public pure returns (uint result) {
       return num1 + num2
   }
} 

此处,函数参数 uint num1 与 uint num2,返回值 uint result 都存储在内存中。

pragma solidity ^0.5.0;  

contract Locations {  

  /* 此处都是状态变量 */  

  // 存储在storage中  
  bool flag;  
  uint number;  
  address account;  

  function doSomething() public  {  

    /* 此处都是局部变量  */  

    // 值类型
    // 所以它们被存储在内存中
    bool flag2;  
    uint number2;  
    address account2;  

    // 引用类型,需要显示指定数据位置,此处指定为内存
    uint[] memory localArray;        
  }  
}   

Calldata:Calldata是不可修改的非持久性数据位置,所有传递给函数的值,都存储在这里。此外,Calldata是外部函数的参数(而不是返回参数)的默认位置。

Stack:堆栈是由EVM (Ethereum虚拟机)维护的非持久性数据。EVM使用堆栈数据位置在执行期间加载变量。堆栈位置最多有1024个级别的限制。

string

Solidity 中,字符串值使用双引号(“)和单引号(‘)包括,字符串类型用string表示。字符串是特殊的数组,是引用类型。

bytes到字符串的转换
可以使用string()构造函数将bytes转换为字符串。

bytes memory bstr = new bytes(10);
string message = string(bstr); 

bytes到字符串的转换

 bytes memory bstr = new bytes(len);
  return string(bstr);

数组
可以是固定大小的,也可以是动态长度的。

对于存储(storage)数组,元素类型可以是任意的(可以是其他数组、映射或结构)。

对于内存(memory)数组,元素类型不能是映射类型,如果它是一个公共函数的参数,那么元素类型必须是ABI类型。

类型为bytes和字符串的变量是特殊数组。bytes类似于byte[],但它在calldata中被紧密地打包。字符串等价于bytes,但(目前)不允许长度或索引访问。因此,相比于byte[],bytes应该优先使用,因为更便宜。

创建内存数组

可以使用new关键字在内存中创建动态数组。与存储数组相反,不能通过设置.length成员来调整内存动态数组的长度。

uint[] memory a = new uint[](7);
bytes memory b = new bytes(len);
 // a.length == 7, b.length == len

push

动态存储数组和bytes(不是字符串)有一个名为push的成员函数,可用于在数组末尾追加一个元素函数返回新的长度

return m_pairsOfFlags.push(flag);

Enum(枚举)

枚举将一个变量的取值限制为几个预定义值中的一个。精确使用枚举类型有助于减少代码中的bug

enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }
choice = FreshJuiceSize.LARGE;

结构体

struct struct_name { 
   type1 type_name_1;
   type2 type_name_2;
   type3 type_name_3;
}

映射(mapping)类型

与数组和结构体一样,映射也是引用类型。下面是声明映射类型的语法。

mapping(_KeyType => _ValueType)

_KeyType – 可以是任何内置类型,或者bytes和字符串。不允许使用引用类型或复杂对象。
_ValueType – 可以是任何类型。

注意
映射的数据位置(data location)只能是storage,通常用于状态变量。
映射可以标记为public,Solidity 自动为它创建getter。

类型转换

显式转换
int8 y = -3;
uint x = uint(y);

隐式转换时必须符合一定条件,不能导致信息丢失。例如,uint8可以转换为uint16,但是int8不可以转换为uint256,因为int8可以包含uint256中不允许的负值。

uint8 a = 12; // no error
uint32 b = 1234; // no error
uint16 c = 0x123456; // error, 有截断,变为 0x3456

以太单位

Solidity 中,以太币的单位可以使用wei、finney、szabo或ether表示。

最小的单位是wei。1e12表示1 x 10^12。

assert(1 wei == 1);
assert(1 szabo == 1e12);
assert(1 finney == 1e15);
assert(1 ether == 1e18);
assert(2 ether == 2000 fenny);

时间单位

与货币单位相似,Solidity中的时间单位如下:

assert(1 seconds == 1);
assert(1 minutes == 60 seconds);
assert(1 hours == 60 minutes);
assert(1 day == 24 hours);
assert(1 week == 7 days);

特殊变量/全局变量

名称 返回

blockhash(uint blockNumber) returns (bytes32)	给定区块的哈希值 – 只适用于256最近区块, 不包含当前区块。
block.coinbase (address payable)	当前区块矿工的地址
block.difficulty (uint)	当前区块的难度
block.gaslimit (uint)	当前区块的gaslimit
block.number (uint)	当前区块的number
block.timestamp (uint)	当前区块的时间戳,为unix纪元以来的秒
gasleft() returns (uint256)	剩余 gas
msg.data (bytes calldata)	完成 calldata
msg.sender (address payable)	消息发送者 (当前 caller)
msg.sig (bytes4)	calldata的前四个字节 (function identifier)
msg.value (uint)	当前消息的wei值
now (uint)	当前块的时间戳
tx.gasprice (uint)	交易的gas价格
tx.origin (address payable)	交易的发送方

编程风格

代码布局

缩进 – 使用4个空格代替制表符作为缩进。避免空格与制表符混用。
空2行规则 – 2个合约定义之间空2行。
空1行规则 – 2个函数之间空1行。在只有声明的情况下, 2个函数之间不需要空行
行长度 – 一行不超过79个字符。
换行规则 – 函数声明中左括号不换行,每个参数一行并缩进,右括号换行,并对齐左括号所在行。

function_with_a_long_name(
    longArgument1,
    longArgument2,
    longArgument3
);

源码编码 – UTF-8
Import – Import语句应该放在文件的顶部,pragma声明之后。
函数顺序 – 函数应该根据它们的可见性来分组。
避免多余空格 – 避免在圆括号、方括号或大括号后有空格。
控制结构 – 大括号的左括号不换行,右括号换行,与左括号所在行对齐。
函数声明 – 使用上面的大括号规则。添加可见性标签。可见性标签应该放在自定义修饰符之前。

function kill() public onlyowner {
    selfdestruct(owner);
}

映射 – 在声明映射变量时避免多余空格。

mapping(uint => uint) map; // 不是 mapping (uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;

变量声明 – 声明数组变量时避免多余空格。

uint[] x;  // 不是 unit [] x;

字符串声明 – 使用双引号声明字符串,而不是单引号。

str = "foo";
str = "Hamlet says, 'To be or not to be...'";

代码中各部分的顺序

Pragma 语句
Import 语句
Interface
库
Contract
在Interface、库或Contract中,各部分顺序应为:

Type declaration / 类型声明
State variable / 状态变量
Event / 事件
Function / 函数

命名约定

合约和库应该使用驼峰式命名。例如,SmartContract, Owner等。合约和库名应该匹配它们的文件名。
如果文件中有多个合约/库,请使用核心合约/库的名称。
结构体名称     驼峰式命名,例如: SmartCoin
事件名称        驼峰式命名,例如:AfterTransfer
函数名           驼峰式命名,首字母小写,比如:initiateSupply
局部变量和状态变量           驼峰式命名,首字母小写,比如creatorAddress、supply
常量          大写字母单词用下划线分隔,例如:MAX_BLOCKS
修饰符的名字        驼峰式命名,首字母小写,例如:onlyAfter
枚举的名字           驼峰式命名,例如:TokenGroup

函数

function function-name(parameter-list) scope returns() {
   //语句
}

return 语句

Solidity中, 函数可以返回多个值。
returns(uint product, uint sum)

函数修饰符
函数修饰符用于修改函数的行为。例如,向函数添加条件限制。

创建带参数修饰符和不带参数修饰符,如下所示:

contract Owner {

   // 定义修饰符 onlyOwner 不带参数
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }

   // 定义修饰符 costs 带参数
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}

修饰符定义中出现特殊符号_的地方,用于插入函数体。如果在调用此函数时,满足了修饰符的条件,则执行该函数,否则将抛出异常。

pragma solidity ^0.5.0;

contract Owner {
   address owner;

   constructor() public {
      owner = msg.sender;
   }

   // 定义修饰符 onlyOwner 不带参数
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }

   // 定义修饰符 costs 带参数
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}

contract Register is Owner {
   mapping (address => bool) registeredAddresses;
   uint price;

   constructor(uint initialPrice) public { price = initialPrice; }

   // 使用修饰符 costs
   function register() public payable costs(price) {
      registeredAddresses[msg.sender] = true;
   }

   // 使用修饰符 onlyOwner
   function changePrice(uint _price) public onlyOwner {
      price = _price;
   }
}

View(视图)函数

View(视图)函数不会修改状态。如果函数中存在以下语句,则被视为修改状态,编译器将抛出警告。

修改状态变量。
触发事件。
创建合约。
使用selfdestruct。
发送以太。
调用任何不是视图函数或纯函数的函数
使用底层调用
使用包含某些操作码的内联程序集。

Getter方法是默认的视图函数。声明视图函数,可以在函数声明里,添加view关键字。

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint product, uint sum){
      uint a = 1; // 局部变量
      uint b = 2;
      product = a * b;
      sum = a + b; 
   }
}

Pure(纯)函数

Pure(纯)函数不读取或修改状态。如果函数中存在以下语句,则被视为读取状态,编译器将抛出警告。

读取状态变量。
访问 address(this).balance 或 <address>.balance
访问任何区块、交易、msg等特殊变量(msg.sig 与 msg.data 允许读取)。
调用任何不是纯函数的函数。
使用包含特定操作码的内联程序集。
如果发生错误,纯函数可以使用revert()和require()函数来还原潜在的状态更改。

声明纯函数,可以在函数声明里,添加pure关键字。

fallback(回退) 函数

fallback(回退) 函数是合约中的特殊函数。它有以下特点

*当合约中不存在的函数被调用时,将调用fallback函数。*
被标记为外部函数。
它没有名字。
它没有参数。
它不能返回任何东西。
每个合约定义一个fallback函数。
如果没有被标记为payable,则当合约收到无数据的以太币转账时,将抛出异常。

fallback(回退) 函数

// 没有名字,没有参数,不返回,标记为external,可以标记为payable
function() external { 
    // statements
}

函数重载

同一个作用域内,相同函数名可以定义多个函数。这些函数的参数(参数类型或参数数量)必须不一样。仅仅是返回值不一样不被允许。

pragma solidity ^0.5.0;

contract Test {
   function getSum(uint a, uint b) public pure returns(uint){      
      return a + b;
   }
   function getSum(uint a, uint b, uint c) public pure returns(uint){      
      return a + b + c;
   }
   function callSumWithTwoArguments() public pure returns(uint){
      return getSum(1,2);
   }
   function callSumWithThreeArguments() public pure returns(uint){
      return getSum(1,2,3);
   }
}

数学函数

Solidity 也提供了内置的数学函数。下面是常用的数学函数:

addmod(uint x, uint y, uint k) returns (uint) 计算(x + y) % k,计算中,以任意精度执行加法,且不限于2^256大小。
mulmod(uint x, uint y, uint k) returns (uint) 计算(x * y) % k,计算中,以任意精度执行乘法,且不限于2^256大小。

加密函数

Solidity 提供了常用的加密函数。以下是一些重要函数:

keccak256(bytes memory) returns (bytes32) 计算输入的Keccak-256散列。
sha256(bytes memory) returns (bytes32) 计算输入的SHA-256散列。
ripemd160(bytes memory) returns (bytes20) 计算输入的RIPEMD-160散列。
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address) 从椭圆曲线签名中恢复与公钥相关的地址,或在出错时返回零。函数参数对应于签名的ECDSA值: r – 签名的前32字节; s: 签名的第二个32字节; v: 签名的最后一个字节。这个方法返回一个地址。
pragma solidity ^0.5.0;

contract Test {   
   function callKeccak256() public pure returns(bytes32 result){
      return keccak256("ABC");
   }  
}

常用模式

提款(Withdrawal)模式

当在智能合约中,直接向一个地址转账时,如该地址是一个合约地址,合约中可以编写代码,拒绝接受付款,导致交易失败。为避免这种情况,通常会使用提款模式。

提款模式是让收款方主动来提取款项,而不是直接转账给收款方

限制(restricted)访问

对合约进行访问限制,是一种常见做法。默认情况下合约是只读的,除非将合约状态指定为public。

使用限制访问修饰符,我们可以限制谁能修改合约状态,或者调用合约函数等操作。

下面示例中,创建了多个修饰符:

onlyBy 限制可以调用该函数的调用者(根据地址)。 onlyAfter 限制该函数只能在特定的时间段之后调用。
costs调用方只能在提供特定值的情况下调用此函数。

智能合约

明天继续学习

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值