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直接在前端。或者,您可能会选择使用Python或Java 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.*
- openzeppelin.com/contracts/
- GitHub
- Community Forum
DappSys - *Safe, simple, flexible building-blocks for smart-contracts.*
- dapp.tools/dappsys
- GitHub
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
- Documentation
- Solidity Language Portal
- Solidity by Example
- GitHub
- Solidity Gitter Chatroom bridged to Solidity Matrix Chatroom
- Cheat Sheet
- Solidity Blog
- Solidity Twitter
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:
- See Solidity types
内存
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, butthis.f()
works).
- External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function
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 typestring
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:
- Writing to state variables.
- Emitting events.
- Creating other contracts.
- Using
selfdestruct
. - Sending ether via calls.
- Calling any function not marked
view
orpure
. - Using low-level calls.
- 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的实现。例如,可以在HQ20,DappSys和OpenZeppelin中找到无处不在的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.*
- Documentation
- GitHub
- Community Forum
DappSys - *Safe, simple, flexible building-blocks for smart-contracts.*
- Documentation
- GitHub
HQ20 - *A Solidity project with contracts, libraries and examples to help you build fully-featured distributed applications for the real world.*
- GitHub
2.4 测试智能合约
测试工具和库
Waffle - *A framework for advanced smart contract development and testing (based on ethers.js).*
- getwaffle.io
- GitHub
Solidity-Coverage - *Alternative solidity code coverage tool.*
- GitHub
hevm - *Implementation of the EVM made specifically for unit testing and debugging smart contracts.*
- GitHub
- DappHub Chat
Whiteblock Genesis - *An end-to-end development sandbox and testing platform for blockchain.*
- Whiteblock.io
- Documentation
- GitHub
OpenZeppelin Test Environment - *Blazing fast smart contract testing. One-line setup for an awesome testing experience.*
- GitHub
- Documentation
OpenZeppelin Test Helpers - *Assertion library for Ethereum smart contract testing. Make sure your contracts behave as expected!*
- GitHub
- Documentation
相关教程
- Solidity and Truffle Continuous Integration Setup – How to setup Travis or Circle CI for Truffle testing along with useful plugins.
- Testing products overview – An overview and comparison of different testing products.
- How to use Echidna to test smart contracts
- How to use Manticore to find smart contract bugs
- How to use Slither to find smart contract bugs
- How to mock Solidity contracts for testing
- How to migrate from Truffle Tests to OpenZeppelin Test Environment
- How to test contracts after they have been deployed on a network
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.*
- Remix
Tenderly - *A platform to easily monitor your smart contracts with error tracking, alerting, performance metrics, and detailed contract analytics.*
- tenderly.co
- GitHub
- Discord
相关教程
- Deploying your first smart contract – An introduction to deploying your first smart contract on an Ethereum test network.
- Interact with other contracts from Solidity – How to deploy a smart contract from an existing contract and interact with it.
- How to downsize your contract size - How to reduce your contract's size to keep it under the limit and save on gas
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.
- Website
- GitHub
- Documentation
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.
- Website
- GitHub
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.*
- trufflesuite.com
- GitHub
Hardhat - *Ethereum development environment for professionals*
- hardhat.org
- GitHub
Brownie - *Python-based development environment and testing framework.*
- Documentation
- GitHub
Embark - *A development environment, testing framework, and other tools integrated with Ethereum, IPFS, and Whisper.*
- Documentation
- GitHub
Web3j - *A platform for developing blockchain applications on the JVM*
- Homepage
- Documentation
- GitHub
OpenZeppelin SDK - *The Ultimate Smart Contract Toolkit: A suite of tools to help you develop, compile, upgrade, deploy and interact with smart contracts.*
- OpenZeppelin SDK
- GitHub
- Community Forum
Create Eth App - *Create Ethereum-powered apps with one command. Comes with a wide offering of UI frameworks and DeFi templates to choose from.*
- GitHub
- Templates
Scaffold-Eth - *Ethers.js + Hardhat + React components and hooks for web3: everything you need to get started building decentralized applications powered by smart contracts.*
- GitHub
The Graph - *The Graph for querying blockchain data efficiently*
- Website
- Tutorial
Alchemy - *Ethereum Development Platform.*
- alchemyapi.io
- GitHub
- Discord
Etherlime - *Ethers.js based framework for dapp development (Solidity & Vyper), deployment, debugging, testing and more.*
- Docs
- GitHub
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'
- Web3js utility functions
- Ethers utility functions
可用库
Web3.js - *Ethereum JavaScript API.*
- Documentation
- GitHub
Ethers.js - *Complete Ethereum wallet implementation and utilities in JavaScript and TypeScript.*
- Documentation
- GitHub
The Graph - *A protocol for indexing Ethereum and IPFS data and querying it using GraphQL.*
- The Graph
- Graph Explorer
- Documentation
- GitHub
- Discord
light.js - *A high-level reactive JS library optimized for light clients.*
- GitHub
Web3-wrapper - *Typescript alternative to Web3.js.*
- Documentation
- GitHub
Alchemyweb3 - *Wrapper around Web3.js with automatic retries and enhanced apis.*
- Documentation
- GitHub
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.*
- alchemyapi.io
- Documentation
- GitHub
- Discord
BlockCypher - *Ethereum Web APIs*
- blockcypher.com
- Documentation
Infura - *The Ethereum API as a service.*
- infura.io
- Documentation
- GitHub
Cloudflare Ethereum Gateway.
- cloudflare-eth.com
Nodesmith - *JSON-RPC API access to Ethereum mainnet and testnets.*
- nodesmith.io
- Documentation
Ethercluster - *Run your own Ethereum API service supporting both ETH and ETC.*
- ethercluster.com
Chainstack - *Shared and dedicated Ethereum nodes as a service.*
- chainstack.com
- Documentation
QuikNode - *Blockchain developer platform.*
- quiknode.io
Python Tooling - *Variety of libraries for Ethereum interaction via Python.*
- py.ethereum.org
- web3.py GitHub
- web3.py Chat
web3j - *A Java/Android/Kotlin/Scala integration library for Ethereum.*
- GitHub
- Docs
- Gitter
Rivet - *Ethereum and Ethereum Classic APIs as a service powered by open source software.*
- rivet.cloud
- Documentation
- GitHub
Nethereum - *An open source .NET integration library for blockchain.*
- GitHub
- Documentation
- Discord
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:
- Geth v1.4
- Hyperledger Besu 1.3
十六进制值编码
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开头)
当前,aleth,go-ethereum和parity通过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:
- eth_getBalance
- eth_getCode
- eth_getTransactionCount
- eth_getStorageAt
- eth_call
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.
- eth_blockNumber
- eth_sendRawTransaction
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.
- eth_getBalance
- eth_getStorageAt
- eth_getTransactionCount
- eth_getCode
- eth_call
- eth_estimateGas
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.
- eth_getBlockTransactionCountByHash
- eth_getBlockTransactionCountByNumber
- eth_getUncleCountByBlockHash
- eth_getUncleCountByBlockNumber
- eth_getBlockByHash
- eth_getBlockByNumber
- eth_getTransactionByHash
- eth_getTransactionByBlockHashAndIndex
- eth_getTransactionByBlockNumberAndIndex
- eth_getTransactionReceipt
- eth_getUncleByBlockHashAndIndex
- eth_getUncleByBlockNumberAndIndex