0x-start-project源码学习 - 3

之前说了基本的fill order的流程,由makertaker可以直接进行交易,是否引入relayer都可以,这是一般open orderbook的策略。另一种策略是matching,它指用户A,用户B它们分别下定单希望进行交易,刚好它们的订单可以匹配,这时由relayer进行matchOrders来完成交易。

需要注意的是订单金额不一定需要完全匹配。如订单A卖出1WETH买入10ZRX,订单B卖出10ZRX买入1WETH,这是一种完全匹配的情况。但是不完全匹配一样可以交易成功,如订单A卖出1WETH买入4ZRX,订单B卖出10ZRX买入1WETH,这个时候可以发现,在订单B可以满足订单A,订单A也可以满足订单B,会多余出6ZRX,因为A只买入4ZRX,B卖出了10ZRX,这6个ZRX的差价就由订单的Matcher获得。

1
2
3
Order A 1 WETH -> 4 ZRX
Order B 10 ZRX -> 1 WETH
Matcher -> 6 ZRX

这6个ZRX的差价就变成了Matcher的收入,当然只有差价有冗余的时候才可以匹配订单,如果超出了兑换数量则也无法匹配,如订单A卖出1WETH买入15ZRX,订单B卖出10ZRX买入1WETH,这种情况就无法进行匹配,尝试匹配订单会返回匹配失败。

1
2
3
4
5
6
7
8
9
10
11
┌─────────────────┬────────────────────────────────────────────────────────────────────┐
│ matchOrders │ 0xbd3bbda041b944502f1e735756de721a756438db0e63716394687d79562cd97e │
├─────────────────┼────────────────────────────────────────────────────────────────────┤
│ left orderHash │ 0xc3542f0cfa9ba9acca4d94c83265515ae8bfe683d072a905d950d909d6368c1b │
├─────────────────┼────────────────────────────────────────────────────────────────────┤
│ right orderHash │ 0x37751bf8d3a016c7e15eded7d06962e19afd0575e52368b61424b8f3d3b7b888 │
├─────────────────┼────────────────────────────────────────────────────────────────────┤
│ gasUsed │ 95539 │
├─────────────────┼────────────────────────────────────────────────────────────────────┤
│ status │ Failure │
└─────────────────┴────────────────────────────────────────────────────────────────────┘

需要注意的是,如果尝试匹配并匹配失败仍然需要支付对应的Gas,这样就得不偿失了。

除了金额的匹配,需要注意的还有交易费用的问题,交易费用不会进行匹配,即交易费用的多少不影响是否匹配成功,如订单A的MakerFee为5ZRXTakerFee为10ZRX,订单A的MakerFee为10ZRXTakerFee为20ZRX,虽然相对于订单A它的Taker就是订单B的Maker,但是它们并不是直接进行交易的,MakerFee毫无疑问由它们自身承担,但是TakerFee承担者并不是对方,而是MatcherMatcher会支付双方订单中TakerFee的交易费用。

