Solidity

可升級的代理庫拋出任何功能,如何連接代理和主合約

  • June 7, 2018

我正在嘗試通過 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)

引用自:https://ethereum.stackexchange.com/questions/50671