Vyper 示例

简单公开拍卖

作为使用 Vyper 编写的智能合约的入门示例,我们将从一个简单的公开拍卖合约开始。在我们深入研究代码时,请记住,所有 Vyper 语法都是有效的 Python3 语法,但并非所有 Python3 功能在 Vyper 中都可用。

在这个合约中,我们将研究一个简单的公开拍卖合约,参与者可以在有限的时间段内提交出价。拍卖期结束后,预定的受益者将收到最高出价的金额。

 1# Open Auction
 2
 3# Auction params
 4# Beneficiary receives money from the highest bidder
 5beneficiary: public(address)
 6auctionStart: public(uint256)
 7auctionEnd: public(uint256)
 8
 9# Current state of auction
10highestBidder: public(address)
11highestBid: public(uint256)
12
13# Set to true at the end, disallows any change
14ended: public(bool)
15
16# Keep track of refunded bids so we can follow the withdraw pattern
17pendingReturns: public(HashMap[address, uint256])
18
19# Create a simple auction with `_auction_start` and
20# `_bidding_time` seconds bidding time on behalf of the
21# beneficiary address `_beneficiary`.
22@external
23def __init__(_beneficiary: address, _auction_start: uint256, _bidding_time: uint256):
24    self.beneficiary = _beneficiary
25    self.auctionStart = _auction_start  # auction start time can be in the past, present or future
26    self.auctionEnd = self.auctionStart + _bidding_time
27    assert block.timestamp < self.auctionEnd # auction end time should be in the future
28
29# Bid on the auction with the value sent
30# together with this transaction.
31# The value will only be refunded if the
32# auction is not won.
33@external
34@payable
35def bid():
36    # Check if bidding period has started.
37    assert block.timestamp >= self.auctionStart
38    # Check if bidding period is over.
39    assert block.timestamp < self.auctionEnd
40    # Check if bid is high enough
41    assert msg.value > self.highestBid
42    # Track the refund for the previous high bidder
43    self.pendingReturns[self.highestBidder] += self.highestBid
44    # Track new high bid
45    self.highestBidder = msg.sender
46    self.highestBid = msg.value
47
48# Withdraw a previously refunded bid. The withdraw pattern is
49# used here to avoid a security issue. If refunds were directly
50# sent as part of bid(), a malicious bidding contract could block
51# those refunds and thus block new higher bids from coming in.
52@external
53def withdraw():
54    pending_amount: uint256 = self.pendingReturns[msg.sender]
55    self.pendingReturns[msg.sender] = 0
56    send(msg.sender, pending_amount)
57
58# End the auction and send the highest bid
59# to the beneficiary.
60@external
61def endAuction():
62    # It is a good guideline to structure functions that interact
63    # with other contracts (i.e. they call functions or send Ether)
64    # into three phases:
65    # 1. checking conditions
66    # 2. performing actions (potentially changing conditions)
67    # 3. interacting with other contracts
68    # If these phases are mixed up, the other contract could call
69    # back into the current contract and modify the state or cause
70    # effects (Ether payout) to be performed multiple times.
71    # If functions called internally include interaction with external
72    # contracts, they also have to be considered interaction with
73    # external contracts.
74
75    # 1. Conditions
76    # Check if auction endtime has been reached
77    assert block.timestamp >= self.auctionEnd
78    # Check if this function has already been called
79    assert not self.ended
80
81    # 2. Effects
82    self.ended = True
83
84    # 3. Interaction
85    send(self.beneficiary, self.highestBid)

如您所见,此示例仅包含一个构造函数、两种可调用的方法和一些用于管理合约状态的变量。信不信由你,这就是实现拍卖智能合约的基本功能所需的一切。

让我们开始吧!

 3# Auction params
 4# Beneficiary receives money from the highest bidder
 5beneficiary: public(address)
 6auctionStart: public(uint256)
 7auctionEnd: public(uint256)
 8
 9# Current state of auction
10highestBidder: public(address)
11highestBid: public(uint256)
12
13# Set to true at the end, disallows any change
14ended: public(bool)
15
16# Keep track of refunded bids so we can follow the withdraw pattern
17pendingReturns: public(HashMap[address, uint256])

我们首先声明一些变量来跟踪我们的合约状态。我们通过调用 public 在数据类型 address 上初始化一个全局变量 beneficiary。该 beneficiary 将是最高出价者资金的接收者。我们还使用数据类型 uint256 初始化变量 auctionStartauctionEnd 来管理公开拍卖期,并使用数据类型 uint256 初始化变量 highestBid(以最小的以太币为单位)来管理拍卖状态。变量 ended 是一个布尔值,用于确定拍卖是否正式结束。变量 pendingReturns 是一个 map,它允许使用键值对来正确跟踪拍卖的提取模式。

您可能注意到所有变量都被传递到 public 函数中。通过声明变量为 public,该变量可由外部合约调用。在没有 public 函数的情况下初始化变量会默认声明为私有,因此只能被同一合约中的方法访问。该 public 函数还会为该变量创建一个“getter”函数,可以通过外部调用访问,例如 contract.beneficiary()

现在,构造函数。

22@external
23def __init__(_beneficiary: address, _auction_start: uint256, _bidding_time: uint256):
24    self.beneficiary = _beneficiary
25    self.auctionStart = _auction_start  # auction start time can be in the past, present or future
26    self.auctionEnd = self.auctionStart + _bidding_time
27    assert block.timestamp < self.auctionEnd # auction end time should be in the future

该合约使用三个参数进行初始化:类型为 address_beneficiary、类型为 uint256_auction_start 和类型为 uint256_bidding_time(拍卖开始时间和结束时间之间的时间差)。然后我们将这三部分信息分别存储到合约变量 self.beneficiaryself.auctionStartself.auctionEnd 中。请注意,我们可以通过调用 block.timestamp 访问当前时间。 block 是任何 Vyper 合约中都可用的一个对象,它提供有关调用时块的信息。与 block 类似,另一个对我们来说很重要的对象是 msg,它提供有关方法调用者的信息,我们很快就会看到这一点。

完成初始设置后,让我们看看用户如何出价。

33@external
34@payable
35def bid():
36    # Check if bidding period has started.
37    assert block.timestamp >= self.auctionStart
38    # Check if bidding period is over.
39    assert block.timestamp < self.auctionEnd
40    # Check if bid is high enough
41    assert msg.value > self.highestBid
42    # Track the refund for the previous high bidder
43    self.pendingReturns[self.highestBidder] += self.highestBid
44    # Track new high bid
45    self.highestBidder = msg.sender
46    self.highestBid = msg.value

@payable 装饰器将允许用户向合约发送一些以太币来调用装饰的方法。在这种情况下,想要出价的用户会在发送等同于他们想要出价的金额(不包括汽油费)的同时调用 bid() 方法。在调用合约中的任何方法时,都会提供一个内置变量 msg,并且我们可以使用 msg.sender 访问任何方法调用者的公共地址。类似地,用户发送的以太币金额可以通过调用 msg.value 来访问。

