Lingkun's Small House

Youth is not a time of life, it is a state of mind.


  • Home

  • Archives

Start with Solidity

Posted on 2018-01-04 | In Learn Solidity

Ethereum and Solidity环境配置

作者:孔令坤,转载请注明出处

由于Remix版本的更新,Metamask的更新已经Ethereum rinkeby network对Github账号申请eth的限制,我发现原来的配置教程其实并不太适用于当前的版本了,所以在这里我简单的介绍一下开发的一些基本配置。在接下来的配置内容中,我借鉴了许多来自IC3 Ethereum/Cornell Bootcamp的内容,主要对其中Github部分的内容做了更新。

Requirements:

  • Google Chrome
  • Twitter Account
  • Metamask

Introduction of Things:

Ethereum

Ethereum is an open-source, public, blockchain-based distributed computing platform featuring smart contract (scripting) functionality.

Metamask

MetaMask is a bridge between Google Chrome and the Ethereum blockchain. It allows you to run Ethereum Dapps in your browser without running a full Ethereum node. MetaMask includes a secure identity vault, providing a user interface to manage your identities on different sites and sign blockchain transactions.

Solidity

Solidity is a programming language used to code smart contracts that are compatible with Ethereum. The documentation for Solidity is located here. We will be using Remix when developing our smart contracts. Remix is an online development environment that allows you to code, deploy, and test smart contracts on the public Ethereum networks and test Ethereum networks.

Rinkeby

Rinkeby is a public Ethereum test network that is used to deploy and test smart contracts for free. In order to receive Rinkeby ether coins, you will need to visit https://www.rinkeby.io/ once you have your Metamask client installed.

Start with Ethereum and Solidity:

a. Setting up Metamask

  1. Install the Metamask Chrome plugin.
  2. Open Metamask (there should be an orange fox icon in your browser toolbar).
  3. Click through the license agreements.
  4. Create a new “den” which will house your Ethereum account.
  5. COPY THE 12 WORDS SOMEWHERE SAFE.
  6. Click the network selection button on the top left part of the Metmask app. It should say “Ethereum Mainnet” or “Ropsten”. Change that to Rinkeby.
  7. Copy your Ethereum account address by clicking the “Copy” icon above the “Buy” and “Send” buttons. Mouse over the icons to reveal their label.

b. Get Rinkeby Testnet Ether

  1. Login into your Twitter account.
  2. Paste your Ethereum address you copied in step a.7 into the “What’s happening?” content boxs to make a new tweet. The Ethereum address should be a jumble of letters and numbers like this: 0x9bcd107A7De7C3cee1be15dFa06B5586672912e9
  3. Click “Tweet” to gennerate a new Tweet.
  4. Copy the URL of the Tweet. Click the button in the upper right of your tweet and select the option of “Copy link to Tweet”. The link should be similar to this:https://twitter.com/316980786Kong/status/946989538890588161
  5. Visit the Rinkeby website and click “Crypto Faucet”.
  6. Paste your Tweet URL in the box provided and click “Give me Ether - 3 ethers/8 hours”.
  7. A box should pop up with with your Twitter icon indicating that your request for testnet ether is being processed.
  8. Wait 10 seconds.
  9. Look at your Metamask client and verify that you received 3 Rinkeby testnet ether.

c. Writing your first smart contract.

  1. Go to the Learning Solidity Part 2: Commit-Reveal Voting.
  2. Walk through the blog and write your first smart contract.

Learn Solidity (3) — Safe Remote Purchase

Posted on 2018-01-03 | In Learn Solidity

Solidity学习笔记(3) — Safe Remote Purchase

作者:孔令坤,转载请注明出处

今天用拍卖合约(Safe Remote Purchase)进行Smart Contract编程的学习。

今天的这一段代码,有一些比较tricky的地方,所以我的笔记也会比较详细。

附上官方的学习代码链接:https://solidity.readthedocs.io/en/latest/solidity-by-example.html#safe-remote-purchase

另外发现了一个不错的大纲型文档:https://medium.com/@mattcondon/getting-up-to-speed-on-ethereum-63ed28821bbe

1.功能简介

这个合约作为一个第三方的平台,用于远程购买一个商品时暂存买方付的钱,然后在买方确认交易后将钱付给商家。整体而言这个合约相当于我们用淘宝网购时的淘宝平台,用户购买时将钱先付给淘宝平台,然后商家发货。用户在收到商品后在淘宝上确认付款,这时淘宝会把钱打给商户。

