以太坊结构

1. 以太坊堆栈介绍

Like any software stack, the complete "Ethereum stack" will vary from project to project depending on your business goals.

There are, however, core technologies of Ethereum that help provide a mental model for how software applications interact with the Ethereum blockchain. Understanding the layers of the stack will help you understand the different ways that Ethereum can be integrated into software projects.

像任何软件堆栈一样,完整的“以太坊堆栈”将根据您的业务目标因项目而异。

但是,有一些以太坊的核心技术可以为软件应用程序与以太坊区块链的交互提供心理模型。了解堆栈的各个层将帮助您了解将以太坊集成到软件项目中的不同方式。

1:以太坊虚拟机

The Ethereum Virtual Machine (EVM) is the runtime environment for smart contracts in Ethereum. All smart contracts and state changes on the Ethereum blockchain are executed by transactions. The EVM handles all of the transaction processing on the Ethereum network.

As with any virtual machine, the EVM creates a level of abstraction between the executing code and the executing machine (an Ethereum node). Currently the EVM is running on thousands of nodes distributed across the world.

Under the hood, the EVM uses a set of opcode instructions to execute specific tasks. These (140 unique) opcodes allow the EVM to be Turing-complete, which means the EVM is able to compute just about anything, given enough resources.

As a dapp developer, you don't need to know much about the EVM other than it exists and that it reliably powers all applications on Ethereum without downtime.

2:智能合约

Smart contracts are the executable programs that run on the Ethereum blockchain.

Smart contracts are written using specific programming languages that compile to EVM bytecode (low-level machine instructions called opcodes).

Not only do smart contracts serve as open source libraries, they are essentially open API services that run 24/7 and can't be taken down. Smart contracts provide public functions which applications (dapps) may interact with, without needing permission. Any application may integrate with deployed smart contracts to compose functionality (such as data feeds or decentralized exchanges). Anyone can deploy new smart contracts to Ethereum in order to add custom functionality to meet their application's needs.

As a dapp developer, you'll need to write smart contracts only if you want to add custom functionality on the Ethereum blockchain. You may find you can achieve most or all of your project's needs by merely integrating with existing smart contracts, for instance if you want to support payments in stablecoins or enable decentralized exchange of tokens.

智能合约是在以太坊区块链上运行的可执行程序。

智能合约使用特定的编程语言编写,这些语言可编译为EVM字节码(称为操作码的低级机器指令)。

智能合约不仅可以用作开源库,而且本质上是开放式API服务,可以24/7运行,不能被删除。智能合约提供了公共功能,应用程序(dapps)可以在不需要许可的情况下与之交互。任何应用程序都可以与已部署的智能合约集成以构成功能(例如数据馈送或分散式交换)。任何人都可以将新的智能合约部署到以太坊,以添加自定义功能来满足其应用程序的需求。

作为dapp开发人员,仅当您想在以太坊区块链上添加自定义功能时才需要编写智能合约。您可能会发现仅通过与现有的智能合约集成就可以满足项目的大部分或全部需求,例如,如果您想以稳定币支持支付或启用分散式代币交换。

3:以太坊节点

In order for an application to interact with the Ethereum blockchain (i.e. read blockchain data and/or send transactions to the network), it must connect to an Ethereum node.

Ethereum nodes are computers running software - an Ethereum client. A client is an implementation of Ethereum that verifies all transactions in each block, keeping the network secure and the data accurate. Ethereum nodes ARE the Ethereum blockchain. They collectively store the state of the Ethereum blockchain and reach consensus on transactions to mutate the blockchain state.

By connecting your application to an Ethereum node (via a JSON RPC spec), your application is able to read data from the blockchain (such as user account balances) as well as broadcast new transactions to the network (such as transferring ETH between user accounts or executing functions of smart contracts).

为了使应用程序与以太坊区块链进行交互(即读取区块链数据和/或将交易发送至网络),它必须连接至以太坊节点

以太坊节点是运行软件的计算机-以太坊客户端。客户端是一种以太坊的实现,它可以验证每个区块中的所有交易,从而确保网络安全和数据准确。以太坊节点是以太坊区块链。他们共同存储以太坊区块链的状态,并就使区块链状态发生变化的交易达成共识。

通过将应用程序连接到以太坊节点(通过JSON RPC规范),您的应用程序能够从区块链读取数据(例如用户帐户余额)以及向网络广播新交易(例如在用户帐户之间转移ETH)或执行智能合约的功能)。

4:以太坊客户端API