在这里,我们首先使用 assert 函数检查当前时间是否在出价期内,该函数使用任何布尔语句进行比较,并使用拍卖的开始时间和结束时间进行比较。我们还检查新出价是否大于最高出价。如果三个 assert 语句通过,我们可以安全地继续执行下一行;否则,bid() 方法将抛出错误并回滚交易。如果两个 assert 语句和检查先前出价不等于零通过,我们可以安全地得出结论,我们有一个有效的新的最高出价。我们将把先前的 highestBid 发送回先前的 highestBidder 并设置我们的新 highestBidhighestBidder

60@external
61def endAuction():
62    # It is a good guideline to structure functions that interact
63    # with other contracts (i.e. they call functions or send Ether)
64    # into three phases:
65    # 1. checking conditions
66    # 2. performing actions (potentially changing conditions)
67    # 3. interacting with other contracts
68    # If these phases are mixed up, the other contract could call
69    # back into the current contract and modify the state or cause
70    # effects (Ether payout) to be performed multiple times.
71    # If functions called internally include interaction with external
72    # contracts, they also have to be considered interaction with
73    # external contracts.
74
75    # 1. Conditions
76    # Check if auction endtime has been reached
77    assert block.timestamp >= self.auctionEnd
78    # Check if this function has already been called
79    assert not self.ended
80
81    # 2. Effects
82    self.ended = True
83
84    # 3. Interaction
85    send(self.beneficiary, self.highestBid)

使用 endAuction() 方法,我们检查当前时间是否超过了我们在合约初始化时设置的 auctionEnd 时间。我们还检查 self.ended 是否之前没有被设置为 True。我们这样做是为了防止在拍卖已经结束时对该方法进行任何调用,如果未进行此检查,这可能会造成潜在的恶意行为。然后我们通过将 self.ended 设置为 True 并将最高出价金额发送给受益者来正式结束拍卖。

就这样 - 一个公开拍卖合约。当然,这是一个简化的示例,具有最基本的功能,可以改进。希望这能提供一些关于 Vyper 潜力的见解。当我们继续探索更复杂的示例时,我们将遇到更多 Vyper 语言的设计模式和功能。

当然,没有哪个智能合约教程是完整的,没有关于安全性的注意事项。

注意

在设计智能合约时,始终牢记安全性非常重要。随着任何应用程序变得更加复杂,引入新的风险的可能性也随之增加。因此,始终保持合约的可读性和简洁性是一种良好的做法。

随时准备好了,让我们在下一个示例中更上一层楼。

盲拍

在我们深入研究其他示例之前,让我们简要探索一下您可以使用 Vyper 构建的另一种类型的拍卖。与 Solidity 中编写的 对应物 相似,这种盲拍允许拍卖在出价期结束时没有时间压力。

  1# Blind Auction. Adapted to Vyper from [Solidity by Example](https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst#blind-auction-1)
  2
  3struct Bid:
  4  blindedBid: bytes32
  5  deposit: uint256
  6
  7# Note: because Vyper does not allow for dynamic arrays, we have limited the
  8# number of bids that can be placed by one address to 128 in this example
  9MAX_BIDS: constant(int128) = 128
 10
 11# Event for logging that auction has ended
 12event AuctionEnded:
 13    highestBidder: address
 14    highestBid: uint256
 15
 16# Auction parameters
 17beneficiary: public(address)
 18biddingEnd: public(uint256)
 19revealEnd: public(uint256)
 20
 21# Set to true at the end of auction, disallowing any new bids
 22ended: public(bool)
 23
 24# Final auction state
 25highestBid: public(uint256)
 26highestBidder: public(address)
 27
 28# State of the bids
 29bids: HashMap[address, Bid[128]]
 30bidCounts: HashMap[address, int128]
 31
 32# Allowed withdrawals of previous bids
 33pendingReturns: HashMap[address, uint256]
 34
 35
 36# Create a blinded auction with `_biddingTime` seconds bidding time and
 37# `_revealTime` seconds reveal time on behalf of the beneficiary address
 38# `_beneficiary`.
 39@external
 40def __init__(_beneficiary: address, _biddingTime: uint256, _revealTime: uint256):
 41    self.beneficiary = _beneficiary
 42    self.biddingEnd = block.timestamp + _biddingTime
 43    self.revealEnd = self.biddingEnd + _revealTime
 44
 45
 46# Place a blinded bid with:
 47#
 48# _blindedBid = keccak256(concat(
 49#       convert(value, bytes32),
 50#       convert(fake, bytes32),
 51#       secret)
 52# )
 53#
 54# The sent ether is only refunded if the bid is correctly revealed in the
 55# revealing phase. The bid is valid if the ether sent together with the bid is
 56# at least "value" and "fake" is not true. Setting "fake" to true and sending
 57# not the exact amount are ways to hide the real bid but still make the
 58# required deposit. The same address can place multiple bids.
 59@external
 60@payable
 61def bid(_blindedBid: bytes32):
 62    # Check if bidding period is still open
 63    assert block.timestamp < self.biddingEnd
 64
 65    # Check that payer hasn't already placed maximum number of bids
 66    numBids: int128 = self.bidCounts[msg.sender]
 67    assert numBids < MAX_BIDS
 68
 69    # Add bid to mapping of all bids
 70    self.bids[msg.sender][numBids] = Bid({
 71        blindedBid: _blindedBid,
 72        deposit: msg.value
 73        })
 74    self.bidCounts[msg.sender] += 1
 75
 76
 77# Returns a boolean value, `True` if bid placed successfully, `False` otherwise.
 78@internal
 79def placeBid(bidder: address, _value: uint256) -> bool:
 80    # If bid is less than highest bid, bid fails
 81    if (_value <= self.highestBid):
 82        return False
 83
 84    # Refund the previously highest bidder
 85    if (self.highestBidder != empty(address)):
 86        self.pendingReturns[self.highestBidder] += self.highestBid
 87
 88    # Place bid successfully and update auction state
 89    self.highestBid = _value
 90    self.highestBidder = bidder
 91
 92    return True
 93
 94
 95# Reveal your blinded bids. You will get a refund for all correctly blinded
 96# invalid bids and for all bids except for the totally highest.
 97@external
 98def reveal(_numBids: int128, _values: uint256[128], _fakes: bool[128], _secrets: bytes32[128]):
 99    # Check that bidding period is over