在使用这个合约时,卖方需要先自己创建(create)合约。整个流程与下图类似:

2.代码简介

初始函数:首先,我们看建立合约的初始函数,根据函数中的payable,说明这里发起协议的seller需要在transaction里面放价格为商品两倍的保证金。这里是整个合约最tricky的地方,即seller放了商品价格两倍的保证金。这样有两个目的,其一可以防止黑心商家,发起协议后不给用户发货,用户在确认后白白损失金额。其二可以让买方积极的去确认收到商品了,因为买方也要交商品价格两倍的保证金,如果不确认收到商品,他们将白白损失钱财。

1
2
3
4
5
6
7
8
enum State { Created, Locked, Inactive }
State public state;
// 这一段用require代码确认seller的value是偶数
function Purchase() public payable {
seller = msg.sender;
value = msg.value / 2;
require((2 * value) == msg.value);
}

其实这么写有浪费gas的问题。因为如果不是偶数,之前的语句已经执行过了,require导致函数回退时将会消耗更多的gas。所以我觉得这一段可以改成:

1
2
3
4
5
function Purchase() public payable {
seller = msg.sender;
require((msg.value % 2) == 0);
value = msg.value / 2;
}

另外神奇的是,在创建合约的函数里面,合约并没有写state = State.Created;然而在下面的abort()函数中,inState(State.Created)的判定竟然通过了。经过代码调试,我发现在State public state;这句话的时候,虽然没有直接赋值,但是系统默认state的值为空,在做==判定的时候,默认state = 0了。总之之后编程需要多多小心。PS. 我这里在调试的时候,加了一个enum State { Created, Locked, Inactive }然后创建了一个2eth的合约。于是我永久的丢掉了2eth。

终止合约:在终止函数里,我想提一下transfer和send函数的区别。两者都是像地址传递钱,区别在于如果transfer函数运行失败,系统会throw一个错误,而send函数则会返回一个false的布尔值。

1
2
3
4
5
6
7
8
9
10
function abort()
public
onlySeller
inState(State.Created)
{
Aborted();
state = State.Inactive;
// 这里把seller的保证金还给seller,里面应该是2V - gas fee
seller.transfer(this.balance);
}

确认支付: 这个函数由buyer在seller发起的协议基础上访问,会在协议里存两倍商品价格的钱,存完后会将Contract的状态标记为锁定, i,e., Locked。至于为何要付两倍的钱,我认为可能是因为这个协议担心用户会懒得确认到货,所以这么做来使得用户自己能够被

1
2
3
4
5
6
7
8
9
10
function confirmPurchase()
public
inState(State.Created)
condition(msg.value == (2 * value))
payable
{
PurchaseConfirmed();
buyer = msg.sender;
state = State.Locked;
}

确认收货: 这个函数由buyer在seller发起的协议基础上访问,buyer确认收到商品,这样之后协议会把钱退回给buyer和seller。需要注意的是,这里官方代码认为所有的交易费用应该由seller承担。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function confirmReceived()
public
onlyBuyer
inState(State.Locked)
{
ItemReceived();
// It is important to change the state first because
// otherwise, the contracts called using `send` below
// can call in again here.
state = State.Inactive;
// 返回用户多余的押金,一倍商品价格
buyer.transfer(value);
// 把剩下的 3V - gas fee的钱给商家。
seller.transfer(this.balance);
}

3.一些思考

在看代码的过程中,我发现这个合约有一个无法解决的问题。

假如有一些恶意的用户想搞垮商家,于是买了商品后就是不确认,这样用户通过损失等价于一个商品价格的费用(因为用户其实收到了商品),就可以坑掉商家三倍商品价格的钱(商家发出了商品)。

其实这个问题的本质源于smart contract无法监管商品的运转,即它无法发现假如钱被锁死在了合约里面,是谁干了坏事。所以我觉得就remote purchace这个问题而言,比较理想的状况还是得有一个买卖双方都信的过的第三方机构介入其中。buyer先把钱寄给这个第三方机构,然后由第三方机构作为仲裁方,决定把钱最终交给buyer还是seller。

Learn Solidity (2) — Auction

Posted on 2018-01-02 | In Learn Solidity

Solidity学习笔记(2) — Auction

作者:孔令坤,转载请注明出处

今天用拍卖合约(Auction Contract)进行Smart Contract编程的学习。

