Inheritance & External Contracts - Discussion

Hi there is my solution for lessons/inheritance-assignment-2

./abc/Ownable.sol

pragma solidity 0.7.5;

contract Ownable {
  address public owner;
  constructor(){
    owner = msg.sender;
  }
  modifier onlyOwner {
    require(msg.sender == owner, "Owner only");
    _;
  }
}

./abc/DestroyableByOwner.sol

pragma solidity 0.7.5;

import "./Ownable.sol";

contract DestroyableByOwner is Ownable {
  function destroy() public onlyOwner {
    selfdestruct(payable(owner));
  }
}

./SimpleBank.sol

pragma solidity 0.7.5;

import "./abc/Ownable.sol";
import "./abc/DestroyableByOwner.sol";

contract SimpleBank is Ownable, DestroyableByOwner {
...
}
1 Like
  1. What is the base contract?
    A base contract is the parent contract (the contract that is inherited).

  2. Which functions are available for derived contracts?
    All public and internal scoped functions.

  3. What is hierarchical inheritance?
    A single contract acts as a base contract for multiple derived contracts.

1 Like

Nice answers @JayM700 :ok_hand:

Just a couple of clarifications …

Correct

No … the base (or parent) contract is inherited by the child (or derived) contract. We can also say that the child contract is derived from the base (or parent) contract.

… through any type of inheritance relationship: single, multi-level, hierarchical or multiple inheritance.

Just let me know if you have any questions.

1 Like

An excellent, and very well-presented solution @fedev :muscle:

We can also streamline the inheritance structure by having Bank explicitly inherit Destroyable only, because it will inherit Ownable implicitly via Destroyable. This will give you a multi-level inheritance structure.

Let me know if you have any questions about this point.

1 Like

Hello. Could someone help me, please? I have a issue when I try to get the information from “getTransaction” function in Government contract. It says “call to Government.getTransaction errored: VM error: invalid opcode.”

I am adding below the two contracts. Maybe is something I did wrong and I can’t seem to notice what.

Government.sol

pragma solidity 0.7.5;

contract Government{

    struct Transaction{
        address from;
        address to;
        uint amount;
        uint txId;
    }

    Transaction[] transactionlog;

    function addTransaction(address _from, address _to, uint _amount) external{
        transactionlog.push(Transaction (_from, _to, _amount, transactionlog.length));
    }

    function getTransaction(uint _index) public view returns (address, address, uint){
        return(transactionlog[_index].from, transactionlog[_index].to, transactionlog[_index].amount);
    }
}

Etherbank.sol

pragma solidity 0.7.5;

import "./Destroyable.sol";
import "./Ownable.sol";

interface GovernmentInterface{
    function addTransaction(address _from, address _to, uint _amount) external;
}

contract EtherBank is Ownable,Destroyable{

    GovernmentInterface GovernmentInstance = GovernmentInterface(0x9ecEA68DE55F316B702f27eE389D10C2EE0dde84);

    //storage - means permanent storage of data (persistant data)
    //memory - temporare data storage (is stored temporarely as long as the function executes)
    //calldata - similar to memory, but READ-ONLY
    
    mapping (address => uint)balance;
    
    event depositDone(uint amount, address depositedTo);
    event balanceTransfered(uint amount, address fromAddress, address toAddress);

    function deposit() public payable returns (uint){
        balance[msg.sender] += msg.value; //this line is not necessarly because the balance mapping has nothing to do with the value of the contract, it just keeps track of our internal balances (which address deposited which money)
        emit depositDone(msg.value, msg.sender);
        return balance[msg.sender];
    }

    function withdraw(uint amount) public returns (uint){
        require(balance[msg.sender] >= amount);
        balance[msg.sender] -= amount;
        msg.sender.transfer(amount);
        return(balance[msg.sender]);
    }

    function getBalance() public view returns (uint){
    return balance[msg.sender];
    }

    function transfer(address recipient, uint amount) public{
    require(balance[msg.sender] >= amount);
    require(msg.sender != recipient);
    uint previousSenderBalance = balance[msg.sender];
    _transfer(msg.sender, recipient, amount);
    
    GovernmentInstance.addTransaction(msg.sender, recipient, amount);

    assert(balance[msg.sender] == previousSenderBalance - amount);
    emit balanceTransfered(amount, msg.sender, recipient);

    }
    function _transfer(address from, address to, uint amount) private{
        balance[from] -= amount;
        balance[to] += amount;

    }
}

