I'd like to save the contract addresses from DAI and UNI into a mapping when calling the smart contract constructor depending on the network. Below is a minimalistic example that works fine:

contract TokenManager {

   mapping(string => address) public contractERC20;
   address private ZERO_ADDRESS = 0x0000000000000000000000000000000000000000;

   // Contract constructor
   constructor(string memory network) public {
       if (compareStrings(network, "ropsten")) {
           registerERC20("DAI", 0xaD6D458402F60fD3Bd25163575031ACDce07538D);
           registerERC20("UNI", 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984);

   // Compare two strings
   function compareStrings(string memory s1, string memory s2) private pure returns(bool){
       return keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2));

   // Register a ERC20 contract address
   function registerERC20(string memory _tokenName, address _tokenContract) public {
       require(bytes(_tokenName).length != 0,
           "Token name can't be empty");
       require(contractERC20[_tokenName] == ZERO_ADDRESS,
           "ERC20 contract address already registered");
       contractERC20[_tokenName] = _tokenContract;
   // ...

However, I believe this is not the best approach since the addresses are hardcoded in the contract, and the full list of tokens multiplied by the number of networks can be big. In addition, it would be better to choose the tokens to be created directly from a migrations file in Truffle (for instance), instead of updating the contract for every deployment.

I was considering passing an array of objects to the constructor with all the addresses, so that the contract loops through this array and assigns each address, but didn't find a way to send such array of objects/mappings to the constructor from Truffle.

Therefore, is there a better way to handle this assignment of addresses during the contract deployment?


First, I recommend switching from string to bytes32, because:

  1. The compiler currently doesn't support passing string arrays as input
  2. Your requirements (network names and token symbols) seem to be less than 32 characters

Second, I recommend implementing it in an owner-only function instead of in the constructor:

pragma solidity 0.6.12;

contract TokenManager {
    address public owner;
    mapping(bytes32 => mapping(bytes32 => address)) public contractERC20;

    constructor() public {
        owner = msg.sender;

    function register(
        bytes32 network,
        bytes32[] memory tokenNames,
        address[] memory tokenAddresses
    ) external {
        require(msg.sender == owner);
        uint256 length = tokenNames.length;
        require(length == tokenAddresses.length);
        for (uint256 i = 0; i < length; i++)
            contractERC20[network][tokenNames[i]] = tokenAddresses[i];

Then, you can register your networks one by one.

  • Great answer and thanks for the bytes32 suggestion. Just a couple of remarks: 1) Compiler 0.6.5 complained about the function being external combined with the use of memory -> I guess doing this function public and for owner only is still OK? 2) I noticed you store the tokenNames.length into a variable length. Is this to avoid retrieving the length again in the for? – Sergi Juanati Nov 21 '20 at 12:14
  • 1
    @SergiJuanati: Not really, though it might be slightly more efficient because of that. I'm doing this because I also need to compare it with the length of the other input array, so... cleaner code, you know... – goodvibration Nov 21 '20 at 16:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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