100    assert block.timestamp > self.biddingEnd
101
102    # Check that reveal end has not passed
103    assert block.timestamp < self.revealEnd
104
105    # Check that number of bids being revealed matches log for sender
106    assert _numBids == self.bidCounts[msg.sender]
107
108    # Calculate refund for sender
109    refund: uint256 = 0
110    for i in range(MAX_BIDS):
111        # Note that loop may break sooner than 128 iterations if i >= _numBids
112        if (i >= _numBids):
113            break
114
115        # Get bid to check
116        bidToCheck: Bid = (self.bids[msg.sender])[i]
117
118        # Check against encoded packet
119        value: uint256 = _values[i]
120        fake: bool = _fakes[i]
121        secret: bytes32 = _secrets[i]
122        blindedBid: bytes32 = keccak256(concat(
123            convert(value, bytes32),
124            convert(fake, bytes32),
125            secret
126        ))
127
128        # Bid was not actually revealed
129        # Do not refund deposit
130        assert blindedBid == bidToCheck.blindedBid
131
132        # Add deposit to refund if bid was indeed revealed
133        refund += bidToCheck.deposit
134        if (not fake and bidToCheck.deposit >= value):
135            if (self.placeBid(msg.sender, value)):
136                refund -= value
137
138        # Make it impossible for the sender to re-claim the same deposit
139        zeroBytes32: bytes32 = empty(bytes32)
140        bidToCheck.blindedBid = zeroBytes32
141
142    # Send refund if non-zero
143    if (refund != 0):
144        send(msg.sender, refund)
145
146
147# Withdraw a bid that was overbid.
148@external
149def withdraw():
150    # Check that there is an allowed pending return.
151    pendingAmount: uint256 = self.pendingReturns[msg.sender]
152    if (pendingAmount > 0):
153        # If so, set pending returns to zero to prevent recipient from calling
154        # this function again as part of the receiving call before `transfer`
155        # returns (see the remark above about conditions -> effects ->
156        # interaction).
157        self.pendingReturns[msg.sender] = 0
158
159        # Then send return
160        send(msg.sender, pendingAmount)
161
162
163# End the auction and send the highest bid to the beneficiary.
164@external
165def auctionEnd():
166    # Check that reveal end has passed
167    assert block.timestamp > self.revealEnd
168
169    # Check that auction has not already been marked as ended
170    assert not self.ended
171
172    # Log auction ending and set flag
173    log AuctionEnded(self.highestBidder, self.highestBid)
174    self.ended = True
175
176    # Transfer funds to beneficiary
177    send(self.beneficiary, self.highestBid)

虽然这种盲拍在功能上几乎与 Solidity 中实现的盲拍相同,但它们的实现方式不同,这有助于说明 Solidity 和 Vyper 之间的差异。

28# State of the bids
29bids: HashMap[address, Bid[128]]
30bidCounts: HashMap[address, int128]

一个主要区别是,由于 Vyper 不允许动态数组,因此在本示例中,我们限制了单个地址可以进行的出价次数为 128。想要进行超过此最大出价次数的竞拍者需要从多个地址进行出价。

安全远程购买

在本示例中,我们有一个托管合约,它实现了一个系统,用于在买方和卖方之间进行无信任交易。在这个系统中,卖方发布待售商品,并向合约存入两倍于商品 value 的金额。此时,合约的余额为 2 * value。只要买方尚未购买,卖方就可以收回押金并关闭交易。如果买方有兴趣购买,他们会支付款项并提交等额的押金(总计 2 * value)到合约中,并锁定合约以防止进一步修改。此时,合约的余额为 4 * value,卖方会将商品发送给买方。在买方收到商品后,买方将在合约中标记商品已收到,从而退回买方的押金(而不是付款),并将剩余资金释放给卖方,从而完成交易。

当然,还有一些其他方法可以设计一个更安全的托管系统,为买方和卖方都减少开销,但为了本示例的目的,我们要探讨一种如何在无信任的情况下实现托管系统的方法。

让我们开始吧!

 1# Safe Remote Purchase
 2# Originally from
 3# https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst
 4# Ported to vyper and optimized.
 5
 6# Rundown of the transaction:
 7# 1. Seller posts item for sale and posts safety deposit of double the item value.
 8#    Balance is 2*value.
 9#    (1.1. Seller can reclaim deposit and close the sale as long as nothing was purchased.)
10# 2. Buyer purchases item (value) plus posts an additional safety deposit (Item value).
11#    Balance is 4*value.
12# 3. Seller ships item.
13# 4. Buyer confirms receiving the item. Buyer's deposit (value) is returned.
14#    Seller's deposit (2*value) + items value is returned. Balance is 0.
15
16value: public(uint256) #Value of the item
17seller: public(address)
18buyer: public(address)
19unlocked: public(bool)
20ended: public(bool)
21
22@external
23@payable
24def __init__():
25    assert (msg.value % 2) == 0
26    self.value = msg.value / 2  # The seller initializes the contract by
27        # posting a safety deposit of 2*value of the item up for sale.
28    self.seller = msg.sender
29    self.unlocked = True
30
31@external
32def abort():
33    assert self.unlocked #Is the contract still refundable?
34    assert msg.sender == self.seller # Only the seller can refund
35        # his deposit before any buyer purchases the item.
36    selfdestruct(self.seller) # Refunds the seller and deletes the contract.
37
38@external
39@payable
40def purchase():
41    assert self.unlocked # Is the contract still open (is the item still up
42                         # for sale)?
43    assert msg.value == (2 * self.value) # Is the deposit the correct value?
44    self.buyer = msg.sender
45    self.unlocked = False
46
47@external
48def received():
49    # 1. Conditions
50    assert not self.unlocked # Is the item already purchased and pending
51                             # confirmation from the buyer?
52    assert msg.sender == self.buyer
53    assert not self.ended
54
55    # 2. Effects
56    self.ended = True
57
58    # 3. Interaction
59    send(self.buyer, self.value) # Return the buyer's deposit (=value) to the buyer.
60    selfdestruct(self.seller) # Return the seller's deposit (=2*value) and the
61                              # purchase price (=value) to the seller.

这也是一个相当短的合约,但逻辑稍微复杂一些。让我们逐段分解这个合约。

16value: public(uint256) #Value of the item
17seller: public(address)
18buyer: public(address)
19unlocked: public(bool)

与其他合约一样,我们首先使用它们各自的数据类型声明我们的全局变量为公共变量。请记住,public 函数允许外部调用者读取变量,但不能写入

22@external
23@payable
24def __init__():
25    assert (msg.value % 2) == 0
26    self.value = msg.value / 2  # The seller initializes the contract by
27        # posting a safety deposit of 2*value of the item up for sale.
28    self.seller = msg.sender
29    self.unlocked = True

在构造函数上使用@payable 装饰器,合约创建者将被要求支付一项初始存款,该存款等于商品value 的两倍,以初始化合约,该存款将在稍后返回。这还需额外支付在区块链上部署合约所需的 gas 费用,该费用不会退还。我们assert 存款可以被 2 整除,以确保卖方存入了有效金额。构造函数将商品的值存储在合约变量 self.value 中,并将合约创建者保存到 self.seller 中。合约变量 self.unlocked 被初始化为 True

