1

I want to create a clone factory, that will initialize ERC20 tokens. I saw some examples for older solidity versions (0.5) and it worked there. Back then was ERC20Detailed contract that could be initialized. With solidity ^0.8.0 it doesn't work anymore or I am doing something wrong.

Here are my contracts:

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";


contract TokenFactory{
    address immutable tokenImplementation;

    constructor () {
        tokenImplementation = address(new Token());
    }

    function createToken(string calldata name, string calldata symbol, uint8 decimals, uint256 initialSupply, address owner, address[] memory airdropDestinations, uint256[] memory airdropValues) external returns (address){
        address clone = Clones.clone(tokenImplementation);
        Token(clone).initialize(name, symbol, decimals, initialSupply, owner, airdropDestinations, airdropValues);
        return clone;
    }
}


contract Token is Initializable, ERC20 {
    uint8  private _decimals;
    string private _name;
    string private _symbol;
    address  private _owner;

    function initialize(string memory name, string memory symbol, uint8 decimals_, uint256 supply, address owner, address[] memory airdropDestinations, uint256[] memory airdropValues) public initializer{
        require(supply > 0);
        ERC20.initialize(name, symbol);
        _name = name;
        _decimals = decimals_;
        _owner = owner;
        _mint(owner, supply);
        _airdrop(airdropDestinations, airdropValues);
    }

    function decimals() public view virtual override returns (uint8) {
        return _decimals;
    }

    function _airdrop(address[] memory destinations, uint256[] memory values) private {
        uint256 i = 0;
        while (i < destinations.length) {
           _transfer(_owner, destinations[i], values[i]);
           i += 1;
        }
    }
}

I am getting an error that ERC20 is not initializable, which is understandable, because it doesn't have initialize method. Anyway, if I skip initializing it, then the will be an error that I need to call ERC20 constructor, which I don't need to.

6
  • which version of openzepplien are you using. their contracts also changing with each release and some methods got removed, moved or renamed
    – Majd TL
    Feb 12, 2022 at 13:50
  • @MajdTL the latest one v4.5.0
    – Ernst
    Feb 12, 2022 at 14:17
  • Do you need a clone proxy or an ERC20 factory? They are different things, and it seems to me you want to create a factory. Feb 14, 2022 at 17:11
  • @GiuseppeBertone I probably need a factory. Basically, I am going to have the same contract for all tokens and I don't want to deploy it millions of times. I thought that clone proxy might be good here.
    – Ernst
    Feb 14, 2022 at 17:16
  • @GiuseppeBertone Actually, factory won't save me money, I think I need minimal proxy which is the same as clone proxy
    – Ernst
    Feb 14, 2022 at 17:28

1 Answer 1

1
+50

You can use this code:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.11;

import "@openzeppelin/contracts-upgradeable/token/ERC20/presets/ERC20PresetFixedSupplyUpgradeable.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";

contract FactoryClone {
    address immutable tokenImplementation;

    constructor() {
        tokenImplementation = address(new ERC20PresetFixedSupplyUpgradeable());
    }

    function createToken(string calldata name, string calldata symbol, uint256 initialSupply) external returns (address) {
        address clone = Clones.clone(tokenImplementation);
        ERC20PresetFixedSupplyUpgradeable(clone).initialize(name, symbol, initialSupply, msg.sender);
        return clone;
    }
}

Some notes:

  • If you don't use Remix, you maybe need to change the path of the ERC20PresetFixedSupplyUpgradeable.sol contract
  • Clones.sol actually defines a library, and ERC20PresetFixedSupplyUpgradeable.sol is just a token smart contract with predefined functions, and you can change it with another one of your choice

Once the factory is deployed (~1.1M gas in total, ~200K more than the token contract deployment), it takes ~180K gas to deploy a new token contract.

This technique is cheaper during the deployment phase, but it's a bit more expensive than a native token contract regarding other operations like transfers and so on. It's up to you to understand if you want to pay more now, and let your users pay less in the long term, or viceversa.

You can find more info about different gas costs in this OpenZeppelin worship repository, and in this video you can watch a full deep dive into gas cost comparison between FactoryNaive.sol, FactoryProxy.sol and FactoryClone.sol.

2
  • Yeah, I came up with this solution, but I am not sure if it's a good idea as I don't need contracts to be upgradeable. It will consume more gas by default because of extra contracts. Basically, I started with this solution, but then I found that in previous open_zeppeling versions it was easy to implement clones factory with non-upgradeable ERC20.
    – Ernst
    Feb 15, 2022 at 8:08
  • You can use a non-upgradable contract, and the technique does not change. You can use, for example, a standard implementation; just remember to add an initialize method with the parameters you prefer. Feb 17, 2022 at 8:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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