web3 以太坊开发-26-以太坊智能合约结构 smart contracts anatomy
详解智能合约
智能合约是一种在以太坊某个地址上运行的程序。
它们是由数据和函数组成的,可以在收到交易时执行。
以下概述一个智能合约的组成。
数据
任何合约数据必须分配到一个位置:要么是存储,要么是内存。
在智能合约中修改存储消耗很大,因此您需要考虑数据在哪里存取。
存储
持久性数据被称之为存储,由状态变量表示。
这些值被永久地存储在区块链上。
您需要声明一个类型,以便于合约在编译时可以跟踪它在区块链上需要多少存储。
// Solidity example
contract SimpleStorage {
uint storedData; // State variable
// ...
}
# Vyper example
storedData: int128
如果用过面向对象编程语言,应该会熟悉大多数类型。
但如果是刚接触以太坊开发,则会发现 address 是一个新类型。
一个 address 类型可以容纳一个以太坊地址,相当于 20 个字节或 160 位。
它以十六进制的形式返回,前导是 0x。
其它类型包括:
布尔
整数(integer)
定点数(fixed point numbers)
固定大小的字节数组(fixed-size byte arrays)
动态大小的字节数组(dynamically-sized byte arrays)
有理数和整数常量(Rational and integer literals)
字符常量(String literals)
十六进制常量(Hexadecimal literals)
枚举(Enums)
内存
仅在合约函数执行期间存储的值被称为内存变量。
由于这些变量不是永久地存储在区块链上,所以它们的使用成本要低得多。
在 Solidity 文档中了解更多关于以太坊虚拟机如何存储数据(存储、内存和栈)。
环境变量
除了在自己合约上定义的变量之外,还有一些特殊的全局变量。
它们主要用于提供有关区块链或当前交易的信息。
示例:
属性 状态变量 描述
block.timestamp uint256 当前区块的时间戳
msg.sender 地址 消息的发送者(当前调用)
函数
用最简单的术语来说,函数可以获得信息或设置信息,以响应传入的交易。
有两种函数调用方式:
internal – 不会创建以太坊虚拟机调用
Internal 函数和状态变量只能在内部访问(只能在合约内部或者从其继承的合约内部访问)。
external – 会创建以太坊虚拟机调用
External 函数是合约接口的一部分,这意味着他可以被其它合约和交易调用。 一个 external 函数 f 不可以被内部调用(即 f() 不行,但 this.f() 可以)。
它们可以是 public 或 private
public 函数可以在合约内部调用或者通过消息在合约外部调用
private 函数仅在其被定义的合约内部可见,并且在该合约的派生合约中不可见。
函数和状态变量都可以被定义为 public 或 private
下面是更新合约上一个状态变量的函数:
// Solidity example
function update_name(string value) public {
dapp_name = value;
}
View 函数
这些函数保证不会修改合约数据的状态。
常见的示例是 "getter" 函数 - 例如,它可以用于接收用户的余额。
// Solidity 示例
function balanceOf(address _owner) public view returns (uint256 _balance) {
return ownerPizzaCount[_owner];
}
dappName: public(string)
@view
@public
def readName() -> string:
return dappName
这些操作被视为修改状态:
写入状态变量。
正在导出事件。
创建其它合约。
使用 selfdestruct。
通过调用发送 ether。
调用任何未标记为 view 或 pure 的函数。
使用底层调用。
使用包含某些操作码的内联程序组。
构造函数
构造函数
constructor 函数只在首次部署合约时执行一次。
与许多基于类的编程语言中的 constructor 函数类似,这些函数常将状态变量初始化到指定的值。
// Solidity 示例
// 初始化合约数据,设置 `owner`为合约的创建者。
constructor() public {
// 所有智能合约依赖外部交易来触发其函数。
// `msg` 是一个全局变量,包含了给定交易的相关数据,
// 例如发送者的地址和交易中包含的 ETH 数量。
// 了解更多:https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
owner = msg.sender;
}
# Vyper 示例
@external
def __init__(_beneficiary: address, _bidding_time: uint256):
self.beneficiary = _beneficiary
self.auctionStart = block.timestamp
self.auctionEnd = self.auctionStart + _bidding_time
内置函数
除了自己在合约中定义的变量和函数外,还有一些特殊的内置函数。
最明显的例子是:
address.send() – Solidity
send(address) – Vyper
这使合约可以发送以太币给其它帐户。
编写函数
您的函数需要:
参数变量及其类型(如果它接受参数)
声明为 internal/external
声明为 pure/view/payable
返回类型(如果它返回值)
pragma solidity >=0.4.0 uint) public balances;
// 事件允许在区块链上记录活动。
// 以太坊客户端可以监听事件,以便对合约状态更改作出反应。
// 了解更多: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#events
event Transfer(address from, address to, uint amount);
// 初始化合约数据,设置 `owner`为合约创建者的地址。
constructor() public {
// 所有智能合约依赖外部交易来触发其函数。
// `msg` 是一个全局变量,包含了给定交易的相关数据,
// 例如发送者的地址和包含在交易中的 ETH 数量。
// 了解更多:https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
owner = msg.sender;
}
// 创建一些新代币并发送给一个地址。
function mint(address receiver, uint amount) public {
// `require` 是一个用于强制执行某些条件的控制结构。
// 如果 `require` 的条件为 `false`,则异常被触发,
// 所有在当前调用中对状态的更改将被还原。
// 学习更多: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions
// 只有合约创建人可以调用这个函数
require(msg.sender == owner, "You are not the owner.");
// 强制执行代币的最大数量
require(amount address) public pizzaToOwner;
// Mapping from owner's address to number of owned token
mapping(address => uint256) public ownerPizzaCount;
// Mapping from token ID to approved address
mapping(uint256 => address) pizzaApprovals;
// You can nest mappings, this example maps owner to operator approvals
mapping(address => mapping(address => bool)) private operatorApprovals;
// Internal function to create a random Pizza from string (name) and DNA
function _createPizza(string memory _name, uint256 _dna)
// The `internal` keyword means this function is only visible
// within this contract and contracts that derive this contract
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#visibility-and-getters
internal
// `isUnique` is a function modifier that checks if the pizza already exists
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html#function-modifiers
isUnique(_name, _dna)
{
// Adds Pizza to array of Pizzas and get id
uint256 id = SafeMath.sub(pizzas.push(Pizza(_name, _dna)), 1);
// Checks that Pizza owner is the same as current user
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions
// note that address(0) is the zero address,
// indicating that pizza[id] is not yet allocated to a particular user.
assert(pizzaToOwner[id] == address(0));
// Maps the Pizza to the owner
pizzaToOwner[id] = msg.sender;
ownerPizzaCount[msg.sender] = SafeMath.add(
ownerPizzaCount[msg.sender],
1
);
}
// Creates a random Pizza from string (name)
function createRandomPizza(string memory _name) public {
uint256 randDna = generateRandomDna(_name, msg.sender);
_createPizza(_name, randDna);
}
// Generates random DNA from string (name) and address of the owner (creator)
function generateRandomDna(string memory _str, address _owner)
public
// Functions marked as `pure` promise not to read from or modify the state
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#pure-functions
pure
returns (uint256)
{
// Generates random uint from string (name) + address (owner)
uint256 rand = uint256(keccak256(abi.encodePacked(_str))) +
uint256(_owner);
rand = rand % dnaModulus;
return rand;
}
// Returns array of Pizzas found by owner
function getPizzasByOwner(address _owner)
public
// Functions marked as `view` promise not to modify state
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#view-functions
view
returns (uint256[] memory)
{
// Uses the `memory` storage location to store values only for the
// lifecycle of this function call.
// 了解更多:https://solidity.readthedocs.io/en/v0.5.10/introduction-to-smart-contracts.html#storage-memory-and-the-stack
uint256[] memory result = new uint256[](ownerPizzaCount[_owner]);
uint256 counter = 0;
for (uint256 i = 0; i 0;
}
}
参考资料
https://ethereum.org/zh/developers/docs/smart-contracts/anatomy/