Leveraging OpenZeppelin with Sound and Gigantic Steps

erc20
erc721

#1

Greetings again @frangio,

Allow me to provide you a quick background: I programmed back at collage [1990-1992], thus I got some logic and programming foundation. Today I am embracing a new challenge :point_right: Project Highlights: to create a sound and secured ERC20 token with an associated Smart Contract in which Token holders [via API] will be able to: a) Access Documents (DocumentName, AccessKey), b) Exercise [5] Voting Rights (FinancingGateName, Vote[yes/no]) and c) View Net Profits [given by Contract Owner] (ProfitSource, Value [ETH], IssuedTimeStamp, Hash).

The Five Voting Rights will trigger the following actions:

  1. Approve the issuance [mint] of additional Tokens [stage 2] and make them available in an exchange.
  2. Approve the release [from escrow] of 100% of the funds received (ETH) from the Tokens [stage 2] to the Contract Owner.
  3. Approve the issuance [mint] of additional Tokens [stage 3] and make them available in an exchange.
  4. Approve the release [from escrow] of 30% of the funds received (ETH) from the Tokens [stage 3] to the Contract Owner.
  5. Approve the release [from escrow] of 70% of the funds received (ETH) from the Tokens [stage 3] to the Contract Owner.

Prior to developing the above described Smart Contract, I will issue an ICO [stage 1] with a conditional success threshold of 70% of tokens sold, equivalent to x number of ETH; if not met, the received funds [ETH] will be returned to the originators.

With this scenario, I believe I can leverage many of the OpenZeppelin contracts that engage with ERC20.sol, Crowdsale.Sol, Escrow.Sol, etc. I still need to do further research on Voting/Ballot Contract Samples to obtain and leverage best practices, given that I did not see any related contracts in OpenZepelin GitHub.

By reviewing openzeppelin.org and zeppelinos.org I figure that I would be able to take sound steps by leveraging a modular approach with OpenZeppelin Contracts and by deploying them via ZeppelinOS.

Therefore, here are my first set of questions:

  1. Could you guide me on how to best take advantage of a modular approach? That is, how to best interact with the existing or modified versions of OpenZeppelin contracts?
  2. Could you guide me on how to best take advantage of the Roles.sol?
  3. When deploying a contract that calls [import] other contracts, which call [import] other contracts, via ZeppelinOS, are all those called contracts bundled up and sent to the Main or Test Networks as ONE Contract?
  4. Are there any other OpenZeppelin Contracts.sol, not mentioned in this message that I should review/consider to leverage into my project?

Looking forward to reading from you soon,

Cheers!


#2

Hello @jaureguino, this seems rather complex!
I know I’m not frangio but I’ll answer what I can haha

  1. Not entirely sure what is being asked here, how do the voting rights affect the users?
  2. Roles allows you to mark people who should have exclusivity for certain functionalities, like restricted access to certain functions. If you need a role set up I’d recommend using one the existing roles contracts and renaming it for each of your roles for what they need to be.
  3. If it just calls other contract, as in importing their interfaces, then it won’t be bundled. You can also link it as a library. However if the contract is not already previously deployed I’m positive the contract it will all be in the same contract.
  4. None that I’m aware of.

Let @frangio respond before going through with it all. He’ll have a better idea than I do.


#3

Greetings again @IvanTheGreatDev, and thanks for taking a shot at these questions. Allow me to respond/elaborate further in order to provide you and @frangio with more clarity/context:

  1. No, the question is related to how can I best interact/use/modify existing OpenZeppelin contracts as I develop my ERC20 Token and Smart Contract. i.e.: a) by calling them [import] (OpenZeppelin repository) from my contract, b) by copying them (OpenZeppelin repository) into my own repository and calling them from my contract, c) by literally integrating (copying) the libraries and interfaces into one contract (script), d) So on and so forth, I just need some best practices or recommendations…
  2. Got it. So, since I am the only one Developer/Contract Owner/Minter, then I should not have to worry/use this? Or are there any other security benefits that I should be aware of?
  3. So, lets say that I copy the OpenZeppelin contracts into my own repository, and call them [import] from my contract, linking them as libraries/interfaces. Then I deploy my “master” contract for the very first time, and this “master” contract calls [import] various libraries, contracts, interfaces available in my repository, then should I presume that all the calls/links that my “master” contract makes would be bundled up and deployed as ONE contract? I am fairly new on this, so I am just trying to make sense out of all this in order to make right decisions early on.
  4. Thanks a lot!

Cheers!


#4
  1. You should install the package through npm and, using a framework like Truffle, import the contracts with Solidity import statements. Please take a look at the “Install” and “Usage” sections in the README. Copying or integrating the source code is strongly discouraged, because you lose the security guarantees we try to build into the contracts.

  2. If you’ll be developing an ERC20 token I would recommend using our ERC20Mintable extension, which uses the MinterRole. This won’t provide security benefits but more flexibility in the future. Other than the built in roles in OpenZeppelin I would suggest to stick to Ownable for anything custom you build, because roles are a complex interface that needs to be tested correctly, and we’re still building some tools for users to create roles themselves.

  3. Every contract you define will result in a separate contract being deployed. If you define contract A is X, Y, the source code for contract A will in a sense “bundle” the source code for A, X and Y together into one contract. If you have multiple contracts in your project, each will be its own “bundle”, which would be deployed separately if you need an instance of it. But some contracts are only there to aid modularization through inheritance, and might not be deployed by themselves, such as maybe X or Y above.

