Flat .sol file generation utility

This commit is contained in:
viktor 2017-07-26 19:13:45 +03:00
commit 191f9d8264
24 changed files with 1023 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
.DS_Store
out/

48
README.md Normal file
View File

@ -0,0 +1,48 @@
## Solidity flat file generation
##### Combines all local imports to one .sol file for any project's structure
##### Limitations:
1. All dependencies are locally in project
2. Only imports like `import "filepath";` are supported
```
git clone https://gitlab.com/blocknotary/oracles-combine-solidity
cd oracles-combine-solidity
npm install
```
You can start script either
```
npm start "path_to_not_flat_contract_definition_file.sol"
```
or without paramaters (path to input file will be extracted from `./config.json`)
```
npm start
```
Expected result:
```
Success! Flat file is generated to ./out directory
```
`./flatContract.sol` - flat .sol file is created in output directory (`./out/` by default)
## Config
path `./config.json`
```
{
"inputFilePath": "./demo/src/Oracles.sol",
"outputDir": "./out"
}
```

4
config.json Normal file
View File

@ -0,0 +1,4 @@
{
"inputFilePath": "./demo/src/Oracles.sol",
"outputDir": "./out"
}

View File

@ -0,0 +1,24 @@
pragma solidity ^0.4.11;
contract BallotClass {
uint[] public ballots;
uint public votingLowerLimit = 3;
struct Ballot {
address owner;
address miningKey;
address affectedKey;
string memo;
uint affectedKeyType;
uint createdAt;
uint votingStart;
uint votingDeadline;
int votesAmmount;
int result;
bool addAction;
bool active;
mapping (address => bool) voted;
}
mapping(uint => Ballot) public ballotsMapping;
}

View File

@ -0,0 +1,26 @@
pragma solidity ^0.4.11;
contract KeyClass {
struct InitialKey {
bool isNew;
}
struct MiningKey {
bool isActive;
}
struct PayoutKey {
bool isActive;
}
struct VotingKey {
bool isActive;
}
mapping(address => MiningKey) public miningKeys;
mapping(address => PayoutKey) public payoutKeys;
mapping(address => VotingKey) public votingKeys;
mapping(address => InitialKey) public initialKeys;
mapping(address => address) public votingMiningKeysPair;
mapping(address => address) public miningPayoutKeysPair;
}

View File

@ -0,0 +1,19 @@
pragma solidity ^0.4.11;
contract ValidatorClass {
address[] public validators;
address[] public disabledValidators;
struct Validator {
string fullName;
string streetName;
string state;
uint zip;
uint licenseID;
uint licenseExpiredAt;
uint disablingDate;
string disablingTX;
}
mapping(address => Validator) public validator;
}

281
demo/src/BallotsManager.sol Normal file
View File

