commit
d0573a149c
@ -0,0 +1,287 @@ |
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
let firstProxyAddress: string |
||||
let lastProxyAddress: string |
||||
module.exports = { |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done) |
||||
}, |
||||
|
||||
'@sources': function () { |
||||
return sources |
||||
}, |
||||
|
||||
'Should show deploy proxy option for UUPS upgradeable contract': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.addFile('myTokenV1.sol', sources[0]['myTokenV1.sol']) |
||||
.clickLaunchIcon('solidity') |
||||
.pause(2000) |
||||
.click('[data-id="compilerContainerCompileBtn"]') |
||||
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000) |
||||
.clickLaunchIcon('udapp') |
||||
.click('select.udapp_contractNames') |
||||
.click('select.udapp_contractNames option[value=MyToken]') |
||||
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]') |
||||
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]') |
||||
}, |
||||
|
||||
'Should show upgrade proxy option for child contract inheriting UUPS parent contract': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.addFile('myTokenV2.sol', sources[1]['myTokenV2.sol']) |
||||
.clickLaunchIcon('solidity') |
||||
.pause(2000) |
||||
.click('[data-id="compilerContainerCompileBtn"]') |
||||
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000) |
||||
.clickLaunchIcon('udapp') |
||||
.click('select.udapp_contractNames') |
||||
.click('select.udapp_contractNames option[value=MyTokenV2]') |
||||
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]') |
||||
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]') |
||||
}, |
||||
|
||||
'Should deploy proxy without initialize parameters': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.openFile('myTokenV1.sol') |
||||
.clickLaunchIcon('solidity') |
||||
.pause(2000) |
||||
.click('[data-id="compilerContainerCompileBtn"]') |
||||
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000) |
||||
.clickLaunchIcon('udapp') |
||||
.click('select.udapp_contractNames') |
||||
.click('select.udapp_contractNames option[value=MyToken]') |
||||
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]') |
||||
.click('[data-id="contractGUIDeployWithProxyLabel"]') |
||||
.createContract('') |
||||
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.click('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') |
||||
}, |
||||
|
||||
'Should interact with deployed contract via ERC1967 (proxy)': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.getAddressAtPosition(1, (address) => { |
||||
firstProxyAddress = address |
||||
}) |
||||
.clickInstance(1) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(firstProxyAddress, 'name - call', null, '0:\nstring: MyToken').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(firstProxyAddress, 'symbol - call', null, '0:\nstring: MTK').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
}, |
||||
|
||||
'Should deploy proxy with initialize parameters': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementPresent('[data-id="deployAndRunClearInstances"]') |
||||
.click('[data-id="deployAndRunClearInstances"]') |
||||
.addFile('initializeProxy.sol', sources[2]['initializeProxy.sol']) |
||||
.clickLaunchIcon('solidity') |
||||
.pause(2000) |
||||
.click('[data-id="compilerContainerCompileBtn"]') |
||||
.waitForElementPresent('select[id="compiledContracts"] option[value=MyInitializedToken]', 60000) |
||||
.clickLaunchIcon('udapp') |
||||
.click('select.udapp_contractNames') |
||||
.click('select.udapp_contractNames option[value=MyInitializedToken]') |
||||
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]') |
||||
.click('[data-id="contractGUIDeployWithProxyLabel"]') |
||||
.waitForElementPresent('input[title="tokenName"]') |
||||
.waitForElementPresent('input[title="tokenSymbol"]') |
||||
.setValue('input[title="tokenName"]', 'Remix') |
||||
.setValue('input[title="tokenSymbol"]', "R") |
||||
.createContract('') |
||||
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.click('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') |
||||
}, |
||||
|
||||
'Should interact with initialized contract to verify parameters': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.getAddressAtPosition(1, (address) => { |
||||
lastProxyAddress = address |
||||
}) |
||||
.clickInstance(1) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(lastProxyAddress, 'name - call', null, '0:\nstring: Remix').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(lastProxyAddress, 'symbol - call', null, '0:\nstring: R').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
}, |
||||
|
||||
'Should upgrade contract using last deployed proxy address (MyTokenV1 to MyTokenV2)': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementPresent('[data-id="deployAndRunClearInstances"]') |
||||
.click('[data-id="deployAndRunClearInstances"]') |
||||
.openFile('myTokenV2.sol') |
||||
.clickLaunchIcon('solidity') |
||||
.pause(2000) |
||||
.click('[data-id="compilerContainerCompileBtn"]') |
||||
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000) |
||||
.clickLaunchIcon('udapp') |
||||
.click('select.udapp_contractNames') |
||||
.click('select.udapp_contractNames option[value=MyTokenV2]') |
||||
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]') |
||||
.click('[data-id="contractGUIUpgradeImplementationLabel"]') |
||||
.waitForElementPresent('[data-id="contractGUIProxyAddressLabel"]') |
||||
.click('[data-id="contractGUIProxyAddressLabel"]') |
||||
.waitForElementPresent('[data-id="lastDeployedERC1967Address"]') |
||||
.assert.containsText('[data-id="lastDeployedERC1967Address"]', lastProxyAddress) |
||||
.createContract('') |
||||
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy') |
||||
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.click('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') |
||||
}, |
||||
|
||||
'Should interact with upgraded function in contract MyTokenV2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickInstance(1) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(lastProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
}, |
||||
|
||||
'Should upgrade contract by providing proxy address in input field (MyTokenV1 to MyTokenV2)': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementPresent('[data-id="deployAndRunClearInstances"]') |
||||
.click('[data-id="deployAndRunClearInstances"]') |
||||
.openFile('myTokenV2.sol') |
||||
.clickLaunchIcon('solidity') |
||||
.pause(2000) |
||||
.click('[data-id="compilerContainerCompileBtn"]') |
||||
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000) |
||||
.clickLaunchIcon('udapp') |
||||
.click('select.udapp_contractNames') |
||||
.click('select.udapp_contractNames option[value=MyTokenV2]') |
||||
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]') |
||||
.waitForElementPresent('[data-id="contractGUIProxyAddressLabel"]') |
||||
.click('[data-id="contractGUIProxyAddressLabel"]') |
||||
.waitForElementPresent('[data-id="ERC1967AddressInput"]') |
||||
.setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress) |
||||
.createContract('') |
||||
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy') |
||||
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.click('[data-id="udappNotify-modal-footer-ok-react"]') |
||||
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)') |
||||
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]') |
||||
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]') |
||||
}, |
||||
|
||||
'Should interact with upgraded contract through provided proxy address': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickInstance(1) |
||||
.perform((done) => { |
||||
browser.testConstantFunction(firstProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => { |
||||
done() |
||||
}) |
||||
}) |
||||
.end() |
||||
} |
||||
} |
||||
|
||||
const sources = [ |
||||
{ |
||||
'myTokenV1.sol': { |
||||
content: ` |
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4; |
||||
|
||||
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; |
||||
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; |
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; |
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; |
||||
|
||||
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable { |
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() { |
||||
_disableInitializers(); |
||||
} |
||||
|
||||
function initialize() initializer public { |
||||
__ERC721_init("MyToken", "MTK"); |
||||
__Ownable_init(); |
||||
__UUPSUpgradeable_init(); |
||||
} |
||||
|
||||
function _authorizeUpgrade(address newImplementation) |
||||
internal |
||||
onlyOwner |
||||
override |
||||
{} |
||||
} |
||||
` |
||||
} |
||||
}, { |
||||
'myTokenV2.sol': { |
||||
content: ` |
||||
import "./myTokenV1.sol"; |
||||
|
||||
contract MyTokenV2 is MyToken { |
||||
function version () public view returns (string memory) { |
||||
return "MyTokenV2!"; |
||||
} |
||||
} |
||||
` |
||||
} |
||||
}, { |
||||
'initializeProxy.sol': { |
||||
content: ` |
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4; |
||||
|
||||
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; |
||||
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; |
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; |
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; |
||||
|
||||
contract MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable { |
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() { |
||||
_disableInitializers(); |
||||
} |
||||
|
||||
function initialize(string memory tokenName, string memory tokenSymbol) initializer public { |
||||
__ERC721_init(tokenName, tokenSymbol); |
||||
__Ownable_init(); |
||||
__UUPSUpgradeable_init(); |
||||
} |
||||
|
||||
function _authorizeUpgrade(address newImplementation) |
||||
internal |
||||
onlyOwner |
||||
override |
||||
{} |
||||
} |
||||
` |
||||
} |
||||
} |
||||
] |
File diff suppressed because one or more lines are too long
@ -1,59 +1,88 @@ |
||||
# Remixd |
||||
|
||||
`remixd` is a tool that intend to be used with [Remix IDE](https://github.com/ethereum/remix-project) (aka. Browser-Solidity). It allows a websocket connection between |
||||
`Remix IDE` (web application) and the local computer. |
||||
[![npm version](https://badge.fury.io/js/%40remix-project%2Fremixd.svg)](https://www.npmjs.com/package/@remix-project/remixd) |
||||
[![npm](https://img.shields.io/npm/dt/@remix-project/remixd.svg?label=Total%20Downloads&logo=npm)](https://www.npmjs.com/package/@remix-project/remixd) |
||||
[![npm](https://img.shields.io/npm/dw/@remix-project/remixd.svg?logo=npm)](https://www.npmjs.com/package/@remix-project/remixd) |
||||
|
||||
Practically Remix IDE makes available a folder shared by `remixd`. |
||||
|
||||
More details are explained in this [tutorial](https://remix-ide.readthedocs.io/en/latest/remixd.html). |
||||
`@remix-project/remixd` is an NPM module that intends to be used with [Remix IDE](https://remix.ethereum.org/) web and desktop applications. It establishes a two-way websocket connection between the local computer and Remix IDE for a particular project directory. |
||||
|
||||
Alternatively `remixd` can be used to setup a development environment that can be used with other popular frameworks like Embark, Truffle, Ganache, etc.. |
||||
`remixd` can be used to setup a development environment with other popular frameworks like Hardhat, Truffle, Slither etc. |
||||
|
||||
`remixd` needs `npm` and `node` |
||||
More details are explained in the [documentation](https://remix-ide.readthedocs.io/en/latest/remixd.html). |
||||
|
||||
## INSTALLATION |
||||
## Installation |
||||
|
||||
`yarn global add @remix-project/remixd` |
||||
`npm install -g @remix-project/remixd` |
||||
|
||||
### Warning for old users |
||||
NOTE: When the remixd NPM module is installed, it also installs [Slither](https://github.com/crytic/slither), [solc-select](https://github.com/crytic/solc-select#quickstart) and sets [solc](https://docs.soliditylang.org/en/latest/installing-solidity.html) to latest version i.e. 0.8.15 currently. |
||||
|
||||
ALSO NOTE: Python3.6+ (pip3) needs to already be installed on the System. In case of any discrepany, Slither can also installed along with other dependencies using command: |
||||
``` |
||||
> remixd -i slither |
||||
``` |
||||
|
||||
_(This packaging of Slither with the remixd module is supported since Remixd v0.6.3)_ |
||||
|
||||
### Warning for quite old users |
||||
There is a new version of remixd with a new npm address: https://npmjs.com/package/@remix-project/remixd |
||||
If you were using the old one you need to: |
||||
|
||||
1. uninstall the old one: `npm uninstall -g remixd` |
||||
2. install the new: `yarn global add @remix-project/remixd` |
||||
2. install the new: `npm install -g @remix-project/remixd` |
||||
|
||||
## remixd command |
||||
|
||||
## HELP SECTION |
||||
The remixd command without options shares present working directory and the shared Remix domain will be https://remix.ethereum.org, https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org |
||||
|
||||
The remixd command is: |
||||
``` |
||||
> remixd |
||||
``` |
||||
Usage: remixd -s <shared folder> |
||||
|
||||
Provide a two-way connection between the local computer and Remix IDE |
||||
If you are using Remix from localhost or you are not running the command from your working directory, you’ll need to use the command with flags. |
||||
|
||||
``` |
||||
> remixd -h |
||||
Usage: remixd [options] |
||||
|
||||
Establish a two-way websocket connection between the local computer and Remix IDE for a folder |
||||
|
||||
Options: |
||||
-v, --version output the version number |
||||
-u, --remix-ide <url> URL of remix instance allowed to connect to this web sockect connection |
||||
-s, --shared-folder <path> Folder to share with Remix IDE |
||||
-u, --remix-ide <url> URL of remix instance allowed to connect |
||||
-s, --shared-folder <path> Folder to share with Remix IDE (Default: CWD) |
||||
-i, --install <name> Module name to install locally (Supported: ["slither"]) |
||||
-r, --read-only Treat shared folder as read-only (experimental) |
||||
-h, --help output usage information |
||||
|
||||
Example: |
||||
|
||||
remixd -s ./ -u http://localhost:8080 |
||||
remixd -s ./shared_project -u http://localhost:8080 |
||||
|
||||
``` |
||||
|
||||
## SHARE A FOLDER |
||||
## Share a project directory |
||||
|
||||
`remixd -s <absolute-path> --remix-ide https://remix.ethereum.org` |
||||
`remixd -s ./shared_project -u https://remix.ethereum.org` |
||||
|
||||
The current user should have `read/write` access to the folder (at least `read` access). |
||||
|
||||
It is important to notice that changes made to the current file in `Remix IDE` are automatically saved to the local computer every 5000 ms. There is no `Save` action. But the `Ctrl-Z` (undo) can be used. |
||||
|
||||
Furthermore : |
||||
Furthermore: |
||||
- No copy of the shared folder are kept in the browser storage. |
||||
- It is not possible to create a file from `Remix IDE` (that might change). |
||||
- Clicking on the new folder or new file icon under localhost will create a new file or folder in the shared folder. |
||||
- If a folder does not contain any file, the folder will not be displayed in the explorer (that might change). |
||||
- Symbolic links are not forwarded to Remix IDE. |
||||
|
||||
## Ports Usage |
||||
remixd creates a websocket connections with Remix IDE on different ports. Ports are defined according to specific purpose. Port usage details are as: |
||||
|
||||
- **65520** : For `remixd` websocket listener, to share a project from local device with Remix IDE. Shared folder will be loaded in the Remix IDE File Explorer workspace named localhost [See more](https://remix-ide.readthedocs.io/en/latest/remixd.html) |
||||
- **65522** : For `Hardhat` websocket listener, to enable the Hardhat Compilation using Remix IDE Solidity Compiler plugin, if shared folder is a Hardhat project [See more](https://remix-ide.readthedocs.io/en/latest/hardhat.html) |
||||
- **65523** : For `Slither` websocket listener, to enable the Slither Analysis using Remix IDE Solidity Static Analysis plugin [See more](https://remix-ide.readthedocs.io/en/latest/slither.html) |
||||
- **65524** : For `Truffle` websocket listener, to enable the Truffle Compilation using Remix IDE Solidity Compiler plugin, if shared folder is a Truffle project [See more](https://remix-ide.readthedocs.io/en/latest/truffle.html) |
||||
|
||||
Note: Please make sure your system is secured enough and these ports are not opened nor forwarded. |
||||
|
||||
|
Loading…
Reference in new issue