Там 2 контракта A
и B
, A
imports B
, где B
находится библиотека, которую можно обновить. Чтобы добиться этого, я попытался заменить B
контракт прокси и контракт делегата.
Теперь у нас есть контракты Foo
, которые импортируют прокси-контракты Bar
, указывающие на делегированный контракт ZeroDelegate
.
Foo.sol
pragma solidity ^0.4.18;
import './Bar.sol';
contract Foo {
uint storageData;
Bar bar;
address barContractAddress;
constructor(address _barContractAddress) public {
barContractAddress = _barContractAddress;
}
function set(uint x) public {
storageData = x;
}
function get() view public returns (uint) {
return storageData;
}
function baz() public returns (uint) {
bar = Bar(barContractAddress);
storageData = bar.baz(storageData);
}
}
Бар.соль
pragma solidity ^0.4.18;
import './Proxy.sol';
contract Bar is Proxy {
function baz(uint x) public returns (uint) {
return x * x;
}
}
ZeroDelegate.sol
pragma solidity ^0.4.18;
contract ZeroDelegate {
function baz(uint x) public returns (uint) {
return x * 0;
}
}
Прокси.sol
pragma solidity ^0.4.18;
import "zeppelin-solidity/contracts/ownership/Ownable.sol";
contract Proxy is Ownable {
event Upgraded(address indexed implementation);
address internal _implementation;
function implementation() public view returns (address) {
return _implementation;
}
function upgradeTo(address impl) public onlyOwner {
require(_implementation != impl);
_implementation = impl;
emit Upgraded(impl);
}
function () payable public {
address _impl = implementation();
require(_impl != address(0));
bytes memory data = msg.data;
assembly {
let result := delegatecall(gas, _impl, add(data, 0x20), mload(data), 0, 0)
let size := returndatasize
let ptr := mload(0x40)
returndatacopy(ptr, 0, size)
switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
}
Теперь мы сначала развертываем контракты Foo
, Bar
, ZeroDelegate
.
bar = await Bar.new()
foo = await Foo.new(bar.address)
zeroDelegate = await ZeroDelegate.new()
И foo.baz()
возводит число 2 в квадрат 4.
x = await foo.baz()
console.log(x.toNumber()) // 4
Далее мы улучшаем Bar
контракт до ZeroDelegate
, но foo.baz()
все еще возводим число 4 в 16.
await bar.upgradeTo(zeroDelegate.address)
bar = _.extend(bar, ZeroDelegate.at(bar.address))
await foo.baz()
x = await foo.get()
console.log(x.toNumber()) // 16, but expects 0
Однако, если бы мы повторно развернули Foo
контракт, он использовал бы обновленный файл Bar
. Почему это так и как мы можем позволить Foo
использовать обновленные Bar
функции без повторного развертывания Foo
, что противоречит цели использования обновляемого контракта.
foo = await Foo.new(bar.address)
await foo.baz()
x = await foo.get()
console.log(x.toNumber()) // 0
Вам нужно сделать резервную функцию с именем baz(), а затем обращаться с Bar.sol так же, как с ZeroDelegate (как с ненаследуемым внешним контрактом — возможно, на самом деле библиотекой). Делегированный вызов будет отправлен этому внешнему контракту (на который в данный момент указывает _imp) и будет использовать логику внутри этого контракта. При обновлении _imp на адрес Bar или ZeroDelegate, чтобы переключить его поведение.
Вот рабочий пример с обновляемой функцией TokenURI:
https://github.com/clovers-network/clovers-contracts/blob/master/contracts/Clovers.sol
, указывающий на:
https://github.com/clovers-network/ клеверы-контракты/blob/мастер/контракты/CloversMetadata.sol