@ -0,0 +1,281 @@
pragma solidity ^0.4.11;
import "./ValidatorsManager.sol";
contract BallotsManager is ValidatorsManager {
/**
@notice Adds new Ballot
@param ballotID Ballot unique ID
@param owner Voting key of notary, who creates ballot
@param miningKey Mining key of notary, which is proposed to add or remove
@param affectedKey Mining/payout/voting key of notary, which is proposed to add or remove
@param affectedKeyType Type of affectedKey: 0 = mining key, 1 = voting key, 2 = payout key
@param addAction Flag: adding is true, removing is false
@param memo Ballot's memo
*/
function addBallot(
uint ballotID,
address owner,
address miningKey,
address affectedKey,
uint affectedKeyType,
bool addAction,
string memo
) {
if (!checkVotingKeyValidity(msg.sender)) throw;
if (licensesIssued == licensesLimit && addAction) throw;
if (ballotsMapping[ballotID].createdAt > 0) throw;
if (affectedKeyType == 0) {//mining key
bool validatorIsAdded = false;
for (uint i = 0; i < validators.length; i++) {
if (validators[i] == affectedKey && addAction) throw; //validator is already added before
if (validators[i] == affectedKey) {
validatorIsAdded = true;
break;
}
}
for (uint j = 0; j < disabledValidators.length; j++) {
if (disabledValidators[j] == affectedKey) throw; //validator is already removed before
}
if (!validatorIsAdded && !addAction) throw; // no such validator in validators array to remove it
} else if (affectedKeyType == 1) {//voting key
if (checkVotingKeyValidity(affectedKey) && addAction) throw; //voting key is already added before
if (!checkVotingKeyValidity(affectedKey) && !addAction) throw; //no such voting key to remove it
} else if (affectedKeyType == 2) {//payout key
if (checkPayoutKeyValidity(affectedKey) && addAction) throw; //payout key is already added before
if (!checkPayoutKeyValidity(affectedKey) && !addAction) throw; //no such payout key to remove it
}
uint votingStart = now;
ballotsMapping[ballotID] = Ballot({
owner: owner,
miningKey: miningKey,
affectedKey: affectedKey,
memo: memo,
affectedKeyType: affectedKeyType,
createdAt: now,
votingStart: votingStart,
votingDeadline: votingStart + 48 * 60 minutes,
votesAmmount: 0,
result: 0,
addAction: addAction,
active: true
});
ballots.push(ballotID);
checkBallotsActivity();
}
/**
@notice Gets active ballots' ids
@return { "value" : "Array of active ballots ids" }
*/
function getBallots() constant returns (uint[] value) {
return ballots;
}
/**
@notice Gets ballot's memo
@param ballotID Ballot unique ID
@return { "value" : "Ballot's memo" }
*/
function getBallotMemo(uint ballotID) constant returns (string value) {
return ballotsMapping[ballotID].memo;
}
/**
@notice Gets ballot's action
@param ballotID Ballot unique ID
@return { "value" : "Ballot's action: adding is true, removing is false" }
*/
function getBallotAction(uint ballotID) constant returns (bool value) {
return ballotsMapping[ballotID].addAction;
}
/**
@notice Gets mining key of notary
@param ballotID Ballot unique ID
@return { "value" : "Notary's mining key" }
*/
function getBallotMiningKey(uint ballotID) constant returns (address value) {
return ballotsMapping[ballotID].miningKey;
}
/**
@notice Gets affected key of ballot
@param ballotID Ballot unique ID
@return { "value" : "Ballot's affected key" }
*/
function getBallotAffectedKey(uint ballotID) constant returns (address value) {
return ballotsMapping[ballotID].affectedKey;
}
/**
@notice Gets affected key type of ballot
@param ballotID Ballot unique ID
@return { "value" : "Ballot's affected key type" }
*/
function getBallotAffectedKeyType(uint ballotID) constant returns (uint value) {
return ballotsMapping[ballotID].affectedKeyType;
}
function toString(address x) internal returns (string) {
bytes memory b = new bytes(20);
for (uint i = 0; i < 20; i++)
b[i] = byte(uint8(uint(x) / (2**(8*(19 - i)))));
return string(b);
}
/**
@notice Gets ballot's owner full name
@param ballotID Ballot unique ID
@return { "value" : "Ballot's owner full name" }
*/
function getBallotOwner(uint ballotID) constant returns (string value) {
address ballotOwnerVotingKey = ballotsMapping[ballotID].owner;
address ballotOwnerMiningKey = votingMiningKeysPair[ballotOwnerVotingKey];
string validatorFullName = validator[ballotOwnerMiningKey].fullName;
bytes memory ownerName = bytes(validatorFullName);
if (ownerName.length == 0)
return toString(ballotOwnerMiningKey);
else
return validatorFullName;
}
/**
@notice Gets ballot's creation time
@param ballotID Ballot unique ID
@return { "value" : "Ballot's creation time" }
*/
function ballotCreatedAt(uint ballotID) constant returns (uint value) {
return ballotsMapping[ballotID].createdAt;
}
/**
@notice Gets ballot's voting start date
@param ballotID Ballot unique ID
@return { "value" : "Ballot's voting start date" }
*/
function getBallotVotingStart(uint ballotID) constant returns (uint value) {
return ballotsMapping[ballotID].votingStart;
}
/**
@notice Gets ballot's voting end date
@param ballotID Ballot unique ID
@return { "value" : "Ballot's voting end date" }
*/
function getBallotVotingEnd(uint ballotID) constant returns (uint value) {
return ballotsMapping[ballotID].votingDeadline;
}
/**
@notice Gets ballot's amount of votes for
@param ballotID Ballot unique ID
@return { "value" : "Ballot's amount of votes for" }
*/
function getVotesFor(uint ballotID) constant returns (int value) {
return (ballotsMapping[ballotID].votesAmmount + ballotsMapping[ballotID].result)/2;
}
/**
@notice Gets ballot's amount of votes against
@param ballotID Ballot unique ID
@return { "value" : "Ballot's amount of votes against" }
*/
function getVotesAgainst(uint ballotID) constant returns (int value) {
return (ballotsMapping[ballotID].votesAmmount - ballotsMapping[ballotID].result)/2;
}
/**
@notice Checks, if ballot is active
@param ballotID Ballot unique ID
@return { "value" : "Ballot's activity: active or not" }
*/
function ballotIsActive(uint ballotID) constant returns (bool value) {
return ballotsMapping[ballotID].active;
}
/**
@notice Checks, if ballot is already voted by signer of current transaction
@param ballotID Ballot unique ID
@return { "value" : "Ballot is already voted by signer of current transaction: yes or no" }
*/
function ballotIsVoted(uint ballotID) constant returns (bool value) {
return ballotsMapping[ballotID].voted[msg.sender];
}
/**
@notice Votes
@param ballotID Ballot unique ID
@param accept Vote for is true, vote against is false
*/
function vote(uint ballotID, bool accept) {
if (!checkVotingKeyValidity(msg.sender)) throw;
Ballot v = ballotsMapping[ballotID];
if (v.votingDeadline < now) throw;
if (v.voted[msg.sender] == true) throw;
v.voted[msg.sender] = true;
v.votesAmmount++;
if (accept) v.result++;
else v.result--;
checkBallotsActivity();
}
/**
@notice Removes element by index from validators array and shift elements in array
@param index Element's index to remove
@return { "value" : "Updated validators array with removed element at index" }
*/
function removeValidator(uint index) internal returns(address[]) {
if (index >= validators.length) return;
for (uint i = index; i<validators.length-1; i++){
validators[i] = validators[i+1];
}
delete validators[validators.length-1];
validators.length--;
}
/**
@notice Checks ballots' activity
@dev Deactivate ballots, if ballot's time is finished and implement action: add or remove notary, if votes for are greater votes against, and total votes are greater than 3
*/
function checkBallotsActivity() internal {
for (uint ijk = 0; ijk < ballots.length; ijk++) {
Ballot b = ballotsMapping[ballots[ijk]];
if (b.votingDeadline < now && b.active) {
if ((int(b.votesAmmount) >= int(votingLowerLimit)) && b.result > 0) {
if (b.addAction) { //add key
if (b.affectedKeyType == 0) {//mining key
if (licensesIssued < licensesLimit) {
licensesIssued++;
validators.push(b.affectedKey);
}
} else if (b.affectedKeyType == 1) {//voting key
votingKeys[b.affectedKey] = VotingKey({isActive: true});
votingMiningKeysPair[b.affectedKey] = b.miningKey;
} else if (b.affectedKeyType == 2) {//payout key
payoutKeys[b.affectedKey] = PayoutKey({isActive: true});
miningPayoutKeysPair[b.miningKey] = b.affectedKey;
}
} else { //invalidate key
if (b.affectedKeyType == 0) {//mining key
for (uint jj = 0; jj < validators.length; jj++) {
if (validators[jj] == b.affectedKey) {
removeValidator(jj);
return;
}
}
disabledValidators.push(b.affectedKey);
validator[b.affectedKey].disablingDate = now;
} else if (b.affectedKeyType == 1) {//voting key
votingKeys[b.affectedKey] = VotingKey({isActive: false});
} else if (b.affectedKeyType == 2) {//payout key
payoutKeys[b.affectedKey] = PayoutKey({isActive: false});
}
}
}
b.active = false;
}
}
}
}

