Solidity
可升級的代理庫拋出任何功能,如何連接代理和主合約
我正在嘗試通過 openZeppelin 使用代理研究的技術來實現代理庫。
我有 4 個合約 1. 帶有邏輯的庫 2. 包含邏輯介面的庫 3. 充當代理的合約 4. 通過委託給庫的介面呼叫代理的主合約
我認為我遇到的問題。儘管在部署時連結了介面,但我的主契約不會將其呼叫發送到代理。
我製作了一個測試文件,用於部署契約並連結庫。我可以確認該庫已連結到代理契約。
如何正確讓 People.sol(主契約)呼叫 PeopleInterface 上的函式,創建必要的呼叫數據以向代理中的委託呼叫發出?
const PeopleProxy = artifacts.require('OwnedUpgradeabilityProxy') const PeopleLib = artifacts.require('PeopleLib') const People = artifacts.require('People') //storage //FIRST DEPLOYMENT: //1. Deploy PeopleLib //2. Deploy PeopleProxy //3. Link implementation (PeopleLib) to proxy --> For normal contracts //4. Link PeopleInterface to PeopleStorage //5. Deploy PeopleStorage //TO UPDATE: //1. Deploy new version of PeopleLib //2. Call upgradeTo on PeopleProxy contract('TestProxy', (accounts) => { describe('Deployment & initial test', () => { let peopleLib, peopleProxy, people before(async () => { peopleLib = await PeopleLib.new() peopleProxy = await PeopleProxy.new({from: accounts[0]}) People.link('PeopleInterface', peopleProxy.address) people = await People.new() await peopleProxy.upgradeTo(peopleLib.address) }) it('Should have implemented our library address in proxy', async () => { console.info(await peopleProxy.implementation(), peopleLib.address) assert.equal(await peopleProxy.implementation(), peopleLib.address, "Addresses not equal") }) it('Should register a new user', async () => { await people.registerUser("Nico", "nico@did.com", "Design is dead", "", accounts[0]) }) }) })
這些是我的契約:
pragma solidity ^0.4.23; library PeopleInterface { struct Person { string name; string email; string company; string avatar; address wallet; address[] groups; mapping (address => bytes32[]) personBounties; //groupaddress -> bounty id's use controller.getBounty(_groupAddress, bountyId) } struct People { mapping (address => Person) people; } function registerUser(People storage _people, string _name, string _email, string _company, string _avatar, address _sender) external; function updateUser(People storage _people, string _name, string _email, string _company, string _avatar, address _sender) external; function getUser(People storage _people, address _address) external view returns (string, string, string, string, address, address[]); function addGroup(People storage _people, address _group, address _sender) external; function leaveGroup(People storage _people, address _group, address _sender) external; function addBounty(People storage _people, address _group, bytes32 _index, address _sender) external; function getUserBountiesByGroup(People storage _people, address _group, address _sender) external view returns (bytes32 []); } library PeopleLib { event logRegistered(address indexed _wallet, string _name, string _email, string _company); event logUpdateProfile(address indexed _wallet, string _email, string _name, string _company, string _avatar); function registerUser (PeopleInterface.People storage _people, string _name, string _email, string _company, string _avatar, address _sender) external { _people.people[_sender].name = _name; _people.people[_sender].email = _email; _people.people[_sender].company = _company; _people.people[_sender].avatar = _avatar; _people.people[_sender].wallet = _sender; emit logRegistered(_sender, _name, _email, _company); } function updateUser (PeopleInterface.People storage _people, string _name, string _email, string _company, string _avatar, address _sender) external { _updateName(_people, _name, _sender); _updateEmail(_people, _email, _sender); _updateCompany(_people, _company, _sender); _updateAvatar(_people, _avatar, _sender); emit logUpdateProfile(_sender, _email, _name, _company, _avatar); } function _updateName (PeopleInterface.People storage _people, string _name, address _sender) public { bytes memory name = bytes(_name); if (name.length > 0) _people.people[_sender].name = _name; } function _updateEmail (PeopleInterface.People storage _people, string _email, address _sender) public { bytes memory email = bytes(_email); if (email.length > 0) _people.people[_sender].email = _email; } function _updateCompany (PeopleInterface.People storage _people, string _company, address _sender) public { bytes memory company = bytes(_company); if (company.length > 0) _people.people[_sender].company = _company; } function _updateAvatar (PeopleInterface.People storage _people, string _avatar, address _sender) public { bytes memory avatar = bytes(_avatar); if (avatar.length > 0) _people.people[_sender].avatar = _avatar; } function getUser (PeopleInterface.People storage _people, address _address) external view returns (string, string, string, string, address, address[]) { address[] memory _groups = _getGroups(_people, _address); return (_people.people[_address].name, _people.people[_address].email, _people.people[_address].company, _people.people[_address].avatar, _address, _groups); } function _getGroups(PeopleInterface.People storage _people, address _address) public view returns (address[]) { return _people.people[_address].groups; } function addGroup (PeopleInterface.People storage _people, address _group, address _sender) external { _people.people[_sender].groups.push(_group); } function leaveGroup (PeopleInterface.People storage _people, address _group, address _sender) external { for (uint i = 0; i < _people.people[_sender].groups.length; i ++) { if ( _group == _people.people[_sender].groups[i] ) { _people.people[_sender].groups = _deleteAddress(_people.people[_sender].groups, i); } } } function _deleteAddress(address[] _array, uint _index) public pure returns (address[]) { address[] memory arrayNew = new address[](_array.length-1); assert(_index < _array.length); for (uint i = 0; i<_array.length-1; i++){ if(i != _index && i<_index){ arrayNew[i] = _array[i]; } else { arrayNew[i] = _array[i+1]; } } delete _array; return arrayNew; } function addBounty (PeopleInterface.People storage _people, address _group, bytes32 _index, address _sender) external { _people.people[_sender].personBounties[_group].push(_index); } function getUserBountiesByGroup (PeopleInterface.People storage _people, address _group, address _sender) external view returns (bytes32 []) { return _people.people[_sender].personBounties[_group]; } } contract People { using PeopleInterface for PeopleInterface.People; PeopleInterface.People people; event logRegistered(address indexed _wallet, string _name, string _email, string _company); event logUpdateProfile(address indexed _wallet, string _email, string _name, string _company, string _avatar); function registerUser(string _name, string _email, string _company, string _avatar, address _sender) external { return people.registerUser(_name, _email, _company, _avatar, _sender); } function updateUser(string _name, string _email, string _company, string _avatar, address _sender) external { return people.updateUser(_name, _email, _company, _avatar, _sender); } function getUser(address _wallet) external view returns (string, string, string, string, address, address[]) { return people.getUser(_wallet); } function addGroup(address _group, address _sender) external { return people.addGroup(_group, _sender); } function leaveGroup(address _group, address _sender) external { return people.leaveGroup(_group, _sender); } function addBounty(address _group, bytes32 _index, address _sender) external { return people.addBounty(_group, _index, _sender); } } pragma solidity ^0.4.23; /** * @title Proxy * @dev Gives the possibility to delegate any call to a foreign implementation. */ contract Proxy { /** * @dev Tells the address of the implementation where every call will be delegated. * @return address of the implementation to which it will be delegated */ function implementation() public view returns (address); /** * @dev Fallback function allowing to perform a delegatecall to the given implementation. * This function will return whatever the implementation call returns */ function () payable public { address _impl = implementation(); require(_impl != address(0)); assembly { let ptr := mload(0x40) calldatacopy(ptr, 0, calldatasize) let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0) let size := returndatasize returndatacopy(ptr, 0, size) switch result case 0 { revert(ptr, size) } default { return(ptr, size) } } } } contract UpgradeabilityProxy is Proxy { /** * @dev This event will be emitted every time the implementation gets upgraded * @param implementation representing the address of the upgraded implementation */ event Upgraded(address indexed implementation); // Storage position of the address of the current implementation bytes32 private constant implementationPosition = keccak256("org.zeppelinos.proxy.implementation"); /** * @dev Constructor function */ function UpgradeabilityProxy() public {} /** * @dev Tells the address of the current implementation * @return address of the current implementation */ function implementation() public view returns (address impl) { bytes32 position = implementationPosition; assembly { impl := sload(position) } } /** * @dev Sets the address of the current implementation * @param newImplementation address representing the new implementation to be set */ function setImplementation(address newImplementation) internal { bytes32 position = implementationPosition; assembly { sstore(position, newImplementation) } } /** * @dev Upgrades the implementation address * @param newImplementation representing the address of the new implementation to be set */ function _upgradeTo(address newImplementation) internal { address currentImplementation = implementation(); require(currentImplementation != newImplementation); setImplementation(newImplementation); emit Upgraded(newImplementation); } } contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { /** * @dev Event to show ownership has been transferred * @param previousOwner representing the address of the previous owner * @param newOwner representing the address of the new owner */ event ProxyOwnershipTransferred(address previousOwner, address newOwner); // Storage position of the owner of the contract bytes32 private constant proxyOwnerPosition = keccak256("org.zeppelinos.proxy.owner"); /** * @dev the constructor sets the original owner of the contract to the sender account. */ function OwnedUpgradeabilityProxy() public { setUpgradeabilityOwner(msg.sender); } /** * @dev Throws if called by any account other than the owner. */ modifier onlyProxyOwner() { require(msg.sender == proxyOwner()); _; } /** * @dev Tells the address of the owner * @return the address of the owner */ function proxyOwner() public view returns (address owner) { bytes32 position = proxyOwnerPosition; assembly { owner := sload(position) } } /** * @dev Sets the address of the owner */ function setUpgradeabilityOwner(address newProxyOwner) internal { bytes32 position = proxyOwnerPosition; assembly { sstore(position, newProxyOwner) } } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferProxyOwnership(address newOwner) public onlyProxyOwner { require(newOwner != address(0)); emit ProxyOwnershipTransferred(proxyOwner(), newOwner); setUpgradeabilityOwner(newOwner); } /** * @dev Allows the proxy owner to upgrade the current version of the proxy. * @param implementation representing the address of the new implementation to be set. */ function upgradeTo(address implementation) public onlyProxyOwner { _upgradeTo(implementation); } /** * @dev Allows the proxy owner to upgrade the current version of the proxy and call the new implementation * to initialize whatever is needed through a low level call. * @param implementation representing the address of the new implementation to be set. * @param data represents the msg.data to bet sent in the low level call. This parameter may include the function * signature of the implementation to be called with the needed payload */ function upgradeToAndCall(address implementation, bytes data) payable public onlyProxyOwner { upgradeTo(implementation); require(this.call.value(msg.value)(data)); } }
我使用了另一種儲存原理(永恆儲存)而不是非結構化儲存。感謝 Maraoz:https ://github.com/maraoz/solidity-proxy
鑑於我在測試文件中更改了一些內容,它現在可以工作了。
這是新的代理:
pragma solidity ^0.4.23; import './Ownable.sol'; contract ProxyStorage is Ownable { address public lib; constructor (address _newLib) public { replace(_newLib); } function replace(address _newLib) public onlyOwner /* onlyDAO */ { lib = _newLib; } } /** * @title Proxy * @dev Gives the possibility to delegate any call to a foreign implementation. */ contract Proxy { /** * @dev Fallback function allowing to perform a delegatecall to the given implementation. * This function will return whatever the implementation call returns */ function () payable public { ProxyStorage proxystorage = ProxyStorage(0x1111222233334444555566667777888899990000); address _impl = proxystorage.lib(); require(_impl != address(0)); assembly { let ptr := mload(0x40) calldatacopy(ptr, 0, calldatasize) let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0) let size := returndatasize returndatacopy(ptr, 0, size) switch result case 0 { revert(ptr, size) } default { return(ptr, size) } } } }
這是新的測試文件:
contract('TestProxy', (accounts) => { describe('Deployment & initial test', () => { let peopleLib, proxystorage, peopleProxy, people before(async () => { peopleLib = await PeopleLib.new() proxystorage = await ProxyStorage.new(peopleLib.address) PeopleProxy.unlinked_binary = PeopleProxy.unlinked_binary.replace('1111222233334444555566667777888899990000', proxystorage.address.slice(2)) peopleProxy = await PeopleProxy.new() People.link('PeopleInterface', peopleProxy.address) people = await People.new() }) it('Should have implemented our library address in proxy', async () => { console.info(await proxystorage.lib(), peopleLib.address) assert.equal(await proxystorage.lib(), peopleLib.address, "Addresses not equal") }) it('Should register a new user', async () => { await people.registerUser("Nico", "nico@did.com", "Design is dead", "", accounts[0]) console.info(await people.getUser(accounts[0])) }) }) })
哪個返回
Contract: TestProxy Deployment & initial test 0x32c836fbd91e88e2843cfad7d5977f0c0697629e 0x32c836fbd91e88e2843cfad7d5977f0c0697629e √ Should have implemented our library address in proxy (102ms) [ 'Nico', 'nico@did.com', 'Design is dead', '', '0x84aff42e44e9b1a278feb8512e463285cd1118b2', [] ] √ Should register a new user (313ms) 2 passing (1s)