Merge remote-tracking branch 'origin/master' into refactor_logic6

pull/1/head
yann300 5 years ago
commit 95c6bf03ec
  1. 4
      README.md
  2. 1
      ci/browser_tests.sh
  3. 2
      ci/makeMockCompiler.js
  4. 156
      contracts/node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol
  5. 1
      contracts/node_modules/openzeppelin-solidity/contracts/sample.sol
  6. 17178
      package-lock.json
  7. 23
      package.json
  8. 10
      src/app.js
  9. 4
      src/app/editor/editor.js
  10. 253
      src/app/editor/example-contracts.js
  11. 1
      src/app/files/compiler-metadata.js
  12. 8
      src/app/files/file-explorer.js
  13. 4
      src/app/files/fileManager.js
  14. 2
      src/app/panels/terminal.js
  15. 15
      src/app/tabs/compile-tab.js
  16. 64
      src/app/tabs/compileTab/compileTab.js
  17. 3
      src/app/tabs/compileTab/compilerContainer.js
  18. 2
      src/app/tabs/debugger/debuggerUI/vmDebugger/MemoryPanel.js
  19. 2
      src/app/tabs/debugger/debuggerUI/vmDebugger/StackPanel.js
  20. 5
      src/app/tabs/runTab/contractDropdown.js
  21. 2
      src/app/tabs/runTab/model/recorder.js
  22. 3
      src/app/tabs/runTab/recorder.js
  23. 25
      src/app/tabs/test-tab.js
  24. 2
      src/app/tabs/testTab/testTab.js
  25. 4
      src/app/tabs/theme-module.js
  26. 2
      src/app/ui/landing-page/landing-page.js
  27. 7
      src/app/ui/multiParamManager.js
  28. 4
      src/app/ui/persmission-handler.js
  29. 109
      src/app/ui/sendTxCallbacks.js
  30. 10
      src/app/ui/txLogger.js
  31. 348
      src/app/ui/universal-dapp-ui.js
  32. 27
      src/blockchain/blockchain.js
  33. 9
      src/lib/helper.js
  34. 18
      src/lib/publishOnIpfs.js
  35. 18
      src/lib/publishOnSwarm.js
  36. 203
      src/remixAppManager.js
  37. 3
      test-browser/commands/clickInstance.js
  38. 2
      test-browser/commands/clickLaunchIcon.js
  39. 1
      test-browser/commands/executeScript.js
  40. 16
      test-browser/commands/getModalBody.js
  41. 8
      test-browser/commands/journalLastChild.js
  42. 19
      test-browser/commands/journalLastChildIncludes.js
  43. 17
      test-browser/commands/modalFooterCancelClick.js
  44. 2
      test-browser/commands/modalFooterOKClick.js
  45. 19
      test-browser/commands/sendLowLevelTx.js
  46. 15
      test-browser/commands/setSolidityCompilerVersion.js
  47. 1
      test-browser/commands/switchFile.js
  48. 11
      test-browser/commands/verifyContracts.js
  49. 19
      test-browser/helpers/init.js
  50. 200
      test-browser/tests/ballot.js
  51. 43
      test-browser/tests/gist.js
  52. 56
      test-browser/tests/importFromGist.js
  53. 37
      test-browser/tests/publishContract.js
  54. 32
      test-browser/tests/remixd.js
  55. 2
      test-browser/tests/signingMessage.js
  56. 160
      test-browser/tests/solidityImport.js
  57. 4
      test-browser/tests/solidityUnittests.js
  58. 273
      test-browser/tests/specialFunctions.js
  59. 17
      test-browser/tests/workspace.js
  60. 2
      test/compiler-test.js

@ -109,7 +109,9 @@ To run the Selenium tests via Nightwatch:
- npm run nightwatch_local_console - npm run nightwatch_local_console
- npm run nightwatch_local_remixd # remixd needs to be run - npm run nightwatch_local_remixd # remixd needs to be run
**the `ballot` tests suite requires to run `ganache-cli` locally.**
**the `remixd` tests suite requires to run `remixd` locally.**
## Usage as a Chrome Extension ## Usage as a Chrome Extension

@ -15,6 +15,7 @@ BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID" echo "$BUILD_ID"
TEST_EXITCODE=0 TEST_EXITCODE=0
npm run ganache-cli &
npm run serve & npm run serve &
setupRemixd setupRemixd

@ -3,7 +3,7 @@
var fs = require('fs') var fs = require('fs')
var compiler = require('solc') var compiler = require('solc')
var compilerInput = require('remix-solidity').CompilerInput var compilerInput = require('remix-solidity').CompilerInput
var defaultVersion = 'v0.5.14+commit.1f1aaa4' var defaultVersion = 'v0.6.1+commit.e6f7d5a4'
compiler.loadRemoteVersion(defaultVersion, (error, solcSnapshot) => { compiler.loadRemoteVersion(defaultVersion, (error, solcSnapshot) => {
if (error) console.log(error) if (error) console.log(error)

@ -0,0 +1,156 @@
pragma solidity ^0.5.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*
* _Available since v2.4.0._
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* _Available since v2.4.0._
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* _Available since v2.4.0._
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}

@ -0,0 +1 @@
import "https://github.com/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; contract test11 {}

17178
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{ {
"name": "remix-ide", "name": "remix-ide",
"version": "v0.9.2", "version": "v0.9.4",
"description": "Extendable Web IDE for Ethereum", "description": "Extendable Web IDE for Ethereum",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.4.5", "@babel/core": "^7.4.5",
@ -37,6 +37,7 @@
"exorcist": "^0.4.0", "exorcist": "^0.4.0",
"fast-async": "^7.0.6", "fast-async": "^7.0.6",
"fast-levenshtein": "^2.0.6", "fast-levenshtein": "^2.0.6",
"ganache-cli": "^6.8.1",
"gists": "^1.0.1", "gists": "^1.0.1",
"ipfs-mini": "^1.1.5", "ipfs-mini": "^1.1.5",
"is-electron": "^2.2.0", "is-electron": "^2.2.0",
@ -53,12 +54,12 @@
"npm-merge-driver": "^2.3.5", "npm-merge-driver": "^2.3.5",
"npm-run-all": "^4.0.2", "npm-run-all": "^4.0.2",
"onchange": "^3.2.1", "onchange": "^3.2.1",
"remix-analyzer": "0.3.19", "remix-analyzer": "0.3.23",
"remix-debug": "0.3.21", "remix-debug": "0.3.26",
"remix-lib": "0.4.18", "remix-lib": "0.4.22",
"remix-solidity": "0.3.21", "remix-solidity": "0.3.25",
"remix-tabs": "1.0.48", "remix-tabs": "1.0.48",
"remix-tests": "0.1.24", "remix-tests": "0.1.28",
"remixd": "0.1.8-alpha.10", "remixd": "0.1.8-alpha.10",
"request": "^2.83.0", "request": "^2.83.0",
"rimraf": "^2.6.1", "rimraf": "^2.6.1",
@ -154,7 +155,7 @@
"build_debugger": "browserify src/app/debugger/remix-debugger/index.js -o src/app/debugger/remix-debugger/build/app.js", "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", "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/", "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.1+commit.e6f7d5a4.js -O soljson.js",
"lint": "standard | notify-error", "lint": "standard | notify-error",
"make-mock-compiler": "node ci/makeMockCompiler.js", "make-mock-compiler": "node ci/makeMockCompiler.js",
"minify": "uglifyjs --in-source-map inline --source-map-inline -c warnings=false", "minify": "uglifyjs --in-source-map inline --source-map-inline -c warnings=false",
@ -167,9 +168,12 @@
"nightwatch_local_transactionExecution": "nightwatch ./test-browser/tests/transactionExecution.js --config nightwatch.js --env chrome ", "nightwatch_local_transactionExecution": "nightwatch ./test-browser/tests/transactionExecution.js --config nightwatch.js --env chrome ",
"nightwatch_local_staticAnalysis": "nightwatch ./test-browser/tests/staticAnalysis.js --config nightwatch.js --env chrome ", "nightwatch_local_staticAnalysis": "nightwatch ./test-browser/tests/staticAnalysis.js --config nightwatch.js --env chrome ",
"nightwatch_local_signingMessage": "nightwatch ./test-browser/tests/signingMessage.js --config nightwatch.js --env chrome ", "nightwatch_local_signingMessage": "nightwatch ./test-browser/tests/signingMessage.js --config nightwatch.js --env chrome ",
"nightwatch_local_specialFunctions": "nightwatch ./test-browser/tests/specialFunctions.js --config nightwatch.js --env chrome ",
"nightwatch_local_solidityUnittests": "nightwatch ./test-browser/tests/solidityUnittests.js --config nightwatch.js --env chrome ", "nightwatch_local_solidityUnittests": "nightwatch ./test-browser/tests/solidityUnittests.js --config nightwatch.js --env chrome ",
"nightwatch_local_remixd": "nightwatch ./test-browser/tests/remix.js --config nightwatch.js --env chrome ", "nightwatch_local_remixd": "nightwatch ./test-browser/tests/remixd.js --config nightwatch.js --env chrome ",
"nightwatch_local_console": "nightwatch ./test-browser/tests/console.js --config nightwatch.js --env chrome ", "nightwatch_local_console": "nightwatch ./test-browser/tests/console.js --config nightwatch.js --env chrome ",
"nightwatch_local_gist": "nightwatch ./test-browser/tests/gist.js --config nightwatch.js --env chrome ",
"nightwatch_local_workspace": "nightwatch ./test-browser/tests/workspace.js --config nightwatch.js --env chrome ",
"onchange": "onchange build/app.js -- npm-run-all lint", "onchange": "onchange build/app.js -- npm-run-all lint",
"prepublish": "mkdirp build; npm-run-all -ls downloadsolc_root build", "prepublish": "mkdirp build; npm-run-all -ls downloadsolc_root build",
"remixd": "remixd -s ./contracts --remix-ide http://127.0.0.1:8080", "remixd": "remixd -s ./contracts --remix-ide http://127.0.0.1:8080",
@ -181,6 +185,7 @@
"test": "csslint && standard && node test/index.js", "test": "csslint && standard && node test/index.js",
"test-browser": "npm-run-all -lpr selenium downloadsolc_root make-mock-compiler serve browsertest", "test-browser": "npm-run-all -lpr selenium downloadsolc_root make-mock-compiler serve browsertest",
"watch": "watchify src/index.js -dv -p browserify-reload -o build/app.js --exclude solc", "watch": "watchify src/index.js -dv -p browserify-reload -o build/app.js --exclude solc",
"reinstall": "rm ./node-modules/ -rf; rm package-lock.json; rm ./build/ -rf; npm install; npm run build" "reinstall": "rm ./node-modules/ -rf; rm package-lock.json; rm ./build/ -rf; npm install; npm run build",
"ganache-cli": "npx ganache-cli"
} }
} }

@ -1,4 +1,3 @@
/* global localStorage */
'use strict' 'use strict'
var isElectron = require('is-electron') var isElectron = require('is-electron')
@ -208,7 +207,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
// APP_MANAGER // APP_MANAGER
const appManager = new RemixAppManager({}) const appManager = new RemixAppManager({})
const workspace = JSON.parse(localStorage.getItem('workspace')) const workspace = appManager.pluginLoader.get()
// SERVICES // SERVICES
// ----------------- import content servive ---------------------------- // ----------------- import content servive ----------------------------
@ -351,12 +350,13 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
const queryParams = new QueryParams() const queryParams = new QueryParams()
const loadedFromGist = gistHandler.loadFromGist(queryParams.get(), fileManager) const loadedFromGist = gistHandler.loadFromGist(queryParams.get(), fileManager)
if (!loadedFromGist) { 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) => { self._components.filesProviders['browser'].resolveDirectory('/', (error, filesList) => {
if (error) console.error(error) if (error) console.error(error)
if (Object.keys(filesList).length === 0) { if (Object.keys(filesList).length === 0) {
fileManager.setFile(examples.ballot.name, examples.ballot.content) for (let file in examples) {
fileManager.setFile(examples.ballot_test.name, examples.ballot_test.content) fileManager.setFile(examples[file].name, examples[file].content)
}
} }
}) })
} }

@ -19,6 +19,7 @@ require('ace-mode-zokrates')
require('brace/mode/javascript') require('brace/mode/javascript')
require('brace/mode/python') require('brace/mode/python')
require('brace/mode/json') require('brace/mode/json')
require('brace/mode/rust')
require('brace/theme/chaos') require('brace/theme/chaos')
require('brace/theme/chrome') require('brace/theme/chrome')
@ -85,7 +86,8 @@ class Editor extends Plugin {
zok: 'ace/mode/zokrates', zok: 'ace/mode/zokrates',
txt: 'ace/mode/text', txt: 'ace/mode/text',
json: 'ace/mode/json', json: 'ace/mode/json',
abi: 'ace/mode/json' abi: 'ace/mode/json',
rs: 'ace/mode/rust'
} }
// Editor Setup // Editor Setup

