Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

用香烟做的云
75 0 0
现如今,整个密码货币生态系统都是由智能合约所驱动!不管我们有多小心,或者我们的代码测试工作做得有多好,如果我们创建的是一个复杂的系统,那我们就有必要更新合约逻辑,以修补其存在的漏洞,或者添加必要的缺失功能。有时候,由于EVM虚拟机的更改或者被新发现的漏洞,我们可能需要去升级我们的智能合约。 ; K: w9 b+ @% X3 M! ^* O- A& L一般来说,开发人员可以很容易地升级他们的软件,但区块链的情况是不一样的,因为它们有着难以更改的属性。如果我们部署了一个合约,这就好比是泼出去的水。然而,如果我们使用适当的技术,我们可以在不同的地址部署一个新的合约,并使得旧合约无效。下面是一些最常见的,创建可升级智能合约的方法。 & D$ o- H% z& w( ^0 _ 9 ]* K2 Q8 L. C+ n0 ~; K% q: N0 O' T$ E 主从合约(Master-Slave contract)7 q4 Q, H7 h6 l% p/ G" y 5 X7 T% z% Y5 {, A/ N! e 主从技术,是可实现升级智能合约最为基础也是最容易理解的技术之一。在这种技术当中,我们部署一个主合约,以及其他合约,其中主合约负责存储所有其他合约的地址,并在需要时返回所需的地址。当这些合约需要和其它合约进行沟通时,它们会充当从合约,从主合约那里获取其它合约的最新地址。为了升级智能合约,我们只需要在网络上部署它,并更改主合约中的地址。虽然这远不是发展可升级智能合约的最佳方式,但它确是最简单的。这种方法存在着很多的局限性,其中之一是,我们不能轻易地把合约的数据或资产迁移到新合约中。 ( o3 B) F; v7 b) ^# w! x 6 S/ v" y, n! R$ p( z永久存储合约(Eternal Storage contract0 T% m( A: i9 o- _3 D6 o1 g ' z2 r/ F/ Z2 g( T! N' C: K4 a 在这种技术当中,我们将逻辑合约和数据合约彼此分离。数据合约应该是永久并且不可升级的。而逻辑合约可以根据需要进行多次升级,并将变化通知给数据合约。这是一项相当基本的技术,并且存在着一个明显的缺陷。由于数据合约是不可升级的,数据结构中需要的任何更改,或数据合约中存在的漏洞,都会导致所有数据变得无用。这种技术的另一个问题是,如果逻辑合约想要访问/操作区块链上的数据,那么这个逻辑合约将需要进行外部调用,而外部调用会消耗额外的gas。通常情况下,这种技术会和主从技术相结合,以促进合约间的通信。( ]% e2 B5 U o) Z% E& @ ' ?( |0 O1 I6 c/ a可升级存储代理合约 % f) _- m% n5 e; a* w* s% Y& m |/ J 我们可通过使永久存储合约充当逻辑合约的代理,以此防止支付额外的gas。这个代理合约,以及这个逻辑合约,将继承同一存储合约,那么它们的存储会在EVM虚拟机中对齐。这个代理合约将有一个回退函数,它将委托调用这个逻辑合约,那么这个逻辑合约就可以在代理存储中进行更改。这个代理合约将是永恒的。这节省了对存储合约多次调用所需的gas,不管数据做了多少的更改,就只需要一次委托调用。* A7 c0 z$ M& R5 }. G 这项技术当中有三个组成部分:4 j0 U3 B, M+ K/ B" T+ I; a! b 代理合约(Proxy contract):它将充当永久存储并负责委托调用逻辑合约;$ K0 z2 y1 g1 G 逻辑合约(Logic contract):它负责完成处理所有的数据;& q' b. }3 x D 存储结构(Storage structure):它包含了存储结构,并会由代理合约和逻辑合约所继承,以便它们的存储指针能够在区块链上保持同步; * x. D5 c+ U5 J3 P. O( a, W ^. z0 ^4 V/ M 9 P) Z) q, d! M, L: W1 ]9 l6 Q- j# y* x% q0 @! A1 {/ L$ [! ~ 委托调用5 D3 h; {5 F1 u' L+ y4 y/ _1 u 5 ^% w) f7 @2 o& f, ~: p" T% j. N 该技术的核心在于EVM所提供的DELEGATECALL操作码,DELEGATECALL就像是一个普通的CALL 调用操作码,不同之处在于目标地址上的代码是在调用合约上下文中执行的,而原始调用的msg.sender以及msg.value将被保留。简单说,DELEGATECALL基本上允许(委托)目标合约在调用合约的存储中做它任何想做的事情。2 H/ A/ A( u, d! w- I 我们将利用这一点,并创建一个代理合约,它将使用DELEGATECALL操作码委托调用逻辑合约,这样我们就可以在代理合约中保持数据的安全,同时我们可以自由地更改逻辑合约。* v$ _% | m+ n& L1 \7 |, a : K; V1 Q1 B% u1 v9 l7 J0 {% I - N6 W% K0 \) @0 f0 o如何使用可升级存储代理合约 3 Z5 Q& y2 q! H8 d4 k& w, O: m0 e) i2 K e+ M 让我们深入研究一下细节。我们需要的第一个合约是存储结构。它将定义我们需要的所有存储变量,并将由代理合约和执行合约所继承。它看起来会是这样的:% ]9 N$ Q4 G: h# t( o
  1. <b>contract StorageStructure { : h' }* F9 v3 j' m/ _" S: T
  2. address public implementation; 6 f( M1 M) n/ _) G/ \0 f
  3. address public owner; I5 q0 F. a6 w, x
  4. mapping (address => uint) internal points; 3 \2 j5 f) _! F7 L
  5. uint internal totalPlayers;3 b) t3 G4 U2 {" E" F
  6. }</b>