77
demo/src/KeysManager.sol Normal file
View File

@ -0,0 +1,77 @@
pragma solidity ^0.4.11;
import "./owned.sol";
import "oracles-contract-key/KeyClass.sol";
import "oracles-contract-validator/ValidatorClass.sol";
import "oracles-contract-ballot/BallotClass.sol";
contract KeysManager is owned, KeyClass, ValidatorClass, BallotClass {
int8 internal initialKeysIssued = 0;
int8 internal initialKeysLimit = 12;
int8 internal licensesIssued = 0;
int8 internal licensesLimit = 52;
/**
@notice Adds initial key
@param key Initial key
*/
function addInitialKey(address key) onlyOwner {
if (initialKeysIssued >= initialKeysLimit) throw;
initialKeysIssued++;
initialKeys[key] = InitialKey({isNew: true});
}
/**
@notice Create production keys for notary
@param miningAddr Mining key
@param payoutAddr Payout key
@param votingAddr Voting key
*/
function createKeys(
address miningAddr,
address payoutAddr,
address votingAddr
) {
if (!checkInitialKey(msg.sender)) throw;
//invalidate initial key
delete initialKeys[msg.sender];
miningKeys[miningAddr] = MiningKey({isActive: true});
payoutKeys[payoutAddr] = PayoutKey({isActive: true});
votingKeys[votingAddr] = VotingKey({isActive: true});
//add mining key to list of validators
licensesIssued++;
validators.push(miningAddr);
votingMiningKeysPair[votingAddr] = miningAddr;
miningPayoutKeysPair[miningAddr] = payoutAddr;
}
/**
@notice Checks, if initial key is new or not
@param key Initial key
@return { "value" : "Is initial key new or not new" }
*/
function checkInitialKey(address key) constant returns (bool value) {
if (msg.sender != key) throw;
return initialKeys[key].isNew;
}
/**
@notice Checks, if payout key is active or not
@param addr Payout key
@return { "value" : "Is payout key active or not active" }
*/
function checkPayoutKeyValidity(address addr) constant returns (bool value) {
//if (msg.sender != addr) throw;
return payoutKeys[addr].isActive;
}
/**
@notice Checks, if voting key is active or not
@param addr Voting key
@return { "value" : "Is voting key active or not active" }
*/
function checkVotingKeyValidity(address addr) constant returns (bool value) {
//if (msg.sender != addr) throw;
return votingKeys[addr].isActive;
}
}

