Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

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

本版积分规则

成为第一个吐槽的人

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

    0

  • 关注

    0

  • 主题

    2