Many convenience libraries (built and maintained by Ethereum's open source community) allow your end user applications to connect to and communicate with the Ethereum blockchain.

If your user-facing application is a web app, you may choose to npm install a JavaScript API directly in your frontend. Or perhaps you'll choose to implement this functionality server-side, using a Python or Java API.

While these APIs are not a necessary piece of the stack, they abstract away much of the complexity of interacting directly with an Ethereum node. They also provide utility functions (e.g. converting ETH to Gwei) so as a developer you can spend less time dealing with the intricacies of Ethereum clients and more time focused on the unique functionality of your application.

许多便利库(由以太坊的开源社区构建和维护)允许您的最终用户应用程序连接到以太坊区块链并与之通信。

如果您的面向用户的应用程序是一个Web应用程序,你可以选择npm install一个的JavaScript API直接在前端。或者,您可能会选择使用PythonJava API在服务器端实现此功能。

尽管这些API并不是堆栈中必不可少的部分,但它们消除了直接与以太坊节点进行交互的许多复杂性。它们还提供实用程序功能(例如,将ETH转换为Gwei),因此,作为开发人员,您可以花更少的时间来处理复杂的以太坊客户端,而将更多的时间集中在应用程序的独特功能上。

5:最终用户应用程序

At the top level of the stack are user-facing applications. These are the standard applications you regularly use and build today: primarily web and mobile apps.

The way you develop these user interfaces remains essentially unchanged. Often users will not need to know the application they're using is built using a blockchain.

面向用户的应用程序是堆栈的顶层。这些是您今天经常使用和构建的标准应用程序:主要是Web和移动应用程序。

开发这些用户界面的方式基本上保持不变。通常,用户不需要知道他们正在使用的应用程序是使用区块链构建的。

准备选择您的堆栈了吗?

Check out our guide to set up a local development environment for your Ethereum application.

2. 智能合约

什么是智能合约?

A "smart contract" is simply a program that runs on the Ethereum blockchain. It's a collection of code (its functions) and data (its state) that resides at a specific address on the Ethereum blockchain.

Smart contracts are a type of Ethereum account. This means they have a balance and they can send transactions over the network. However they're not controlled by a user, instead they are deployed to the network and run as programmed. User accounts can then interact with a smart contract by submitting transactions that execute a function defined on the smart contract. Smart contracts can define rules, like a regular contract, and automatically enforce them via the code.

“智能合约”只是在以太坊区块链上运行的程序。它是位于以太坊区块链上特定地址的代码(其功能)和数据(其状态)的集合。

智能合约是一种以太坊账户。这意味着他们有余额,可以通过网络发送交易。但是,它们不受用户控制,而是被部署到网络并按程序运行。然后,用户帐户可以通过提交执行在智能合约上定义的功能的交易来与智能合约进行交互。智能合约可以定义规则(如常规合约),并通过代码自动执行。

数字自动售货机

Perhaps the best metaphor for a smart contract is a vending machine, as described by Nick Szabo. With the right inputs, a certain output is guaranteed.

To get a snack from a vending machine:

money + snack selection = snack dispensed

This logic is programmed into the vending machine.

A smart contract, like a vending machine, has logic programmed into it. Here's a simple example of how this vending machine might look like as a smart contract:

pragma solidity 0.6.11;

contract VendingMachine {

    // Declare state variables of the contract
    address public owner;
    mapping (address => uint) public cupcakeBalances;

    // When 'VendingMachine' contract is deployed:
    // 1. set the deploying address as the owner of the contract
    // 2. set the deployed smart contract's cupcake balance to 100
    constructor() public {
        owner = msg.sender;
        cupcakeBalances[address(this)] = 100;
    }

    // Allow the owner to increase the smart contract's cupcake balance
    function refill(uint amount) public {
        require(msg.sender == owner, "Only the owner can refill.");
        cupcakeBalances[address(this)] += amount;
    }

    // Allow anyone to purchase cupcakes
    function purchase(uint amount) public payable {
        require(msg.value >= amount * 1 ether, "You must pay at least 1 ETH per cupcake");
        require(cupcakeBalances[address(this)] >= amount, "Not enough cupcakes in stock to complete this purchase");
        cupcakeBalances[address(this)] -= amount;
        cupcakeBalances[msg.sender] += amount;
    }
}

Like how a vending machine removes the need for a vendor employee, smart contracts can replace intermediaries in many industries.

无许可

Anyone can write a smart contract and deploy it to the network. You just need to learn how to code in a smart contract language, and have enough ETH to deploy your contract. Deploying a smart contract is technically a transaction, so you need to pay your Gas in the same way that you need to pay gas for a simple ETH transfer. Gas costs for contract deployment are far higher, however.

Ethereum has developer-friendly languages for writing smart contracts:

  • Solidity
  • Vyper

However, they must be compiled before they can be deployed so that Ethereum's virtual machine can interpret and store the contract.

可组合性

Smart contracts are public on Ethereum and can be thought of as open APIs. That means you can call other smart contracts in your own smart contract to greatly extend what's possible. Contracts can even deploy other contracts.

局限性

Smart contracts alone cannot get information about "real-world" events because they can't send HTTP requests. This is by design. Relying on external information could jeopardise consensus, which is important for security and decentralization.

There are ways to get around this using oracles.

仅智能合约无法获得有关“真实世界”事件的信息,因为它们无法发送HTTP请求。这是设计使然。依赖外部信息可能会破坏共识,这对于安全和权力下放很重要。

有多种方法可以使用oracle解决此问题。

智能合约资源

OpenZeppelin Contracts - *Library for secure smart contract development.*

DappSys - *Safe, simple, flexible building-blocks for smart-contracts.*

2.1 智能合约语言

A great aspect about Ethereum is that smart contracts can be programmed using relatively developer-friendly languages. If you're experienced with Python or any curly-bracket language, you can find a language with familiar syntax.

The two most active and maintained languages are:

  • Solidity
  • Vyper

More experienced developers also might want to use Yul, an intermediate language for the Ethereum Virtual Machine, or Yul+, an extension to Yul.

If you're curious and like to help test new languages that are still under heavy development you can experiment with Fe, an emerging smart contract language which is currently still in its infancy.

Solidity

  • Object-oriented, high-level language for implementing smart contracts.

  • Curly-bracket language that has been most profoundly influenced by C++.

  • Statically typed (the type of a variable is known at compile time).

  • Supports:

    • Inheritance (you can extend other contracts).
    • Libraries (you can create reusable code that you can call from different contracts – like static functions in a static class in other object oriented programming languages).
    • Complex user-defined types.
  • 面向对象的高级语言,用于实现智能合约。

  • 受C ++影响最深的弯括号语言。

  • 静态类型(在编译时已知变量的类型)。

  • 支持:

    • 继承(您可以扩展其他合同)。
    • 库(您可以创建可从不同协定调用的可重用代码,例如其他面向对象的编程语言中的静态类中的静态函数)。
    • 复杂的用户定义类型。

Important links

Example contract

// SPDX-License-Identifier: GPL-3.0
pragma solidity >= 0.7.0;

contract Coin {
    // The keyword "public" makes variables
    // accessible from other contracts
    address public minter;
    mapping (address => uint) public balances;

    // Events allow clients to react to specific
    // contract changes you declare
    event Sent(address from, address to, uint amount);

    // Constructor code is only run when the contract
    // is created
    constructor() {
        minter = msg.sender;
    }

    // Sends an amount of newly created coins to an address
    // Can only be called by the contract creator
    function mint(address receiver, uint amount) public {
        require(msg.sender == minter);
        require(amount < 1e60);
        balances[receiver] += amount;
    }

    // Sends an amount of existing coins
    // from any caller to an address
    function send(address receiver, uint amount) public {
        require(amount <= balances[msg.sender], "Insufficient balance.");
        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        emit Sent(msg.sender, receiver, amount);
    }
}

This example should give you a sense of what Solidity contract syntax is like. For a more detailed description of the functions and variables, see the docs

What is great about Solidity?

  • If you are a beginner, there are many tutorials and learning tools out there. See more about that in the Learn by Coding section.
  • Good developer tooling available.
  • Solidity has a big developer community, which means you'll most likely find answers to your questions quite quickly.

2.2 智能合约剖析

A smart contract is a program that runs at an address on Ethereum. They're made up of data and functions that can execute upon receiving a transaction. Here's an overview of what makes up a smart contract.

数据

Any contract data must be assigned to a location: either to storage or memory. It's costly to modify storage in a smart contract so you need to consider where your data should live.

储存

Persistent data is referred to as storage and is represented by state variables. These values get stored permanently on the blockchain. You need to declare the type so that the contract can keep track of how much storage on the blockchain it needs when it compiles.

持久数据称为存储,并由状态变量表示。这些值将永久存储在区块链上。您需要声明类型,以便合同可以跟踪编译时需要在区块链上存储多少存储空间。

// Solidity example
contract SimpleStorage {
    uint storedData; // State variable
    // ...
}

If you've already programmed object-oriented languages, you'll likely be familiar with most types. However address should be new to you if you're new to Ethereum development.

An address type can hold an Ethereum address which equates to 20 bytes or 160 bits. It returns in hexadecimal notation with a leading 0x.

Other types include:

  • boolean
  • integer
  • fixed point numbers
  • fixed-size byte arrays
  • dynamically-sized byte arrays
  • Rational and integer literals
  • String literals
  • Hexadecimal literals
  • Enums

For more explanation, take a look at the docs:

内存

Values that are only stored for the lifetime of a contract function's execution are called memory variables. Since these are not stored permanently on the blockchain, they are much cheaper to use.

Learn more about how the EVM stores data (Storage, Memory, and the Stack) in the Solidity docs

仅在合约函数执行的整个生命周期内存储的值称为内存变量。由于这些不是永久存储在区块链上,因此使用起来便宜得多。

Solidity文档中了解有关EVM如何存储数据(存储,内存和堆栈)的更多信息。

环境变量

In addition to the variables you define on your contract, there are some special global variables. They are primarily used to provide information about the blockchain or current transaction.

Examples:

Prop State variable Description
block.timestamp uint256 Current block epoch timestamp
msg.sender address Sender of the message (current call)

功能

In the most simplistic terms, functions can get information or set information in response to incoming transactions.

There are two types of function calls:

  • internal – these don't create an EVM call
    • Internal functions and state variables can only be accessed internally (i.e. from within the current contract or contracts deriving from it)
  • external - these do create an EVM call
    • External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function f cannot be called internally (i.e. f() does not work, but this.f() works).

They can also be public or private

  • public functions can be called internally from within the contract or externally via messages
  • private functions are only visible for the contract they are defined in and not in derived contracts

Both functions and state variables can be made public or private

Here's a function for updating a state variable on a contract:

// Solidity example
function update_name(string value) public {
    dapp_name = value;
}
  • The parameter value of type string is passed into the function: update_name
  • It's declared public, meaning anyone can access it
  • It's not declared view, so it can modify the contract state

View functions

These functions promise not to modify the state of the contract's data. Command examples are "getter" functions – you might use this to receive a user's balance for example.

// Solidity example
function balanceOf(address _owner) public view returns (uint256 _balance) {
    return ownerPizzaCount[_owner];
}

What is considered modifying state:

  1. Writing to state variables.
  2. Emitting events.
  3. Creating other contracts.
  4. Using selfdestruct.
  5. Sending ether via calls.
  6. Calling any function not marked view or pure.
  7. Using low-level calls.
  8. Using inline assembly that contains certain opcodes.

Constructor functions

constructor functions are only executed once when the contract is first deployed. Like constructor in many class-based programming languages, these functions often initialize state variables to their specified values.

// Solidity example
// Initializes the contract's data, setting the `owner`
// to the address of the contract creator.
constructor() public {
    // All smart contracts rely on external transactions to trigger its functions.
    // `msg` is a global variable that includes relevant data on the given transaction,
    // such as the address of the sender and the ETH value included in the transaction.
    // Learn more: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
    owner = msg.sender;
}

Built-in functions

In addition to the variables and functions you define on your contract, there are some special built-in functions. The most obvious example is:

  • address.send() – Solidity
  • send(address) – Vyper

These allow contracts to send ETH to other accounts.

写功能

Your function needs:

  • parameter variable and type (if it accepts parameters)
  • declaration of internal/external
  • declaration of pure/view/payable
  • returns type (if it returns a value)
pragma solidity >=0.4.0 <=0.6.0;

contract ExampleDapp {
    string dapp_name; // state variable

    // Called when the contract is deployed and initializes the value
    constructor() public {
        dapp_name = "My Example dapp";
    }

    // Get Function
    function read_name() public view returns(string) {
        return dapp_name;
    }

    // Set Function
    function update_name(string value) public {
        dapp_name = value;
    }
}

A complete contract might look something like this. Here the constructor function provides an initial value for the dapp_name variable.

事件和日志

Events let you communicate with your smart contract from your frontend or other subscribing applications. When a transaction is mined, smart contracts can emit events and write logs to the blockchain that the frontend can then process.

带注释的示例

These are some examples written in Solidity. If you'd like to play with the code, you can interact with them in Remix.

Hello world

// Specifies the version of Solidity, using semantic versioning.
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
pragma solidity ^0.5.10;

// Defines a contract named `HelloWorld`.
// A contract is a collection of functions and data (its state).
// Once deployed, a contract resides at a specific address on the Ethereum blockchain.
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
contract HelloWorld {

    // Declares a state variable `message` of type `string`.
    // State variables are variables whose values are permanently stored in contract storage.
    // The keyword `public` makes variables accessible from outside a contract
    // and creates a function that other contracts or clients can call to access the value.
    string public message;

    // Similar to many class-based object-oriented languages, a constructor is
    // a special function that is only executed upon contract creation.
    // Constructors are used to initialize the contract's data.
    // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
    constructor(string memory initMessage) public {
        // Accepts a string argument `initMessage` and sets the value
        // into the contract's `message` storage variable).
        message = initMessage;
    }

    // A public function that accepts a string argument
    // and updates the `message` storage variable.
    function update(string memory newMessage) public {
        message = newMessage;
    }
}

Token

pragma solidity ^0.5.10;

contract Token {
    // An `address` is comparable to an email address - it's used to identify an account on Ethereum.
    // Addresses can represent a smart contract or an external (user) accounts.
    // Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#address
    address public owner;

    // A `mapping` is essentially a hash table data structure.
    // This `mapping` assigns an unsigned integer (the token balance) to an address (the token holder).
    // Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#mapping-types
    mapping (address => uint) public balances;

    // Events allow for logging of activity on the blockchain.
    // Ethereum clients can listen for events in order to react to contract state changes.
    // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#events
    event Transfer(address from, address to, uint amount);

    // Initializes the contract's data, setting the `owner`
    // to the address of the contract creator.
    constructor() public {
        // All smart contracts rely on external transactions to trigger its functions.
        // `msg` is a global variable that includes relevant data on the given transaction,
        // such as the address of the sender and the ETH value included in the transaction.
        // Learn more: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
        owner = msg.sender;
    }

    // Creates an amount of new tokens and sends them to an address.
    function mint(address receiver, uint amount) public {
        // `require` is a control structure used to enforce certain conditions.
        // If a `require` statement evaluates to `false`, an exception is triggered,
        // which reverts all changes made to the state during the current call.
        // Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions

        // Only the contract owner can call this function
        require(msg.sender == owner, "You are not the owner.");

        // Enforces a maximum amount of tokens
        require(amount < 1e60, "Maximum issuance exceeded");

        // Increases the balance of `receiver` by `amount`
        balances[receiver] += amount;
    }

    // Sends an amount of existing tokens from any caller to an address.
    function transfer(address receiver, uint amount) public {
        // The sender must have enough tokens to send
        require(amount <= balances[msg.sender], "Insufficient balance.");

        // Adjusts token balances of the two addresses
        balances[msg.sender] -= amount;
        balances[receiver] += amount;

        // Emits the event defined earlier
        emit Transfer(msg.sender, receiver, amount);
    }
}

Unique digital asset

pragma solidity ^0.5.10;

// Imports symbols from other files into the current contract.
// In this case, a series of helper contracts from OpenZeppelin.
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#importing-other-source-files

import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "../node_modules/@openzeppelin/contracts/introspection/ERC165.sol";
import "../node_modules/@openzeppelin/contracts/math/SafeMath.sol";

// The `is` keyword is used to inherit functions and keywords from external contracts.
// In this case, `CryptoPizza` inherits from the `IERC721` and `ERC165` contracts.
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#inheritance
contract CryptoPizza is IERC721, ERC165 {
    // Uses OpenZeppelin's SafeMath library to perform arithmetic operations safely.
    // Learn more: https://docs.openzeppelin.com/contracts/2.x/api/math#SafeMath
    using SafeMath for uint256;

    // Constant state variables in Solidity are similar to other languages
    // but you must assign from an expression which is constant at compile time.
    // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constant-state-variables
    uint256 constant dnaDigits = 10;
    uint256 constant dnaModulus = 10 ** dnaDigits;
    bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;

    // Struct types let you define your own type
    // Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#structs
    struct Pizza {
        string name;
        uint256 dna;
    }

    // Creates an empty array of Pizza structs
    Pizza[] public pizzas;

    // Mapping from pizza ID to its owner's address
    mapping(uint256 => 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
        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.
        // Learn more: 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 < pizzas.length; i++) {
            if (pizzaToOwner[i] == _owner) {
                result[counter] = i;
                counter++;
            }
        }
        return result;
    }

    // Transfers Pizza and ownership to other address
    function transferFrom(address _from, address _to, uint256 _pizzaId) public {
        require(_from != address(0) && _to != address(0), "Invalid address.");
        require(_exists(_pizzaId), "Pizza does not exist.");
        require(_from != _to, "Cannot transfer to the same address.");
        require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");

        ownerPizzaCount[_to] = SafeMath.add(ownerPizzaCount[_to], 1);
        ownerPizzaCount[_from] = SafeMath.sub(ownerPizzaCount[_from], 1);
        pizzaToOwner[_pizzaId] = _to;

        // Emits event defined in the imported IERC721 contract
        emit Transfer(_from, _to, _pizzaId);
        _clearApproval(_to, _pizzaId);
    }

    /**
     * Safely transfers the ownership of a given token ID to another address
     * If the target address is a contract, it must implement `onERC721Received`,
     * which is called upon a safe transfer, and return the magic value
     * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`;
     * otherwise, the transfer is reverted.
    */
    function safeTransferFrom(address from, address to, uint256 pizzaId)
        public
    {
        // solium-disable-next-line arg-overflow
        this.safeTransferFrom(from, to, pizzaId, "");
    }

    /**
     * Safely transfers the ownership of a given token ID to another address
     * If the target address is a contract, it must implement `onERC721Received`,
     * which is called upon a safe transfer, and return the magic value
     * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`;
     * otherwise, the transfer is reverted.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 pizzaId,
        bytes memory _data
    ) public {
        this.transferFrom(from, to, pizzaId);
        require(_checkOnERC721Received(from, to, pizzaId, _data), "Must implmement onERC721Received.");
    }

    /**
     * Internal function to invoke `onERC721Received` on a target address
     * The call is not executed if the target address is not a contract
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 pizzaId,
        bytes memory _data
    ) internal returns (bool) {
        if (!isContract(to)) {
            return true;
        }

        bytes4 retval = IERC721Receiver(to).onERC721Received(
            msg.sender,
            from,
            pizzaId,
            _data
        );
        return (retval == _ERC721_RECEIVED);
    }

    // Burns a Pizza - destroys Token completely
    // The `external` function modifier means this function is
    // part of the contract interface and other contracts can call it
    function burn(uint256 _pizzaId) external {
        require(msg.sender != address(0), "Invalid address.");
        require(_exists(_pizzaId), "Pizza does not exist.");
        require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");

        ownerPizzaCount[msg.sender] = SafeMath.sub(
            ownerPizzaCount[msg.sender],
            1
        );
        pizzaToOwner[_pizzaId] = address(0);
    }

    // Returns count of Pizzas by address
    function balanceOf(address _owner) public view returns (uint256 _balance) {
        return ownerPizzaCount[_owner];
    }

    // Returns owner of the Pizza found by id
    function ownerOf(uint256 _pizzaId) public view returns (address _owner) {
        address owner = pizzaToOwner[_pizzaId];
        require(owner != address(0), "Invalid Pizza ID.");
        return owner;
    }

    // Approves other address to transfer ownership of Pizza
    function approve(address _to, uint256 _pizzaId) public {
        require(msg.sender == pizzaToOwner[_pizzaId], "Must be the Pizza owner.");
        pizzaApprovals[_pizzaId] = _to;
        emit Approval(msg.sender, _to, _pizzaId);
    }

    // Returns approved address for specific Pizza
    function getApproved(uint256 _pizzaId)
        public
        view
        returns (address operator)
    {
        require(_exists(_pizzaId), "Pizza does not exist.");
        return pizzaApprovals[_pizzaId];
    }

    /**
     * Private function to clear current approval of a given token ID
     * Reverts if the given address is not indeed the owner of the token
     */
    function _clearApproval(address owner, uint256 _pizzaId) private {
        require(pizzaToOwner[_pizzaId] == owner, "Must be pizza owner.");
        require(_exists(_pizzaId), "Pizza does not exist.");
        if (pizzaApprovals[_pizzaId] != address(0)) {
            pizzaApprovals[_pizzaId] = address(0);
        }
    }

    /*
     * Sets or unsets the approval of a given operator
     * An operator is allowed to transfer all tokens of the sender on their behalf
     */
    function setApprovalForAll(address to, bool approved) public {
        require(to != msg.sender, "Cannot approve own address");
        operatorApprovals[msg.sender][to] = approved;
        emit ApprovalForAll(msg.sender, to, approved);
    }

    // Tells whether an operator is approved by a given owner
    function isApprovedForAll(address owner, address operator)
        public
        view
        returns (bool)
    {
        return operatorApprovals[owner][operator];
    }

    // Takes ownership of Pizza - only for approved users
    function takeOwnership(uint256 _pizzaId) public {
        require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");
        address owner = this.ownerOf(_pizzaId);
        this.transferFrom(owner, msg.sender, _pizzaId);
    }

    // Checks if Pizza exists
    function _exists(uint256 pizzaId) internal view returns (bool) {
        address owner = pizzaToOwner[pizzaId];
        return owner != address(0);
    }

    // Checks if address is owner or is approved to transfer Pizza
    function _isApprovedOrOwner(address spender, uint256 pizzaId)
        internal
        view
        returns (bool)
    {
        address owner = pizzaToOwner[pizzaId];
        // Disable solium check because of
        // https://github.com/duaraghav8/Solium/issues/175
        // solium-disable-next-line operator-whitespace
        return (spender == owner ||
            this.getApproved(pizzaId) == spender ||
            this.isApprovedForAll(owner, spender));
    }

    // Check if Pizza is unique and doesn't exist yet
    modifier isUnique(string memory _name, uint256 _dna) {
        bool result = true;
        for (uint256 i = 0; i < pizzas.length; i++) {
            if (
                keccak256(abi.encodePacked(pizzas[i].name)) ==
                keccak256(abi.encodePacked(_name)) &&
                pizzas[i].dna == _dna
            ) {
                result = false;
            }
        }
        require(result, "Pizza with such name already exists.");
        _;
    }

    // Returns whether the target address is a contract
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        // Currently there is no better way to check if there is a contract in an address
        // than to check the size of the code at that address.
        // See https://ethereum.stackexchange.com/a/14016/36603
        // for more details about how this works.
        // TODO Check this again before the Serenity release, because all addresses will be
        // contracts then.
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }
}