13
demo/src/Oracles.sol Normal file
View File

@ -0,0 +1,13 @@
pragma solidity ^0.4.11;
import "./BallotsManager.sol";
/**
@title Oracles Interface
@author Oracles
*/
contract Oracles is BallotsManager {
function Oracles() {
validators.push(owner);
}
}

View File

@ -0,0 +1,119 @@
pragma solidity ^0.4.11;
import "oracles-contract-validator/ValidatorClass.sol" as ValidatorClass2;
import "./KeysManager.sol";
contract ValidatorsManager is ValidatorClass, KeysManager {
/**
@notice Adds new notary
@param miningKey Notary's mining key
@param zip Notary's zip code
@param licenseID Notary's license ID
@param licenseExpiredAt Notary's expiration date
@param fullName Notary's full name
@param streetName Notary's address
@param state Notary's US state full name
*/
function addValidator(
address miningKey,
uint zip,
uint licenseID,
uint licenseExpiredAt,
string fullName,
string streetName,
string state
) {
if (!checkVotingKeyValidity(msg.sender) && !checkInitialKey(msg.sender)) throw;
if (licensesIssued == licensesLimit) throw;
validator[miningKey] = ValidatorClass2.Validator({
fullName: fullName,
streetName: streetName,
state: state,
zip: zip,
licenseID: licenseID,
licenseExpiredAt: licenseExpiredAt,
disablingDate: 0,
disablingTX: ""
});
}
/**
@notice Gets active notaries mining keys
@return { "value" : "Array of active notaries mining keys" }
*/
function getValidators() constant returns (address[] value) {
return validators;
}
/**
@notice Gets disabled notaries mining keys
@return { "value" : "Array of disabled notaries mining keys" }
*/
function getDisabledValidators() constant returns (address[] value) {
return disabledValidators;
}
/**
@notice Gets notary's full name
@param addr Notary's mining key
@return { "value" : "Notary's full name" }
*/
function getValidatorFullName(address addr) constant returns (string value) {
return validator[addr].fullName;
}
/**
@notice Gets notary's address
@param addr Notary's mining key
@return { "value" : "Notary's address" }
*/
function getValidatorStreetName(address addr) constant returns (string value) {
return validator[addr].streetName;
}
/**
@notice Gets notary's state full name
@param addr Notary's mining key
@return { "value" : "Notary's state full name" }
*/
function getValidatorState(address addr) constant returns (string value) {
return validator[addr].state;
}
/**
@notice Gets notary's zip code
@param addr Notary's mining key
@return { "value" : "Notary's zip code" }
*/
function getValidatorZip(address addr) constant returns (uint value) {
return validator[addr].zip;
}
/**
@notice Gets notary's license ID
@param addr Notary's mining key
@return { "value" : "Notary's license ID" }
*/
function getValidatorLicenseID(address addr) constant returns (uint value) {
return validator[addr].licenseID;
}
/**
@notice Gets notary's license expiration date
@param addr Notary's mining key
@return { "value" : "Notary's license expiration date" }
*/
function getValidatorLicenseExpiredAt(address addr) constant returns (uint value) {
return validator[addr].licenseExpiredAt;
}
/**
@notice Gets notary's disabling date
@param addr Notary's mining key
@return { "value" : "Notary's disabling date" }
*/
function getValidatorDisablingDate(address addr) constant returns (uint value) {
return validator[addr].disablingDate;
}
}