31@external
32def abort():
33    assert self.unlocked #Is the contract still refundable?
34    assert msg.sender == self.seller # Only the seller can refund
35        # his deposit before any buyer purchases the item.
36    selfdestruct(self.seller) # Refunds the seller and deletes the contract.

abort() 方法是只有卖方可以调用且合约仍然处于unlocked 状态下才能调用的一种方法,这意味着只有在买方进行购买之前才能调用该方法。正如我们在purchase() 方法中看到的那样,当买方调用purchase() 方法并将有效金额发送到合约时,合约将被锁定,卖方将不再能够调用abort()

当卖方调用abort() 并且assert 语句通过时,合约将调用selfdestruct() 函数,并将资金退还给卖方,随后销毁合约。

38@external
39@payable
40def purchase():
41    assert self.unlocked # Is the contract still open (is the item still up
42                         # for sale)?
43    assert msg.value == (2 * self.value) # Is the deposit the correct value?
44    self.buyer = msg.sender
45    self.unlocked = False

与构造函数类似,purchase() 方法也带有一个@payable 装饰器,这意味着它可以被调用并附带付款。为了让买方进行有效购买,我们必须首先assert 合约的unlocked 属性为True 并且发送的金额等于商品价值的两倍。然后,我们将买方设置为msg.sender 并锁定合约。此时,合约的余额等于商品价值的四倍,卖方必须将商品发送给买方。

47@external
48def received():
49    # 1. Conditions
50    assert not self.unlocked # Is the item already purchased and pending
51                             # confirmation from the buyer?
52    assert msg.sender == self.buyer
53    assert not self.ended
54
55    # 2. Effects
56    self.ended = True
57
58    # 3. Interaction
59    send(self.buyer, self.value) # Return the buyer's deposit (=value) to the buyer.
60    selfdestruct(self.seller) # Return the seller's deposit (=2*value) and the
61                              # purchase price (=value) to the seller.

最后,在买方收到商品后,买方可以通过调用received() 方法确认收货,以按照预期分配资金,即卖方收到合约余额的 3/4,买方收到 1/4。

通过调用received(),我们首先检查合约是否确实处于锁定状态,以确保买方先前已付款。我们还确保该方法只能由买方调用。如果这两个assert 语句通过,我们将退还买方的初始存款,并将剩余资金发送给卖方。合约最终被销毁,交易完成。

无论何时准备好,让我们继续下一个示例。

众筹

现在,让我们探索一个简单直观的众筹合约示例,其中潜在参与者可以为活动捐款。如果活动收到的总捐款达到或超过预定的筹款目标,则在活动截止日期结束时,资金将发送给受益人。如果总筹款未达到目标,则参与者将获得其各自捐款的退款。

 1# Setup private variables (only callable from within the contract)
 2
 3funders: HashMap[address, uint256]
 4beneficiary: address
 5deadline: public(uint256)
 6goal: public(uint256)
 7timelimit: public(uint256)
 8
 9# Setup global variables
10@external
11def __init__(_beneficiary: address, _goal: uint256, _timelimit: uint256):
12    self.beneficiary = _beneficiary
13    self.deadline = block.timestamp + _timelimit
14    self.timelimit = _timelimit
15    self.goal = _goal
16
17# Participate in this crowdfunding campaign
18@external
19@payable
20def participate():
21    assert block.timestamp < self.deadline, "deadline not met (yet)"
22
23    self.funders[msg.sender] += msg.value
24
25# Enough money was raised! Send funds to the beneficiary
26@external
27def finalize():
28    assert block.timestamp >= self.deadline, "deadline has passed"
29    assert self.balance >= self.goal, "the goal has not been reached"
30
31    selfdestruct(self.beneficiary)
32
33# Let participants withdraw their fund
34@external
35def refund():
36    assert block.timestamp >= self.deadline and self.balance < self.goal
37    assert self.funders[msg.sender] > 0
38
39    value: uint256 = self.funders[msg.sender]
40    self.funders[msg.sender] = 0
41
42    send(msg.sender, value)

在我们之前的示例中,大多数代码应该比较直观。让我们直接开始吧。

 3funders: HashMap[address, uint256]
 4beneficiary: address
 5deadline: public(uint256)
 6goal: public(uint256)
 7timelimit: public(uint256)
 8
 9# Setup global variables
10@external
11def __init__(_beneficiary: address, _goal: uint256, _timelimit: uint256):
12    self.beneficiary = _beneficiary
13    self.deadline = block.timestamp + _timelimit

与其他示例一样,我们首先初始化我们的变量,但这一次我们没有使用public 函数调用它们。以这种方式初始化的变量默认情况下是私有的。

注意

public() 函数的存在不同,没有等效的private() 函数。如果变量在没有public() 函数的情况下初始化,则它们默认情况下为私有的。

变量funders 被初始化为一个映射,其中键是地址,值为一个数字,表示每个参与者的捐款。beneficiary 将是众筹期结束后资金的最终接收者,由deadlinetimelimit 变量确定。变量goal 是所有参与者的目标总捐款。

 9# Setup global variables
10@external
11def __init__(_beneficiary: address, _goal: uint256, _timelimit: uint256):
12    self.beneficiary = _beneficiary
13    self.deadline = block.timestamp + _timelimit
14    self.timelimit = _timelimit
15    self.goal = _goal

我们的构造函数接受 3 个参数:受益人的地址、以 wei 值表示的目标以及众筹开始到结束的时间差。我们将参数初始化为与它们对应的名称相同的合约变量。此外,self.deadline 被初始化为为众筹期设定一个明确的结束时间。

现在让我们看看如何参与众筹。

17# Participate in this crowdfunding campaign
18@external
19@payable
20def participate():
21    assert block.timestamp < self.deadline, "deadline not met (yet)"
22
23    self.funders[msg.sender] += msg.value

我们再次在方法上看到@payable 装饰器,它允许用户在调用方法时发送一些以太币。在本例中,participate() 方法使用msg.sender 访问发送者的地址,使用msg.value 访问发送的相应金额。这些信息被存储到一个结构体中,然后使用self.nextFunderIndex 作为键保存到funders 映射中。随着更多参与者被添加到映射中,self.nextFunderIndex 将适当递增,以便正确索引每个参与者。

25# Enough money was raised! Send funds to the beneficiary
26@external
27def finalize():
28    assert block.timestamp >= self.deadline, "deadline has passed"
29    assert self.balance >= self.goal, "the goal has not been reached"
30
31    selfdestruct(self.beneficiary)

finalize() 方法用于完成众筹流程。但是,要完成众筹,该方法首先会检查众筹期是否已结束,并且余额是否已达到/超过了设定的目标。如果这两个条件都通过,则合约将调用selfdestruct() 函数,并将收集到的资金发送给受益人。