2.3 智能合约库

You don't need to write every smart contract in your project from scratch. There are many open source smart contract libraries available that provide reusable building blocks for your project that can save you from having to reinvent the wheel.

库里有什么

You can usually find two kind of building blocks in smart contract libraries: reusable behaviors you can add to your contracts, and implementations of various standards.

行为

When writing smart contracts, there is a good chance you'll find yourself writing similar patterns over and over, like assigning an admin address to carry out protected operations in a contract, or adding an emergency pause button in the event of an unexpected issue.

Smart contract libraries usually provide reusable implementations of these behaviors as libraries or via inheritance in Solidity.

As an example, following is a simplified version of the Ownable contract from the OpenZeppelin Contracts library, which designs an address as the owner of a contract, and provides a modifier for restricting access to a method only to that owner.

contract Ownable {
    address public owner;

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

    modifier onlyOwner() {
        require(owner == msg.sender, "Ownable: caller is not the owner");
        _;
    }
}

To use a building block like this in your contract, you would need to first import it, and then extend from it in your own contracts. This will allow you to use the modifier provided by the base Ownable contract to secure your own functions.

import ".../Ownable.sol"; // Path to the imported library

contract MyContract is Ownable {
    // The following function can only be called by the owner
    function secured() onlyOwner public {
        msg.sender.transfer(1 ether);
    }
}