14
demo/src/owned.sol Normal file
View File

@ -0,0 +1,14 @@
pragma solidity ^0.4.11;
contract owned {
address public owner;
function owned() {
owner = 0xDd0BB0e2a1594240fED0c2f2c17C1E9AB4F87126; //msg.sender
}
modifier onlyOwner {
if (msg.sender != owner) throw;
_;
}
}

40
helpers/add-libraries.js Normal file
View File

@ -0,0 +1,40 @@
const fs = require('fs');
const glob = require("glob");
const variables = require("./variables.js");
const findUsingLibraryFor = require("./find-libraries-usage.js");
function addLibraries(parentDir, inputFileContent, srcFiles, cb) {
let updatedFileContent = inputFileContent;
let usingLibrariesFound = 0;
findUsingLibraryFor(updatedFileContent, function(usingLibraries) {
for (let k = 0; k < usingLibraries.length; k++) {
let usingLibraryName = usingLibraries[k];
for (let j = 0; j < srcFiles.length; j++) {
let fileContent = fs.readFileSync(srcFiles[j], "utf8");
if (fileContent.indexOf("library " + usingLibraryName) > -1) {
if (variables.importedSrcFiles.indexOf(srcFiles[j]) === -1) {
updatedFileContent = fileContent + updatedFileContent;
srcFiles.splice(j,1);
variables.importedSrcFiles.push(srcFiles[j]);
usingLibrariesFound++;
}
break;
}
}
}
if (usingLibraries.length > usingLibrariesFound) {
if (parentDir.lastIndexOf("/") > -1) {
parentDir = parentDir.substring(0, parentDir.lastIndexOf("/"));
glob(parentDir + "/**/*.sol", function(err, srcFiles) {
addLibraries(parentDir, inputFileContent, srcFiles, cb);
});
return;
}
}
cb(updatedFileContent);
});
}
module.exports = addLibraries;

View File

@ -0,0 +1,57 @@
const fs = require('fs');
const path = require('path');
const findFile = require("./find-file.js");
function findAllImportPaths(dir, content, cb) {
const subStr = "import ";
let allImports = [];
let regex = new RegExp(subStr,"gi");
var importsCount = (content.match(regex) || []).length;
let importsIterator = 0;
while ( (result = regex.exec(content)) ) {
let startImport = result.index;
let endImport = startImport + content.substr(startImport).indexOf(";") + 1;
let fullImportStatement = content.substring(startImport, endImport);
let dependencyPath = fullImportStatement.split("\"").length > 1 ? fullImportStatement.split("\"")[1]: fullImportStatement.split("'")[1];
let alias = fullImportStatement.split(" as ").length > 1?fullImportStatement.split(" as ")[1].split(";")[0]:null;
let contractName;
importObj = {
"startIndex": startImport,
"endIndex": endImport,
"dependencyPath": dependencyPath,
"fullImportStatement": fullImportStatement,
"alias": alias,
"contractName": null
};
if (alias) {
alias = alias.replace(/\s/g,'');
var fileExists = fs.existsSync(dependencyPath, fs.F_OK);
if (fileExists) {
importsIterator++;
let fileContent = fs.readFileSync(dependencyPath, "utf8");
if (fileContent.indexOf("contract ") > -1) {
importObj.contractName = fileContent.substring((fileContent.indexOf("contract ") + ("contract ").length), fileContent.indexOf("{")).replace(/\s/g,'');
}
allImports.push(importObj);
} else {
findFile.byName(dir.substring(0, dir.lastIndexOf("/")), path.basename(dependencyPath), function(fileContent) {
importsIterator++;
if (fileContent.indexOf("contract ") > -1) {
importObj.contractName = fileContent.substring((fileContent.indexOf("contract ") + ("contract ").length), fileContent.indexOf("{")).replace(/\s/g,'');
}
allImports.push(importObj);
if (importsIterator == importsCount) cb(allImports);
});
}
} else {
importsIterator++;
allImports.push(importObj);
}
}
if (importsIterator == importsCount) cb(allImports);
}
module.exports = findAllImportPaths;

