Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

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

本版积分规则

成为第一个吐槽的人

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

    0

  • 关注

    0

  • 主题

    2