Another popular example is SafeMath or DsMath. These are libraries (as opposed to base contracts) that provide arithmetic functions with overflow checks, which are not provided by the language. It's a good practice to use either of these libraries instead of native arithmetic operations to guard your contract against overflows, which can have disastrous consequences!

标准

To facilitate composability and interoperability, the Ethereum community has defined several standards in the form of ERCs. You can read more about them in the standards section.

When including an ERC as part of your contracts, it's a good idea to look for standard implementations rather than trying to roll out your own. Many smart contract libraries include implementations for the most popular ERCs. For example, the ubiquitous ERC20 fungible token standard can be found in HQ20, DappSys and OpenZeppelin. Additionally, some ERCs also provide canonical implementations as part of the ERC itself.

It's worth mentioning that some ERCs are not standalone, but are additions to other ERCs. For example, ERC2612 adds an extension to ERC20 for improving its usability.

为了促进可组合性和互操作性,以太坊社区以ERC的形式定义了一些标准。您可以在标准部分中阅读有关它们的更多信息。

当将ERC包含在合同中时,最好是寻找标准的实现方式,而不是尝试自己开发。许多智能合约库都包含最流行的ERC的实现。例如,可以在HQ20DappSysOpenZeppelin中找到无处不在的ERC20可替代令牌标准。此外,一些ERC还提供规范的实现,作为ERC本身的一部分。

