Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

用香烟做的云
141 0 0
现如今,整个密码货币生态系统都是由智能合约所驱动!不管我们有多小心,或者我们的代码测试工作做得有多好,如果我们创建的是一个复杂的系统,那我们就有必要更新合约逻辑,以修补其存在的漏洞,或者添加必要的缺失功能。有时候,由于EVM虚拟机的更改或者被新发现的漏洞,我们可能需要去升级我们的智能合约。8 R! \5 q \; t1 D; j 一般来说,开发人员可以很容易地升级他们的软件,但区块链的情况是不一样的,因为它们有着难以更改的属性。如果我们部署了一个合约,这就好比是泼出去的水。然而,如果我们使用适当的技术,我们可以在不同的地址部署一个新的合约,并使得旧合约无效。下面是一些最常见的,创建可升级智能合约的方法。8 X$ w$ c* N+ ]3 c: ]) C ! D9 U; R/ G! g/ z r( s0 A 2 ?, h& H& _# O, ~6 ^" W5 b8 q; Z 主从合约(Master-Slave contract) 3 o9 \6 h: h6 x0 m2 _0 b8 b4 x 0 \" y0 p, ?$ O( p' B5 w主从技术,是可实现升级智能合约最为基础也是最容易理解的技术之一。在这种技术当中,我们部署一个主合约,以及其他合约,其中主合约负责存储所有其他合约的地址,并在需要时返回所需的地址。当这些合约需要和其它合约进行沟通时,它们会充当从合约,从主合约那里获取其它合约的最新地址。为了升级智能合约,我们只需要在网络上部署它,并更改主合约中的地址。虽然这远不是发展可升级智能合约的最佳方式,但它确是最简单的。这种方法存在着很多的局限性,其中之一是,我们不能轻易地把合约的数据或资产迁移到新合约中。5 v, L6 [* o3 C8 j # \. a# G, b9 m1 D) N2 d 永久存储合约(Eternal Storage contract % O8 ^: ]& g9 P ' X" X0 d% Z7 i' n在这种技术当中,我们将逻辑合约和数据合约彼此分离。数据合约应该是永久并且不可升级的。而逻辑合约可以根据需要进行多次升级,并将变化通知给数据合约。这是一项相当基本的技术,并且存在着一个明显的缺陷。由于数据合约是不可升级的,数据结构中需要的任何更改,或数据合约中存在的漏洞,都会导致所有数据变得无用。这种技术的另一个问题是,如果逻辑合约想要访问/操作区块链上的数据,那么这个逻辑合约将需要进行外部调用,而外部调用会消耗额外的gas。通常情况下,这种技术会和主从技术相结合,以促进合约间的通信。 5 C5 o0 u1 C% N$ V+ }( j : Z$ s$ A5 n% N1 j7 ]. k 可升级存储代理合约 " d2 [; i. L3 f% C4 ] 8 V C- J A- q0 h 我们可通过使永久存储合约充当逻辑合约的代理,以此防止支付额外的gas。这个代理合约,以及这个逻辑合约,将继承同一存储合约,那么它们的存储会在EVM虚拟机中对齐。这个代理合约将有一个回退函数,它将委托调用这个逻辑合约,那么这个逻辑合约就可以在代理存储中进行更改。这个代理合约将是永恒的。这节省了对存储合约多次调用所需的gas,不管数据做了多少的更改,就只需要一次委托调用。 # a- R0 l4 h0 |& h. u这项技术当中有三个组成部分:& V, O8 f( o7 h2 d j) N 代理合约(Proxy contract):它将充当永久存储并负责委托调用逻辑合约; 2 E( ~( h, W8 l1 g; ]逻辑合约(Logic contract):它负责完成处理所有的数据;6 t9 `1 ]2 Z6 ?* o 存储结构(Storage structure):它包含了存储结构,并会由代理合约和逻辑合约所继承,以便它们的存储指针能够在区块链上保持同步;# _+ }$ q$ \" [9 l, ~/ x# H/ n 9 O6 K6 R* ^! D" J; i; V ( X5 A9 E# l) a& l% x" J - M5 Y, F& P0 \5 J6 l( Q }" i委托调用 3 }" h* _ F6 o$ c/ j0 ]2 V5 g3 m0 c+ {6 d% G- _% H 该技术的核心在于EVM所提供的DELEGATECALL操作码,DELEGATECALL就像是一个普通的CALL 调用操作码,不同之处在于目标地址上的代码是在调用合约上下文中执行的,而原始调用的msg.sender以及msg.value将被保留。简单说,DELEGATECALL基本上允许(委托)目标合约在调用合约的存储中做它任何想做的事情。# G% i. v& B+ H 我们将利用这一点,并创建一个代理合约,它将使用DELEGATECALL操作码委托调用逻辑合约,这样我们就可以在代理合约中保持数据的安全,同时我们可以自由地更改逻辑合约。5 R; a8 J8 [' m/ S+ k d/ H9 s) G# u # \% v! o. H5 |. b& c& v& j : ^. O: V& U% f- X如何使用可升级存储代理合约( E$ f# Z3 a; J- s3 S; l + t1 ]; q, u5 m( ?+ a让我们深入研究一下细节。我们需要的第一个合约是存储结构。它将定义我们需要的所有存储变量,并将由代理合约和执行合约所继承。它看起来会是这样的:9 L+ }+ ^8 g- q+ s
  1. <b>contract StorageStructure { : }4 L/ } |' V$ b" s) g4 P \
  2. address public implementation; 8 N& {6 s! x. }4 c, n) t7 D
  3. address public owner;1 q& q* {4 T3 M, c: A
  4. mapping (address => uint) internal points;4 s" g+ |0 b* \# ~: r
  5. uint internal totalPlayers; 5 a. O, @+ g# E' v) E" ~* P
  6. }</b>