61
helpers/find-file.js Normal file
View File

@ -0,0 +1,61 @@
const fs = require('fs');
const glob = require("glob");
const path = require("path");
const variables = require("./variables.js");
function byName(dir, fileName, cb) {
glob(dir + "/**/*.sol", function(err, srcFiles) {
if (err) return console.log(err.message);
for (var j = 0; j < srcFiles.length; j++) {
if (path.basename(srcFiles[j]) == fileName) {
var fileContent = fs.readFileSync(srcFiles[j], "utf8");
cb(fileContent);
return;
}
}
dir = dir.substring(0, dir.lastIndexOf("/"));
byName(dir, fileName, cb);
});
}
function byNameAndReplace(dir, fileName, updatedFileContent, importStatement, cb) {
glob(dir + "/**/*.sol", function(err, srcFiles) {
if (err) return console.log(err.message);
var importIsReplacedBefore = false;
for (var j = 0; j < srcFiles.length; j++) {
if (path.basename(srcFiles[j]) == fileName) {
if (variables.importedSrcFiles.indexOf(srcFiles[j]) === -1) {
var fileContent = fs.readFileSync(srcFiles[j], "utf8");
updatedFileContent = updatedFileContent.replace(importStatement, fileContent);
variables.importedSrcFiles.push(srcFiles[j]);
cb(updatedFileContent);
return;
} else {
importIsReplacedBefore = true;
}
break;
}
}
if (importIsReplacedBefore) {
updatedFileContent = updatedFileContent.replace(importStatement, "");
cb(updatedFileContent);
} else {
if (dir.indexOf("/") > -1) {
dir = dir.substring(0, dir.lastIndexOf("/"));
byNameAndReplace(dir, fileName, updatedFileContent, importStatement, cb);
} else {
updatedFileContent = updatedFileContent.replace(importStatement, "");
cb(updatedFileContent);
}
}
});
}
module.exports = {
byName: byName,
byNameAndReplace: byNameAndReplace
};

View File

@ -0,0 +1,15 @@
function findUsingLibraryFor(content, cb) {
const subStr = "using ";
let usingLibraries = [];
let regex = new RegExp(subStr,"gi");
while ( (result = regex.exec(content)) ) {
let startUsingLibraryFor = result.index;
let endUsingLibraryFor = startUsingLibraryFor + content.substr(startUsingLibraryFor).indexOf(";") + 1;
let dependencyPath = content.substring(startUsingLibraryFor, endUsingLibraryFor);
dependencyPath = dependencyPath.split(subStr)[1].split("for")[0].replace(/\s/g,'');
usingLibraries.indexOf(dependencyPath) === -1 ? usingLibraries.push(dependencyPath) : console.log("This item already exists");
}
cb(usingLibraries);
}
module.exports = findUsingLibraryFor;

View File

@ -0,0 +1,22 @@
const removeTabs = require("./remove-tabs.js");
function removeDoubledSolidityVersion(content) {
const subStr = "pragma solidity";
//1st pragma solidity declaration
let firstIndex = content.indexOf(subStr);
let lastIndex = firstIndex + content.substr(firstIndex).indexOf(";") + 1;
let contentPart = content.substr(lastIndex);
let contentFiltered = contentPart;
//remove other pragma solidity declarations
let regex = new RegExp(subStr,"gi");
while ( (result = regex.exec(contentPart)) ) {
let start = result.index;
let end = start + contentPart.substr(start).indexOf(";") + 1;
if (start != firstIndex) contentFiltered = contentFiltered.replace(contentPart.substring(start, end), "");
}
let finalContent = content.substr(0, lastIndex) + contentFiltered;
return removeTabs(finalContent);
}
module.exports = removeDoubledSolidityVersion;