复制代码
* q4 O; U! W; \* u a2 M l% @2 l+ Z1 l8 a% w我们现在需要一个执行/逻辑合约。让我们创建一个简单版的合约,在添加新玩家时不会增加totalPlayers计数器的数字。 }$ F; Q% d: x 2 |6 j8 G% v# @9 h* J- q$ Q8 G
  1. contract ImplementationV1 is StorageStructure {. L4 H3 Q2 y2 ^+ p7 z
  2. modifier onlyOwner() {( \6 C: F1 t( e! A- x- c
  3. require (msg.sender == owner);0 W% ~- y( o1 R" m, z& K& Y1 o3 C
  4. _; * w2 I5 |! m( L2 p8 k0 |- ? ]& j/ i
  5. } : C! e% r/ `- C) f
  6. function addPlayer(address _player, uint _points) 8 F+ j2 v z) v0 [% N2 O: f
  7. public onlyOwner 3 V; ]& f, N2 B5 b
  8. { 9 n5 ?3 ]) i) E1 u L8 p1 C( J
  9. require (points[_player] == 0); - G- t+ E5 v* C. q5 k
  10. points[_player] = _points; }6 _" x; i1 v l: u
  11. } - k# d$ Z9 Q* f; G1 O5 d) K9 d5 H
  12. function setPoints(address _player, uint _points)6 N' h* J% a: |
  13. public onlyOwner- U6 b* x( H# u9 F% R5 n
  14. {6 @5 D: p M2 d% B7 @
  15. require (points[_player] != 0); - ]+ l3 S% g" w, F1 k
  16. points[_player] = _points;) K' u; P$ g6 w* d G/ N
  17. }: I" V) G/ p4 P6 R0 w5 L/ v6 i
  18. }
复制代码
6 S# u3 I, m' v/ Y: J) V6 k9 D0 E . p2 J7 m& Z7 G$ ?5 I1 F + `& x5 B4 {* h5 b# f$ S, O 下面就是最关键的部分:代理合约; 7 u% w m* v/ A" q1 c% A. ~
  1. contract Proxy is StorageStructure {" t# c, }8 k/ V; I$ u
  2. / V. m( B" v+ G: i' Z6 s1 E6 D& z
  3. modifier onlyOwner() {: t, F- }6 ]4 f0 }. o5 Z( O J
  4. require (msg.sender == owner); " J0 o# K) e- _2 k+ j7 t3 U# k
  5. _;: s& e* @! E0 v
  6. }" l3 T( L* p0 V* O3 ^# t
  7. /** " r) J) i$ T9 V
  8. * @dev constructor that sets the owner address 0 {, n$ Z2 A, u# N/ X( E
  9. */3 Z$ z! ]- C, z; h- b
  10. constructor() public {! [( @4 ]+ l r- e% P [' E
  11. owner = msg.sender; 6 R+ \5 } m1 P- M! z
  12. }: n- X* I1 l. o' ?
  13. /**. I) U7 J' V1 ?* y8 I
  14. * @dev Upgrades the implementation address6 J5 D8 a' q3 g3 M- B* j) h7 {
  15. * @param _newImplementation address of the new implementation% `0 g( l, A: T1 f( P
  16. */# s0 n* s8 s7 O# X! R2 j
  17. function upgradeTo(address _newImplementation)* q# |5 q4 C6 U: P7 Y. ~
  18. external onlyOwner+ b" c9 z8 Y& N. x `5 Z
  19. { 1 N4 @9 Y, a7 Z+ C( t6 Z
  20. require(implementation != _newImplementation);9 c+ a; q8 }- ^7 _) }5 f# M0 a
  21. _setImplementation(_newImplementation); ( H3 }- g g$ x& a# N$ H0 o* r* x9 b* R
  22. } X+ \6 \' l4 x, M4 C) R( I3 o
  23. /** E$ w, S! B+ H3 \/ d1 l( \. h. L
  24. * @dev Fallback function allowing to perform a delegatecall1 _% P/ f7 G4 a
  25. * to the given implementation. This function will return / E D: h. ^' V
  26. * whatever the implementation call returns ; B, z) ]3 t3 ^, H$ X! m* p
  27. */ 6 s' ?& Z% @7 Q0 m$ u' n: h
  28. function () payable public { ; Y! N# [, T6 N$ p z: ?) S3 x
  29. address impl = implementation; + F) g7 h$ C) X# I5 Y
  30. require(impl != address(0)); 5 e ]" w% E9 S. K
  31. assembly {" h2 J1 L* i( M
  32. let ptr := mload(0x40)/ i' |7 b6 F q/ g T
  33. calldatacopy(ptr, 0, calldatasize), b: u1 d5 n7 n: j' c* M4 n9 I* b
  34. let result := delegatecall(gas, impl, ptr, calldatasize, 0, 0) i! ?" P7 y* V7 ?) S
  35. let size := returndatasize ( p: C* k0 T6 O
  36. returndatacopy(ptr, 0, size) ; ]1 x" U) d: y2 p5 r* r
  37. switch result ' V. ]# W: p$ u: B, v& p
  38. case 0 { revert(ptr, size) }; }! Q- g" ?: u) |/ _! u
  39. default { return(ptr, size) }$ d& N+ M$ A& m; m
  40. }: i" s9 j) o% m/ b% \( c; I
  41. }4 V2 ^6 u- t4 W- f
  42. 5 B& v$ O1 A; D2 E) C9 ~
  43. /*** v1 {, e$ a% V3 \8 m; j8 ~# H0 f
  44. * @dev Sets the address of the current implementation- \- H/ E5 x) @9 g2 F3 r' y& |
  45. * @param _newImp address of the new implementation * X* a6 `$ r+ j; t% s; b
  46. */ 5 a; J) |, J: [- g
  47. function _setImplementation(address _newImp) internal {9 G1 p) B5 i. f* \9 ?
  48. implementation = _newImp; 3 a( j6 l1 B! n: t3 \
  49. }) F3 E8 w( [" Z! v' A# F" p
  50. }
复制代码
2 I/ `4 u4 r2 Y% H1 t- V4 Z- M- Z9 g( x# a: F/ { # P. X8 Z) Z4 { }+ F- z h 为了让合约生效,我们首先需要部署代理合约以及ImplementationV1合约,然后调用这个代理合约的upgradeTo(address)函数,同时pass掉我们的ImplementationV1合约地址。现在,我们可以忘记这个ImplementationV1合约的地址,并把代理合约的地址作为我们的主地址。; S; c* h; j( S6 Y( R4 }) \1 w 为了升级这个合约,我们需要创建一个新的逻辑合约实现,它可以是这样的:5 i* U6 I9 o( |2 f7 R
  1. contract ImplementationV2 is ImplementationV1 { . P; E' ] ^- [5 m
  2. 9 t7 ]+ N3 `8 @' @% D/ H+ a' f
  3. 3 A& I, U% ?/ I2 r g- t
  4. function addPlayer(address _player, uint _points)5 N1 K8 }! `$ L& \, j
  5. public onlyOwner$ w3 h, ]6 q' p
  6. {8 x. A' z) \' w* j8 y+ S/ |
  7. require (points[_player] == 0);! O3 G% e+ y" G$ M' `* e7 w
  8. points[_player] = _points; - Z! p a+ \: ~) y# E0 h4 S* `
  9. totalPlayers++;0 a# ]6 l2 j; I$ o
  10. } % t- R/ ?5 [0 v6 F& k
  11. }