Hey Cosmin,

There’s nothing wrong with your code.

Whenever you redeploy Government, then it will have a different contract address, and so you also need to change the Government address assigned to your governmentInstance in this line of your code in Bank:

You need to change this address before you deploy Bank, so that it’s the same as the current address that your Government contract is deployed at. If you forget to replace the previous address of your external contract for the new one, then when you call Bank’s transfer() function either of the following can happen …

(1) The transaction completes successfully, but the addTransaction() function isn’t called on the Government contract that you currently have deployed and showing in the Deploy & Run Transactions panel. So, when you then call getTransaction() on your Government contract, you get the error message that you’ve been getting:

call to Government.getTransaction errored: VM error: invalid opcode.

This is because the transfer data from the transaction wasn’t passed to the addTransaction() function that you are expecting, and so wasn’t added to the transactionlog array that you are now trying to retrieve data from. You get this error message because there is no data at the array index you are trying to look up.

OR

(2) The transaction reverts, because the external function call fails, and you get the following default error message for revert, which doesn’t actually explain what the error is:

transact to Bank.transfer errored: VM error: revert.

The transaction has been reverted to the initial state.
Note: The called function should be payable if you send value
and the value you send should be less than your current balance.
Debug the transaction to get more information.

Forgetting to update the Government contract address is easily done, and catches most of us out the first time we make this mistake :sweat_smile:

I hope that resolves your issue, but let me know if you still experience any problems.

1 Like

Thank you very much for your help, Jon. I assumed I understood your remarks, but it seems that I didn’t and I don’t know why. I am sorry, but could you please help me again? I followed your steps and I still get the revert error. Please see below the steps I made.

  1. I changed the address for the GovernmentInterface with a new one, but didn’t deploy the Etherbank contract yet:
GovernmentInterface GovernmentInstance = GovernmentInterface(0x617F2E2fD72FD9D5503197092aC168c91465E7f2);
  1. I went into the Government contract and deployed it with the address 0x617F2E2fD72FD9D5503197092aC168c91465E7f2

  2. Now I deployed Etherbank contract with the address 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4

  3. I deposited 2 ether with the address 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, got the ballance 2000000000000000000.

  4. Try to transfer 1000000000000000000 from the address 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 to address 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2.

  5. Got the error:

transact to Bank.transfer errored: VM error: revert.

The transaction has been reverted to the initial state.
Note: The called function should be payable if you send value
and the value you send should be less than your current balance.
Debug the transaction to get more information.

Hey Cosmin,

You’ve got confused between external wallet addresses (user/deployer addresses) and contract addresses. The address that deploys the contract is not the same as the address the contract is then deployed at.


(1)

Do this first.
0x617…7f2 is the external address of the contract deployer.

(2) To get the contract address (the address it’s deployed at), go to the bottom of the Deploy & Run Transactions panel to the deployed contract, where it will say   GOVERNMENT AT   and then the address the Government contract is now deployed at. This is the address you need to copy and paste (use the icon to the right of the address) into your governmentInstance declaration in Bank …

To create an instance of the Government interface, we need to declare where the contract (on which the interface is based) is deployed at. Otherwise, an external function call can’t be made to it. At the moment, your Bank contract is trying to call a function on the deployer’s external address which doesn’t exist.


(3) Continue with your steps 3 and 4

(4) And your step 5 will now work, without throwing the error :raised_hands:

(5) You can now call getTransaction() in Government with a transaction ID of 0, and retrieve the transaction data from the transaction log in Government.


Just let me know if you still have problems getting this to work.

1 Like

Thanks Jon. All good now. I appreciate your help :love_you_gesture:

1 Like

Thanks for the thorough explanation Jon. This was really helpful. There is just one other thing i am hoping you can answer regarding the transferFrom function… Supposing I approve someone to spend my funds. That person can then use the transferFrom to send my funds to whomever he wants correct?

So in the transferFrom header… the “from” address is the person i have approved to spend my funds? And the “to”/msg.sender" is where he is sending the funds i have given him the approval to spend? Or do i have something mixed up here?

