Updating functions couple of times without using multi inheritance using proxy

Hey,
I want to deploy my BEP20 token to Binance and make it upgradable.
I decided to go with the UUPS proxy as I understood it's more efficient by having the upgradability on the implementation itself (feel free to suggest a better proxy).

My token is as follows (I'm using local file import instead of @openzeppelin/... import because I modified some of the OpenZeppelin files and I don't want it to be run over by Github:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
 
import "./ERC20Upgradeable.sol";
import "./Initializable.sol";
import "./OwnableUpgradeable.sol";
import "../proxy/UUPSUpgradeable.sol";
 
contract RonToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize() initializer public {
        __ERC20_init("Ron Token", "RON");
        __Ownable_init();
        _mint(msg.sender, 100000 * 10 ** decimals());
    }
    
    function mint(address to, uint amount) public onlyOwner {
        _mint(to, amount);
    }
}

ERC20Upgradeable has the function _transfer(address from, address to, uint256 amount) internal virtual which I modified by adding taxes.
Let's assume I want to update the taxes in _transfer using my proxy, as I understood I need to do the following:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./$RON.sol";
 
contract RonTokenV2 is RonToken {
    mapping(address => uint256) private _balances;

    function _transfer(address from, address to, uint256 amount) internal override {
        require(from   != address(0), "ERC20: transfer from the zero address");
        require(to     != address(0), "ERC20: transfer to the zero address");
        // The rest of the code
    }

    function version() pure public returns (string memory) {
        return "v2";
    }
}

I don't want to go with this update method of this function, because:

  1. This function exists in ERC20Upgradeable and not in $Ron.sol, updating it in $RonV2.sol feels wrong.
  2. If I would need to change it a couple more times and I will also need to update the functionality of the token, I would end up with missing functions and may end up with more than 3 following inheritences (which is bad practice), for e.g.:
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./$RON.sol";
 
contract RonTokenV3 is RonToken {
    // I can't use `RonTokenV3 is RonTokenV2` because I would end up with 2 override functions
    mapping(address => uint256) private _balances;

    function _transfer(address from, address to, uint256 amount) internal override {
        require(from   != address(0), "ERC20: transfer from the zero address");
        require(to     != address(0), "ERC20: transfer to the zero address");
        // The rest of the code
    }

    function v3Func() pure public returns (string memory) {
        return "v3Func";
    }
}
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./$RON.sol";
 
contract RonTokenV4 is RonToken {
    // I can't use `RonTokenV4 is RonTokenV3` because I would end up with 2 override functions
    mapping(address => uint256) private _balances;

    function _transfer(address from, address to, uint256 amount) internal override {
        require(from   != address(0), "ERC20: transfer from the zero address");
        require(to     != address(0), "ERC20: transfer to the zero address");
        // The rest of the code
    }

    function v4Func() pure public returns (string memory) {
        return "v4Func";
    }
}

In this example, if I use RonTokenV4, I lose the function v3Func and I need to re-write it in RonTokenV4, which looks simple but can lead to many errors when I will apply multiple token updates. I also can't inherit from the previous token version, because I can't have 2 override functions of _transfer and I also don't want RonTokenV10 (for example) to depend on 10 inheritances.

Is there another way of updating the implementation of existing functions without using multiple inheritances (RonTokenV5 inherited from RonTokenV4 inherited from RonTokenV3 etc')?

:computer: Environment

I only found good tutorials using Hardhat to deploy the UUPS proxy, although I think that Truffle is better and I would prefer using it instead, thus feel free to suggest both.

Thanks

RonToken.sol would be the correct location, you override it with a specific implementation for your token.

When RonTokenV2 inherits from RonToken if comes with your overridden _transfer function, so you do not have to add it again.

Hey,
It's not about adding it again, is about changing and updating it's implementation as I have my own implementation of _transfer which include taxes.
Thanks

yes, however your changes are specific for your token, so it is only logical it is part of your Token contract.

I'm not sure I understand your answer, it doesn't solve the inheritance issue

ah i see the problem, you said you where unable to override the function a 2nd time which is incorrect. You can override it a 2nd time, however you need to mark your "override" as virtual aswell :slight_smile:

contract RonToken {

    function RandomFunction() public virtual {
        //something
    }
}

contract RonTokenV2 is RonToken  {
    uint256 public tax_dev = 0;

    function RandomFunction() public virtual override {
		//Add tax to developer wallet 
        tax_dev = 10;
    }

    function v2Func() public view returns (uint256) {
        return tax_dev;
    }
}


contract RonTokenV3 is RonTokenV2 {

    uint256 public tax_markerting = 0;


    function RandomFunction() public virtual override {
        tax_dev = 5;
        tax_markerting = 5;
    }

    function v3Func() public view returns (uint256, uint256) {
        return (tax_dev, tax_markerting);
    }
}

Hey,
I guess I wasn't clear.
If I'll continue updating with your method of updating, on the 10th upgrade it will be RonTokenV10 which is inherited from RonTokenV9 which is inherited from RonTokenV8 which is inherited from RonTokenV7, etc'.
Over 3 following inheritances is a really bad practice, is there another way to update to token without the following inheritances?
Thanks

I've always preferred inheriting the previous contract as it will prevent alot of storage slot problems.
The question becomes why do you expect soo many upgrades? If you're creating a token/nft people don't like upgradable contracts, they actually want people to renounce ownership so they know the contract can not be changed. If you are building a platform like a DAO then you will have to look into how you setup your contract and if its not better to deploy a completely new contract instead. A solidity contract can only be 24576 bytes, if you have a lot of logic that makes up your contract then you'll run into this very quickly. In those situations you'll have to look into different design patterns where you split up the logic of your application into different contracts and use delegate calls to call those functions, something like the the diamond pattern, although that has its own limitations/issues.
When developing for the blockchain which is based around immutability its important to keep your contract as small as possible and split up the different responsibilities into separate contracts. You don't make a token which includes a timelock mechanism or staking functionality. You could, but you want to seperate that into different contracts :slight_smile: