2

I firstly had a problem with import statement inside my contracts, I figured out that the solc compiler didn't handle import statement, and that we needed to make out own. With the help of some nice peoople, I made this script as my own compiler with a built in function that handle import statement :

"use-strict";

const path = require("path");
const solc = require("solc"); //don"t forget to install the right solc version !
const fs = require("fs-extra");

// test folder
const ERC20SourceCode = fs.readFileSync("./test/ERC20.sol");

// interfaces folder
const IERC20SourceCode = fs.readFileSync("./interfaces/IERC20.sol");
const IUniswapV2CalleeSourceCode = fs.readFileSync("./interfaces/IUniswapV2Callee.sol");
const IUniswapV2ERC20SourceCode = fs.readFileSync("./interfaces/IUniswapV2ERC20.sol");
const IUniswapV2FactorySourceCode = fs.readFileSync("./interfaces/IUniswapV2Factory.sol");
const IUniswapV2PairSourceCode = fs.readFileSync("./interfaces/IUniswapV2Pair.sol");

// libraries folder
const MathSourceCode = fs.readFileSync("./libraries/Math.sol");
const UQ112x112SourceCode = fs.readFileSync("./libraries/UQ112x112.sol");
const SafeMathSourceCode = fs.readFileSync("./libraries/SafeMath.sol");

// core folder
const UniswapV2PairSourceCode = fs.readFileSync("./UniswapV2Pair.sol");
const UniswapV2ERC20SourceCode = fs.readFileSync("./UniswapV2ERC20.sol");
const UniswapV2FactorySourceCode = fs.readFileSync("./UniswapV2Factory.sol");

const buildPath = path.resolve(__dirname, "build");

fs.removeSync(buildPath);

function compileContract(Contract) {
    const contractPath = path.resolve(__dirname, ...Contract);  
    
    const contractSourceCode = fs.readFileSync(contractPath, "utf8");
    
    fs.ensureDirSync(buildPath);

    var input = {
        language: "Solidity",
        sources: {
            Contract: {
                content: contractSourceCode
            }
        },
        settings: {
            optimizer: {
                enabled: true
            },
            outputSelection: {
                "*": {
                    "*": [ "*" ]
                }
            }
        }
    };

    function findImports(path) {
        if (path === "test/ERC20.sol") return { contents: `${ERC20SourceCode}` };
        if (path === "interfaces/IUniswapV2Factory.sol") return { contents: `${IUniswapV2FactorySourceCode}` };
        if (path === "interfaces/IERC20.sol") return { contents: `${IERC20SourceCode}` };
        if (path === "interfaces/IUniswapV2Callee.sol") return { contents: `${IUniswapV2CalleeSourceCode}` };
        if (path === "interfaces/IUniswapV2ERC20.sol") return { contents: `${IUniswapV2ERC20SourceCode}` };
        if (path === "interfaces/IUniswapV2Pair.sol") return { contents: `${IUniswapV2PairSourceCode}` };
        if (path === "libraries/Math.sol") return { contents: `${MathSourceCode}` };
        if (path === "libraries/UQ112x112.sol") return { contents: `${UQ112x112SourceCode}` };
        if (path === "libraries/SafeMath.sol") return { contents: `${SafeMathSourceCode}` };
        if (path === "UniswapV2Pair.sol") return { contents: `${UniswapV2PairSourceCode}` };
        if (path === "UniswapV2ERC20.sol") return { contents: `${UniswapV2ERC20SourceCode}` };
        if (path === "UniswapV2Factory.sol") return { contents: `${UniswapV2FactorySourceCode}` };
        else return { error: "File not found" };
      }

    let output = JSON.parse(solc.compile(JSON.stringify(input), { import: findImports }));

    for(let contractName in output.contracts.Contract) {
        fs.outputJsonSync(
            path.resolve(buildPath, `${contractName}.json`),
            output.contracts.Contract[contractName]
        );
    }    
}