6
helpers/remove-tabs.js Normal file
View File

@ -0,0 +1,6 @@
function removeTabs(content) {
content = content.replace(/[\r\n]+/g, '\n'); //removes tabs
return content;
}
module.exports = removeTabs;

View File

@ -0,0 +1,43 @@
const fs = require('fs');
const path = require("path");
const variables = require("./variables.js");
const findFile = require("./find-file.js");
const replaceRelativeImportPaths = require("./replace-relative-import-paths.js");
const updateImportObjectLocationInTarget = require("./update-import-object-location-in-target.js");
function replaceAllImportsInCurrentLayer(i, importObjs, updatedFileContent, dir, cb) {
if (i < importObjs.length) {
var importObj = importObjs[i];
//replace contracts aliases
if (importObj.contractName) {
updatedFileContent = updatedFileContent.replace(importObj.alias + ".", importObj.contractName + ".");
}
importObj = updateImportObjectLocationInTarget(importObj, updatedFileContent);
var importStatement = updatedFileContent.substring(importObj.startIndex, importObj.endIndex);
var fileExists = fs.existsSync(dir + importObj.dependencyPath, fs.F_OK);
if (fileExists) {
var importedFileContent = fs.readFileSync(dir + importObj.dependencyPath, "utf8");
replaceRelativeImportPaths(importedFileContent, path.dirname(importObj.dependencyPath) + "/", function(importedFileContentUpdated) {
if (variables.importedSrcFiles.indexOf(path.basename(dir + importObj.dependencyPath)) === -1) {
variables.importedSrcFiles.push(path.basename(dir + importObj.dependencyPath));
updatedFileContent = updatedFileContent.replace(importStatement, importedFileContentUpdated);
}
else updatedFileContent = updatedFileContent.replace(importStatement, "");
i++;
replaceAllImportsInCurrentLayer(i, importObjs, updatedFileContent, dir, cb);
});
} else {
console.log("!!!" + importObj.dependencyPath + " SOURCE FILE NOT FOUND. TRY TO FIND IT RECURSIVELY!!!");
findFile.byNameAndReplace(dir.substring(0, dir.lastIndexOf("/")), path.basename(importObj.dependencyPath), updatedFileContent, importStatement, function(_updatedFileContent) {
i++;
replaceAllImportsInCurrentLayer(i, importObjs, _updatedFileContent, dir, cb);
});
}
} else cb(updatedFileContent);
}
module.exports = replaceAllImportsInCurrentLayer;

View File

@ -0,0 +1,16 @@
const findAllImportPaths = require("./find-all-import-paths.js");
const replaceAllImportsInCurrentLayer = require("./replace-all-imports-in-current-layer");
function replaceAllImportsRecursively(fileContent, dir, cb) {
let updatedFileContent = fileContent;
findAllImportPaths(dir, updatedFileContent, function(_importObjs) {
if (!_importObjs) return cb(updatedFileContent);
if (_importObjs.length == 0) return cb(updatedFileContent);
replaceAllImportsInCurrentLayer(0, _importObjs, updatedFileContent, dir, function(_updatedFileContent) {
replaceAllImportsRecursively(_updatedFileContent, dir, cb);
});
});
};
module.exports = replaceAllImportsRecursively;

View File

@ -0,0 +1,33 @@
const updateImportObjectLocationInTarget = require("./update-import-object-location-in-target.js");
const findAllImportPaths = require("./find-all-import-paths.js");
function replaceRelativeImportPaths(fileContent, curDir, cb) {
let updatedFileContent = fileContent;
findAllImportPaths(curDir, fileContent, function(importObjs) {
if (!importObjs) return cb(updatedFileContent);
if (importObjs.length == 0) return cb(updatedFileContent);
for (let j = 0; j < importObjs.length; j++) {
let importObj = importObjs[j];
importObj = updateImportObjectLocationInTarget(importObj, updatedFileContent);
let importStatement = updatedFileContent.substring(importObj.startIndex, importObj.endIndex);
let newPath;
if (importObj.dependencyPath.indexOf("../") == 0) {
newPath = curDir + importObj.dependencyPath;
}
else if (importObj.dependencyPath.indexOf("./") == 0) {
newPath = curDir + importObj.dependencyPath;
}
else {
newPath = importObj.dependencyPath;
}
let importStatementNew = importStatement.replace(importObj.dependencyPath, newPath);
updatedFileContent = updatedFileContent.replace(importStatement, importStatementNew);
}
cb(updatedFileContent);
});
}
module.exports = replaceRelativeImportPaths;