值得一提的是,某些ERC并不是独立的,而是对其他ERC的补充。例如,ERC2612为ERC20添加了扩展,以提高其可用性。

如何添加库

Always refer to the documentation of the library you are including for specific instructions on how to include it in your project. Several Solidity contract libraries are packaged using npm, so you can just npm install them. Most tools for compiling contracts will look into your node_modules for smart contract libraries, so you can do the following:

// This will load the @openzeppelin/contracts library from your node_modules
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract MyNFT is ERC721 {
    constructor() ERC721("MyNFT", "MNFT") public { }
}

Regardless of the method you use, when including a library, always keep an eye on the language version. For instance, you cannot use a library for Solidity 0.6 if you are writing your contracts in Solidity 0.5.

何时使用

Using a smart contract library for your project has several benefits. First and foremost, it saves you time by providing you with ready-to-use building blocks you can include in your system, rather than having to code them yourself.

Security is also a major plus. Open source smart contract libraries are also often heavily scrutinized. Given many projects depend on them, there is a strong incentive by the community to keep them under constant review. It's much more common to find errors in application code than in reusable contract libraries. Some libraries also undergo external audits for additional security.

However, using smart contract libraries carry the risk of including code you are not familiar with into your project. It's tempting to import a contract and include it directly into your project, but without a good understanding of what that contract does, you may be inadvertently introducing an issue in your system due to an unexpected behavior. Always make sure to read the documentation of the code you are importing, and then review the code itself before making it a part of your project!

Last, when deciding on whether to include a library, consider its overall usage. A widely-adopted one has the benefits of having a larger community and more eyes looking into it for issues. Security should be your primary focus when building with smart contracts!

为您的项目使用智能合约库有很多好处。首先,它为您提供了可以包含在系统中的即用型构建块,而不需要自己编写代码,从而节省了时间。

安全性也是一大优点。开源智能合约库也经常受到严格审查。鉴于许多项目都依赖于它们,因此社区强烈激励人们不断对其进行审查。在应用程序代码中发现错误比在可重用合同库中发现错误更为常见。一些图书馆还接受外部审核,以提高安全性。

但是,使用智能合约库有将不熟悉的代码包含到项目中的风险。试图导入合同并将其直接包含到项目中是很诱人的,但是如果没有很好地了解该合同的功能,由于意外行为,您可能会无意间在系统中引入了问题。始终确保阅读要导入的代码的文档,然后再将代码本身纳入项目中,然后再对其进行检查!

最后,在决定是否包含库时,请考虑其整体用法。一个被广泛采用的社区的好处是拥有一个更大的社区,并有更多的眼睛关注该问题。使用智能合约进行构建时,安全性应该是您的主要重点!

相关工具

OpenZeppelin Contracts - *Most popular library for secure smart contract development.*

DappSys - *Safe, simple, flexible building-blocks for smart-contracts.*

HQ20 - *A Solidity project with contracts, libraries and examples to help you build fully-featured distributed applications for the real world.*

2.4 测试智能合约

测试工具和库

Waffle - *A framework for advanced smart contract development and testing (based on ethers.js).*

Solidity-Coverage - *Alternative solidity code coverage tool.*

hevm - *Implementation of the EVM made specifically for unit testing and debugging smart contracts.*

Whiteblock Genesis - *An end-to-end development sandbox and testing platform for blockchain.*

OpenZeppelin Test Environment - *Blazing fast smart contract testing. One-line setup for an awesome testing experience.*

OpenZeppelin Test Helpers - *Assertion library for Ethereum smart contract testing. Make sure your contracts behave as expected!*

相关教程

2.5 编译智能合约

You need to compile your contract so that your web app and the Ethereum virtual machine (EVM) can understand it.

EVM

For the EVM to be able to run your contract it needs to be in bytecode. Compilation turns this:

pragma solidity 0.4.24;

contract Greeter {

    function greet() public constant returns (string) {
        return "Hello";
    }

}

into this

PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x41 JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0xCFAE3217 EQ PUSH2 0x46 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x52 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x5B PUSH2 0xD6 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x9B JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x80 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0xC8 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x40 DUP1 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x5 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x48656C6C6F000000000000000000000000000000000000000000000000000000 DUP2 MSTORE POP SWAP1 POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 SLT 0xec 0xe 0xf5 0xf8 SLT 0xc7 0x2d STATICCALL ADDRESS SHR 0xdb COINBASE 0xb1 BALANCE 0xe8 0xf8 DUP14 0xda 0xad DUP13 LOG1 0x4c 0xb4 0x26 0xc2 DELEGATECALL PUSH7 0x8994D3E002900

Web 应用

The compiler will also produce the Application Binary Interface (ABI) which you need in order for your application to understand the contract and call the contract's functions.

The ABI is a JSON file that describes the deployed contract and its smart contract functions. This helps bridge the gap between web2 and web3

A Javascript client library will read the ABI in order for you to call on your smart contract in your web app's interface.

Below is the ABI for the ERC-20 token contract. An ERC-20 is a token you can trade on Ethereum.

[
  {
    "constant": true,
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "name": "",
        "type": "string"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_spender",
        "type": "address"
      },
      {
        "name": "_value",
        "type": "uint256"
      }
    ],
    "name": "approve",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "totalSupply",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_from",
        "type": "address"
      },
      {
        "name": "_to",
        "type": "address"
      },
      {
        "name": "_value",
        "type": "uint256"
      }
    ],
    "name": "transferFrom",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "name": "",
        "type": "uint8"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "_owner",
        "type": "address"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "name": "balance",
        "type": "uint256"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "name": "",
        "type": "string"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_to",
        "type": "address"
      },
      {
        "name": "_value",
        "type": "uint256"
      }
    ],
    "name": "transfer",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "_owner",
        "type": "address"
      },
      {
        "name": "_spender",
        "type": "address"
      }
    ],
    "name": "allowance",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "payable": true,
    "stateMutability": "payable",
    "type": "fallback"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": true,
        "name": "spender",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Approval",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Transfer",
    "type": "event"
  }
]

