Which smart contract flattener is best suited when we have oz SDK dependencies

Topic:
Which smart contract flattener is best suited when we have oz SDK dependencies

Background:

  • I have built and deployed a few smart contracts with oz SDK dependencies
  • However, it fails when attempting to verify it using the source code in Etherscan &/or Terminal
  • I was asked to try a smart contract flattener

Smart Contract Snippet

pragma solidity >=0.5.0 <0.7.0;

/// dependencies
import "@openzeppelin/upgrades/contracts/Initializable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/lifecycle/Pausable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/StandaloneERC20.sol";

Question:
Which of the following two flatteners should I use?

  1. Truffle Flattener
  2. Solidity Flattener

Has anyone else run into a similar issue?

1 Like

Hi @pkr,

You can verify using the OpenZeppelin CLI: Verify with OpenZeppelin SDK

For manual verification of logic contracts I use truffle-flattener: Verifying a contract inheriting from OpenZeppelin Contracts

If you run into issues, please share your contract that you are attempting to verify, along with the compiler settings.

1 Like

Thanks @abcoathup I tried oz verify and it is failing – any tips?

Smart Contract

pragma solidity >=0.5.0 <0.7.0;

/// dependencies
import "@openzeppelin/upgrades/contracts/Initializable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/lifecycle/Pausable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/StandaloneERC20.sol";

contract AST20 is Initializable, Ownable, Pausable, StandaloneERC20 {
    using SafeMath for uint256;
    /// states
    address aST20Owner;
    string tokenName;
    /// events
    event TokenDeployed(address _owner);
    event GetTokenName(string _tokenName);
    /// functions
    /// initialize function
    function initialize(
        string memory _name,
        string memory _ticker,
        uint8 _DECIMALS,
        uint256 _INITIALSUPPLY,
        address _owner
    )
        public initializer
        returns(address)
    {
        aST20Owner = _owner;
        Ownable.initialize(aST20Owner);
        Pausable.initialize(aST20Owner);
        address[] memory minters = new address[](1);
        minters[0] = aST20Owner;
        address[] memory pausers = new address[](1);
        pausers[0] = aST20Owner;
        // initialize StandaloneERC20
        StandaloneERC20.initialize(_name, _ticker, _DECIMALS, _INITIALSUPPLY, _owner, minters, pausers);
        emit TokenDeployed(aST20Owner);
        return aST20Owner;

    }
}

Steps

$ oz check
$ oz create
$ oz send-tx
$ oz verify (w/ optimizer No)
$ oz verify (w/ optimizer Yes)

Errors

$ oz check
Nothing to compile, all contracts are up to date.
No issues were found

$ oz create
Nothing to compile, all contracts are up to date.
? Pick a contract to instantiate AST20
? Pick a network rinkeby
All contracts are up to date
? Do you want to call a function on the instance after creating it? No
Possible initialization method (initialize) found in contract. Make sure you initialize your instance.
✓ Instance created at 0xF945192F6f26D31F85CcE8666f358D0c1a665705
0xF945192F6f26D31F85CcE8666f358D0c1a665705

$ oz send-tx
? Pick a network rinkeby
? Pick an instance AST20 at 0xF945192F6f26D31F85CcE8666f358D0c1a665705
? Select which function * initialize(name: string, symbol: string, decimals: uint8, initialSupply: uint256, initialHolder: address, minters: address[], pausers: address[])
? name (string): Deakin University Library
? symbol (string): DUL
? decimals (uint8): 2
? initialSupply (uint256): 100000
? initialHolder (address): 0xb99f16787923B6Ba407d2Bd0cB4bAD92739bdec8
? minters (address[]): 0xb99f16787923B6Ba407d2Bd0cB4bAD92739bdec8
? pausers (address[]): 0xb99f16787923B6Ba407d2Bd0cB4bAD92739bdec8
✓ Transaction successful. Transaction hash: 0xc479e61ac7a00a4b2ecbc85faef6a524e4448df16339bf7d667ef87f04b95d11
Events emitted: 
 - Transfer(0x0000000000000000000000000000000000000000, 0xb99f16787923B6Ba407d2Bd0cB4bAD92739bdec8, 100000)
 - MinterRemoved(0xF945192F6f26D31F85CcE8666f358D0c1a665705)
 - PauserRemoved(0xF945192F6f26D31F85CcE8666f358D0c1a665705)

