diff --git a/package.json b/package.json index de9330fb41..a55f364317 100644 --- a/package.json +++ b/package.json @@ -171,6 +171,7 @@ "make-mock-compiler": "node ci/makeMockCompiler.js", "minify": "uglifyjs --in-source-map inline --source-map-inline -c warnings=false", "nightwatch_local": "nightwatch --config nightwatch.js --env local", + "nightwatch_local_general": "nightwatch ./test-browser/tests/generalTests.js --config nightwatch.js --env local ", "nightwatch_local_debugger": "nightwatch --config nightwatch_debugger.js --env local", "nightwatch_remote_chrome": "nightwatch --config nightwatch.js --env chrome", "nightwatch_remote_firefox": "nightwatch --config nightwatch.js --env default", diff --git a/src/app/tabs/runTab/model/settings.js b/src/app/tabs/runTab/model/settings.js index 1e20960a71..d5671fdbe0 100644 --- a/src/app/tabs/runTab/model/settings.js +++ b/src/app/tabs/runTab/model/settings.js @@ -1,6 +1,7 @@ var ethJSUtil = require('ethereumjs-util') var Personal = require('web3-eth-personal') var remixLib = require('remix-lib') +const addTooltip = require('../../../ui/tooltip') var EventManager = remixLib.EventManager var executionContext = remixLib.execution.executionContext @@ -73,6 +74,10 @@ class Settings { return (!isVM && !isInjected) } + isInjectedWeb3 () { + return executionContext.getProvider() === 'injected' + } + signMessage (message, account, passphrase, cb) { var isVM = executionContext.isVM() var isInjected = executionContext.getProvider() === 'injected' @@ -92,6 +97,7 @@ class Settings { if (isInjected) { const hashedMsg = executionContext.web3().sha3(message) try { + addTooltip('Please check your provider to approve') executionContext.web3().eth.sign(account, hashedMsg, (error, signedData) => { cb(error.message, hashedMsg, signedData) }) diff --git a/src/app/tabs/runTab/settings.js b/src/app/tabs/runTab/settings.js index 18459fb070..0a4f060902 100644 --- a/src/app/tabs/runTab/settings.js +++ b/src/app/tabs/runTab/settings.js @@ -58,11 +58,11 @@ class SettingsUI { ${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)} - + ` @@ -223,6 +223,10 @@ class SettingsUI { var signMessageDialog = { 'title': 'Sign a message', 'text': 'Enter a message to sign', 'inputvalue': 'Message to sign' } var $txOrigin = this.el.querySelector('#txorigin') + if (!$txOrigin.selectedOptions[0] && (this.settings.isInjectedWeb3() || this.settings.isWeb3Provider())) { + return addTooltip(`Account list is empty, please make sure the current provider is properly connected to remix`) + } + var account = $txOrigin.selectedOptions[0].value var promptCb = (passphrase) => { @@ -231,13 +235,26 @@ class SettingsUI { if (err) { return addTooltip(err) } - modalDialogCustom.alert(yo`
hash:${msgHash}
signature:${signedData}
`) + modalDialogCustom.alert(yo` +
+ hash:
+ ${msgHash} +
signature:
+ ${signedData} +
+ `) }) }, false) } if (this.settings.isWeb3Provider()) { - return modalDialogCustom.promptPassphrase('Passphrase to sign a message', 'Enter your passphrase for this account to sign the message', '', promptCb, false) + return modalDialogCustom.promptPassphrase( + 'Passphrase to sign a message', + 'Enter your passphrase for this account to sign the message', + '', + promptCb, + false + ) } promptCb() }) diff --git a/src/app/tabs/settings-tab.js b/src/app/tabs/settings-tab.js index 821e6957d0..5ed5d0f943 100644 --- a/src/app/tabs/settings-tab.js +++ b/src/app/tabs/settings-tab.js @@ -67,7 +67,8 @@ module.exports = class SettingsTab extends BaseApi { var gistAddToken = yo` { this.config.set('settings/gist-access-token', gistAccessToken.value); tooltip('Access token saved') }} value="Save" type="button">` var gistRemoveToken = yo` { gistAccessToken.value = ''; this.config.set('settings/gist-access-token', ''); tooltip('Access token removed') }} value="Remove" type="button">` this._view.gistToken = yo`
${gistAccessToken}${copyToClipboard(() => this.config.get('settings/gist-access-token'))}${gistAddToken}${gistRemoveToken}
` - this._view.optionVM = yo`` + this._view.optionVM = yo`` + if (this.config.get('settings/always-use-vm') === undefined) this.config.set('settings/always-use-vm', true) if (this.config.get('settings/always-use-vm')) this._view.optionVM.setAttribute('checked', '') this._view.personal = yo`` if (this.config.get('settings/personal-mode')) this._view.personal.setAttribute('checked', '') diff --git a/test-browser/helpers/contracts.js b/test-browser/helpers/contracts.js index d022a0a2ba..949afb0217 100644 --- a/test-browser/helpers/contracts.js +++ b/test-browser/helpers/contracts.js @@ -25,7 +25,8 @@ module.exports = { removeFile, getAddressAtPosition, clickLaunchIcon, - scrollInto + scrollInto, + signMsg } function clickLaunchIcon (icon) { @@ -168,6 +169,30 @@ function scrollInto (target) { }) } +function signMsg (browser, msg, cb) { + let hash, signature + browser + .click('i[id="remixRunSignMsg"]') + .setValue('textarea[id="prompt_text"]', msg, () => { + browser.modalFooterOKClick().perform( + (client, done) => { + browser.getText('span[id="remixRunSignMsgHash"]', (v) => { hash = v; done() }) + } + ) + .perform( + (client, done) => { + browser.getText('span[id="remixRunSignMsgSignature"]', (v) => { signature = v; done() }) + } + ) + .modalFooterOKClick() + .perform( + () => { + cb(hash, signature) + } + ) + }) +} + function _scrollInto (browser, target, cb) { browser.execute(function (target) { document.querySelector(target).scrollIntoView() diff --git a/test-browser/tests/simpleContract.js b/test-browser/tests/generalTests.js similarity index 81% rename from test-browser/tests/simpleContract.js rename to test-browser/tests/generalTests.js index e525e3cf32..e820a54e3b 100644 --- a/test-browser/tests/simpleContract.js +++ b/test-browser/tests/generalTests.js @@ -21,7 +21,10 @@ function runTests (browser) { browser.setEditorValue = contractHelper.setEditorValue browser.getEditorValue = contractHelper.getEditorValue browser.clickLaunchIcon = contractHelper.clickLaunchIcon + browser.modalFooterOKClick = contractHelper.modalFooterOKClick + browser.clickFunction = contractHelper.clickFunction browser.scrollInto = contractHelper.scrollInto + browser.verifyCallReturnValue = contractHelper.verifyCallReturnValue browser .waitForElementVisible('#icon-panel', 10000) .clickLaunchIcon('solidity') @@ -34,7 +37,8 @@ function runTests (browser) { testFailedImport, /* testGitHubImport */ addDeployLibTestFile, testAutoDeployLib, - testManualDeployLib + testManualDeployLib, + testSignature ], function () { browser.end() @@ -158,6 +162,36 @@ function checkDeployShouldSucceed (browser, address, callback) { }) } +function testSignature (browser, callback) { + let hash, signature + browser.clickLaunchIcon('run').pause(4000).perform((client, done) => { + contractHelper.signMsg(browser, 'test message', (h, s) => { + hash = h + signature = s + contractHelper.addFile(browser, 'signMassage.sol', sources[6]['browser/signMassage.sol'], () => { + contractHelper.switchFile(browser, 'browser/signMassage.sol', () => { + contractHelper.selectContract(browser, 'ECVerify', () => { // deploy lib + contractHelper.createContract(browser, '', () => { + browser.waitForElementPresent('.instance:nth-of-type(4)') + .click('.instance:nth-of-type(4) > div > button') + .clickFunction('ecrecovery - call', {types: 'bytes32 hash, bytes sig', values: `"${hash.value}","${signature.value}"`}).perform( + () => { + contractHelper.verifyCallReturnValue( + browser, + '0x08970fed061e7747cd9a38d680a601510cb659fb', + ['0: address: 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'], + () => { callback(null, browser) } + ) + } + ) + }) + }) + }) + }) + }) + }) +} + /* function testGitHubImport (browser, callback) { contractHelper.addFile(browser, 'Untitled4.sol', sources[3]['browser/Untitled4.sol'], () => { @@ -320,6 +354,46 @@ var sources = [ function get () public view returns (uint) { return lib.getInt(); } -}`} + }`} + }, + { + 'browser/signMassage.sol': {content: ` + contract SignMassageTest { + function testRecovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) public pure returns (address) { + return ecrecover(h, v, r, s); + } + } + + library ECVerify { + function ecrecovery(bytes32 hash, bytes memory sig) public pure returns (address) { + bytes32 r; + bytes32 s; + uint8 v; + + if (sig.length != 65) { + return address(0); + } + + assembly { + r := mload(add(sig, 32)) + s := mload(add(sig, 64)) + v := and(mload(add(sig, 65)), 255) + } + + if (v < 27) { + v += 27; + } + + if (v != 27 && v != 28) { + return address(0); + } + + return ecrecover(hash, v, r, s); + } + + function ecverify(bytes32 hash, bytes memory sig, address signer) public pure returns (bool) { + return signer == ecrecovery(hash, sig); + } + }`} } ]