Upgrade Token to include partial burn on transfer

Hi @costech,

To add new functionality in an upgrade,

  • add any new state variables (we cannot change types or order of existing state variables)
  • add new functions (or modify existing functions)
  • setup new state variables in an upgrade function, being sure to appropriately protect the function so that it can only be called once, just as the initialize function is protected with the initializer modifier.

See the documentation for more information on this:
https://docs.openzeppelin.com/sdk/2.5/writing-contracts#modifying-your-contracts

Please note I have not done any of the required testing.

I recommend that any such contract be fully tested and audited.
For testing, I suggest using the following guide: Test smart contracts like a rockstar

It is also recommended to test your upgrade locally before running the upgrade in production.

MyToken.sol V0

Save V0 of MyToken.sol

pragma solidity ^0.5.0;

import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol";

/**
 * @title MyToken
 * @dev Very simple ERC20 Token example, where all tokens are pre-assigned to the creator.
 * Note they can later distribute these tokens as they wish using `transfer` and other
 * `ERC20` functions.
 */
contract MyToken is Initializable, ERC20, ERC20Detailed {
    /**
     * @dev Constructor that gives msg.sender all of existing tokens.
     */
    function initialize(address sender) public initializer {
        ERC20Detailed.initialize("MyToken", "MYT", 18);

        _mint(sender, 10000 * (10 ** uint256(decimals())));
    }
}

Create

$ oz create
✓ Compiled contracts with solc 0.5.11 (commit.c082d0b4)
? Pick a contract to instantiate MyToken
? Pick a network development
✓ Contract MyToken deployed
All contracts have been deployed
? Do you want to call a function on the instance after creating it? Yes
? Select which function * initialize(sender: address)
? sender (address): 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
✓ Setting everything up to create contract instances
✓ Instance created at 0x59d3631c86BbE35EF041872d502F218A39FBa150
0x59d3631c86BbE35EF041872d502F218A39FBa150

MyToken.sol V1

Save V1 of MyToken.sol
Added Mintable and Pausable
Upgrade is protected by _upgradeV1 boolean

pragma solidity ^0.5.0;

import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Mintable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Pausable.sol";
/**
 * @title MyToken
 * @dev ERC20 Token example, where all tokens are pre-assigned to the creator.
 * Note they can later distribute these tokens as they wish using `transfer` and other
 * `ERC20` functions.
 */
contract MyToken is Initializable, ERC20, ERC20Detailed, ERC20Mintable, ERC20Pausable {

    bool private _upgradeV1;
    uint256 private _minimumSupply;

    function initialize(address sender) public initializer {
        ERC20Detailed.initialize("MyToken", "MYT", 18);

        _mint(sender, 10000 * (10 ** uint256(decimals())));
    }

    /**
     * @dev Gives holder all of existing tokens.
     */
    function upgradeV1(address minter, address pauser) public {
        require (!_upgradeV1, "MyToken: already upgraded to V1");
        _upgradeV1 = true;
        
        _addMinter(minter);
        _addPauser(pauser);

        _minimumSupply = 2000 * (10 ** 18);
    }

    function transfer(address to, uint256 amount) public returns (bool) {
        return super.transfer(to, _partialBurn(amount));
    }

    function transferFrom(address from, address to, uint256 amount) public returns (bool) {
        return super.transferFrom(from, to, _partialBurn(amount));
    }

    function _partialBurn(uint256 amount) internal returns (uint256) {
        uint256 burnAmount = _calculateBurnAmount(amount);

        if (burnAmount > 0) {
            _burn(msg.sender, burnAmount);
        }

        return amount.sub(burnAmount);
    }

    function _calculateBurnAmount(uint256 amount) internal view returns (uint256) {
        uint256 burnAmount = 0;

        // burn amount calculations
        if (totalSupply() > _minimumSupply) {
            burnAmount = amount.mul(3).div(100);
            uint256 availableBurn = totalSupply().sub(_minimumSupply);
            if (burnAmount > availableBurn) {
                burnAmount = availableBurn;
            }
        }

        return burnAmount;
    }
}

Upgrade

$ oz upgrade
? Pick a network development
✓ Compiled contracts with solc 0.5.11 (commit.c082d0b4)
- Variables _minters (MinterRole), _pausers (PauserRole) contain a struct or enum. These are not automatically checked for storage compatibility in the current version. See https://docs.openzeppelin.com/sdk/2.5/writing_contracts.html#modifying-your-contracts for more info.
- New variable 'Roles.Role _minters' was added in contract MinterRole in @openzeppelin/contracts-ethereum-package/contracts/access/roles/MinterRole.sol:1 at the end of the contract.
- New variable 'uint256[50] ______gap' was added in contract MinterRole in @openzeppelin/contracts-ethereum-package/contracts/access/roles/MinterRole.sol:1 at the end of the contract.
- New variable 'uint256[50] ______gap' was added in contract ERC20Mintable in @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Mintable.sol:1 at the end of the contract.
- New variable 'Roles.Role _pausers' was added in contract PauserRole in @openzeppelin/contracts-ethereum-package/contracts/access/roles/PauserRole.sol:1 at the end of the contract.
- New variable 'uint256[50] ______gap' was added in contract PauserRole in @openzeppelin/contracts-ethereum-package/contracts/access/roles/PauserRole.sol:1 at the end of the contract.
- New variable 'bool _paused' was added in contract Pausable in @openzeppelin/contracts-ethereum-package/contracts/lifecycle/Pausable.sol:1 at the end of the contract.
- New variable 'uint256[50] ______gap' was added in contract Pausable in @openzeppelin/contracts-ethereum-package/contracts/lifecycle/Pausable.sol:1 at the end of the contract.
- New variable 'uint256[50] ______gap' was added in contract ERC20Pausable in @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Pausable.sol:1 at the end of the contract.
- New variable 'bool _upgradeV1' was added in contract MyToken in contracts/MyToken.sol:1 at the end of the contract.
- New variable 'uint256 _minimumSupply' was added in contract MyToken in contracts/MyToken.sol:1 at the end of the contract.
See https://docs.openzeppelin.com/sdk/2.5/writing_contracts.html#modifying-your-contracts for more info.
✓ Contract MyToken deployed
All contracts have been deployed
? Which instances would you like to upgrade? Choose by address
? Pick an instance to upgrade MyToken at 0x59d3631c86BbE35EF041872d502F218A39FBa150
? Do you want to call a function on the instance after upgrading it? Yes
? Select which function upgradeV1(minter: address, pauser: address)
? minter (address): 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
? pauser (address): 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
✓ Instance upgraded at 0x59d3631c86BbE35EF041872d502F218A39FBa150. Transaction receipt: 0x363f0e43abf490f99172ee5344aa5656d018dc8f565bf4a733f88c792c15bb66
✓ Instance at 0x59d3631c86BbE35EF041872d502F218A39FBa150 upgraded

Check new functionality

$ oz send-tx
? Pick a network development
? Pick an instance MyToken at 0x59d3631c86BbE35EF041872d502F218A39FBa150
? Select which function transfer(to: address, amount: uint256)
? to (address): 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0
? amount (uint256): 100
✓ Transaction successful. Transaction hash: 0xbb3c056b3ca931a3b957615c10fec873ff94f75ed3c1cd5344247fabc2222e09
$ oz call
? Pick a network development
? Pick an instance MyToken at 0x59d3631c86BbE35EF041872d502F218A39FBa150
? Select which function balanceOf(owner: address)
? owner (address): 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0
✓ Method 'balanceOf(address)' returned: 97
97
2 Likes