View File

@ -0,0 +1,9 @@
function updateImportObjectLocationInTarget(importObj, content) {
let startIndexNew = content.indexOf(importObj.fullImportStatement);
let endIndexNew = startIndexNew - importObj.startIndex + importObj.endIndex;
importObj.startIndex = startIndexNew;
importObj.endIndex = endIndexNew;
return importObj;
}
module.exports = updateImportObjectLocationInTarget;

31
helpers/variables.js Normal file
View File

@ -0,0 +1,31 @@
const path = require("path");
const fs = require('fs');
const configPath = "./config.json";
let configExists = fs.existsSync(configPath, fs.F_OK);
let config;
if (configExists) config = JSON.parse(fs.readFileSync(configPath, "utf8"));
//Input solidity file path
let args = process.argv.slice(2);
let inputFilePath = args.length > 0?args[0]:config.inputFilePath;
//Input solidity file dir name
let inputFileDir = path.dirname(inputFilePath);
//Input parent dir
let parentDir = inputFileDir;
//Output directory to store flat combined solidity file
let outDir = config.outputDir?config.outputDir:"./out";
let allSrcFiles = [];
let importedSrcFiles = [];
module.exports = {
args: args,
inputFilePath: inputFilePath,
inputFileDir: inputFileDir,
parentDir: parentDir,
outDir: outDir,
allSrcFiles: allSrcFiles,
importedSrcFiles: importedSrcFiles
}

34
index.js Normal file
View File

@ -0,0 +1,34 @@
const fs = require('fs');
const glob = require("glob");
const path = require("path");
const variables = require("./helpers/variables.js");
const removeDoubledSolidityVersion = require("./helpers/remove-doubled-solidity-version.js");
const replaceAllImportsRecursively = require("./helpers/replace-all-imports-recursively.js");
const addLibraries = require("./helpers/add-libraries.js");
fs.readFile(variables.inputFilePath, "utf8", readInputFileCallBack);
function readInputFileCallBack(err, inputFileContent) {
if (err) return console.log(err.message);
generateFlatFile(variables.parentDir + "/", variables.parentDir + "/**/*.sol", inputFileContent);
}
function generateFlatFile(dir, path, inputFileContent) {
glob(path, function(err, srcFiles) {
variables.allSrcFiles = srcFiles;
if (err) return console.log(err.message);
getAllSolFilesCallBack(inputFileContent, dir, path, srcFiles);
});
}
function getAllSolFilesCallBack(inputFileContent, dir, path, srcFiles) {
addLibraries(variables.parentDir, inputFileContent, variables.allSrcFiles, function(intermediateFileContent) {
replaceAllImportsRecursively(inputFileContent, dir, function(outputFileContent) {
outputFileContent = removeDoubledSolidityVersion(outputFileContent);
if (!fs.existsSync(variables.outDir)) fs.mkdirSync(variables.outDir);
fs.writeFileSync(variables.outDir + "/flatContract.sol", outputFileContent);
console.log("Success! Flat file is generated to " + variables.outDir + " directory");
});
});
}

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "oracles-combine-solidity",
"version": "1.0.0",
"description": "Combine solidity files to one flat file",
"scripts": {
"start": "node index.js"
},
"repository": {
"type": "git",
"url": "https://github.com/oraclesorg/oracles-combine-solidity"
},
"keywords": [],
"author": "oraclesorg",
"license": "MIT",
"homepage": "https://oracles.org/",
"dependencies": {
"glob": "^7.1.2",
"path": "^0.12.7"
},
"engines": {
"node": "6.10.1"
},
"bugs": {
"url": "https://github.com/oraclesorg/oracles-combine-solidity"
},
"main": "index.js",
"devDependencies": {}
}