复制代码
5 F: S1 {0 p7 a+ Q( c! Y' d5 c 0 E0 }, B! i a6 b% @+ ~4 `5 Y你应该注意到,这个合约也继承了存储结构合约(StorageStructure contract),尽管它是间接地。% H$ i% k. G H @2 q! ^8 L6 _ 所有的执行方案都必须继承这个存储结构合约,并且在部署代理合约后不得进行更改,以避免对代理的存储进行意外覆盖。 + N! q& k1 ?$ F6 Z9 X- O9 y) _1 V2 I 为了实现升级,我们在网络上部署这个合约,然后调用代理合约的upgradeTo(address) 函数,同时pass掉ImplementationV2合约的地址。 " [+ e5 Q' T7 p7 `2 u这种技术,使得升级合约逻辑变得相当容易,但它仍然不允许我们升级合约的存储结构。我们可以通过使用非结构化的代理合约来解决这个问题。 ^/ u, d- _6 P: ~! T9 l. }8 q1 C; D, \6 ?: K . r: H( I! q# U8 V ~/ K7 G2 e2 \ 非结构化可升级存储代理合约 % G8 D+ g5 D) u9 f% @$ @* r1 G: |5 W: Y& r 这是当前最先进的,可实现智能合约升级的方法之一。它通过保存合约地址以及在存储中固定位置所有者的方法,以实现它们不会被执行/逻辑合约提供的数据所覆盖。我们可以使用sload以及sstore操作码来直接读取和写入由固定指针所引用的特定存储槽。 / r0 v2 @5 z l" p- F此方法利用了存储中状态变量的布局,以避免逻辑合约覆盖掉固定位置。如果我们将固定位置设置为0x7,那么在使用前7个存储槽后,它就会被覆盖掉。为了避免这种情况,我们将固定位置设置为类似keccak256(“org.govblocks.implemenation.address”). , {8 W( R3 X) q2 K" J这消除了在代理合约中继承存储结构合约的需要,这意味着我们现在也可以升级存储结构了。然而,升级存储结构是一项棘手的任务,因为我们需要确保,我们所提交的更改,不会导致新的存储布局与先前的存储布局不匹配。6 c3 m% q6 G5 X' |' R" x + |5 U, W2 r X. [) h: B }. N& O 这项技术有两个组成部分。- F, O/ J: m. [' _* ?, g 1、代理合约:它负责将执行合约的地址存储在一个固定的地址当中,并负责委托调用它; 2、执行合约:它是主要合约,负责把我逻辑以及存储结构; j0 N- r8 ?$ [8 g b' b$ `; E6 ?5 y 你甚至可以将这项技术用于你现有的合约,因为它不需要对你的执行合约进行任何更改。 i6 o# x8 i: a* q; G 这个代理合约会是这样子的:, n) I) T% [8 z3 K- o2 t
  1. contract UnstructuredProxy {' y w/ b5 E/ r
  2. ' w$ E) ~$ Q5 \
  3. // Storage position of the address of the current implementation 0 z* ]$ \5 l% ~( N6 @
  4. bytes32 private constant implementationPosition =4 S0 \, z( E7 N2 [5 Z( H' B
  5. keccak256("org.govblocks.implementation.address");. O$ m: @) _, O; F K
  6. // Storage position of the owner of the contract $ t0 A B$ v0 l6 v. y
  7. bytes32 private constant proxyOwnerPosition =& e) t1 F: \+ D8 K
  8. keccak256("org.govblocks.proxy.owner"); 4 v% s4 M7 }3 X+ r a
  9. /** ; J. B" j% l1 W q8 N
  10. * @dev Throws if called by any account other than the owner.- e( q$ r# m6 @! G* h' t
  11. */ ) D, S1 d) L* x! l j& F
  12. modifier onlyProxyOwner() { % `( ?( U5 Y$ ]
  13. require (msg.sender == proxyOwner());/ Y& n6 G! |( [6 R- x2 R
  14. _; ' d& X; f& m( Q
  15. } c& u+ J, {3 l9 x. \
  16. /** 0 b3 b' x* v( L/ z% x" c* K- `
  17. * @dev the constructor sets owner7 P |# O9 q3 l# a1 l
  18. */ + i) w z9 g3 Z, V# {" O: U
  19. constructor() public { . }/ X, c+ o- x. S$ h& H1 r
  20. _setUpgradeabilityOwner(msg.sender);+ f3 f5 I& ~5 d4 u- D; h
  21. }6 i0 C' \' j; U. p+ j$ X* _- f
  22. /**" x, X3 e" t/ E' C& `& Q" \; r o
  23. * @dev Allows the current owner to transfer ownership$ N9 ]& L! Y" k+ n3 z
  24. * @param _newOwner The address to transfer ownership to ) O c0 v4 [0 B% p
  25. */8 J: s- Z+ X- n' ]2 r3 h
  26. function transferProxyOwnership(address _newOwner)/ B( f9 W" }; d, [( h' e
  27. public onlyProxyOwner 9 q$ K; d: A Q T$ H) K) f& X
  28. { I; ^+ J$ S i W- n: B. m
  29. require(_newOwner != address(0));& Q( U( o3 e1 j* \/ ~: q1 G
  30. _setUpgradeabilityOwner(_newOwner);! W5 a/ B. _- a% g1 m& B
  31. }! I, `% F! N) |
  32. /**: y3 p! W& @% |
  33. * @dev Allows the proxy owner to upgrade the implementation% Z# I/ \& V$ ^, K% h
  34. * @param _implementation address of the new implementation* ?9 d3 u! h' W' c) `4 a
  35. */) r! L# E8 T( o: M( \3 n
  36. function upgradeTo(address _implementation) 3 d* Y# V$ {) u& y9 D3 L! ^. g
  37. public onlyProxyOwner 5 C( S( L) G! r7 x) A6 U: _3 f& L7 g
  38. { ! _$ w# R# K) h5 _& f
  39. _upgradeTo(_implementation);. w& a% W7 P* s7 G& o" y
  40. }; b1 L. K1 O' y- F
  41. /** 9 E5 @. w' x6 y& P6 F
  42. * @dev Tells the address of the current implementation' ^+ P/ \5 l% E9 w
  43. * @return address of the current implementation $ z5 E2 [. ]+ |6 k
  44. */ 1 i6 \: ]) r2 a2 V. a
  45. function implementation() public view returns (address impl) {" E! e; W3 G. [+ O2 a8 h
  46. bytes32 position = implementationPosition;& g ^. p( ^& P
  47. assembly {3 c+ B% _5 f! G6 v) h# S* P
  48. impl := sload(position) ) H1 j! R# {- |0 k/ G7 X1 M4 G
  49. } # L3 @+ i2 N; r2 Y* Q# B9 T9 m
  50. } & B8 D1 B4 Q' J
  51. /** . u4 V. @( \. V
  52. * @dev Tells the address of the owner 0 ?5 v- X# P& R, X6 y
  53. * @return the address of the owner+ s- E( m3 N8 s# }/ ]1 K& k
  54. */1 b/ [& H& M: [7 P
  55. function proxyOwner() public view returns (address owner) {6 n3 w. o1 M. l2 V- P
  56. bytes32 position = proxyOwnerPosition;- C$ W3 {" ^7 T+ S0 o# E4 x
  57. assembly {5 ?" N& D0 B- F1 k
  58. owner := sload(position)$ p f: F1 l3 {5 Y A3 }, n: H
  59. }, y/ F) b [) D3 B
  60. }* K* [- i- Y) H. N/ L
  61. /** + G, A4 Z* ?+ r5 I. y
  62. * @dev Sets the address of the current implementation ! N* O1 U- H; t6 {* t. P0 _
  63. * @param _newImplementation address of the new implementation- k5 `7 F {: h/ \9 I* a
  64. */ ) [; K) H; ]) k' R
  65. function _setImplementation(address _newImplementation) / e' C* G+ K( T. n5 g' @
  66. internal 0 j4 ]4 t5 g g' s; ~
  67. { 3 ]/ ^4 Z3 L' _3 n/ \+ [0 O% X
  68. bytes32 position = implementationPosition; / Z+ _9 S6 B8 ~; a& ^3 J
  69. assembly { 3 X+ Z2 n8 [9 ~7 |
  70. sstore(position, _newImplementation) / y+ Q3 k( i; O7 N) f4 \
  71. }9 p1 S4 l8 \- H/ y3 }* a2 ?
  72. } ( Q* F7 K% S( U3 T/ W; x9 ?$ J C T
  73. /**/ X9 ]* `/ a3 w
  74. * @dev Upgrades the implementation address 9 x3 Q' X1 s8 r' @
  75. * @param _newImplementation address of the new implementation1 v+ p+ M5 ~$ @3 d6 p1 }
  76. */ , P/ a) D8 F" V" Z
  77. function _upgradeTo(address _newImplementation) internal { 1 B% s0 U' I- _$ p
  78. address currentImplementation = implementation(); * r$ @1 Z3 C1 x9 L6 c [9 l( n
  79. require(currentImplementation != _newImplementation); I2 C& H4 I' \/ H. _2 c" j/ t
  80. _setImplementation(_newImplementation); 3 _6 S- y8 Z6 n4 l
  81. }% G' i4 q) B3 A8 J
  82. $ Z2 H0 }; B& V- o/ w
  83. /** - k3 E* a" G' _: i) {; l1 u1 F
  84. * @dev Sets the address of the owner 3 H4 O/ K$ [: _2 z0 S
  85. */% w% e7 w5 }' `1 T# S. c
  86. function _setUpgradeabilityOwner(address _newProxyOwner) # S9 p7 b2 W% g* L+ l
  87. internal + ?2 n& x! X2 ^# ~0 j% \; u& h
  88. {+ F0 ^ r e# C+ g. h6 F
  89. bytes32 position = proxyOwnerPosition;# h! w+ d- W1 n# }/ g
  90. assembly {. h0 B" l" A8 W3 V3 ~- l
  91. sstore(position, _newProxyOwner), W! _! _# L3 j5 v! F
  92. } , }4 Z0 \8 `0 }6 D
  93. } 0 b7 ?; \' m8 j5 n3 m9 g5 c
  94. }
复制代码
% p' s5 u9 v# h T, x K& m 4 X0 X6 e% I) a q$ L 6 N5 e( n- N- p, K3 ]如何使用非结构化可升级存储代理合约?6 P- i9 b n( f# h: u. g9 z 7 D$ i4 S! S0 ]; n使用非结构化可升级存储代理合约是非常简单的,因为这种技术几乎可以处理所有现有的合约。想要使用这种技术,你只需要遵循以下步骤:% a& G: O7 Y' ^% |0 g! G' B3 G& ] 部署代理合约和执行合约; # h, Z" h" {' z7 e5 H8 g调用代理合约的upgradeTo(address)函数,同时pass掉执行合约的地址。 . b N% e- K9 u. `. G* X4 k我们现在可以忘掉这个执行合约地址,然后把代理合约的地址作为主地址。: f7 C$ N! t2 q2 d : @1 ^! x3 I* P" Q! |而要升级这个新实施的合约,我们只需要部署新的执行合约,并调用代理合约的upgradeTo(address) 函数,同时pass掉这个新执行合约的地址。就是这么简单!: ? V3 r- U7 h) m 让我们简单举个例子。我们将再次使用上述可升级存储代理合约中使用的同一逻辑合约,但是我们不需要用到存储结构。因此,我们的ImplementationV1合约看起来会是这样的: 4 Y; G( X( R- d0 A. l
  1. contract ImplementationV1 {4 a5 S; ^! c7 P* }0 [/ O! L' Q! e+ e
  2. address public owner;4 }5 t2 I) w' b. [# C
  3. mapping (address => uint) internal points; # c. ?2 c& o. \: E* @9 p9 Q
  4. 8 Q) k" L' e- i4 v
  5. modifier onlyOwner() {3 m6 @) N0 O7 g+ w( n0 A' ^. ]) V* R( R
  6. require (msg.sender == owner);* W" X- ?$ m( h G0 {9 P) Q
  7. _; 2 a( s* r! W/ x/ j5 i$ R8 w
  8. } - p/ i, S) c% Z& {* p! b# ]
  9. function initOwner() external {1 C! w3 T; m. W9 Q& J, V
  10. require (owner == address(0)); ! X, c- c& M/ p, D9 U# h, e
  11. owner = msg.sender;2 H+ @6 B* X$ d d' p, j/ D
  12. } - [7 D) U; H2 f3 a1 P
  13. function addPlayer(address _player, uint _points)2 N: n" J2 x! W. ~2 @
  14. public onlyOwner % `+ j" e" L) L( c4 U
  15. { ! s$ a. D2 Q1 P+ E" G& n
  16. require (points[_player] == 0);: H0 y( }+ o) X8 m) d. V5 A$ H
  17. points[_player] = _points; 6 f- D* \5 y' ^: R
  18. } % G8 F/ N2 M% k. }) c d* ]
  19. 6 Y6 t9 _( H4 n1 N, k
  20. function setPoints(address _player, uint _points)5 D- `, a, Y5 _, j
  21. public onlyOwner 1 m3 ~, U: W; A$ A0 }9 [5 o f+ I
  22. { % P: [& n; m9 l, y3 ?
  23. require (points[_player] != 0); 9 j) v) W# g6 X# L4 v
  24. points[_player] = _points; : k+ d Z3 t- m- m" k3 O
  25. } ; w; L. H U$ h6 }. k" M
  26. }