之后我们来看看0x-start-project中match_order的源码:

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
PrintUtils.printScenario('Match Orders');
// Initialize the ContractWrappers, this provides helper functions around calling
// 0x contracts as well as ERC20/ERC721 token contracts on the blockchain
const contractWrappers = new ContractWrappers(providerEngine, { networkId: NETWORK_CONFIGS.networkId });
// Initialize the Web3Wrapper, this provides helper functions around fetching
// account information, balances, general contract logs
const web3Wrapper = new Web3Wrapper(providerEngine);
// 用户地址
// matcherAccount为撮合者地址,一般是Relayer
const [leftMaker, rightMaker, matcherAccount] = await web3Wrapper.getAvailableAddressesAsync();
// 待交易代币地址
const zrxTokenAddress = contractAddresses.zrxToken;
const etherTokenAddress = contractAddresses.etherToken;
const printUtils = new PrintUtils(
web3Wrapper,
contractWrappers,
{ leftMaker, rightMaker, matcherAccount },
{ WETH: etherTokenAddress, ZRX: zrxTokenAddress },
);
// 打印账户信息
printUtils.printAccounts();
// 设置交易金额
// the amount the maker is selling of maker asset
const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS);
// the amount the maker wants of taker asset
const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.4), DECIMALS);
// 0x v2 uses hex encoded asset data strings to encode all the information needed to identify an asset
// 交易代币数据
const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxTokenAddress);
const takerAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddress);
let txHash;
let txReceipt;
// 设置代币授权
// Allow the 0x ERC20 Proxy to move ZRX on behalf of makerAccount
const leftMakerZRXApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
zrxTokenAddress,
leftMaker,
);
await printUtils.awaitTransactionMinedSpinnerAsync('Left Maker ZRX Approval', leftMakerZRXApprovalTxHash);
// Approve the ERC20 Proxy to move ZRX for rightMaker
const rightMakerZRXApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
zrxTokenAddress,
rightMaker,
);
await printUtils.awaitTransactionMinedSpinnerAsync('Right Maker ZRX Approval', rightMakerZRXApprovalTxHash);
// Approve the ERC20 Proxy to move ZRX for matcherAccount
// TODO:为什么需要对matcher的zrx账户进行授权?matcher需要支出交易费用?应该不需要
const matcherZRXApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
zrxTokenAddress,
matcherAccount,
);
await printUtils.awaitTransactionMinedSpinnerAsync('Matcher ZRX Approval', matcherZRXApprovalTxHash);
// Approve the ERC20 Proxy to move WETH for rightMaker
const rightMakerWETHApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
etherTokenAddress,
rightMaker,
);
await printUtils.awaitTransactionMinedSpinnerAsync('Right Maker WETH Approval', rightMakerZRXApprovalTxHash);
// Convert ETH into WETH for taker by depositing ETH into the WETH contract
const rightMakerWETHDepositTxHash = await contractWrappers.etherToken.depositAsync(
etherTokenAddress,
takerAssetAmount,
rightMaker,
);
await printUtils.awaitTransactionMinedSpinnerAsync('Right Maker WETH Deposit', rightMakerWETHDepositTxHash);
PrintUtils.printData('Setup', [
['Left Maker ZRX Approval', leftMakerZRXApprovalTxHash],
['Right Maker ZRX Approval', rightMakerZRXApprovalTxHash],
['Matcher Maker ZRX Approval', matcherZRXApprovalTxHash],
['Right Maker WETH Approval', rightMakerWETHApprovalTxHash],
['RIght Maker WETH Deposit', rightMakerWETHDepositTxHash],
]);
// Set up the Order and fill
// 订单过期时间
const randomExpiration = getRandomFutureDateInSeconds();
const exchangeAddress = contractAddresses.exchange;
// Create the order
// 创建订单A
const leftOrder: Order = {
exchangeAddress,
makerAddress: leftMaker,
takerAddress: NULL_ADDRESS,
senderAddress: NULL_ADDRESS,
feeRecipientAddress: NULL_ADDRESS,
expirationTimeSeconds: randomExpiration,
salt: generatePseudoRandomSalt(),
makerAssetAmount,
takerAssetAmount,
makerAssetData,
takerAssetData,
makerFee: ZERO,
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.01), DECIMALS),
};
PrintUtils.printData('Left Order', Object.entries(leftOrder));
// Create the matched order
// 创建订单B
// 它们交易金额不一致
const rightOrderTakerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(4), DECIMALS);
const rightOrder: Order = {
exchangeAddress,
makerAddress: rightMaker,
takerAddress: NULL_ADDRESS,
senderAddress: NULL_ADDRESS,
feeRecipientAddress: NULL_ADDRESS,
expirationTimeSeconds: randomExpiration,
salt: generatePseudoRandomSalt(),
makerAssetAmount: leftOrder.takerAssetAmount,
takerAssetAmount: rightOrderTakerAssetAmount,
makerAssetData: leftOrder.takerAssetData,
takerAssetData: leftOrder.makerAssetData,
makerFee: ZERO,
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.01), DECIMALS),
};
PrintUtils.printData('Right Order', Object.entries(rightOrder));
// 分别对订单进行签名
// Generate the order hash and sign it
const leftOrderHashHex = orderHashUtils.getOrderHashHex(leftOrder);
const leftOrderSignature = await signatureUtils.ecSignHashAsync(providerEngine, leftOrderHashHex, leftMaker);
const leftSignedOrder = { ...leftOrder, signature: leftOrderSignature };
// Generate the order hash and sign it
const rightOrderHashHex = orderHashUtils.getOrderHashHex(rightOrder);
const rightOrderSignature = await signatureUtils.ecSignHashAsync(providerEngine, rightOrderHashHex, rightMaker);
const rightSignedOrder = { ...rightOrder, signature: rightOrderSignature };
// Print out the Balances and Allowances
await printUtils.fetchAndPrintContractAllowancesAsync();
await printUtils.fetchAndPrintContractBalancesAsync();
// Match the orders via 0x Exchange
// 执行交易订单匹配
txHash = await contractWrappers.exchange.matchOrdersAsync(leftSignedOrder, rightSignedOrder, matcherAccount, {
gasLimit: TX_DEFAULTS.gas,
});
txReceipt = await printUtils.awaitTransactionMinedSpinnerAsync('matchOrders', txHash);
printUtils.printTransaction('matchOrders', txReceipt, [
['left orderHash', leftOrderHashHex],
['right orderHash', rightOrderHashHex],
]);