环境的安装和使用方法这里不做过多的赘述,附上两个链接,大家可以用来参考:

MetaMask配置:https://karl.tech/learning-solidity-part-1-deploy-a-contract/

Solidity配置与运行案例:https://karl.tech/learning-solidity-part-2-voting/

另外附上官方的学习代码链接:https://solidity.readthedocs.io/en/latest/solidity-by-example.html#blind-auction

另外发现了一个不错的大纲型文档:https://medium.com/@mattcondon/getting-up-to-speed-on-ethereum-63ed28821bbe

1.SimpleAuction

通过智能合约,实现了拍卖中用户们的匿名化。具体而言,在拍卖中,用户们通过调用智能合约进行出价,由智能合约后台经过处理后选出出价最高的用户进行最后交易,并退回其它用户的钱。

首先,我们来看官方给出第一个例子,contract SimpleAuction{}。在这个例子中,在拍卖结束后,这个合约需要被人为地操作auctionEnd()函数,这样收益人beneficiary才能获得来自出价最高的用户的钱。另外,这个合约中,如果一个用户的报价被接受了,并不意味着这个用户已经可以拍得这个商品,因为有可能只是他出的前是目前而言最高的。如果之后有更高的出价,他需要操作withdraw()函数才能退回自己的金额。

代码分析:

payable函数几乎是所有smart contract涉及到金额流动都会有的一个函数,因为涉及交易金额的信息都已经在以太坊的transaction中了,所以payable函数并不需要参数输入。在这个函数中,合约先判断是否还在拍卖截止前,然后看用户提供的拍卖金额是不是当前最高的,如果不是直接退回,如果是的话则存储到pendingReturns中,等待最后拍卖截止后再看是不是最高的价格。另外值得注意的是,由于require(msg.value > highestBid);所以在用户价格相同的时候,会取先出价的用户。

另外在这段代码中,出现了对event的调用(HighestBidIncreased()),通俗的来说event, i.e., 事件是以太坊EVM提供的一种日志基础设施。事件可以用来做操作记录,存储为日志。也可以用来实现一些交互功能,比如通知UI,返回函数调用结果等。(http://me.tryblockchain.org/blockchain-solidity-event.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
event HighestBidIncreased(address bidder, uint amount);
function bid() public payable {
require(now <= auctionEnd);
require(msg.value > highestBid);

if (highestBidder != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their money themselves.
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
HighestBidIncreased(msg.sender, msg.value);
}

withdraw()函数可以说是simple auction中最笨的地方了,这里由于合约无法自动退回一些没有成功达成交易的用户的拍卖投标,用户需要在拍卖结束后自己去调用withdraw()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function withdraw() public returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `send` returns.
pendingReturns[msg.sender] = 0;

if (!msg.sender.send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}

在最后的auctionEnd()函数中,合约将最高的金额转移给受益人。

1
2
3
4
5
6
7
8
9
10
11
12
13
bool ended;
function auctionEnd() public {
// 1. Conditions
require(now >= auctionEnd); // auction did not yet end
require(!ended); // this function has already been called

// 2. Effects
ended = true;
AuctionEnded(highestBidder, highestBid);

// 3. Interaction
beneficiary.transfer(highestBid);
}

2.Blind Auction

在官方提供的blind auction智能合约中,通过哈希函数的一系列特性,用户可以不直接把自己的拍卖投标放到合约中,而是只用提供自己的投标的哈希值。在拍卖结束后,用户通过提供未被哈希过的值作为依据,交给合约验证,从而判断之前提供的哈希值是有效的 — 确实由本人提交并且金额无误。

代码分析:

由于代码较长,这里我直接通过注释的方式对代码进行说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint deposit;
}
address public beneficiary;
uint public biddingEnd; // 投标的截止时间
uint public revealEnd; // 确认投标的截止时间
bool public ended; // 拍卖截止时间

mapping(address => Bid[]) public bids;

address public highestBidder;
uint public highestBid;

mapping(address => uint) pendingReturns;

event AuctionEnded(address winner, uint highestBid);

// Modifiers 是一种简易的检验输入的方法,_代替旧的函数体
modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { require(now > _time); _; }

function BlindAuction(
uint _biddingTime,
uint _revealTime,
address _beneficiary
) public {
beneficiary = _beneficiary;
biddingEnd = now + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}

// 盲标 = keccak256(value, fake, secret).
// https://emn178.github.io/online-tools/keccak_256.html
// 一个地址可以下投多个盲标
function bid(bytes32 _blindedBid)
public
payable
onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({
blindedBid: _blindedBid,
deposit: msg.value
}));
}