Hi @Bitborn,

I’ve tried to find a post of yours with your full Bank contract, but it doesn’t seem like you ever posted it… is that right? I wanted to see it because you mention a transferFrom() function header, and this doesn’t appear in the Bank contract used in the course videos/assignments.

Anyway, I’ll assume you mean the _transfer() helper function which is called from within the transfer() function…

function _transfer(address from, address to, uint amount) private {
    balance[from] -= amount;
    balance[to] += amount;
}

This is a private function, and so can’t be called from outside the Bank contract. When the public transfer() function is called, the _transfer() function is then called from within the transfer() function in this line of code …

_transfer(msg.sender, recipient, amount);

As you can see, the msg.sender argument is passed to the _transfer() function as the address from parameter.

So, no … it’s the caller of the public transfer() function…

function transfer(address recipient, uint amount) public { ... }

The caller of this function is the address which executes the internal transfer, which is effectively a reallocation of part, or all, of their own share of the total amount of ether held in the contract (recorded as their individual balance mapped to their address in the mapping) to another address of their choosing. No ether leaves the contract: the reallocated share is added to the other address’s individual balance (also mapped to their address in the mapping). This reallocation is performed in the _transfer() helper function.

The recipient argument is passed to the _transfer() function as the address to parameter. The msg.sender argument is passed to the _transfer() function as the address from parameter.

msg.sender / from
is transferring part, or all, of their own share of the total amount of ether held in the contract
TO
recipient / to

The Bank contract used in the course/assignments doesn’t allow an address to approve another address to perform any transactions on its behalf. Our use of msg.sender in the code ensures that…

  1. The caller of the deposit() function can only transfer ether from their external address balance to the contract, where it is recorded in the mapping as their own share of the total contract balance:   balance[msg.sender] += msg.value

  2. The caller of the withdraw() function can only transfer ether out of the contract to their own external address balance:   msg.sender.transfer(amount)
    … and that this reduction in the total contract ether balance can only be recorded as a reduction in that same caller’s own share of the total contract balance:   balance[msg.sender] -= amount

  3. The caller of the transfer() function can only reallocate part, or all, of their own share of the total amount of ether held in the contract:
    balance[from] -= amount
    from is msg.sender passed to _transfer() helper function from public transfer() function
    … so that ownership of this share of the total contract ether balance (expressed as an amount) is transferred to an address of their choosing (input as the recipient argument):
    balance[to] += amount
    to is recipient passed to _transfer() helper function from public transfer() function

I hope that helps to clarify things some more. But if I’ve misunderstood what you mean by the transferFrom() function, then post your full Bank contract code and I’ll take another look. Let me know if anything I’ve explained is unclear, or if you have any further questions :slight_smile:

Did you resolve this in the end?

You may find the errors are the following (but these may just be copy-and-paste mistakes in your post, and not errors in your code in Remix) …

(1)

The units of ether must be written in lower case letters i.e.

1 ether
1 gwei
1 wei

(2)

Your operator here is a dash, and not a minus sign. A dash is longer, and will throw a compiler error.

previousSenderBalance – amount // dash => incorrect syntax
previousSenderBalance - amount // minus sign => correct subtraction operator

If you look carefully you can just see the difference.


If these were just errors in your post, then post a full copy of your Bank contract, appropriately formatted as explained here, so that I can compile it myself and identify the issues.

Hi Filip,
In the Value calls tutorial. you have mentioned {value: 1 ether}. Please tell me why it is written 1 ether. can we write 2 ether, etc., and what does this value signify? Shouldn’t it be written as {value: amount ether} the amount that we want to transfer?
Thanks and regards.

1 Like

Hi @astro1,

Great questions!

Our particuar example code is based on a contractual relationship between a bank and the government. The idea is that the government needs to collect some kind of tax (or levy) on each banking transaction that is an internal bank transfer. To keep things simple in our example, the government is collecting a fixed amount of tax on each transfer. I would suggest an amount much smaller than 1 ether would be more appropriate, for example any value from 1 wei up to 1 gwei. This amount (which is the amount of tax paid by the Bank, and not the amount transferred from one bank account holder to another) will be automatically transferred from the Bank contract balance to the Government contract balance on each successfully executed transfer transaction. For record keeping purposes, the relevant data for each transfer is also sent from Bank to Government and stored in the government’s transaction log (an array).

