Solidity入门知识点集

Solidity 的代码都包裹在合约里面. 一份合约就是以太应币应用的基本模块, 全部的变量和函数都属于一份合约, 它是你全部应用的起点.前端

最基本的合约 — 每次创建一个新的项目时的第一段代码(一份名为 HelloWorld 的空合约以下:):

pragma solidity ^0.4.19;

contract HelloWorld {

}

版本指令

pragma solidity ^0.4.19;

状态变量

状态变量是被永久地保存在合约中。也就是说它们被写入以太币区块链中,你能够想象成写入一个数据库。web

contract Example {
  // 这个无符号整数将会永久的被保存在区块链中
  uint myUnsignedInteger = 100;
}

无符号整数: uint

uint 无符号数据类型, 指其值不能是负数,对于有符号的整数存在名为 int 的数据类型。数据库

注: Solidity中, uint 其实是 uint256代名词。你也能够定义位数少的uints — uint8, uint16,
uint32, 等…… 但通常来说你愿意使用简单的 uint, 除非在某些特殊状况下。

数学运算

  • 加法: x + y
  • 减法: x - y,
  • 乘法: x * y
  • 除法: x / y
  • 取模 / 求余: x % y (例如, 13 % 5 余 3, 由于13除以5,余3)
  • 乘方: x ** y (x^y)

结构体

struct Person {
  uint age;
  string name;
}
结构体容许你生成一个更复杂的数据类型,它有多个属性。

建立新的结构体

Person[] public people;
// 建立一个新的Person:
Person satoshi = Person(172, "Satoshi");

// 将新建立的satoshi添加进people数组:
people.push(satoshi);

数组

Solidity 支持两种数组: 静态 数组和动态 数组编程

// 固定长度为2的静态数组:
uint[2] fixedArray;
// 固定长度为5的string类型的静态数组:
string[5] stringArray;
// 动态数组,长度不固定,能够动态添加元素:
uint[] dynamicArray;
// 结构体类型的数组
Person[] people;
记住:状态变量被永久保存在区块链中。因此在你的合约中建立动态数组来保存成结构的数据是很是有意义的。

公共数组

Person[] public people;
定义 public 数组, Solidity 会自动建立 getter 方法.
其它的合约能够从这个数组读取数据(但不能写入数据),因此这在合约中是一个有用的保存公共数据的模式。

定义函数

function eatHamburgers(string _name, uint _amount) {

}

eatHamburgers("vitalik", 100);
注:习惯上函数里的变量都是以(_)开头 (但不是硬性规定) 以区别全局变量。

私有 / 公共函数

Solidity 定义的函数的属性默认为公共。 这就意味着任何一方 (或其它合约) 均可以调用你合约里的函数。数组

显然,不是何时都须要这样,并且这样的合约易于受到攻击。 因此将本身的函数定义为私有是一个好的编程习惯,只有当你须要外部世界调用它时才将它设置为公共。安全

// 定义一个私有的函数, 私有函数的名字用(_)起始
uint[] numbers;

function _addToArray(uint _number) private {
  numbers.push(_number);
}

只有咱们合约中的其它函数才可以调用这个函数,给 numbers 数组添加新成员。app

函数的返回值

string greeting = "What's up dog";

function sayHello() public returns (string) {
  return greeting;
}

函数的修饰符: view / pure

view: 意味着它只能读取数据不能更改数据
pure: 不读取区块链上的数据

Keccak256生成伪随机数

keccak256("aaaab");
//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256("aaaac");
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
注: 在区块链中安全地产生一个随机数是一个很难的问题

类型转换

uint8 a = 5;
uint b = 6;
// 将会抛出错误,由于 a * b 返回 uint, 而不是 uint8:
uint8 c = a * b;
// 咱们须要将 b 转换为 uint8:
uint8 c = a * uint8(b);

事件

事件 是合约和区块链通信的一种机制。你的前端应用“监听”某些事件,并作出反应。函数

// 这里创建事件
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 实现以下:区块链