注意

请注意,我们可以通过调用self.balance 来访问发送到合约的总金额,这是一个我们从未明确设置的变量。类似于msgblockself.balance 是所有 Vyper 合约中都可以使用的内置变量。

如果一切顺利,我们可以完成活动,但是如果众筹活动不成功会发生什么?我们需要一种方法来退还所有参与者的资金。

33# Let participants withdraw their fund
34@external
35def refund():
36    assert block.timestamp >= self.deadline and self.balance < self.goal
37    assert self.funders[msg.sender] > 0
38
39    value: uint256 = self.funders[msg.sender]
40    self.funders[msg.sender] = 0
41
42    send(msg.sender, value)

refund() 方法中,我们首先使用assert 语句检查众筹期是否确实已结束,并且收集到的总余额是否小于goal。如果这两个条件都通过,我们将允许用户使用提款模式取回他们的资金。

投票

在此合约中,我们将实现一个系统,供参与者对提案列表进行投票。合约主席可以赋予每个参与者投票权,每个参与者可以选择投票,或将其投票权委托给其他投票者。最后,在调用winningProposals() 方法时,将确定获胜提案,该方法将遍历所有提案,并返回获得票数最多的提案。

  1# Voting with delegation.
  2
  3# Information about voters
  4struct Voter:
  5    # weight is accumulated by delegation
  6    weight: int128
  7    # if true, that person already voted (which includes voting by delegating)
  8    voted: bool
  9    # person delegated to
 10    delegate: address
 11    # index of the voted proposal, which is not meaningful unless `voted` is True.
 12    vote: int128
 13
 14# Users can create proposals
 15struct Proposal:
 16    # short name (up to 32 bytes)
 17    name: bytes32
 18    # number of accumulated votes
 19    voteCount: int128
 20
 21voters: public(HashMap[address, Voter])
 22proposals: public(HashMap[int128, Proposal])
 23voterCount: public(int128)
 24chairperson: public(address)
 25int128Proposals: public(int128)
 26
 27
 28@view
 29@internal
 30def _delegated(addr: address) -> bool:
 31    return self.voters[addr].delegate != empty(address)
 32
 33
 34@view
 35@external
 36def delegated(addr: address) -> bool:
 37    return self._delegated(addr)
 38
 39
 40@view
 41@internal
 42def _directlyVoted(addr: address) -> bool:
 43    return self.voters[addr].voted and (self.voters[addr].delegate == empty(address))
 44
 45
 46@view
 47@external
 48def directlyVoted(addr: address) -> bool:
 49    return self._directlyVoted(addr)
 50
 51
 52# Setup global variables
 53@external
 54def __init__(_proposalNames: bytes32[2]):
 55    self.chairperson = msg.sender
 56    self.voterCount = 0
 57    for i in range(2):
 58        self.proposals[i] = Proposal({
 59            name: _proposalNames[i],
 60            voteCount: 0
 61        })
 62        self.int128Proposals += 1
 63
 64# Give a `voter` the right to vote on this ballot.
 65# This may only be called by the `chairperson`.
 66@external
 67def giveRightToVote(voter: address):
 68    # Throws if the sender is not the chairperson.
 69    assert msg.sender == self.chairperson
 70    # Throws if the voter has already voted.
 71    assert not self.voters[voter].voted
 72    # Throws if the voter's voting weight isn't 0.
 73    assert self.voters[voter].weight == 0
 74    self.voters[voter].weight = 1
 75    self.voterCount += 1
 76
 77# Used by `delegate` below, callable externally via `forwardWeight`
 78@internal
 79def _forwardWeight(delegate_with_weight_to_forward: address):
 80    assert self._delegated(delegate_with_weight_to_forward)
 81    # Throw if there is nothing to do:
 82    assert self.voters[delegate_with_weight_to_forward].weight > 0
 83
 84    target: address = self.voters[delegate_with_weight_to_forward].delegate
 85    for i in range(4):
 86        if self._delegated(target):
 87            target = self.voters[target].delegate
 88            # The following effectively detects cycles of length <= 5,
 89            # in which the delegation is given back to the delegator.
 90            # This could be done for any int128ber of loops,
 91            # or even infinitely with a while loop.
 92            # However, cycles aren't actually problematic for correctness;
 93            # they just result in spoiled votes.
 94            # So, in the production version, this should instead be
 95            # the responsibility of the contract's client, and this
 96            # check should be removed.
 97            assert target != delegate_with_weight_to_forward
 98        else:
 99            # Weight will be moved to someone who directly voted or
100            # hasn't voted.
101            break
102
103    weight_to_forward: int128 = self.voters[delegate_with_weight_to_forward].weight
104    self.voters[delegate_with_weight_to_forward].weight = 0
105    self.voters[target].weight += weight_to_forward
106
107    if self._directlyVoted(target):
108        self.proposals[self.voters[target].vote].voteCount += weight_to_forward
109        self.voters[target].weight = 0
110
111    # To reiterate: if target is also a delegate, this function will need
112    # to be called again, similarly to as above.
113
114# Public function to call _forwardWeight
115@external
116def forwardWeight(delegate_with_weight_to_forward: address):
117    self._forwardWeight(delegate_with_weight_to_forward)
118
119# Delegate your vote to the voter `to`.
120@external
121def delegate(to: address):
122    # Throws if the sender has already voted
123    assert not self.voters[msg.sender].voted
124    # Throws if the sender tries to delegate their vote to themselves or to
125    # the default address value of 0x0000000000000000000000000000000000000000
126    # (the latter might not be problematic, but I don't want to think about it).
127    assert to != msg.sender
128    assert to != empty(address)
129
130    self.voters[msg.sender].voted = True
131    self.voters[msg.sender].delegate = to
132
133    # This call will throw if and only if this delegation would cause a loop
134        # of length <= 5 that ends up delegating back to the delegator.
135    self._forwardWeight(msg.sender)
136
137# Give your vote (including votes delegated to you)
138# to proposal `proposals[proposal].name`.
139@external
140def vote(proposal: int128):
141    # can't vote twice
142    assert not self.voters[msg.sender].voted
143    # can only vote on legitimate proposals
144    assert proposal < self.int128Proposals
145
146    self.voters[msg.sender].vote = proposal
147    self.voters[msg.sender].voted = True
148
149    # transfer msg.sender's weight to proposal
150    self.proposals[proposal].voteCount += self.voters[msg.sender].weight
151    self.voters[msg.sender].weight = 0
152
153# Computes the winning proposal taking all
154# previous votes into account.
155@view
156@internal
157def _winningProposal() -> int128:
158    winning_vote_count: int128 = 0
159    winning_proposal: int128 = 0
160    for i in range(2):
161        if self.proposals[i].voteCount > winning_vote_count:
162            winning_vote_count = self.proposals[i].voteCount
163            winning_proposal = i
164    return winning_proposal
165
166@view
167@external
168def winningProposal() -> int128:
169    return self._winningProposal()
170
171
172# Calls winningProposal() function to get the index
173# of the winner contained in the proposals array and then
174# returns the name of the winner
175@view
176@external
177def winnerName() -> bytes32:
178    return self.proposals[self._winningProposal()].name

