0

I am currently going deep into some Solidity functionalities and I would like to be able to change a contract's A ownership from a contract B, knowing that the caller of contract B is the owner of contract A. so he has the right to do it.

Basically, I am creating a Consortium contract that has an owner who is able to add and remove members to the Consortium. Once a member is added, a new Organization contract is created for him. An Organization is Ownable and the owner should be the new member and not the Consortium contract, so I immediately transfer the ownership to the member.

Then, I would like to create a function updateMember where the member could change the ownership of his organization directly via the function. I tried using delegateCall without success... Here is the code for Consortium.

The interesting functions are addMember and updateMember

pragma solidity ^0.4.21;

import "zeppelin-solidity/contracts/lifecycle/Destructible.sol";
import "./Organization.sol";

contract Consortium is Destructible {

    /* --- Structures and variables --- */
    struct Member {
        string name;
        address member;
        Organization organization;
        uint since;
    }

    mapping (address => uint) public memberId;
    Member[] public members;

    /* --- Events --- */
    event MembershipChanged(address member, bool isMember);

    /* --- Modifiers --- */
    modifier onlyMembers {
        require(memberId[msg.sender] != 0);
        _;
    }

    /* --- Public functions --- */

    /**
     * Constructor function
     */
    function Consortium() public {
        addMember(owner, "Consortium Funder");
    }

    /**
     * Add member
     *
     * Make `targetMember` a member named `memberName`
     *
     * @param _targetMember ethereum address to be added
     * @param _memberName public name for that member
     */
    function addMember(address _targetMember, string _memberName) onlyOwner public returns(bool) {
        uint id = memberId[_targetMember];
        if (id == 0) {
            memberId[_targetMember] = members.length;
            id = members.length++;
        }

        Organization org = new Organization(_memberName);
        org.transferOwnership(_targetMember);
        members[id] = Member({name: _memberName, member: _targetMember, organization: org, since: now});
        emit MembershipChanged(_targetMember, true);
        return true;
    }

    /**
     * Update member name
     * @param _name public name for that member
     */
    function updateMemberName(string _name) onlyMembers public returns (bool) {
        members[memberId[msg.sender]].name = _name;
        return true;
    }

    /**
     * Update member
     * @param _name public name for that member
     * @param _memberAddress ethereum address to be added
     */
    function updateMember(string _name, address _memberAddress) onlyMembers public returns (bool) {
        Member storage member = members[memberId[msg.sender]];
        member.name = _name;
        address orgAddress = address(member.organization);
        orgAddress.delegatecall(bytes4(keccak256("transferOwnership(address)")), _memberAddress);
        member.member = _memberAddress;
        emit MembershipChanged(_memberAddress, true);
        return true;
    }

    /**
     * Remove member 
     *
     * @notice Remove membership from `msg.sender` called by member itself
     */
    function removeMember() onlyMembers public {
        for (uint i = memberId[msg.sender]; i < members.length-1; i++){
            members[i] = members[i+1];
        }
        memberId[msg.sender] = 0;
        delete members[members.length-1];
        members.length--;
    }

    /**
     * Remove member
     *
     * @notice Remove membership from `targetMember` called by owner
     *
     * @param _targetMember ethereum address to be removed
     */
    function removeMember(address _targetMember) onlyOwner public {
        require(memberId[_targetMember] != 0);

        for (uint i = memberId[_targetMember]; i < members.length-1; i++){
            members[i] = members[i+1];
        }
        memberId[_targetMember] = 0;
        delete members[members.length-1];
        members.length--;
    }

    /**
     * Check if passed address is a Member
     *
     * @param _targetMember address to check
     */
    function isMember(address _targetMember) public view returns (bool) {
        return memberId[_targetMember] != 0;
    } 

    /**
     * Returns the number of members in the consortium
     */
    function numMembers() public view returns (uint) {
        return members.length;
    }
}

Also feel free to criticize this contract if you think my whole approach is not optimal.

Thank you !

2
  • It seems the member can get the address of their Organization from the Consortium. Why not have the user call Organization.transferOwnership directly?
    – Henk
    Commented Apr 18, 2018 at 9:16
  • Yes that's a possibility but I was wondering if this could be done in a single call since delegatecall has been created for that purpose. It's just that here my call to delegatecall doesn't seems to work (event though there are no errors, the owner is not changed). Commented Apr 18, 2018 at 9:57

1 Answer 1

1

This can be solved using tx.origin. If the owner of A tries to execute a transaction from B to change the ownership of A then:

  1. msg.sender will be B contract's address
  2. tx.origin will be the address of who is executing the function in B.

So the transferOwnership of A can simply require for:

require(msg.sender==contractBAddress && tx.origing==owner)

Other solution is that you can change the transferOwnership function to receive an extra parameter as suggested here

EDIT:

One more thing, in this post is recommended not to use tx.origin unless strictly necessary. This is because your contract will not be able to be owned by another contract but in your case, this is not a problem. Just take into consideration that some wallets are contracts.

I am adding this just for completeness of the answer.

you can find more information on these links:

Likn1-dated 2016

Link2-dated 2018

5
  • Nice! That's a perfect solution. So I need to actually modify the transferOwnership code but that's okay. Thanks ! Commented Apr 18, 2018 at 9:59
  • I just updated the answer, adding information on being careful using tx.origing if you plan that one contract will be the owner of another contract. I know this is not your case but is important to have this in mind.
    – Jaime
    Commented Apr 18, 2018 at 10:17
  • 1
    If someone tricks the owner into sending a transaction to a contract which then calls transferOwnership checking tx.origin, the ownership can be stolen. I'd strongly advise against it. See docs
    – Henk
    Commented Apr 18, 2018 at 10:20
  • 1
    Agree, the solution would be to have both rquirements: require(msg.sender == contractB_address && tx.origin == owner) or just to pass an extra parameter from B to the function in A after verifying that msg.sender is the owner of A. I added this in the answer.
    – Jaime
    Commented Apr 18, 2018 at 10:26
  • I understand... It is indeed problematic. Commented Apr 19, 2018 at 11:11

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.