web3.eth.contract(abi).at('0x78e97bcc5b5dd9ed228fed7a4887c0d7287344a9').IntegersAdded(function(error, result) { 
  // 干些事
}

Addresses (地址)

以太坊区块链由 account (帐户)组成,你能够把它想象成银行帐户。一个账户的余额是 以太币 eth,你能够和其余账户之间支付和接受以太币,就像你的银行账户能够电汇资金到其余银行账户同样。ui

每一个账户都有一个“地址”,你能够把它想象成银行帐号。这是帐户惟一的标识符,它看起来长这样:

0x0cE446255506E92DF41614C46F1d6df9Cc969183

地址属于特定用户(或智能合约)的。

映射(Mapping)

映射 是另外一种在 Solidity 中存储有组织数据的方法。

//对于金融应用程序,将用户的余额保存在一个 uint类型的变量中:
mapping (address => uint) public accountBalance;
//或者能够用来经过userId 存储/查找的用户名
mapping (uint => string) userIdToName;
映射本质上是存储和查找数据所用的键-值对。在第一个例子中,键是一个 address,值是一个
uint,在第二个例子中,键是一个uint,值是一个 string。

Msg.sender

在 Solidity 中,有一些全局变量能够被全部函数调用。 其中一个就是 msg.sender,它指的是当前调用者(或智能合约)的 address。

注意:在 Solidity 中,功能执行始终须要从外部调用者开始。 一个合约只会在区块链上什么也不作,除非有人调用其中的函数。因此
msg.sender老是存在的。

如下是使用 msg.sender 来更新 mapping 的例子:

mapping (address => uint) favoriteNumber;

function setMyNumber(uint _myNumber) public {
  // 更新咱们的 `favoriteNumber` 映射来将 `_myNumber`存储在 `msg.sender`名下
  favoriteNumber[msg.sender] = _myNumber;
  // 存储数据至映射的方法和将数据存储在数组类似
}

function whatIsMyNumber() public view returns (uint) {
  // 拿到存储在调用者地址名下的值
  // 若调用者还没调用 setMyNumber, 则值为 `0`
  return favoriteNumber[msg.sender];
}

在这个例子中,任何人均可以调用 setMyNumber 在咱们的合约中存下一个 uint 而且与他们的地址相绑定。 而后,他们调用 whatIsMyNumber 就会返回他们存储的 uint。

使用 msg.sender 很安全,由于它具备以太坊区块链的安全保障 —— 除非窃取与以太坊地址相关联的私钥,不然是没有办法修改其余人的数据的。

Require

require使得函数在执行过程当中,当不知足某些条件时抛出错误,并中止执行。

function sayHiToVitalik(string _name) public returns (string) {
  // 比较 _name 是否等于 "Vitalik". 若是不成立,抛出异常并终止程序
  // (敲黑板: Solidity 并不支持原生的字符串比较, 咱们只能经过比较
  // 两字符串的 keccak256 哈希值来进行判断)
  require(keccak256(_name) == keccak256("Vitalik"));
  // 若是返回 true, 运行以下语句
  return "Hi!";
}

继承(Inheritance)

当代码过于冗长的时候,最好将代码和逻辑分拆到多个不一样的合约中,以便于管理。合约继承用is关键字。

contract Doge {
  function catchphrase() public returns (string) {
    return "So Wow CryptoDoge";
  }
}

contract BabyDoge is Doge {
  function anotherCatchphrase() public returns (string) {
    return "Such Moon BabyDoge";
  }
}

因为 BabyDoge 是从 Doge 那里 inherits (继承)过来的。 这意味着当你编译和部署了 BabyDoge,它将能够访问 catchphrase() 和 anotherCatchphrase()和其余咱们在 Doge 中定义的其余公共函数。

引入(Import)

上面继承的例子中,若是分两个文件,就须要引入。

import "./someothercontract.sol";

contract newContract is SomeOtherContract {

}

Storage与Memory

在 Solidity 中,有两个地方能够存储变量 —— storage 或 memory

Storage 变量是指永久存储在区块链中的变量。 Memory 变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。
你能够把它想象成存储在你电脑的硬盘或是RAM中数据的关系。

大多数时候你都用不到这些关键字,默认状况下 Solidity 会自动处理它们。 状态变量(在函数以外声明的变量)默认为“存储”形式,并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失。

然而也有一些状况下,你须要手动声明存储类型,主要用于处理函数内的 结构体 和 数组 时:

contract SandwichFactory {
  struct Sandwich {
    string name;
    string status;
  }

  Sandwich[] sandwiches;

  function eatSandwich(uint _index) public {
    // Sandwich mySandwich = sandwiches[_index];

    // ^ 看上去很直接,不过 Solidity 将会给出警告
    // 告诉你应该明确在这里定义 `storage` 或者 `memory`。

    // 因此你应该明肯定义 `storage`:
    Sandwich storage mySandwich = sandwiches[_index];
    // ...这样 `mySandwich` 是指向 `sandwiches[_index]`的指针
    // 在存储里,另外...
    mySandwich.status = "Eaten!";
    // ...这将永久把 `sandwiches[_index]` 变为区块链上的存储

    // 若是你只想要一个副本,可使用`memory`:
    Sandwich memory anotherSandwich = sandwiches[_index + 1];
    // ...这样 `anotherSandwich` 就仅仅是一个内存里的副本了
    // 另外
    anotherSandwich.status = "Eaten!";
    // ...将仅仅修改临时变量,对 `sandwiches[_index + 1]` 没有任何影响
    // 不过你能够这样作:
    sandwiches[_index + 1] = anotherSandwich;
    // ...若是你想把副本的改动保存回区块链存储
  }
}

函数可见性: internal 和 external

internal 和 private 相似,不过, 若是某个合约继承自其父合约,这个合约便可以访问父合约中定义的“内部”函数。

external 与 public 相似,只不过这些函数只能在合约以外调用 - 它们不能被合约内的其余函数调用。

contract Sandwich {
  uint private sandwichesEaten = 0;

  function eat() internal {
    sandwichesEaten++;
  }
}

contract BLT is Sandwich {
  uint private baconSandwichesEaten = 0;

  function eatWithBacon() public returns (string) {
    baconSandwichesEaten++;
    // 由于eat() 是internal 的,因此咱们能在这里调用
    eat();
  }
}

与其余合约的交互

若是咱们的合约须要和区块链上的其余的合约会话,则需先定义一个 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);
}
在咱们的 app 代码中使用这个接口,合约就知道其余合约的函数是怎样的,应该如何调用,以及可期待什么类型的返回值。

