Solidity

可与Ethereum,比特币,Binance Chain,Tron互操作的生产就绪型多链平台 dapp
https://ethfiddle.com/另一个solidity在线编译器

【竹三七译文】酷!黑科技!在20秒内分析120万ETH智能合约,并发现其漏洞!用Eveem和BigQuery就行!
https://eveem.org/

Solidity 中 revert(), assert() 和 require() 的使用方法
如果您从来不希望x是假的,则使用ASSERT(X),而不是在任何情况下(除了代码中的bug)。如果x可能为false,则使用Required(X),原因是输入无效或外部组件失败

1. 习惯上函数里的变量都是以(_)开头 (但不是硬性规定) 以区别全局变量

Solidity 定义的函数的属性默认为公共。 这就意味着任何一方 (或其它合约) 都可以调用你合约里的函数。
显然,不是什么时候都需要这样,而且这样的合约易于受到攻击。 所以将自己的函数定义为私有是一个好的编程习惯,只有当你需要外部世界调用它时才将它设置为公共

必须仔细地检查所有声明为 public 和 external的函数,一个个排除用户滥用它们的可能,谨防安全漏洞。请记住,如果这些函数没有类似 onlyOwner 这样的函数修饰符,用户能利用各种可能的参数去调用它们

私有函数的名字用(_)起始。这意味着只有我们合约中的其它函数才能够调用这个函数
private 意味着它只能被合约内部调用; internal 就像 private 但是也能被继承的合约调用; external 只能从合约外部调用;最后 public 可以在任何地方调用,不管是内部还是外部

2. 函数定义为 view, 意味着它只能读取数据不能更改数据。

在所能只读的函数上标记上表示“只读”的“external view 声明,不花gas
注意:如果一个 view 函数在另一个函数的内部被调用,而调用函数与 view 函数的不属于同一个合约,也会产生调用成本。这是因为如果主调函数在以太坊创建了一个事务,它仍然需要逐个节点去验证。所以标记为 view 的函数只有在外部调用时才是免费的。external view

pure 函数, 表明这个函数甚至都不访问应用里的数据。这个函数甚至都不读取应用里的状态 — 它的返回值完全取决于它的输入参数,在这种情况下我们把函数定义为 pure
定义 public 数组, Solidity 会自动创建 getter 方法.Person[] public people;
其它的合约可以从这个数组读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式

view 告诉我们运行这个函数不会更改和保存任何数据; pure 告诉我们这个函数不但不会往区块链写数据,它甚至不从区块链读取数据。这两种在被从合约外部调用的时候都不花费任何gas(但是它们在被内部其他函数调用的时候将会耗费gas)

3. 事件 是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应。

indexd修饰符可用于事件过滤
例子:

// 这里建立事件
event IntegersAdded(uint x, uint y, uint result);

function add(uint _x, uint _y) public {
  uint result = _x + _y;
  //触发事件,通知app
  IntegersAdded(_x, _y, result);
  return result;
}
你的 app 前端可以监听这个事件。JavaScript 实现如下:

YourContract.IntegersAdded(function(error, result) { 
  // 干些事
}

msg.sender,它指的是当前调用者(或智能合约)的 address

4. Solidity 不支持原生的字符串比较, 我们只能通过比较两字符串的 keccak256 哈希值来进行判断)

require(keccak256(_name) == keccak256("Vitalik")) //0.5版本之前
require(keccak256(abi.encodePacked(a))==keccak256(abi.encodePacked('b'))); //0.5版本
Solidity v0.5.0 重大更新

5. Storage与Memory

状态变量(在函数之外声明的变量)默认为“存储”形式,并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失

6. internal 和 external

internal 和 private 类似,不过, 如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”(internal)函数。

external 与public 类似,只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用

7. 与其他合约的交互(interface)

如果我们的合约需要和区块链上的其他的合约会话,则需先定义一个 interface (接口)。

先举一个简单的栗子。 假设在区块链上有这么一个合约:

contract LuckyNumber {
  mapping(address => uint) numbers;

  function setNum(uint _num) public {
    numbers[msg.sender] = _num;
  }

  function getNum(address _myAddress) public view returns (uint) {
    return numbers[_myAddress];
  }
}

这是个很简单的合约,您可以用它存储自己的幸运号码,并将其与您的以太坊地址关联。 这样其他人就可以通过您的地址查找您的幸运号码了。

现在假设我们有一个外部合约,使用 getNum 函数可读取其中的数据。

首先,我们定义 LuckyNumber 合约的 interface :

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

请注意,这个过程虽然看起来像在定义一个合约,但其实内里不同:

首先,我们只声明了要与之交互的函数 —— 在本例中为 getNum —— 在其中我们没有使用到任何其他的函数或状态变量。

其次,我们并没有使用大括号({ 和 })定义函数体,我们单单用分号(;)结束了函数声明。这使它看起来像一个合约框架。

编译器就是靠这些特征认出它是一个接口的。

在我们的 app 代码中使用这个接口,合约就知道其他合约的函数是怎样的,应该如何调用,以及可期待什么类型的返回值。

8. 外部依赖关系

我们不能硬编码,而要采用“函数”,以便于 DApp 的关键部分可以以参数形式修改
import "./ownable.sol"; import导入其他合约

9. 构造函数不是必须的,它与合约同名,构造函数一生中唯一的一次执行,就是在合约最初被创建的时候

10. 省 gas 的招数:结构封装 (Struct packing)

通常情况下我们不会考虑使用 uint 变种,因为无论如何定义 uint的大小,Solidity 为它保留256位的存储空间。例如,使用 uint8 而不是uint(uint256)不会为你节省任何 gas。

除非,把 uint 绑定到 struct 里面。

如果一个 struct 中有多个 uint,则尽可能使用较小的 uint, Solidity 会将这些 uint 打包在一起,从而占用较少的存储空间。
struct MiniMe {
uint32 a;
uint32 b;
uint c;
}
MiniMe mini = MiniMe(10, 20, 30);
所以,当 uint 定义在一个 struct 中的时候,尽量使用最小的整数子类型以节约空间。 并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置),这样 Solidity 可以将存储空间最小化。例如,有两个 struct:

uint c; uint32 a; uint32 b; 和 uint32 a; uint c; uint32 b;

前者比后者需要的gas更少,因为前者把uint32放一起了。

11.时间

Unix时间传统用一个32位的整数进行存储。这会导致“2038年”问题,当这个32位的unix时间戳不够用,产生溢出
因为 now 返回类型 uint256。所以我们需要明确将它转换成一个 uint32 类型的变量

12.将结构体作为参数传入

由于结构体的存储指针可以以参数的方式传递给一个 private 或 internal 的函数,因此结构体可以在多个函数之间相互传递

13.数组

uint[] memory values = new uint;
由于从外部调用一个 view 函数是免费的,我们也可以在 getZombiesByOwner 函数中用一个for循环遍历整个僵尸数组,把属于某个主人的僵尸挑出来构建出僵尸数组。那么我们的 transfer 函数将会便宜得多,因为我们不需要挪动存储里的僵尸数组重新排序,总体上这个方法会更便宜,虽然有点反直觉
函数中使用的数组是运行时在内存中通过 for 循环实时构建,而不是预先建立在存储中的

你可能感兴趣的:(Solidity)