// 揭示盲标是否合法
function reveal(
uint[] _values,
bool[] _fake,
bytes32[] _secret
)
public
onlyAfter(biddingEnd)
onlyBefore(revealEnd)
{ // 这里由于一个地址可以下多个盲标,这里判断是否收到的输入是正确的
uint length = bids[msg.sender].length;
require(_values.length == length);
require(_fake.length == length);
require(_secret.length == length);

uint refund;
for (uint i = 0; i < length; i++) {
var bid = bids[msg.sender][i];
var (value, fake, secret) =
(_values[i], _fake[i], _secret[i]);
if (bid.blindedBid != keccak256(value, fake, secret)) {
// 如果寄过来的信息求哈希后和数据库中存储的不同,则说明揭示失败
continue;
}
refund += bid.deposit;
if (!fake && bid.deposit >= value) {
if (placeBid(msg.sender, value))
refund -= value;
}
// 把blindedBid设置为0,防止用户重复揭示盲标
bid.blindedBid = bytes32(0);
}
msg.sender.transfer(refund); //把fake的投标的钱和真的投标的 deposit - value的钱还给用户
}

// 这是一个"internal"函数,意味着这个函数只能由合约自身来调用
// 之后的操作与simple auction相类似
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != 0) {
// Refund the previously highest bidder.
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}