2.6 部署智能合约

You need to deploy your smart contract in order for it to be available to users of an Ethereum network.

To deploy a smart contract, you merely send an Ethereum transaction containing the code of the compiled smart contract without specifying any recipients.

您需要部署智能合约,以使其对以太坊网络的用户可用。

要部署智能合约,您只需发送以太坊交易,其中包含已编译的智能合约的代码,而无需指定任何接收者。

如何部署智能合约

This means you'll need to pay a transaction fee so make sure you have some ETH.

你需要什么

  • your contract's bytecode – this is generated through compilation.
  • Ether for gas – you'll set your gas limit like other transactions so be aware that contract deployment needs a lot more gas than a simple ETH transfer.
  • a deployment script or plugin.
  • access to an Ethereum node, either by running your own, connecting to a public node, or via an API key using a service like Infura or Alchemy

Once deployed, your contract will have an Ethereum address like other accounts.

相关工具

Remix - *Remix IDE allows developing, deploying and administering smart contracts for Ethereum like blockchains.*

Tenderly - *A platform to easily monitor your smart contracts with error tracking, alerting, performance metrics, and detailed contract analytics.*

相关教程

2.7 智能合约可组合性

Smart contracts are public on Ethereum and can be thought of as open APIs. You don't need to write your own smart contract to become a dapp developer, you just need to know how to interact with them. For example, you can use the existing smart contracts of Uniswap, a decentralized exchange, to handle all the token swap logic in your app – you don't need to start from scratch. Check out some of their contracts.

智能合约在以太坊上是公开的,可以被认为是开放的API。您无需编写自己的智能合约即可成为dapp开发人员,只需要知道如何与之交互即可。例如,您可以使用去中心化交易所Uniswap的现有智能合约来处理应用程序中的所有令牌交换逻辑-无需从头开始。查看他们的一些合同

3. 开发网络

When building an Ethereum application with smart contracts, you'll want to run it on a local network to see how it works before deploying it.

Similar to how you might run a local server on your computer for web development, you can use a development network to create a local blockchain instance to test your dapp. These Ethereum development networks provide features that allow for much faster iteration than a public testnet (for instance you don’t need to deal with acquiring ETH from a testnet faucet).

使用智能合约构建以太坊应用程序时,您需要在本地网络上运行该应用程序,以查看其工作原理,然后再进行部署。

与您可以在计算机上运行本地服务器进行Web开发的方式类似,您可以使用开发网络创建本地区块链实例来测试dapp。这些以太坊开发网络提供的功能比公共测试网提供更快的迭代速度(例如,您无需处理从testnet faucet获取ETH)。

什么是开发网络?

Development networks are essentially Ethereum clients (implementations of Ethereum) designed specifically for local development.

Why not just run a standard Ethereum node locally?

You could run a node (like Geth, OpenEthereum, or Nethermind) but since development networks are purpose-built for development, they often come packed with convenient features like:

  • Deterministically seeding your local blockchain with data (e.g. accounts with ETH balances)
  • Instantly mining blocks with each transaction it receives, in order and with no delay
  • Enhanced debugging and logging functionality

可用工具

Note: Most development frameworks include a built-in development network. We recommend starting with a framework to set up your local development environment.

Ganache

Quickly fire up a personal Ethereum blockchain which you can use to run tests, execute commands, and inspect state while controlling how the chain operates.

Ganache provides both a desktop application (Ganache UI), as well as a command-line tool (ganache-cli). It is part of the Truffle suite of tools.

Hardhat Network

A local Ethereum network designed for development. It allows you to deploy your contracts, run your tests and debug your code

Hardhat Network comes built-in with Hardhat, an Ethereum development environment for professionals.

4. DAPP开发框架

框架介绍

Building a full-fledged dapp requires different pieces of technology. Software frameworks include many of the needed features or provide easy plugin systems to pick the tools you desire.

Frameworks come with a lot of out-of-the-box functionality, like:

  • Features to spin up a local blockchain instance.
  • Utilities to compile and test your smart contracts.
  • Client development add-ons to build your user-facing application within the same project/repository.
  • Configuration to connect to Ethereum networks and deploy contracts, whether to a locally running instance, or one of Ethereum's public networks.
  • Decentralized app distribution - integrations with storage options like IPFS.

构建成熟的dapp需要不同的技术。软件框架包括许多必需的功能,或者提供简单的插件系统来选择所需的工具。

框架具有许多现成的功能,例如:

  • 启动本地区块链实例的功能。
  • 编译和测试您的智能合约的实用程序。
  • 客户端开发附加组件可在同一项目/存储库中构建面向用户的应用程序。
  • 配置以连接到以太坊网络并部署合同(无论是本地运行的实例还是以太坊的公共网络之一)。
  • 分散的应用程序分发-与IPFS等存储选项集成。

可用的框架

Truffle - *A development environment, testing framework, build pipeline, and other tools.*

Hardhat - *Ethereum development environment for professionals*

Brownie - *Python-based development environment and testing framework.*

Embark - *A development environment, testing framework, and other tools integrated with Ethereum, IPFS, and Whisper.*

Web3j - *A platform for developing blockchain applications on the JVM*

OpenZeppelin SDK - *The Ultimate Smart Contract Toolkit: A suite of tools to help you develop, compile, upgrade, deploy and interact with smart contracts.*

Create Eth App - *Create Ethereum-powered apps with one command. Comes with a wide offering of UI frameworks and DeFi templates to choose from.*

Scaffold-Eth - *Ethers.js + Hardhat + React components and hooks for web3: everything you need to get started building decentralized applications powered by smart contracts.*

The Graph - *The Graph for querying blockchain data efficiently*

Alchemy - *Ethereum Development Platform.*

Etherlime - *Ethers.js based framework for dapp development (Solidity & Vyper), deployment, debugging, testing and more.*

6. Ethereum client APIs

6.1 JavaScript APIs

In order for a web app to interact with the Ethereum blockchain (i.e. read blockchain data and/or send transactions to the network), it must connect to an Ethereum node.

For this purpose, every Ethereum client implements the JSON-RPC specification, so there are a uniform set of endpoints that applications can rely on.

If you want to use JavaScript to connect with an Ethereum node, it's possible to use vanilla JavaScript but several convenience libraries exist within the ecosystem that make this much easier. With these libraries, developers can write intuitive, one-line methods to initialize JSON RPC requests (under the hood) that interact with Ethereum.

为了使Web应用程序与以太坊区块链进行交互(即读取区块链数据和/或向网络发送交易),它必须连接到以太坊节点。

为此,每个以太坊客户端都实现JSON-RPC规范,因此应用程序可以依赖一组统一的端点

如果您想使用JavaScript与以太坊节点连接,则可以使用原始JavaScript,但是生态系统中存在多个便捷库,这使此操作变得更加容易。使用这些库,开发人员可以编写直观的单行方法来初始化与以太坊交互的JSON RPC请求(在后台)。

为什么要使用库?

These libraries abstract away much of the complexity of interacting directly with an Ethereum node. They also provide utility functions (e.g. converting ETH to Gwei) so as a developer you can spend less time dealing with the intricacies of Ethereum clients and more time focused on the unique functionality of your application.