正如我们所看到的,这是我们将逐节分析的长度适中的合约。让我们开始吧!

 3# Information about voters
 4struct Voter:
 5    # weight is accumulated by delegation
 6    weight: int128
 7    # if true, that person already voted (which includes voting by delegating)
 8    voted: bool
 9    # person delegated to
10    delegate: address
11    # index of the voted proposal, which is not meaningful unless `voted` is True.
12    vote: int128
13
14# Users can create proposals
15struct Proposal:
16    # short name (up to 32 bytes)
17    name: bytes32
18    # number of accumulated votes
19    voteCount: int128
20
21voters: public(HashMap[address, Voter])
22proposals: public(HashMap[int128, Proposal])
23voterCount: public(int128)
24chairperson: public(address)
25int128Proposals: public(int128)

变量voters 被初始化为一个映射,其中键是投票者的公钥地址,值为一个结构体,描述了投票者的属性:weightvoteddelegatevote,以及它们各自的数据类型。

同样,变量proposals 被初始化为一个public 映射,键的数据类型为int128,结构体用于表示每个提案,具有namevote_count 属性。与我们的最后一个示例一样,我们可以通过使用数字作为键访问映射中的任何值,就像在数组中使用索引一样。

然后,voterCountchairperson 被初始化为public,具有它们各自的数据类型。

让我们继续讨论构造函数。

53@external
54def __init__(_proposalNames: bytes32[2]):
55    self.chairperson = msg.sender
56    self.voterCount = 0
57    for i in range(2):
58        self.proposals[i] = Proposal({
59            name: _proposalNames[i],
60            voteCount: 0
61        })
62        self.int128Proposals += 1

在构造函数中,我们对合约进行了硬编码,以接受一个包含两个提案名称的数组参数,类型为bytes32,用于合约初始化。因为在初始化时,__init__() 方法由合约创建者调用,所以我们可以使用msg.sender 访问合约创建者的地址,并将其存储在合约变量self.chairperson 中。我们还将合约变量self.voter_count 初始化为零,以最初表示允许的投票数。这个值将在方法giveRightToVote() 中赋予每个参与者投票权时递增,我们将在接下来探讨该方法。我们循环遍历来自参数的两个提案,并将它们插入到proposals 映射中,使用它们在原始数组中的索引作为键。

现在初始设置已完成,让我们来看看功能。

66@external
67def giveRightToVote(voter: address):
68    # Throws if the sender is not the chairperson.
69    assert msg.sender == self.chairperson
70    # Throws if the voter has already voted.
71    assert not self.voters[voter].voted
72    # Throws if the voter's voting weight isn't 0.
73    assert self.voters[voter].weight == 0
74    self.voters[voter].weight = 1
75    self.voterCount += 1

注意

在本合约中,我们使用了一种模式,其中 @external 函数从以下划线开头的相同名称的 @internal 函数返回数据。这是因为 Vyper 不允许在同一个合约中调用外部函数之间的函数。内部函数处理逻辑并允许内部访问,而外部函数充当获取器以允许外部查看。

我们需要一种方法来控制谁有投票的能力。方法 giveRightToVote() 是一种只有主席可以调用的方法,通过获取投票者地址并通过递增投票者的 weight 属性来授予其投票权。我们使用 assert 依次检查 3 个条件。 assert not 函数将检查虚假布尔值——在这种情况下,我们想知道投票者是否还没有投票。为了表示投票权,我们将他们的 weight 设置为 1,并且我们将通过递增 voterCount 来跟踪投票者的总数。

120@external
121def delegate(to: address):
122    # Throws if the sender has already voted
123    assert not self.voters[msg.sender].voted
124    # Throws if the sender tries to delegate their vote to themselves or to
125    # the default address value of 0x0000000000000000000000000000000000000000
126    # (the latter might not be problematic, but I don't want to think about it).
127    assert to != msg.sender
128    assert to != empty(address)
129
130    self.voters[msg.sender].voted = True
131    self.voters[msg.sender].delegate = to
132
133    # This call will throw if and only if this delegation would cause a loop
134        # of length <= 5 that ends up delegating back to the delegator.
135    self._forwardWeight(msg.sender)

delegate 方法中,首先,我们检查 msg.sender 是否已经投票,其次,目标委托人和 msg.sender 是否相同。投票者不应该能够将投票委托给自己。然后,我们循环遍历所有投票者,以确定被委托的人是否将他们的投票进一步委托给了其他人,以便遵循委托链。如果他们委托了他们的投票,我们就会将 msg.sender 标记为已投票。如果委托人已经投票,我们会直接递增提议的 voterCount,或者如果委托人还没有投票,我们会增加委托人的投票 weight

139@external
140def vote(proposal: int128):
141    # can't vote twice
142    assert not self.voters[msg.sender].voted
143    # can only vote on legitimate proposals
144    assert proposal < self.int128Proposals
145
146    self.voters[msg.sender].vote = proposal
147    self.voters[msg.sender].voted = True
148
149    # transfer msg.sender's weight to proposal
150    self.proposals[proposal].voteCount += self.voters[msg.sender].weight
151    self.voters[msg.sender].weight = 0

现在,让我们看看 vote() 方法中的逻辑,这出奇地简单。该方法接受 proposals 映射中提议的键作为参数,检查方法调用者是否已经投票,将投票者的 vote 属性设置为提议键,并将提议的 voteCount 递增投票者的 weight

在所有基本功能都完成后,剩下的就是简单地返回获胜的提议。为此,我们有两种方法: winningProposal(),它返回提议的键,以及 winnerName(),它返回提议的名称。注意这两个方法上的 @view 装饰器。我们这样做是因为这两个方法只读取区块链状态,不修改它。请记住,读取区块链状态是免费的;修改状态需要 gas。通过使用 @view 装饰器,我们让 EVM 知道这是一个只读函数,并且我们可以通过节省 gas 费用来获益。

153# Computes the winning proposal taking all
154# previous votes into account.
155@view
156@internal
157def _winningProposal() -> int128:
158    winning_vote_count: int128 = 0
159    winning_proposal: int128 = 0
160    for i in range(2):
161        if self.proposals[i].voteCount > winning_vote_count:
162            winning_vote_count = self.proposals[i].voteCount
163            winning_proposal = i
164    return winning_proposal
165
166@view
167@external
168def winningProposal() -> int128:
169    return self._winningProposal()
170