Hope this helps @jaureguino!


#5

Greetings @frangio !

the “Install” and “Usage” sections in README says that I should import from [openzeppelin-solidity], such as:

pragma solidity ^0.5.0;
import 'openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol';
import 'openzeppelin-solidity/contracts/token/ERC721/ERC721Mintable.sol';
contract MyNFT is ERC721Full, ERC721Mintable {
  constructor() ERC721Full("MyNFT", "MNFT") public {
  }
}

However, [https://docs.zeppelinos.org/docs/linking.html] tells me to import from [openzeppelin-eth],
such as:

pragma solidity ^0.4.24;
import "openzeppelin-eth/contracts/token/ERC721/ERC721Mintable.sol";
contract MyLinkedContract {
  ERC721Mintable private _token;
  function setToken(ERC721Mintable token) external {
    require(token != address(0));
    _token = token;
  }
} 

and [https://docs.zeppelinos.org/docs/deploying.html] tells me to import from [zos-lib] such as:

pragma solidity ^0.4.24;
import "zos-lib/contracts/Initializable.sol";
contract MyContract is Initializable {
  uint256 public x;
  string public s;
  function initialize(uint256 _x, string _s) initializer public {
    x = _x;
    s = _s;
  }
}

However, I noticed that the ERC20Mintable extension found in [openzeppelin-eth/contracts/token/ERC20/] also calls/imports [zos-lib/contracts/Initializable.sol]. So, I presume that the Initializable.sol contract will always be there…

Q. Which library should I use to develop secured and upgradeable contracts?


#6

If you’re developing upgradeable contracts you should be using openzeppelin-eth. Prioritize the guides in docs.zeppelinos.org, they are made specifically for your use case.


#7

Greetings @frangio & @elopio, I hope you all are back safe from EthDenver,

As described at the beginning of this topic, this project´s scope includes both the creation of a token and a smart contact that would allow token holders to vote on five (5) different ballot events at different future periods.

While doing further research I came across with :point_right: https://medium.com/validitylabs/how-to-vote-safely-with-an-erc20-token-518adadbf923 which has led me to believe that I would need an ERC721 instead of the ERC20 in order to avoid:

  1. Double voting per Token.
  2. Having to disable Token trading during voting periods.

Before I re-write the token creation contact I was working on (based on ERC20 logic), I have the following questions:

  1. Could I use the ERC721 logic and still treat each Token as having equal weight/value as if it was fungible?
  2. Do I have to create a logic that generates a unique tokenId each time I call the mintWithTokenURI function? or Did I miss something and such unique tokenId is already provided somewhere in the ERC721Metadata.sol logic?
  3. Can I assign a single and universal “image” to each ABC_Token such as tokenURI = http://[abc].com/images/ABC_Token_Image.png ?

g.a.


#9

I’m not sure going with ERC721 is necessarily the best way to go: the questions you arise regarding URIs reflect this in a way.

What you could do is, instead of pausing token transfers during the voting period, use something like our ERC20Snapshot, which will let you store the balances of all accounts at any given point in time, and then use those to calculate voting power.

Keep in mind though ERC0Snapshot is under development at the moment and is not yet part of a release, though it will be included in version 2.2 of OpenZeppelin, in about two weeks.


#10

Thanks @nventuro for your kind response,

Do you have an additional technical reason why you would advise against taking the ERC721 approach?

I would not mind assigning the same universal “image” value into tokenURI to all tokens, which in fact reinforces that each token has the same design/value/weight; and if we add that each tokenId is unique and distinguishable (sort of a certificate number) and that they are also whole, in other words, there are no decimals, it would “look and work” exactly as intended :point_right: a “voting-right” bearer share.

Considering that a snapshot of the balances can be taken via ERC20Snapshot, then only the token holders at that snapshot moment [second] would be able to exercise their vote, and the general intention is that the token holder could still trade their token and/or exercise their vote during the voting period [one week]. The possibility to record the tokenId used during the ballot, would allow me to code the means to prevent its current or future holder (whomever) exercise that right again with the same tokenId. It is expected that a buyer of a token during the voting period may or may not be able to exercise their vote, based on the right exercised or not by the previous owner.

Since the ERC20Snapshot is still under development and I must go live with this token by March 1st, would you have any other technical or logical rationale for me to consider ERC721 a terrible choice?

I look forward to reading your response and thanks again for your contribution,

Dear @frangio or @elopio, do you guys have any other thoughts on this subject?

g.a.


#11

If what you are modelling is closer to paper ballots, then yes, ERC721 may be the way to go. Keep in mind though that, like real life paper ballots, handling multiple of these may be difficult and annoying (e.g. a user votes using their 50 ballots).

Regarding the code itself, you could simply use ERC721, leaving out the ERC721Metadata (optional) extension, and remove the need to deal with tokenURIs that way.


#12

Dear @frangio,

While reading https://docs.zeppelinos.org/docs/writing_contracts.html I found what I believe could be a mistake. Could you please verify/confirm/correct my understanding on this section?

After finishing the reading I realized that I may not want to do the Token upgradeable. But will do so with the associated Smart Contract that will handle the three modules mentioned at the beginning of this topic. Therefore I am committed to use Zepkit (which I already installed) to test and deploy the whole Web3 Dapp with its Token and associated Smart Contract.

For the reasons explained during my last two posts, I have leveraged the ERC721MetadataMintable.sol and followed your previous advice and put together the following TFF_Token.sol contract:

pragma solidity ^0.5.0;

import "openzeppelin-eth/contracts/token/ERC721/ERC721Metadata.sol";
import "openzeppelin-eth/contracts/access/roles/MinterRole.sol";
import "openzeppelin-eth/contracts/ownership/Ownable.sol";
import "zos-lib/contracts/Initializable.sol";

/**
 * @title TFF_Token
 * @dev ERC721 minting logic with metadata, leveraging ZeppelinOS EVM
 * @dev ERC721 facilitates preventing: a) double-voting per token and
 * b) disabling trading during voting periods.
 */

contract TFF_Token is Initializable, ERC721, ERC721Metadata, MinterRole, Ownable {

  address private Owner;
  string private Name;
  string private Symbol;
  string private TokenURI;
  uint256 private TokenId;
  uint8[4] private MintStage;
  uint256[4] private TokensToMint;

  function initialize(address sender) public initializer {
    require(ERC721._hasBeenInitialized());
    require(ERC721Metadata._hasBeenInitialized());
    Name = "TFF_Token";
    Symbol = "TFF";
    ERC721.initialize();
    ERC721Metadata.initialize(Name, Symbol);
    MinterRole.initialize(sender);
    Ownable.initialize(sender);
    Owner = sender;
    TokenURI = "http://thefaustflick.com/images/TFF_Token.png";
    TokenId = 1;
    MintStage[0] = 0;
    TokensToMint[0] = 500000;
    TokensToMint[1] = 3000000;
    TokensToMint[2] = 3000000;
    TokensToMint[3] = 3500000;
    Mint_TFF(0);
  }

  function Mint_TFF(uint8 _Stage) public onlyMinter returns (bool) {
    require(MintStage[_Stage] == 0 || MintStage[_Stage] == 0 && MintStage[_Stage.sub(1)] == 1) // still working on these conditions
    uint256 tokensToMint = TokensToMint[_Stage]
    for (uint256 counter = 1; counter <= tokensToMint; counter++) {
      _mint(Owner, TokenId);
      _setTokenURI(TokenId, TokenURI);
      TokenId = TokenId.add(1);
    }
    MintStage[_Stage] = 1;
    return true;
  }

  function LastTokenId() public view returns (uint256) {
      return TokenId;
  }

  function GetTokenURI() public view returns (uint256) {
      return TokenURI;
  }

  uint256[50] private ______gap;
}

Could you or anyone from the Zeppelin Team be able to review/confirm/validate/correct some of assumptions I made in the following sections:

  1. import [Do I call all the appropriate imports?]
  2. function initialize [Are the require , the [ X ].initialize and the rest of the logic correct]
  3. function Mint_TFF [Is the logic correct? Did I miss something?]
  4. When deploying via ZeppelinOS, I would have to use the following command line:
    $ zos create ./contracts/TFF_Token --init initialize --args <address> ?

Also, when trying to deploy with $ npx zos add ./contracts/TFF_token it is calling an older version of the TFF_Token.sol file that had an array definition error, but has been fixed since.

It seems as when trying to deploy the bugged version it stored the TFF_Token.sol in some sort of cache memory, see image :point_down:. I even exited the command prompt and entered back again and gave me the same error.

  1. How do I clear the TFF_Token.sol from cache?

Thanks in advance for all the great work that you guys are doing at Zeppelin; I look forward to hearing from you soon.

g.a.


#13

Hey Jaureguino! I’m gonna tag @spalladino because he might know more about your ZOS “caching” issue.

  1. Your imports are fine

For 3, are you minting ERC721s that all have the same metadata?


#14

Do you mean by metadata exclusively :point_down:?

    Name = "TFF_Token";
    Symbol = "TFF";

because these above will be the same for all tokens.

Or does it also include :point_down:?

  string private TokenURI;
  uint256 private TokenId;

because the TokenURI will be the same but the TokenId will be unique…

g.a.


#15

I meant the latter, I see :+1:


#16

Hello Jaureguino! So after looking for a bit I saw that the error is actually in a different file called ABC_Token.sol. zos add actually runs truffle compile in the package so make sure there’s no other errors! Or if you don’t need that file then you can get rid of it as well

Try that please and let me know :smile:

For 4 it should be zos create ./contracts/TFF_Token --init initialize --args "addresshere" if I remember correctly.


#17

:man_facepalming:t2:

What an eye!, I guess I should read carefully and entirely the error message. Let me correct that and I will keep you posted…