$ oz verify
? Pick a contract AST20
? Pick a network rinkeby
? Was the optimizer enabled when you compiled your contracts? No
? Select an endpoint etherscan
? Provide an etherscan API KEY <my api key>
✖ Verifying and publishing contract source code of AST20 on etherscan (this usually takes under 30 seconds)
Error while trying to verify contract: Fail - Unable to verify

$ oz verify
? Pick a contract AST20
? Pick a network rinkeby
? Was the optimizer enabled when you compiled your contracts? Yes
? Specify the optimizer runs 200
? Select an endpoint etherscan
? Provide an etherscan API KEY <my api key>
✖ Verifying and publishing contract source code of AST20 on etherscan (this usually takes under 30 seconds)
Error while trying to verify contract: Fail - Unable to verify
1 Like

Hi @pkr,

As per Verify with OpenZeppelin SDK due to an Issue compile explicitly with the optimizer off.

Compile (with optimizer off)

$ oz compile --optimizer off
✓ Compiled contracts with solc 0.5.12 (commit.7709ece9)

Create

I didn’t bother to call initialize for the purposes of this example

$ oz create
Nothing to compile, all contracts are up to date.
? Pick a contract to instantiate AST20
? Pick a network rinkeby
- 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.
✓ Contract AST20 deployed
All contracts have been deployed
? Do you want to call a function on the instance after creating it? No
Possible initialization method (initialize) found in contract. Make sure you initialize your instance.
✓ Instance created at 0x36bc96eA75667c26cBF0AC7c84208A85f42357fC
0x36bc96eA75667c26cBF0AC7c84208A85f42357fC

Verify

$ oz verify
? Pick a contract AST20
? Pick a network rinkeby
? Was the optimizer enabled when you compiled your contracts? No
? Select an endpoint etherscan
? Provide an etherscan API KEY ZZZZZMYAPIKEYZZZZZ
✓ Contract source code of AST20 verified and published successfully. You can check it here: https://rinkeby.etherscan.io/address/0x6b5003B6cF30CbD8345aa09586114140399F1792#code

Verified Logic Contract

https://rinkeby.etherscan.io/address/0x6b5003B6cF30CbD8345aa09586114140399F1792#code

That help came in timely, thank you @abcoathup

I re-created those steps and it works on my end as well.

I made an observation that when you oz create it creates an instance with the following address 0x36bc96eA75667c26cBF0AC7c84208A85f42357fC

And when you oz verify it creates yet another new address 0x6b5003B6cF30CbD8345aa09586114140399F1792

The same thing happened here when I ran those commands.

Now we have 2 instances of the same contract – Is this okay and normal behavior?

1 Like

Hi @pkr,

One address is for the proxy, this is the address shown from oz create, whilst the other address is for the logic conract, this is the address shown from oz verify:
Proxy contract: 0x36bc96eA75667c26cBF0AC7c84208A85f42357fC
Logic contract: 0x6b5003B6cF30CbD8345aa09586114140399F1792

If you have a look at rinkeby.json in .openzeppelin directory it should show the address of the contract (Proxy) and the address of the implementation (Logic).

rinkeby.json

      {
        "address": "0x36bc96eA75667c26cBF0AC7c84208A85f42357fC",
        "version": "1.0.0",
        "implementation": "0x6b5003B6cF30CbD8345aa09586114140399F1792",
        "admin": "0xF933700d11B6e14F8DB0CF21c1d3eD394973ACC8",
        "kind": "Upgradeable"
      }
1 Like