_winningProposal() 方法返回 proposals 映射中提议的键。我们将分别使用变量 winningVoteCountwinningProposal 来跟踪最多票数和获胜提议,方法是循环遍历所有提议。

winningProposal() 是一个允许访问 _winningProposal() 的外部函数。

175@view
176@external
177def winnerName() -> bytes32:
178    return self.proposals[self._winningProposal()].name

最后, winnerName() 方法通过使用 winningProposal() 方法的返回结果作为键值来访问 proposals 映射,从而返回提议的名称。

这就是投票合约。目前,需要执行许多交易才能将投票权分配给所有参与者。作为一项练习,我们可以尝试优化它吗?

现在我们已经熟悉了基本合约。让我们提高难度。

公司股票

这个合约比我们之前遇到的合约要更全面。在这个例子中,我们将查看一个全面的合约,它管理一家公司所有股票的持有情况。该合约允许个人购买、出售和转让公司股票,并允许公司以以太坊支付给个人。在合约初始化时,公司首先持有公司所有股票,但可以出售所有股票。

让我们开始吧。

  1# Financial events the contract logs
  2
  3event Transfer:
  4    sender: indexed(address)
  5    receiver: indexed(address)
  6    value: uint256
  7
  8event Buy:
  9    buyer: indexed(address)
 10    buy_order: uint256
 11
 12event Sell:
 13    seller: indexed(address)
 14    sell_order: uint256
 15
 16event Pay:
 17    vendor: indexed(address)
 18    amount: uint256
 19
 20
 21# Initiate the variables for the company and it's own shares.
 22company: public(address)
 23totalShares: public(uint256)
 24price: public(uint256)
 25
 26# Store a ledger of stockholder holdings.
 27holdings: HashMap[address, uint256]
 28
 29# Set up the company.
 30@external
 31def __init__(_company: address, _total_shares: uint256, initial_price: uint256):
 32    assert _total_shares > 0
 33    assert initial_price > 0
 34
 35    self.company = _company
 36    self.totalShares = _total_shares
 37    self.price = initial_price
 38
 39    # The company holds all the shares at first, but can sell them all.
 40    self.holdings[self.company] = _total_shares
 41
 42# Public function to allow external access to _stockAvailable
 43@view
 44@external
 45def stockAvailable() -> uint256:
 46    return self._stockAvailable()
 47
 48# Give some value to the company and get stock in return.
 49@external
 50@payable
 51def buyStock():
 52    # Note: full amount is given to company (no fractional shares),
 53    #       so be sure to send exact amount to buy shares
 54    buy_order: uint256 = msg.value / self.price # rounds down
 55
 56    # Check that there are enough shares to buy.
 57    assert self._stockAvailable() >= buy_order
 58
 59    # Take the shares off the market and give them to the stockholder.
 60    self.holdings[self.company] -= buy_order
 61    self.holdings[msg.sender] += buy_order
 62
 63    # Log the buy event.
 64    log Buy(msg.sender, buy_order)
 65
 66# Public function to allow external access to _getHolding
 67@view
 68@external
 69def getHolding(_stockholder: address) -> uint256:
 70    return self._getHolding(_stockholder)
 71
 72# Return the amount the company has on hand in cash.
 73@view
 74@external
 75def cash() -> uint256:
 76    return self.balance
 77
 78# Give stock back to the company and get money back as ETH.
 79@external
 80def sellStock(sell_order: uint256):
 81    assert sell_order > 0 # Otherwise, this would fail at send() below,
 82        # due to an OOG error (there would be zero value available for gas).
 83    # You can only sell as much stock as you own.
 84    assert self._getHolding(msg.sender) >= sell_order
 85    # Check that the company can pay you.
 86    assert self.balance >= (sell_order * self.price)
 87
 88    # Sell the stock, send the proceeds to the user
 89    # and put the stock back on the market.
 90    self.holdings[msg.sender] -= sell_order
 91    self.holdings[self.company] += sell_order
 92    send(msg.sender, sell_order * self.price)
 93
 94    # Log the sell event.
 95    log Sell(msg.sender, sell_order)
 96
 97# Transfer stock from one stockholder to another. (Assume that the
 98# receiver is given some compensation, but this is not enforced.)
 99@external
100def transferStock(receiver: address, transfer_order: uint256):
101    assert transfer_order > 0 # This is similar to sellStock above.
102    # Similarly, you can only trade as much stock as you own.
103    assert self._getHolding(msg.sender) >= transfer_order
104
105    # Debit the sender's stock and add to the receiver's address.
106    self.holdings[msg.sender] -= transfer_order
107    self.holdings[receiver] += transfer_order
108
109    # Log the transfer event.
110    log Transfer(msg.sender, receiver, transfer_order)
111
112# Allow the company to pay someone for services rendered.
113@external
114def payBill(vendor: address, amount: uint256):
115    # Only the company can pay people.
116    assert msg.sender == self.company
117    # Also, it can pay only if there's enough to pay them with.
118    assert self.balance >= amount
119
120    # Pay the bill!
121    send(vendor, amount)
122
123    # Log the payment event.
124    log Pay(vendor, amount)
125
126# Public function to allow external access to _debt
127@view
128@external
129def debt() -> uint256:
130    return self._debt()
131
132# Return the cash holdings minus the debt of the company.
133# The share debt or liability only is included here,
134# but of course all other liabilities can be included.
135@view
136@external
137def worth() -> uint256:
138    return self.balance - self._debt()
139
140# Return the amount in wei that a company has raised in stock offerings.
141@view
142@internal
143def _debt() -> uint256:
144    return (self.totalShares - self._stockAvailable()) * self.price
145
146# Find out how much stock the company holds
147@view
148@internal
149def _stockAvailable() -> uint256:
150    return self.holdings[self.company]
151
152# Find out how much stock any address (that's owned by someone) has.
153@view
154@internal
155def _getHolding(_stockholder: address) -> uint256:
156    return self.holdings[_stockholder]

注意

在本合约中,我们使用了一种模式,其中 @external 函数从以下划线开头的相同名称的 @internal 函数返回数据。这是因为 Vyper 不允许在同一个合约中调用外部函数之间的函数。内部函数处理逻辑,而外部函数充当获取器以允许查看。

该合约包含许多修改合约状态的方法以及一些“获取器”方法来读取它。我们首先声明合约记录的几个事件。然后我们声明全局变量,然后是函数定义。

 3event Transfer:
 4    sender: indexed(address)
 5    receiver: indexed(address)
 6    value: uint256
 7
 8event Buy:
 9    buyer: indexed(address)
10    buy_order: uint256
11
12event Sell:
13    seller: indexed(address)
14    sell_order: uint256
15
16event Pay:
17    vendor: indexed(address)
18    amount: uint256
19
20
21# Initiate the variables for the company and it's own shares.
22company: public(address)
23totalShares: public(uint256)
24price: public(uint256)
25
26# Store a ledger of stockholder holdings.
27holdings: HashMap[address, uint256]

