diff --git a/.circleci/config.yml b/.circleci/config.yml index 35509a56e..b7e427d3e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -551,7 +551,7 @@ jobs: command: | node --version npm --version - test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) + test/externalTests/solc-js/solc-js.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) t_ems_external_gnosis: docker: diff --git a/scripts/test_emscripten.sh b/scripts/test_emscripten.sh index 783a64065..13eeeebde 100755 --- a/scripts/test_emscripten.sh +++ b/scripts/test_emscripten.sh @@ -39,6 +39,4 @@ SOLJSON="$REPO_ROOT/$BUILD_DIR/libsolc/soljson.js" VERSION=$("$REPO_ROOT"/scripts/get_version.sh) echo "Running solcjs tests...." -"$REPO_ROOT/test/solcjsTests.sh" "$SOLJSON" "$VERSION" -echo "Running external tests...." -"$REPO_ROOT/test/externalTests.sh" "$SOLJSON" +"$REPO_ROOT/test/externalTests/solc-js/solc-js.sh" "$SOLJSON" "$VERSION" diff --git a/test/externalTests.sh b/test/externalTests.sh index acac38629..d7ba829a7 100755 --- a/test/externalTests.sh +++ b/test/externalTests.sh @@ -44,6 +44,7 @@ printTask "Running external tests..." $REPO_ROOT/externalTests/zeppelin.sh "$SOLJSON" $REPO_ROOT/externalTests/gnosis.sh "$SOLJSON" +$REPO_ROOT/externalTests/colony.sh "$SOLJSON" # Disabled temporarily as it needs to be updated to latest Truffle first. #test_truffle Gnosis https://github.com/axic/pm-contracts.git solidity-050 diff --git a/test/externalTests/colony.sh b/test/externalTests/colony.sh index 0e867c997..2f95f71a8 100755 --- a/test/externalTests/colony.sh +++ b/test/externalTests/colony.sh @@ -32,7 +32,7 @@ function colony_test { OPTIMIZER_LEVEL=3 FORCE_ABIv2=false - setup https://github.com/JoinColony/colonyNetwork.git develop master + truffle_setup https://github.com/JoinColony/colonyNetwork.git develop run_install install_fn CONFIG=$(find_truffle_config) @@ -42,7 +42,7 @@ function colony_test git clone https://github.com/erak/dappsys-monolithic.git -b callvalue-payable-fix dappsys cd .. - run_test compile_fn test_fn + truffle_run_test compile_fn test_fn } external_test ColonyNetworks colony_test diff --git a/test/externalTests/common.sh b/test/externalTests/common.sh index d6574da4d..6c427dddc 100644 --- a/test/externalTests/common.sh +++ b/test/externalTests/common.sh @@ -30,16 +30,40 @@ function verify_input fi } +function verify_version_input +{ + if [ -z "$1" ] || [ ! -f "$1" ] || [ -z "$2" ]; then + printError "Usage: $0 " + exit 1 + fi +} + +function setup +{ + local branch="$1" + + setup_solcjs "$DIR" "$SOLJSON" "$branch" "solc" + cd solc +} + function setup_solcjs { local dir="$1" local soljson="$2" + local branch="$3" + local path="$4" cd "$dir" printLog "Setting up solc-js..." - git clone --depth 1 -b v0.5.0 https://github.com/ethereum/solc-js.git solc + git clone --depth 1 -b "$branch" https://github.com/ethereum/solc-js.git "$path" + + cd "$path" + + # disable "prepublish" script which downloads the latest version + # (we will replace it anyway and it is often incorrectly cached + # on travis) + npm config set script.prepublish '' - cd solc npm install cp "$soljson" soljson.js SOLCVERSION=$(./solcjs --version) @@ -59,12 +83,12 @@ function download_project echo "Current commit hash: `git rev-parse HEAD`" } -function setup +function truffle_setup { local repo="$1" local branch="$2" - setup_solcjs "$DIR" "$SOLJSON" + setup_solcjs "$DIR" "$SOLJSON" "v0.5.0" "solc" download_project "$repo" "$branch" "$DIR" replace_version_pragmas @@ -187,6 +211,18 @@ function run_test local compile_fn="$1" local test_fn="$2" + printLog "Running compile function..." + $compile_fn + + printLog "Running test function..." + $test_fn +} + +function truffle_run_test +{ + local compile_fn="$1" + local test_fn="$2" + force_solc "$CONFIG" "$DIR" "$SOLJSON" printLog "Checking optimizer level..." diff --git a/test/externalTests/gnosis.sh b/test/externalTests/gnosis.sh index f86816fd0..ef5718b4e 100755 --- a/test/externalTests/gnosis.sh +++ b/test/externalTests/gnosis.sh @@ -31,13 +31,13 @@ function test_fn { npm test; } function gnosis_safe_test { OPTIMIZER_LEVEL=1 - setup https://github.com/gnosis/safe-contracts.git development + truffle_setup https://github.com/gnosis/safe-contracts.git development run_install install_fn CONFIG=$(find_truffle_config) replace_libsolc_call - run_test compile_fn test_fn + truffle_run_test compile_fn test_fn } external_test Gnosis-Safe gnosis_safe_test diff --git a/test/externalTests/solc-js/DAO/DAO.sol b/test/externalTests/solc-js/DAO/DAO.sol new file mode 100644 index 000000000..3aed551c6 --- /dev/null +++ b/test/externalTests/solc-js/DAO/DAO.sol @@ -0,0 +1,943 @@ +/* +This file is part of the DAO. + +The DAO is free software: you can redistribute it and/or modify +it under the terms of the GNU lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The DAO is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU lesser General Public License for more details. + +You should have received a copy of the GNU lesser General Public License +along with the DAO. If not, see . +*/ + + +/* +Standard smart contract for a Decentralized Autonomous Organization (DAO) +to automate organizational governance and decision-making. +*/ + +import "./TokenCreation.sol"; +import "./ManagedAccount.sol"; + +contract DAOInterface { + + // The amount of days for which people who try to participate in the + // creation by calling the fallback function will still get their ether back + uint constant creationGracePeriod = 40 days; + // The minimum debate period that a generic proposal can have + uint constant minProposalDebatePeriod = 2 weeks; + // The minimum debate period that a split proposal can have + uint constant minSplitDebatePeriod = 1 weeks; + // Period of days inside which it's possible to execute a DAO split + uint constant splitExecutionPeriod = 27 days; + // Period of time after which the minimum Quorum is halved + uint constant quorumHalvingPeriod = 25 weeks; + // Period after which a proposal is closed + // (used in the case `executeProposal` fails because it throws) + uint constant executeProposalPeriod = 10 days; + // Denotes the maximum proposal deposit that can be given. It is given as + // a fraction of total Ether spent plus balance of the DAO + uint constant maxDepositDivisor = 100; + + // Proposals to spend the DAO's ether or to choose a new Curator + Proposal[] public proposals; + // The quorum needed for each proposal is partially calculated by + // totalSupply / minQuorumDivisor + uint public minQuorumDivisor; + // The unix time of the last time quorum was reached on a proposal + uint public lastTimeMinQuorumMet; + + // Address of the curator + address payable public curator; + // The whitelist: List of addresses the DAO is allowed to send ether to + mapping (address => bool) public allowedRecipients; + + // Tracks the addresses that own Reward Tokens. Those addresses can only be + // DAOs that have split from the original DAO. Conceptually, Reward Tokens + // represent the proportion of the rewards that the DAO has the right to + // receive. These Reward Tokens are generated when the DAO spends ether. + mapping (address => uint) public rewardToken; + // Total supply of rewardToken + uint public totalRewardToken; + + // The account used to manage the rewards which are to be distributed to the + // DAO Token Holders of this DAO + ManagedAccount public rewardAccount; + + // The account used to manage the rewards which are to be distributed to + // any DAO that holds Reward Tokens + ManagedAccount public DAOrewardAccount; + + // Amount of rewards (in wei) already paid out to a certain DAO + mapping (address => uint) public DAOpaidOut; + + // Amount of rewards (in wei) already paid out to a certain address + mapping (address => uint) public paidOut; + // Map of addresses blocked during a vote (not allowed to transfer DAO + // tokens). The address points to the proposal ID. + mapping (address => uint) public blocked; + + // The minimum deposit (in wei) required to submit any proposal that is not + // requesting a new Curator (no deposit is required for splits) + uint public proposalDeposit; + + // the accumulated sum of all current proposal deposits + uint sumOfProposalDeposits; + + // Contract that is able to create a new DAO (with the same code as + // this one), used for splits + DAO_Creator public daoCreator; + + // A proposal with `newCurator == false` represents a transaction + // to be issued by this DAO + // A proposal with `newCurator == true` represents a DAO split + struct Proposal { + // The address where the `amount` will go to if the proposal is accepted + // or if `newCurator` is true, the proposed Curator of + // the new DAO). + address payable recipient; + // The amount to transfer to `recipient` if the proposal is accepted. + uint amount; + // A plain text description of the proposal + string description; + // A unix timestamp, denoting the end of the voting period + uint votingDeadline; + // True if the proposal's votes have yet to be counted, otherwise False + bool open; + // True if quorum has been reached, the votes have been counted, and + // the majority said yes + bool proposalPassed; + // A hash to check validity of a proposal + bytes32 proposalHash; + // Deposit in wei the creator added when submitting their proposal. It + // is taken from the msg.value of a newProposal call. + uint proposalDeposit; + // True if this proposal is to assign a new Curator + bool newCurator; + // Data needed for splitting the DAO + SplitData[] splitData; + // Number of Tokens in favor of the proposal + uint yea; + // Number of Tokens opposed to the proposal + uint nay; + // Simple mapping to check if a shareholder has voted for it + mapping (address => bool) votedYes; + // Simple mapping to check if a shareholder has voted against it + mapping (address => bool) votedNo; + // Address of the shareholder who created the proposal + address payable creator; + } + + // Used only in the case of a newCurator proposal. + struct SplitData { + // The balance of the current DAO minus the deposit at the time of split + uint splitBalance; + // The total amount of DAO Tokens in existence at the time of split. + uint totalSupply; + // Amount of Reward Tokens owned by the DAO at the time of split. + uint rewardToken; + // The new DAO contract created at the time of split. + DAO newDAO; + } + + /// @dev Constructor setting the Curator and the address + /// for the contract able to create another DAO as well as the parameters + /// for the DAO Token Creation + /// @param _curator The Curator + /// @param _daoCreator The contract able to (re)create this DAO + /// @param _proposalDeposit The deposit to be paid for a regular proposal + /// @param _minTokensToCreate Minimum required wei-equivalent tokens + /// to be created for a successful DAO Token Creation + /// @param _closingTime Date (in Unix time) of the end of the DAO Token Creation + /// @param _privateCreation If zero the DAO Token Creation is open to public, a + /// non-zero address means that the DAO Token Creation is only for the address + /// @param _tokenName The name that the DAO's token will have + /// @param _tokenSymbol The ticker symbol that this DAO token should have + /// @param _decimalPlaces The number of decimal places that the token is + /// counted from. + // This is the constructor: it can not be overloaded so it is commented out + // function DAO( + // address payable _curator, + // DAO_Creator _daoCreator, + // uint _proposalDeposit, + // uint _minTokensToCreate, + // uint _closingTime, + // address _privateCreation + // string _tokenName, + // string _tokenSymbol, + // uint8 _decimalPlaces + // ); + + /// @notice Create Token with `msg.sender` as the beneficiary + function () external payable; + + + /// @dev This function is used to send ether back + /// to the DAO, it can also be used to receive payments that should not be + /// counted as rewards (donations, grants, etc.) + /// @return Whether the DAO received the ether successfully + function receiveEther() public returns (bool); + + /// @notice `msg.sender` creates a proposal to send `_amount` Wei to + /// `_recipient` with the transaction data `_transactionData`. If + /// `_newCurator` is true, then this is a proposal that splits the + /// DAO and sets `_recipient` as the new DAO's Curator. + /// @param _recipient Address of the recipient of the proposed transaction + /// @param _amount Amount of wei to be sent with the proposed transaction + /// @param _description String describing the proposal + /// @param _transactionData Data of the proposed transaction + /// @param _debatingPeriod Time used for debating a proposal, at least 2 + /// weeks for a regular proposal, 10 days for new Curator proposal + /// @param _newCurator Bool defining whether this proposal is about + /// a new Curator or not + /// @return The proposal ID. Needed for voting on the proposal + function newProposal( + address payable _recipient, + uint _amount, + string memory _description, + bytes memory _transactionData, + uint _debatingPeriod, + bool _newCurator + ) public payable returns (uint _proposalID); + + /// @notice Check that the proposal with the ID `_proposalID` matches the + /// transaction which sends `_amount` with data `_transactionData` + /// to `_recipient` + /// @param _proposalID The proposal ID + /// @param _recipient The recipient of the proposed transaction + /// @param _amount The amount of wei to be sent in the proposed transaction + /// @param _transactionData The data of the proposed transaction + /// @return Whether the proposal ID matches the transaction data or not + function checkProposalCode( + uint _proposalID, + address payable _recipient, + uint _amount, + bytes memory _transactionData + ) public view returns (bool _codeChecksOut); + + /// @notice Vote on proposal `_proposalID` with `_supportsProposal` + /// @param _proposalID The proposal ID + /// @param _supportsProposal Yes/No - support of the proposal + /// @return The vote ID. + function vote( + uint _proposalID, + bool _supportsProposal + ) public returns (uint _voteID); + + /// @notice Checks whether proposal `_proposalID` with transaction data + /// `_transactionData` has been voted for or rejected, and executes the + /// transaction in the case it has been voted for. + /// @param _proposalID The proposal ID + /// @param _transactionData The data of the proposed transaction + /// @return Whether the proposed transaction has been executed or not + function executeProposal( + uint _proposalID, + bytes memory _transactionData + ) public returns (bool _success); + + /// @notice ATTENTION! I confirm to move my remaining ether to a new DAO + /// with `_newCurator` as the new Curator, as has been + /// proposed in proposal `_proposalID`. This will burn my tokens. This can + /// not be undone and will split the DAO into two DAO's, with two + /// different underlying tokens. + /// @param _proposalID The proposal ID + /// @param _newCurator The new Curator of the new DAO + /// @dev This function, when called for the first time for this proposal, + /// will create a new DAO and send the sender's portion of the remaining + /// ether and Reward Tokens to the new DAO. It will also burn the DAO Tokens + /// of the sender. + function splitDAO( + uint _proposalID, + address payable _newCurator + ) public returns (bool _success); + + /// @dev can only be called by the DAO itself through a proposal + /// updates the contract of the DAO by sending all ether and rewardTokens + /// to the new DAO. The new DAO needs to be approved by the Curator + /// @param _newContract the address of the new contract + function newContract(address payable _newContract) public; + + + /// @notice Add a new possible recipient `_recipient` to the whitelist so + /// that the DAO can send transactions to them (using proposals) + /// @param _recipient New recipient address + /// @dev Can only be called by the current Curator + /// @return Whether successful or not + function changeAllowedRecipients(address _recipient, bool _allowed) external returns (bool _success); + + + /// @notice Change the minimum deposit required to submit a proposal + /// @param _proposalDeposit The new proposal deposit + /// @dev Can only be called by this DAO (through proposals with the + /// recipient being this DAO itself) + function changeProposalDeposit(uint _proposalDeposit) external; + + /// @notice Move rewards from the DAORewards managed account + /// @param _toMembers If true rewards are moved to the actual reward account + /// for the DAO. If not then it's moved to the DAO itself + /// @return Whether the call was successful + function retrieveDAOReward(bool _toMembers) external returns (bool _success); + + /// @notice Get my portion of the reward that was sent to `rewardAccount` + /// @return Whether the call was successful + function getMyReward() public returns (bool _success); + + /// @notice Withdraw `_account`'s portion of the reward from `rewardAccount` + /// to `_account`'s balance + /// @return Whether the call was successful + function withdrawRewardFor(address payable _account) internal returns (bool _success); + + /// @notice Send `_amount` tokens to `_to` from `msg.sender`. Prior to this + /// getMyReward() is called. + /// @param _to The address of the recipient + /// @param _amount The amount of tokens to be transfered + /// @return Whether the transfer was successful or not + function transferWithoutReward(address _to, uint256 _amount) public returns (bool success); + + /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it + /// is approved by `_from`. Prior to this getMyReward() is called. + /// @param _from The address of the sender + /// @param _to The address of the recipient + /// @param _amount The amount of tokens to be transfered + /// @return Whether the transfer was successful or not + function transferFromWithoutReward( + address payable _from, + address _to, + uint256 _amount + ) public returns (bool success); + + /// @notice Doubles the 'minQuorumDivisor' in the case quorum has not been + /// achieved in 52 weeks + /// @return Whether the change was successful or not + function halveMinQuorum() public returns (bool _success); + + /// @return total number of proposals ever created + function numberOfProposals() public view returns (uint _numberOfProposals); + + /// @param _proposalID Id of the new curator proposal + /// @return Address of the new DAO + function getNewDAOAddress(uint _proposalID) public view returns (address _newDAO); + + /// @param _account The address of the account which is checked. + /// @return Whether the account is blocked (not allowed to transfer tokens) or not. + function isBlocked(address _account) internal returns (bool); + + /// @notice If the caller is blocked by a proposal whose voting deadline + /// has exprired then unblock him. + /// @return Whether the account is blocked (not allowed to transfer tokens) or not. + function unblockMe() public returns (bool); + + event ProposalAdded( + uint indexed proposalID, + address recipient, + uint amount, + bool newCurator, + string description + ); + event Voted(uint indexed proposalID, bool position, address indexed voter); + event ProposalTallied(uint indexed proposalID, bool result, uint quorum); + event NewCurator(address indexed _newCurator); + event AllowedRecipientChanged(address indexed _recipient, bool _allowed); +} + +// The DAO contract itself +contract DAO is DAOInterface, Token, TokenCreation { + + // Modifier that allows only shareholders to vote and create new proposals + modifier onlyTokenholders { + if (balanceOf(msg.sender) == 0) revert(); + _; + } + + constructor( + address payable _curator, + DAO_Creator _daoCreator, + uint _proposalDeposit, + uint _minTokensToCreate, + uint _closingTime, + address _privateCreation, + string memory _tokenName, + string memory _tokenSymbol, + uint8 _decimalPlaces + ) TokenCreation( + _minTokensToCreate, + _closingTime, + _privateCreation, + _tokenName, + _tokenSymbol, + _decimalPlaces) public { + + curator = _curator; + daoCreator = _daoCreator; + proposalDeposit = _proposalDeposit; + rewardAccount = new ManagedAccount(address(this), false); + DAOrewardAccount = new ManagedAccount(address(this), false); + if (address(rewardAccount) == 0x0000000000000000000000000000000000000000) + revert(); + if (address(DAOrewardAccount) == 0x0000000000000000000000000000000000000000) + revert(); + lastTimeMinQuorumMet = now; + minQuorumDivisor = 5; // sets the minimal quorum to 20% + proposals.length = 1; // avoids a proposal with ID 0 because it is used + + allowedRecipients[address(this)] = true; + allowedRecipients[curator] = true; + } + + function () external payable { + if (now < closingTime + creationGracePeriod && msg.sender != address(extraBalance)) + createTokenProxy(msg.sender); + else + receiveEther(); + } + + + function receiveEther() public returns (bool) { + return true; + } + + + function newProposal( + address payable _recipient, + uint _amount, + string memory _description, + bytes memory _transactionData, + uint _debatingPeriod, + bool _newCurator + ) onlyTokenholders public payable returns (uint _proposalID) { + + // Sanity check + if (_newCurator && ( + _amount != 0 + || _transactionData.length != 0 + || _recipient == curator + || msg.value > 0 + || _debatingPeriod < minSplitDebatePeriod)) { + revert(); + } else if ( + !_newCurator + && (!isRecipientAllowed(_recipient) || (_debatingPeriod < minProposalDebatePeriod)) + ) { + revert(); + } + + if (_debatingPeriod > 8 weeks) + revert(); + + if (!isFueled + || now < closingTime + || (msg.value < proposalDeposit && !_newCurator)) { + + revert(); + } + + if (now + _debatingPeriod < now) // prevents overflow + revert(); + + // to prevent a 51% attacker to convert the ether into deposit + if (msg.sender == address(this)) + revert(); + + // to prevent curator from halving quorum before first proposal + if (proposals.length == 1) // initial length is 1 (see constructor) + lastTimeMinQuorumMet = now; + + _proposalID = proposals.length++; + Proposal storage p = proposals[_proposalID]; + p.recipient = _recipient; + p.amount = _amount; + p.description = _description; + p.proposalHash = keccak256(abi.encodePacked(_recipient, _amount, _transactionData)); + p.votingDeadline = now + _debatingPeriod; + p.open = true; + //p.proposalPassed = False; // that's default + p.newCurator = _newCurator; + if (_newCurator) + p.splitData.length++; + p.creator = msg.sender; + p.proposalDeposit = msg.value; + + sumOfProposalDeposits += msg.value; + + emit ProposalAdded( + _proposalID, + _recipient, + _amount, + _newCurator, + _description + ); + } + + + function checkProposalCode( + uint _proposalID, + address payable _recipient, + uint _amount, + bytes memory _transactionData + ) public view returns (bool _codeChecksOut) { + Proposal storage p = proposals[_proposalID]; + return p.proposalHash == keccak256(abi.encodePacked(_recipient, _amount, _transactionData)); + } + + + function vote( + uint _proposalID, + bool _supportsProposal + ) onlyTokenholders public returns (uint _voteID) { + + Proposal storage p = proposals[_proposalID]; + if (p.votedYes[msg.sender] + || p.votedNo[msg.sender] + || now >= p.votingDeadline) { + + revert(); + } + + if (_supportsProposal) { + p.yea += balances[msg.sender]; + p.votedYes[msg.sender] = true; + } else { + p.nay += balances[msg.sender]; + p.votedNo[msg.sender] = true; + } + + if (blocked[msg.sender] == 0) { + blocked[msg.sender] = _proposalID; + } else if (p.votingDeadline > proposals[blocked[msg.sender]].votingDeadline) { + // this proposal's voting deadline is further into the future than + // the proposal that blocks the sender so make it the blocker + blocked[msg.sender] = _proposalID; + } + + emit Voted(_proposalID, _supportsProposal, msg.sender); + } + + + function executeProposal( + uint _proposalID, + bytes memory _transactionData + ) public returns (bool _success) { + + Proposal storage p = proposals[_proposalID]; + + uint waitPeriod = p.newCurator + ? splitExecutionPeriod + : executeProposalPeriod; + // If we are over deadline and waiting period, assert proposal is closed + if (p.open && now > p.votingDeadline + waitPeriod) { + closeProposal(_proposalID); + return false; + } + + // Check if the proposal can be executed + if (now < p.votingDeadline // has the voting deadline arrived? + // Have the votes been counted? + || !p.open + || p.proposalPassed // anyone trying to call us recursively? + // Does the transaction code match the proposal? + || p.proposalHash != keccak256(abi.encodePacked(p.recipient, p.amount, _transactionData))) { + + revert(); + } + + // If the curator removed the recipient from the whitelist, close the proposal + // in order to free the deposit and allow unblocking of voters + if (!isRecipientAllowed(p.recipient)) { + closeProposal(_proposalID); + p.creator.send(p.proposalDeposit); + return false; + } + + bool proposalCheck = true; + + if (p.amount > actualBalance()) + proposalCheck = false; + + uint quorum = p.yea + p.nay; + + // require 53% for calling newContract() + if (_transactionData.length >= 4 && _transactionData[0] == 0x68 + && _transactionData[1] == 0x37 && _transactionData[2] == 0xff + && _transactionData[3] == 0x1e + && quorum < minQuorum(actualBalance() + rewardToken[address(this)])) { + + proposalCheck = false; + } + + if (quorum >= minQuorum(p.amount)) { + if (!p.creator.send(p.proposalDeposit)) + revert(); + + lastTimeMinQuorumMet = now; + // set the minQuorum to 20% again, in the case it has been reached + if (quorum > totalSupply / 5) + minQuorumDivisor = 5; + } + + // Execute result + if (quorum >= minQuorum(p.amount) && p.yea > p.nay && proposalCheck) { + // we are setting this here before the CALL() value transfer to + // assure that in the case of a malicious recipient contract trying + // to call executeProposal() recursively money can't be transferred + // multiple times out of the DAO + p.proposalPassed = true; + + (bool success,) = p.recipient.call.value(p.amount)(_transactionData); + if (!success) + revert(); + + _success = true; + + // only create reward tokens when ether is not sent to the DAO itself and + // related addresses. Proxy addresses should be forbidden by the curator. + if (p.recipient != address(this) && p.recipient != address(rewardAccount) + && p.recipient != address(DAOrewardAccount) + && p.recipient != address(extraBalance) + && p.recipient != address(curator)) { + + rewardToken[address(this)] += p.amount; + totalRewardToken += p.amount; + } + } + + closeProposal(_proposalID); + + // Initiate event + emit ProposalTallied(_proposalID, _success, quorum); + } + + + function closeProposal(uint _proposalID) internal { + Proposal storage p = proposals[_proposalID]; + if (p.open) + sumOfProposalDeposits -= p.proposalDeposit; + p.open = false; + } + + function splitDAO( + uint _proposalID, + address payable _newCurator + ) onlyTokenholders public returns (bool _success) { + + Proposal storage p = proposals[_proposalID]; + + // Sanity check + + if (now < p.votingDeadline // has the voting deadline arrived? + //The request for a split expires XX days after the voting deadline + || now > p.votingDeadline + splitExecutionPeriod + // Does the new Curator address match? + || p.recipient != _newCurator + // Is it a new curator proposal? + || !p.newCurator + // Have you voted for this split? + || !p.votedYes[msg.sender] + // Did you already vote on another proposal? + || (blocked[msg.sender] != _proposalID && blocked[msg.sender] != 0) ) { + + revert(); + } + + // If the new DAO doesn't exist yet, create the new DAO and store the + // current split data + if (address(p.splitData[0].newDAO) == 0x0000000000000000000000000000000000000000) { + p.splitData[0].newDAO = createNewDAO(_newCurator); + // Call depth limit reached, etc. + if (address(p.splitData[0].newDAO) == 0x0000000000000000000000000000000000000000) + revert(); + // should never happen + if (address(this).balance < sumOfProposalDeposits) + revert(); + p.splitData[0].splitBalance = actualBalance(); + p.splitData[0].rewardToken = rewardToken[address(this)]; + p.splitData[0].totalSupply = totalSupply; + p.proposalPassed = true; + } + + // Move ether and assign new Tokens + uint fundsToBeMoved = + (balances[msg.sender] * p.splitData[0].splitBalance) / + p.splitData[0].totalSupply; + if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false) + revert(); + + + // Assign reward rights to new DAO + uint rewardTokenToBeMoved = + (balances[msg.sender] * p.splitData[0].rewardToken) / + p.splitData[0].totalSupply; + + uint paidOutToBeMoved = DAOpaidOut[address(this)] * rewardTokenToBeMoved / + rewardToken[address(this)]; + + rewardToken[address(p.splitData[0].newDAO)] += rewardTokenToBeMoved; + if (rewardToken[address(this)] < rewardTokenToBeMoved) + revert(); + rewardToken[address(this)] -= rewardTokenToBeMoved; + + DAOpaidOut[address(p.splitData[0].newDAO)] += paidOutToBeMoved; + if (DAOpaidOut[address(this)] < paidOutToBeMoved) + revert(); + DAOpaidOut[address(this)] -= paidOutToBeMoved; + + // Burn DAO Tokens + emit Transfer(msg.sender, 0x0000000000000000000000000000000000000000, balances[msg.sender]); + withdrawRewardFor(msg.sender); // be nice, and get his rewards + totalSupply -= balances[msg.sender]; + balances[msg.sender] = 0; + paidOut[msg.sender] = 0; + return true; + } + + function newContract(address payable _newContract) public { + if (msg.sender != address(this) || !allowedRecipients[_newContract]) return; + // move all ether + (bool success,) = _newContract.call.value(address(this).balance)(""); + if (!success) { + revert(); + } + + //move all reward tokens + rewardToken[_newContract] += rewardToken[address(this)]; + rewardToken[address(this)] = 0; + DAOpaidOut[_newContract] += DAOpaidOut[address(this)]; + DAOpaidOut[address(this)] = 0; + } + + + function retrieveDAOReward(bool _toMembers) external returns (bool _success) { + DAO dao = DAO(msg.sender); + + if ((rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) / + totalRewardToken < DAOpaidOut[msg.sender]) + revert(); + + uint reward = + (rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) / + totalRewardToken - DAOpaidOut[msg.sender]; + + reward = address(DAOrewardAccount).balance < reward ? address(DAOrewardAccount).balance : reward; + + if(_toMembers) { + if (!DAOrewardAccount.payOut(address(dao.rewardAccount()), reward)) + revert(); + } + else { + if (!DAOrewardAccount.payOut(address(dao), reward)) + revert(); + } + DAOpaidOut[msg.sender] += reward; + return true; + } + + function getMyReward() public returns (bool _success) { + return withdrawRewardFor(msg.sender); + } + + + function withdrawRewardFor(address payable _account) noEther internal returns (bool _success) { + if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply < paidOut[_account]) + revert(); + + uint reward = + (balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply - paidOut[_account]; + + reward = address(rewardAccount).balance < reward ? address(rewardAccount).balance : reward; + + if (!rewardAccount.payOut(_account, reward)) + revert(); + paidOut[_account] += reward; + return true; + } + + + function transfer(address _to, uint256 _value) public returns (bool success) { + if (isFueled + && now > closingTime + && !isBlocked(msg.sender) + && _to != address(this) + && transferPaidOut(msg.sender, _to, _value) + && super.transfer(_to, _value)) { + + return true; + } else { + revert(); + } + } + + + function transferWithoutReward(address _to, uint256 _value) public returns (bool success) { + if (!getMyReward()) + revert(); + return transfer(_to, _value); + } + + + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { + if (isFueled + && now > closingTime + && !isBlocked(_from) + && _to != address(this) + && transferPaidOut(_from, _to, _value) + && super.transferFrom(_from, _to, _value)) { + + return true; + } else { + revert(); + } + } + + + function transferFromWithoutReward( + address payable _from, + address _to, + uint256 _value + ) public returns (bool success) { + + if (!withdrawRewardFor(_from)) + revert(); + return transferFrom(_from, _to, _value); + } + + + function transferPaidOut( + address _from, + address _to, + uint256 _value + ) internal returns (bool success) { + + uint transferPaidOut = paidOut[_from] * _value / balanceOf(_from); + if (transferPaidOut > paidOut[_from]) + revert(); + paidOut[_from] -= transferPaidOut; + paidOut[_to] += transferPaidOut; + return true; + } + + + function changeProposalDeposit(uint _proposalDeposit) external { + if (msg.sender != address(this) || _proposalDeposit > (actualBalance() + rewardToken[address(this)]) + / maxDepositDivisor) { + + revert(); + } + proposalDeposit = _proposalDeposit; + } + + + function changeAllowedRecipients(address _recipient, bool _allowed) external returns (bool _success) { + if (msg.sender != curator) + revert(); + allowedRecipients[_recipient] = _allowed; + emit AllowedRecipientChanged(_recipient, _allowed); + return true; + } + + + function isRecipientAllowed(address _recipient) internal returns (bool _isAllowed) { + if (allowedRecipients[_recipient] + || (_recipient == address(extraBalance) + // only allowed when at least the amount held in the + // extraBalance account has been spent from the DAO + && totalRewardToken > extraBalance.accumulatedInput())) + return true; + else + return false; + } + + function actualBalance() public view returns (uint _actualBalance) { + return address(this).balance - sumOfProposalDeposits; + } + + + function minQuorum(uint _value) internal view returns (uint _minQuorum) { + // minimum of 20% and maximum of 53.33% + return totalSupply / minQuorumDivisor + + (_value * totalSupply) / (3 * (actualBalance() + rewardToken[address(this)])); + } + + + function halveMinQuorum() public returns (bool _success) { + // this can only be called after `quorumHalvingPeriod` has passed or at anytime after + // fueling by the curator with a delay of at least `minProposalDebatePeriod` + // between the calls + if ((lastTimeMinQuorumMet < (now - quorumHalvingPeriod) || msg.sender == curator) + && lastTimeMinQuorumMet < (now - minProposalDebatePeriod) + && now >= closingTime + && proposals.length > 1) { + lastTimeMinQuorumMet = now; + minQuorumDivisor *= 2; + return true; + } else { + return false; + } + } + + function createNewDAO(address payable _newCurator) internal returns (DAO _newDAO) { + emit NewCurator(_newCurator); + return daoCreator.createDAO( + _newCurator, + 0, + 0, + now + splitExecutionPeriod, + name, + symbol, + decimals + ); + } + + function numberOfProposals() public view returns (uint _numberOfProposals) { + // Don't count index 0. It's used by isBlocked() and exists from start + return proposals.length - 1; + } + + function getNewDAOAddress(uint _proposalID) public view returns (address _newDAO) { + return address(proposals[_proposalID].splitData[0].newDAO); + } + + function isBlocked(address _account) internal returns (bool) { + if (blocked[_account] == 0) + return false; + Proposal storage p = proposals[blocked[_account]]; + if (now > p.votingDeadline) { + blocked[_account] = 0; + return false; + } else { + return true; + } + } + + function unblockMe() public returns (bool) { + return isBlocked(msg.sender); + } +} + +contract DAO_Creator { + function createDAO( + address payable _curator, + uint _proposalDeposit, + uint _minTokensToCreate, + uint _closingTime, + string memory _tokenName, + string memory _tokenSymbol, + uint8 _decimalPlaces + ) public returns (DAO _newDAO) { + + return new DAO( + _curator, + DAO_Creator(this), + _proposalDeposit, + _minTokensToCreate, + _closingTime, + msg.sender, + _tokenName, + _tokenSymbol, + _decimalPlaces + ); + } +} diff --git a/test/externalTests/solc-js/DAO/ManagedAccount.sol b/test/externalTests/solc-js/DAO/ManagedAccount.sol new file mode 100644 index 000000000..5edb32696 --- /dev/null +++ b/test/externalTests/solc-js/DAO/ManagedAccount.sol @@ -0,0 +1,68 @@ +/* +This file is part of the DAO. + +The DAO is free software: you can redistribute it and/or modify +it under the terms of the GNU lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The DAO is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU lesser General Public License for more details. + +You should have received a copy of the GNU lesser General Public License +along with the DAO. If not, see . +*/ + + +/* +Basic account, used by the DAO contract to separately manage both the rewards +and the extraBalance accounts. +*/ + +contract ManagedAccountInterface { + // The only address with permission to withdraw from this account + address public owner; + // If true, only the owner of the account can receive ether from it + bool public payOwnerOnly; + // The sum of ether (in wei) which has been sent to this contract + uint public accumulatedInput; + + /// @notice Sends `_amount` of wei to _recipient + /// @param _amount The amount of wei to send to `_recipient` + /// @param _recipient The address to receive `_amount` of wei + /// @return True if the send completed + function payOut(address payable _recipient, uint _amount) public returns (bool); + + event PayOut(address indexed _recipient, uint _amount); +} + + +contract ManagedAccount is ManagedAccountInterface{ + + // The constructor sets the owner of the account + constructor(address _owner, bool _payOwnerOnly) public { + owner = _owner; + payOwnerOnly = _payOwnerOnly; + } + + // When the contract receives a transaction without data this is called. + // It counts the amount of ether it receives and stores it in + // accumulatedInput. + function() external payable { + accumulatedInput += msg.value; + } + + function payOut(address payable _recipient, uint _amount) public returns (bool) { + if (msg.sender != owner || (payOwnerOnly && _recipient != owner)) + revert(); + (bool success,) = _recipient.call.value(_amount)(""); + if (success) { + emit PayOut(_recipient, _amount); + return true; + } else { + return false; + } + } +} diff --git a/test/externalTests/solc-js/DAO/Token.sol b/test/externalTests/solc-js/DAO/Token.sol new file mode 100644 index 000000000..ee38f29d7 --- /dev/null +++ b/test/externalTests/solc-js/DAO/Token.sol @@ -0,0 +1,150 @@ +/* +This file is part of the DAO. + +The DAO is free software: you can redistribute it and/or modify +it under the terms of the GNU lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The DAO is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU lesser General Public License for more details. + +You should have received a copy of the GNU lesser General Public License +along with the DAO. If not, see . +*/ + + +/* +Basic, standardized Token contract with no "premine". Defines the functions to +check token balances, send tokens, send tokens on behalf of a 3rd party and the +corresponding approval process. Tokens need to be created by a derived +contract (e.g. TokenCreation.sol). + +Thank you ConsenSys, this contract originated from: +https://github.com/ConsenSys/Tokens/blob/master/Token_Contracts/contracts/Standard_Token.sol +Which is itself based on the Ethereum standardized contract APIs: +https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs +*/ + +/// @title Standard Token Contract. + +contract TokenInterface { + mapping (address => uint256) balances; + mapping (address => mapping (address => uint256)) allowed; + + /// Public variables of the token, all used for display + string public name; + string public symbol; + uint8 public decimals; + string public standard = 'Token 0.1'; + + /// Total amount of tokens + uint256 public totalSupply; + + /// @param _owner The address from which the balance will be retrieved + /// @return The balance + function balanceOf(address _owner) public view returns (uint256 balance); + + /// @notice Send `_amount` tokens to `_to` from `msg.sender` + /// @param _to The address of the recipient + /// @param _amount The amount of tokens to be transferred + /// @return Whether the transfer was successful or not + function transfer(address _to, uint256 _amount) public returns (bool success); + + /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it + /// is approved by `_from` + /// @param _from The address of the origin of the transfer + /// @param _to The address of the recipient + /// @param _amount The amount of tokens to be transferred + /// @return Whether the transfer was successful or not + function transferFrom(address _from, address _to, uint256 _amount) public returns (bool success); + + /// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on + /// its behalf + /// @param _spender The address of the account able to transfer the tokens + /// @param _amount The amount of tokens to be approved for transfer + /// @return Whether the approval was successful or not + function approve(address _spender, uint256 _amount) public returns (bool success); + + /// @param _owner The address of the account owning tokens + /// @param _spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens of _owner that _spender is allowed + /// to spend + function allowance( + address _owner, + address _spender + ) public view returns (uint256 remaining); + + event Transfer(address indexed _from, address indexed _to, uint256 _amount); + event Approval( + address indexed _owner, + address indexed _spender, + uint256 _amount + ); +} + +contract tokenRecipient { + function receiveApproval(address _from, uint256 _value, address _token, bytes memory _extraData) public; +} + +contract Token is TokenInterface { + // Protects users by preventing the execution of method calls that + // inadvertently also transferred ether + modifier noEther() {if (msg.value > 0) revert(); _; } + + function balanceOf(address _owner) public view returns (uint256 balance) { + return balances[_owner]; + } + + function transfer(address _to, uint256 _amount) public returns (bool success) { + if (balances[msg.sender] >= _amount && _amount > 0) { + balances[msg.sender] -= _amount; + balances[_to] += _amount; + emit Transfer(msg.sender, _to, _amount); + return true; + } else { + return false; + } + } + + function transferFrom( + address _from, + address _to, + uint256 _amount + ) public returns (bool success) { + + if (balances[_from] >= _amount + && allowed[_from][msg.sender] >= _amount + && _amount > 0) { + + balances[_to] += _amount; + balances[_from] -= _amount; + allowed[_from][msg.sender] -= _amount; + emit Transfer(_from, _to, _amount); + return true; + } else { + return false; + } + } + + function approve(address _spender, uint256 _amount) public returns (bool success) { + allowed[msg.sender][_spender] = _amount; + emit Approval(msg.sender, _spender, _amount); + return true; + } + + /// Allow another contract to spend some tokens in your behalf + function approveAndCall(address _spender, uint256 _value, bytes memory _extraData) + public returns (bool success) { + allowed[msg.sender][_spender] = _value; + tokenRecipient spender = tokenRecipient(_spender); + spender.receiveApproval(msg.sender, _value, address(this), _extraData); + return true; + } + + function allowance(address _owner, address _spender) public view returns (uint256 remaining) { + return allowed[_owner][_spender]; + } +} diff --git a/test/externalTests/solc-js/DAO/TokenCreation.sol b/test/externalTests/solc-js/DAO/TokenCreation.sol new file mode 100644 index 000000000..1d22ebdf6 --- /dev/null +++ b/test/externalTests/solc-js/DAO/TokenCreation.sol @@ -0,0 +1,155 @@ +/* +This file is part of the DAO. + +The DAO is free software: you can redistribute it and/or modify +it under the terms of the GNU lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The DAO is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU lesser General Public License for more details. + +You should have received a copy of the GNU lesser General Public License +along with the DAO. If not, see . +*/ + + +/* + * Token Creation contract, used by the DAO to create its tokens and initialize + * its ether. Feel free to modify the divisor method to implement different + * Token Creation parameters +*/ + +import "./Token.sol"; +import "./ManagedAccount.sol"; + +contract TokenCreationInterface { + + // End of token creation, in Unix time + uint public closingTime; + // Minimum fueling goal of the token creation, denominated in tokens to + // be created + uint public minTokensToCreate; + // True if the DAO reached its minimum fueling goal, false otherwise + bool public isFueled; + // For DAO splits - if privateCreation is 0, then it is a public token + // creation, otherwise only the address stored in privateCreation is + // allowed to create tokens + address public privateCreation; + // hold extra ether which has been sent after the DAO token + // creation rate has increased + ManagedAccount public extraBalance; + // tracks the amount of wei given from each contributor (used for refund) + mapping (address => uint256) weiGiven; + + /// @dev Constructor setting the minimum fueling goal and the + /// end of the Token Creation + /// @param _minTokensToCreate Minimum fueling goal in number of + /// Tokens to be created + /// @param _closingTime Date (in Unix time) of the end of the Token Creation + /// @param _privateCreation Zero means that the creation is public. A + /// non-zero address represents the only address that can create Tokens + /// (the address can also create Tokens on behalf of other accounts) + // This is the constructor: it can not be overloaded so it is commented out + // function TokenCreation( + // uint _minTokensTocreate, + // uint _closingTime, + // address _privateCreation + // string _tokenName, + // string _tokenSymbol, + // uint _decimalPlaces + // ); + + /// @notice Create Token with `_tokenHolder` as the initial owner of the Token + /// @param _tokenHolder The address of the Tokens's recipient + /// @return Whether the token creation was successful + function createTokenProxy(address payable _tokenHolder) payable public returns (bool success); + + /// @notice Refund `msg.sender` in the case the Token Creation did + /// not reach its minimum fueling goal + function refund() public; + + /// @return The divisor used to calculate the token creation rate during + /// the creation phase + function divisor() public view returns (uint divisor); + + event FuelingToDate(uint value); + event CreatedToken(address indexed to, uint amount); + event Refund(address indexed to, uint value); +} + + +contract TokenCreation is TokenCreationInterface, Token { + constructor( + uint _minTokensToCreate, + uint _closingTime, + address _privateCreation, + string memory _tokenName, + string memory _tokenSymbol, + uint8 _decimalPlaces) public { + + closingTime = _closingTime; + minTokensToCreate = _minTokensToCreate; + privateCreation = _privateCreation; + extraBalance = new ManagedAccount(address(this), true); + name = _tokenName; + symbol = _tokenSymbol; + decimals = _decimalPlaces; + + } + + function createTokenProxy(address payable _tokenHolder) payable public returns (bool success) { + if (now < closingTime && msg.value > 0 + && (privateCreation == 0x0000000000000000000000000000000000000000 || privateCreation == msg.sender)) { + + uint token = (msg.value * 20) / divisor(); + address(extraBalance).call.value(msg.value - token)(""); + balances[_tokenHolder] += token; + totalSupply += token; + weiGiven[_tokenHolder] += msg.value; + emit CreatedToken(_tokenHolder, token); + if (totalSupply >= minTokensToCreate && !isFueled) { + isFueled = true; + emit FuelingToDate(totalSupply); + } + return true; + } + revert(); + } + + function refund() public { + if (now > closingTime && !isFueled) { + // Get extraBalance - will only succeed when called for the first time + if (address(extraBalance).balance >= extraBalance.accumulatedInput()) + extraBalance.payOut(address(this), extraBalance.accumulatedInput()); + + // Execute refund + (bool success,) = msg.sender.call.value(weiGiven[msg.sender])(""); + if (success) { + emit Refund(msg.sender, weiGiven[msg.sender]); + totalSupply -= balances[msg.sender]; + balances[msg.sender] = 0; + weiGiven[msg.sender] = 0; + } + } + } + + function divisor() public view returns (uint divisor) { + // The number of (base unit) tokens per wei is calculated + // as `msg.value` * 20 / `divisor` + // The fueling period starts with a 1:1 ratio + if (closingTime - 2 weeks > now) { + return 20; + // Followed by 10 days with a daily creation rate increase of 5% + } else if (closingTime - 4 days > now) { + return (20 + (now - (closingTime - 2 weeks)) / (1 days)); + // The last 4 days there is a constant creation rate ratio of 1:1.5 + } else { + return 30; + } + } + function() external payable { + } +} diff --git a/test/externalTests/solc-js/determinism.js b/test/externalTests/solc-js/determinism.js new file mode 100644 index 000000000..cd95d76ce --- /dev/null +++ b/test/externalTests/solc-js/determinism.js @@ -0,0 +1,45 @@ +const tape = require('tape'); +const fs = require('fs'); +const solc = require('../index.js'); + +tape('Deterministic Compilation', function (t) { + t.test('DAO', function (st) { + var input = {}; + var prevBytecode = null; + var testdir = 'test/DAO/'; + var files = ['DAO.sol', 'Token.sol', 'TokenCreation.sol', 'ManagedAccount.sol']; + var i; + for (i in files) { + var file = files[i]; + input[file] = { content: fs.readFileSync(testdir + file, 'utf8') }; + } + for (i = 0; i < 10; i++) { + var output = JSON.parse(solc.compileStandardWrapper(JSON.stringify({ + language: 'Solidity', + settings: { + optimizer: { + enabled: true + }, + outputSelection: { + '*': { + '*': [ 'evm.bytecode' ] + } + } + }, + sources: input + }))); + st.ok(output); + st.ok(output.contracts); + st.ok(output.contracts['DAO.sol']); + st.ok(output.contracts['DAO.sol']['DAO']); + st.ok(output.contracts['DAO.sol']['DAO'].evm.bytecode.object); + var bytecode = output.contracts['DAO.sol']['DAO'].evm.bytecode.object; + st.ok(bytecode.length > 0); + if (prevBytecode !== null) { + st.equal(prevBytecode, bytecode); + } + prevBytecode = bytecode; + } + st.end(); + }); +}); diff --git a/test/solcjsTests.sh b/test/externalTests/solc-js/solc-js.sh similarity index 53% rename from test/solcjsTests.sh rename to test/externalTests/solc-js/solc-js.sh index 7c263d51f..4267ba11b 100755 --- a/test/solcjsTests.sh +++ b/test/externalTests/solc-js/solc-js.sh @@ -1,12 +1,5 @@ #!/usr/bin/env bash -#------------------------------------------------------------------------------ -# Bash script to execute the Solidity tests. -# -# The documentation for solidity is hosted at: -# -# https://solidity.readthedocs.org -# # ------------------------------------------------------------------------------ # This file is part of solidity. # @@ -23,41 +16,43 @@ # You should have received a copy of the GNU General Public License # along with solidity. If not, see # -# (c) 2017 solidity contributors. +# (c) 2019 solidity contributors. #------------------------------------------------------------------------------ +source scripts/common.sh +source test/externalTests/common.sh -set -e - -if [ ! -f "$1" -o -z "$2" ] -then - echo "Usage: $0 " - exit 1 -fi - +verify_version_input "$1" "$2" SOLJSON="$1" VERSION="$2" -DIR=$(mktemp -d) -( - echo "Preparing solc-js (master_060)..." - git clone --depth 1 --branch master_060 https://github.com/ethereum/solc-js "$DIR" - cd "$DIR" - # disable "prepublish" script which downloads the latest version - # (we will replace it anyway and it is often incorrectly cached - # on travis) - npm config set script.prepublish '' - npm install +function install_fn { echo "Nothing to install."; } +function compile_fn { echo "Nothing to compile."; } +function test_fn { npm test; } - # Replace soljson with current build - echo "Replacing soljson.js" - rm -f soljson.js - cp "$SOLJSON" soljson.js +function solcjs_test +{ + TEST_DIR=$(pwd) + SOLCJS_INPUT_DIR="$TEST_DIR"/test/externalTests/solc-js + + # set up solc-js on the branch specified + setup master_060 + + printLog "Updating index.js file..." + echo "require('./determinism.js');" >> test/index.js + + printLog "Copying determinism.js..." + cp -f $SOLCJS_INPUT_DIR/determinism.js test/ + + printLog "Copying contracts..." + cp -Rf $SOLCJS_INPUT_DIR/DAO test/ + + run_install install_fn # Update version (needed for some tests) echo "Updating package.json to version $VERSION" npm version --allow-same-version --no-git-tag-version $VERSION - echo "Running solc-js tests..." - npm run test -) -rm -rf "$DIR" + run_test compile_fn test_fn +} + +external_test solc-js solcjs_test diff --git a/test/externalTests/zeppelin.sh b/test/externalTests/zeppelin.sh index b24617f37..e18fef976 100755 --- a/test/externalTests/zeppelin.sh +++ b/test/externalTests/zeppelin.sh @@ -31,13 +31,13 @@ function test_fn { npm run test; } function zeppelin_test { OPTIMIZER_LEVEL=1 - setup https://github.com/OpenZeppelin/openzeppelin-solidity.git master + truffle_setup https://github.com/OpenZeppelin/openzeppelin-solidity.git master run_install install_fn CONFIG="truffle-config.js" replace_libsolc_call - run_test compile_fn test_fn + truffle_run_test compile_fn test_fn } external_test Zeppelin zeppelin_test