Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

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

本版积分规则

成为第一个吐槽的人

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

    0

  • 关注

    0

  • 主题

    2