复制代码
v8 C/ [( p4 N4 q# w& Q5 M1 @4 i, ~! J( I! \# |' L: j 我们现在需要一个执行/逻辑合约。让我们创建一个简单版的合约,在添加新玩家时不会增加totalPlayers计数器的数字。 / O9 ]" u9 R4 g' Z @! S8 P& i3 W. E/ i
  1. contract ImplementationV1 is StorageStructure {9 W5 r5 _+ |/ t6 @/ R9 N
  2. modifier onlyOwner() { 0 k. Y: `- t6 S- ?2 e
  3. require (msg.sender == owner); 9 y; B0 N2 R7 v- s, b
  4. _; 1 L* J2 [7 a1 [0 p; c2 Y; p V
  5. } % A" N! s9 y# C, Q# r1 m
  6. function addPlayer(address _player, uint _points)8 [: P& `1 K: |$ r
  7. public onlyOwner. |, G3 y# {. E- f; U
  8. {7 J2 Q8 ]" h, i+ X3 G
  9. require (points[_player] == 0); % V0 g* s8 C9 C4 S
  10. points[_player] = _points; . n+ k1 h, B3 [
  11. }6 n" s* d+ a7 m& L- A( y8 Y
  12. function setPoints(address _player, uint _points): T2 W/ T/ F4 r: k7 j" D! g# g
  13. public onlyOwner0 X& |- K# Z- G" B. I( `/ i
  14. { 8 O# p* e' O; D3 U8 d! m' z3 g% a! u
  15. require (points[_player] != 0); 1 a* B5 ]3 q; d. U7 i( J
  16. points[_player] = _points; # k( j0 S3 j- T8 Y: d0 f1 m
  17. }0 x3 ?/ |4 L' y, l4 B
  18. }
复制代码
* m% \. ]. _7 N6 o 1 t) ? b$ ], [; U {/ o, m 5 ?) H$ Z% y$ l% u 下面就是最关键的部分:代理合约; 3 k- w# U+ h" E( ?/ ~
  1. contract Proxy is StorageStructure { . V1 {1 y( z. G* ~2 ^
  2. / l! _! B' ?3 H& q/ _8 I! X
  3. modifier onlyOwner() { ! z: C. f( z4 D- [/ N2 A, s- ^
  4. require (msg.sender == owner); ' B* v8 |: P& q' d6 R1 Y. R' _) M
  5. _; 7 s; ?5 j7 O! j, ?2 i p3 ]
  6. }, S, w# M+ G/ H# }7 s
  7. /** 0 L1 u% c. H4 E# Q4 r0 n1 N
  8. * @dev constructor that sets the owner address ; D( n4 x+ q$ |
  9. */2 R" _5 Q% ~) M: l9 I; ?; s$ J9 N9 ]$ s
  10. constructor() public {4 H. J+ J+ B& L* u. y
  11. owner = msg.sender;4 O; {) @; B) \$ ~
  12. } 9 f+ ]" u# q1 ~+ Q" W! @3 H
  13. /**( U) l$ ^" J5 a) X+ ]7 S
  14. * @dev Upgrades the implementation address 6 ~, J0 c9 c3 E5 D2 P/ n/ ~" t
  15. * @param _newImplementation address of the new implementation" S0 a( A1 F( C% [8 @1 f1 X
  16. */, y, u/ p. I2 I: G4 U7 ^2 z- O: L
  17. function upgradeTo(address _newImplementation)$ T) \* I5 t. d6 M
  18. external onlyOwner 0 K6 S! W+ W" }! j
  19. {4 n+ \! }8 M, g6 D0 ^2 \
  20. require(implementation != _newImplementation); ) _/ Q2 v" U0 v& N/ D
  21. _setImplementation(_newImplementation); 0 ?5 f$ u0 S; d4 Y0 s
  22. }) d1 L* B( r/ `$ r
  23. /** 9 ?# d O8 n+ c4 y: S; A% b
  24. * @dev Fallback function allowing to perform a delegatecall ' O1 @, J( N. X# N
  25. * to the given implementation. This function will return # M/ w7 Z3 g! ]1 O% g5 T+ K5 G
  26. * whatever the implementation call returns A+ q6 R9 l: a, p* C- t% a
  27. */ $ m4 C) q& @, ~3 L
  28. function () payable public { 4 V1 O' Y6 a# h: M$ k
  29. address impl = implementation; & j% L# E1 {9 e, [
  30. require(impl != address(0)); L5 ?; r. D' n+ n
  31. assembly {! H4 ?3 x2 c6 |' w7 k' c9 ]$ V
  32. let ptr := mload(0x40)- B5 r+ m2 o& z# m, u. m
  33. calldatacopy(ptr, 0, calldatasize) ; s5 ]! v. |; |7 }! q) s
  34. let result := delegatecall(gas, impl, ptr, calldatasize, 0, 0) ; s2 o' s5 k0 {' K; Y( R6 x
  35. let size := returndatasize 2 J( Y( V( ~7 C5 I- s; I; o
  36. returndatacopy(ptr, 0, size) + c; y# v$ q* i0 q1 |" w
  37. switch result9 @- v' O' V, {2 ^
  38. case 0 { revert(ptr, size) }$ e2 m* U6 K* v6 m
  39. default { return(ptr, size) }/ P7 d7 U1 R' d7 [: W" ?
  40. } # [- h2 p* S) K) T# g7 g/ i
  41. }/ i! j C2 G" O4 y L7 `/ `
  42. 8 f. ^- e/ Z. ]; g( f" S. r/ D
  43. /**& O T! H+ c6 W
  44. * @dev Sets the address of the current implementation " O4 ?. O! T- v: o. f# } Y
  45. * @param _newImp address of the new implementation 6 `+ F4 \: P5 Y. s; J
  46. */ 8 K/ }4 @! s3 M$ O6 }
  47. function _setImplementation(address _newImp) internal {. x! i9 |7 e1 B/ y1 T! X0 [
  48. implementation = _newImp; 3 v; {0 g( P1 P- T$ X! O" z$ c
  49. } 8 n0 `0 }. s+ }/ i$ {
  50. }
复制代码
, L4 b- F2 q" N7 J' C) l* D+ { % h/ V% [" V4 u7 L. c0 z5 m; ~ 6 f5 c$ I2 i% C为了让合约生效,我们首先需要部署代理合约以及ImplementationV1合约,然后调用这个代理合约的upgradeTo(address)函数,同时pass掉我们的ImplementationV1合约地址。现在,我们可以忘记这个ImplementationV1合约的地址,并把代理合约的地址作为我们的主地址。# P- ^, J/ Z2 X1 }, ` 为了升级这个合约,我们需要创建一个新的逻辑合约实现,它可以是这样的: 6 k" R3 ?3 _# S+ D
  1. contract ImplementationV2 is ImplementationV1 { 7 |, ` s2 i! u; O
  2. 5 _$ x' q l) X* e _0 Y
  3. ( y6 ?! d' w7 ?$ ~, h: T) q$ h
  4. function addPlayer(address _player, uint _points)9 V d8 W6 r7 { f
  5. public onlyOwner8 O0 M& ?( R$ Y" E* t
  6. {# \3 x- }2 R G
  7. require (points[_player] == 0);' p( z O8 l4 Y9 f% Q9 M
  8. points[_player] = _points; % ^4 U, K+ s! E6 k# J D
  9. totalPlayers++;) ? x' z- i4 b6 t# ~! e
  10. }$ |/ S# |2 f6 d/ w8 c k# J
  11. }
复制代码
! n1 c* Z, G) P( i4 Q0 A0 p" b5 _0 D: M 你应该注意到,这个合约也继承了存储结构合约(StorageStructure contract),尽管它是间接地。3 U! s2 M" f3 e: \ 所有的执行方案都必须继承这个存储结构合约,并且在部署代理合约后不得进行更改,以避免对代理的存储进行意外覆盖。 : m2 U% P3 ]% e2 _9 S) e9 F $ E& t; @2 I# P+ d为了实现升级,我们在网络上部署这个合约,然后调用代理合约的upgradeTo(address) 函数,同时pass掉ImplementationV2合约的地址。9 }0 m" o% g; W! N7 a 这种技术,使得升级合约逻辑变得相当容易,但它仍然不允许我们升级合约的存储结构。我们可以通过使用非结构化的代理合约来解决这个问题。 K8 _+ ~$ T8 l1 x / q# u2 [7 V; i# Q. Z( j c) z ' a3 V9 t# ?! c8 n1 J0 J 1 R6 b- E# T8 ]2 j4 O9 }8 e( _非结构化可升级存储代理合约 " e, Y9 s2 z* z, U. y2 e ' Q" f, G( _2 [! d5 `, \! u这是当前最先进的,可实现智能合约升级的方法之一。它通过保存合约地址以及在存储中固定位置所有者的方法,以实现它们不会被执行/逻辑合约提供的数据所覆盖。我们可以使用sload以及sstore操作码来直接读取和写入由固定指针所引用的特定存储槽。7 u8 w {; A# Q7 Y% n$ d! B 此方法利用了存储中状态变量的布局,以避免逻辑合约覆盖掉固定位置。如果我们将固定位置设置为0x7,那么在使用前7个存储槽后,它就会被覆盖掉。为了避免这种情况,我们将固定位置设置为类似keccak256(“org.govblocks.implemenation.address”)., h% G, D {6 u; h 这消除了在代理合约中继承存储结构合约的需要,这意味着我们现在也可以升级存储结构了。然而,升级存储结构是一项棘手的任务,因为我们需要确保,我们所提交的更改,不会导致新的存储布局与先前的存储布局不匹配。, c& ^* ]9 d x- Z6 M9 D ! ^4 P- @! B, e$ ~# T 这项技术有两个组成部分。6 _( F: q0 E9 V$ }8 V 1、代理合约:它负责将执行合约的地址存储在一个固定的地址当中,并负责委托调用它; 2、执行合约:它是主要合约,负责把我逻辑以及存储结构;4 X7 L% g) b1 s8 R; | 你甚至可以将这项技术用于你现有的合约,因为它不需要对你的执行合约进行任何更改。 % U$ [ J% }4 G2 [这个代理合约会是这样子的: * T# @+ u5 n" f. U! z7 F
  1. contract UnstructuredProxy { * D' y9 ~# {1 F/ r9 `4 l# Q
  2. 0 j) ]& ^6 {1 r
  3. // Storage position of the address of the current implementation6 J8 n" I& X: k j
  4. bytes32 private constant implementationPosition = / ]! V* L' o" d0 f+ n
  5. keccak256("org.govblocks.implementation.address");0 |1 C7 w: e9 j8 |: `" _
  6. // Storage position of the owner of the contract # s7 c$ N- G) J8 \# r! d% j0 l
  7. bytes32 private constant proxyOwnerPosition = 8 X) w8 g- ]0 V8 L' P
  8. keccak256("org.govblocks.proxy.owner"); 9 f% {- J) H1 \8 B" a& u
  9. /** a2 x7 w# X5 |7 w+ R
  10. * @dev Throws if called by any account other than the owner. / E* B z# V' @5 j2 o
  11. */) I$ J4 t' U$ w. a) C2 l" m
  12. modifier onlyProxyOwner() { 7 U7 B7 W( q2 i% D
  13. require (msg.sender == proxyOwner());- P. E/ {' T7 O) ^0 H8 n O
  14. _; ( O$ y9 F, r( Q, i
  15. }1 F; \7 g7 i4 W+ Y- N
  16. /** 6 B# `0 t5 |7 }) m9 B
  17. * @dev the constructor sets owner5 \7 a( h5 ~! j. s- z! t, U0 J
  18. */ ( K/ A6 D( }3 V0 e: y! r8 q& {
  19. constructor() public { ( d: n' T( c0 R( e/ ?
  20. _setUpgradeabilityOwner(msg.sender); 8 _% b! |& k' X5 H* L6 C1 _8 ^
  21. } + |+ T! @8 u/ [: R+ p
  22. /**; R4 h3 T8 v% ?
  23. * @dev Allows the current owner to transfer ownership ) X. t% S. t8 D# R) y0 A
  24. * @param _newOwner The address to transfer ownership to . F% ?0 E5 y8 X6 I& J
  25. */ " |5 S5 O" [" w, Y. \- c+ q
  26. function transferProxyOwnership(address _newOwner) 6 }, u( M. e0 \- d) U5 }8 A
  27. public onlyProxyOwner$ D5 `* _; w3 e
  28. {* M5 ]3 ^1 W3 A+ r! |
  29. require(_newOwner != address(0)); + |8 g" E8 o: @0 O5 U W6 y
  30. _setUpgradeabilityOwner(_newOwner);% R3 x6 c/ c7 A% H7 @
  31. }- q' @5 F& A! L4 C2 s- O+ H
  32. /**# N6 I) l& m- d0 I" h
  33. * @dev Allows the proxy owner to upgrade the implementation5 a) }9 o7 @% {5 _: X6 c3 r
  34. * @param _implementation address of the new implementation 6 T: J& i! `& j/ {- N
  35. */6 e6 q" ?* |2 s
  36. function upgradeTo(address _implementation)& ^* Y# U6 W/ C4 \9 Y/ e
  37. public onlyProxyOwner 3 e$ Y7 k l0 q7 D( n- z* L
  38. { ) |, z/ U* x/ [0 Z5 q% f7 J1 T6 N
  39. _upgradeTo(_implementation);* \* s; |) f5 a/ f& O
  40. } % T/ J0 `6 [, c) H& n- s
  41. /** ! ]: W7 J y1 f3 a
  42. * @dev Tells the address of the current implementation - `, f: J% c4 D8 J- S
  43. * @return address of the current implementation " D o2 s, Q# o; C' f+ c1 a/ Q! m2 H
  44. */) M; N4 D- n K7 `0 n" a
  45. function implementation() public view returns (address impl) {# v/ o. d6 v* f1 a2 a1 s8 w6 U( ~
  46. bytes32 position = implementationPosition; 9 U# F3 V: _* I2 T' f7 m
  47. assembly { " j: ~9 ~9 ?5 F' R- J
  48. impl := sload(position) ' S# }: N! o6 J1 j
  49. }# X, Y1 f# J# |- S' N0 |
  50. } 7 P& t& h, \6 U! `
  51. /** " h$ g3 k' g. F- [0 B
  52. * @dev Tells the address of the owner 5 D; l x& F+ o8 V" `, l
  53. * @return the address of the owner , r5 g) H: f- W) k6 t' {
  54. */ % ~2 t5 u) i. v% k! t" g* @: M
  55. function proxyOwner() public view returns (address owner) { ) S+ T8 l* ^) c' V! g
  56. bytes32 position = proxyOwnerPosition; ! Q6 u5 x9 [7 K
  57. assembly {# y8 @9 I! Y# p$ {) O h
  58. owner := sload(position). E, \9 e6 Q g$ M: \
  59. } / T. o8 D# e0 V* H
  60. }7 S8 N2 t0 ?6 ?$ j+ c
  61. /** , s: M/ m# J9 z6 Z
  62. * @dev Sets the address of the current implementation+ J) i' ~) K2 H$ U' j' l: k0 _4 N
  63. * @param _newImplementation address of the new implementation ' l! z5 Y2 t; q* b8 E
  64. */. t2 G1 t4 B5 j2 v# w0 X
  65. function _setImplementation(address _newImplementation) / M5 t) n# I) F, h* R9 L9 S
  66. internal( i' U$ K' @; R: e
  67. { 8 P! X4 O3 n* L- t- b
  68. bytes32 position = implementationPosition; , i1 J; ^3 P1 R9 _
  69. assembly {4 D. g3 y9 c/ }4 V" n; G4 ^' x% i
  70. sstore(position, _newImplementation)1 y+ V* G) `* ~$ {9 n
  71. }. `- E7 s8 |4 [4 w; Q
  72. } & P5 Z9 d% n& t. t
  73. /**3 ~* R8 t; a; b( y4 E
  74. * @dev Upgrades the implementation address 5 u1 H# _" _( b R, w) Z
  75. * @param _newImplementation address of the new implementation5 ~6 e, ^; C' B5 E1 _* p7 K
  76. */4 G. e2 Q D( K* E" N; e' z/ g
  77. function _upgradeTo(address _newImplementation) internal { 1 z, w7 F/ j& A$ Z" e
  78. address currentImplementation = implementation(); ) E4 C2 s! R6 `
  79. require(currentImplementation != _newImplementation); " l: f$ n- P) A6 C
  80. _setImplementation(_newImplementation); 2 [% Z$ g( a9 {7 r( E$ P, X1 d9 O
  81. } ) U3 R- g' x) x
  82. ; @( a( {2 I% q8 v, t2 m# M
  83. /** 4 R7 }0 D$ n( J/ [" D
  84. * @dev Sets the address of the owner+ a$ E u' m8 a- D% k ?$ L. T& h- X' [
  85. */ * y- m. Q# T; [0 D
  86. function _setUpgradeabilityOwner(address _newProxyOwner) : w/ g3 k" \9 [% j
  87. internal+ R; H6 L# x X& I% L
  88. { % L8 M! y. a9 n. P4 _: ]' e
  89. bytes32 position = proxyOwnerPosition; " V6 r; `, d3 D5 k
  90. assembly {3 n+ X; X4 H9 u* j8 P& u- K( \
  91. sstore(position, _newProxyOwner)& w+ i" ~. o0 R# E
  92. } 0 W5 Q$ a1 z# ^. u) B8 c7 d7 R/ `
  93. } % u. e* D! y6 g! I% |
  94. }
复制代码
# U( D( z" j3 m6 @4 M W/ n+ F, L4 P ( S* H# o- g# M 如何使用非结构化可升级存储代理合约? 0 O: t6 J& R( z* e& ]: M( [$ o8 X" {5 F& E 使用非结构化可升级存储代理合约是非常简单的,因为这种技术几乎可以处理所有现有的合约。想要使用这种技术,你只需要遵循以下步骤:3 ~. D0 u0 v. D9 x* e5 O+ x 部署代理合约和执行合约;* F% h4 U8 F4 ` y 调用代理合约的upgradeTo(address)函数,同时pass掉执行合约的地址。/ M2 ^$ m1 W( J0 E4 e4 p6 [% B 我们现在可以忘掉这个执行合约地址,然后把代理合约的地址作为主地址。 1 u1 i6 h9 A: I4 H" o8 W. G% q4 ] 2 p/ W% S8 T5 C3 h1 V: j而要升级这个新实施的合约,我们只需要部署新的执行合约,并调用代理合约的upgradeTo(address) 函数,同时pass掉这个新执行合约的地址。就是这么简单! * S; c/ {+ r* W; b- {9 o/ O让我们简单举个例子。我们将再次使用上述可升级存储代理合约中使用的同一逻辑合约,但是我们不需要用到存储结构。因此,我们的ImplementationV1合约看起来会是这样的:3 `3 H: W Z6 T0 U' C; C
  1. contract ImplementationV1 { B1 `* n! U2 X
  2. address public owner; 4 ^1 ^# M) G- g. e D
  3. mapping (address => uint) internal points; 9 {0 l* `, s/ B _% w( ?
  4. 9 G) b3 l" {7 `- g& D$ {
  5. modifier onlyOwner() { ) y9 k5 e1 A% O3 M% W; n \7 Y: F
  6. require (msg.sender == owner);) Q7 Z; c! E8 m: {
  7. _; ; U( T7 R9 R1 l' ^( n
  8. } ; |' k; I b% ?
  9. function initOwner() external { ! _1 M+ [9 P* Y- Q4 S9 P6 v* o* H+ a9 k
  10. require (owner == address(0)); 4 U- f! F# J# }3 s6 u
  11. owner = msg.sender; , W) g, C; p9 v5 `% O7 J; _; y( f
  12. }3 t9 R% t" ]; s, N! H
  13. function addPlayer(address _player, uint _points)- G: G* G1 |# C" q7 t: ?8 i$ w! r
  14. public onlyOwner/ _7 o7 Z6 s0 H0 M n5 U: Y
  15. {8 f2 P4 Y, a. |+ G: c5 P
  16. require (points[_player] == 0); 8 Y/ w' \: S0 a; k' D' u- s7 W# d% a
  17. points[_player] = _points;2 G9 f5 f/ t1 Q g5 q
  18. } 4 j: {! K8 R! W+ w, ^2 o
  19. 1 S6 {+ z* k2 p4 D+ a0 B- D8 O0 K
  20. function setPoints(address _player, uint _points)6 P( F$ K+ B5 u e' e! e/ v
  21. public onlyOwner 3 ]7 D8 e5 F6 o; e4 K
  22. {* O. H& H- M( Q$ G2 X" r" t( J
  23. require (points[_player] != 0);- u7 _7 ~2 t: v' }; h3 ?1 u7 h P
  24. points[_player] = _points;( T; Q, c9 e* D, ?4 A6 ~% q
  25. } `3 ] ~3 z; W
  26. }
复制代码
! y- Y; U1 u' v" ^ 8 g4 ^7 C+ K8 u$ A9 Q b下一步是部署这个执行合约以及我们的代理合约。然后,再调用代理合约的upgradeTo(address) 函数,同时pass掉执行合约的地址。 ' m% A5 w0 N, q* M! \3 q' o0 V' K2 }1 i- ^8 b3 P 你可能注意到,在这个执行合约中,甚至没有声明totalPlayers变量,我们可以升级这个执行合约,其中具有 totalPlayers变量,这个新的执行合约看起来会是这样的:# y' q$ O$ q* L# f1 p5 D Q
  1. contract ImplementationV2 is ImplementationV1 { 9 ] P8 J- {8 @& Q" Y
  2. uint public totalPlayers; ; P5 u, I. R* ^/ t0 M1 K
  3. 9 y# U6 V3 h+ b# U1 B: @& b4 `( I
  4. 2 X1 {4 B9 _, H8 K% [7 H
  5. function addPlayer(address _player, uint _points) 5 o( O& [3 Z* i2 p0 O
  6. public onlyOwner / @: K( y. n( T7 `
  7. { 9 Y- v+ ?" M/ g# Q2 f, ^
  8. require (points[_player] == 0); 1 C3 q% e0 @: Z! U& Q4 Z
  9. points[_player] = _points; G& x1 H! d6 {6 b+ n, ?4 S4 X
  10. totalPlayers++;- w# R; d) t4 _- D$ @
  11. } 9 S5 j6 s4 ~# c1 @1 M6 a$ r. h
  12. }
复制代码
! G# W' b# e. ]* S7 {& \9 S3 f. j/ l9 A& n4 w 而要升级这个新的执行合约,我们需要做的,就是在网络上部署这个合约,然后,嗯你猜对了,就是调用代理合约的upgradeTo(address)函数,并同时pass掉我们新执行合约的地址。现在,我们的合约已演变为能够保持跟踪 totalPlayers,同时仍然为用户提供相同的地址。 8 T+ D( d; \, e6 K" U7 ]2 o2 k 3 V3 Y$ d$ W# r4 M这种方法是强大的,但也存在着一些局限性。主要关注的一点是,代理合约拥有者(proxyOwner)有太多的权力。而且,这种方法对复杂的系统而言是不够的。对于构建具有可升级合约的 dApp而言,组合主从合约以及非结构化可升级存储代理合约,会是更为灵活的一种方法,这也是作者所在的GovBlocks所使用的方法。 - V( [5 V! d4 g/ f # s* Z. E6 P7 {; X R 7 f" @9 d }8 @ I 结论 : # L. t8 T( }" o- r: p( K' @ 非结构化存储代理合约,是创建可升级智能合约最先进的技术之一,但它仍然是不完美的。毕竟,我们并不希望dApp所有者对dApp具有不当的控制权。如果开发者拥有了这种权力,那这个dapp还能称之为去中心化应用吗?在这里,我建议读者可以阅读下Nitika提出的反对使用onlyOwner的论点。你也可以在GitHub上窥探到我们的代理合约。! L1 `* h. w1 M) X: q 希望这篇文章可以帮助你创建可升级的智能合约。* ?4 s( N+ j4 ]$ j# M; S$ Z 同时向Zepplin在代理技术方面进行的工作致敬。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

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

    0

  • 关注

    0

  • 主题

    2