Instead of as a tax, another way to interpret this transfer of value between the two contracts could be as payment, or fees, for some kind of record keeping service i.e. the Bank contract pays the Government contract for storing all of its transaction data.

However we interpret this payment from one contract to the other, it demonstrates (albeit in an overly simplistic way) how we can program automatic interactions between individual contracts for the purposes of both data and value transfer. Hopefully, this example shows the potential for these sorts of programmed interactions to be applied to many different use cases.


Yes …

In Solidity, unsigned integers representing Ether values are equivalent to uints of wei e.g.

{value: 1000000000000000000}

/* A value call of 1000000000000000000 Wei  =  1000000000 Gwei  =  1 Ether

However, Solidity syntax also includes the suffixes ether and gwei , which provide us with a useful shorthand. For example, the following 3 alternatives are all equivalent to value calls of 1 ether …

{value: 1000000000000000000}
{value: 1000000000 gwei}
{value: 1 ether}

The suffix wei is also available, but this would only seem to be worth using in situations where there could be a risk of confusion, because the following 2 alternatives are both equivalent to value calls of 0.05 gwei …

{value: 50000000 wei}
{value: 50000000}

Here is a link to the relevant section in the Solidity documentation:
https://docs.soliditylang.org/en/latest/units-and-global-variables.html#ether-units

I’ve already partly answered this, above. The Ether value we are transferring to the Government contract is a payment made by Bank to Government in respect of the transfer of funds between two individual users of the Bank contract. The transfer itself is an internal transfer within Bank, and the amount value sent to Government is part of the data which will be added to the Government transaction log as a record of the transfer for which Bank has made a payment (of tax, or a fee for a service).

In any case, the suffixes ether, gwei and wei can only be used after literal numbers. You could reference an unsigned integer stored in a variable, but without the suffix e.g.

uint tax;

function transfer(address recipient, uint amount) public {
   // additional code
   governmentInstance.addTransaction{value: tax}(msg.sender, recipient, amount);
   // additional code
}

Just one final point… If you haven’t already, you’ll find it really helpful to add the following function to both the Bank and Government contracts. It enables you to retrieve the total contract address balance for each contract before and after specific transactions. This is useful for testing and for understanding the movements of Ether at a contract level.

function getContractBalance() public view returns(uint) {
    return address(this).balance;
}

Let me know if anything is unclear, or if you have any further questions :slight_smile:

Thanks Jon! Makes sense and see the error now.

1 Like

Thanks for the explanation with all this as it really clarified the intentions of the transfer and _transfer functions. Tbh I accidentally posted my question about transferFrom here when it was in fact a lesson from Eth Smart Contract Programming 201, “TokenStandards”. My bad, I’ve been going back and forth, occasionally going over old material and confused what lesson i was on. In any case, i will post that in the appropriate lesson so not to confuse anyone here because its not part of the lesson here. Either way this was helpful, thanks.

1 Like

Hey @Bitborn,

Ahhhh … OK that makes sense now :sweat_smile:
Not to worry … it’s great that you’re re-visiting earlier material, as you can often get more out of it, and discover things you didn’t notice before, after having aquired more technical knowledge and gained more practical experience :muscle:

Anyway, I’m glad the extra explanations were helpful :slight_smile:

Hey filip, I’m a bit confused on how value calls work. I would appreciate it if you could type a brief explanation or send an article. :grinning:

Hi @Adrian1,

I would suggest looking at this post, which first gives a detailed explanation of what the transfer() function itself is actually doing (point 1), and then goes on to explain how the external value call works within the context of the transfer() function’s operations (point 2). I think looking at this wider picture first will help you to identify what it is you have been missing, or have misunderstood, and is causing your confusion.

Then have a look at this post, which repeats some of the information in the first post, but also goes into detail about the different ways we can code the actual ETH value being transferred from one contract to another — using unsigned integers with or without the suffixes ether, gwei or wei; or by referencing an unsigned-integer value stored in a variable. This could help clear up any confusion relating to this particular aspect of the value call.

You can then let me know if there is still something specific that you don’t understand, and I’ll help you out with that :slight_smile:

Fkin legend with the government joke :smiley: Sadly exponentially more true today than 2 years ago.