0

I'm trying to implement a proxy library through the techniques researched by openZeppelin using proxies.

I have 4 contracts 1. Library with logic 2. Library containing the interface for the logic 3. Contract that acts as the proxy 4. Main contract that calls the proxy through an interface which delegates to the library

The problem I'm having, I think. Is that my main contract doesn't send its calls to the proxy despite linking the interface on deployment.

I made a test file which deploys the contracts and links the libraries. I can confirm that the library is being linked to the proxy contract.

How can I properly have People.sol (main contract) call a function on the PeopleInterface creating the necessary calldata to issue to the delegatecall in the 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", "[email protected]", "Design is dead", "", accounts[0])
    })
  })
})

These are my contracts:

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

1 Answer 1

0

I used another storage principle (eternal storage) rather than unstructured storage. Credit to Maraoz : https://github.com/maraoz/solidity-proxy

It works now , given that I change a few things in my test file.

This is the new 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) }
    }
  }
}

And this is the new test file:

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", "[email protected]", "Design is dead", "", accounts[0])
      console.info(await people.getUser(accounts[0]))
    })
  })
})

Which returns

  Contract: TestProxy
    Deployment & initial test
0x32c836fbd91e88e2843cfad7d5977f0c0697629e 0x32c836fbd91e88e2843cfad7d5977f0c0697629e
      √ Should have implemented our library address in proxy (102ms)
[ 'Nico',
  '[email protected]',
  'Design is dead',
  '',
  '0x84aff42e44e9b1a278feb8512e463285cd1118b2',
  [] ]
      √ Should register a new user (313ms)


  2 passing (1s)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.