diff --git a/ci/makeMockCompiler.js b/ci/makeMockCompiler.js index 137c20a331..fbcf5f433c 100644 --- a/ci/makeMockCompiler.js +++ b/ci/makeMockCompiler.js @@ -3,7 +3,7 @@ var fs = require('fs') var compiler = require('solc') var compilerInput = require('remix-solidity').CompilerInput -var defaultVersion = 'v0.5.14+commit.1f1aaa4' +var defaultVersion = 'v0.6.0+commit.26b70077' compiler.loadRemoteVersion(defaultVersion, (error, solcSnapshot) => { if (error) console.log(error) diff --git a/package.json b/package.json index b1cc9d5e56..4c6cbc0c20 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "build_debugger": "browserify src/app/debugger/remix-debugger/index.js -o src/app/debugger/remix-debugger/build/app.js", "browsertest": "sleep 5 && npm run nightwatch_local", "csslint": "csslint --ignore=order-alphabetical --errors='errors,duplicate-properties,empty-rules' --exclude-list='assets/css/font-awesome.min.css' assets/css/", - "downloadsolc_root": "wget --no-check-certificate https://solc-bin.ethereum.org/bin/soljson-v0.5.14+commit.1f1aaa4.js -O soljson.js", + "downloadsolc_root": "wget --no-check-certificate https://solc-bin.ethereum.org/bin/soljson-v0.6.0+commit.26b70077.js -O soljson.js", "lint": "standard | notify-error", "make-mock-compiler": "node ci/makeMockCompiler.js", "minify": "uglifyjs --in-source-map inline --source-map-inline -c warnings=false", diff --git a/src/app.js b/src/app.js index bd928fdbad..d37a36fa2d 100644 --- a/src/app.js +++ b/src/app.js @@ -347,12 +347,13 @@ Please make a backup of your contracts and start using http://remix.ethereum.org const queryParams = new QueryParams() const loadedFromGist = gistHandler.loadFromGist(queryParams.get(), fileManager) if (!loadedFromGist) { - // insert ballot contract if there are no files to show + // insert example contracts if there are no files to show self._components.filesProviders['browser'].resolveDirectory('/', (error, filesList) => { if (error) console.error(error) if (Object.keys(filesList).length === 0) { - fileManager.setFile(examples.ballot.name, examples.ballot.content) - fileManager.setFile(examples.ballot_test.name, examples.ballot_test.content) + for (let file in examples) { + fileManager.setFile(examples[file].name, examples[file].content) + } } }) } diff --git a/src/app/editor/example-contracts.js b/src/app/editor/example-contracts.js index 880b29b741..a43984fb58 100644 --- a/src/app/editor/example-contracts.js +++ b/src/app/editor/example-contracts.js @@ -1,96 +1,263 @@ 'use strict' -var ballot = `pragma solidity >=0.4.22 <0.6.0; -contract Ballot { +const storage = `pragma solidity >=0.4.22 <0.7.0; + +/** + * @title Storage + * @dev Store & retreive value in a variable + */ + +contract Storage { + + uint256 number; + + /** + * @dev Store value in variable + * @param num value to store + */ + + function store(uint256 num) public { + number = num; + } + + /** + * @dev Return value + * @return value of 'number' + */ + + function retreive() public view returns (uint256){ + return number; + } +}` + +const owner = `pragma solidity >=0.4.22 <0.7.0; + +/** + * @title Owner + * @dev Set & change owner + */ + +contract Owner { + + address private owner; + + // event for EVM logging + event OwnerSet(address indexed oldOwner, address indexed newOwner); + + // modifier to check if caller is owner + modifier isOwner() { + // If the first argument of 'require' evaluates to 'false', execution terminates and all + // changes to the state and to Ether balances are reverted. + // This used to consume all gas in old EVM versions, but not anymore. + // It is often a good idea to use 'require' to check if functions are called correctly. + // As a second argument, you can also provide an explanation about what went wrong. + require(msg.sender == owner, "Caller is not owner"); + _; + } + + /** + * @dev Set contract deployer as owner + */ + + constructor() public { + owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor + emit OwnerSet(address(0), owner); + } + + /** + * @dev Change owner + * @param newOwner address of new owner + */ + + function changeOwner(address newOwner) public isOwner { + emit OwnerSet(owner, newOwner); + owner = newOwner; + } + + /** + * @dev Return owner address + * @return address of owner + */ + + function getOwner() external view returns (address) { + return owner; + } +}` + +const ballot = `pragma solidity >=0.4.22 <0.7.0; + +/** + * @title Ballot + * @dev Implements voting process along with vote delegation + */ +contract Ballot { + struct Voter { - uint weight; - bool voted; - uint8 vote; - address delegate; + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal } + struct Proposal { - uint voteCount; + // If you can limit the length to a certain number of bytes, + // always use one of bytes1 to bytes32 because they are much cheaper + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes } - address chairperson; - mapping(address => Voter) voters; - Proposal[] proposals; + address public chairperson; + + mapping(address => Voter) public voters; + + Proposal[] public proposals; - /// Create a new ballot with $(_numProposals) different proposals. - constructor(uint8 _numProposals) public { + /** + * @dev Create a new ballot to choose one of 'proposalNames'. + * @param proposalNames names of proposals + */ + + constructor(bytes32[] memory proposalNames) public { chairperson = msg.sender; voters[chairperson].weight = 1; - proposals.length = _numProposals; - } - /// Give $(toVoter) the right to vote on this ballot. - /// May only be called by $(chairperson). - function giveRightToVote(address toVoter) public { - if (msg.sender != chairperson || voters[toVoter].voted) return; - voters[toVoter].weight = 1; + for (uint i = 0; i < proposalNames.length; i++) { + // 'Proposal({...})' creates a temporary + // Proposal object and 'proposals.push(...)' + // appends it to the end of 'proposals'. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + } + + /** + * @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. + * @param voter address of voter + */ + + function giveRightToVote(address voter) public { + require( + msg.sender == chairperson, + "Only chairperson can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; } - /// Delegate your vote to the voter $(to). + /** + * @dev Delegate your vote to the voter 'to'. + * @param to address to which vote is delegated + */ + function delegate(address to) public { - Voter storage sender = voters[msg.sender]; // assigns reference - if (sender.voted) return; - while (voters[to].delegate != address(0) && voters[to].delegate != msg.sender) + Voter storage sender = voters[msg.sender]; + require(!sender.voted, "You already voted."); + require(to != msg.sender, "Self-delegation is disallowed."); + + while (voters[to].delegate != address(0)) { to = voters[to].delegate; - if (to == msg.sender) return; + + // We found a loop in the delegation, not allowed. + require(to != msg.sender, "Found loop in delegation."); + } sender.voted = true; sender.delegate = to; - Voter storage delegateTo = voters[to]; - if (delegateTo.voted) - proposals[delegateTo.vote].voteCount += sender.weight; - else - delegateTo.weight += sender.weight; + Voter storage delegate_ = voters[to]; + if (delegate_.voted) { + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate_.vote].voteCount += sender.weight; + } else { + // If the delegate did not vote yet, + // add to her weight. + delegate_.weight += sender.weight; + } } - /// Give a single vote to proposal $(toProposal). - function vote(uint8 toProposal) public { + /** + * @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. + * @param proposal index of proposal in the proposals array + */ + + function vote(uint proposal) public { Voter storage sender = voters[msg.sender]; - if (sender.voted || toProposal >= proposals.length) return; + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); sender.voted = true; - sender.vote = toProposal; - proposals[toProposal].voteCount += sender.weight; + sender.vote = proposal; + + // If 'proposal' is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; } - function winningProposal() public view returns (uint8 _winningProposal) { - uint256 winningVoteCount = 0; - for (uint8 prop = 0; prop < proposals.length; prop++) - if (proposals[prop].voteCount > winningVoteCount) { - winningVoteCount = proposals[prop].voteCount; - _winningProposal = prop; + /** + * @dev Computes the winning proposal taking all previous votes into account. + * @return winningProposal_ index of winning proposal in the proposals array + */ + + function winningProposal() public view + returns (uint winningProposal_) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; } + } + } + + /** + * @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then + * @return winnerName_ the name of the winner + */ + + function winnerName() public view + returns (bytes32 winnerName_) + { + winnerName_ = proposals[winningProposal()].name; } } ` -var ballotTest = ` -pragma solidity >=0.4.22 <0.6.0; +var ballotTest = `pragma solidity >=0.4.22 <0.7.0; import "remix_tests.sol"; // this import is automatically injected by Remix. -import "./ballot.sol"; +import "./3_Ballot.sol"; -contract test3 { +contract BallotTest { + + bytes32[] proposalNames; Ballot ballotToTest; function beforeAll () public { - ballotToTest = new Ballot(2); + proposalNames.push(bytes32("candidate1")); + ballotToTest = new Ballot(proposalNames); } function checkWinningProposal () public { - ballotToTest.vote(1); - Assert.equal(ballotToTest.winningProposal(), uint(1), "1 should be the winning proposal"); + ballotToTest.vote(0); + Assert.equal(ballotToTest.winningProposal(), uint(0), "proposal at index 0 should be the winning proposal"); + Assert.equal(ballotToTest.winnerName(), bytes32("candidate1"), "candidate1 should be the winner name"); } function checkWinninProposalWithReturnValue () public view returns (bool) { - return ballotToTest.winningProposal() == 1; + return ballotToTest.winningProposal() == 0; } } ` module.exports = { - ballot: { name: 'ballot.sol', content: ballot }, - ballot_test: { name: 'ballot_test.sol', content: ballotTest } + storage: { name: '1_Storage.sol', content: storage }, + owner: { name: '2_Owner.sol', content: owner }, + ballot: { name: '3_Ballot.sol', content: ballot }, + ballot_test: { name: '4_Ballot_test.sol', content: ballotTest } } diff --git a/src/app/tabs/compileTab/compilerContainer.js b/src/app/tabs/compileTab/compilerContainer.js index 6ef23e50c7..9790b66784 100644 --- a/src/app/tabs/compileTab/compilerContainer.js +++ b/src/app/tabs/compileTab/compilerContainer.js @@ -24,7 +24,7 @@ class CompilerContainer { timeout: 300, allversions: null, selectedVersion: null, - defaultVersion: 'soljson-v0.5.14+commit.1f1aaa4.js', // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler + defaultVersion: 'soljson-v0.6.0+commit.26b70077.js', // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler baseurl: 'https://solc-bin.ethereum.org/bin' } } diff --git a/test-browser/tests/ballot.js b/test-browser/tests/ballot.js index ff7cb79845..ea85a16d26 100644 --- a/test-browser/tests/ballot.js +++ b/test-browser/tests/ballot.js @@ -20,7 +20,7 @@ module.exports = { .clickLaunchIcon('solidity') .testContracts('Untitled.sol', sources[0]['browser/Untitled.sol'], ['Ballot']) .clickLaunchIcon('udapp') - .setValue('input[placeholder="uint8 _numProposals"]', '1') + .setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]') .click('#runTabView button[class^="instanceButton"]') .waitForElementPresent('.instance:nth-of-type(2)') .click('.instance:nth-of-type(2) > div > button') @@ -36,7 +36,7 @@ module.exports = { .clickLaunchIcon('debugger') .click('#jumppreviousbreakpoint') .pause(2000) - .goToVMTraceStep(58) + .goToVMTraceStep(79) .pause(1000) .checkVariableDebug('soliditystate', stateCheck) .checkVariableDebug('soliditylocals', localsCheck) @@ -85,13 +85,13 @@ var stateCheck = { 'value': false, 'type': 'bool' }, - 'vote': { - 'value': '0', - 'type': 'uint8' - }, 'delegate': { 'value': '0x0000000000000000000000000000000000000000', 'type': 'address' + }, + 'vote': { + 'value': '0', + 'type': 'uint256' } }, 'type': 'struct Ballot.Voter' @@ -104,6 +104,10 @@ var stateCheck = { 'value': [ { 'value': { + 'name': { + 'value': '0x48656C6C6F20576F726C64210000000000000000000000000000000000000000', + 'type': 'bytes32' + }, 'voteCount': { 'value': '0', 'type': 'uint256' @@ -118,4 +122,169 @@ var stateCheck = { } } -var ballotABI = '[{"constant":false,"inputs":[{"name":"to","type":"address"}],"name":"delegate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"winningProposal","outputs":[{"name":"_winningProposal","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"toVoter","type":"address"}],"name":"giveRightToVote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"toProposal","type":"uint8"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_numProposals","type":"uint8"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]' +var ballotABI = `[ +{ + "inputs": [ + { + "internalType": "bytes32[]", + "name": "proposalNames", + "type": "bytes32[]" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" +}, +{ + "constant": true, + "inputs": [], + "name": "chairperson", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" +}, +{ + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "delegate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "giveRightToVote", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "proposals", + "outputs": [ + { + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "voteCount", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" +}, +{ + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "proposal", + "type": "uint256" + } + ], + "name": "vote", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "voters", + "outputs": [ + { + "internalType": "uint256", + "name": "weight", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "voted", + "type": "bool" + }, + { + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "internalType": "uint256", + "name": "vote", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" +}, +{ + "constant": true, + "inputs": [], + "name": "winnerName", + "outputs": [ + { + "internalType": "bytes32", + "name": "winnerName_", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" +}, +{ + "constant": true, + "inputs": [], + "name": "winningProposal", + "outputs": [ + { + "internalType": "uint256", + "name": "winningProposal_", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" +} +]` diff --git a/test-browser/tests/solidityUnittests.js b/test-browser/tests/solidityUnittests.js index 9de320c2ae..8de6f15f2c 100644 --- a/test-browser/tests/solidityUnittests.js +++ b/test-browser/tests/solidityUnittests.js @@ -23,12 +23,12 @@ function runTests (browser) { .click('#icon-panel div[plugin="pluginManager"]') .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_solidityUnitTesting"] button') .clickLaunchIcon('fileExplorers') - .switchFile('browser/ballot.sol') + .switchFile('browser/3_Ballot.sol') .clickLaunchIcon('solidityUnitTesting') .scrollAndClick('#runTestsTabRunAction') .waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]') .pause(10000) - .assert.containsText('#solidityUnittestsOutput', 'browser/ballot_test.sol (test3)') + .assert.containsText('#solidityUnittestsOutput', 'browser/4_Ballot_test.sol (BallotTest)') .assert.containsText('#solidityUnittestsOutput', '✓ (Check winning proposal)') .assert.containsText('#solidityUnittestsOutput', '✓ (Check winnin proposal with return value)') .end()