La biblioteca de proxy actualizable incluye cualquier función, cómo conectar el proxy y el contrato principal

Estoy tratando de implementar una biblioteca de proxy a través de las técnicas investigadas por openZeppelin usando proxies.

Tengo 4 contratos 1. Biblioteca con lógica 2. Biblioteca que contiene la interfaz para la lógica 3. Contrato que actúa como proxy 4. Contrato principal que llama al proxy a través de una interfaz que delega en la biblioteca

El problema que estoy teniendo, creo. Es que mi contrato principal no envía sus llamadas al proxy a pesar de vincular la interfaz en la implementación.

Hice un archivo de prueba que implementa los contratos y vincula las bibliotecas. Puedo confirmar que la biblioteca se vincula al contrato de proxy.

¿Cómo puedo hacer que People.sol (contrato principal) llame correctamente a una función en PeopleInterface creando los datos de llamada necesarios para emitir la llamada delegada en el proxy?

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])
    })
  })
})

Estos son mis contratos:

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));
  }
}

Respuestas (1)

Usé otro principio de almacenamiento (almacenamiento eterno) en lugar del almacenamiento no estructurado. Crédito a Maraoz: https://github.com/maraoz/solidity-proxy

Funciona ahora, dado que cambio algunas cosas en mi archivo de prueba.

Este es el nuevo 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) }
    }
  }
}

Y este es el nuevo archivo de prueba:

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]))
    })
  })
})

que vuelve

  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)