这些库消除了直接与以太坊节点进行交互的许多复杂性。它们还提供实用程序功能(例如,将ETH转换为Gwei),因此,作为开发人员,您可以花更少的时间来处理复杂的以太坊客户端,而将更多的时间集中在应用程序的独特功能上。

库特点

连接到以太坊节点

Using providers, these libraries allow you to connect to Ethereum and read its data, whether that's over JSON-RPC, INFURA, Etherscan, Alchemy or MetaMask.

Ethers example

// A Web3Provider wraps a standard Web3 provider, which is
// what Metamask injects as window.ethereum into each page
const provider = new ethers.providers.Web3Provider(window.ethereum)

// The Metamask plugin also allows signing transactions to
// send ether and pay to change state within the blockchain.
// For this, we need the account signer...
const signer = provider.getSigner()

Web3js example

var web3 = new Web3("http://localhost:8545")
// or
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))

// change provider
web3.setProvider("ws://localhost:8546")
// or
web3.setProvider(new Web3.providers.WebsocketProvider("ws://localhost:8546"))

// Using the IPC provider in node.js
var net = require("net")
var web3 = new Web3("/Users/myuser/Library/Ethereum/geth.ipc", net) // mac os path
// or
var web3 = new Web3(
  new Web3.providers.IpcProvider("/Users/myuser/Library/Ethereum/geth.ipc", net)
) // mac os path
// on windows the path is: "\\\\.\\pipe\\geth.ipc"
// on linux the path is: "/users/myuser/.ethereum/geth.ipc"

Once set up you'll be able to query the blockchain for:

  • block numbers
  • gas estimates
  • smart contract events
  • network id
  • and more...

钱包功能

These libraries give you functionality to create wallets, manage keys and sign transactions.

Here's an examples from Ethers

// Create a wallet instance from a mnemonic...
mnemonic =
  "announce room limb pattern dry unit scale effort smooth jazz weasel alcohol"
walletMnemonic = Wallet.fromMnemonic(mnemonic)

// ...or from a private key
walletPrivateKey = new Wallet(walletMnemonic.privateKey)

walletMnemonic.address === walletPrivateKey.address
// true

// The address as a Promise per the Signer API
walletMnemonic.getAddress()
// { Promise: '0x71CB05EE1b1F506fF321Da3dac38f25c0c9ce6E1' }

// A Wallet address is also available synchronously
walletMnemonic.address
// '0x71CB05EE1b1F506fF321Da3dac38f25c0c9ce6E1'

// The internal cryptographic components
walletMnemonic.privateKey
// '0x1da6847600b0ee25e9ad9a52abbd786dd2502fa4005dd5af9310b7cc7a3b25db'
walletMnemonic.publicKey
// '0x04b9e72dfd423bcf95b3801ac93f4392be5ff22143f9980eb78b3a860c4843bfd04829ae61cdba4b3b1978ac5fc64f5cc2f4350e35a108a9c9a92a81200a60cd64'

// The wallet mnemonic
walletMnemonic.mnemonic
// {
//   locale: 'en',
//   path: 'm/44\'/60\'/0\'/0/0',
//   phrase: 'announce room limb pattern dry unit scale effort smooth jazz weasel alcohol'
// }

// Note: A wallet created with a private key does not
//       have a mnemonic (the derivation prevents it)
walletPrivateKey.mnemonic
// null

// Signing a message
walletMnemonic.signMessage("Hello World")
// { Promise: '0x14280e5885a19f60e536de50097e96e3738c7acae4e9e62d67272d794b8127d31c03d9cd59781d4ee31fb4e1b893bd9b020ec67dfa65cfb51e2bdadbb1de26d91c' }

tx = {
  to: "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
  value: utils.parseEther("1.0"),
}

// Signing a transaction
walletMnemonic.signTransaction(tx)
// { Promise: '0xf865808080948ba1f109551bd432803012645ac136ddd64dba72880de0b6b3a7640000801ca0918e294306d177ab7bd664f5e141436563854ebe0a3e523b9690b4922bbb52b8a01181612cec9c431c4257a79b8c9f0c980a2c49bb5a0e6ac52949163eeb565dfc' }

// The connect method returns a new instance of the
// Wallet connected to a provider
wallet = walletMnemonic.connect(provider)

// Querying the network
wallet.getBalance()
// { Promise: { BigNumber: "42" } }
wallet.getTransactionCount()
// { Promise: 0 }

// Sending ether
wallet.sendTransaction(tx)

Read the full docs

Once set up you'll be able to:

  • create accounts
  • send transactions
  • sign transactions
  • and more...

与智能合约功能互动

Javascript client libraries allow your application to call smart contract functions by reading the Application Binary Interface (ABI) of a compiled contract.

The ABI essentially explains the contract's functions in a JSON format and allows you to use it like a normal JavaScript object.

So the following Solidity contract:

contract Test {
    uint a;
    address d = 0x12345678901234567890123456789012;

    function Test(uint testInt)  { a = testInt;}

    event Event(uint indexed b, bytes32 c);

    event Event2(uint indexed b, bytes32 c);

    function foo(uint b, bytes32 c) returns(address) {
        Event(b, c);
        return d;
    }
}

Would result in the following JSON:

[{
    "type":"constructor",
    "payable":false,
    "stateMutability":"nonpayable"
    "inputs":[{"name":"testInt","type":"uint256"}],
  },{
    "type":"function",
    "name":"foo",
    "constant":false,
    "payable":false,
    "stateMutability":"nonpayable",
    "inputs":[{"name":"b","type":"uint256"}, {"name":"c","type":"bytes32"}],
    "outputs":[{"name":"","type":"address"}]
  },{
    "type":"event",
    "name":"Event",
    "inputs":[{"indexed":true,"name":"b","type":"uint256"}, {"indexed":false,"name":"c","type":"bytes32"}],
    "anonymous":false
  },{
    "type":"event",
    "name":"Event2",
    "inputs":[{"indexed":true,"name":"b","type":"uint256"},{"indexed":false,"name":"c","type":"bytes32"}],
    "anonymous":false
}]

This means you can:

  • Send a transaction to the smart contract and execute its method
  • Call to estimate the gas a method execution will take when executed in the EVM
  • Deploy a contract
  • And more...

实用功能

Utility functions give you handy shortcuts that make building with Ethereum a little easier.

ETH values are in Wei by default. 1 ETH = 1,000,000,000,000,000,000 WEI – this means you're dealing with a lot of numbers! web3.utils.toWei converts ether to Wei for you.

And in ethers it looks like this:

// Get the balance of an account (by address or ENS name)
balance = await provider.getBalance("ethers.eth")
// { BigNumber: "2337132817842795605" }

// Often you will need to format the output for the user
// which prefer to see values in ether (instead of wei)
ethers.utils.formatEther(balance)
// '2.337132817842795605'

可用库

Web3.js - *Ethereum JavaScript API.*

Ethers.js - *Complete Ethereum wallet implementation and utilities in JavaScript and TypeScript.*

The Graph - *A protocol for indexing Ethereum and IPFS data and querying it using GraphQL.*

light.js - *A high-level reactive JS library optimized for light clients.*

Web3-wrapper - *Typescript alternative to Web3.js.*

Alchemyweb3 - *Wrapper around Web3.js with automatic retries and enhanced apis.*

6.2 后端API库

In order for a software application to interact with the Ethereum blockchain (i.e. read blockchain data and/or send transactions to the network), it must connect to an Ethereum node.

