Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

如何实现可升级的智能合约?

用香烟做的云
99 0 0
现如今,整个密码货币生态系统都是由智能合约所驱动!不管我们有多小心,或者我们的代码测试工作做得有多好,如果我们创建的是一个复杂的系统,那我们就有必要更新合约逻辑,以修补其存在的漏洞,或者添加必要的缺失功能。有时候,由于EVM虚拟机的更改或者被新发现的漏洞,我们可能需要去升级我们的智能合约。% E9 G: ?& d' ?% J 一般来说,开发人员可以很容易地升级他们的软件,但区块链的情况是不一样的,因为它们有着难以更改的属性。如果我们部署了一个合约,这就好比是泼出去的水。然而,如果我们使用适当的技术,我们可以在不同的地址部署一个新的合约,并使得旧合约无效。下面是一些最常见的,创建可升级智能合约的方法。" G/ a# [2 T7 i* R) g1 f2 } / P% f% L; a5 o9 u5 s2 Q ' p' @6 v. |( o% r: h; `- F主从合约(Master-Slave contract) ( |9 ~# @5 d! t+ j8 G5 q; d2 N 7 }( d4 F& ~- s) y: p: V* o+ Z主从技术,是可实现升级智能合约最为基础也是最容易理解的技术之一。在这种技术当中,我们部署一个主合约,以及其他合约,其中主合约负责存储所有其他合约的地址,并在需要时返回所需的地址。当这些合约需要和其它合约进行沟通时,它们会充当从合约,从主合约那里获取其它合约的最新地址。为了升级智能合约,我们只需要在网络上部署它,并更改主合约中的地址。虽然这远不是发展可升级智能合约的最佳方式,但它确是最简单的。这种方法存在着很多的局限性,其中之一是,我们不能轻易地把合约的数据或资产迁移到新合约中。 . b' O$ Y- W9 b B0 L 1 U2 M9 E9 t4 r# o6 o" y F/ j 永久存储合约(Eternal Storage contract5 i. t+ b1 |% v# m3 r / w3 K7 }2 J& P8 Z5 ~ 在这种技术当中,我们将逻辑合约和数据合约彼此分离。数据合约应该是永久并且不可升级的。而逻辑合约可以根据需要进行多次升级,并将变化通知给数据合约。这是一项相当基本的技术,并且存在着一个明显的缺陷。由于数据合约是不可升级的,数据结构中需要的任何更改,或数据合约中存在的漏洞,都会导致所有数据变得无用。这种技术的另一个问题是,如果逻辑合约想要访问/操作区块链上的数据,那么这个逻辑合约将需要进行外部调用,而外部调用会消耗额外的gas。通常情况下,这种技术会和主从技术相结合,以促进合约间的通信。 j) o; O* B3 X# X1 Z$ ]3 b+ ]: } # [9 m( z8 {* m8 v: F可升级存储代理合约 9 M; k5 N! k) r$ S9 d2 s / s, z- ?& G6 {% P" H# \6 [ 我们可通过使永久存储合约充当逻辑合约的代理,以此防止支付额外的gas。这个代理合约,以及这个逻辑合约,将继承同一存储合约,那么它们的存储会在EVM虚拟机中对齐。这个代理合约将有一个回退函数,它将委托调用这个逻辑合约,那么这个逻辑合约就可以在代理存储中进行更改。这个代理合约将是永恒的。这节省了对存储合约多次调用所需的gas,不管数据做了多少的更改,就只需要一次委托调用。7 v1 ~4 q& F B* x2 x, H4 L2 j 这项技术当中有三个组成部分:# O9 i+ Q4 F. C$ q9 | 代理合约(Proxy contract):它将充当永久存储并负责委托调用逻辑合约;% G$ x6 l2 a( M# J! u W 逻辑合约(Logic contract):它负责完成处理所有的数据; & h3 w8 G9 @$ f% p r2 r存储结构(Storage structure):它包含了存储结构,并会由代理合约和逻辑合约所继承,以便它们的存储指针能够在区块链上保持同步; 9 H' _% W2 o+ ~; [, u6 _2 [ G, q, v# e: t# N- k & ?0 A' `) X% |! e. E* S . I, f. c& B4 i- I3 ?0 N9 y委托调用 3 P7 C7 V+ w) Z3 Z, k3 e ) X) E* F% {6 t. H5 _$ ]% c该技术的核心在于EVM所提供的DELEGATECALL操作码,DELEGATECALL就像是一个普通的CALL 调用操作码,不同之处在于目标地址上的代码是在调用合约上下文中执行的,而原始调用的msg.sender以及msg.value将被保留。简单说,DELEGATECALL基本上允许(委托)目标合约在调用合约的存储中做它任何想做的事情。7 F- ]1 [# R, s6 f4 Y* A, Q9 x 我们将利用这一点,并创建一个代理合约,它将使用DELEGATECALL操作码委托调用逻辑合约,这样我们就可以在代理合约中保持数据的安全,同时我们可以自由地更改逻辑合约。/ b9 B; s. q5 ^2 T/ K: \3 f . M, C0 K' t) m6 X$ a ( O0 Q# T" |) X' X; l5 Q& m 如何使用可升级存储代理合约# ]- R; w! }2 D6 H2 D4 X2 n ! d+ J/ z9 X! `' \) W 让我们深入研究一下细节。我们需要的第一个合约是存储结构。它将定义我们需要的所有存储变量,并将由代理合约和执行合约所继承。它看起来会是这样的:! r; T% y K5 h7 z+ E' o4 S
  1. <b>contract StorageStructure {8 e7 \3 W1 [( V3 U; p5 ?! j9 D5 Z
  2. address public implementation;( l# f) `: o. `
  3. address public owner; ) S$ S+ e$ l& R# a! @! h% G
  4. mapping (address => uint) internal points; 1 L) n$ x& L" f3 `, E
  5. uint internal totalPlayers; 8 ^# z% P2 Y! E' G, ^
  6. }</b>
复制代码
' |& t/ i/ }8 ?* C8 _# j) ~' J( t 9 q+ |' k( ]! D* O1 [ 我们现在需要一个执行/逻辑合约。让我们创建一个简单版的合约,在添加新玩家时不会增加totalPlayers计数器的数字。 % ^: b1 Z, L; r0 P2 P3 J; q8 j; H! `' z+ f( ?8 ]) h X" O
  1. contract ImplementationV1 is StorageStructure { 4 V( D- O: d2 U/ E8 v
  2. modifier onlyOwner() { 7 q4 G6 k7 p; b/ F G, ?* f
  3. require (msg.sender == owner);6 u; v$ I) r# o1 ?
  4. _; ! P( e- P2 V5 ]
  5. }( x8 g; z( m" E; W; ^
  6. function addPlayer(address _player, uint _points) 8 D. P5 o" I1 h' a
  7. public onlyOwner ; W& ~% b* V4 S+ P( m: c+ N& b- F
  8. {. G( t; P2 S9 @1 _
  9. require (points[_player] == 0);2 j4 S5 Q( P5 {
  10. points[_player] = _points; 6 h8 o4 i0 e9 Z7 G" L: ~* Z) |
  11. } # {- {$ D8 i% f" ]7 o0 i- V9 \6 ~
  12. function setPoints(address _player, uint _points) ! [' I6 T) M% ^0 P+ d8 `
  13. public onlyOwner; I$ z8 a3 \! e# B, L8 {+ x+ _
  14. { 6 Q/ b- r# ]) K3 S) P- ^$ f
  15. require (points[_player] != 0);0 F. I9 _8 F; w8 L$ @6 J# D
  16. points[_player] = _points;4 ]6 L8 b; |8 s) }4 R) N, h% p
  17. }( {: I5 k7 A* M$ p! x: M& B
  18. }
复制代码
: Z. H- p! Q: K( C( Q2 ?5 D$ E1 C3 z . r/ S' @" i! `6 K下面就是最关键的部分:代理合约;1 a2 d* _2 x) K6 R: \
  1. contract Proxy is StorageStructure { ' D; ^, S8 Y4 Q7 c/ H) Z
  2. e( a/ ]2 y) f4 ^; ^& j" e& k
  3. modifier onlyOwner() { 4 e$ K; h+ G2 K
  4. require (msg.sender == owner);! P4 j+ ~3 E, J
  5. _; 4 U ^/ ]/ X( o
  6. } 4 m# k/ q$ [* c2 \: p5 {
  7. /** + u3 b: `3 _- Q2 Z
  8. * @dev constructor that sets the owner address! B+ |8 o, ]- |% g& l
  9. */" v* J) v7 D7 q# Q
  10. constructor() public { 2 H- j! { D W1 S' K7 j
  11. owner = msg.sender;& }! @' ^3 K/ A2 T
  12. }8 x7 ^0 C2 m+ {7 X# }2 A
  13. /**7 G9 U) z8 A: ], k$ I
  14. * @dev Upgrades the implementation address 9 O! w* s6 V/ @2 p% F! r
  15. * @param _newImplementation address of the new implementation# D9 P6 X6 a h+ P: n" f
  16. */. \) G% P! Y2 d- A" x( s
  17. function upgradeTo(address _newImplementation)% O2 J* S/ n- A* n
  18. external onlyOwner . Z; Z0 F4 m/ ?
  19. { 1 D- N$ `8 B; V- g
  20. require(implementation != _newImplementation); / I* [$ X2 E% {" I# ?4 p! P! }: \
  21. _setImplementation(_newImplementation); 9 D. `4 u8 y6 W# p- V5 h
  22. }0 g( @9 P6 S* V( X/ U- @: b- ]5 e
  23. /** ; C3 E. G1 o( m9 ]
  24. * @dev Fallback function allowing to perform a delegatecall . O! ]2 m( a- X- n
  25. * to the given implementation. This function will return3 l# D' E& e3 R! f8 w0 g1 D
  26. * whatever the implementation call returns. v6 ?; a. g/ r \! u
  27. */6 R. m7 R$ ], t2 F% e
  28. function () payable public {2 O, v m* Y5 v4 j" H5 X/ S" `
  29. address impl = implementation; 9 [3 h7 t9 g$ t4 ^$ X
  30. require(impl != address(0)); & [ O* u/ {5 y( K/ H
  31. assembly { R# B7 v- I* g5 i! [- U! Y- ~2 r* Y* l
  32. let ptr := mload(0x40) 5 B2 l2 V& X1 o. ?! m! O. n t, J
  33. calldatacopy(ptr, 0, calldatasize)0 ?8 r8 Z2 G! R
  34. let result := delegatecall(gas, impl, ptr, calldatasize, 0, 0)# @. [ ^: K/ v, q3 ?* m
  35. let size := returndatasize/ c% a2 Z- O) _5 d; @ T* H+ ~
  36. returndatacopy(ptr, 0, size) 7 O! ?6 i3 d& ^) ]
  37. switch result/ h1 S/ N9 T: f9 k' I
  38. case 0 { revert(ptr, size) }) e$ r& V) x2 g2 t5 M0 E
  39. default { return(ptr, size) }5 u& `( K& `4 A0 l% f
  40. } , `$ C3 e: o# V4 e4 X% h* G$ `: d
  41. } 1 w- ~7 @. M5 a* t
  42. 1 R7 L9 N" c) D3 v% n4 z) Z5 g
  43. /** ) \0 K: p8 w6 K
  44. * @dev Sets the address of the current implementation 5 |* e; _( \' w+ B7 M1 Y
  45. * @param _newImp address of the new implementation4 h3 c" c! \: i
  46. */ 7 L+ J1 S/ @- R! k1 A
  47. function _setImplementation(address _newImp) internal { ( a" m9 k6 K7 k/ d: R# i
  48. implementation = _newImp; x$ i. ^& n' M2 T {" k
  49. } 1 q( s4 C8 }3 n; |7 F( q
  50. }
复制代码
7 ~- Y7 @- g, ?1 K4 Z' d* S5 m! B- M, {0 M* F $ g+ ~. `5 g) c& b! F为了让合约生效,我们首先需要部署代理合约以及ImplementationV1合约,然后调用这个代理合约的upgradeTo(address)函数,同时pass掉我们的ImplementationV1合约地址。现在,我们可以忘记这个ImplementationV1合约的地址,并把代理合约的地址作为我们的主地址。9 `# |, w1 x, F; Y; m" h 为了升级这个合约,我们需要创建一个新的逻辑合约实现,它可以是这样的:. u8 J- v, z9 e/ n9 t8 V; u# j
  1. contract ImplementationV2 is ImplementationV1 {9 s$ E% v: Y7 k7 k+ i! b
  2. % W9 n X& W' Z& Q, K
  3. # r/ j" S* q- E' v5 O( J3 ^
  4. function addPlayer(address _player, uint _points)$ Q7 d0 T9 s: c: E. d6 x
  5. public onlyOwner9 Z# I- K* Q F
  6. {* ?' m& c- S( n) @
  7. require (points[_player] == 0);5 `1 y5 f; Y" j3 F6 B
  8. points[_player] = _points;( b. o: \4 [' j' t
  9. totalPlayers++; 1 T6 ]7 c& l6 l m
  10. }8 L* E6 K+ ?& U
  11. }
复制代码
" j+ d9 u8 J# i' {/ Z4 [$ ^8 H ' F& ^* P. m+ s0 W: t$ J5 a你应该注意到,这个合约也继承了存储结构合约(StorageStructure contract),尽管它是间接地。/ U! M3 T7 w. b5 a: }6 j 所有的执行方案都必须继承这个存储结构合约,并且在部署代理合约后不得进行更改,以避免对代理的存储进行意外覆盖。! K5 \/ U! G' y6 a ' Q4 T+ ]* O0 @# ?/ | 为了实现升级,我们在网络上部署这个合约,然后调用代理合约的upgradeTo(address) 函数,同时pass掉ImplementationV2合约的地址。$ w) t3 P$ s* d5 X/ G6 k 这种技术,使得升级合约逻辑变得相当容易,但它仍然不允许我们升级合约的存储结构。我们可以通过使用非结构化的代理合约来解决这个问题。 : S5 W' ^1 U8 i7 S' W+ U4 L; P5 K# f 8 ]0 A% N0 L3 _* P$ m' ]0 y8 Y( s. a ( @+ O" X8 C* P/ t0 ^非结构化可升级存储代理合约 ! o( O. O7 ~2 j $ f/ R4 e, i Z' I7 g4 \这是当前最先进的,可实现智能合约升级的方法之一。它通过保存合约地址以及在存储中固定位置所有者的方法,以实现它们不会被执行/逻辑合约提供的数据所覆盖。我们可以使用sload以及sstore操作码来直接读取和写入由固定指针所引用的特定存储槽。 / E! `; l. d7 U! u% y8 \此方法利用了存储中状态变量的布局,以避免逻辑合约覆盖掉固定位置。如果我们将固定位置设置为0x7,那么在使用前7个存储槽后,它就会被覆盖掉。为了避免这种情况,我们将固定位置设置为类似keccak256(“org.govblocks.implemenation.address”).- p/ C0 _# X: Z6 `* \! @) } 这消除了在代理合约中继承存储结构合约的需要,这意味着我们现在也可以升级存储结构了。然而,升级存储结构是一项棘手的任务,因为我们需要确保,我们所提交的更改,不会导致新的存储布局与先前的存储布局不匹配。 3 ]2 e$ T0 E# X7 c8 _8 e - i5 ~% ?6 T/ g9 x, [& E7 f 这项技术有两个组成部分。/ Q6 X# ~$ N% j' o7 } 1、代理合约:它负责将执行合约的地址存储在一个固定的地址当中,并负责委托调用它; 2、执行合约:它是主要合约,负责把我逻辑以及存储结构;- I9 }- F8 n$ p/ e 你甚至可以将这项技术用于你现有的合约,因为它不需要对你的执行合约进行任何更改。: ?) T" ^! Q0 }- d# z* Q( @; m( \ 这个代理合约会是这样子的:$ k. m$ [& m D- d1 N1 }$ o% C
  1. contract UnstructuredProxy { & a: q; o* ]- S6 `- f: c
  2. $ n7 n7 @1 V- d) J5 C
  3. // Storage position of the address of the current implementation1 {+ z$ {& o' k3 \ ?% y |$ D
  4. bytes32 private constant implementationPosition =% `$ _4 K( ?, ^: C& l
  5. keccak256("org.govblocks.implementation.address"); |) w& Q. w4 @- o& ^7 y
  6. // Storage position of the owner of the contract5 u: j' k( W0 R
  7. bytes32 private constant proxyOwnerPosition = ' m+ e: U+ B& h+ W% g) k" r; O: Q
  8. keccak256("org.govblocks.proxy.owner"); ) F7 D# a7 {" S4 p+ z; k
  9. /** 5 b: u. h# D* I3 b: E. _3 l
  10. * @dev Throws if called by any account other than the owner.8 L7 S: C% Y, M
  11. */ " J" T, ?5 ^' p
  12. modifier onlyProxyOwner() {9 p9 J. I) V$ r- l
  13. require (msg.sender == proxyOwner()); ( H4 a7 U& ~- d! j+ Y. |2 Q% b% Q
  14. _;2 Z4 j; i0 B$ W9 z. J1 a6 O
  15. }9 d; b) f8 o9 C9 ?
  16. /**2 W' S2 F |' p
  17. * @dev the constructor sets owner 6 U* r( E4 g% U g7 G) f: W
  18. */# i. b" b, R* I
  19. constructor() public { - [6 N4 d& X" |9 U& C& X3 S
  20. _setUpgradeabilityOwner(msg.sender);" C5 P3 g; A9 Y6 @: W1 K; L) A# j
  21. }6 F2 N" \+ o' [, S
  22. /** % f8 }9 B, l% ?+ m& }( J
  23. * @dev Allows the current owner to transfer ownership ) j( X( P. E( o. _3 k
  24. * @param _newOwner The address to transfer ownership to8 Z- e1 j* r( V4 G
  25. */ " z6 z$ g# F9 ^; j: j1 T
  26. function transferProxyOwnership(address _newOwner): Z: [: V! Y$ c" l8 Y! S. { Z
  27. public onlyProxyOwner . @4 Y+ J7 Q* s* {. Z# _: {
  28. {# c5 O$ e; _& T6 ]9 I
  29. require(_newOwner != address(0));7 V9 i- c) L1 V
  30. _setUpgradeabilityOwner(_newOwner);7 A4 Y2 P3 ]4 `: C8 w
  31. }, B7 I5 e' T; E3 {, m- i$ Q
  32. /** $ U. D) C6 i/ r9 Z5 X& C2 c
  33. * @dev Allows the proxy owner to upgrade the implementation 0 X/ s, v ^5 H5 v
  34. * @param _implementation address of the new implementation) k2 M, \' q u, ]2 _4 D8 d) H
  35. */9 c0 t3 K4 U* U- q
  36. function upgradeTo(address _implementation) ; `6 k1 ?- a! H
  37. public onlyProxyOwner 2 Q { l# ?# c6 y+ q4 r
  38. {7 Y( {2 b( K d6 F* U0 ]
  39. _upgradeTo(_implementation); & Y2 `: R9 e6 R' `5 q
  40. }$ Q4 n2 N! ~8 J4 {7 c
  41. /**) [* ]* x1 d# J$ D3 r
  42. * @dev Tells the address of the current implementation 9 n. I1 O; y+ R+ m: x
  43. * @return address of the current implementation & q4 c! R* N0 I" S% {0 x
  44. */% `2 N- Q7 M& f% D, _2 h0 }. S( ?- g/ m: j
  45. function implementation() public view returns (address impl) {% ~0 u. {% K& m O1 K# q4 w
  46. bytes32 position = implementationPosition; + u7 ]: Z. Z) ^ L
  47. assembly {+ g$ `: U9 `/ R, N, O8 ]7 \+ n+ p+ j
  48. impl := sload(position)% a. {. H) H- {
  49. }1 E# u6 f/ Q1 ^4 `- x" ]
  50. } u* L1 p/ V! F+ c+ v/ O
  51. /** 7 p( R) a$ G) W9 h4 X
  52. * @dev Tells the address of the owner1 H3 h8 l0 e$ a: b" o0 n* L
  53. * @return the address of the owner' `& m" }& h ]. f1 n# X0 l) y
  54. */ 4 Q6 v2 ^) n, q* b
  55. function proxyOwner() public view returns (address owner) {4 D# o( ]5 `/ q6 \
  56. bytes32 position = proxyOwnerPosition;: I1 J; N/ V8 o" W' f5 [" |
  57. assembly { 2 N& g N4 V h! y/ b7 u
  58. owner := sload(position)+ H3 Q1 @9 M5 E9 L7 P( i
  59. }9 h$ J6 S) _ b( u
  60. } # o1 i$ I$ ~* |& o, L# y: s, N
  61. /** , U# Q5 E5 X8 ?& X' S1 t
  62. * @dev Sets the address of the current implementation6 W9 ]" u" z9 v& R
  63. * @param _newImplementation address of the new implementation- V; r0 G% l9 |5 |' u1 L; r
  64. */: ~9 x* y V+ ]* {; [, b
  65. function _setImplementation(address _newImplementation) ; o* {( s+ b2 w+ T) U
  66. internal+ v" i; b! i5 O
  67. { , v* L8 H) v' F; [
  68. bytes32 position = implementationPosition; 5 m9 a7 b' f8 @4 P
  69. assembly { % X% x' F. ]$ z% k Q% Y, Z
  70. sstore(position, _newImplementation)1 o$ G1 D% \ Q" I% G& a
  71. }8 k7 K4 G: c' x8 I
  72. } 0 \3 }& }9 ^4 c7 b' l% [' f/ q
  73. /**. ]) F8 r- h, u; _; [6 e8 q
  74. * @dev Upgrades the implementation address # h. z6 o+ ~7 p! q$ k b1 ~, {# V
  75. * @param _newImplementation address of the new implementation }- n: c# p! c8 C) I
  76. */ & k- i" g% V* R5 p7 P" |& ~) C
  77. function _upgradeTo(address _newImplementation) internal {* @/ Z" B6 P* w( y8 O! P
  78. address currentImplementation = implementation();1 C7 p1 J- X+ T) n; T
  79. require(currentImplementation != _newImplementation);6 M; |( Y& {) n6 u( J* r
  80. _setImplementation(_newImplementation);- t4 p5 _9 M! w& z% a! y1 Q; o) W
  81. }. N) w- }7 S* N% O) y/ u+ v7 i
  82. 7 H, M9 w d7 s5 T2 w
  83. /**! k, t+ q+ F# [5 S4 s
  84. * @dev Sets the address of the owner 3 ]8 E# P' `$ u8 s3 o. `* E
  85. */ / x1 s; {# i' ?! G) n3 L
  86. function _setUpgradeabilityOwner(address _newProxyOwner)4 w; j/ L; R) K6 n
  87. internal7 T& m$ y' I9 u- f2 C* g
  88. {8 a' \$ J) \0 o9 p) A
  89. bytes32 position = proxyOwnerPosition;& ^- C. Q% Z0 G
  90. assembly {! \4 I d8 d( e+ w$ \+ Y
  91. sstore(position, _newProxyOwner) - h' t8 I! h; F" R" u- A i
  92. } 8 B2 r$ y* B- z7 Q* @9 w
  93. } ( `) H* l. g% k. a
  94. }
复制代码
8 R3 S( E: {; [+ G. ?% V3 U $ W; u! {6 ?9 _2 w3 t3 V3 j. t/ P ?6 Z! ]/ u q; }( U如何使用非结构化可升级存储代理合约?3 S4 b2 S {5 q/ x, H " k( y" `$ b" u! S使用非结构化可升级存储代理合约是非常简单的,因为这种技术几乎可以处理所有现有的合约。想要使用这种技术,你只需要遵循以下步骤:7 c6 I# R0 z0 K$ Y6 P 部署代理合约和执行合约; / E5 L$ V' d! u5 F4 a$ f调用代理合约的upgradeTo(address)函数,同时pass掉执行合约的地址。$ n9 _, c/ g* a" c, z+ c, W% {$ d 我们现在可以忘掉这个执行合约地址,然后把代理合约的地址作为主地址。 7 H3 }+ v5 U/ {: w" Y; @ " r7 y: w) T; \$ O3 e Z1 v! Z6 S; @而要升级这个新实施的合约,我们只需要部署新的执行合约,并调用代理合约的upgradeTo(address) 函数,同时pass掉这个新执行合约的地址。就是这么简单! 6 `$ ?6 j5 V0 {3 `9 s让我们简单举个例子。我们将再次使用上述可升级存储代理合约中使用的同一逻辑合约,但是我们不需要用到存储结构。因此,我们的ImplementationV1合约看起来会是这样的: ( W# k/ Q- k* q# c5 F- `3 y
  1. contract ImplementationV1 { - ~5 a. [, @: R; E1 Z X1 x
  2. address public owner;' K( k8 N h5 ?: r% c* @' b; k# y
  3. mapping (address => uint) internal points; W& N. d2 O5 _1 f1 I( x6 y3 g/ K
  4. % s* t9 t$ v- d# M e/ i" T
  5. modifier onlyOwner() {2 ^+ u3 l0 w9 G$ x/ F& `
  6. require (msg.sender == owner); / B1 I) ^7 B/ T6 q: W. Q4 T
  7. _;! e3 `, o0 S( E% O6 L/ ]" @
  8. } 9 T3 m5 i) i9 v5 m z3 A. i) D B
  9. function initOwner() external { [5 l) z; t+ X5 k% x7 q& h3 k
  10. require (owner == address(0));9 l% m$ _$ W& ?+ o: D: Z, n9 s4 B
  11. owner = msg.sender;9 X. V+ b5 e+ O+ Q+ l" T& m6 A
  12. }9 V8 m. N0 L% A8 |- I5 J% S
  13. function addPlayer(address _player, uint _points)- u2 w& s# f! W D
  14. public onlyOwner " r$ b+ }4 {/ I% M& k) @/ S, D3 Y
  15. {$ j5 R) N2 m4 y2 i; I B0 C
  16. require (points[_player] == 0); $ y: U c. X( I6 I5 Q) O0 T
  17. points[_player] = _points; 4 R, r" U1 ]; r+ R! v
  18. }5 I) O2 z# L* B h! _: @' i
  19. + |; E7 S. W. W/ u# B2 }, T$ P
  20. function setPoints(address _player, uint _points) 5 f# J5 i# {2 S; @* g! j
  21. public onlyOwner0 y, a2 N% \5 |3 f
  22. { 6 c5 c+ G: K" R( E% X; P N; |
  23. require (points[_player] != 0); 4 y% M+ e; J* o! [9 W
  24. points[_player] = _points;& D( e8 |8 ]+ M- _8 s9 [
  25. } Y2 n; J# |& ^" t- N
  26. }
复制代码
) |# ~4 P; n/ x! g- Z* ]9 A- e1 M1 ~( D; Z j 下一步是部署这个执行合约以及我们的代理合约。然后,再调用代理合约的upgradeTo(address) 函数,同时pass掉执行合约的地址。% {: P* j. w2 u T2 Q* ~ / s/ A: c$ {# N你可能注意到,在这个执行合约中,甚至没有声明totalPlayers变量,我们可以升级这个执行合约,其中具有 totalPlayers变量,这个新的执行合约看起来会是这样的: 9 P/ U( O. {5 d' @# R
  1. contract ImplementationV2 is ImplementationV1 { 5 {: c! H# Z. F6 U: E8 m
  2. uint public totalPlayers;. Z1 g- Q. U; h2 Q1 F; P
  3. 9 r: T# _' U6 b8 e9 g' Y
  4. 7 w0 Z5 Y+ J! X
  5. function addPlayer(address _player, uint _points)0 @* n5 { T, l
  6. public onlyOwner ' _ k) l+ y8 Y; ?
  7. {# j: ~3 ^. ~8 b8 r3 A1 |
  8. require (points[_player] == 0);0 X" s/ v5 l4 U! ?
  9. points[_player] = _points;( Y( s& H# W4 S1 B, t% H2 s
  10. totalPlayers++; 9 M; E! [ w% o5 q L; z) d7 d& T
  11. } ; G' }5 H6 D( l% \( Z% _6 [
  12. }
复制代码
, I0 a1 Y+ z6 ]! T( N$ g' W 3 V) Y2 T- Z* ]而要升级这个新的执行合约,我们需要做的,就是在网络上部署这个合约,然后,嗯你猜对了,就是调用代理合约的upgradeTo(address)函数,并同时pass掉我们新执行合约的地址。现在,我们的合约已演变为能够保持跟踪 totalPlayers,同时仍然为用户提供相同的地址。- [- o: {4 U5 ^' |6 S - P$ w: W6 v- L, O. X这种方法是强大的,但也存在着一些局限性。主要关注的一点是,代理合约拥有者(proxyOwner)有太多的权力。而且,这种方法对复杂的系统而言是不够的。对于构建具有可升级合约的 dApp而言,组合主从合约以及非结构化可升级存储代理合约,会是更为灵活的一种方法,这也是作者所在的GovBlocks所使用的方法。1 u: A+ M& x0 I # e7 p/ d4 h* U + V: T& d. l6 x" c* j( V" u 结论 : : N; E+ L) @2 I& S o 非结构化存储代理合约,是创建可升级智能合约最先进的技术之一,但它仍然是不完美的。毕竟,我们并不希望dApp所有者对dApp具有不当的控制权。如果开发者拥有了这种权力,那这个dapp还能称之为去中心化应用吗?在这里,我建议读者可以阅读下Nitika提出的反对使用onlyOwner的论点。你也可以在GitHub上窥探到我们的代理合约。9 H- m1 `, A' \. N' T8 ^; Y6 | 希望这篇文章可以帮助你创建可升级的智能合约。 8 m, u, s6 K. B U) M t/ H同时向Zepplin在代理技术方面进行的工作致敬。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

用香烟做的云 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    2