ERC20 Wrapper tutorial

In this tutorial we are going learn how to create a wrapped token using the ERC20Wrapper contract provided by OpenZeppelin, but first let's get familiar with what a wrapped token is.

Introduction to wrapped tokens

A wrapped token is a token whose value is tied to an underlying cryptocurrency. An amount of the original token is locked in a digital vault, and in return this allows an equivalent amount of wrapped tokens to be minted.

This is useful for several reasons, to mention a few:

  • To extend the functionalities of an existing token in conjunction with other ERC20 modules.
  • Allow a native cryptocurrency to behave like an ERC20, e.g. Wrapped ether (WETH).
  • Allow the use of currencies outside its native blockchain, e.g. Wrapped bitcoin (WBTC).

This OpenZeppelin contract covers the first case: underlying ERC20 tokens on the same chain can be wrapped and thus virtually extended with new functionality. For this tutorial we are implementing a wrapped token that enables voting in a governance protocol with a token that was not originally designed for this purpose.

Let’s start!
We’ll assume a project X was launched with its own token, a non upgradable ERC20 contract, but some time after the launch they found themselves in need of on chain governance. For said purpose they would need to deploy a governance system through a Governor contract that will look something like this:

contract MyGovernor is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {
    constructor(ERC20Votes _token, TimelockController _timelock)
        Governor("MyGovernor")
        GovernorSettings(1 /* 1 block */, 45818 /* 1 week */, 0)
        GovernorVotes(_token)
        GovernorVotesQuorumFraction(4)
        GovernorTimelockControl(_timelock)
    {}
 
    // ...
}

For more information on how to set an on chain governance please take a look at this guide.

As you might have noticed it uses an ERC20Votes type of token to perform all its operations, and the launched token was just a non upgradeable ERC20, therefore not compatible with the governance needs. The solution to their predicament is fairly simple, let's wrap the existing token adding the ERC20Votes module.

First we will inherit from the contracts ERC20Wrapper and ERC20Votes:

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
 
contract VotesWrapper is ERC20, ERC20Wrapper, ERC20Votes {

ERC20Wrapper takes a parameter for the original token that we want to wrap. We would use the previously launched token address as this parameter. And we’ll setvalues for all the other required parameters, notice that we have to include ERC20Permit constructor call because it is a parent for our wrapped token parent ERC20Votes:

constructor(IERC20 wrappedToken)
   ERC20("Wrapped Token", "wTKN")
   ERC20Permit("Wrapped Token")
   ERC20Wrapper(wrappedToken)
{}

Note that the compiler requires writing some overrides.

Click here to see the full code


import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
 
contract VotesWrapper is ERC20, ERC20Wrapper, ERC20Votes{
    constructor(IERC20 wrappedToken)
       ERC20("Wrapped Token", "wTKN")
       ERC20Permit("Wrapped Token")
       ERC20Wrapper(wrappedToken)
    {}
    
    function _afterTokenTransfer(address from, address to, uint256 amount)
        internal
        override(ERC20, ERC20Votes)
    {
        super._afterTokenTransfer(from, to, amount);
    }
 
    function _mint(address to, uint256 amount)
        internal
        override(ERC20, ERC20Votes)
    {
        super._mint(to, amount);
    }
 
    function _burn(address account, uint256 amount)
        internal
        override(ERC20, ERC20Votes)
    {
        super._burn(account, amount);
    }

Now you can use your wrapped token to execute any voting operations, by deploying the wrapped token first, and then deploying the Governor contract configured with the wrapped token as its voting token.

In order to vote for governance proposals, token holders must first use the wrapped token’s method depositFor to get voting tokens 1-to-1 for the tokens they already hold.

After there are some locked tokens on the wrapped token’s contract, or in other words after there are some wrapped tokens minted, you can start using the Governor contract as if you were using your token as a voting token.

10 Likes

Hi there. Complete beginner here.

Can somebody shed light on the following, please: In Remix I copy/past ERC20Wrapper.sol because I only need to wrap an existing token (on Moonbeam). I don't type anything. Only choose compiler 0.8.2, and get the green checkmark. Then I'd like to deploy, but get the message "This contract may be abstract, it may not implement an abstract parent's methods completely or it may not invoke an inherited contract's constructor correctly.".
What do I have to do, so it deploys? Is this linked to "only" 1 token, and it must be defined in the contract?? (if yes, what do I have to type?)
Thanks in advance for some helpful inputs.
Cheers

or I use a simple fixed rate swap smart contract. if there is any.. should be, shouldn't it??

The ERCwrapper contract is an abstract class, which means you will have to create an new contract and inherit from it

Please check the above guide and look at the VotesWrapper contract for an example on how to create your own contract.

1 Like

Could any one help on this:

constructor(IERC20 wrappedToken)
   ERC20("Wrapped Token", "wTKN")
   ERC20Permit("Wrapped Token")
   ERC20Wrapper(wrappedToken)
{}

ERC20Permit has a parent of ERC20. Why the constructor also has to initialize the ERC20? Why ERC20Permit don't need to initialize ERC20 in its constructor?

Since you haven't provided your contract declaration here, I'll answer with "probably"...

The "extra" initialization is required probably because your contract itself inherits from ERC20.

Since your contract inherits from ERC20Permit and ERC20Permit inherits from ERC20, you may as well get rid of your contract's inheritance from ERC20, which will allow you to get rid of this "extra" initialization that you're complaining about.

  • ERC20's constructor ask for a name and a symbol that will be used for the token itself
  • ERC20Permit inherit from EIP712. That requires you to provide is a name to setup the domain separator (for EIP712 domains).

Since EIP712 and ER20 are independant, they don't share any common name structure, so you have to provide the 2 separatelly ... and technically the 2 could be different is you wanted.

I deployed this to the mainnet and it didn't work. Can you elaborate on what might be updated now in 2024, so people don't make the same mistake?

Not before you elaborate on that (obviously).

1 Like

@barakman Here's an open question that shows the contracts deployed. It would be great to figure out where I went wrong because right now I'm at a total loss. Wrapped ERC-20 Token Issue

Getting this error, on Openzeppelin 5.0.2 version
"
DeclarationError: Identifier not found or not unique.
--> contracts/wxBTC.sol:10:8:
|
10 | ERC20Permit("Wrapped xBTC")
| ^^^^^^^^^^^
"

Erc20 token yapıp adını btc token olarak olusturduktan sonra bunun btc ile eşdeğer fiyatta aynı olması ve fantom ağında fantom BTC olarak kullanmak için ne yapmalıyım nereden baslamalıyım