使用接口

上面的接口,咱们能够在合约中这样使用:

contract MyContract {
  address NumberInterfaceAddress = 0xab38...;
  // ^ 这是FavoriteNumber合约在以太坊上的地址
  NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
  // 如今变量 `numberContract` 指向另外一个合约对象

  function someFunction() public {
    // 如今咱们能够调用在那个合约中声明的 `getNum`函数:
    uint num = numberContract.getNum(msg.sender);
    // ...在这儿使用 `num`变量作些什么
  }
}

经过这种方式,只要将您合约的可见性设置为public(公共)或external(外部),它们就能够与以太坊区块链上的任何其余合约进行交互。

处理多返回值

function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  // 这样来作批量赋值:
  (a, b, c) = multipleReturns();
}

// 或者若是咱们只想返回其中一个变量:
function getLastReturnValue() external {
  uint c;
  // 能够对其余字段留空:
  (,,c) = multipleReturns();
}

if 语句

function eatBLT(string sandwich) public {
  // 看清楚了,当咱们比较字符串的时候,须要比较他们的 keccak256 哈希码
  if (keccak256(sandwich) == keccak256("BLT")) {
    eat();
  }
}

payable 修饰符

payable 方法是让 Solidity 和以太坊变得如此酷的一部分 —— 它们是一种能够接收以太的特殊函数。

在以太坊中, 由于钱 (以太), 数据 (事务负载), 以及合约代码自己都存在于以太坊。你能够在同时调用函数 并付钱给另一个合约。

contract OnlineStore {
  function buySomething() external payable {
    // 检查以肯定0.001以太发送出去来运行函数:
    require(msg.value == 0.001 ether);
    // 若是为真,一些用来向函数调用者发送数字内容的逻辑
    transferThing(msg.sender);
  }
}

在这里,msg.value 是一种能够查看向合约发送了多少以太的方法,另外 ether 是一个內建单元。

这里发生的事是,一些人会从 web3.js 调用这个函数 (从DApp的前端), 像这样 :

// 假设 OnlineStore 在以太坊上指向你的合约:

OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))
注意这个 value 字段, JavaScript
调用来指定发送多少(0.001)以太。若是把事务想象成一个信封,你发送到函数的参数就是信的内容。 添加一个 value 很像在信封里面放钱
—— 信件内容和钱同时发送给了接收者。

提现

在你发送以太以后,它将被存储进以合约的以太坊帐户中, 并冻结在哪里 —— 除非你添加一个函数来从合约中把以太提现。

你能够写一个函数来从合约中提现以太,相似这样:

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }
}

你能够经过 transfer 函数向一个地址发送以太, 而后 this.balance 将返回当前合约存储了多少以太。 因此若是100个用户每人向咱们支付1以太, this.balance 将是100以太。

你能够经过 transfer 向任何以太坊地址付钱。 好比,你能够有一个函数在 msg.sender 超额付款的时候给他们退钱:

uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);
相关文章
相关标签/搜索