function withdraw() public {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
pendingReturns[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
function auctionEnd()
public
onlyAfter(revealEnd)
{
require(!ended);
AuctionEnded(highestBidder, highestBid);
ended = true;
beneficiary.transfer(highestBid);
}
}

3.一些思考与改进

在看代码的过程中,我发现用户的钱在投标后其实是会把钱直接锁死在了智能合约中。根据本合约的逻辑,最终其实会有很多用户的钱被锁定(因为在用户调用withdraw()函数之前,他们都认为自己下的标是最大的)这其实对用户而言非常的不友好。

这里,我重新修改了placeBid()函数,当highestBid发生变化时我直接把前一个highestBid的钱退还给了相应的用户。我把我的代码放到了下面的链接里:https://github.com/Ohyoukillkenny/Learn-Solidity

Learn Solidity (1) — Ballot

Posted on 2018-01-01 | In Learn Solidity

Solidity学习笔记(1) — Ballot

作者:孔令坤,转载请注明出处

最近开始学习Solidity,开始学习写smart contract。今天开始用一个官方提供的投票合同(Ballot Contract)进行第一步的学习。

环境的安装和使用方法这里不做过多的赘述,附上两个链接,大家可以用来参考:

MetaMask配置:https://karl.tech/learning-solidity-part-1-deploy-a-contract/

Solidity配置与运行案例:https://karl.tech/learning-solidity-part-2-voting/

另外附上官方的学习代码链接:https://solidity.readthedocs.io/en/latest/solidity-by-example.html#voting

1.逻辑简介

如下图所示,整个投票过程中有三个角色,其中ChairMan负责组织投票,而Voters进行投票,Proposals是本次投票中的候选人。其中,Chairman组织投票的方式为指定全部的候选人名单,并逐一赋予候选人们投票的权力。

Roles

之后,Voters开始进行投票,他们投票有两种方式,第一种简单明了,Voter直接对心仪的Proposal进行投票。

第二种方式略为复杂,类似于人大代表大会的制度,Voters可以选择信任的Voter(不能是自己)进行委托(delegate),然后由委托人进行再次委托或者直接进行投票,示意图如下图所示。其中白色的字母表示Voter拥有的票数,即官方代码中的weight,而红色的字母是候选人最后获得的票数,即官方代码中的voteCount。

Voters

2.代码摘要

指定候选者名单:chairperson的初始化和指定候选者的名单是在初始化整个合同的过程中同时进行的,这确保了合同的发起人msg.sender就是本次投票的组织者,chairman。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// Create a new ballot to choose one of `proposalNames`.
function Ballot(bytes32[] proposalNames) public {
chairperson = msg.sender;
voters[chairperson].weight = 1;

// For each of the provided proposal names,
// create a new proposal object and add it
// to the end of the array.
for (uint i = 0; i < proposalNames.length; i++) {
// `Proposal({...})` creates a temporary
// Proposal object and `proposals.push(...)`
// appends it to the end of `proposals`.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}

赋予投票者权力:而在给予Voters投票权力时,合同则是采用了函数。这时,为了确保只有chairperson可以赋予去权力,官方的代码中使用了Solidity特有的require(arg)写法:如果require函数框内的参数输入为真,函数正常运行;如果输入为假,则终止程序的运行并且返回整个函数,e.g.,giveRightToVote()运行前的状态。

使用require函数的优势在于这样做会比较安全,保证如果程序中某部分运行出错可以退出程序并返回到安全状态,但是同样的,require函数的使用会消耗所有提供的gas。(gas是以太坊中特有的一种概念,用于奖励矿工们对交易进行确认的行为,基本上处理数据量越大的合同,越复杂的合同会消耗越多的gas)

赋予voter权力的代码如下:

1
2
3
4
5
6
// Give `voter` the right to vote on this ballot.
// May only be called by `chairperson`.
function giveRightToVote(address voter) public {
require((msg.sender == chairperson) && !voters[voter].voted &&(voters[voter].weight == 0));
voters[voter].weight = 1;
}

投票者直接向候选人投票:这部分的代码逻辑比较简单,但是值得注意的是,官方代码使用了storage变量的声明。Ethereum Virtual Machine有三个地方用于存储,分别是storage, memory和stack,存储开销依次递减。具体的区别可见链接。但是需要注意的是,本地的一些变量如struct, array 或者 mapping 会自动被存储在storage中。在代码中,官方使用storage变量的声明做代码简化,即当用storage申明了sender后,之后对sender各项属性的赋值会直接写入storage中,而不用每次都做voters[msg.sender].voted = true此类操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// Give your vote (including votes delegated to you)
/// to proposal `proposals[proposal].name`.
function vote(uint proposal) public {
// assigns reference
Voter storage sender = voters[msg.sender];
require(!sender.voted);
sender.voted = true;
sender.vote = proposal;

// If `proposal` is out of the range of the array,
// this will throw automatically and revert all
// changes.
proposals[proposal].voteCount += sender.weight;
}

投票者通过委托进行投票:如果你理解了在逻辑简介中图例的思想,这部分的代码很容易看懂。简单来说,Voters通过选择委托人,然后通过while循环一路找到最终的委托人,之后将自己的票给到这个最终的委托人手中,由他进行操作。需要注意的是在Solidity的编程中对while语句的使用一定要小心,防止有长时间的循环导致block中的gas耗尽,程序无法得到执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/// Delegate your vote to the voter `to`.
function delegate(address to) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted);

// Self-delegation is not allowed.
require(to != msg.sender);

while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// We found a loop in the delegation, not allowed.
require(to != msg.sender);
}
sender.voted = true;
sender.delegate = to;
Voter storage delegate = voters[to];
if (delegate.voted) {
// If the delegate already voted,
// directly add to the number of votes
proposals[delegate.vote].voteCount += sender.weight;
} else {
// If the delegate did not vote yet,
// add to her weight.
delegate.weight += sender.weight;
}
}

统计胜者:最后就是简单的对所有候选人的票数进行统计,然后得到最终的胜者。需用注意的是在函数后面跟着一个view,它的用法详见链接。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// @dev Computes the winning proposal taking all
/// previous votes into account.
function winningProposal() public view
returns (uint winningProposal)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal = p;
}
}
}
// Calls winningProposal() function to get the index
// of the winner contained in the proposals array and then
// returns the name of the winner
function winnerName() public view
returns (bytes32 winnerName)
{
winnerName = proposals[winningProposal()].name;
}

对官方代码的优化

官方的代码中存有两个值得改进的地方,第一在于并没有设置整个投票过程中的时长,讲道理一个规范的投票需要投票发起人指定一个投票的进行时长,超过这个时间后,就无法进行继续投票。第二在于代码没有考虑平票的情况。基于这两个问题,我对官方的代码进行了简单的优化,优化的结果放在下面的链接中,另外之后我的一些笔记会陆续放到下面的链接中:

https://github.com/Ohyoukillkenny/Learn-Solidity

打底裤和荷尔蒙

Posted on 2017-01-28 | In articles

第一篇影评送给Take the Lead中老师教导学生探戈的片段。这也是我心目中所有看过的电影里可以排进前五的一段舞蹈。

