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