For this purpose, every Ethereum client implements the JSON-RPC specification, so there are a uniform set of endpoints that applications can rely on.

If you want to use a specific programming language to connect with an Ethereum node, there are many convenience libraries within the ecosystem that make this much easier. With these libraries, developers can write intuitive, one-line methods to initialize JSON-RPC requests (under the hood) that interact with Ethereum.

为了使软件应用程序与以太坊区块链进行交互(即读取区块链数据和/或向网络发送交易),它必须连接到以太坊节点。

为此,每个以太坊客户端都实现JSON-RPC规范,因此应用程序可以依赖一组统一的端点

如果您想使用特定的编程语言来连接以太坊节点,那么生态系统中有许多便利库可以简化这一工作。使用这些库,开发人员可以编写直观的单行方法来初始化与以太坊交互的JSON-RPC请求(在后台)。

为什么要使用库?

These libraries abstract away much of the complexity of interacting directly with an Ethereum node. They also provide utility functions (e.g. converting ETH to Gwei) so as a developer you can spend less time dealing with the intricacies of Ethereum clients and more time focused on the unique functionality of your application.

可用库

Alchemy - *Ethereum Development Platform.*

BlockCypher - *Ethereum Web APIs*

Infura - *The Ethereum API as a service.*

Cloudflare Ethereum Gateway.

Nodesmith - *JSON-RPC API access to Ethereum mainnet and testnets.*

Ethercluster - *Run your own Ethereum API service supporting both ETH and ETC.*

Chainstack - *Shared and dedicated Ethereum nodes as a service.*

QuikNode - *Blockchain developer platform.*

Python Tooling - *Variety of libraries for Ethereum interaction via Python.*

web3j - *A Java/Android/Kotlin/Scala integration library for Ethereum.*

Rivet - *Ethereum and Ethereum Classic APIs as a service powered by open source software.*

Nethereum - *An open source .NET integration library for blockchain.*

6.3 JSON-RPC API

In order for a software application to interact with the Ethereum blockchain (by reading blockchain data and/or sending transactions to the network), it must connect to an Ethereum node.

For this purpose, every Ethereum client implements a JSON-RPC specification, so there are a uniform set of methods that applications can rely on.

为了使软件应用程序与以太坊区块链进行交互(通过读取区块链数据和/或向网络发送交易),它必须连接到以太坊节点。

为此,每个以太坊客户端都实现JSON-RPC规范,因此应用程序可以使用一组统一的方法

库的便利性

While you may choose to interact directly with Ethereum clients via this JSON-RPC API, there are often easier options for dapp developers. Many JavaScript and backend API libraries exist to provide wrappers on top of the JSON-RPC API. With these libraries, developers can write intuitive, one-line methods in the programming language of their choice to initialize JSON-RPC requests (under the hood) that interact with Ethereum.

虽然您可以选择通过此JSON-RPC API直接与以太坊客户端进行交互,但对于dapp开发人员而言,通常会有更简单的选择。存在许多JavaScript后端API库,以在JSON-RPC API之上提供包装器。使用这些库,开发人员可以使用他们选择的编程语言编写直观的单行方法,以初始化与以太坊交互的JSON-RPC请求(在后台)。

JSON-RPC端点

Ethereum clients each may utilize different programming languages when implementing the JSON-RPC specification. See individual client documentation for further details related to specific programming languages.

JSON-RPC支持

cpp-ethereum go-ethereum py-ethereum parity hyperledger-besu
JSON-RPC 1.0
JSON-RPC 2.0
Batch requests
HTTP
IPC
WS

View full list of Ethereum clients. We recommend checking the documentation of each client for the latest API support information.

发布/订阅

Publish / subscribe (pub/sub) is a method of using JSON-RPC notifications to subscribe to Ethereum events without needing to poll for them:

十六进制值编码

At present there are two key datatypes that are passed over JSON: unformatted byte arrays and quantities. Both are passed with a hex encoding, however with different requirements to formatting:

When encoding QUANTITIES (integers, numbers): encode as hex, prefix with "0x", the most compact representation (slight exception: zero should be represented as "0x0"). Examples:

  • 0x41 (65 in decimal)
  • 0x400 (1024 in decimal)
  • WRONG: 0x (should always have at least one digit - zero is "0x0")
  • WRONG: 0x0400 (no leading zeroes allowed)
  • WRONG: ff (must be prefixed 0x)

When encoding UNFORMATTED DATA (byte arrays, account addresses, hashes, bytecode arrays): encode as hex, prefix with "0x", two hex digits per byte. Examples:

  • 0x41 (size 1, "A")
  • 0x004200 (size 3, "\0B\0")
  • 0x (size 0, "")
  • WRONG: 0xf0f0f (must be even number of digits)
  • WRONG: 004200 (must be prefixed 0x)

Currently aleth, go-ethereum, and parity provide JSON-RPC communication over http and IPC (unix socket Linux and OSX/named pipes on Windows). Version 1.4 of go-ethereum, version 1.6 of Parity and version 1.3 of Hyperledger Besu onwards have websocket support.

当前,有两种通过JSON传递的关键数据类型:未格式化的字节数组和数量。两者都以十六进制编码传递,但是对格式有不同的要求:

编码QUANTITIES(整数,数字)时:编码为十六进制,前缀为“ 0x”,这是最紧凑的表示形式(轻微的例外:零应表示为“ 0x0”)。例子:

  • 0x41(十进制65)
  • 0x400(十进制1024)
  • 错误:0x(应始终至少有一位数字-零为“ 0x0”)
  • 错误:0x0400(不允许使用前导零)
  • 错误:ff(必须以0x开头)

编码UNFORMATTED DATA(字节数组,帐户地址,哈希,字节码数组)时:编码为十六进制,前缀为“ 0x”,每个字节两个十六进制数字。例子:

  • 0x41(大小1,“ A”)
  • 0x004200(大小3,“ \ 0B \ 0”)
  • 0x(大小0,“”)
  • 错误:0xf0f0f(必须为偶数位数)
  • 错误:004200(必须以0x开头)

当前,alethgo-ethereumparity通过http和IPC(Windows上的unix套接字Linux和OSX /命名管道)提供JSON-RPC通信。go-ethereum的1.4版,Parity的1.6版和Hyperledger Besu的1.3版及更高版本都具有websocket支持。

默认块参数

The following methods have an extra default block parameter:

When requests are made that act on the state of Ethereum, the last default block parameter determines the height of the block.

The following options are possible for the defaultBlock parameter:

  • HEX String - an integer block number
  • String "earliest" for the earliest/genesis block
  • String "latest" - for the latest mined block
  • String "pending" - for the pending state/transactions

Curl 例子

The curl options below might return a response where the node complains about the content type, this is because the --data option sets the content type to application/x-www-form-urlencoded. If your node does complain, manually set the header by placing -H "Content-Type: application/json" at the start of the call.

The examples also do not include the URL/IP & port combination which must be the last argument given to curl (e.x. 127.0.0.1:8545)

Gossip, State, History

Gossip Methods

These methods track the head of the chain. This is how transactions make their way around the network, find their way into blocks, and how clients find out about new blocks.

State Methods

Methods that report the current state of all the data stored. The "state" is like one big shared piece of RAM, and includes account balances, contract data, and gas estimations.

History Methods

Fetches historical records of every block back to genesis. This is like one large append-only file, and includes all block headers, block bodies, uncle blocks, and transaction receipts.

JSON-RPC API 方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容