Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

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

本版积分规则

成为第一个吐槽的人

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

    0

  • 关注

    0

  • 主题

    2