@ -1,96 +1,249 @@
'use strict' 'use strict'
var ballot = `pragma solidity >=0.4.22 <0.6.0; const storage = `pragma solidity >=0.4.22 <0.7.0;
contract Ballot {
/**
* @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 { struct Voter {
uint weight; uint weight; // weight is accumulated by delegation
bool voted; bool voted; // if true, that person already voted
uint8 vote; address delegate; // person delegated to
address delegate; uint vote; // index of the voted proposal
} }
struct 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; address public chairperson;
mapping(address => Voter) voters;
Proposal[] proposals;
/// Create a new ballot with $(_numProposals) different proposals. mapping(address => Voter) public voters;
constructor(uint8 _numProposals) public {
Proposal[] public proposals;
/**
* @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals
*/
constructor(bytes32[] memory proposalNames) public {
chairperson = msg.sender; chairperson = msg.sender;
voters[chairperson].weight = 1; voters[chairperson].weight = 1;
proposals.length = _numProposals;
}
/// Give $(toVoter) the right to vote on this ballot. for (uint i = 0; i < proposalNames.length; i++) {
/// May only be called by $(chairperson). // 'Proposal({...})' creates a temporary
function giveRightToVote(address toVoter) public { // Proposal object and 'proposals.push(...)'
if (msg.sender != chairperson || voters[toVoter].voted) return; // appends it to the end of 'proposals'.
voters[toVoter].weight = 1; 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 { function delegate(address to) public {
Voter storage sender = voters[msg.sender]; // assigns reference Voter storage sender = voters[msg.sender];
if (sender.voted) return; require(!sender.voted, "You already voted.");
while (voters[to].delegate != address(0) && voters[to].delegate != msg.sender) require(to != msg.sender, "Self-delegation is disallowed.");
while (voters[to].delegate != address(0)) {
to = voters[to].delegate; 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.voted = true;
sender.delegate = to; sender.delegate = to;
Voter storage delegateTo = voters[to]; Voter storage delegate_ = voters[to];
if (delegateTo.voted) if (delegate_.voted) {
proposals[delegateTo.vote].voteCount += sender.weight; // If the delegate already voted,
else // directly add to the number of votes
delegateTo.weight += sender.weight; 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]; 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.voted = true;
sender.vote = toProposal; sender.vote = proposal;
proposals[toProposal].voteCount += sender.weight;
// 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; * @dev Computes the winning proposal taking all previous votes into account.
for (uint8 prop = 0; prop < proposals.length; prop++) * @return winningProposal_ index of winning proposal in the proposals array
if (proposals[prop].voteCount > winningVoteCount) { */
winningVoteCount = proposals[prop].voteCount; function winningProposal() public view
_winningProposal = prop; 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 = ` var ballotTest = `pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.6.0;
import "remix_tests.sol"; // this import is automatically injected by Remix. 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; Ballot ballotToTest;
function beforeAll () public { function beforeAll () public {
ballotToTest = new Ballot(2); proposalNames.push(bytes32("candidate1"));
ballotToTest = new Ballot(proposalNames);
} }
function checkWinningProposal () public { function checkWinningProposal () public {
ballotToTest.vote(1); ballotToTest.vote(0);
Assert.equal(ballotToTest.winningProposal(), uint(1), "1 should be the winning proposal"); 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) { function checkWinninProposalWithReturnValue () public view returns (bool) {
return ballotToTest.winningProposal() == 1; return ballotToTest.winningProposal() == 0;
} }
} }
` `
module.exports = { module.exports = {
ballot: { name: 'ballot.sol', content: ballot }, storage: { name: '1_Storage.sol', content: storage },
ballot_test: { name: 'ballot_test.sol', content: ballotTest } owner: { name: '2_Owner.sol', content: owner },
ballot: { name: '3_Ballot.sol', content: ballot },
ballot_test: { name: '4_Ballot_test.sol', content: ballotTest }
} }

@ -98,6 +98,7 @@ class CompilerMetadata extends Plugin {
this.blockchain.detectNetwork((err, { id, name } = {}) => { this.blockchain.detectNetwork((err, { id, name } = {}) => {
if (err) { if (err) {
console.log(err) console.log(err)
reject(err)
} else { } else {
var fileName = this._JSONFileName(path, contractName) var fileName = this._JSONFileName(path, contractName)
provider.get(fileName, (error, content) => { provider.get(fileName, (error, content) => {

@ -470,7 +470,9 @@ fileExplorer.prototype.toGist = function (id) {
return data.files || [] return data.files || []
} }
this.packageFiles(this.files, 'browser/gists/' + id, (error, packaged) => { // If 'id' is not defined, it is not a gist update but a creation so we have to take the files from the browser explorer.
const folder = id ? 'browser/gists/' + id : 'browser/'
this.packageFiles(this.files, folder, (error, packaged) => {
if (error) { if (error) {
console.log(error) console.log(error)
modalDialogCustom.alert('Failed to create gist: ' + error) modalDialogCustom.alert('Failed to create gist: ' + error)
@ -632,7 +634,7 @@ fileExplorer.prototype.renderMenuItems = function () {
items = this.menuItems.map(({action, title, icon}) => { items = this.menuItems.map(({action, title, icon}) => {
if (action === 'uploadFile') { if (action === 'uploadFile') {
return yo` return yo`
<label class="${icon} ${css.newFile}" title="${title}"> <label id=${action} class="${icon} ${css.newFile}" title="${title}">
<input type="file" onchange=${(event) => { <input type="file" onchange=${(event) => {
event.stopPropagation() event.stopPropagation()
this.uploadFile(event) this.uploadFile(event)
@ -641,7 +643,7 @@ fileExplorer.prototype.renderMenuItems = function () {
` `
} else { } else {
return yo` return yo`
<span onclick=${(event) => { event.stopPropagation(); this[ action ]() }} class="newFile ${icon} ${css.newFile}" title=${title}></span> <span id=${action} onclick=${(event) => { event.stopPropagation(); this[ action ]() }} class="newFile ${icon} ${css.newFile}" title=${title}></span>
` `
} }
}) })

@ -314,6 +314,10 @@ class FileManager extends Plugin {
}) })
} }
getProvider (name) {
return this._deps.filesProviders[name]
}
fileProviderOf (file) { fileProviderOf (file) {
if (file.indexOf('localhost') === 0) { if (file.indexOf('localhost') === 0) {
return this._deps.filesProviders['localhost'] return this._deps.filesProviders['localhost']

@ -148,7 +148,7 @@ class Terminal extends Plugin {
${self._view.dragbar} ${self._view.dragbar}
<div class="${css.menu} border-top border-dark bg-light"> <div class="${css.menu} border-top border-dark bg-light">
${self._view.icon} ${self._view.icon}
<div class=${css.clear} onclick=${clear}> <div class=${css.clear} id="clearConsole" onclick=${clear}>
<i class="fas fa-ban" aria-hidden="true" title="Clear console" <i class="fas fa-ban" aria-hidden="true" title="Clear console"
onmouseenter=${hover} onmouseleave=${hover}></i> onmouseenter=${hover} onmouseleave=${hover}></i>
</div> </div>

@ -187,7 +187,7 @@ class CompileTab extends ViewPlugin {
} }
getCompilationResult () { getCompilationResult () {
return this.compileTabLogic.compiler.lastCompilationResult return this.compileTabLogic.compiler.state.lastCompilationResult
} }
// This function is used by remix-plugin // This function is used by remix-plugin
@ -200,6 +200,15 @@ class CompileTab extends ViewPlugin {
return this.compilerContainer.data.selectedVersion return this.compilerContainer.data.selectedVersion
} }
// This function is used for passing the compiler configuration to 'remix-tests'
getCurrentCompilerConfig () {
return {
currentVersion: this.compilerContainer.data.selectedVersion,
evmVersion: this.compileTabLogic.evmVersion,
optimize: this.compileTabLogic.optimize
}
}
/********* /*********
* SUB-COMPONENTS * SUB-COMPONENTS
*/ */
@ -239,11 +248,11 @@ class CompileTab extends ViewPlugin {
${selectEl} ${selectEl}
</div> </div>
<article class="px-2 mt-2 pb-0"> <article class="px-2 mt-2 pb-0">
<button class="btn btn-secondary btn-block" title="Publish on Swarm" onclick="${() => { this.publish('swarm') }}"> <button id="publishOnSwarm" class="btn btn-secondary btn-block" title="Publish on Swarm" onclick="${() => { this.publish('swarm') }}">
<span>Publish on Swarm</span> <span>Publish on Swarm</span>
<img id="swarmLogo" class="${css.storageLogo} ml-2" src="${swarmImg}"> <img id="swarmLogo" class="${css.storageLogo} ml-2" src="${swarmImg}">
</button> </button>
<button class="btn btn-secondary btn-block" title="Publish on Ipfs" onclick="${() => { this.publish('ipfs') }}"> <button id="publishOnIpfs" class="btn btn-secondary btn-block" title="Publish on Ipfs" onclick="${() => { this.publish('ipfs') }}">
<span>Publish on Ipfs</span> <span>Publish on Ipfs</span>
<img id="ipfsLogo" class="${css.storageLogo} ml-2" src="${ipfsImg}"> <img id="ipfsLogo" class="${css.storageLogo} ml-2" src="${ipfsImg}">
</button> </button>

@ -24,26 +24,26 @@ class CompileTab {
this.optimize = this.queryParams.get().optimize this.optimize = this.queryParams.get().optimize
this.optimize = this.optimize === 'true' this.optimize = this.optimize === 'true'
this.queryParams.update({ optimize: this.optimize }) this.queryParams.update({ optimize: this.optimize })
this.compiler.setOptimize(this.optimize) this.compiler.set('optimize', this.optimize)
this.evmVersion = this.queryParams.get().evmVersion this.evmVersion = this.queryParams.get().evmVersion
if (this.evmVersion === 'undefined' || this.evmVersion === 'null' || !this.evmVersion) { if (this.evmVersion === 'undefined' || this.evmVersion === 'null' || !this.evmVersion) {
this.evmVersion = null this.evmVersion = null
} }
this.queryParams.update({ evmVersion: this.evmVersion }) this.queryParams.update({ evmVersion: this.evmVersion })
this.compiler.setEvmVersion(this.evmVersion) this.compiler.set('evmVersion', this.evmVersion)
} }
setOptimize (newOptimizeValue) { setOptimize (newOptimizeValue) {
this.optimize = newOptimizeValue this.optimize = newOptimizeValue
this.queryParams.update({ optimize: this.optimize }) this.queryParams.update({ optimize: this.optimize })
this.compiler.setOptimize(this.optimize) this.compiler.set('optimize', this.optimize)
} }
setEvmVersion (newEvmVersion) { setEvmVersion (newEvmVersion) {
this.evmVersion = newEvmVersion this.evmVersion = newEvmVersion
this.queryParams.update({ evmVersion: this.evmVersion }) this.queryParams.update({ evmVersion: this.evmVersion })
this.compiler.setEvmVersion(this.evmVersion) this.compiler.set('evmVersion', this.evmVersion)
} }
/** /**
@ -51,7 +51,7 @@ class CompileTab {
* @params lang {'Solidity' | 'Yul'} ... * @params lang {'Solidity' | 'Yul'} ...
*/ */
setLanguage (lang) { setLanguage (lang) {
this.compiler.setLanguage(lang) this.compiler.set('language', lang)
} }
/** /**
@ -99,6 +99,15 @@ class CompileTab {
}) })
} }
/**
* import the content of @arg url.
* first look in the browser localstorage (browser explorer) or locahost explorer. if the url start with `browser/*` or `localhost/*`
* then check if the @arg url is located in the localhost, in the node_modules or installed_contracts folder
* then check if the @arg url match any external url
*
* @param {String} url - URL of the content. can be basically anything like file located in the browser explorer, in the localhost explorer, raw HTTP, github address etc...
* @param {Function} cb - callback
*/
importFileCb (url, filecb) { importFileCb (url, filecb) {
if (url.indexOf('remix_tests.sol') !== -1) return filecb(null, remixTests.assertLibCode) if (url.indexOf('remix_tests.sol') !== -1) return filecb(null, remixTests.assertLibCode)
@ -107,26 +116,39 @@ class CompileTab {
if (provider.type === 'localhost' && !provider.isConnected()) { if (provider.type === 'localhost' && !provider.isConnected()) {
return filecb(`file provider ${provider.type} not available while trying to resolve ${url}`) return filecb(`file provider ${provider.type} not available while trying to resolve ${url}`)
} }
return provider.exists(url, (error, exist) => { provider.exists(url, (error, exist) => {
if (error) return filecb(error) if (error) return filecb(error)
if (exist) { if (!exist && provider.type === 'localhost') return filecb(`not found ${url}`)
return provider.get(url, filecb)
/*
if the path is absolute and the file does not exist, we can stop here
Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop.
*/
if (!exist && url.startsWith('browser/')) return filecb(`not found ${url}`)
if (!exist && url.startsWith('localhost/')) return filecb(`not found ${url}`)
if (exist) return provider.get(url, filecb)
// try to resolve localhost modules (aka truffle imports) - e.g from the node_modules folder
const localhostProvider = this.fileManager.getProvider('localhost')
if (localhostProvider.isConnected()) {
var splitted = /([^/]+)\/(.*)$/g.exec(url)
return async.tryEach([
(cb) => { this.importFileCb('localhost/installed_contracts/' + url, cb) },
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.importFileCb('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], cb) } },
(cb) => { this.importFileCb('localhost/node_modules/' + url, cb) },
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.importFileCb('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], cb) } }],
(error, result) => {
if (error) return this.importExternal(url, filecb)
filecb(null, result)
}
)
} else {
// try to resolve external content
this.importExternal(url, filecb)
} }
this.importExternal(url, filecb)
}) })
} }
if (this.compilerImport.isRelativeImport(url)) {
// try to resolve localhost modules (aka truffle imports)
var splitted = /([^/]+)\/(.*)$/g.exec(url)
return async.tryEach([
(cb) => { this.importFileCb('localhost/installed_contracts/' + url, cb) },
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.importFileCb('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], cb) } },
(cb) => { this.importFileCb('localhost/node_modules/' + url, cb) },
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.importFileCb('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], cb) } }],
(error, result) => { filecb(error, result) }
)
}
this.importExternal(url, filecb)
} }
} }

@ -24,7 +24,7 @@ class CompilerContainer {
timeout: 300, timeout: 300,
allversions: null, allversions: null,
selectedVersion: 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.1+commit.e6f7d5a4.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' baseurl: 'https://solc-bin.ethereum.org/bin'
} }
} }
@ -204,6 +204,7 @@ class CompilerContainer {
this._view.evmVersionSelector = yo` this._view.evmVersionSelector = yo`
<select onchange="${this.onchangeEvmVersion.bind(this)}" class="custom-select" id="evmVersionSelector"> <select onchange="${this.onchangeEvmVersion.bind(this)}" class="custom-select" id="evmVersionSelector">
<option value="default">compiler default</option> <option value="default">compiler default</option>
<option>istanbul</option>
<option>petersburg</option> <option>petersburg</option>
<option>constantinople</option> <option>constantinople</option>
<option>byzantium</option> <option>byzantium</option>

@ -15,7 +15,7 @@ MemoryPanel.prototype.update = function (calldata) {
} }
MemoryPanel.prototype.render = function () { MemoryPanel.prototype.render = function () {
return yo`<div id="memorypanel" class="text-monospace small">${this.basicPanel.render()}</div>` return yo`<div id="memorypanel">${this.basicPanel.render()}</div>`
} }
module.exports = MemoryPanel module.exports = MemoryPanel

@ -11,7 +11,7 @@ StackPanel.prototype.update = function (calldata) {
} }
StackPanel.prototype.render = function () { StackPanel.prototype.render = function () {
return yo`<div id="stackpanel" class="text-monospace small">${this.basicPanel.render()}</div>` return yo`<div id="stackpanel">${this.basicPanel.render()}</div>`
} }
module.exports = StackPanel module.exports = StackPanel

@ -214,17 +214,18 @@ class ContractDropdownUI {
} }
getConfirmationCb (modalDialog, confirmDialog) { getConfirmationCb (modalDialog, confirmDialog) {
// this code is the same as in recorder.js. TODO need to be refactored out
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
if (network.name !== 'Main') { if (network.name !== 'Main') {
return continueTxExecution(null) return continueTxExecution(null)
} }
const amount = this.blockchain.fromWei(tx.value, true, 'ether') const amount = this.blockchain.fromWei(tx.value, true, 'ether')
const content = confirmDialog(tx, amount, gasEstimation, null, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice) const content = confirmDialog(tx, amount, gasEstimation, null, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain))
modalDialog('Confirm transaction', content, modalDialog('Confirm transaction', content,
{ label: 'Confirm', { label: 'Confirm',
fn: () => { fn: () => {
this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked) this.blockchain.udapp.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
// TODO: check if this is check is still valid given the refactor // TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) { if (!content.gasPriceStatus) {
cancelCb('Given gas price is not correct') cancelCb('Given gas price is not correct')

@ -211,6 +211,8 @@ class Recorder {
fnABI = txHelper.getConstructorInterface(abi) fnABI = txHelper.getConstructorInterface(abi)
} else if (tx.record.type === 'fallback') { } else if (tx.record.type === 'fallback') {
fnABI = txHelper.getFallbackInterface(abi) fnABI = txHelper.getFallbackInterface(abi)
} else if (tx.record.type === 'receive') {
fnABI = txHelper.getReceiveInterface(abi)
} else { } else {
fnABI = txHelper.getFunction(abi, record.name + record.inputs) fnABI = txHelper.getFunction(abi, record.name + record.inputs)
} }

@ -78,12 +78,13 @@ class RecorderUI {
} }
getConfirmationCb (modalDialog, confirmDialog) { getConfirmationCb (modalDialog, confirmDialog) {
// this code is the same as in contractDropdown.js. TODO need to be refactored out
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
if (network.name !== 'Main') { if (network.name !== 'Main') {
return continueTxExecution(null) return continueTxExecution(null)
} }
const amount = this.blockchain.fromWei(tx.value, true, 'ether') const amount = this.blockchain.fromWei(tx.value, true, 'ether')
const content = confirmDialog(tx, amount, gasEstimation, null, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice) const content = confirmDialog(tx, amount, gasEstimation, null, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain))
modalDialog('Confirm transaction', content, modalDialog('Confirm transaction', content,
{ label: 'Confirm', { label: 'Confirm',

@ -153,8 +153,15 @@ module.exports = class TestTab extends ViewPlugin {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let runningTest = {} let runningTest = {}
runningTest[path] = { content } runningTest[path] = { content }
let currentCompilerUrl = this.baseurl + '/' + this.compileTab.getCurrentVersion() const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig()
remixTests.runTestSources(runningTest, currentCompilerUrl, canUseWorker(this.compileTab.getCurrentVersion()), () => {}, () => {}, (error, result) => { const currentCompilerUrl = this.baseurl + '/' + currentVersion
const compilerConfig = {
currentCompilerUrl,
evmVersion,
optimize,
usingWorker: canUseWorker(currentVersion)
}
remixTests.runTestSources(runningTest, compilerConfig, () => {}, () => {}, (error, result) => {
if (error) return reject(error) if (error) return reject(error)
resolve(result) resolve(result)
}, (url, cb) => { }, (url, cb) => {
@ -166,13 +173,19 @@ module.exports = class TestTab extends ViewPlugin {
runTest (testFilePath, callback) { runTest (testFilePath, callback) {
this.loading.hidden = false this.loading.hidden = false
this.fileManager.getFile(testFilePath).then((content) => { this.fileManager.getFile(testFilePath).then((content) => {
var runningTest = {} const runningTest = {}
runningTest[testFilePath] = { content } runningTest[testFilePath] = { content }
let currentCompilerUrl = this.baseurl + '/' + this.compileTab.getCurrentVersion() const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = this.baseurl + '/' + currentVersion
const compilerConfig = {
currentCompilerUrl,
evmVersion,
optimize,
usingWorker: canUseWorker(currentVersion)
}
remixTests.runTestSources( remixTests.runTestSources(
runningTest, runningTest,
currentCompilerUrl, compilerConfig,
canUseWorker(this.compileTab.getCurrentVersion()),
(result) => this.testCallback(result), (result) => this.testCallback(result),
(_err, result, cb) => this.resultsCallback(_err, result, cb), (_err, result, cb) => this.resultsCallback(_err, result, cb),
(error, result) => { (error, result) => {

@ -37,7 +37,7 @@ class TestTabLogic {
} }
generateTestContractSample () { generateTestContractSample () {
return `pragma solidity >=0.4.0 <0.6.0; return `pragma solidity >=0.4.0 <0.7.0;
import "remix_tests.sol"; // this import is automatically injected by Remix. import "remix_tests.sol"; // this import is automatically injected by Remix.
// file name has to end with '_test.sol' // file name has to end with '_test.sol'

@ -3,8 +3,8 @@ import { EventEmitter } from 'events'
import * as packageJson from '../../../package.json' import * as packageJson from '../../../package.json'
const themes = [ const themes = [
{name: 'Dark', quality: 'dark', url: 'https://res.cloudinary.com/dvtmp0niu/raw/upload/v1574178106/remix-dark.css'}, {name: 'Dark', quality: 'dark', url: 'https://res.cloudinary.com/dvtmp0niu/raw/upload/v1578991867/remix-dark-theme.css'},
{name: 'Light', quality: 'light', url: 'https://res.cloudinary.com/dvtmp0niu/raw/upload/v1574252300/remix-light-theme.css'}, {name: 'Light', quality: 'light', url: 'https://res.cloudinary.com/dvtmp0niu/raw/upload/v1578991821/light-theme.css'},
// switching to the url Todo: remove when the theme is ready // switching to the url Todo: remove when the theme is ready
// {name: 'Dark', quality: 'dark', url: 'assets/css/remix-dark-theme.css'}, // {name: 'Dark', quality: 'dark', url: 'assets/css/remix-dark-theme.css'},

@ -196,7 +196,6 @@ export class LandingPage extends ViewPlugin {
<div class="${css.enviroments} pt-2"> <div class="${css.enviroments} pt-2">
<button class="btn btn-lg btn-secondary mr-3" onclick=${() => startSolidity()}>Solidity</button> <button class="btn btn-lg btn-secondary mr-3" onclick=${() => startSolidity()}>Solidity</button>
<button class="btn btn-lg btn-secondary mr-3" onclick=${() => startVyper()}>Vyper</button> <button class="btn btn-lg btn-secondary mr-3" onclick=${() => startVyper()}>Vyper</button>
<button class="btn btn-lg btn-secondary mr-3" onclick=${() => startWorkshop()}>Workshops</button>
</div> </div>
</div> </div>
<div class="file"> <div class="file">
@ -231,6 +230,7 @@ export class LandingPage extends ViewPlugin {
<h4>Featured Plugins</h4> <h4>Featured Plugins</h4>
<p class="mb-1 ${css.text}" onclick=${() => { startPipeline() }}>Pipeline</p> <p class="mb-1 ${css.text}" onclick=${() => { startPipeline() }}>Pipeline</p>
<p class="mb-1 ${css.text}" onclick=${() => { startDebugger() }}>Debugger</p> <p class="mb-1 ${css.text}" onclick=${() => { startDebugger() }}>Debugger</p>
<p class="mb-1 ${css.text}" onclick=${() => startWorkshop()}>Workshops</p>
<p class="mb-1"> <p class="mb-1">
<button onclick=${() => { startPluginManager() }} class="btn btn-sm btn-secondary ${css.seeAll}"> <button onclick=${() => { startPluginManager() }} class="btn btn-sm btn-secondary ${css.seeAll}">
See all Plugins See all Plugins

@ -113,7 +113,7 @@ class MultiParamManager {
} else if (this.funABI.name) { } else if (this.funABI.name) {
title = this.funABI.name title = this.funABI.name
} else { } else {
title = '(fallback)' title = this.funABI.type === 'receive' ? '(receive)' : '(fallback)'
} }
this.basicInputField = yo`<input></input>` this.basicInputField = yo`<input></input>`
@ -121,7 +121,7 @@ class MultiParamManager {
this.basicInputField.setAttribute('title', this.inputs) this.basicInputField.setAttribute('title', this.inputs)
this.basicInputField.setAttribute('style', 'flex: 4') this.basicInputField.setAttribute('style', 'flex: 4')
var onClick = (domEl) => { var onClick = () => {
this.clickCallBack(this.funABI.inputs, this.basicInputField.value) this.clickCallBack(this.funABI.inputs, this.basicInputField.value)
} }
let funcButton = yo`<button onclick=${() => onClick()} class="${css.instanceButton} btn btn-sm">${title}</button>` let funcButton = yo`<button onclick=${() => onClick()} class="${css.instanceButton} btn btn-sm">${title}</button>`
@ -204,8 +204,9 @@ class MultiParamManager {
if (this.funABI.inputs && this.funABI.inputs.length > 0) { if (this.funABI.inputs && this.funABI.inputs.length > 0) {
contractProperty.classList.add(css.hasArgs) contractProperty.classList.add(css.hasArgs)
} else if (this.funABI.type === 'fallback') { } else if (this.funABI.type === 'fallback' || this.funABI.type === 'receive') {
contractProperty.classList.add(css.hasArgs) contractProperty.classList.add(css.hasArgs)
this.basicInputField.setAttribute('title', `'(${this.funABI.type}')`) // probably should pass name instead
this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden' this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden'
} else { } else {
this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden' this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden'

@ -143,8 +143,8 @@ export class PermissionHandler {
? yo`<input type="checkbox" onchange="${switchMode}" checkbox class="custom-control-input" id="remember">` ? yo`<input type="checkbox" onchange="${switchMode}" checkbox class="custom-control-input" id="remember">`
: yo`<input type="checkbox" onchange="${switchMode}" class="custom-control-input" id="remember">` : yo`<input type="checkbox" onchange="${switchMode}" class="custom-control-input" id="remember">`
const message = remember const message = remember
? `${fromName} has changed and would like to access the plugin ${toName}.` ? `"${fromName}" has changed and would like to access "${toName}"`
: `${fromName} would like to access plugin ${toName}.` : `"${fromName}" would like to access "${toName}"`
return yo` return yo`
<section class="${css.permission}"> <section class="${css.permission}">

@ -0,0 +1,109 @@
const yo = require('yo-yo')
const remixLib = require('remix-lib')
const confirmDialog = require('./confirmDialog')
const modalCustom = require('./modal-dialog-custom')
const modalDialog = require('./modaldialog')
const typeConversion = remixLib.execution.typeConversion
const Web3 = require('web3')
module.exports = {
getCallBacksWithContext: (udappUI, executionContext) => {
let callbacks = {}
callbacks.confirmationCb = confirmationCb
callbacks.continueCb = continueCb
callbacks.promptCb = promptCb
callbacks.udappUI = udappUI
callbacks.executionContext = executionContext
return callbacks
}
}
const continueCb = function (error, continueTxExecution, cancelCb) {
if (error) {
const msg = typeof error !== 'string' ? error.message : error
modalDialog(
'Gas estimation failed',
yo`
<div>Gas estimation errored with the following message (see below).
The transaction execution will likely fail. Do you want to force sending? <br>${msg}</div>
`,
{
label: 'Send Transaction',
fn: () => continueTxExecution()
},
{
label: 'Cancel Transaction',
fn: () => cancelCb()
}
)
} else {
continueTxExecution()
}
}
const promptCb = function (okCb, cancelCb) {
modalCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
}
const confirmationCb = function (network, tx, gasEstimation, continueTxExecution, cancelCb) {
let self = this
if (network.name !== 'Main') {
return continueTxExecution(null)
}
var amount = Web3.utils.fromWei(typeConversion.toInt(tx.value), 'ether')
var content = confirmDialog(tx, amount, gasEstimation, self.udappUI,
(gasPrice, cb) => {
let txFeeText, priceStatus
// TODO: this try catch feels like an anti pattern, can/should be
// removed, but for now keeping the original logic
try {
var fee = Web3.utils.toBN(tx.gas).mul(Web3.utils.toBN(Web3.utils.toWei(gasPrice.toString(10), 'gwei')))
txFeeText = ' ' + Web3.utils.fromWei(fee.toString(10), 'ether') + ' Ether'
priceStatus = true
} catch (e) {
txFeeText = ' Please fix this issue before sending any transaction. ' + e.message
priceStatus = false
}
cb(txFeeText, priceStatus)
},
(cb) => {
self.executionContext.web3().eth.getGasPrice((error, gasPrice) => {
const warnMessage = ' Please fix this issue before sending any transaction. '
if (error) {
return cb('Unable to retrieve the current network gas price.' + warnMessage + error)
}
try {
var gasPriceValue = Web3.utils.fromWei(gasPrice.toString(10), 'gwei')
cb(null, gasPriceValue)
} catch (e) {
cb(warnMessage + e.message, null, false)
}
})
}
)
modalDialog(
'Confirm transaction',
content,
{ label: 'Confirm',
fn: () => {
self.udappUI.udapp.config.setUnpersistedProperty(
'doNotShowTransactionConfirmationAgain',
content.querySelector('input#confirmsetting').checked
)
// TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) {
cancelCb('Given gas price is not correct')
} else {
var gasPrice = Web3.utils.toWei(content.querySelector('#gasprice').value, 'gwei')
continueTxExecution(gasPrice)
}
}
},
{
label: 'Cancel',
fn: () => {
return cancelCb('Transaction canceled by user.')
}
}
)
}

@ -278,12 +278,12 @@ function renderEmptyBlock (self, data) {
} }
function checkTxStatus (tx, type) { function checkTxStatus (tx, type) {
if (tx.status === '0x1') { if (tx.status === '0x1' || tx.status === true) {
return yo`<i class="${css.txStatus} ${css.succeeded} fas fa-check-circle"></i>` return yo`<i class="${css.txStatus} ${css.succeeded} fas fa-check-circle"></i>`
} }
if (type === 'call' || type === 'unknownCall') { if (type === 'call' || type === 'unknownCall') {
return yo`<i class="${css.txStatus} ${css.call}">call</i>` return yo`<i class="${css.txStatus} ${css.call}">call</i>`
} else if (tx.status === '0x0') { } else if (tx.status === '0x0' || tx.status === false) {
return yo`<i class="${css.txStatus} ${css.failed} fas fa-times-circle"></i>` return yo`<i class="${css.txStatus} ${css.failed} fas fa-times-circle"></i>`
} else { } else {
return yo`<i class="${css.txStatus} ${css.notavailable} fas fa-circle-thin" title='Status not available' ></i>` return yo`<i class="${css.txStatus} ${css.notavailable} fas fa-circle-thin" title='Status not available' ></i>`
@ -387,10 +387,10 @@ function createTable (opts) {
var table = yo`<table class="${css.txTable}" id="txTable"></table>` var table = yo`<table class="${css.txTable}" id="txTable"></table>`
if (!opts.isCall) { if (!opts.isCall) {
var msg = '' var msg = ''
if (opts.status) { if (opts.status !== undefined && opts.status !== null) {
if (opts.status === '0x0') { if (opts.status === '0x0' || opts.status === false) {
msg = ' Transaction mined but execution failed' msg = ' Transaction mined but execution failed'
} else if (opts.status === '0x1') { } else if (opts.status === '0x1' || opts.status === true) {
msg = ' Transaction mined and execution succeed' msg = ' Transaction mined and execution succeed'
} }
} else { } else {

@ -1,27 +1,30 @@
/* global */ /* global */
'use strict' 'use strict'
const $ = require('jquery') var $ = require('jquery')
const yo = require('yo-yo') var yo = require('yo-yo')
const ethJSUtil = require('ethereumjs-util') var ethJSUtil = require('ethereumjs-util')
const BN = ethJSUtil.BN var BN = ethJSUtil.BN
const helper = require('../../lib/helper') var helper = require('../../lib/helper')
const copyToClipboard = require('./copy-to-clipboard') var copyToClipboard = require('./copy-to-clipboard')
const css = require('../../universal-dapp-styles') var css = require('../../universal-dapp-styles')
const MultiParamManager = require('./multiParamManager') var MultiParamManager = require('./multiParamManager')
const remixLib = require('remix-lib') var remixLib = require('remix-lib')
const txFormat = remixLib.execution.txFormat var txFormat = remixLib.execution.txFormat
const txHelper = remixLib.execution.txHelper const txHelper = remixLib.execution.txHelper
var TreeView = require('./TreeView')
var txCallBacks = require('./sendTxCallbacks')
const confirmDialog = require('./confirmDialog') function UniversalDAppUI (blockchain, logCallback) {
const modalCustom = require('./modal-dialog-custom') this.blockchain = blockchain
const modalDialog = require('./modaldialog') this.logCallback = logCallback
const TreeView = require('./TreeView') this.compilerData = {contractsDetails: {}}
}
function decodeResponseToTreeView (response, fnabi) { function decodeResponseToTreeView (response, fnabi) {
const treeView = new TreeView({ var treeView = new TreeView({
extractData: (item, parent, key) => { extractData: (item, parent, key) => {
let ret = {} var ret = {}
if (BN.isBN(item)) { if (BN.isBN(item)) {
ret.self = item.toString(10) ret.self = item.toString(10)
ret.children = [] ret.children = []
@ -34,42 +37,28 @@ function decodeResponseToTreeView (response, fnabi) {
return treeView.render(txFormat.decodeResponse(response, fnabi)) return treeView.render(txFormat.decodeResponse(response, fnabi))
} }
class UniversalDAppUI { UniversalDAppUI.prototype.renderInstance = function (contract, address, contractName) {
var noInstances = document.querySelector('[class^="noInstancesText"]')
constructor (blockchain, logCallback) { if (noInstances) {
this.blockchain = blockchain noInstances.parentNode.removeChild(noInstances)
this.logCallback = logCallback
this.compilerData = { contractsDetails: {} }
}
renderInstance (contract, address, contractName) {
const noInstances = document.querySelector('[class^="noInstancesText"]')
if (noInstances) {
noInstances.parentNode.removeChild(noInstances)
}
const abi = txHelper.sortAbiFunction(contract.abi)
return this.renderInstanceFromABI(abi, address, contractName)
} }
const abi = txHelper.sortAbiFunction(contract.abi)
return this.renderInstanceFromABI(abi, address, contractName)
}
// TODO this function was named before "appendChild". // TODO this function was named before "appendChild".
// this will render an instance: contract name, contract address, and all the public functions // this will render an instance: contract name, contract address, and all the public functions
// basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed // basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed
// this returns a DOM element // this returns a DOM element
renderInstanceFromABI (contractABI, address, contractName) { UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName) {
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex') let self = this
address = ethJSUtil.toChecksumAddress(address) address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex')
const instance = yo`<div class="instance ${css.instance} ${css.hidesub}" id="instance${address}"></div>` address = ethJSUtil.toChecksumAddress(address)
const context = (this.blockchain.getProvider() === 'vm' ? 'memory' : 'blockchain') var instance = yo`<div class="instance ${css.instance} ${css.hidesub}" id="instance${address}"></div>`
const context = this.blockchain.context()
function toggleClass (e) {
$(instance).toggleClass(`${css.hidesub}`) var shortAddress = helper.shortenAddress(address)
// e.currentTarget.querySelector('i') var title = yo`
e.currentTarget.querySelector('i').classList.toggle(`fa-angle-right`)
e.currentTarget.querySelector('i').classList.toggle(`fa-angle-down`)
}
const shortAddress = helper.shortenAddress(address)
const title = yo`
<div class="${css.title} alert alert-secondary p-2"> <div class="${css.title} alert alert-secondary p-2">
<button class="btn ${css.titleExpander}" onclick="${(e) => { toggleClass(e) }}"> <button class="btn ${css.titleExpander}" onclick="${(e) => { toggleClass(e) }}">
<i class="fas fa-angle-right" aria-hidden="true"></i> <i class="fas fa-angle-right" aria-hidden="true"></i>
@ -87,155 +76,174 @@ class UniversalDAppUI {
</div> </div>
` `
const close = yo` var close = yo`
<button <button
class="${css.udappClose} p-1 btn btn-secondary" class="${css.udappClose} p-1 btn btn-secondary"
onclick=${() => { instance.remove() }} onclick=${remove}
title="Remove from the list" title="Remove from the list"
> >
<i class="${css.closeIcon} fas fa-times" aria-hidden="true"></i> <i class="${css.closeIcon} fas fa-times" aria-hidden="true"></i>
</button>` </button>`
title.querySelector('.btn-group').appendChild(close) title.querySelector('.btn-group').appendChild(close)
const contractActionsWrapper = yo` var contractActionsWrapper = yo`
<div class="${css.cActionsWrapper}"> <div class="${css.cActionsWrapper}">
</div> </div>
` `
instance.appendChild(title) function remove () {
instance.appendChild(contractActionsWrapper) instance.remove()
// @TODO perhaps add a callack here to warn the caller that the instance has been removed
// Add the fallback function }
const fallback = txHelper.getFallbackInterface(contractABI)
if (fallback) {
contractActionsWrapper.appendChild(this.getCallButton({
funABI: fallback,
address: address,
contractAbi: contractABI,
contractName: contractName
}))
}
$.each(contractABI, (i, funABI) => { function toggleClass (e) {
if (funABI.type !== 'function') { $(instance).toggleClass(`${css.hidesub}`)
return // e.currentTarget.querySelector('i')
} e.currentTarget.querySelector('i').classList.toggle(`fa-angle-right`)
// @todo getData cannot be used with overloaded functions e.currentTarget.querySelector('i').classList.toggle(`fa-angle-down`)
contractActionsWrapper.appendChild(this.getCallButton({
funABI: funABI,
address: address,
contractAbi: contractABI,
contractName: contractName
}))
})
return instance
} }
getConfirmationCb (modalDialog, confirmDialog) { instance.appendChild(title)
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { instance.appendChild(contractActionsWrapper)
if (network.name !== 'Main') {
return continueTxExecution(null)
}
const amount = this.blockchain.fromWei(tx.value, true, 'ether')
const content = confirmDialog(tx, amount, gasEstimation, null, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice)
modalDialog('Confirm transaction', content,
{
label: 'Confirm',
fn: () => {
this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
// TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) {
cancelCb('Given gas price is not correct')
} else {
const gasPrice = this.blockchain.toWei(content.querySelector('#gasprice').value, 'gwei')
continueTxExecution(gasPrice)
}
}
}, {
label: 'Cancel',
fn: () => {
return cancelCb('Transaction canceled by user.')
}
}
)
}
return confirmationCb $.each(contractABI, (i, funABI) => {
} if (funABI.type !== 'function') {
return
}
// @todo getData cannot be used with overloaded functions
contractActionsWrapper.appendChild(this.getCallButton({
funABI: funABI,
address: address,
contractABI: contractABI,
contractName: contractName
}))
})
// TODO this is used by renderInstance when a new instance is displayed. const calldataInput = yo`
// this returns a DOM element. <input id="deployAndRunLLTxCalldata" class="w-100 m-0" title="The Calldata to send to fallback function of the contract.">
getCallButton (args) { `
// args.funABI, args.address [fun only] const llIError = yo`
// args.contractName [constr only] <label id="deployAndRunLLTxError" class="text-danger"></label>
const lookupOnly = args.funABI.stateMutability === 'view' || args.funABI.stateMutability === 'pure' || args.funABI.constant `
// constract LLInteractions elements
const lowLevelInteracions = yo`
<div class="d-flex flex-column">
<div class="d-flex flex-row justify-content-between mt-2">
<label class="pt-2 border-top d-flex justify-content-start flex-grow-1">
Low level interactions
</label>
<a
href="https://solidity.readthedocs.io/en/v0.6.2/contracts.html#receive-ether-function"
title="check out docs for using 'receive'/'fallback'"
target="_blank"
>
<i aria-hidden="true" class="fas fa-info text-info my-2 mr-2"></i>
</a>
</div>
<div class="d-flex flex-column">
<div class="d-flex justify-content-end m-2 align-items-center">
<label class="mr-2 m-0">Calldata</label>
${calldataInput}
<button id="deployAndRunLLTxSendTransaction" class="btn btn-sm btn-secondary" title="Send data to contract." onclick=${() => sendData()}>Transact</button>
</div>
</div>
<div>
${llIError}
</div>
</div>
`
const outputOverride = yo`<div class=${css.value}></div>` // show return value function sendData () {
function setLLIError (text) {
llIError.innerText = text
}
const clickButton = (valArr, inputsValues) => { setLLIError('')
let logMsg const fallback = txHelper.getFallbackInterface(contractABI)
if (!lookupOnly) { const receive = txHelper.getReceiveInterface(contractABI)
logMsg = `call to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}` const args = {
} else { funABI: fallback || receive,
logMsg = `transact to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}` address: address,
contractName: contractName,
contractABI: contractABI
}
const amount = document.querySelector('#value').value
if (amount !== '0') {
// check for numeric and receive/fallback
if (!helper.isNumeric(amount)) {
return setLLIError('Value to send should be a number')
} else if (!receive && !(fallback && fallback.stateMutability === 'payable')) {
return setLLIError("In order to receive Ether transfer the contract should have either 'receive' or payable 'fallback' function")
} }
}
const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog) let calldata = calldataInput.value
const continueCb = (error, continueTxExecution, cancelCb) => { if (calldata) {
if (error) { if (calldata.length < 2 || calldata.length < 4 && helper.is0XPrefixed(calldata)) {
const msg = typeof error !== 'string' ? error.message : error return setLLIError('The calldata should be a valid hexadecimal value with size of at least one byte.')
modalDialog( } else {
'Gas estimation failed', if (helper.is0XPrefixed(calldata)) {
yo` calldata = calldata.substr(2, calldata.length)
<div>Gas estimation errored with the following message (see below). }
The transaction execution will likely fail. Do you want to force sending? <br>${msg}</div> if (!helper.isHexadecimal(calldata)) {
`, return setLLIError('The calldata should be a valid hexadecimal value.')
{
label: 'Send Transaction',
fn: () => continueTxExecution()
},
{
label: 'Cancel Transaction',
fn: () => cancelCb()
}
)
} else {
continueTxExecution()
} }
} }
if (!fallback) {
const outputCb = (returnValue) => { return setLLIError("'Fallback' function is not defined")
const decoded = decodeResponseToTreeView(returnValue, args.funABI)
outputOverride.innerHTML = ''
outputOverride.appendChild(decoded)
} }
}
const promptCb = (okCb, cancelCb) => { if (!receive && !fallback) return setLLIError(`Both 'receive' and 'fallback' functions are not defined`)
modalCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
}
const callType = args.funABI.type !== 'fallback' ? inputsValues : '' // we have to put the right function ABI:
this.blockchain.runOrCallContractMethod(args.contractName, args.contractAbi, args.funABI, inputsValues, args.address, callType, lookupOnly, logMsg, this.logCallback, outputCb, confirmationCb, continueCb, promptCb) // if receive is defined and that there is no calldata => receive function is called
} // if fallback is defined => fallback function is called
if (receive && !calldata) args.funABI = receive
else if (fallback) args.funABI = fallback
let inputs = '' if (!args.funABI) return setLLIError(`Please define a 'Fallback' function to send calldata and a either 'Receive' or payable 'Fallback' to send ethers`)
if (args.funABI.inputs) { self.runTransaction(false, args, null, calldataInput.value, null)
inputs = txHelper.inputParametersDeclarationToString(args.funABI.inputs) }
}
const multiParamManager = new MultiParamManager(lookupOnly, args.funABI, (valArray, inputsValues, domEl) => { contractActionsWrapper.appendChild(lowLevelInteracions)
clickButton(valArray, inputsValues, domEl) return instance
}, inputs) }
const contractActionsContainer = yo`<div class="${css.contractActionsContainer}" >${multiParamManager.render()}</div>` // TODO this is used by renderInstance when a new instance is displayed.
contractActionsContainer.appendChild(outputOverride) // this returns a DOM element.
UniversalDAppUI.prototype.getCallButton = function (args) {
let self = this
var outputOverride = yo`<div class=${css.value}></div>` // show return value
const isConstant = args.funABI.constant !== undefined ? args.funABI.constant : false
const lookupOnly = args.funABI.stateMutability === 'view' || args.funABI.stateMutability === 'pure' || isConstant
const multiParamManager = new MultiParamManager(
lookupOnly,
args.funABI,
(valArray, inputsValues) => self.runTransaction(lookupOnly, args, valArray, inputsValues, outputOverride),
self.blockchain.getInputs(args.funABI)
)
const contractActionsContainer = yo`<div class="${css.contractActionsContainer}" >${multiParamManager.render()}</div>`
contractActionsContainer.appendChild(outputOverride)
return contractActionsContainer
}
return contractActionsContainer UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, inputsValues, outputOverride) {
} const functionName = args.funABI.type === 'function' ? args.funABI.name : `(${args.funABI.type})`
const logMsg = `${lookupOnly ? 'call' : 'transact'} to ${args.contractName}.${functionName}`
const callbacksInContext = txCallBacks.getCallBacksWithContext(this, this.executionContext)
const outputCb = (returnValue) => {
if (outputOverride) {
const decoded = decodeResponseToTreeView(returnValue, args.funABI)
outputOverride.innerHTML = ''
outputOverride.appendChild(decoded)
}
}
const params = args.funABI.type !== 'fallback' ? inputsValues : ''
this.blockchain.runOrCallContractMethod(args.contractName, args.contractAbi, args.funABI, inputsValues, args.address, params, lookupOnly, logMsg, this.logCallback, outputCb, callbacksInContext)
} }
module.exports = UniversalDAppUI module.exports = UniversalDAppUI

@ -2,8 +2,9 @@ const remixLib = require('remix-lib')
const txFormat = remixLib.execution.txFormat const txFormat = remixLib.execution.txFormat
const txExecution = remixLib.execution.txExecution const txExecution = remixLib.execution.txExecution
const typeConversion = remixLib.execution.typeConversion const typeConversion = remixLib.execution.typeConversion
const TxRunner = remixLib.execution.txRunner
const Txlistener = remixLib.execution.txListener const Txlistener = remixLib.execution.txListener
const TxRunner = remixLib.execution.txRunner
const txHelper = remixLib.execution.txHelper
const EventManager = remixLib.EventManager const EventManager = remixLib.EventManager
const executionContext = remixLib.execution.executionContext const executionContext = remixLib.execution.executionContext
const ethJSUtil = require('ethereumjs-util') const ethJSUtil = require('ethereumjs-util')
@ -56,6 +57,18 @@ class Blockchain {
this.executionContext.event.register('removeProvider', (name) => { this.executionContext.event.register('removeProvider', (name) => {
this.event.trigger('removeProvider', [name]) this.event.trigger('removeProvider', [name])
}) })
// this.udapp.event.register('initiatingTransaction', (timestamp, tx, payLoad) => {
// this.event.trigger('initiatingTransaction', [timestamp, tx, payLoad])
// })
// this.udapp.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp) => {
// this.event.trigger('transactionExecuted', [error, from, to, data, call, txResult, timestamp])
// })
// this.udapp.event.register('transactionBroadcasted', (txhash, networkName) => {
// this.event.trigger('transactionBroadcasted', [txhash, networkName])
// })
} }
setupProviders () { setupProviders () {
@ -146,6 +159,13 @@ class Blockchain {
}) })
} }
getInputs (funABI) {
if (!funABI.inputs) {
return ''
}
return txHelper.inputParametersDeclarationToString(funABI.inputs)
}
fromWei (value, doTypeConversion, unit) { fromWei (value, doTypeConversion, unit) {
if (doTypeConversion) { if (doTypeConversion) {
return Web3.utils.fromWei(typeConversion.toInt(value), unit || 'ether') return Web3.utils.fromWei(typeConversion.toInt(value), unit || 'ether')
@ -230,6 +250,7 @@ class Blockchain {
getTxListener (opts) { getTxListener (opts) {
opts.event = { opts.event = {
// udapp: this.udapp.event
udapp: this.event udapp: this.event
} }
const txlistener = new Txlistener(opts, this.executionContext) const txlistener = new Txlistener(opts, this.executionContext)
@ -268,6 +289,10 @@ class Blockchain {
}) })
} }
context () {
return (this.executionContext.isVM() ? 'memory' : 'blockchain')
}
// NOTE: the config is only needed because exectuionContext.init does // NOTE: the config is only needed because exectuionContext.init does
// if config.get('settings/always-use-vm'), we can simplify this later // if config.get('settings/always-use-vm'), we can simplify this later
resetAndInit (config, transactionContextAPI) { resetAndInit (config, transactionContextAPI) {

@ -44,6 +44,15 @@ module.exports = {
checkSpecialChars (name) { checkSpecialChars (name) {
return name.match(/[:*?"<>\\'|]/) != null return name.match(/[:*?"<>\\'|]/) != null
}, },
isHexadecimal (value) {
return /^[0-9a-fA-F]+$/.test(value) && (value.length % 2 === 0)
},
is0XPrefixed (value) {
return value.substr(0, 2) === '0x'
},
isNumeric (value) {
return /^\+?(0|[1-9]\d*)$/.test(value)
},
find: find find: find
} }

@ -27,11 +27,21 @@ module.exports = (contract, fileManager, cb, ipfsVerifiedPublishCallBack) => {
async.eachSeries(Object.keys(metadata.sources), function (fileName, cb) { async.eachSeries(Object.keys(metadata.sources), function (fileName, cb) {
// find hash // find hash
var hash let hash = null
try { try {
hash = metadata.sources[fileName].urls[1].match('dweb:/ipfs/(.+)')[1] // we try extract the hash defined in the metadata.json
// in order to check if the hash that we get after publishing is the same as the one located in metadata.json
// if it's not the same, we throw "hash mismatch between solidity bytecode and uploaded content"
// if we don't find the hash in the metadata.json, the check is not done.
//
// TODO: refactor this with publishOnSwarm
if (metadata.sources[fileName].urls) {
metadata.sources[fileName].urls.forEach(url => {
if (url.includes('ipfs')) hash = url.match('dweb:/ipfs/(.+)')[1]
})
}
} catch (e) { } catch (e) {
return cb('Metadata inconsistency') return cb('Error while extracting the hash from metadata.json')
} }
fileManager.fileProviderOf(fileName).get(fileName, (error, content) => { fileManager.fileProviderOf(fileName).get(fileName, (error, content) => {
@ -94,7 +104,7 @@ module.exports = (contract, fileManager, cb, ipfsVerifiedPublishCallBack) => {
async function ipfsVerifiedPublish (content, expectedHash, cb) { async function ipfsVerifiedPublish (content, expectedHash, cb) {
try { try {
const results = await severalGatewaysPush(content) const results = await severalGatewaysPush(content)
if (results !== expectedHash) { if (expectedHash && results !== expectedHash) {
cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + results, hash: results }) cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + results, hash: results })
} else { } else {
cb(null, { message: 'ok', url: 'dweb:/ipfs/' + results, hash: results }) cb(null, { message: 'ok', url: 'dweb:/ipfs/' + results, hash: results })

@ -20,11 +20,21 @@ module.exports = (contract, fileManager, cb, swarmVerifiedPublishCallBack) => {
async.eachSeries(Object.keys(metadata.sources), function (fileName, cb) { async.eachSeries(Object.keys(metadata.sources), function (fileName, cb) {
// find hash // find hash
var hash let hash = null
try { try {
hash = metadata.sources[fileName].urls[0].match('(bzzr|bzz-raw)://(.+)')[1] // we try extract the hash defined in the metadata.json
// in order to check if the hash that we get after publishing is the same as the one located in metadata.json
// if it's not the same, we throw "hash mismatch between solidity bytecode and uploaded content"
// if we don't find the hash in the metadata.json, the check is not done.
//
// TODO: refactor this with publishOnIpfs
if (metadata.sources[fileName].urls) {
metadata.sources[fileName].urls.forEach(url => {
if (url.includes('bzz')) hash = url.match('(bzzr|bzz-raw)://(.+)')[1]
})
}
} catch (e) { } catch (e) {
return cb('Metadata inconsistency') return cb('Error while extracting the hash from metadata.json')
} }
fileManager.fileProviderOf(fileName).get(fileName, (error, content) => { fileManager.fileProviderOf(fileName).get(fileName, (error, content) => {
@ -90,7 +100,7 @@ function swarmVerifiedPublish (content, expectedHash, cb) {
swarmgw.put(content, function (err, ret) { swarmgw.put(content, function (err, ret) {
if (err) { if (err) {
cb(err) cb(err)
} else if (ret !== expectedHash) { } else if (expectedHash && ret !== expectedHash) {
cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + ret, hash: ret }) cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + ret, hash: ret })
} else { } else {
cb(null, { message: 'ok', url: 'bzz-raw://' + ret, hash: ret }) cb(null, { message: 'ok', url: 'bzz-raw://' + ret, hash: ret })

@ -2,6 +2,7 @@
import { PluginEngine, IframePlugin } from '@remixproject/engine' import { PluginEngine, IframePlugin } from '@remixproject/engine'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { PermissionHandler } from './app/ui/persmission-handler' import { PermissionHandler } from './app/ui/persmission-handler'
import QueryParams from './lib/query-params'
const requiredModules = [ // services + layout views + system views const requiredModules = [ // services + layout views + system views
'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'fileManager', 'contentImport', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'fileManager', 'contentImport',
@ -19,15 +20,13 @@ export class RemixAppManager extends PluginEngine {
constructor (plugins) { constructor (plugins) {
super(plugins, settings) super(plugins, settings)
this.event = new EventEmitter() this.event = new EventEmitter()
this.donotAutoReload = ['remixd'] // that would be a bad practice to force loading some plugins at page load.
this.registered = {} this.registered = {}
this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/profile.json' this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json'
this.pluginLoader = new PluginLoader()
} }
onActivated (plugin) { onActivated (plugin) {
if (!this.donotAutoReload.includes(plugin.name)) { this.pluginLoader.set(plugin, this.actives)
localStorage.setItem('workspace', JSON.stringify(this.actives))
}
this.event.emit('activate', plugin.name) this.event.emit('activate', plugin.name)
} }
@ -46,7 +45,7 @@ export class RemixAppManager extends PluginEngine {
} }
onDeactivated (plugin) { onDeactivated (plugin) {
localStorage.setItem('workspace', JSON.stringify(this.actives)) this.pluginLoader.set(plugin, this.actives)
this.event.emit('deactivate', plugin.name) this.event.emit('deactivate', plugin.name)
} }
@ -78,157 +77,51 @@ export class RemixAppManager extends PluginEngine {
} }
async registeredPlugins () { async registeredPlugins () {
const pipeline = { const res = await fetch(this.pluginsDirectory)
name: 'pipeline', const plugins = await res.json()
displayName: 'Pipeline', return plugins.map(plugin => new IframePlugin(plugin))
events: [], }
methods: [], }
notifications: {
'solidity': ['compilationFinished'] /** @class Reference loaders.
}, * A loader is a get,set based object which load a workspace from a defined sources.
url: 'https://pipeline-alpha.pipeos.one', * (localStorage, queryParams)
description: 'Visual IDE for contracts and dapps', **/
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAAAAACIM/FCAAAJU0lEQVR42u3ce1QUVRgA8EEemgoWvjkltIZlIJhCmmTmqVBJJSofaaWpYUUn08wHmkfR6GFWqKh51CDzLZkPKs2MNAkFVAgxw007WZklaWqsLutt0b13LusM7LDfN+x07vcXe3dmvvkxszN35rszEvmfhCQgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgHgspXbcsdVoybKQsXrPfqivk0qoBJqSImV+uG6Tyq34mxOi+7oI+EMt4E3L0P60H5PRjJvSIOYwPsejgsO9ev6FDXjXpEgMvIUM2mXSKibgQWz+9IJ3OoEJWcbtx+t4yM2gcK8jk/k9TUSHyefClUjNClM0NpQnCLiJCfmCOJDNSLGQpNiJCVgQ1b+YfcFPrdt1KsSDmwSFtAgP8AwLbjMeCVGYOu1FyRPvEfBzG4WlRXo4cfvGv/4sByYmQ+Gg8/hiC4/1W1ZIEZbgNWb62+ueKV/0kp+i2FZqR19/LOcnQ392EJEpdc7iP1u7S9XHDZlhH4a0KSW7+xV2IJD28hW2PxyWlaLEF0pEbrpgk6qTbEEnq8uW1TyMl5QiE/MmHqyTpesl9iH2rVB3NCyS1GAHnmKua5C0IiH2rZFdGqOaQ1kM58puo5vA/DQKxb1v5zzZPb8zb+XpnuSEGCjJWXqZpXPa+7EkmuWEyEESO4Y4z1AYf1vQpEOQGtsQZtmsd7VT5nHW+zpATCUqO/uxnt9qXts2EcexiSeRe71TWllU3iC2zZwMlxy1cjyGZNibAQF6jy+vDrUgf2jivTpC9d6n86DrlyROdpo0dYSCD6PKWc2vyFW0cXQdI5cvqhygp7JMrdLrmjqYmMJBuNMXxaqvniPvrABki1Rg9yhzTRdIW4LOhVQkSpR0ytWZH30I6IT06+sJA2O5crgS5RzNkd4OaGJE75SnphK1hIA/SHFwK8hdtHKgVUtG+Jke0TZ5yK228DwbyAl3eFG51ZtLGaVohu2vesbayCS+E0rZxMJBldHlNyliSMtZr+UArZFTNEP9sx3R/RrG2dBhIqXwBUuRIcvRm1nZEI8TWku2UwxUlvslV/bfKTWx7SAElQF2ULmyRzdOrTryW9ObyoVJrX+t7OmfTP7i+Vv+PmsofmsSOGsxfyE2E6jRm8pc5A0cP5C/et2uFfEznfEruNEblEpKiurO1OwIFkY9b10W85uuR2XI/wQF5ZFvVkcpiUsuxEsxhzm2kkqNxkWbIeDrvrmuQe3IdXxxQueoZYwaM91Qgq4lmyHN03n1VkDi5Y0W+aKiUYuiPkBBzip9SkneIdgj7YRSQxN77qp9hWihsD1iH/bLd97ocfiuIe5ATzlOefNT5d77EDB6bna8guhUSNyEK8W0MlyHgtSNmhDi2gD+uBK+/QhAghOQ/3NDXS5K8/Rr3KjEjxY+jmvrZk3j5+EVuryQEB0J2seLFKizInjtpivmEoEEudqJZInbjOArj2f+qBBFCZrI0HaYj1HrKFnRmCR4nmJDjoXK1MmxQ0ouw8WQXrqz7GSqEvK1XeTqe4EJ+7ayPo30uMoQcDtMFsplgQ0iWHo6ZBB9CcqKwGbevrNQDQn7ui+u4O4cQXSDk8sYBeIyeS88RvSD2KJ7RB0UxYWclIXpCqvorP31fDBtlZ0ldwx2IR8X/FMIKkvsNDmE1nhyDQ9jd7wyDQz6kkLEGhxSxO7x/GRtibUYlE/4xNISrj7Sds99QFifIF5IHh1ejkIcmbbjkEsQS7MkSx83HPBcgZI3nQySv0Rdqh5DpBpBIwUdqh9juMoKk9alaIeTcy0aQ3Hu5Vgghac0MIHnDBQgp/6hHA0+HND7uAsQeJz4eFx8T1dWjIiKEL80tdA3imWH9ZoI3hfQxMsQeKynE94KxIYSNX8oxOGQyhSwxOGQ9hUw3OGQPhbxkcEg+hSQaHFIgIMpR/u2m1bCxZsv+8zpDbCULHkKpKIQmrPxJR0jpMMwqT9IpvSAZobj1Kn6UNCLk7BD8SugUKz7E9rweNd0UfEiSLkV202JsyAZ9HKbQIlxIRS+dIKancCEZXKrBs9Lmg0Zachy3+AJMiLUnyzPmM4xRZxvk8v2zmJA9LM0InOFz5h9iaYbbziBCUoPbtmzRsm2wqfchrBGNOeGmdm1atmgVFJKFBvklNYRe9yfsxYJ8Hk2fOe245CwK5NAr/Bj8hmOyMRirHvHmkgTOOQkPyfBxutPn/TY442iSt1OSZruBIValR5YGHYV1HIi5PofPHFCI7QnF268DQN+4UxSmmGSu88rk0W9e0A6ZpHIjeSwkpLdyDi+nt4HIZc6JmiE7VW+JAz7Pk6qWw6es+trMo1/M0wqxdFWFhIM9sZCnXpMZWX11Yml7llbIfG6hvVIWpfTiPs+AgnAPp3jHv7VoaiSXZC+/Nj+z5t+0QjqwWSOvFYbz5DLjrUCv3cmXKx+xV28/XMkKYi1DuZUp6UhbOxCNkINsgXG0Vm+JY21Ab3hho3nk91T8fTvbRAs/uBoLU0fKD3JKs7RC1tI5g+QHtMvZvysNBvIsXd598tDMQz411t52aIUk0TnXKumehIF0psv7zoWj/tWIJ1oh/eisxVxjMfv1w0DoY7l+/OvaanqUu+kxzZBoOm8F11hBGyNgILSIHKbYGVGIbaTuEAt/bqGNnWAg9LVa4S5C3iTaIX3pzPwjTyXAu1agY3GNXNq1AneQOkA878c+Ruk9p7VDWD24A3f4ZSfJd2Egz7BzlXz4LQ5QQPjHpim/eFbTCdFSvyfEpZnZJTa11awdcll+5rS3o4vCjshSO6CLqzy5izLE0UVhBxkpwZVLDa2dxlmLZunQaUxX7zS6Afn3TtWj4G2HoSC71Lvxrj1YabwLKzcgKm8/s/evzYARrXKpu5jAQWzD9Lj5EO7azQd3IMQap5DifuAXJRy4Q2G/SiKgEE+6QecmhBwcUO2W6QPbzAjxYTRPCXjmJIGH1MNNbE2PI2oqK6TQssKDR7Eg+ayssI4QNIhHFHogINZoJkk8iOHYI5feniaIEI8ohoJAKmL0Kk8PJ6gQ/QYMFCJDPGAIBxDk4mg9HBMJOqT+hzlBQcjl97AHnq0hRA9IfQ8FBIQQ29fJWIMzZxcRoh+kKuptuCw0xNNCQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQATEY+M/DV/rjLxphEwAAAAASUVORK5CYII=', class PluginLoader {
location: 'mainPanel' get currentLoader () {
} return this.loaders[this.current]
const provable = { }
name: 'provable',
displayName: 'Provable - oracle service', constructor () {
events: [], const queryParams = new QueryParams()
methods: [], this.donotAutoReload = ['remixd'] // that would be a bad practice to force loading some plugins at page load.
notifications: { this.loaders = {}
'udapp': ['newTransaction'], this.loaders['localStorage'] = {
'network': ['providerChanged'] set: (plugin, actives) => {
}, if (!this.donotAutoReload.includes(plugin.name)) {
url: 'https://remix-plugin.provable.xyz', localStorage.setItem('workspace', JSON.stringify(actives))
documentation: 'https://docs.oraclize.it/#development-tools-remix-ide-provable-plugin', }
description: 'request real-world data for your contracts',
icon: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNTcuNTMgMTU5Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTtzdHJva2U6I2IzYjNiMztzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2Utd2lkdGg6OHB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+bG9nby1vdXRibGFjay1pbm5lcmdyYXk8L3RpdGxlPjxnIGlkPSJmZzEiPjxwYXRoIGQ9Ik0xNjkuMjksNjZDMTU5LjM3LDQ1LjQ5LDE0MiwyOS4xMywxMTkuNzYsMjMuMTVBNzkuMDgsNzkuMDgsMCwwLDAsNDgsMzkuNTVjLTMuNjgsMy4xMywxLjY1LDguNDIsNS4zLDUuMzFhNzEuMjYsNzEuMjYsMCwwLDEsNjUuNzgtMTQuMTFjMTkuOTIsNS43NywzNC44MywyMC42Niw0My43MiwzOSwyLjEsNC4zNSw4LjU3LjU1LDYuNDgtMy43OFoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMS42NSAtMjAuNDgpIi8+PHBhdGggZD0iTTEyNiwxNzAuNDJjMTUuNzQtNi4xOSwyOS41Ny0xNi4zMSwzOC42OC0zMC43NCw5LjY5LTE1LjMyLDEzLjA3LTM0LjE0LDEwLjc5LTUyLS42LTQuNzItOC4xMS00Ljc4LTcuNSwwLDIuMDcsMTYuMjEtLjU0LDMzLTkuMDgsNDcuMTEtOCwxMy4yMS0yMC42MSwyMi43OC0zNC44OCwyOC40LTQuNDQsMS43NC0yLjUxLDksMiw3LjIzWiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTIxLjY1IC0yMC40OCkiLz48cGF0aCBkPSJNMzQuMDgsNTUuNThjLTE1Ljc5LDI1LjQxLTE3LDU3LjQ0LTEsODMuMThBNzguMiw3OC4yLDAsMCwwLDEwMiwxNzUuNDFjNC44MS0uMTYsNC44NC03LjY2LDAtNy41LTI1LjE0LjgtNDkuMTItMTEuNDMtNjIuNDYtMzIuOTMtMTQuNjEtMjMuNTQtMTMuNDMtNTIuMzksMS03NS42MSwyLjU2LTQuMTEtMy45My03Ljg4LTYuNDctMy43OVoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMS42NSAtMjAuNDgpIi8+PHBhdGggZD0iTTk4Ljg0LDQwLjg2YTU4LDU4LDAsMCwwLTQ5LDI3Ljc4Yy0xMi4xNiwyMC0xMC44NCw0My44OSwzLjM2LDYyLjQ1LDIuODksMy43OCw5LjQxLDAsNi40OC0zLjc5LTYuNTMtOC41Mi0xMS4yMS0xOC40OC0xMS0yOS40MkE1Mi4xMSw1Mi4xMSwwLDAsMSw1Ni4zLDcyLjQyLDUwLjI4LDUwLjI4LDAsMCwxLDk4Ljg0LDQ4LjM2YzQuODMtLjA2LDQuODQtNy41NiwwLTcuNVoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMS42NSAtMjAuNDgpIi8+PHBhdGggZD0iTTcxLjIyLDE0OC4wNWMyMSwxMi4wOSw0OC4zMyw4LjQyLDY2LjIxLTcuODFBNTcuMjksNTcuMjksMCwwLDAsMTI0LjUsNDYuNzdjLTQuMjktMi4xOS04LjA5LDQuMjctMy43OSw2LjQ4LDI0LjYzLDEyLjYzLDM1LjM3LDQzLjMxLDIyLDY4LjEtMTIuOCwyMy44My00NCwzMy44Ni02Ny43NSwyMC4yMy00LjItMi40MS04LDQuMDctMy43OSw2LjQ3WiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTIxLjY1IC0yMC40OCkiLz48Y2lyY2xlIGNsYXNzPSJjbHMtMSIgY3g9Ijc3LjE5IiBjeT0iNzcuNDgiIHI9IjMzIi8+PGNpcmNsZSBjeD0iMjkuNTQiIGN5PSIyMS43NSIgcj0iNy43NSIvPjxjaXJjbGUgY3g9IjgwLjU0IiBjeT0iMTUxLjI1IiByPSI3Ljc1Ii8+PGNpcmNsZSBjeD0iMTQ5Ljc4IiBjeT0iNjcuMjUiIHI9IjcuNzUiLz48Y2lyY2xlIGN4PSIxMDAuNzkiIGN5PSIyOS41IiByPSI3Ljc1Ii8+PGNpcmNsZSBjeD0iMzQuNzkiIGN5PSIxMTAuNSIgcj0iNy43NSIvPjwvZz48L3N2Zz4=',
location: 'sidePanel'
}
const threeBox = {
name: 'box',
displayName: '3Box Spaces',
description: 'A decentralized storage for everything that happen on Remix',
methods: ['login', 'isEnabled', 'getUserAddress', 'openSpace', 'closeSpace', 'isSpaceOpened', 'getSpacePrivateValue', 'setSpacePrivateValue', 'getSpacePublicValue', 'setSpacePublicValue', 'getSpacePublicData'],
events: [],
version: '0.1.0-beta',
url: 'https://remix-3box.surge.sh',
icon: 'https://raw.githubusercontent.com/3box/3box-dapp/master/public/3Box3.png',
location: 'sidePanel'
}
const debugPlugin = {
name: 'debugPlugin',
displayName: 'Debug Tools for Remix plugins',
description: 'Easily test and debug your plugins !',
methods: ['sayHello', 'sayMyName', 'sayOurNames'], // test calls with 0, 1, and 2 args
events: [],
version: '0.1.0-alpha',
url: 'https://remix-debug-a.surge.sh',
icon: 'https://remix-debug-a.surge.sh/icon.png',
location: 'sidePanel'
}
const libraTools = {
name: 'libratools',
displayName: 'Libra and Move Tools',
events: [],
methods: [],
url: 'https://libra.pipeos.one',
description: 'Create, compile, deploy and interact with Libra modules and scripts',
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiCiAgIHhtbG5zOmlua3NjYXBlPSJodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIgogICB3aWR0aD0iMTAyNCIKICAgaGVpZ2h0PSIxMDI0IgogICB2aWV3Qm94PSIwIDAgMjcwLjkzMzMzIDI3MC45MzMzMyIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0ic3ZnOCIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45Mi4yIDVjM2U4MGQsIDIwMTctMDgtMDYiCiAgIHNvZGlwb2RpOmRvY25hbWU9ImxpYnJhLnN2ZyI+CiAgPGRlZnMKICAgICBpZD0iZGVmczIiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxLjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjAuNzczNDM3NSIKICAgICBpbmtzY2FwZTpjeD0iNTEyIgogICAgIGlua3NjYXBlOmN5PSI1MTIiCiAgICAgaW5rc2NhcGU6ZG9jdW1lbnQtdW5pdHM9InB4IgogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9ImxheWVyMSIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgdW5pdHM9InB4IgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTY1MiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDA1IgogICAgIGlua3NjYXBlOndpbmRvdy14PSIwIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIxIgogICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjEiIC8+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNSI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgICA8ZGM6dGl0bGU+PC9kYzp0aXRsZT4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMjYuMDY2NzEpIj4KICAgIDxnCiAgICAgICBpZD0iZzk3MCIKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAuODQzMTI1MjMsMCwwLDAuODQzMTI1MjMsMjEuMjUxMzAxLDI1LjM0MDUxMykiPgogICAgICA8cGF0aAogICAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzc2MiCiAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgIGlkPSJwYXRoOTQ3IgogICAgICAgICBkPSJtIDQwLjc4NjQ0MywxMDcuNzM2MTcgYyAwLDAgMTkuMzI4MzA0LC0zNi44NDk4MzEgNDguOTMwMzg1LC0zNi44NDk4MzEgMjkuNjAyMDgyLDAgNjIuODI4ODkyLDI5LjYwMjA3MSA5Mi40MzA5ODIsMjkuNjAyMDcxIDI5LjYwMjA4LDAgNDcuNzI1NzksLTM4LjY2MzkzNSA0Ny43MjU3OSwtMzguNjYzOTM1IgogICAgICAgICBzdHlsZT0ib3BhY2l0eToxO2ZpbGw6bm9uZTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MjcuNDg3NjQyMjk7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLW9wYWNpdHk6MSIgLz4KICAgICAgPHBhdGgKICAgICAgICAgc3R5bGU9Im9wYWNpdHk6MTtmaWxsOm5vbmU7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI3LjQ4NzY0MjI5O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICAgIGQ9Im0gNDAuNzg2NDQzLDE4NC4xNTc4NSBjIDAsMCAxOS4zMjgzMDQsLTM2Ljg0OTgyIDQ4LjkzMDM4NywtMzYuODQ5ODIgMjkuNjAyMDgsMCA2Mi44Mjg5LDI5LjYwMjA3IDkyLjQzMDk4LDI5LjYwMjA3IDI5LjYwMjA4LDAgNDcuNzI1NzksLTM4LjY2Mzk0IDQ3LjcyNTc5LC0zOC42NjM5NCIKICAgICAgICAgaWQ9InVzZTk1MSIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjc3NjIiAvPgogICAgICA8cGF0aAogICAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzc2MiCiAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgIGlkPSJ1c2U5NTUiCiAgICAgICAgIGQ9Im0gNDAuNzg2NDQzLDI2MC41Nzk1NSBjIDAsMCAxOS4zMjgzMDYsLTM2Ljg0OTgzIDQ4LjkzMDM4NywtMzYuODQ5ODMgMjkuNjAyMDgsMCA2Mi44Mjg5LDI5LjYwMjA3IDkyLjQzMDk4LDI5LjYwMjA3IDI5LjYwMjA4LDAgNDcuNzI1NzksLTM4LjY2MzkzIDQ3LjcyNTc5LC0zOC42NjM5MyIKICAgICAgICAgc3R5bGU9Im9wYWNpdHk6MTtmaWxsOm5vbmU7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI3LjQ4NzY0MjI5O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiIC8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K',
location: 'sidePanel'
}
const oneClickDapp = {
name: 'oneClickDapp',
displayName: 'One Click Dapp',
events: [],
methods: [],
version: '0.1.0',
notifications: {
solidity: ['compilationFinished']
},
url: 'https://remix-one-click-dapp.surge.sh',
description: 'A free tool to generate smart contract interfaces.',
documentation: 'https://github.com/pi0neerpat/remix-plugin-one-click-dapp',
icon: 'https://remix-one-click-dapp.surge.sh/icon.png',
location: 'sidePanel'
}
const gasProfiler = {
name: 'gasProfiler',
displayName: 'Gas Profiler',
events: [],
methods: [],
version: '0.1.0-alpha',
url: 'https://remix-gas-profiler.surge.sh',
description: 'Profile gas costs',
icon: 'https://res.cloudinary.com/key-solutions/image/upload/v1565781702/gas-profiler_nxmsal.png',
location: 'sidePanel'
}
const flattener = {
name: 'flattener',
displayName: 'Flattener',
events: [],
methods: [],
version: '0.1.0',
url: 'https://remix-flattener.netlify.com',
description: 'Flattens compiled smart contracts',
documentation: 'https://github.com/Destiner/remix-flattener',
icon: 'https://remix-flattener.netlify.com/logo.svg',
location: 'sidePanel'
}
const ethpm = {
name: 'ethPM',
displayName: 'ethPM',
events: [],
methods: [],
notifications: {
solidity: ['compilationFinished']
}, },
url: 'https://ethpm.surge.sh', get: () => { return JSON.parse(localStorage.getItem('workspace')) }
description: 'Generate and import ethPM packages.',
documentation: 'https://docs.ethpm.com/ethpm-developer-guide/ethpm-and-remix-ide',
icon: 'https://ethpm.surge.sh/ethpmlogo.png',
location: 'mainPanel'
} }
const zokrates = {
name: 'ZoKrates', this.loaders['queryParams'] = {
displayName: 'ZoKrates', set: () => {},
description: 'ZoKrates toolbox for zkSNARKs on Ethereum', get: () => {
documentation: 'https://zokrates.github.io/', const { plugins } = queryParams.get()
methods: [], if (!plugins) return []
events: [], return plugins.split(',')
version: '0.1.0-alpha', }
url: 'https://zokrates.blockchain-it.hr/',
icon: 'https://zokrates.blockchain-it.hr/zokrates.svg',
location: 'sidePanel'
}
const quorum = {
name: 'quorum',
displayName: 'Quorum Network',
description: 'Deploy and interact with private contracts on a Quorum network.',
events: [],
methods: [],
url: '//remix-plugin.goquorum.com/',
icon: '//remix-plugin.goquorum.com/tab_icon.png',
documentation: 'https://docs.goquorum.com/en/latest/RemixPlugin/Overview/',
version: '0.1.4-beta',
location: 'sidePanel'
} }
const res = await fetch(this.pluginsDirectory)
const plugins = await res.json() this.current = queryParams.get()['plugins'] ? 'queryParams' : 'localStorage'
return [ }
new IframePlugin(pipeline),
new IframePlugin(provable), set (plugin, actives) {
new IframePlugin(threeBox), this.currentLoader.set(plugin, actives)
new IframePlugin(debugPlugin), }
new IframePlugin(libraTools),
new IframePlugin(oneClickDapp), get () {
new IframePlugin(gasProfiler), return this.currentLoader.get()
new IframePlugin(flattener),
new IframePlugin(ethpm),
new IframePlugin(zokrates),
new IframePlugin(quorum),
...plugins.map(plugin => new IframePlugin(plugin))
]
} }
} }

@ -3,7 +3,8 @@ const EventEmitter = require('events')
class ClickInstance extends EventEmitter { class ClickInstance extends EventEmitter {
command (index) { command (index) {
index = index + 2 index = index + 2
this.api.click('.instance:nth-of-type(' + index + ') > div > button').perform(() => { this.emit('complete') }) let selector = '.instance:nth-of-type(' + index + ') > div > button'
this.api.waitForElementPresent(selector).scrollAndClick(selector).perform(() => { this.emit('complete') })
return this return this
} }
} }

@ -2,7 +2,7 @@ const EventEmitter = require('events')
class ClickLaunchIcon extends EventEmitter { class ClickLaunchIcon extends EventEmitter {
command (icon) { command (icon) {
this.api.click('#icon-panel div[plugin="' + icon + '"]').perform(() => { this.api.waitForElementVisible('#icon-panel div[plugin="' + icon + '"]').click('#icon-panel div[plugin="' + icon + '"]').perform(() => {
this.emit('complete') this.emit('complete')
}) })
return this return this

@ -6,6 +6,7 @@ class ExecuteScript extends EventEmitter {
.click('#terminalCli') .click('#terminalCli')
.keys(script) .keys(script)
.keys(this.api.Keys.ENTER) .keys(this.api.Keys.ENTER)
.keys(this.api.Keys.ENTER) // that's a bug... sometimes we need to press 2 times to execute a command
.perform(() => { .perform(() => {
this.emit('complete') this.emit('complete')
}) })

@ -0,0 +1,16 @@
const EventEmitter = require('events')
class GetModalBody extends EventEmitter {
command (callback) {
this.api.waitForElementVisible('.modal-body')
.getText('.modal-body', (result) => {
console.log(result)
callback(result.value, () => {
this.emit('complete')
})
})
return this
}
}
module.exports = GetModalBody

@ -3,10 +3,10 @@ const EventEmitter = require('events')
class JournalLastChild extends EventEmitter { class JournalLastChild extends EventEmitter {
command (val) { command (val) {
this.api this.api
.waitForElementVisible('#journal div:last-child span.text-info', 10000) .waitForElementVisible('#journal > div:last-child', 10000)
.assert.containsText('#journal div:last-child span.text-info', val).perform(() => { .assert.containsText('#journal > div:last-child', val).perform(() => {
this.emit('complete') this.emit('complete')
}) })
return this return this
} }
} }

@ -0,0 +1,19 @@
const EventEmitter = require('events')
/*
Check if the last log in the console contains a specific text
*/
class JournalLastChildIncludes extends EventEmitter {
command (val) {
this.api
.waitForElementVisible('#journal > div:last-child', 10000)
.getText('#journal > div:last-child', (result) => {
console.log('JournalLastChildIncludes', result.value)
if (result.value.indexOf(val) === -1) return this.api.assert.fail(`wait for ${val} in ${result.value}`)
this.emit('complete')
})
return this
}
}
module.exports = JournalLastChildIncludes

@ -0,0 +1,17 @@
const EventEmitter = require('events')
class ModalFooterOKClick extends EventEmitter {
command () {
this.api.waitForElementVisible('#modal-footer-cancel').perform((client, done) => {
this.api.execute(function () {
document.querySelector('#modal-footer-cancel').click()
}, [], (result) => {
done()
this.emit('complete')
})
})
return this
}
}
module.exports = ModalFooterOKClick

@ -2,7 +2,7 @@ const EventEmitter = require('events')
class ModalFooterOKClick extends EventEmitter { class ModalFooterOKClick extends EventEmitter {
command () { command () {
this.api.perform((client, done) => { this.api.waitForElementVisible('#modal-footer-ok').perform((client, done) => {
this.api.execute(function () { this.api.execute(function () {
document.querySelector('#modal-footer-ok').click() document.querySelector('#modal-footer-ok').click()
}, [], (result) => { }, [], (result) => {

@ -0,0 +1,19 @@
const EventEmitter = require('events')
class sendLowLevelTx extends EventEmitter {
command (address, value, callData) {
console.log('low level transact to ', address, value, callData)
this.api.waitForElementVisible(`#instance${address} #deployAndRunLLTxSendTransaction`, 1000)
.clearValue(`#instance${address} #deployAndRunLLTxCalldata`)
.setValue(`#instance${address} #deployAndRunLLTxCalldata`, callData)
.waitForElementVisible('#value')
.clearValue('#value')
.setValue('#value', value)
.scrollAndClick(`#instance${address} #deployAndRunLLTxSendTransaction`)
.perform(() => {
this.emit('complete')
})
return this
}
}
module.exports = sendLowLevelTx

@ -0,0 +1,15 @@
const EventEmitter = require('events')
class SetSolidityCompilerVersion extends EventEmitter {
command (version) {
this.api
.click(`#compileTabView #versionSelector [value="${version}"]`)
.pause(5000)
.perform(() => {
this.emit('complete')
})
return this
}
}
module.exports = SetSolidityCompilerVersion

@ -15,6 +15,7 @@ class SwitchFile extends EventEmitter {
// click on fileExplorer can toggle it. We go through settings to be sure FE is open // click on fileExplorer can toggle it. We go through settings to be sure FE is open
function switchFile (browser, name, done) { function switchFile (browser, name, done) {
browser.clickLaunchIcon('settings').clickLaunchIcon('fileExplorers') browser.clickLaunchIcon('settings').clickLaunchIcon('fileExplorers')
.waitForElementVisible('li[key="' + name + '"]')
.click('li[key="' + name + '"]') .click('li[key="' + name + '"]')
.pause(2000) .pause(2000)
.perform(() => { .perform(() => {

@ -1,9 +1,9 @@
const EventEmitter = require('events') const EventEmitter = require('events')
class VerifyContracts extends EventEmitter { class VerifyContracts extends EventEmitter {
command (compiledContractNames) { command (compiledContractNames, opts = { wait: 1000 }) {
this.api.perform((done) => { this.api.perform((done) => {
verifyContracts(this.api, compiledContractNames, () => { verifyContracts(this.api, compiledContractNames, opts, () => {
done() done()
this.emit('complete') this.emit('complete')
}) })
@ -12,9 +12,10 @@ class VerifyContracts extends EventEmitter {
} }
} }
function getCompiledContracts (browser, callback) { function getCompiledContracts (browser, opts, callback) {
browser browser
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.pause(opts.wait)
.waitForElementPresent('#compileTabView select#compiledContracts option') .waitForElementPresent('#compileTabView select#compiledContracts option')
.execute(function () { .execute(function () {
var contracts = document.querySelectorAll('#compileTabView select#compiledContracts option') var contracts = document.querySelectorAll('#compileTabView select#compiledContracts option')
@ -32,8 +33,8 @@ function getCompiledContracts (browser, callback) {
}) })
} }
function verifyContracts (browser, compiledContractNames, callback) { function verifyContracts (browser, compiledContractNames, opts, callback) {
getCompiledContracts(browser, (result) => { getCompiledContracts(browser, opts, (result) => {
if (result.value) { if (result.value) {
for (var contract in compiledContractNames) { for (var contract in compiledContractNames) {
console.log(' - ' + compiledContractNames[contract], result.value) console.log(' - ' + compiledContractNames[contract], result.value)

@ -1,14 +1,16 @@
module.exports = function (browser, callback) { module.exports = function (browser, callback, url, preloadPlugins = true) {
browser browser
.url('http://127.0.0.1:8080') .url(url || 'http://127.0.0.1:8080')
.injectScript('test-browser/helpers/applytestmode.js', function () { .injectScript('test-browser/helpers/applytestmode.js', function () {
browser.resizeWindow(2560, 1440, () => { browser.resizeWindow(2560, 1440, () => {
initModules(browser, () => { if (preloadPlugins) {
browser.clickLaunchIcon('solidity').click('#autoCompile') initModules(browser, () => {
.perform(function () { browser.clickLaunchIcon('solidity').click('#autoCompile')
callback() .perform(function () {
callback()
})
}) })
}) } else callback()
}) })
}) })
} }
@ -22,5 +24,8 @@ function initModules (browser, callback) {
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_solidityStaticAnalysis"] button') .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_solidityStaticAnalysis"] button')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button') .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
.scrollAndClick('#icon-panel div[plugin="fileExplorers"]') .scrollAndClick('#icon-panel div[plugin="fileExplorers"]')
.clickLaunchIcon('settings')
.setValue('#gistaccesstoken', process.env.gist_token)
.click('#savegisttoken')
.perform(() => { callback() }) .perform(() => { callback() })
} }

@ -20,7 +20,7 @@ module.exports = {
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.testContracts('Untitled.sol', sources[0]['browser/Untitled.sol'], ['Ballot']) .testContracts('Untitled.sol', sources[0]['browser/Untitled.sol'], ['Ballot'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.setValue('input[placeholder="uint8 _numProposals"]', '1') .setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]')
.click('#runTabView button[class^="instanceButton"]') .click('#runTabView button[class^="instanceButton"]')
.waitForElementPresent('.instance:nth-of-type(2)') .waitForElementPresent('.instance:nth-of-type(2)')
.click('.instance:nth-of-type(2) > div > button') .click('.instance:nth-of-type(2) > div > button')
@ -36,7 +36,7 @@ module.exports = {
.clickLaunchIcon('debugger') .clickLaunchIcon('debugger')
.click('#jumppreviousbreakpoint') .click('#jumppreviousbreakpoint')
.pause(2000) .pause(2000)
.goToVMTraceStep(58) .goToVMTraceStep(79)
.pause(1000) .pause(1000)
.checkVariableDebug('soliditystate', stateCheck) .checkVariableDebug('soliditystate', stateCheck)
.checkVariableDebug('soliditylocals', localsCheck) .checkVariableDebug('soliditylocals', localsCheck)
@ -55,8 +55,25 @@ module.exports = {
.testFunction('delegate - transact (not payable)', '0xca58080c8099429caeeffe43b8104df919c2c543dceb9edf9242fa55f045c803', .testFunction('delegate - transact (not payable)', '0xca58080c8099429caeeffe43b8104df919c2c543dceb9edf9242fa55f045c803',
`[vm]\nfrom:0xca3...a733c\nto:Ballot.delegate(address) 0x692...77b3a\nvalue:0 wei\ndata:0x5c1...4d2db\nlogs:0\nhash:0xca5...5c803`, `[vm]\nfrom:0xca3...a733c\nto:Ballot.delegate(address) 0x692...77b3a\nvalue:0 wei\ndata:0x5c1...4d2db\nlogs:0\nhash:0xca5...5c803`,
{types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"'}, null, null) {types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"'}, null, null)
},
'Deploy and use Ballot using external web3': function (browser) {
browser
.click('#selectExEnvOptions #web3-mode')
.modalFooterOKClick()
.clickLaunchIcon('solidity')
.testContracts('Untitled.sol', sources[0]['browser/Untitled.sol'], ['Ballot'])
.clickLaunchIcon('udapp')
.setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]')
.click('#runTabView button[class^="instanceButton"]')
.clickInstance(0)
.click('#clearConsole')
.clickFunction('delegate - transact (not payable)', {types: 'address to', values: '0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'})
.journalLastChildIncludes('Ballot.delegate(address)')
.journalLastChildIncludes('data:0x5c1...a733c')
.end() .end()
}, },
tearDown: sauce tearDown: sauce
} }
@ -85,13 +102,13 @@ var stateCheck = {
'value': false, 'value': false,
'type': 'bool' 'type': 'bool'
}, },
'vote': {
'value': '0',
'type': 'uint8'
},
'delegate': { 'delegate': {
'value': '0x0000000000000000000000000000000000000000', 'value': '0x0000000000000000000000000000000000000000',
'type': 'address' 'type': 'address'
},
'vote': {
'value': '0',
'type': 'uint256'
} }
}, },
'type': 'struct Ballot.Voter' 'type': 'struct Ballot.Voter'
@ -104,6 +121,10 @@ var stateCheck = {
'value': [ 'value': [
{ {
'value': { 'value': {
'name': {
'value': '0x48656C6C6F20576F726C64210000000000000000000000000000000000000000',
'type': 'bytes32'
},
'voteCount': { 'voteCount': {
'value': '0', 'value': '0',
'type': 'uint256' 'type': 'uint256'
@ -118,4 +139,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"
}
]`

@ -0,0 +1,43 @@
'use strict'
const init = require('../helpers/init')
const sauce = require('./sauce')
// 99266d6da54cc12f37f11586e8171546c7700d67
module.exports = {
before: function (browser, done) {
init(browser, done)
},
'UploadToGists': function (browser) {
/*
- set the access token
- publish to gist
- retrieve the gist
- switch to a file in the new gist
*/
console.log('token', process.env.gist_token)
browser
.waitForElementVisible('#icon-panel', 10000)
.clickLaunchIcon('fileExplorers')
.click('#publishToGist')
.modalFooterOKClick()
.getModalBody((value, done) => {
const reg = /gist.github.com\/([^.]+)/
const id = value.match(reg)
console.log('gist regex', id)
if (!id) {
browser.assert.fail('cannot get the gist id', '', '')
} else {
let gistid = id[1]
browser
.modalFooterCancelClick()
.executeScript(`remix.loadgist('${gistid}')`)
.switchFile('browser/gists')
.switchFile(`browser/gists/${gistid}`)
.switchFile(`browser/gists/${gistid}/1_Storage.sol`)
.perform(done)
}
})
.end()
},
tearDown: sauce
}

@ -0,0 +1,56 @@
'use strict'
const init = require('../helpers/init')
const sauce = require('./sauce')
const testData = {
validGistId: '1859c97c6e1efc91047d725d5225888e',
invalidGistId: '6368b389f9302v32902msk2402'
}
module.exports = {
before: function (browser, done) {
init(browser, done)
},
'Load Gist Modal': function (browser) {
browser
.waitForElementVisible('#icon-panel', 10000)
.clickLaunchIcon('fileExplorers')
.scrollAndClick('div.file > div.btn-group > button:nth-child(1)')
.waitForElementVisible('h6.modal-title')
.assert.containsText('h6.modal-title', 'Load a Gist')
.waitForElementVisible('div.modal-body > div')
.assert.containsText('div.modal-body > div', 'Enter the ID of the Gist or URL you would like to load.')
.waitForElementVisible('#prompt_text')
.click('#modal-footer-cancel')
},
'Display Error Message For Invalid Gist ID': function (browser) {
browser
.waitForElementVisible('#icon-panel', 10000)
.clickLaunchIcon('fileExplorers')
.scrollAndClick('div.file > div.btn-group > button:nth-child(1)')
.waitForElementVisible('#prompt_text')
.setValue('#prompt_text', testData.invalidGistId)
.modalFooterOKClick()
.waitForElementVisible('div.modal-body > div')
.assert.containsText('div.modal-body > div', 'Gist load error: Not Found')
.modalFooterOKClick()
},
'Import From Gist For Valid Gist ID': function (browser) {
browser
.waitForElementVisible('#icon-panel', 10000)
.clickLaunchIcon('fileExplorers')
.scrollAndClick('div.file > div.btn-group > button:nth-child(1)')
.waitForElementVisible('#prompt_text')
.setValue('#prompt_text', testData.validGistId)
.modalFooterOKClick()
.switchFile(`browser/gists/${testData.validGistId}`)
.switchFile(`browser/gists/${testData.validGistId}/ApplicationRegistry`)
.waitForElementVisible(`div[title='browser/gists/${testData.validGistId}/ApplicationRegistry']`)
.assert.containsText(`div[title='browser/gists/${testData.validGistId}/ApplicationRegistry'] > span`, 'ApplicationRegistry')
.end()
},
tearDown: sauce
}

@ -0,0 +1,37 @@
'use strict'
var init = require('../helpers/init')
var sauce = require('./sauce')
module.exports = {
before: function (browser, done) {
init(browser, done)
},
'@sources': function () {
return []
},
'Publish on IPFS': function (browser) {
browser
.waitForElementVisible('#icon-panel', 10000)
.clickLaunchIcon('fileExplorers')
.switchFile('browser/3_Ballot.sol')
.verifyContracts(['Ballot'])
.click('#publishOnIpfs')
.getModalBody((value, done) => {
if (value.indexOf('Metadata published successfully.') === -1) browser.assert.fail('ipfs deploy failed', '', '')
if (value.indexOf('dweb:/ipfs') === -1) browser.assert.fail('ipfs deploy failed', '', '')
done()
})
.modalFooterOKClick()
},
'Publish on Swarm': function (browser) {
browser
.click('#publishOnSwarm')
.getModalBody((value, done) => {
if (value.indexOf('Metadata published successfully.') === -1) browser.assert.fail('swarm deploy failed', '', '')
if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed', '', '')
done()
})
.end()
},
tearDown: sauce
}

@ -28,6 +28,12 @@ var sources = [
{ {
'localhost/src/gmbh/company.sol': {content: assetsTestContract}, 'localhost/src/gmbh/company.sol': {content: assetsTestContract},
'localhost/src/gmbh/contract.sol': {content: gmbhTestContract} 'localhost/src/gmbh/contract.sol': {content: gmbhTestContract}
},
{
'browser/test_import_node_modules.sol': {content: 'import "openzeppelin-solidity/contracts/math/SafeMath.sol";'}
},
{
'browser/test_import_node_modules_with_github_import.sol': {content: 'import "openzeppelin-solidity/contracts/sample.sol";'}
} }
] ]
@ -41,6 +47,29 @@ module.exports = {
'Remixd': function (browser) { 'Remixd': function (browser) {
runTests(browser) runTests(browser)
}, },
'Import from node_modules ': function (browser) {
/*
when a relative import is used (i.e import "openzeppelin-solidity/contracts/math/SafeMath.sol")
remix (as well as truffle) try to resolve it against the node_modules and installed_contracts folder.
*/
browser
.waitForElementVisible('#icon-panel', 2000)
.clickLaunchIcon('fileExplorers')
.addFile('test_import_node_modules.sol', sources[3]['browser/test_import_node_modules.sol'])
.clickLaunchIcon('solidity')
.testContracts('test_import_node_modules.sol', sources[3]['browser/test_import_node_modules.sol'], ['SafeMath'])
},
'Import from node_modules and reference a github import': function (browser) {
browser
.waitForElementVisible('#icon-panel', 2000)
.clickLaunchIcon('fileExplorers')
.addFile('test_import_node_modules_with_github_import.sol', sources[4]['browser/test_import_node_modules_with_github_import.sol'])
.clickLaunchIcon('solidity')
.testContracts('test_import_node_modules_with_github_import.sol', sources[4]['browser/test_import_node_modules_with_github_import.sol'], ['ERC20', 'test11'])
.clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_remixd"] button')
.end()
},
tearDown: sauce tearDown: sauce
} }
@ -98,9 +127,6 @@ function runTests (browser, testData) {
.waitForElementNotPresent('[data-path="localhost/folder1/contract_' + browserName + '.sol"]') // check if renamed (old) file is not present .waitForElementNotPresent('[data-path="localhost/folder1/contract_' + browserName + '.sol"]') // check if renamed (old) file is not present
.waitForElementNotPresent('[data-path="localhost/folder1/contract_' + browserName + '_toremove.sol"]') // check if removed (old) file is not present .waitForElementNotPresent('[data-path="localhost/folder1/contract_' + browserName + '_toremove.sol"]') // check if removed (old) file is not present
.click('[data-path="localhost/folder1/renamed_contract_' + browserName + '.sol"]') .click('[data-path="localhost/folder1/renamed_contract_' + browserName + '.sol"]')
.clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_remixd"] button')
.end()
} }
function testImportFromRemixd (browser, callback) { function testImportFromRemixd (browser, callback) {

@ -49,7 +49,7 @@ module.exports = {
var sources = [ var sources = [
{ {
'browser/signMassage.sol': {content: ` 'browser/signMassage.sol': {content: `
pragma solidity >=0.4.22 <0.6.0; pragma solidity >=0.4.22 <0.7.0;
contract SignMassageTest { contract SignMassageTest {
function testRecovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) public pure returns (address) { function testRecovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) public pure returns (address) {
return ecrecover(h, v, r, s); return ecrecover(h, v, r, s);

@ -22,129 +22,40 @@ module.exports = {
'Test Failed Import': function (browser) { 'Test Failed Import': function (browser) {
browser.addFile('Untitled3.sol', sources[2]['browser/Untitled3.sol']) browser.addFile('Untitled3.sol', sources[2]['browser/Untitled3.sol'])
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.assert.containsText('#compileTabView .error pre', 'Unable to import "browser/Untitled11.sol": File not found') .assert.containsText('#compileTabView .error pre', 'not found browser/Untitled11.sol')
.end()
}, },
tearDown: sauce
}
var abstractENS = `
contract AbstractENS {
function owner(bytes32 node) public view returns(address);
function resolver(bytes32 node) public view returns(address);
function ttl(bytes32 node) public view returns(uint64);
function setOwner(bytes32 node, address owner) public;
function setSubnodeOwner(bytes32 node, bytes32 label, address owner) public;
function setResolver(bytes32 node, address resolver) public;
function setTTL(bytes32 node, uint64 ttl) public;
// Logged when the owner of a node assigns a new owner to a subnode.
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);
// Logged when the owner of a node transfers ownership to a new account.
event Transfer(bytes32 indexed node, address owner);
// Logged when the resolver for a node changes.
event NewResolver(bytes32 indexed node, address resolver);
// Logged when the TTL of a node changes
event NewTTL(bytes32 indexed node, uint64 ttl);
}`
var ENS = `pragma solidity ^0.4.0;
import './AbstractENS.sol';
/**
* The ENS registry contract.
*/
contract ENS is AbstractENS {
struct Record {
address owner;
address resolver;
uint64 ttl;
}
mapping(bytes32=>Record) records;
// Permits modifications only by the owner of the specified node.
modifier only_owner(bytes32 node) {
if (records[node].owner != msg.sender) revert();
_;
}
/**
* Constructs a new ENS registrar.
*/
constructor() public {
records[0].owner = msg.sender;
}
/**
* Returns the address that owns the specified node.
*/
function owner(bytes32 node) public view returns (address) {
return records[node].owner;
}
/** 'Test Github Import - from master branch': function (browser) {
* Returns the address of the resolver for the specified node. browser
*/ .setSolidityCompilerVersion('soljson-v0.5.0+commit.1d4f565a.js')
function resolver(bytes32 node) public view returns (address) { .addFile('Untitled4.sol', sources[3]['browser/Untitled4.sol'])
return records[node].resolver; .clickLaunchIcon('fileExplorers')
} .verifyContracts(['test7', 'ERC20', 'SafeMath'], {wait: 10000})
},
/**
* Returns the TTL of a node, and any records associated with it.
*/
function ttl(bytes32 node) public view returns (uint64) {
return records[node].ttl;
}
/**
* Transfers ownership of a node to a new address. May only be called by the current
* owner of the node.
* @param node The node to transfer ownership of.
* @param owner The address of the new owner.
*/
function setOwner(bytes32 node, address owner) public only_owner(node) {
emit Transfer(node, owner);
records[node].owner = owner;
}
/** 'Test Github Import - from other branch': function (browser) {
* Transfers ownership of a subnode sha3(node, label) to a new address. May only be browser
* called by the owner of the parent node. .addFile('Untitled5.sol', sources[4]['browser/Untitled5.sol'])
* @param node The parent node. .clickLaunchIcon('fileExplorers')
* @param label The hash of the label specifying the subnode. .verifyContracts(['test8', 'ERC20', 'SafeMath'], {wait: 10000})
* @param owner The address of the new owner. },
*/
function setSubnodeOwner(bytes32 node, bytes32 label, address owner) public only_owner(node) {
bytes32 subnode = keccak256(abi.encodePacked(node, label));
emit NewOwner(node, label, owner);
records[subnode].owner = owner;
}
/** 'Test Github Import - no branch specified': function (browser) {
* Sets the resolver address for the specified node. browser
* @param node The node to update. .addFile('Untitled6.sol', sources[5]['browser/Untitled6.sol'])
* @param resolver The address of the resolver. .clickLaunchIcon('fileExplorers')
*/ .verifyContracts(['test10', 'ERC20', 'SafeMath'], {wait: 10000})
function setResolver(bytes32 node, address resolver) public only_owner(node) { },
emit NewResolver(node, resolver);
records[node].resolver = resolver;
}
/** 'Test Github Import - raw URL': function (browser) {
* Sets the TTL for the specified node. browser
* @param node The node to update. .addFile('Untitled7.sol', sources[6]['browser/Untitled7.sol'])
* @param ttl The TTL in seconds. .clickLaunchIcon('fileExplorers')
*/ .verifyContracts(['test11', 'ERC20', 'SafeMath'], {wait: 10000})
function setTTL(bytes32 node, uint64 ttl) public only_owner(node) { .end()
emit NewTTL(node, ttl); },
records[node].ttl = ttl; tearDown: sauce
} }
}`
var sources = [ var sources = [
{ {
@ -158,12 +69,15 @@ var sources = [
'browser/Untitled3.sol': {content: 'import "./Untitled11.sol"; contract test6 {}'} 'browser/Untitled3.sol': {content: 'import "./Untitled11.sol"; contract test6 {}'}
}, },
{ {
'browser/Untitled4.sol': {content: 'import "github.com/ethereum/ens/contracts/ENS.sol"; contract test7 {}'}, 'browser/Untitled4.sol': {content: 'import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol"; contract test7 {}'}
'github.com/ethereum/ens/contracts/ENS.sol': {content: ENS} },
{
'browser/Untitled5.sol': {content: 'import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v2.3.0/contracts/token/ERC20/ERC20.sol"; contract test8 {}'}
},
{
'browser/Untitled6.sol': {content: 'import "https://github.com/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; contract test10 {}'}
}, },
{ {
'browser/Untitled4.sol': {content: 'import "github.com/ethereum/ens/contracts/ENS.sol"; contract test7 {}'}, 'browser/Untitled7.sol': {content: 'import "https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol"; contract test11 {}'}
'github.com/ethereum/ens/contracts/ENS.sol': {content: ENS},
'github.com/ethereum/ens/contracts/AbstractENS.sol': {content: abstractENS}
} }
] ]

@ -23,12 +23,12 @@ function runTests (browser) {
.click('#icon-panel div[plugin="pluginManager"]') .click('#icon-panel div[plugin="pluginManager"]')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_solidityUnitTesting"] button') .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_solidityUnitTesting"] button')
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.switchFile('browser/ballot.sol') .switchFile('browser/3_Ballot.sol')
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.scrollAndClick('#runTestsTabRunAction') .scrollAndClick('#runTestsTabRunAction')
.waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]') .waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]')
.pause(10000) .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 winning proposal)')
.assert.containsText('#solidityUnittestsOutput', '✓ (Check winnin proposal with return value)') .assert.containsText('#solidityUnittestsOutput', '✓ (Check winnin proposal with return value)')
.end() .end()

@ -0,0 +1,273 @@
'use strict'
var init = require('../helpers/init')
var sauce = require('./sauce')
module.exports = {
before: function (browser, done) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Use special functions receive/fallback - both are declared, sending data': function (browser) {
browser.waitForElementVisible('#icon-panel', 10000)
.testContracts('receiveAndFallback.sol', sources[0]['browser/receiveAndFallback.sol'], ['CheckSpecials']) // compile
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.createContract('') // deploy
.clickInstance(0)
.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
browser.sendLowLevelTx(address, '0', '0xaa')
.pause(1000)
.journalLastChildIncludes('to:CheckSpecials.(fallback)')
.journalLastChildIncludes('value:0 wei')
.journalLastChildIncludes('data:0xaa')
.perform(done)
})
})
},
'Use special functions receive/fallback - both are declared, failing sending data < 1 byte': function (browser) {
// don't need to redeploy it, same contract
browser.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
browser.sendLowLevelTx(address, '0', '0xa')
.pause(1000)
.waitForElementVisible(`#instance${address} label[id="deployAndRunLLTxError"]`)
.assert.containsText(`#instance${address} label[id="deployAndRunLLTxError"]`, `The calldata should be a valid hexadecimal value with size of at least one byte.`)
.perform(done)
})
})
},
'Use special functions receive/fallback - both are declared, failing sending data with odd number of digits': function (browser) {
// don't need to redeploy it, same contract
browser.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
browser.sendLowLevelTx(address, '0', '0x1aa')
.pause(1000)
.waitForElementVisible(`#instance${address} label[id="deployAndRunLLTxError"]`)
.assert.containsText(`#instance${address} label[id="deployAndRunLLTxError"]`, `The calldata should be a valid hexadecimal value.`)
.perform(done)
})
})
},
'Use special functions receive/fallback - both are declared - receive called, sending wei': function (browser) {
// don't need to redeploy it, same contract
browser.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
browser.sendLowLevelTx(address, '1', '')
.pause(1000)
.journalLastChildIncludes('to:CheckSpecials.(receive)')
.journalLastChildIncludes('value:1 wei')
.journalLastChildIncludes('data:0x')
.perform(done)
})
})
},
'Use special functions receive/fallback - both are declared - fallback should fail cause not payable, sending data and wei': function (browser) {
// don't need to redeploy it, same contract
browser.perform((done) => {
browser.getAddressAtPosition(0, (address) => {
browser.sendLowLevelTx(address, '10', '0xaa')
.pause(1000)
.journalLastChildIncludes('to CheckSpecials.(fallback) errored:')
.journalLastChildIncludes('The called function should be payable if you send value')
.perform(done)
})
})
},
'Use special functions receive/fallback - only receive is declared, sending wei': function (browser) {
browser.waitForElementVisible('#icon-panel', 10000)
.testContracts('receiveOnly.sol', sources[1]['browser/receiveOnly.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.createContract('')
.clickInstance(1)
.perform((done) => {
browser.getAddressAtPosition(1, (address) => {
browser.sendLowLevelTx(address, '1', '')
.pause(1000)
.journalLastChildIncludes('to:CheckSpecials.(receive)')
.journalLastChildIncludes('value:1 wei')
.journalLastChildIncludes('data:0x')
.perform(done)
})
})
},
'Use special functions receive/fallback - only receive is declared, failing, fallback is not declared, sending data': function (browser) {
// don't need to redeploy it, same contract
browser.perform((done) => {
browser.getAddressAtPosition(1, (address) => {
browser.sendLowLevelTx(address, '0', '0xaa')
.pause(1000)
.waitForElementVisible(`#instance${address} label[id="deployAndRunLLTxError"]`)
.assert.containsText(`#instance${address} label[id="deployAndRunLLTxError"]`, `'Fallback' function is not defined`)
.perform(done)
})
})
},
'Use special functions receive/fallback - only fallback declared and is payable, sending wei': function (browser) {
browser.waitForElementVisible('#icon-panel', 10000)
.testContracts('fallbackOnlyPayable.sol', sources[2]['browser/fallbackOnlyPayable.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.createContract('')
.clickInstance(2)
.perform((done) => {
browser.getAddressAtPosition(2, (address) => {
browser.sendLowLevelTx(address, '1', '')
.pause(1000)
.journalLastChildIncludes('to:CheckSpecials.(fallback)')
.journalLastChildIncludes('value:1 wei')
.journalLastChildIncludes('data:0x')
.perform(done)
})
})
},
'Use special functions receive/fallback - only fallback is diclared and is payable, sending data and wei': function (browser) {
// don't need to redeploy it, same contract
browser.perform((done) => {
browser.getAddressAtPosition(2, (address) => {
browser.sendLowLevelTx(address, '1', '0xaa')
.pause(1000)
.journalLastChildIncludes('to:CheckSpecials.(fallback)')
.journalLastChildIncludes('value:1 wei')
.journalLastChildIncludes('data:0xaa')
.perform(done)
})
})
},
'Use special functions receive/fallback - only fallback is declared, fallback should fail cause not payable, sending wei': function (browser) {
browser.waitForElementVisible('#icon-panel', 10000)
.testContracts('fallbackOnlyNotPayable.sol', sources[3]['browser/fallbackOnlyNotPayable.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.createContract('')
.clickInstance(3)
.perform((done) => {
browser.getAddressAtPosition(3, (address) => {
browser.sendLowLevelTx(address, '1', '')
.pause(1000)
.waitForElementVisible(`#instance${address} label[id="deployAndRunLLTxError"]`)
.assert.containsText(`#instance${address} label[id="deployAndRunLLTxError"]`, `should have either 'receive' or payable 'fallback'`)
.perform(done)
})
})
},
'Use special functions receive/fallback - receive and fallback are declared, sending data and wei': function (browser) {
browser.waitForElementVisible('#icon-panel', 10000)
.testContracts('receiveAndFallbackBothPayable.sol', sources[4]['browser/receiveAndFallbackBothPayable.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.waitForElementVisible('#value')
.clearValue('#value')
.setValue('#value', 0)
.createContract('')
.clickInstance(4)
.pause(1000)
.perform((done) => {
browser.getAddressAtPosition(4, (address) => {
browser.sendLowLevelTx(address, '1', '0xaa')
.pause(1000)
.journalLastChildIncludes('to:CheckSpecials.(fallback)')
.journalLastChildIncludes('value:1 wei')
.journalLastChildIncludes('data:0xaa')
.perform(done)
})
})
},
'Use special functions receive/fallback - receive and fallback are declared and payable, sending wei': function (browser) {
browser.perform((done) => {
browser.getAddressAtPosition(4, (address) => {
browser.sendLowLevelTx(address, '1', '')
.pause(1000)
.journalLastChildIncludes('to:CheckSpecials.(receive)')
.journalLastChildIncludes('value:1 wei')
.journalLastChildIncludes('data:0x')
.perform(done)
})
})
},
'Use special functions receive/fallback - receive and fallback are not declared, sending nothing': function (browser) {
browser.waitForElementVisible('#icon-panel', 10000)
.testContracts('notSpecial.sol', sources[5]['browser/notSpecial.sol'], ['CheckSpecials'])
.clickLaunchIcon('udapp')
.selectContract('CheckSpecials')
.waitForElementVisible('#value')
.clearValue('#value')
.setValue('#value', 0)
.createContract('')
.clickInstance(5)
.pause(1000)
.perform((done) => {
browser.getAddressAtPosition(5, (address) => {
browser.sendLowLevelTx(address, '0', '')
.pause(1000)
.waitForElementVisible(`#instance${address} label[id="deployAndRunLLTxError"]`)
.assert.containsText(`#instance${address} label[id="deployAndRunLLTxError"]`, `Both 'receive' and 'fallback' functions are not defined`)
.perform(done)
})
})
.end()
},
tearDown: sauce
}
var sources = [
{
'browser/receiveAndFallback.sol': {
content: `
contract CheckSpecials {
receive() payable external{}
fallback() external {}
}
`
}
},
{
'browser/receiveOnly.sol': {
content: `
contract CheckSpecials {
receive() payable external {}
}
`
}
},
{
'browser/fallbackOnlyPayable.sol': {
content: `
contract CheckSpecials {
fallback() payable external {}
}
`
}
},
{
'browser/fallbackOnlyNotPayable.sol': {
content: `
contract CheckSpecials {
fallback() external {}
}
`
}
},
{
'browser/receiveAndFallbackBothPayable.sol': {
content: `
contract CheckSpecials {
receive() payable external {}
fallback() payable external {}
}
`
}
},
{
'browser/notSpecial.sol': {
content: `
contract CheckSpecials {
function otherFallback() payable external {}
}
`
}
}
]

@ -0,0 +1,17 @@
'use strict'
const init = require('../helpers/init')
const sauce = require('./sauce')
module.exports = {
before: function (browser, done) {
init(browser, done, 'http://127.0.0.1:8080?plugins=solidity,udapp', false)
},
'CheckSolidityActivatedAndUDapp': function (browser) {
browser
.waitForElementVisible('#icon-panel', 10000)
.clickLaunchIcon('solidity')
.clickLaunchIcon('udapp')
.end()
},
tearDown: sauce
}

@ -10,7 +10,7 @@ test('compiler.compile smoke', function (t) {
var noop = function () {} var noop = function () {}
var fakeImport = function (url, cb) { cb('Not implemented') } var fakeImport = function (url, cb) { cb('Not implemented') }
var compiler = new Compiler(fakeImport) var compiler = new Compiler(fakeImport)
compiler.setCompileJSON(noop) compiler.compileJSON = noop
compiler.compile({ 'test': '' }, 'test') compiler.compile({ 'test': '' }, 'test')
t.ok(compiler) t.ok(compiler)
}) })

Loading…
Cancel
Save