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:
- This function exists in
ERC20Upgradeable
and not in$Ron.sol
, updating it in$RonV2.sol
feels wrong. - 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')?
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