Micropayment

Smart Contract: Micropayment

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

在Solidity的官网上,Micropayment Channel的例子目前暂待开发。于是我决定自己写一个Micropayment的合约。

Background

什么是Micropayment?

假设你是一个中国移动用户,正在拨打国际长途电话,然后中国移动会根据你的通话时长来进行收费,每分钟1元钱,并按照满分钟计算(比如打了2分钟20秒,记为3分钟)。这个时候,我们可以通过一个智能合约,进行你与中国移动之间的付款。显然,如果你每分钟都要给中国移动付钱,那么这样就太麻烦了,相当于你每打一分钟电话就要再掏出一分钟的话费。这个时候,我们可以用所谓的Micropayment,来进行一次性的费用支付。

如上图所示,Alice 通过智能合约向Bob(服务提供者)进行支付。首先,Alice往合约里存100单位的钱,然后获得Bob提供的服务。此后,智能合约开始计算时长。当Alice停止服务后,他将调用合约的函数,进行服务的停止。然后合约会自动结算Bob的服务费,然后将存在合约里的钱分别给到Alice和Bob手里。

同时,为了防止Alice一直不停止服务,Bob拿不到应该拿到的服务费用,我们应当在合约中提供让Bob终止合约,并拿到所有的钱的选项。这个选项的判定条件应当是Alice已经用完了所有服务的时长。

Code 1:

以下是第一段代码,需要注意的是这里我在code中并没有像传统的Micropayment Channel一样使用multi-sig。但是事实上,由于remix特殊的编程环境,user需要在service provider发起的合约的地址下At Address进行startService函数的使用。因此,其实从某种一样上,这个合约里也存在着用户与服务提供方对协议的共识。此代码remix亲测可用:

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
pragma solidity ^0.4.11;

contract Micropayment{
uint public value;
uint public feePerMinute; // the fee is counted by wei
uint public startTime;
uint public endTime;
address public serviceProvider;
address public user;

function Micropayment(uint fee) public {
require(fee > 0);
serviceProvider = msg.sender;
feePerMinute = fee;
}

modifier condition(bool _condition) {
require(_condition);
_;
}

modifier onlyProvider() {
require(msg.sender == serviceProvider);
_;
}

modifier onlyUser() {
require(msg.sender == user);
_;
}

event ServiceStarted();
event ServiceEnded();
event ForceEnd();

/// Force end the Micropayment (the service provider),
/// and get paid,
/// when user has run out his time.
function forcePay()
public
onlyProvider
{
require(now >= endTime);
ForceEnd();
serviceProvider.transfer(this.balance);
}

/// Start service as user.
/// Give a expected service time.
function startService(uint eT) // minutes
public
condition(msg.value >= feePerMinute * eT)
payable
{
ServiceStarted();
user = msg.sender;
startTime = now;
endTime = now + eT * 1 minutes;
value = msg.value;
}

/// End the service (the user).
/// Pay bill to service provider according to using time.
function endService()
public
onlyUser
{
require(now < endTime);
uint usingTime = (now - startTime) / 60 + 1;
uint bill = usingTime * feePerMinute;
user.transfer(value - bill);
serviceProvider.transfer(this.balance);
ServiceEnded();
}
}

也可以在我的github中找到:https://github.com/Ohyoukillkenny/Learn-Solidity

Potential Improvement

目前的micropayment合约有一个比较伤的缺点,就是无法防止service的提供者, i.e., 中国移动耍赖。比如线下的时候明明还没有到服务截止时间,就停止了服务。从合约的角度来讲,目前无法杜绝这种情况的发生。所以和之前的remote purchase合约一样,需要线下有第三方的监管。

Code 2:

在与他人的交流中,有一位大佬觉得Code 1中的合约其实不是传统意义上的Micropayment Channel,所以这里我又写了一段代码,加入了传统的multi-sig,使得合约更加贴近通常意义上的Micropayment Channel。

这里我假设Bob, i.e., worker是给Alice, i.e., moneyProvider干活的人。

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
pragma solidity ^0.4.11;

contract Channel {

address public moneyProvider;
address public worker;
uint public startTime;
uint public timeOut;
mapping (bytes32 => address) signatures;

function Channel(address to, uint t) payable {
worker = to;
moneyProvider = msg.sender;
startTime = now;
timeOut = t;
}
// this function can be both called by moneyProvider and worker,
// we use sha3 as hash function in this case.
function CloseChannel(bytes32 h, uint8 v, bytes32 r, bytes32 s, uint value){
address signer;
bytes32 proof;
// get signer from signature
signer = ecrecover(h, v, r, s);
// signature is invalid, throw
require (signer == moneyProvider || signer == worker);
proof = sha3(this, value);
// signature also needs to match the data provided
require (proof == h);
if (signatures[proof] == 0)
signatures[proof] = signer;
else if (signatures[proof] != signer){
// channel completed, both signatures provided
worker.transfer(value);
selfdestruct(moneyProvider);
}
}
// this function is called by moneyProvider, when worker is malicious.
function moneyBack(){
require (startTime + timeOut > now && msg.sender == moneyProvider);
selfdestruct(moneyProvider);
}
}

总体而言,传统的写法对后端执行的要求更多,需要涉及哈希和签名的处理,另外支付的金额也是在后台进行计算的。此外由于有比较长的数据包要被smart contract多次处理,所以会消耗相对而言多得多的gas。

另外在code 2的逻辑中,同样无法避免的是moneyProvider耍赖的情况,因为最后是由moneyProvider决定最后给worker发多少钱, i.e., value,假如moneyProvider发的钱和之前说好的不一样,合约也无法对其进行制裁。所以我们仍然需要第三方进行监管。但是由于应当支付的钱不断被worker用closeChannel函数作为transaction写到block里面去,所以相对而言,最后moneyProvider要发多少是被记录下来的,是有迹可循的。