-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
1347 lines (1277 loc) · 73.7 KB
/
atom.xml
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://tangminjie.github.io</id>
<title>Tangminjie's Blog</title>
<updated>2023-04-18T06:30:45.212Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://tangminjie.github.io"/>
<link rel="self" href="https://tangminjie.github.io/atom.xml"/>
<subtitle>个人技术博客,做好当下</subtitle>
<logo>https://tangminjie.github.io/images/avatar.png</logo>
<icon>https://tangminjie.github.io/favicon.ico</icon>
<rights>All rights reserved 2023, Tangminjie's Blog</rights>
<entry>
<title type="html"><![CDATA[空投,量化交易工具集合]]></title>
<id>https://tangminjie.github.io/post/--WKNkawg/</id>
<link href="https://tangminjie.github.io/post/--WKNkawg/">
</link>
<updated>2023-02-10T10:10:16.000Z</updated>
<content type="html"><![CDATA[<p>** 配置指纹浏览器:**<br>
这里用的是Adspower,优点就是稳定,可配置的环境数(账号)更多,业内用的较多,缺点就是比较贵。配置方法都一样,就是把参数输入进去,主要是IP的参数要从渠道商那里获取。<br>
传送门:<br>
https://mirror.xyz/0x1890.eth/x-Z_KsGCDkZQngxU8tgGEW2zT1zqrrGo2XYx3B77rNQ</p>
<p>** 独立IP购买渠道 **<br>
拼团方式的话,各个博主都有建群,可以联系@Uncle_Simon25,私聊入群。他那边人数多,需要挨个拉。</p>
<p>** 谷歌邮箱,DC,Twitter购买渠道 **<br>
直接购买:<br>
https://wuyoo.top/ 推特:2.8r Discord:1.5r 邮箱:3r</p>
<p>https://www.lekafa.cn/links/FB8E9633 推特:3r Discord:2.5r http://www.zztka1.xyz/jingdian/index/henggoodbyname.html?key=tw 推特:3r Discord:2r 邮箱:3r<br>
https://accsmarket.com/ 比较便宜 号的质量不知道</p>
<p>接码平台:<br>
https://sms-man.com/ 选择印度接码 推特:0.17U Discord:0.22U 邮箱:0.15U</p>
<p>** 同步器下载及教程 **<br>
以麒麟同步器为例<br>
下载地址:http://xzy1y.ysepan.com/</p>
<p>下载好以后我们需要注册卡才可以使用,去淘宝搜索麒麟同步器,月卡成本25r。</p>
<p>https://mirror.xyz/0x1890.eth/pCQa7Z0S-2xcLRO1izlb8ciCQdMb5tW-Bv_Ytl2WBIw</p>
<p>** 查看空投工具 **<br>
https://www.daylight.xyz/abilities</p>
<p>** https://testnetbridge.com/ **<br>
测试币和主网币兑换</p>
<p>** 水龙头 2023更新 **<br>
https://blockwander.com/ethereum-goerli-faucet/</p>
<p>** 量化交易工具网站 **</p>
<ol>
<li>作者产出很高 https://www.fmz.com/</li>
</ol>
<p>** 节点部署国外服务器 **</p>
<ol>
<li>https://dedipath.com/</li>
<li>https://www.vultr.com/ 按小时收费</li>
</ol>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Hardhat 学习笔记]]></title>
<id>https://tangminjie.github.io/post/hardhat-xue-xi-bi-ji/</id>
<link href="https://tangminjie.github.io/post/hardhat-xue-xi-bi-ji/">
</link>
<updated>2022-10-20T16:50:46.000Z</updated>
<content type="html"><![CDATA[<h1 id="入门">入门</h1>
<h2 id="安装">安装</h2>
<pre><code>```
npm install --save-dev hardhat
npx hardhat
```
</code></pre>
<h2 id="编译合约">编译合约</h2>
<pre><code>```npx hardhat compile```
</code></pre>
<h2 id="部署合约">部署合约</h2>
<ol>
<li>编写js代码<pre><code>const Token = await ethers.getContractFactory("合约名");//
const hardhatToken = await Token.deploy();
await hardhatToken.deployed();
// 接下来可以直接调用合约方法 获取余额
const ownerBalance = await hardhatToken.balanceOf(address);
</code></pre>
</li>
<li>执行js代码<pre><code>// node js文件路径
node scripts/index.js
</code></pre>
</li>
</ol>
<h2 id="常用操作">常用操作</h2>
<h3 id="fork主网">fork主网</h3>
<ol>
<li>命令的形式<pre><code>// fork eth链
// npx hardhat node --fork 地址
npx hardhat node --fork https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161
// fork指定块高度(锁定区块)
// npx hardhat node --fork 地址 --fork-block-number 块高度
npx hardhat node --fork https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --fork-block-number 115445
</code></pre>
</li>
<li>配置hardhat.config.js<pre><code>networks: {
hardhat: {
forking: {
url: "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", // okChain
// blockNumber: 5115288, //指定fork的块高度(锁定区块)
},
}
},
</code></pre>
</li>
</ol>
<h3 id="重置fork">重置Fork</h3>
<p>你可以在运行时里操作Fork,如重置回全新的Fork状态、从另一个区块号Fork,或者通过调用hardhat_reset禁用Fork:</p>
<ol>
<li>重新fork<pre><code>await network.provider.request({
method: "hardhat_reset",
params: [{
forking: {
jsonRpcUrl: "https://eth-mainnet.alchemyapi.io/v2/<key>",
blockNumber: 11095000
}
}]
})
</code></pre>
</li>
<li>禁用fork<pre><code>await network.provider.request({
method: "hardhat_reset",
params: []
})
</code></pre>
</li>
</ol>
<h2 id="挖矿">挖矿</h2>
<ol>
<li>
<p>禁用挖矿</p>
<pre><code>async function f6() {
await hardhat.network.provider.send("evm_setAutomine", [false]);
}
f6()
</code></pre>
</li>
<li>
<p>启用区间挖掘<br>
<code>await network.provider.send("evm_setIntervalMining", [5000]);</code></p>
</li>
<li>
<p>手动挖矿<br>
在hardhat.config.js没有配置手动挖矿 或 当自动挖矿被禁用时,可以执行手动挖矿(没有被禁用一样可以执行手动)</p>
<pre><code> async function f8() {
await hardhat.network.provider.request({
method: "evm_mine",
});
}
f8()
</code></pre>
</li>
</ol>
<h3 id="获取pending中的交易列表">获取pending中的交易列表</h3>
<p>可以先禁用挖矿,再获取pending列表<br>
<code>const pendingBlock = await network.provider.send("eth_getBlockByNumber", [ "pending", false, ]);</code></p>
<h3 id="删除和替换交易-hardhat_droptransaction">删除和替换交易 hardhat_dropTransaction</h3>
<ol>
<li>删除交易<pre><code>const txHash = "0xabc...";
await network.provider.send("hardhat_dropTransaction", [txHash]);
</code></pre>
</li>
<li>替换交易 直接发起一个交易,指定的交易的哈希为你先覆盖的哈希即可,就像在 Geth 中一样,新的 gas/fees 价格必须至少比当前交易的 gas 价格高 10%</li>
</ol>
<h3 id="设置下一个块的时间戳">设置下一个块的时间戳</h3>
<pre><code>```
const hardhat = require("hardhat");
hardhat.network.provider.request({
method: "evm_setNextBlockTimestamp",
params: [1640966400],
});
```
** 1640966400为你要设置的时间戳 场景:比如到点才能claim **
</code></pre>
<h3 id="配置项-hardhatconfigjs">配置项 hardhat.config.js</h3>
<pre><code> networks: {
hardhat: {
chainId: 31337,//默认链id
from: account,//默认账户
gas: 'auto'|'3000',// gas费,auto自动或者一个具体的数值
gasPrice: 'auto'|'3000', // 类似gas
gasMultiplier: '1',用于乘以气体估计结果的数字,默认值1
accounts: { // 此字段可以配置为以下之一
mnemonic: //助记池 BIP39 定义的 12 或 24 个单词的助记词。默认值:"test test test test test test test test test test test junk"
initialIndex: // 要派生的初始索引。默认值:0
path:所有派生密钥的 HD 父级。默认值:"m/44'/60'/0'/0"。
count: 20,要派生的帐户数。默认值:20。
accountsBalance: 分配给每个派生帐户的余额(以 wei 为单位)的字符串。默认值:"10000000000000000000000"(10000 ETH)
},
blockGasLimit: gas 限制。默认值:30000000 (3gwei)
minGasPrice: 最低 gas 价格,默认0we
hardfork: 这个设置改变了安全帽网络的工作方式,在给定的硬分叉上模仿以太坊的主网。它必须是一个"byzantium","constantinople","petersburg","istanbul","muirGlacier","berlin"和"london"。默认值:"london"
throwOnTransactionFailures:控制安全帽网络是否引发交易失败的布尔值。如果此值为true,Hardhat Network 将在事务失败时抛出组合的 JavaScript 和 Solidity 堆栈跟踪。如果是false,它将返回失败的交易哈希。在这两种情况下,交易都被添加到区块链中。默认值:true
throwOnCallFailures:一个布尔值,用于控制 Hardhat Network 是否在调用失败时引发。如果此值为true,Hardhat Network 将在调用失败时抛出组合的 JavaScript 和 Solidity 堆栈跟踪。如果是false,它将返回调用的return data,其中可以包含还原原因。默认值:true
loggingEnabled:控制安全帽网络是否记录每个请求的布尔值。默认值:false对于进程内 Hardhat Network 提供者,true对于 Hardhat Network 支持的 JSON-RPC 服务器(即node任务)。
initialDate:设置区块链日期的可选字符串。有效值是Javascript 的日期时间字符串。默认值:如果不分叉另一个网络,则为当前日期和时间。当分叉另一个网络时,使用您分叉的区块的时间戳加上一秒。
allowUnlimitedContractSize:一个可选的布尔值,禁用EIP 170强加的合约大小限制。默认值:false
forking: {描述分叉配置的对象,可以具有以下字段:
url: 指向具有要分叉的状态的 JSON-RPC 节点的 URL。此字段没有默认值。必须为前叉提供它才能工作。
blockNumber:一个可选数字,用于固定要从哪个块中分叉。如果未提供值,则使用最新的块。
enabled: 一个可选的布尔值,用于打开或关闭 fork 功能。默认值:true如果url设置,false否则。
}
minGasPrice:gasPrice交易必须具有的最小值。如果"hardfork"是"london"或以后的,则此字段不得存在。默认值:"0"。
initialBaseFeePerGas: 第baseFeePerGas一个块的。请注意,在分叉远程网络时,“第一个块”是您分叉的块之后的那个。此字段必须不存在,如果"hardfork"不是"london"或更高之一。默认值:"1000000000"如果不分叉。在 fork 远程网络时,如果远程网络使用 EIP-1559,则第一个本地块将baseFeePerGas根据 EIP使用权限,否则"10000000000"使用
}
}
</code></pre>
<h2 id="evm方法">EVM方法</h2>
<p>方法具体解析可以看以太坊虚拟机官方API说明 ethereum-json-rpc-api</p>
<h3 id="标准方法">标准方法</h3>
<ol>
<li>eth_accounts 返回主账户,这个账户是在hardhat.config.js配置的私钥的账户地址</li>
<li>eth_blockNumber 返回当前块高度</li>
<li>eth_call<br>
立刻执行一个新的消息调用<pre><code> 参数
Object - 交易调用对象
from: DATA, 20 Bytes - 发送交易的原地址,可选
to: DATA, 20 Bytes - 交易目标地址
gas: QUANTITY - 交易可用gas量,可选。eth_call不消耗gas,但是某些执行环节需要这个参数
gasPrice: QUANTITY - gas价格,可选
value: QUANTITY - 交易发送的以太数量,可选
data: DATA - 方法签名和编码参数的哈希,可选
QUANTITY|TAG - 整数块编号,或字符串"latest"、"earliest"或"pending"
</code></pre>
</li>
<li>eth_chainId 返回当前链id</li>
<li>eth_coinbase 返回接收挖矿回报的账户地址</li>
<li>eth_estimateGas 返回估计调用需要耗费的gas量</li>
<li>eth_gasPrice 返回当前的gas价格</li>
<li>eth_getBalance 获取指定账户余额</li>
<li>eth_getBlockByHash 返回具有指定哈希的块<pre><code> params: [
hash,
true//为true时返回完整的交易对象,否则仅返回交易哈希
]
</code></pre>
</li>
<li>eth_getBlockByNumber 返回指定编号的块。<pre><code>params: [
'0x1b4', // 整数块编号,或字符串"earliest"、"latest" 或"pending"
true// 为true时返回完整的交易对象,否则仅返回交易哈希
]
</code></pre>
</li>
<li>eth_getBlockTransactionCountByHash 返回指定块内的交易数量,使用哈希来指定块<pre><code> params: [
hash
]
</code></pre>
</li>
<li>eth_getBlockTransactionCountByNumber 返回指定块内的交易数量,使用块编号指定块<pre><code> params: [
'0xe8', // 232
]
</code></pre>
</li>
<li>eth_getCode 返回指定地址的代码<pre><code> params: [
address,
'0x2' //整数块编号,或字符串"latest"、"earliest" 或"pending"
]
</code></pre>
</li>
<li>如何调用<pre><code> const hardhat = require("hardhat");
hardhat.network.provider.request({
method: 方法名,
params: [参数],
});
</code></pre>
</li>
</ol>
<h2 id="hardhat-方法">Hardhat 方法</h2>
<ol>
<li>hardhat_addCompilationResult 添加有关已编译合同的信息</li>
<li>hardhat_dropTransaction 从内存池中删除交易</li>
<li>hardhat_impersonateAccount 模拟账户<pre><code> async function f1(){
await hardhat.network.provider.request({
method: "hardhat_impersonateAccount",
params: [被模拟的账户],
});
const signer = await ethers.provider.getSigner(被模拟的账户)
// 用模拟的账户给指定账户转账
await signer.sendTransaction({
to: "0x23FCB0E1DDbC821Bd26D5429BA13B7D5c96C0DE0",
value: ethers.utils.parseEther("1.0"),
});
console.log('success')
// 取消模拟
await hardhat.network.provider.request({
method: "hardhat_stopImpersonatingAccount",
params: [被模拟的账户],
});
}
f1().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
</code></pre>
</li>
<li>查询交易信息<pre><code>async function UnsignedTransaction(hash) {
const data = await hardhat.ethers.provider.getTransaction(hash)
console.log('data', data)
}
//UnsignedTransaction(交易hash)
UnsignedTransaction('0xa475cf601cb72f4435b22cec9e622eaee1c98f4bd5fa39d1d7e0e69c3f5f62f5')
</code></pre>
</li>
<li>修改账户余额<pre><code>async function f2(){
await hardhat.network.provider.send("hardhat_setBalance", [
"0x23FCB0E1DDbC821Bd26D5429BA13B7D5c96C0DE0",
Web3.utils.numberToHex(hardhat.ethers.utils.parseEther('123.12')),//十六进制,可以通过ethers.utils的转换接口
]);
}
f2()
</code></pre>
</li>
<li>Token转账<pre><code>// 代币转账
async function f4(){
// 合约部署
const ERC20 = await ethers.getContractFactory("ERC20");
let erc20 = await ERC20.deploy();
erc20.deployed();
const accounts =await ethers.getSigners();
const beforeBalance = await erc20.balanceOf(accounts[1].address)
// 设置转账数量
const amount = ethers.utils.parseEther('10')
// 广播交易
const tx = await erc20.transfer(accounts[1].address, amount)
// 等待交易上链
await tx.wait()
// 查询目标地址余额
const afterBalance = await erc20.balanceOf(accounts[1].address)
console.log('转帐前', beforeBalance.toString())
console.log('转帐后', afterBalance.toString())
}
f4()
</code></pre>
</li>
<li>ETH转账<pre><code>// 代币转账
async function f5(){
const [owner] = await ethers.getSigners();
await owner.sendTransaction({
from: owner.address,
to: "0xD528d6B7ff1f46417a7A7b2f2869Fe0CC8e9ed67",
value: ethers.utils.parseEther("1.2"),
});
// 查询目标地址余额
const balance = await hardhat.ethers.provider.getBalance('0xD528d6B7ff1f46417a7A7b2f2869Fe0CC8e9ed67')
console.log(balance.toString())
}
f5()
</code></pre>
</li>
<li>常见单位转换<pre><code>// gas转换 num2wei
hardhat.ethers.utils.parseUnits('300', 'gwei'),
// ether转换 num2wei
hardhat.ethers.utils.parseEther('1')
// num 2 16hex
Web3.utils.numberToHex('1')
ethers.utils.hexValue(1)
</code></pre>
</li>
</ol>
<h3 id="编译配置">编译配置</h3>
<pre><code> solidity: {
compilers: [
{
version: "0.5.1", // 编译器版本
settings: {
optimizer: {
enabled: true,
runs: 200
}
},
...其他配置
</code></pre>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[WEB3 求职网站]]></title>
<id>https://tangminjie.github.io/post/web3-qiu-zhi-wang-zhan/</id>
<link href="https://tangminjie.github.io/post/web3-qiu-zhi-wang-zhan/">
</link>
<updated>2022-08-04T08:18:00.000Z</updated>
<content type="html"><![CDATA[<ol>
<li>https://abetterweb3.notion.site/e3577a9eea1746b18b6421227cfa3ccf?v=2b91fe0284a54a6ab06b0283ca81223b</li>
</ol>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[ERC-721A]]></title>
<id>https://tangminjie.github.io/post/erc-721a/</id>
<link href="https://tangminjie.github.io/post/erc-721a/">
</link>
<updated>2022-05-12T08:14:52.000Z</updated>
<content type="html"><![CDATA[<p>ERC-721A 是由 Azuki 研发的 ERC-721 实现。ERC-721A,已经运用到了他們发行的 Azuki NFT Collection 中。根据他们的文档介绍,铸造一个NFT 所消耗的 gas fee,相比 OZ ERC721,节省了一倍多,如果是一次mint 5 个,节省的 gas 甚至达到 7 倍。<br>
<img src="https://s2.loli.net/2022/05/12/5lXz4cfLMqEn1Cb.png" alt="gas费用对比" loading="lazy"></p>
<h1 id="erc-721a优化">ERC-721A优化</h1>
<p><strong>优化 1 - 从 OpenZeppelin (OZ) ERC721Enumerable 中删除重复存储</strong><br>
IERC721Enumerable 的广泛使用的 OZ 实现包括每个令牌元数据的冗余存储。这种非规范化的方法以编写函数的巨大成本优化读取函数,考虑到用户不太可能为读取函数付费,这并不理想。此外,我们的令牌从 0 开始编号,这一事实让我们从基本实现中删除了一些冗余存储。我们强烈建议所有新发布的产品在寻找大赢家时仔细检查此文件。<br>
ERC721Enumerable接口提供了读取NFT发现量,持有者,TokenID等情况,由智能合约来实现:</p>
<pre><code class="language-totalSupply():">tokenByIndex(): //返回指定位置NFT的TokenID
tokenOfOwnerByIndex(): //返回指定地址的所有TokenID
</code></pre>
<p>首先 ERC721a 对 ERC721Enumerable 中的实现做了优化,去除了一些不必要的存储。<br>
OZ ERC721Enumerable:</p>
<pre><code class="language-//">uint256[] private _allTokens;
/**
* @dev See {IERC721Enumerable-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _allTokens.length;
}
</code></pre>
<p>Azuki ERC721a:</p>
<pre><code class="language-uint256">/**
* @dev See {IERC721Enumerable-totalSupply}.
*/
function totalSupply() public view override returns (uint256) {
return currentIndex;
}
</code></pre>
<p>可以看到相比 OZ ERC721Enumerable, Azuki ERC721a 沒有使用昂贵的 array 存储空间來保存 allTokens, 而是直接用 currentIndex 來返回(之所以能这样做是因为 Azuki ERC721a 的所有 tokenID 都是从 0 开始,逐个增长)。<br>
<strong>Storage 存储空间的优化</strong><br>
在 @openzeppelin/ERC721Enumerable 实现中,为了方便读取 NFT 的所有者信息,做了许多冗余的元数据存储,作为代价,在 mint 函数内,则需要额外的开销来存储这些信息。而 ERC721A 实现则相反,将所占的必须存储压缩到了最小,这样虽然增加了读取操作的复杂度,但是,读取是免费的。<br>
@openzeppelin/ERC721Enumerable 中所用的存储:</p>
<pre><code class="language-abstract"> //存储钱包地址==>(tokenIDindex==>tokenID)
mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
//保存了该 NFT ID 到用户拥有索引的映射,如_ownedTokensIndex[201] = 0 表示 ID 为 201 的该 NFT 是所属用户的拥有列表中的第一个。
mapping(uint256 => uint256) private _ownedTokensIndex;
//表示了所以被 mint 出来的该 NFT 的 ID 列表。
uint256[] private _allTokens;
//表示了具体某个 ID 的 NFT 在 _allTokens 列表中的位置
mapping(uint256 => uint256) private _allTokensIndex;
// ...
}
</code></pre>
<p>而在 ERC721A 的实现中,去除了那两个冗余索引:</p>
<pre><code class="language-contract"> Context,
ERC165,
IERC721,
IERC721Metadata,
IERC721Enumerable
{
struct TokenOwnership {
address addr;
uint64 startTimestamp;
}
struct AddressData {
uint128 balance;
uint128 numberMinted;
}
//ID => 钱包地址
mapping(uint256 => TokenOwnership) private _ownerships;
//钱包地址 => 所有数量
mapping(address => AddressData) private _addressData;
// ...
}
</code></pre>
<p><strong>优化 2 - 每个批次铸币请求更新所有者的余额一次,而不是每个铸币 NFT</strong><br>
假设 Alice 有 2 个代币并想再购买 5 个。在 Solidity 中,更新储值需要消耗 gas。因此,如果我们在存储中跟踪 Alice 拥有多少代币,那么通过一次更新将 Alice 的持有量从 2 直接更新到 7 会更便宜,而不是将该值更新 5 次(在OZ中需要逐个mint,并且每个额外的代币增加一次,从 2 到 3、3到 4 等)。<br>
虽然这是一个相对简单的概念,但 NFT 领域的绝大多数批量铸币厂还没有采用这一点,因为 OZ 默认实现不包括批量铸币厂 API,并且很容易在不调整的情况下从现成的现有解决方案中获取它. 我们强烈建议所有支持批量铸币的项目都考虑这个技巧。<br>
Azuki ERC-721A mint function:</p>
<pre><code class="language-function"> address to,
uint256 quantity,
bytes memory _data
) internal {
uint256 startTokenId = currentIndex;
...
AddressData memory addressData = _addressData[to];
_addressData[to] = AddressData(
addressData.balance + uint128(quantity),
addressData.numberMinted + uint128(quantity)
);
_ownerships[startTokenId] = TokenOwnership(to, uint64(block.timestamp));
uint256 updatedIndex = startTokenId;
for (uint256 i = 0; i < quantity; i++) {
emit Transfer(address(0), to, updatedIndex);
require(
_checkOnERC721Received(address(0), to, updatedIndex, _data),
"ERC721A: transfer to non ERC721Receiver implementer"
);
updatedIndex++;
}
currentIndex = updatedIndex;
_afterTokenTransfers(address(0), to, startTokenId, quantity);
}
</code></pre>
<p>这样一来,ERC721A 做到了就把对 storage 的写入从 O(N) 优化到了 O(1) 。单次 mint 的数量越多,优化效果则越明显。<br>
<strong>优化 3 - 每个批次铸币请求更新一次所有者数据,而不是每个铸币 NFT</strong><br>
这在精神上类似于优化 2。假设 Alice 想购买 3 个代币 - 代币 #100、#101 和 #102。与其将 Alice 保存为所有者 3 次(每次都要花费我们的 gas),我们可以改为只保存一次所有者值,这种方式在语义上意味着 Alice 拥有所有 3 个令牌。<br>
如何?假设 Alice 铸造代币 #100、#101 和 #102,而 Bob 铸造代币 #103 和 #104。内部所有者跟踪器如下所示:<br>
<img src="https://s2.loli.net/2022/05/12/ZTHS6cOinkMuaNx.png" alt="token队列.png" loading="lazy"><br>
这里的关键是,如果我们想查看谁拥有 #102,我们实际上不需要将 Alice 明确设置为 #102 的明确所有者。我们可以更改 ownerOf 函数来执行以下操作:<br>
<img src="https://s2.loli.net/2022/05/12/eFEXUnH49qN3WJG.png" alt="OwnerOf.png" loading="lazy"><br>
关键见解:如果我们将其实现更改为递减,直到它找到明确的所有者集,ownerOf 仍然按预期工作。<br>
虽然如果代币不是 HODL 的话,这些延迟的所有者写入可能仍会在代币生命周期的后期发生,但我们仍然期望从整体上节省大量净成本,因为这可以减少铸币厂的 gas 消耗,从而降低集中 gas 的严重性薄荷时间整个生态系统的峰值。这种优化涉及一些额外的逻辑,特别是在传输方面,</p>
<h1 id="erc-721a存在的问题">ERC-721A存在的问题</h1>
<p>因为Azuki ERC-721A中TokenID必须要保持连续性为基础,所以目前Azuki ERC-721A并没有提供 burn Token 功能。所以要使用类似销毁NFT的功能,Azuki ERC-721A则不试用。如果你的需求是TokenID是随机数,那么Azuki ERC-721A也不适用。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[多签钱包]]></title>
<id>https://tangminjie.github.io/post/duo-qian-qian-bao/</id>
<link href="https://tangminjie.github.io/post/duo-qian-qian-bao/">
</link>
<updated>2022-05-10T09:33:35.000Z</updated>
<content type="html"><![CDATA[<p>多签钱包最大的特点是需由多个私钥持有者的授权才能进行钱包交易。通常情况下,多签钱包在创建时便需确认好“m-n模式”,即创建总计n个私钥,并通过这n个私钥计算生成一个钱包地址,只有这n个私钥中的m个持有者共同签名授权才能完成对该钱包地址所对应的加密货币的相关操作。2-3是多签钱包中最常见的运作模式,即每次交易都需全部3个私钥中的2个进行签名授权才能完成,能够较好地平衡安全性与便捷性之间的关系。<br>
<strong>多签钱包得优势:</strong><br>
在单签钱包中,决定加密货币所有权和管理权的私钥仅掌握在单人手中,一旦私钥丢失或持有者遗忘钱包助记词,那就意味着持有者失去了对该钱包地址的控制权,与其相关联的加密资产将完全丢失。<br>
而多签钱包的存在,最大程度降低了单个私钥丢失时的资产损失风险。以2-3模式为例,在全部3个私钥中,只要有2个私钥完成了签名授权操作就能进行相关加密货币的交易。即使有1个私钥丢失,还能通过剩下的2个私钥完成对资产的转移,避免资产损失。<br>
多签钱包的地址是由多个私钥通过计算生成的,这一机制相比单签钱包要复杂得多。因此,对比单签钱包,黑客要想攻击钱包地址并盗取钱包内的加密货币资产,就得破解多个私钥,这一难度是成倍增长的。多签的机制保障了钱包本身在面对外部攻击时的安全性。</p>
<h1 id="ownbit-和-gnosis">Ownbit 和 Gnosis</h1>
<p>Ownbit 和 Gnosis 是市场上用户量相对最多得两个多签钱包。<br>
Ownbit 和 Gnosis 均通过合约账户实现以太坊多签,但是其实现的逻辑却迥然不同。分别代表了当前两种主流的实现方式,我们通过合约源码来讲解实现原理和各自的优缺点。</p>
<h2 id="gnosis-实现多签逻辑">Gnosis 实现多签逻辑</h2>
<p>Gnosis 实现多签逻辑的过程如下:<br>
1.任意一方通过 submitTransaction 方法提交交易,得到一个交易号(transactionId,该交易号并非我们常见的交易哈希,而是一个自增长的 uint256):</p>
<pre><code class="language-function"> public
returns (uint transactionId)
{
transactionId = addTransaction(destination, value, data);
confirmTransaction(transactionId);
}
</code></pre>
<p>2.其他参与方提交 ETH 交易,调用合约的 confirmTransaction 方法,来表示他们对某个交易执行的认可:</p>
<pre><code class="language-function"> public
ownerExists(msg.sender)
transactionExists(transactionId)
notConfirmed(transactionId, msg.sender)
{
confirmations[transactionId][msg.sender] = true;
Confirmation(msg.sender, transactionId);
executeTransaction(transactionId);
}
</code></pre>
<p>3.当 confirm 的人数达到最低(_required)要求,executeTransaction 的内部逻辑将被触发,从而执行第一步用户所提交的逻辑(value 和 data):</p>
<pre><code class="language-function"> public
notExecuted(transactionId)
{
if (isConfirmed(transactionId)) {
Transaction tx = transactions[transactionId];
tx.executed = true;
if (tx.destination.call.value(tx.value)(tx.data))
Execution(transactionId);
else {
ExecutionFailure(transactionId);
tx.executed = false;
}
}
}
</code></pre>
<h2 id="ownbit-实现多签逻辑">Ownbit 实现多签逻辑</h2>
<p>Ownbit 实现多签的逻辑和 Gnosis 不同。可以认为 Gnosis 的实现逻辑为线上方式,而 Ownbit 的实现逻辑为线下方式。<br>
1.相关参与方(满足 _required 个数)线下对即将执行的交易进行签名(所谓线下,即这个过程不需要向以太坊发送交易),生成签名结果(r、v、s):</p>
<pre><code class="language-function"> //the sequence should match generateMultiSigV2 in JS
bytes32 message = keccak256(abi.encodePacked(address(this), erc20Contract, destination, value, spendNonce));
return message;
}
</code></pre>
<p>参与签名的参数有:多签合约地址、Erc20代币合约地址(对于转移 ether 使用 0x0)、转移的目标地址、金额、控制重放的合约内部 spendNonce。<br>
对以上参数签名,表示参与方同意对指定合约转移指定金额。<br>
2.任意一方(甚至可以是多签参与方以外的其他人)发送 ETH 交易,调用合约的 spend 或 spendERC20 方法,并将以上签名结果作为参数传入:</p>
<pre><code class="language-function"> require(destination != address(this), "Not allow sending to yourself");
//transfer erc20 token
//uint256 tokenValue = Erc20(erc20contract).balanceOf(address(this));
require(value > 0, "Erc20 spend value invalid");
require(_validSignature(erc20contract, destination, value, vs, rs, ss), "invalid signatures");
spendNonce = spendNonce + 1;
// transfer tokens from this contract to the destination address
Erc20(erc20contract).transfer(destination, value);
emit SpentERC20(erc20contract, destination, value);
}
</code></pre>
<p>_validSignature 将对签名的有效性进行验证。验证通过的情况下,相关转币逻辑即被执行。<br>
以上便完成了 Ownbit 多签合约的调用。Ownbit 将不同目的分解到不同的方法中,例如:spend 进行 ether 转移,spendERC20 进行 Erc20 代币转移,spendAny 进行任意功能的调用。</p>
<h2 id="两种方式的优缺点">两种方式的优缺点</h2>
<p>以上两种实现 ETH 多签的不同方式,具有很好的代表性。这也是目前实现 ETH 多签最常用的两种手段。<br>
<strong>Gnosis 方式的优点:</strong><br>
1.采用发送交易来表示参与方同意某个花费或调用,避免了复杂的签名计算;<br>
2.全程线上,具有更好的审计性(参与方的 Reject 的态度也保留在区块链上);<br>
<strong>Gnosis 方式的主要缺点:</strong><br>
1.每个参与方都需向线上发送交易,多次花费手续费,不经济;<br>
2.每个参与方所花费的手续费不均等,使 confirm 人数刚好等于 _required 的交易将花费更大的手续费以执行 executeTransaction 内部逻辑;<br>
3.交易逻辑隐藏在 data 里,可欺骗性大;<br>
<strong>Gnosis 方法的优点正是 Ownbit 方法的缺点,Gnosis 方法的缺点也是 Ownbit 方法的优点。总体而言,Ownbit 的方法因为其经济性,使用得更多。</strong></p>
<h1 id="基于solidity0813实现gnosis合约逻辑">基于solidity0.8.13实现Gnosis合约逻辑</h1>
<p>github:<br>
https://github.com/tangminjie/learnblockchain/tree/main/Multi-Sig-Wallet</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[学习资料]]></title>
<id>https://tangminjie.github.io/post/xue-xi-zi-liao/</id>
<link href="https://tangminjie.github.io/post/xue-xi-zi-liao/">
</link>
<updated>2022-05-06T06:25:10.000Z</updated>
<content type="html"><![CDATA[<p><strong>solidity实例</strong><br>
https://solidity-by-example.org/</p>
<p><strong>learnWeb3官网</strong><br>
https://www.learnweb3.io/</p>
<p><strong>Dapp-learning-dao</strong><br>
学习组织很全面的学习资料以及项目路径<br>
https://github.com/Dapp-Learning-DAO</p>
<p><strong>登链社区</strong><br>
中文社区,比较全面的中文文档<br>
https://learnblockchain.cn</p>
<p><strong>登链集训营资料</strong><br>
https://learnblockchain.cn/video/play/300<br>
https://learnblockchain.cn/video/play/349</p>
<p><strong>mirror搜索</strong><br>
https://mirrorbeats.xyz/</p>
<p><strong>freecodecamp视频课</strong><br>
https://www.freecodecamp.org/news/learn-solidity-blockchain-and-smart-contracts-in-a-free/</p>
<p><strong>Secureum 训练营</strong><br>
https://ventral.digital/posts/2021/10/30/secureum-bootcamp-solidity-201-quiz</p>
<p><strong>合约升级相关</strong><br>
https://blog.openzeppelin.com/the-state-of-smart-contract-upgrades/</p>
<p><strong>DeFi Hacks Analysis - Root Cause黑客攻击分析</strong><br>
https://wooded-meter-1d8.notion.site/0e85e02c5ed34df3855ea9f3ca40f53b?v=22e5e2c506ef4caeb40b4f78e23517ee</p>
<p><strong>最快速的编程语言学习网站</strong><br>
https://learnxinyminutes.com/</p>
<p><strong>MEV学习文章</strong><br>
https://amberlabs.substack.com/p/extractable-value</p>
<p><strong>各大高校计算机课程汇总</strong><br>
https://studyhard.cf/</p>
<p>** layer3 跟新比较快的教学类项目**<br>
https://layer3.xyz/</p>
<p>** 项目相关信息**<br>
https://defieye.io/<br>
biteyer 专注于L1/L2、DeFi、NFT和Web3 的区块链研究机构和社区。</p>
<p>** solidity博客 **<br>
https://paco0x.org/</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[AMM原理,滑点,和无偿损失]]></title>
<id>https://tangminjie.github.io/post/amm-yuan-li-hua-dian-he-wu-chang-sun-shi/</id>
<link href="https://tangminjie.github.io/post/amm-yuan-li-hua-dian-he-wu-chang-sun-shi/">
</link>
<updated>2022-04-29T08:47:02.000Z</updated>
<content type="html"><![CDATA[<h1 id="amm自动化做市商">AMM自动化做市商</h1>
<p>DeFi领域去中心化交易所DEX能够崛起的一个核心原因是引入了自动做市商(AMM)模式。AMM又称自动化做市商,它是去中心化交易所(DEX)最为关键的技术之一,已被证明是最具影响力的DeFi创新之一,它们能够为一系列不同代币创建和运行可公开获取的链上流动性。<br>
任何市场都可能存在没有足够的有机流动性以支持活跃的交易的状况,做市商本质上就是通过促进这些市场中不会发生的交易来缓解这一问题的代理商。在传统的CEX里许多做市商都是专业的团队或是机构。而AMM(Automated Market Maker),相当于把他们这个角色给真正的去中心化了。<br>
每个用户都可以把自己的代币扔到流动池里,成为一个小的做市商,然后享受交易对手续费分红。且流动池资金是去中心化开源合约控制,AMM交易数据全部上链,不像传统CEX的平台币销毁或是分红,毕竟没有人知道他们手续费真的挣了多少,平台币流通了多少等等。而在AMM这里,一切透明。更重要的是,你的资产依旧在你个人控制的钱包里,而不是进了交易平台,所以资产依旧100%安全,这是传统CEX无论如何不可能实现的。</p>
<h2 id="amm实现原理">AMM实现原理</h2>
<p>AMM通过数学公式对资产进行定价。资产定价通过定价算法完成,不再使用传统交易平台常用的订单簿。定价公式随不同协议而变化。例如,Uniswap使用的公式为x * y= k,其中x代表流动性资金池中一种代币的数量,y则表示另一种代币的数量。在此公式中,k是一个固定常数,表明池中的流动性总量必须保持不变。<br>
我们假设UNISWAP一个交易池中是ETH-DAI,第一个流动性提供者放进去了a个ETH和b个DAI。这时候这个交易对,对应的初始值是 x=a, y=b;那K的初始值=a*b;<br>
此时ETH的价格就是b/a,DAI的价格=a/b;在K不变的情况下,如果这个交易池有trader T进来,想用w个EHT换DAI。他会以什么样的价格拿到多少DAI呢?<br>
在恒定乘积下,这个运算过程是这样的:<br>
<code>y’=K/(x+w)</code><br>
其中y’等于此次交易后,交易池中的DAI的个数。由此可知,T得到的DAI的个数=y-y’;这些DAI的价格=w/(y-y’).<br>
所以你发现了吗?在交易者告诉平台自己要兑换的数量之前,他能拿到什么价格是不确定的。这就让UNISWAP的价格获取跟中心化交易所和利用价格预言机的交易所完全区分开了。<br>
还有别忘了,我们假定的前提条件是K不变,那哪些情况下K值是要改变的?答案有二,一是交易费,二是流动性。<br>
<strong>交易费</strong><br>
用户每一次的交易,需要交0.3%的手续费。拿上面交易举例,我们为了简化计算忽略了手续费,真实的情况是uniswap平台在计算出y’后会扣除0.3%y’的手续费,完成交易后,这0.3%dy会被添加到流动性池里,此时K值就变成了 xy+x0.3%y’=x*(y+0.3%y).<br>
所以,你发现了吗?K值变大了。反之亦然,减少流动性,会减少K值。换言之,恒定乘积算法的K值并不恒定,每一笔交易都会影响K值。<br>
<strong>流动性</strong><br>
流动性是怎么改变K值的呢?当第二个人在一个交易对中按照比例增加了ETH和DAI的数量,X和Y就同时增加了,很显然,K的值就是增加的;如果第一个人取走自己的交易对,K值就减少,非常简单。</p>
<h2 id="滑点">滑点</h2>
<p>滑点是指成交价与下单时的预期价不同,通常是由于从下单到成交时的价格变动造成的。当我们在dex上交易的时候可以手动设置滑点。当交易价格超过我们可以接受的滑点范围时交易会失败。<br>
<strong>怎样才能避免滑点</strong><br>
1.我们无法完全避免滑点,但可以减少滑点带来的风险,将损失降到最低。首先,只在具有良好流动性和完善基础设施的平台上进行交易。最好的办法是选择高交易量、高流动性的钱币。<br>
2.可以选择更容易成交的小额交易,而不是一下子下一笔大单。如果你下的单超过了现有的流动性下DEX是无法撮合交易的。如果把大单分解成几个小单,就更有可能成交,不会有流动性不足的情况。</p>
<h1 id="无偿损失">无偿损失</h1>
<p>在DEFI协议中我们可以组LP来为流动池提供流动性,以收取挖矿收益或者手续费收益。例如UNISWAP中的0.3%的手续费会按LP比例来分配收益。因提供流动性需要将两种代币质押到LP池子,如果两个代币的价格发生波动,根据恒定乘积公式 X*Y=K,质押的代币数量会发生变化,按U本位计价在比价波动前后,会承担额外的损失,这部分额外损失就是我们常说的无常损失。</p>
<pre><code>以ETH/USDT这个交易对为例,在ETH价格为$500时,我们可以提供这样的资金:
ETH: 20枚
USDT: $10,000
总值: $20,000 这是池中的所有的资金。
x*x*eth单价=20000
如果ETH的外部价格上涨到$550,就会有投机者来这里买入便宜的ETH,并把价格推高到$550。
按 x * y = k 的Uniswap自动做市商公式:
20 * 10,000 = 200,000 (ETH=$500)
19.07 * 10488 = 200,000(ETH=$550)
投机者可以用$488来买走0.93ETH,从而把价格推高到跟市场一致。而他的成本是:ETH=$524.7,有一定的套利空间。
在新的状态下(ETH=$550),对于LP来说
ETH:19.07枚
USDT:$10488
总值:$20976
但是,如果我们这一对ETH/USDT资金没有来放入流动性池,情况是:
总值:$21000
因此,因为ETH上涨,我们损失了$24。这就是无常损失。
如果ETH价格跌到了$400,情况会怎么样呢?
按 x * y = k 的Uniswap自动做市商公式:
20 * 10,000 = 200,000 (ETH=$500)
22.36 * 8944 = 200,000(ETH=$400)
在新的状态下(ETH=$400),对于LP来说
ETH:22.36枚
USDT:$8944
总值:$17888
但是,如果我们这一对ETH/USDT资金没有来放入流动性池,情况是:
总值:$18000
因此,因为ETH下跌,我们损失了$112。也就是说,价格下跌,我们也会遭遇无常损失。唯有价格不动,LP才不会有无常损失。
</code></pre>
<p>当我们把一对ETH/USDT存入流动性池子中,变化是:<br>
● 当ETH价格上涨时,系统实际上帮我们自动卖出ETH。<br>
● 当ETH价格下跌时,系统实际上帮我们自动买入ETH。<br>
因此,我们的第一选择是做稳定币池的LP,因为它们之间价格几乎没有波动,其次是做ETH/BTC的LP,因为二者之间往往是同向的,比整体市场波动小。<br>
总的来说,流动性提供者承受了无常损失,提供了流动性服务,并因无常损失而获得了相应的回报</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[委托调用与合约升级-1]]></title>
<id>https://tangminjie.github.io/post/wei-tuo-diao-yong-yu-he-yue-sheng-ji-1/</id>
<link href="https://tangminjie.github.io/post/wei-tuo-diao-yong-yu-he-yue-sheng-ji-1/">
</link>
<updated>2022-04-26T09:13:58.000Z</updated>
<content type="html"><![CDATA[<p>在传统的WEB2开发中我们习惯对产品进行升级迭代,这也是必要的,因为在开发中我们要经常进行BUG的修复和新特性的更新。但是在智能合约中,我们部署上去的合约数据一旦上链就无法隐藏和更改。这也是以太坊去中心化的核心。<br>
但是随之而来的问题也很明显,当合约部署到链上之后,就再也无法改变源码。 当部署的合约存在bug或者安全漏洞时(合约审计并没有十分完善的代码审核)。在早期就只能重新部署合约,或者引入一个新的合约并且将数据迁移。这都会改变合约的地址,对于社区或者投资人和项目本身都是不利的。<br>
所以后来引入了合约升级方案。</p>
<h1 id="委托调用">委托调用</h1>
<p>在合约升级之前我们必须要知道的一个概念就是委托调用。这也是合约升级能够实现的一个重要技术点。<br>
在编写 Ethereum SmartContract 代码时,在某些情况下我们需要与其他合约进行交互。在Solidity 中,为此目的,有几种方法可以实现这一目标。<br>
** 1.如果我们知道目标合约 ABI,我们可以直接使用函数签名 **<br>
假设我们部署了一个名为“Storage”的简单合约,允许用户保存一个值。</p>
<pre><code class="language-pragma">contract Storage {
uint public val;
constructor(uint v) public {
val = v;
}
function setValue(uint v) public {
val = v;
}
}
</code></pre>
<p>我们要部署另一个名为“Machine”的合约,它是“Storage”合约的调用者。“Machine”引用“Storage”合约并更改其值。</p>
<pre><code class="language-pragma">import "./Storage.sol";
contract Machine {
Storage public s;
constructor(Storage addr) public {
s = addr;
calculateResult = 0;
}
function saveValue(uint x) public returns (bool) {
s.setValue(x);
return true;
}
function getValue() public view returns (uint) {
return s.val();
}
}
</code></pre>
<p>在这种情况下,我们知道“Storage”的ABI及其地址,这样我们就可以用该地址初始化现有的“Storage”合约,ABI告诉我们如何调用“Storage”合约的函数。我们可以看到“Machine”合约调用“Storage”setValue()功能。<br>
并编写测试代码检查“Machine”是否saveValue()真的调用了“Storage”setValue()函数并改变其状态。</p>
<pre><code class="language-const">const MachineFactory = artifacts.require('Machine');
contract('Machine', accounts => {
const [owner, ...others] = accounts;
beforeEach(async () => {
Storage = await StorageFactory.new(new BN('0'));
Machine = await MachineFactory.new(Storage.address);
});
describe('#saveValue()', () => {
it('should successfully save value', async () => {
await Machine.saveValue(new BN('54'));
(await Storage.val()).should.be.bignumber.equal(new BN('54'));
});
});
});
</code></pre>
<p>查看测试结果:</p>
<pre><code class="language-Contract:"> After initalize
#saveValue()
✓ should successfully save value (56ms)
1 passing (56ms)
</code></pre>
<p>** 2.如果我们不知道目标合约 ABI,请使用 call 或 delegatecall **<br>
在解释以太坊 Solidity中call()和delegatecall()之前,先看看 EVM 如何保存合约的变量。<br>
** EVM 如何将字段变量保存到 Storage **<br>
在以太坊中,保存合约字段变量的空间有两种。一个是“memory”,另一个是“storage”。storage声明的数据会永久的储存在区块链上。<br>
那么单个合约中的这么多变量怎么可能不重叠彼此的地址空间呢?EVM 将槽号分配给字段变量。</p>
<pre><code class="language-contract"> uint256 first; // slot 0
uint256 second; // slot 1
}
</code></pre>
<p>因为first首先在“Sample1”中声明,所以它被分配了 0 个插槽。每个不同的变量由它的槽号来区分。<br>
在 EVM 中,它在智能合约存储中有 2²⁵⁶ slot,每个 slot 可以保存 32 字节大小的数据。<br>
** call和delegatecall的区别 **<br>
call委托调用是用户地址调用代理合约去调用业务合约,修改的是被调用者的storage,并且被调用合约的msg.sender是代理合约。<br>
delegatecall 是修改的代理合约本身的storage,并且被调用合约的msg。sender是用户地址。<br>
可以理解成delegatecall只是使用了业务合约的接口,所有的数据状态变化全都是代理合约本身。<br>
** 测试用例 **<br>
注意:在使用delegatecall的时候要十分注意调用者合约和委托调用合约之间变量字段的顺序,这涉及到合约的插槽顺序,我们用一个例子来说明:<br>
首先编码一个调用者合约:</p>
<pre><code class="language-pragma">import "./Storage.sol";
contract Machine {
Storage public s;
uint256 public calculateResult;
address public user;
event AddedValuesByDelegateCall(uint256 a, uint256 b, bool success);
event AddedValuesByCall(uint256 a, uint256 b, bool success);
constructor(Storage addr) public {
...
calculateResult = 0;
}
...
function addValuesWithDelegateCall(address calculator, uint256 a, uint256 b) public returns (uint256) {
(bool success, bytes memory result) = calculator.delegatecall(abi.encodeWithSignature("add(uint256,uint256)", a, b));
emit AddedValuesByDelegateCall(a, b, success);
return abi.decode(result, (uint256));
}
function addValuesWithCall(address calculator, uint256 a, uint256 b) public returns (uint256) {
(bool success, bytes memory result) = calculator.call(abi.encodeWithSignature("add(uint256,uint256)", a, b));
emit AddedValuesByCall(a, b, success);
return abi.decode(result, (uint256));
}
}
</code></pre>
<p>委托调用合约:</p>
<pre><code class="language-pragma">contract Calculator {
uint256 public calculateResult;
address public user;
event Add(uint256 a, uint256 b);
function add(uint256 a, uint256 b) public returns (uint256) {
calculateResult = a + b;
assert(calculateResult >= a);
emit Add(a, b);
user = msg.sender;
return calculateResult;
}
}
</code></pre>
<p>测试脚本:</p>
<pre><code class="language-describe('#addValuesWithDelegateCall()',"> let Calculator;
beforeEach(async () => {
Calculator = await CalculatorFactory.new();
});
it('should successfully add values with delegate call', async () => {
const result = await Machine.addValuesWithDelegateCall(Calculator.address, new BN('1'), new BN('2'));
expectEvent.inLogs(result.logs, 'AddedValuesByDelegateCall', {
a: new BN('1'),
b: new BN('2'),
success: true,
});
(result.receipt.from).should.be.equal(owner.toString().toLowerCase());
(result.receipt.to).should.be.equal(Machine.address.toString().toLowerCase());
// Calculator storage DOES NOT CHANGE!
(await Calculator.calculateResult()).should.be.bignumber.equal(new BN('0'));
// Only calculateResult in Machine contract should be changed
(await Machine.calculateResult()).should.be.bignumber.equal(new BN('3'));
(await Machine.user()).should.be.equal(owner);
(await Calculator.user()).should.be.equal(constants.ZERO_ADDRESS);
});
});
</code></pre>
<p>我们想要测试的是:<br>
1.因为上下文在“Calculator”而不是“Machine”上,所以添加结果应该保存到“Calculator”存储中。<br>
2.Calculator calculateResult应该是0,user.address应该是0地址。<br>
3.Machine calculateResult应该是3,user是EOA。<br>
但是允许TEST的结果是我们失败了:</p>
<pre><code class="language-0">1 failing
1) Contract: Machine
After initalize
#addValuesWithDelegateCall()
should successfully add values with delegate call:
AssertionError: expected '562046206989085878832492993516240920558397288279' to equal '3'
+ expected - actual
-562046206989085878832492993516240920558397288279
+3
```
正如我们之前提到的,每个字段变量都有自己的插槽。而当我们委托调用“Calculator”时,上下文在“Machine”上,但槽号基于“Calculator”。因此,因为“计算器”逻辑用 覆盖地址Storage,calculateResult所以测试失败。
基于这些知识,我们可以找到“562046206989085878832492993516240920558397288279”的来源。它是 EOA 的十进制版本。
所以要解决这个问题,我们需要改变“Machine”字段变量的顺序。使它和“Calculator”插槽对应:
``` uint256 public calculateResult;
address public user;
Storage public s;
</code></pre>
<p>总结:<br>
如果我们知道目标函数的ABI,我们可以直接使用目标函数签名<br>
如果我们不知道目标函数的 ABI,我们可以使用call(), 或delegatecall(). 但是在 的情况下delegatecall(),我们需要关心字段变量的顺序。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[委托调用与合约升级-2]]></title>
<id>https://tangminjie.github.io/post/wei-tuo-diao-yong-yu-he-yue-sheng-ji-2/</id>
<link href="https://tangminjie.github.io/post/wei-tuo-diao-yong-yu-he-yue-sheng-ji-2/">
</link>
<updated>2022-04-26T09:13:58.000Z</updated>
<content type="html"><![CDATA[<h1 id="合约升级">合约升级</h1>
<h2 id="合约升级的三种模式">合约升级的三种模式:</h2>
<p>在openzeppelin-labs中提出了三种合约代理升级得方案:<br>
1.继承存储模式 Inherited Storage<br>
2.永久存储模式 Eternal Storage<br>
3.非结构化存储模式 Unstructured Storage<br>
在 https://github.com/OpenZeppelin/openzeppelin-labs.git 中有三种升级方式实验室版本得实现demo,在openzeppelin-constract正式版中提供了Proxy库来实现代理升级得架构,使用得是非结构化存储模式,所以我们重点学习整个模式。<br>
<strong>非结构化存储模式 Unstructured Storage</strong><br>
非结构化存储模式类似继承存储模式,但并不需要目标合约继承与升级相关的任何状态变量。此模式使用代理合约中定义的非结构化存储插槽来保存升级所需的数据。<br>
在代理合约中,我们定义了一个常量变量,在对它进行Hash时,应提供足够随机的存储位置来存储代理合约调用逻辑合约的地址。</p>
<pre><code class="language-bytes32">keccak256("org.zeppelinos.proxy.implementation");
</code></pre>
<p>在erc-1967中规定了存储插槽得随机位置算法。由于常量不会占用存储插槽,因此不必担心implementationPosition被目标合约意外覆盖。由于Solidity状态变量存储的规定,目标合约中定义的其他内容使用此存储插槽冲突的可能性极小。<br>
通过这种模式,逻辑合约不需要知道代理合约的存储结构,但是所有未来的逻辑合约都必须继承其初始版本定义的存储变量。就像在继承存储模式中一样,将来升级的目标合约可以升级现有功能以及引入新功能和新存储变量。<br>
并且Zeppelin在实现这种存储代理模式时,引入了代理所有权的概念。只有代理所有者有权将新版本合约写入代理合约中,或者将所有权进行移交。</p>
<h1 id="openzeppelin的合约升级方式">Openzeppelin的合约升级方式</h1>
<p><strong>ERC-1967</strong><br>
ERC-1967标准化了代理委托的逻辑合约的地址的存储插槽地址以及其他特定于代理的信息的位置<br>
1.逻辑合约地址:<br>
存储槽0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc (bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1))<br>
2.信标合约地址<br>
存储槽0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50(bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1))<br>
3.管理员地址<br>
存储槽0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 (bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1))</p>
<h2 id="proxy底层代码实现升级">Proxy底层代码实现升级</h2>
<p>openzeppenlin proxy库实现了非结构化存储代理合约的底层实现。主要功能集中在以下几个合约文件中:<br>
1.Proxy: 实现核心委托功能的抽象合约。<br>
2.ERC1967Upgrade:获取和设置 EIP1967 中定义的存储槽的内部函数<br>
3.ERC1967Proxy:使用 EIP1967 存储槽的代理。默认不可升级。<br>
4.TransparentUpgradeableProxy:具有内置管理和升级界面的代理。<br>
5.UUPSUpgradeable:将包含在实施合同中的可升级机制。<br>
6.BeaconProxy:从信标合约中检索其实现的代理。<br>
7.UpgradeableBeacon:带有内置管理员的信标合约,可以升级BeaconProxy指向它的指向。<br>
在实际使用proxy升级时我们需要部署三个合约地址:<br>
1.业务合约 Params 部署(先不进行初始化,initialize,本方法对应的 code 为 0x8129fc1c )<br>
2.ProxyAdmin 管理合约部署,代理合约的管理员<br>
3.TransparentUpgradeableProxy 代理合约,此为用户直接交互的合约地址,一直不变;<br>
合约升级:<br>
1.逻辑合约 Params 升级为 ParamsNew;<br>
2.调用 ProxyAdmin 进行升级;</p>
<p>ProxyAdmin 提供两个方法进行升级:<br>
1.upgrade,需要传入 proxy 地址,新的逻辑实现地址;<br>
2.upgradeAndCall,需要传入 proxy 地址,新的逻辑实现地址,初始化调用数据</p>
<pre><code class="language-//">proxyAdminContract = await proxyAdminContractFactory.deploy();
await proxyAdminContract.deployed();
console.log("ProxyAdmin contract address: ", proxyAdminContract.address)
// Deploy TransparentUpgradeableProxy
let transparentUpgradeableProxyContractFactory = await ethers.getContractFactory(
'TransparentUpgradeableProxy'
);
transparentUpgradeableProxyContract = await transparentUpgradeableProxyContractFactory.deploy(params.address, proxyAdminContract.address,"0x8129fc1c" );
await transparentUpgradeableProxyContract.deployed();
let paramsNewContractFactory = await ethers.getContractFactory('ParamsNew');
paramsNew = await paramsNewContractFactory.deploy();
await paramsNew.deployed();
//update constract
await proxyAdminContract.upgrade(transparentUpgradeableProxyContract.address,paramsNew.address );
</code></pre>
<p><strong>ProxyAdmin.sol</strong><br>
管理合约,主要实现了代理合约的管理者地址的设置和获取,检查。<br>
还对外提供两个升级接口:</p>
<pre><code class="language-/**"> * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
*/
function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
proxy.upgradeTo(implementation);
}
/**
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
* {TransparentUpgradeableProxy-upgradeToAndCall}.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
*/
function upgradeAndCall(
TransparentUpgradeableProxy proxy,
address implementation,
bytes memory data
) public payable virtual onlyOwner {
proxy.upgradeToAndCall{value: msg.value}(implementation, data);
}
```
**TransparentUpgradeableProxy.sol**
代理合约,为用户交互的地址,一直不变,继承了ERC1967Proxy。在构造函数中实现了ERC1967的初始化和管理者的修改:
``` constructor(
address _logic,
address admin_,
bytes memory _data