复制代码
. D5 l& Z3 a& N* d" ^5 r- H2 G+ K& b n 下一步是部署这个执行合约以及我们的代理合约。然后,再调用代理合约的upgradeTo(address) 函数,同时pass掉执行合约的地址。 , c9 o# \0 C# p7 {/ u# t! s$ A# f+ J1 ]0 {/ V' w$ c 你可能注意到,在这个执行合约中,甚至没有声明totalPlayers变量,我们可以升级这个执行合约,其中具有 totalPlayers变量,这个新的执行合约看起来会是这样的:7 T( J9 ~5 D K/ I7 J) H
  1. contract ImplementationV2 is ImplementationV1 {6 c+ C) q5 [( F) l0 j# j9 w2 h; ]
  2. uint public totalPlayers;- H1 K3 i* Y+ x6 {; ]- ]
  3. / Z/ J) y! `- ]- \( o) n
  4. 6 P K: E; n9 I0 h
  5. function addPlayer(address _player, uint _points) : M5 M% N- q+ k0 S. N8 A
  6. public onlyOwner# {8 d& \! T2 P0 D4 n
  7. { # c9 V% E. Z( p8 v
  8. require (points[_player] == 0);' @6 T- K7 O8 K" R8 H, J/ Z: M- e
  9. points[_player] = _points; 6 L4 l0 F5 o1 \
  10. totalPlayers++;8 x# ^* x, J: G( w# R( P
  11. }4 O9 y+ R) Y" E$ |7 o# N; O
  12. }
复制代码
* a% \/ Y0 X. q: q% R# b8 d/ N9 d9 Q7 ^4 q4 m, \2 X 而要升级这个新的执行合约,我们需要做的,就是在网络上部署这个合约,然后,嗯你猜对了,就是调用代理合约的upgradeTo(address)函数,并同时pass掉我们新执行合约的地址。现在,我们的合约已演变为能够保持跟踪 totalPlayers,同时仍然为用户提供相同的地址。& _5 Y, f$ p/ q% E7 ~ 1 x9 U: ?0 [1 @这种方法是强大的,但也存在着一些局限性。主要关注的一点是,代理合约拥有者(proxyOwner)有太多的权力。而且,这种方法对复杂的系统而言是不够的。对于构建具有可升级合约的 dApp而言,组合主从合约以及非结构化可升级存储代理合约,会是更为灵活的一种方法,这也是作者所在的GovBlocks所使用的方法。3 i5 ]4 e( `& c2 K5 q8 a( |6 _6 }! j # J. U6 [. O! k, Z . `' w* N' [5 p: J* [# | 结论 : 6 ]" y8 O6 @" Z; Q# [3 g 非结构化存储代理合约,是创建可升级智能合约最先进的技术之一,但它仍然是不完美的。毕竟,我们并不希望dApp所有者对dApp具有不当的控制权。如果开发者拥有了这种权力,那这个dapp还能称之为去中心化应用吗?在这里,我建议读者可以阅读下Nitika提出的反对使用onlyOwner的论点。你也可以在GitHub上窥探到我们的代理合约。$ i9 T7 m' e; @, V 希望这篇文章可以帮助你创建可升级的智能合约。 * C% g' T# N; p' P! r8 ]同时向Zepplin在代理技术方面进行的工作致敬。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

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

    0

  • 关注

    0

  • 主题

    2