Solidity – How to Transfer Ownership to Another Contract

accountsaddressescontract-deploymentsolidityweb3js

I have a contract which inherits from contract Claimable by OpenZeppelin (see here):

pragma solidity ^0.4.24;
import "./Claimable.sol";

contract MyTestContract is Claimable {
    uint256 public id;
    function set(uint256 _id) external onlyOwner {id = _id;}
}

The purpose of course is to restrict the calling permission for function set.

This function can be called only from the account which I use in order to deploy the contract.

After deployment, I would like to transfer ownership from this account to another contract.

The reason for this is not a part of my question, but for all it matters, I need to enforce multiple-signature on the function, and allow it to be executed only after X out of Y permitted users have submitted a request. Hence I need to transfer the ownership of the contract from my account to a MultiSig contract by Gnosis (see here), which I deploy myself.

The issue which I am having a problem with and cannot really wrap my head around, is how to transfer ownership to something which is "not exactly an account".

When I transfer ownership from my account to another account on the network, it works fine.

For example, I set up a Ganache network with 8 accounts, and then run this Truffle test:

contract('MyTestContract', (accounts) => {
    it('Transfer Ownership', async () => {
        let   myTestContract = await artifacts.require('MyTestContract').new();
        let   owner = await myTestContract.owner();
        assert(owner === accounts[0]);
        await myTestContract.transferOwnership(accounts[1]);
        await myTestContract.claimOwnership({from: accounts[1]});
        let   newOwner = await myTestContract.owner();
        assert(newOwner === accounts[1]);
    });
});

But when I transfer ownership from my account to the address of a contract which I deploy into the network, Truffle reports:

Error: sender account not recognized

Now, the error by itself makes sense, because there is no such account on the network.

So my question here – is it even possible to do what I'm trying to achieve?

I feel that I have some fundamental misunderstanding of the difference between accounts and contracts, which I'd be very grateful if someone could clarify for me.

Thank you very much!

UPDATE:

To clarify, this is how I transfer ownership to the MultiSig contract:

await myTestContract.transferOwnership(multiSigContract.address);
await myTestContract.claimOwnership({from: multiSigContract.address});

And by the way, when I use Ownable instead of Claimable, i.e.:

On-Chain Code:

contract MyTestContract is Ownable {...}

Off-Chain Code:

await myTestContract.transferOwnership(multiSigContract.address);

The ownership-transfer completes successfully, and the MultiSig contract is the only one which can call the set function.

So the problem is essentially at:

await myTestContract.claimOwnership({from: multiSigContract.address});

Best Answer

await myTestContract.claimOwnership({from: multiSigContract.address});

is where the problem lies. You're telling truffle to send a transaction signed by an address it does not control. Thus the error.

You can think about the Gnosis MultiSigContract as a "Relay" that you post transactions to, which then require approvals to go through ( if required confirmations is higher than 1 ).

In order for the "contract" to "claimOwnership" of your main contract you need to create a transaction that does just that:

Flow:

  1. deploy main contract
  2. deploy multi-sig ( set 2 confirmations , and add account 1 and 2 as owner )
  3. call transferOwnership from "myTestcontract"
  4. create and submit a transaction that calls the "claimOwnership" method on main contract to the multisig
  5. call "confirmTransaction" on multi-sig contract from account 1 and 2.
  6. multi-sig should be the new owner.

The hard part is step 4, which takes you in the inner workings of how solidity abi calls are built. You can choose to manually do it ( bytes4(sha3("contract_method(bytes,bool,uint256[])") ), or let the truffle instance.contract.metod.getData helper do it for you.

Take a look at https://github.com/gnosis/MultiSigWallet/blob/master/test/javascript/testExternalCalls.js

Your final code should look similar to: ( untested )

    // 1 - deploy main contract

    let   myTestContract = await artifacts.require('MyTestContract').new();
    let   owner = await myTestContract.owner();
    assert(owner === accounts[0]);

    // 2 - deploy multi-sig ( set 2 confirmations , and add account 1 and 2 as owner )
    const deployMultisig = (owners, confirmations) => {
        return MultiSigWallet.new(owners, confirmations)
    }

    let requiredConfirmations = 2
    let multisigInstance = await deployMultisig([accounts[1], accounts[2]], requiredConfirmations)

    // 3 - call transferOwnership from "myTestcontract"
    await myTestContract.transferOwnership(multisigInstance.address);

    // 4 - create and submit a transaction that calls the "claimOwnership" method on main contract to the multisig

    // Encode claimOwnership call for the multisig
    const claimOwnershipEncoded = myTestContract.contract.claimOwnership.getData()

    await multisigInstance.submitTransaction(
        myTestContract.address, // address to call with this transaction
        0,                      // value
        claimOwnershipEncoded,  // encoded data
        {from: accounts[1]}     // first owner
    );

    // 5 - call "confirmTransaction" on multi-sig contract from account 2nd account

    const transactionId = 1 // transactionCount starts at 0, so once we add one, we're at 1 
    await multisigInstance.confirmTransaction(transactionId, {from: accounts[2]})

    // 6 - multi-sig should be the new owner.

    let   newOwner = await myTestContract.owner();
    assert(newOwner === multisigInstance.address);
Related Topic