我们将 company 变量初始化为类型 address,它是公开的。 totalShares 变量类型为 uint256,在这种情况下它表示公司可用的总股份。 price 变量表示每股的 wei 值, holdings 是一个映射,它将地址映射到该地址拥有的股份数量。

29# Set up the company.
30@external
31def __init__(_company: address, _total_shares: uint256, initial_price: uint256):
32    assert _total_shares > 0
33    assert initial_price > 0
34
35    self.company = _company
36    self.totalShares = _total_shares
37    self.price = initial_price
38
39    # The company holds all the shares at first, but can sell them all.
40    self.holdings[self.company] = _total_shares

在构造函数中,我们设置了合约,以便通过两个 assert 语句来检查合约初始化期间的有效输入。如果输入有效,则合约变量将相应设置,并且公司的地址被初始化为在 holdings 映射中持有公司所有股份。

42# Public function to allow external access to _stockAvailable
43@view
44@external
45def stockAvailable() -> uint256:
46    return self._stockAvailable()

我们将在本合约中看到几个 @view 装饰器,用于装饰只读取合约状态或返回合约状态的简单计算而不修改它的方法。请记住,读取区块链是免费的,写入区块链则不是。由于 Vyper 是一种静态类型语言,我们看到 _stockAvailable() 方法定义后的箭头,它只是表示函数预期返回的数据类型。在该方法中,我们只是使用公司的地址作为键值访问 self.holdings 并检查它的持有情况。因为 _stockAvailable() 是一个内部方法,我们还包含 stockAvailable() 方法以允许外部访问。

现在,让我们来看看一个允许个人从公司的持股中购买股票的方法。

51def buyStock():
52    # Note: full amount is given to company (no fractional shares),
53    #       so be sure to send exact amount to buy shares
54    buy_order: uint256 = msg.value / self.price # rounds down
55
56    # Check that there are enough shares to buy.
57    assert self._stockAvailable() >= buy_order
58
59    # Take the shares off the market and give them to the stockholder.
60    self.holdings[self.company] -= buy_order
61    self.holdings[msg.sender] += buy_order
62
63    # Log the buy event.
64    log Buy(msg.sender, buy_order)

buyStock() 方法是一个 @payable 方法,它接受发送的以太坊数量并计算 buyOrder(调用时的股票价值等价物)。股份数量从公司的持股中扣除并转让到发送者的 holdings 映射中。

现在人们可以购买股票了,我们如何检查某人的持股情况呢?

66# Public function to allow external access to _getHolding
67@view
68@external
69def getHolding(_stockholder: address) -> uint256:
70    return self._getHolding(_stockholder)
71

_getHolding() 是另一个 @view 方法,它接受一个 address 并通过访问 self.holdings 返回其对应的股票持股情况。同样,一个外部函数 getHolding() 被包含进来以允许访问。

72# Return the amount the company has on hand in cash.
73@view
74@external
75def cash() -> uint256:
76    return self.balance

要检查公司的以太坊余额,我们可以简单地调用获取器方法 cash()

78# Give stock back to the company and get money back as ETH.
79@external
80def sellStock(sell_order: uint256):
81    assert sell_order > 0 # Otherwise, this would fail at send() below,
82        # due to an OOG error (there would be zero value available for gas).
83    # You can only sell as much stock as you own.
84    assert self._getHolding(msg.sender) >= sell_order
85    # Check that the company can pay you.
86    assert self.balance >= (sell_order * self.price)
87
88    # Sell the stock, send the proceeds to the user
89    # and put the stock back on the market.
90    self.holdings[msg.sender] -= sell_order
91    self.holdings[self.company] += sell_order
92    send(msg.sender, sell_order * self.price)
93
94    # Log the sell event.
95    log Sell(msg.sender, sell_order)

要出售股票,我们有 sellStock() 方法,它接受一个人希望出售的股票数量,并将等值的以太坊发送到卖方的地址。我们首先 assert 一个人希望出售的股票数量大于 0。我们还 assert 检查用户是否只能出售他们拥有的数量以及公司是否拥有足够的以太坊来完成出售。如果所有条件都满足,则持股将从卖方扣除并给公司。然后以太坊将发送给卖方。

 97# Transfer stock from one stockholder to another. (Assume that the
 98# receiver is given some compensation, but this is not enforced.)
 99@external
100def transferStock(receiver: address, transfer_order: uint256):
101    assert transfer_order > 0 # This is similar to sellStock above.
102    # Similarly, you can only trade as much stock as you own.
103    assert self._getHolding(msg.sender) >= transfer_order
104
105    # Debit the sender's stock and add to the receiver's address.
106    self.holdings[msg.sender] -= transfer_order
107    self.holdings[receiver] += transfer_order
108
109    # Log the transfer event.
110    log Transfer(msg.sender, receiver, transfer_order)

股东也可以使用 transferStock() 方法将他们的股票转让给另一个股东。该方法接受一个接收者地址和要发送的股份数量。它首先 asserts 要发送的金额大于 0,并且 asserts 发送者是否有足够的股票发送。如果两个条件都满足,则进行转让。

112# Allow the company to pay someone for services rendered.
113@external
114def payBill(vendor: address, amount: uint256):
115    # Only the company can pay people.
116    assert msg.sender == self.company
117    # Also, it can pay only if there's enough to pay them with.
118    assert self.balance >= amount
119
120    # Pay the bill!
121    send(vendor, amount)
122
123    # Log the payment event.
124    log Pay(vendor, amount)

公司还被允许通过调用 payBill() 方法将一定数量的以太坊支付给地址。该方法只能由公司调用,因此首先检查方法调用者的地址是否与公司的地址匹配。另一个要检查的重要条件是公司是否有足够的资金来支付该金额。如果两个条件都满足,则合约将以太坊发送到地址。

126# Public function to allow external access to _debt
127@view
128@external
129def debt() -> uint256:
130    return self._debt()

我们还可以通过将公司已售股份数量乘以每股价格来检查公司筹集了多少资金。在内部,我们通过调用 _debt() 方法获得此值。在外部,可以通过 debt() 方法访问它。

132# Return the cash holdings minus the debt of the company.
133# The share debt or liability only is included here,
134# but of course all other liabilities can be included.
135@view
136@external
137def worth() -> uint256:
138    return self.balance - self._debt()

最后,在这个 worth() 方法中,我们可以通过从以太坊余额中减去公司债务来检查公司的价值。

这个合约是到目前为止功能和特性最全面的例子。然而,尽管这样一个合约的全面性,但逻辑仍然很简单。希望到目前为止,Vyper 语言已经让你相信它在编写智能合约方面的功能和可读性。