compileContract(["./", "UniswapV2Factory.sol"]);
compileContract(["./", "UniswapV2Pair.sol"]);
compileContract(["./", "UniswapV2ERC20.sol"]);
compileContract(["./", "libraries", "Math.sol"]);
compileContract(["./", "libraries", "SafeMath.sol"]);
compileContract(["./", "libraries", "UQ112x112.sol"]);
compileContract(["./", "interfaces", "IUniswapV2Factory.sol"]);
compileContract(["./", "interfaces", "IERC20.sol"]);
compileContract(["./", "interfaces", "IUniswapV2Callee.sol"]);
compileContract(["./", "interfaces", "IUniswapV2ERC20.sol"]);
compileContract(["./", "interfaces", "IUniswapV2Pair.sol"]);
compileContract(["./", "test", "ERC20.sol"]);

My problem is that I am new in the development world, and I would like to have the opinion of more experienced people about the elegancy of this solution. Am I using rightly the findImports function ?

I have many if statement which makes the solution potentially not optimal. I also have to store each contract's source code for each import statement I have in my contracts ... How would you have approched the problem yourself ?

PS : here are the docs I used to make this

4
  • the only solution that matters is the one that saves gas. everybody is sick of these fees
    – Nulik
    Jul 25 at 15:55
  • 1
    @Nulik you are right, when you write code in Solidity you should sometimes trade your elegancy to create more cheap solutions. But even though the question is related to Solidity it is more about how to implement imports more elegantly in JS which, fortunately, doesn't require sacrificing in beauty of the code. Jul 25 at 16:06
  • 1
    I see. I don't know if I'm missing or misunderstanding something, but compiling has no direct link with gas fees does it ? At the end, whatever the compile script, it should give the same json file right ?
    – Cizia
    Jul 25 at 16:07
  • 1
    I would read the files inside the findImports function so you don't have to hardcode the paths. It is good to learn how solc works. You should consider having similar behavior than existing frameworks with imports, like Hardhat or Truffle.
    – Ismael
    Jul 26 at 7:59
1

I don't know why I made it so complicated in my head, but yeah, here is a more elegant version !

    function findImports(path) {
        const importSourceCode = fs.readFileSync(`./${path}`);
        return { contents: `${importSourceCode}` }
      }

If any one has a better answer, feel free to share ! But this one satisfy my small skills for now (it still gets a little more complicated when path get more complex, going into node_modules or other folders).

EDIT : here is the other solution for more complex root. This function works for the periphery folder of uniswap v2 contracts. Basically it handle :

  • imports from node_module
  • contracts that are in the same folder of the compiled contract (i.e not accessible from the compile.js script using the import path of the compiled contract)
  • import paths that need one way up
  • import path within the root

Here is the function :

    function findImports(path) {
        let sourceCodeToImport;
        if(path[0] === "@") { // directly into node_ module
            sourceCodeToImport = fs.readFileSync(`../../../node_modules/${path}`);
            return { contents: `${sourceCodeToImport}` };
        } 
        if (arrayContractPath.length === 2) { // array contract path is "./" + contract.sol, i.e simple import in the same folder as the compile.js
            sourceCodeToImport = fs.readFileSync(`./${path}`);
            return { contents: `${sourceCodeToImport}` };
        }
        if(!path.includes("/")) { // === contract to import is in the same folder as the contract we are compiling i.e the import path from the contract fiel doesn't include "/"
            sourceCodeToImport = fs.readFileSync(`./${intermediateFoldersOfCurrentContract}/${path}`);
            return { contents: `${sourceCodeToImport}` };
        }
        else { // if neither of these, contract must be (in my case) accessible from the compile.js, i.e no need to change the path
            sourceCodeToImport = fs.readFileSync(`./${path}`);
            return { contents: `${sourceCodeToImport}` }
        }
      }

It was hard to know if the imported contract was in the same folder or not, from withing the compile.js file ! Because the path argument will be the same wether it starts with "./" or "../" ... This made it hard. But it work :)

Feel free to take it and improve it. If you do improve it, please notify me.

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.