1

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});
4
  • Ethereum is a virtual machine that allows you to do everything you want. Now , about the rules of changing ownership, it is really the rules of your contract, which you are able to control. And the address can contain any value of 20 bytes you want.
    – Nulik
    Commented Jul 15, 2018 at 19:06
  • Are you by any chance trying to send the transaction from the address of the contract to which you are trying to transfer ownership?
    – Henk
    Commented Jul 15, 2018 at 19:10
  • @Nulik: Thank you for the clarification. So there isn't anything that I am really missing here. The only question remaining is, how can I successfully complete the call to myTestContract.claimOwnership({from: myMultiSigContract.address})? Commented Jul 15, 2018 at 20:00
  • @Henk: Yes, precisely. See my response to Nulik above. According to the Claimable contract test (also on OpenZeppelin's GitHub repository), this is what I'm supposed to do. Am I wrong here? Commented Jul 15, 2018 at 20:03

1 Answer 1

2

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);
4
  • So you actually use the MultiSig contract in order to call function claimOwnership of myTestContract????? Commented Jul 16, 2018 at 6:34
  • I doubt that the MultiSig will be able to complete this transaction, because my contract at this point is still owned by accounts[0] (and not by the MultiSig contract). Commented Jul 16, 2018 at 7:09
  • Sorry, I take it back, you are absolutely right. The multisigInstance is the pending owner, so it is allowed to call function claimOwnership (in fact, it is the only one who is allowed to do that). Brilliant, thanks!!! Commented Jul 16, 2018 at 8:05
  • You're very welcome :) it takes a little while to wrap your head around how things need to be done, but once you're there the sky is the limit Commented Jul 16, 2018 at 17:47

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.