说起电影中的舞蹈的佳作,其实有很多。每一个男人心中,都希望能有一个能和他一起不顾众人目光,纵情跳着兔子舞的米亚;上校牵起佳人的纤手,尽管已经盲目,却在探戈中更能体会佳人的羞涩动人;芳芳对着镜子纵情狂欢,尽显青春的活泼和清纯的魅力;配着爵士乐 -- Down in Mexico,美女朝大叔跳起贴面舞,微微鼓起的肚囊让人欲罢不能(Death Proof,个人也超喜欢这段);美国往事中,少女在面包的粉尘之中,翩翩起舞,宛若仙女下凡 ……

然而,我心中的最经典的舞曲,却一直留给Take the ead中这段惊艳了青春的探戈。

这部电影作为宣扬美国主旋律的青春歌舞片,其实从故事角度而言,电影的情节略显单薄。故事的承起转折,略有些生硬和俗套 -- 主要讲了一个教师通过教导学生跳交际舞,从而将这些“不良少年”感化,将他们重新拉上了人生的正轨。这些学生都是“街舞少年”,在国内就是大名鼎鼎的“葬爱家族”里的成员。原来他们只喜欢跳街舞,喜欢在Pop和Rap的旋律中乱扭,而认为交际舞特别无聊,一点都不够酷炫。影片中的这段探戈,就是教师用于吸引这些“街舞少年”投入交际舞的怀抱的工具。(确实成功了,反正我看到我也会想去学)

当时,这个电影片段出现于我高中的音乐课。我记得老师是为了介绍“探戈”这个经典的舞蹈形式,然后放了这部电影给我们看。当时我就震惊了。说实话,就像是电影中的那个黑人小哥看到这段舞时情不自禁的吼出来的 just getting his flirt on。确实,这段舞更像是情人之间的调情,点燃了高中时我们无处安放的荷尔蒙。我印象特别深刻,当时观看这段舞的时候,已经下课了,但是全班的所有人都盯着视频看,没有一个人想要离开,或者发出声音。教室里除了舞曲的鼓点和下课的铃声,恐怕也就只剩下我们咽口水的声音了。

学习的时间很枯燥,也很漫长。所有人都拼命着压抑着野蛮生长的荷尔蒙,抵挡着各种奇形怪状的思绪带来的干扰,埋头学习,努力地往前冲冲冲。而这段舞蹈却像是一个有魔力的炸弹,点燃了所有人的荷尔蒙。反正我是记得,有人直接放声地说了出来--“万恶的安全裤”然后一片哄笑,众人面红耳赤,窃窃私语,漏出燥热而意味深长的笑容。

探戈起源于情人之间的秘密舞蹈。传说中男士跳舞时都佩带着短刀。现在虽已不佩带短刀,但舞蹈者必须表情严肃,提防被人发现的表情。然而这种表情的严肃,配上激情四溢的舞曲动作,更体现出一种反差与冲突之美,恰似在压抑中燃烧的荷尔蒙。(不知道为什么,跳舞跳着,两个人都想把对方吃掉(; ̄ェ ̄),不敢看)

需要注意的是,这里其实男主原来并不会跳探戈。导演特意特意请了专业舞蹈演员(女)教导男主学习探戈,并在短短一周内,两人磨合出了这段令人惊艳的舞蹈。(默默吐槽一下,毕竟看男主大家都只看脸)当然这也归功于优秀的摄影和剪辑。明亮的光线从窗外泻入,使得画面简明而动感。摄像机剪辑女舞者婀娜的身姿以及翩飞的舞步,更显探戈抓人的魅力。此外,影片对传统的Asi Se Baila El Tango(著名探戈舞曲)进行了改编,加入了鼓点,钢琴以及人声,使得舞蹈的表现显得更为有力。

总而言之,这部电影并不是一部剧情感人至深的佳作,影片本身并不能给舞蹈添色。所以很多人眼中可能这段舞蹈并不能摘得心目中影史最佳舞蹈的桂冠。但是,这段舞蹈对于我而言,并不仅仅只是一段舞,或者是一段优秀的电影片段,它还是我压抑里,躁动的青春。

想要从油管看这个舞蹈,请戳我。

1234

Lingkun Kong

17 posts
4 categories
4 tags
© 2018 Lingkun Kong
Powered by Hexo v3.7.1
|
Theme — NexT.Muse v6.1.0