commit
408b44d1df
@ -0,0 +1,96 @@ |
|||||||
|
'use strict' |
||||||
|
import { NightwatchBrowser } from 'nightwatch' |
||||||
|
import init from '../helpers/init' |
||||||
|
|
||||||
|
declare global { |
||||||
|
interface Window { testplugin: { name: string, url: string }; } |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
'@disabled': true, |
||||||
|
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||||
|
init(browser, done, null, true, { name: 'vyper', url: 'http://127.0.0.1:5002'}) |
||||||
|
}, |
||||||
|
|
||||||
|
'Should connect to vyper plugin #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser.clickLaunchIcon('pluginManager') |
||||||
|
.scrollAndClick('[data-id="pluginManagerComponentActivateButtonvyper"]') |
||||||
|
.clickLaunchIcon('vyper') |
||||||
|
.pause(5000) |
||||||
|
// @ts-ignore
|
||||||
|
.frame(0) |
||||||
|
}, |
||||||
|
|
||||||
|
'Should add the Ballot.vy #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser.click('button[data-id="add-ballot"]') |
||||||
|
.frameParent() |
||||||
|
.openFile('ballot.vy') |
||||||
|
}, |
||||||
|
|
||||||
|
'Compile ballot.vy should error #group1': function (browser: NightwatchBrowser) { |
||||||
|
browser.clickLaunchIcon('vyper') |
||||||
|
// @ts-ignore
|
||||||
|
.frame(0) |
||||||
|
.click('[data-id="remote-compiler"]') |
||||||
|
.click('[data-id="compile"]') |
||||||
|
.assert.containsText('[data-id="error-message"]', 'unexpected indent') |
||||||
|
}, |
||||||
|
|
||||||
|
'Compile test contract should success #group1': function (browser: NightwatchBrowser) { |
||||||
|
let contractAddress |
||||||
|
browser |
||||||
|
.frameParent() |
||||||
|
.addFile('test.vy', { content: testContract }) |
||||||
|
.clickLaunchIcon('vyper') |
||||||
|
// @ts-ignore
|
||||||
|
.frame(0) |
||||||
|
.click('[data-id="compile"]') |
||||||
|
.frameParent() |
||||||
|
.clickLaunchIcon('udapp') |
||||||
|
.createContract('') |
||||||
|
.clickInstance(0) |
||||||
|
.clickFunction('totalPokemonCount - call') |
||||||
|
.getAddressAtPosition(0, (address) => { |
||||||
|
console.log('Vyper contract ' + address) |
||||||
|
contractAddress = address |
||||||
|
}) |
||||||
|
.perform((done) => { |
||||||
|
browser.verifyCallReturnValue(contractAddress, ['0:uint256: 0']) |
||||||
|
.perform(() => done()) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const testContract = ` |
||||||
|
# @version >=0.2.4 <0.3.0 |
||||||
|
|
||||||
|
DNA_DIGITS: constant(uint256) = 16 |
||||||
|
DNA_MODULUS: constant(uint256) = 10 ** DNA_DIGITS |
||||||
|
# add HP_LIMIT |
||||||
|
|
||||||
|
struct Pokemon: |
||||||
|
name: String[32] |
||||||
|
dna: uint256 |
||||||
|
HP: uint256 |
||||||
|
matches: uint256 |
||||||
|
wins: uint256 |
||||||
|
|
||||||
|
totalPokemonCount: public(uint256) |
||||||
|
pokemonList: HashMap[uint256, Pokemon] |
||||||
|
|
||||||
|
@pure |
||||||
|
@internal |
||||||
|
def _generateRandomDNA(_name: String[32]) -> uint256: |
||||||
|
random: uint256 = convert(keccak256(_name), uint256) |
||||||
|
return random % DNA_MODULUS |
||||||
|
# modify _createPokemon |
||||||
|
@internal |
||||||
|
def _createPokemon(_name: String[32], _dna: uint256, _HP: uint256): |
||||||
|
self.pokemonList[self.totalPokemonCount] = Pokemon({ |
||||||
|
name: _name, |
||||||
|
dna: _dna, |
||||||
|
HP: _HP, |
||||||
|
matches: 0, |
||||||
|
wins: 0 |
||||||
|
}) |
||||||
|
self.totalPokemonCount += 1` |
@ -0,0 +1,25 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
set -e |
||||||
|
|
||||||
|
BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}} |
||||||
|
echo "$BUILD_ID" |
||||||
|
TEST_EXITCODE=0 |
||||||
|
|
||||||
|
yarn run serve:production & |
||||||
|
npx nx serve vyper & |
||||||
|
|
||||||
|
sleep 5 |
||||||
|
|
||||||
|
yarn run build:e2e |
||||||
|
|
||||||
|
TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "vyper_api" | sort | circleci tests split ) |
||||||
|
for TESTFILE in $TESTFILES; do |
||||||
|
npx nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js $TESTFILE --env=chrome || TEST_EXITCODE=1 |
||||||
|
done |
||||||
|
|
||||||
|
echo "$TEST_EXITCODE" |
||||||
|
if [ "$TEST_EXITCODE" -eq 1 ] |
||||||
|
then |
||||||
|
exit 1 |
||||||
|
fi |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"presets": [ |
||||||
|
[ |
||||||
|
"@nrwl/react/babel", { |
||||||
|
"runtime": "automatic" |
||||||
|
|
||||||
|
} |
||||||
|
] |
||||||
|
], |
||||||
|
"plugins": [ |
||||||
|
|
||||||
|
] |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
# This file is used by: |
||||||
|
# 1. autoprefixer to adjust CSS to support the below specified browsers |
||||||
|
# 2. babel preset-env to adjust included polyfills |
||||||
|
# |
||||||
|
# For additional information regarding the format and rule options, please see: |
||||||
|
# https://github.com/browserslist/browserslist#queries |
||||||
|
# |
||||||
|
# If you need to support different browsers in production, you may tweak the list below. |
||||||
|
|
||||||
|
last 1 Chrome version |
||||||
|
last 1 Firefox version |
||||||
|
last 2 Edge major versions |
||||||
|
last 2 Safari major version |
||||||
|
last 2 iOS major versions |
||||||
|
Firefox ESR |
||||||
|
not IE 9-11 # For IE 9-11 support, remove 'not'. |
@ -0,0 +1,18 @@ |
|||||||
|
{ |
||||||
|
"env": { |
||||||
|
"browser": true, |
||||||
|
"es6": true |
||||||
|
}, |
||||||
|
"extends": "../../.eslintrc.json", |
||||||
|
"globals": { |
||||||
|
"Atomics": "readonly", |
||||||
|
"SharedArrayBuffer": "readonly" |
||||||
|
}, |
||||||
|
"parserOptions": { |
||||||
|
"ecmaVersion": 11, |
||||||
|
"sourceType": "module" |
||||||
|
}, |
||||||
|
"rules": { |
||||||
|
"standard/no-callback-literal": "off" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,124 @@ |
|||||||
|
* { |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
html, body, #root, main { |
||||||
|
height: 100%; |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
|
||||||
|
#vyper-plugin header { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
height: 50px; |
||||||
|
padding: 5px 15px; |
||||||
|
} |
||||||
|
|
||||||
|
#vyper-plugin header a, |
||||||
|
#vyper-plugin header .title { |
||||||
|
display: flex; |
||||||
|
height: 60%; |
||||||
|
} |
||||||
|
|
||||||
|
#vyper-plugin header svg, |
||||||
|
#vyper-plugin header img { |
||||||
|
width: auto; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
#vyper-plugin section { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
height: calc(100% - 50px); |
||||||
|
} |
||||||
|
|
||||||
|
.btn-group-toggle { |
||||||
|
width: 90%; |
||||||
|
text-transform: uppercase; |
||||||
|
margin: 10px 0; |
||||||
|
} |
||||||
|
|
||||||
|
.btn-group-toggle .btn { |
||||||
|
cursor: pointer; |
||||||
|
font-size: 0.8rem; |
||||||
|
} |
||||||
|
|
||||||
|
#local-url { |
||||||
|
width: 90%; |
||||||
|
} |
||||||
|
|
||||||
|
#compile-btn { |
||||||
|
width: 90%; |
||||||
|
} |
||||||
|
|
||||||
|
#compile-btn * { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
#result { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
width: 100%; |
||||||
|
padding-top: 10px; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
#result.error { |
||||||
|
justify-content: flex-start; |
||||||
|
} |
||||||
|
#result.error svg { |
||||||
|
width: 40px; |
||||||
|
height: 40px; |
||||||
|
margin: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
#result nav { |
||||||
|
width: 100%; |
||||||
|
flex-wrap: nowrap; |
||||||
|
justify-content: space-evenly; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#result nav a { |
||||||
|
padding: 0.5rem 1rem; |
||||||
|
font-size: 0.8rem; |
||||||
|
} |
||||||
|
|
||||||
|
#result .tab-content { |
||||||
|
flex: 1; |
||||||
|
width: 100%; |
||||||
|
padding: 15px; |
||||||
|
overflow: auto; |
||||||
|
} |
||||||
|
|
||||||
|
#result .tab-pane.active { |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: stretch; |
||||||
|
} |
||||||
|
|
||||||
|
#result .copy { |
||||||
|
padding: 5x; |
||||||
|
font-size: 0.8rem; |
||||||
|
margin: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
#result p { |
||||||
|
text-align: center; |
||||||
|
width: 100% !important; |
||||||
|
} |
||||||
|
|
||||||
|
#result .react-json-view { |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
|
||||||
|
#result textarea { |
||||||
|
border: none; |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
} |
@ -0,0 +1,118 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' |
||||||
|
|
||||||
|
import { VyperCompilationOutput, remixClient } from './utils' |
||||||
|
import { CompilationResult } from '@remixproject/plugin-api' |
||||||
|
|
||||||
|
// Components
|
||||||
|
import CompilerButton from './components/CompilerButton' |
||||||
|
import WarnRemote from './components/WarnRemote' |
||||||
|
import VyperResult from './components/VyperResult' |
||||||
|
import LocalUrlInput from './components/LocalUrl' |
||||||
|
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup' |
||||||
|
import ToggleButton from 'react-bootstrap/ToggleButton' |
||||||
|
|
||||||
|
import vyperLogo from './logo.svg' |
||||||
|
import './app.css' |
||||||
|
|
||||||
|
interface AppState { |
||||||
|
status: 'idle' | 'inProgress' |
||||||
|
environment: 'remote' | 'local' |
||||||
|
compilationResult?: CompilationResult |
||||||
|
localUrl: string |
||||||
|
} |
||||||
|
|
||||||
|
interface OutputMap { |
||||||
|
[fileName: string]: VyperCompilationOutput |
||||||
|
} |
||||||
|
|
||||||
|
const App: React.FC = () => { |
||||||
|
const [contract, setContract] = useState<string>() |
||||||
|
const [output, setOutput] = useState<OutputMap>({}) |
||||||
|
const [state, setState] = useState<AppState>({ |
||||||
|
status: 'idle', |
||||||
|
environment: 'local', |
||||||
|
localUrl: 'http://localhost:8000/compile' |
||||||
|
}) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
async function start() { |
||||||
|
try { |
||||||
|
await remixClient.loaded() |
||||||
|
remixClient.onFileChange(name => setContract(name)) |
||||||
|
const name = await remixClient.getContractName() |
||||||
|
setContract(name) |
||||||
|
} catch (err) { |
||||||
|
console.log(err) |
||||||
|
} |
||||||
|
} |
||||||
|
start() |
||||||
|
}, []) |
||||||
|
|
||||||
|
/** Update the environment state value */ |
||||||
|
function setEnvironment(environment: 'local' | 'remote') { |
||||||
|
setState({ ...state, environment }) |
||||||
|
} |
||||||
|
|
||||||
|
function setLocalUrl(url: string) { |
||||||
|
setState({ ...state, localUrl: url }) |
||||||
|
} |
||||||
|
|
||||||
|
function compilerUrl() { |
||||||
|
return state.environment === 'remote' |
||||||
|
? 'https://vyper.live/compile' |
||||||
|
: state.localUrl |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<main id="vyper-plugin"> |
||||||
|
<header className="bg-light"> |
||||||
|
<div className="title"> |
||||||
|
<img src={vyperLogo} alt="Vyper logo" /> |
||||||
|
<h4>yper Compiler</h4> |
||||||
|
</div> |
||||||
|
<a |
||||||
|
rel="noopener noreferrer" |
||||||
|
href="https://github.com/GrandSchtroumpf/vyper-remix" |
||||||
|
target="_blank" |
||||||
|
> |
||||||
|
<i className="fab fa-github"></i> |
||||||
|
</a> |
||||||
|
</header> |
||||||
|
<section> |
||||||
|
<ToggleButtonGroup |
||||||
|
name="remote" |
||||||
|
onChange={setEnvironment} |
||||||
|
type="radio" |
||||||
|
value={state.environment} |
||||||
|
> |
||||||
|
<ToggleButton data-id="remote-compiler" variant="secondary" name="remote" value="remote"> |
||||||
|
Remote Compiler |
||||||
|
</ToggleButton> |
||||||
|
<ToggleButton data-id="local-compiler" variant="secondary" name="local" value="local"> |
||||||
|
Local Compiler |
||||||
|
</ToggleButton> |
||||||
|
</ToggleButtonGroup> |
||||||
|
<LocalUrlInput |
||||||
|
url={state.localUrl} |
||||||
|
setUrl={setLocalUrl} |
||||||
|
environment={state.environment} |
||||||
|
/> |
||||||
|
<WarnRemote environment={state.environment} /> |
||||||
|
<div id="compile-btn"> |
||||||
|
<CompilerButton |
||||||
|
compilerUrl={compilerUrl()} |
||||||
|
contract={contract} |
||||||
|
setOutput={(name, update) => |
||||||
|
setOutput({ ...output, [name]: update }) |
||||||
|
} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<article id="result"> |
||||||
|
<VyperResult output={contract ? output[contract] : undefined} /> |
||||||
|
</article> |
||||||
|
</section> |
||||||
|
</main> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default App |
@ -0,0 +1,75 @@ |
|||||||
|
import React from 'react' |
||||||
|
import { |
||||||
|
isVyper, |
||||||
|
compile, |
||||||
|
toStandardOutput, |
||||||
|
VyperCompilationOutput, |
||||||
|
isCompilationError, |
||||||
|
remixClient |
||||||
|
} from '../utils' |
||||||
|
import Button from 'react-bootstrap/Button' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
compilerUrl: string |
||||||
|
contract?: string, |
||||||
|
setOutput: (name: string, output: VyperCompilationOutput) => void |
||||||
|
} |
||||||
|
|
||||||
|
function CompilerButton({ contract, setOutput, compilerUrl }: Props) { |
||||||
|
|
||||||
|
if (!contract || !contract) { |
||||||
|
return <Button disabled>No contract selected</Button> |
||||||
|
} |
||||||
|
|
||||||
|
if (!isVyper(contract)) { |
||||||
|
return <Button disabled>Not a vyper contract</Button> |
||||||
|
} |
||||||
|
|
||||||
|
/** Compile a Contract */ |
||||||
|
async function compileContract() { |
||||||
|
try { |
||||||
|
const _contract = await remixClient.getContract() |
||||||
|
remixClient.changeStatus({ |
||||||
|
key: 'loading', |
||||||
|
type: 'info', |
||||||
|
title: 'Compiling' |
||||||
|
}) |
||||||
|
const output = await compile(compilerUrl, _contract) |
||||||
|
setOutput(_contract.name, output) |
||||||
|
// ERROR
|
||||||
|
if (isCompilationError(output)) { |
||||||
|
const line = output.line |
||||||
|
const lineColumnPos = { |
||||||
|
start: { line: line - 1 }, |
||||||
|
end: { line: line - 1 } |
||||||
|
} |
||||||
|
remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4') |
||||||
|
throw new Error(output.message) |
||||||
|
} |
||||||
|
// SUCCESS
|
||||||
|
remixClient.discardHighlight() |
||||||
|
remixClient.changeStatus({ |
||||||
|
key: 'succeed', |
||||||
|
type: 'success', |
||||||
|
title: 'succeed' |
||||||
|
}) |
||||||
|
const data = toStandardOutput(_contract.name, output) |
||||||
|
remixClient.compilationFinish(_contract.name, _contract.content, data) |
||||||
|
} catch (err: any) { |
||||||
|
remixClient.changeStatus({ |
||||||
|
key: 'failed', |
||||||
|
type: 'error', |
||||||
|
title: err.message |
||||||
|
}) |
||||||
|
console.error(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<Button data-id="compile" onClick={compileContract} variant="primary"> |
||||||
|
Compile {contract} |
||||||
|
</Button> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CompilerButton |
@ -0,0 +1,36 @@ |
|||||||
|
import React from 'react' |
||||||
|
import Form from 'react-bootstrap/Form' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
url: string |
||||||
|
setUrl: (url: string) => void, |
||||||
|
environment: 'remote' | 'local' |
||||||
|
} |
||||||
|
|
||||||
|
function LocalUrlInput({ url, setUrl, environment }: Props) { |
||||||
|
|
||||||
|
if (environment === 'remote') { |
||||||
|
return <></> |
||||||
|
} |
||||||
|
|
||||||
|
function updateUrl(event: React.FocusEvent<HTMLInputElement>) { |
||||||
|
setUrl(event.target.value) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<Form id="local-url"> |
||||||
|
<Form.Group controlId="localUrl"> |
||||||
|
<Form.Label>Local Compiler Url</Form.Label> |
||||||
|
<Form.Control onBlur={updateUrl} |
||||||
|
defaultValue={url} |
||||||
|
type="email" |
||||||
|
placeholder="eg http://localhost:8000/compile" /> |
||||||
|
<Form.Text className="text-muted"> |
||||||
|
The url to your local compiler |
||||||
|
</Form.Text> |
||||||
|
</Form.Group> |
||||||
|
</Form> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default LocalUrlInput; |
@ -0,0 +1,70 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { |
||||||
|
VyperCompilationResult, |
||||||
|
VyperCompilationOutput, |
||||||
|
isCompilationError, |
||||||
|
remixClient |
||||||
|
} from '../utils'; |
||||||
|
import Tabs from 'react-bootstrap/Tabs' |
||||||
|
import Tab from 'react-bootstrap/Tab' |
||||||
|
import { Ballot } from '../examples/ballot'; |
||||||
|
import Button from 'react-bootstrap/Button'; |
||||||
|
import JSONTree from 'react-json-view' |
||||||
|
import { CopyToClipboard } from '@remix-ui/clipboard' |
||||||
|
|
||||||
|
|
||||||
|
interface VyperResultProps { |
||||||
|
output?: VyperCompilationOutput; |
||||||
|
} |
||||||
|
|
||||||
|
function VyperResult({ output }: VyperResultProps) { |
||||||
|
const [ active, setActive ] = useState<keyof VyperCompilationResult>('abi'); |
||||||
|
|
||||||
|
if (!output) return ( |
||||||
|
<div id="result"> |
||||||
|
<p>No contract compiled yet.</p> |
||||||
|
<Button data-id="add-ballot" variant="info" onClick={() => remixClient.loadContract(Ballot)}> |
||||||
|
Create Ballot.vy example |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
) |
||||||
|
|
||||||
|
if (isCompilationError(output)) { |
||||||
|
return ( |
||||||
|
<div id="result" className="error"> |
||||||
|
<i className="fas fa-exclamation-circle text-danger"></i> |
||||||
|
<p data-id="error-message" className="alert alert-danger">{output.message}</p> |
||||||
|
</div>) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<Tabs id="result" activeKey={active} onSelect={(key: any) => setActive(key)}> |
||||||
|
<Tab eventKey="abi" title="ABI"> |
||||||
|
<CopyToClipboard getContent={() => JSON.stringify(output.abi)}> |
||||||
|
<Button variant="info" className="copy">Copy ABI</Button> |
||||||
|
</CopyToClipboard> |
||||||
|
<JSONTree src={output.abi} /> |
||||||
|
</Tab> |
||||||
|
<Tab eventKey="bytecode" title="Bytecode"> |
||||||
|
<CopyToClipboard getContent={() => output.bytecode}> |
||||||
|
<Button variant="info" className="copy">Copy Bytecode</Button> |
||||||
|
</CopyToClipboard> |
||||||
|
<textarea defaultValue={output.bytecode}></textarea> |
||||||
|
</Tab> |
||||||
|
<Tab eventKey="bytecode_runtime" title="Runtime Bytecode"> |
||||||
|
<CopyToClipboard getContent={() => output.bytecode_runtime}> |
||||||
|
<Button variant="info" className="copy">Copy Runtime Bytecode</Button> |
||||||
|
</CopyToClipboard> |
||||||
|
<textarea defaultValue={output.bytecode_runtime}></textarea> |
||||||
|
</Tab> |
||||||
|
<Tab eventKey="ir" title="LLL"> |
||||||
|
<CopyToClipboard getContent={() => output.ir}> |
||||||
|
<Button variant="info" className="copy">Copy LLL Code</Button> |
||||||
|
</CopyToClipboard> |
||||||
|
<textarea defaultValue={output.ir}></textarea> |
||||||
|
</Tab> |
||||||
|
</Tabs> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default VyperResult; |
@ -0,0 +1,19 @@ |
|||||||
|
import React from 'react' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
environment: 'remote' | 'local' |
||||||
|
} |
||||||
|
|
||||||
|
function WarnRemoteLabel({ environment }: Props) { |
||||||
|
|
||||||
|
if (environment === 'local') { |
||||||
|
return <></> |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="alert alert-warning">It is really important to not use the remote compiler for production environment. |
||||||
|
Please only use it for testing purpose and prefer to using a local compiler for production like environment.</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default WarnRemoteLabel; |
@ -0,0 +1,161 @@ |
|||||||
|
export const Ballot = { |
||||||
|
name: 'browser/ballot.vy', |
||||||
|
content: `# Voting with delegation.
|
||||||
|
|
||||||
|
# Information about voters |
||||||
|
struct Voter: |
||||||
|
# weight is accumulated by delegation |
||||||
|
weight: int128 |
||||||
|
# if true, that person already voted (which includes voting by delegating) |
||||||
|
voted: bool |
||||||
|
# person delegated to |
||||||
|
delegate: address |
||||||
|
# index of the voted proposal, which is not meaningful unless 'voted' is True. |
||||||
|
vote: int128 |
||||||
|
|
||||||
|
# Users can create proposals |
||||||
|
struct Proposal: |
||||||
|
# short name (up to 32 bytes) |
||||||
|
name: bytes32 |
||||||
|
# number of accumulated votes |
||||||
|
voteCount: int128 |
||||||
|
|
||||||
|
voters: public(map(address, Voter)) |
||||||
|
proposals: public(map(int128, Proposal)) |
||||||
|
voterCount: public(int128) |
||||||
|
chairperson: public(address) |
||||||
|
int128Proposals: public(int128) |
||||||
|
|
||||||
|
|
||||||
|
@public |
||||||
|
@constant |
||||||
|
def delegated(addr: address) -> bool: |
||||||
|
return self.voters[addr].delegate != ZERO_ADDRESS |
||||||
|
|
||||||
|
|
||||||
|
@public |
||||||
|
@constant |
||||||
|
def directlyVoted(addr: address) -> bool: |
||||||
|
return self.voters[addr].voted and (self.voters[addr].delegate == ZERO_ADDRESS) |
||||||
|
|
||||||
|
|
||||||
|
# Setup global variables |
||||||
|
@public |
||||||
|
def __init__(_proposalNames: bytes32[2]): |
||||||
|
self.chairperson = msg.sender |
||||||
|
self.voterCount = 0 |
||||||
|
for i in range(2): |
||||||
|
self.proposals[i] = Proposal({ |
||||||
|
name: _proposalNames[i], |
||||||
|
voteCount: 0 |
||||||
|
}) |
||||||
|
self.int128Proposals += 1 |
||||||
|
|
||||||
|
# Give a 'voter' the right to vote on this ballot. |
||||||
|
# This may only be called by the 'chairperson'. |
||||||
|
@public |
||||||
|
def giveRightToVote(voter: address): |
||||||
|
# Throws if the sender is not the chairperson. |
||||||
|
assert msg.sender == self.chairperson |
||||||
|
# Throws if the voter has already voted. |
||||||
|
assert not self.voters[voter].voted |
||||||
|
# Throws if the voter's voting weight isn't 0. |
||||||
|
assert self.voters[voter].weight == 0 |
||||||
|
self.voters[voter].weight = 1 |
||||||
|
self.voterCount += 1 |
||||||
|
|
||||||
|
# Used by 'delegate' below, and can be called by anyone. |
||||||
|
@public |
||||||
|
def forwardWeight(delegate_with_weight_to_forward: address): |
||||||
|
assert self.delegated(delegate_with_weight_to_forward) |
||||||
|
# Throw if there is nothing to do: |
||||||
|
assert self.voters[delegate_with_weight_to_forward].weight > 0 |
||||||
|
|
||||||
|
target: address = self.voters[delegate_with_weight_to_forward].delegate |
||||||
|
for i in range(4): |
||||||
|
if self.delegated(target): |
||||||
|
target = self.voters[target].delegate |
||||||
|
# The following effectively detects cycles of length <= 5, |
||||||
|
# in which the delegation is given back to the delegator. |
||||||
|
# This could be done for any int128ber of loops, |
||||||
|
# or even infinitely with a while loop. |
||||||
|
# However, cycles aren't actually problematic for correctness; |
||||||
|
# they just result in spoiled votes. |
||||||
|
# So, in the production version, this should instead be |
||||||
|
# the responsibility of the contract's client, and this |
||||||
|
# check should be removed. |
||||||
|
assert target != delegate_with_weight_to_forward |
||||||
|
else: |
||||||
|
# Weight will be moved to someone who directly voted or |
||||||
|
# hasn't voted. |
||||||
|
break |
||||||
|
|
||||||
|
weight_to_forward: int128 = self.voters[delegate_with_weight_to_forward].weight |
||||||
|
self.voters[delegate_with_weight_to_forward].weight = 0 |
||||||
|
self.voters[target].weight += weight_to_forward |
||||||
|
|
||||||
|
if self.directlyVoted(target): |
||||||
|
self.proposals[self.voters[target].vote].voteCount += weight_to_forward |
||||||
|
self.voters[target].weight = 0 |
||||||
|
|
||||||
|
# To reiterate: if target is also a delegate, this function will need |
||||||
|
# to be called again, similarly to as above. |
||||||
|
|
||||||
|
# Delegate your vote to the voter 'to'. |
||||||
|
@public |
||||||
|
def delegate(to: address): |
||||||
|
# Throws if the sender has already voted |
||||||
|
assert not self.voters[msg.sender].voted |
||||||
|
# Throws if the sender tries to delegate their vote to themselves or to |
||||||
|
# the default address value of 0x0000000000000000000000000000000000000000 |
||||||
|
# (the latter might not be problematic, but I don't want to think about it). |
||||||
|
assert to != msg.sender |
||||||
|
assert to != ZERO_ADDRESS |
||||||
|
|
||||||
|
self.voters[msg.sender].voted = True |
||||||
|
self.voters[msg.sender].delegate = to |
||||||
|
|
||||||
|
# This call will throw if and only if this delegation would cause a loop |
||||||
|
# of length <= 5 that ends up delegating back to the delegator. |
||||||
|
self.forwardWeight(msg.sender) |
||||||
|
|
||||||
|
# Give your vote (including votes delegated to you) |
||||||
|
# to proposal 'proposals[proposal].name'. |
||||||
|
@public |
||||||
|
def vote(proposal: int128): |
||||||
|
# can't vote twice |
||||||
|
assert not self.voters[msg.sender].voted |
||||||
|
# can only vote on legitimate proposals |
||||||
|
assert proposal < self.int128Proposals |
||||||
|
|
||||||
|
self.voters[msg.sender].vote = proposal |
||||||
|
self.voters[msg.sender].voted = True |
||||||
|
|
||||||
|
# transfer msg.sender's weight to proposal |
||||||
|
self.proposals[proposal].voteCount += self.voters[msg.sender].weight |
||||||
|
self.voters[msg.sender].weight = 0 |
||||||
|
|
||||||
|
# Computes the winning proposal taking all |
||||||
|
# previous votes into account. |
||||||
|
@public |
||||||
|
@constant |
||||||
|
def winningProposal() -> int128: |
||||||
|
winning_vote_count: int128 = 0 |
||||||
|
winning_proposal: int128 = 0 |
||||||
|
for i in range(2): |
||||||
|
if self.proposals[i].voteCount > winning_vote_count: |
||||||
|
winning_vote_count = self.proposals[i].voteCount |
||||||
|
winning_proposal = i |
||||||
|
return winning_proposal |
||||||
|
|
||||||
|
# Calls winningProposal() function to get the index |
||||||
|
# of the winner contained in the proposals array and then |
||||||
|
# returns the name of the winner |
||||||
|
@public |
||||||
|
@constant |
||||||
|
def winnerName() -> bytes32: |
||||||
|
return self.proposals[self.winningProposal()].name |
||||||
|
|
||||||
|
` |
||||||
|
} |
||||||
|
|
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 347 B |
@ -0,0 +1,139 @@ |
|||||||
|
import { CompilationResult, ABIDescription } from "@remixproject/plugin-api"; |
||||||
|
|
||||||
|
export interface Contract { |
||||||
|
name: string; |
||||||
|
content: string; |
||||||
|
} |
||||||
|
|
||||||
|
export interface VyperCompilationResult { |
||||||
|
status: 'success', |
||||||
|
bytecode: string, |
||||||
|
bytecode_runtime: string, |
||||||
|
abi: ABIDescription[], |
||||||
|
ir: string, |
||||||
|
method_identifiers: { |
||||||
|
[method: string]: string |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export interface VyperCompilationError { |
||||||
|
status: 'failed' |
||||||
|
column: number |
||||||
|
line: number |
||||||
|
message: string |
||||||
|
} |
||||||
|
|
||||||
|
export type VyperCompilationOutput = VyperCompilationResult | VyperCompilationError |
||||||
|
|
||||||
|
/** Check if the output is an error */ |
||||||
|
export function isCompilationError(output: VyperCompilationOutput): output is VyperCompilationError { |
||||||
|
return output.status === 'failed' |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Compile the a contract |
||||||
|
* @param url The url of the compiler
|
||||||
|
* @param contract The name and content of the contract |
||||||
|
*/ |
||||||
|
export async function compile(url: string, contract: Contract): Promise<VyperCompilationOutput> { |
||||||
|
if (!contract.name) { |
||||||
|
throw new Error('Set your Vyper contract file.') |
||||||
|
} |
||||||
|
const extension = contract.name.split('.')[1] |
||||||
|
if (extension !== 'vy') { |
||||||
|
throw new Error('Use extension .vy for Vyper.') |
||||||
|
} |
||||||
|
const response = await fetch(url, { |
||||||
|
method: 'POST', |
||||||
|
headers: { 'Content-Type': 'application/json' }, |
||||||
|
body: JSON.stringify({ code: contract.content }) |
||||||
|
}) |
||||||
|
|
||||||
|
if (response.status === 404) { |
||||||
|
throw new Error(`Vyper compiler not found at "${url}".`) |
||||||
|
} |
||||||
|
/*if (response.status === 400) { |
||||||
|
throw new Error(`Vyper compilation failed: ${response.statusText}`) |
||||||
|
}*/ |
||||||
|
return response.json() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Transform Vyper Output to Solidity-like Compiler output |
||||||
|
* @param name Name of the contract file |
||||||
|
* @param compilationResult Result returned by the compiler |
||||||
|
*/ |
||||||
|
export function toStandardOutput(fileName: string, compilationResult: VyperCompilationResult): CompilationResult { |
||||||
|
const contractName = fileName.split('/').slice(-1)[0].split('.')[0]; |
||||||
|
const methodIdentifiers = JSON.parse(JSON.stringify(compilationResult['method_identifiers']).replace(/0x/g,'')); |
||||||
|
return { |
||||||
|
sources: { |
||||||
|
[fileName]: { |
||||||
|
id: 1, |
||||||
|
ast: {} as any, |
||||||
|
legacyAST: {} as any |
||||||
|
} |
||||||
|
}, |
||||||
|
contracts: { |
||||||
|
[fileName]: { |
||||||
|
// If the language used has no contract names, this field should equal to an empty string
|
||||||
|
[contractName]: { |
||||||
|
// The Ethereum Contract ABI. If empty, it is represented as an empty array.
|
||||||
|
// See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
||||||
|
abi: compilationResult['abi'], |
||||||
|
evm: { |
||||||
|
bytecode: { |
||||||
|
linkReferences: {}, |
||||||
|
object: compilationResult['bytecode'].replace('0x',''), |
||||||
|
opcodes: "" |
||||||
|
}, |
||||||
|
deployedBytecode: { |
||||||
|
linkReferences: {}, |
||||||
|
object: compilationResult['bytecode_runtime'].replace('0x',''), |
||||||
|
opcodes: "" |
||||||
|
}, |
||||||
|
methodIdentifiers: methodIdentifiers |
||||||
|
} |
||||||
|
} |
||||||
|
} as any |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
export function createCompilationResultMessage(name: string, result: any) { |
||||||
|
if(result.status == 'success') { |
||||||
|
return { |
||||||
|
bytecode: this.state.compilationResult['bytecode'], |
||||||
|
bytecode_runtime: this.state.compilationResult['bytecode_runtime'], |
||||||
|
abi: JSON.stringify(this.state.compilationResult['abi'], null , "\t"), |
||||||
|
ir: this.state.compilationResult['ir'] |
||||||
|
} |
||||||
|
} else if(result.status == 'failed' && result.column && result.line) { |
||||||
|
const header = `${name}:${result.line}:${result.column}` |
||||||
|
const body = this.state.compilationResult.message.split(/\r\n|\r|\n/) |
||||||
|
const arr = [header].concat(body).join("\n") |
||||||
|
return { |
||||||
|
bytecode: arr, |
||||||
|
bytecode_runtime: arr, |
||||||
|
abi: arr, |
||||||
|
ir: arr |
||||||
|
} |
||||||
|
} else if(result.status == 'failed') { |
||||||
|
const message = this.state.compilationResult.message |
||||||
|
return { |
||||||
|
bytecode: message, |
||||||
|
bytecode_runtime: message, |
||||||
|
abi: message, |
||||||
|
ir: message |
||||||
|
} |
||||||
|
} |
||||||
|
return { |
||||||
|
bytecode: "", |
||||||
|
bytecode_runtime: "", |
||||||
|
abi: "", |
||||||
|
ir: "" |
||||||
|
} |
||||||
|
} |
||||||
|
*/ |
@ -0,0 +1,12 @@ |
|||||||
|
export * from './compiler' |
||||||
|
export * from './remix-client' |
||||||
|
|
||||||
|
export function contractName(fileName: string): string { |
||||||
|
const parts = fileName.split('/') |
||||||
|
return parts[parts.length - 1] |
||||||
|
} |
||||||
|
|
||||||
|
export function isVyper(name: string): boolean { |
||||||
|
const parts = name.split('.') |
||||||
|
return parts[parts.length - 1] === 'vy' |
||||||
|
} |
@ -0,0 +1,71 @@ |
|||||||
|
import { HighlightPosition, CompilationResult, RemixApi } from '@remixproject/plugin-api'; |
||||||
|
import { Api, Status } from '@remixproject/plugin-utils'; |
||||||
|
import { createClient } from '@remixproject/plugin-webview' |
||||||
|
import { PluginClient } from '@remixproject/plugin'; |
||||||
|
import { Contract } from './compiler'; |
||||||
|
|
||||||
|
export class RemixClient extends PluginClient { |
||||||
|
private client = createClient<Api, Readonly<RemixApi>>(this); |
||||||
|
|
||||||
|
loaded() { |
||||||
|
return this.client.onload() |
||||||
|
} |
||||||
|
|
||||||
|
/** Emit an event when file changed */ |
||||||
|
async onFileChange(cb: (contract: string) => any) { |
||||||
|
this.client.on('fileManager', 'currentFileChanged', async (name: string) => { |
||||||
|
if (!name) return |
||||||
|
cb(name) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/** Load Ballot contract example into the file manager */ |
||||||
|
async loadContract({name, content}: Contract) { |
||||||
|
try { |
||||||
|
await this.client.call('fileManager', 'setFile', name, content) |
||||||
|
await this.client.call('fileManager', 'switchFile', name) |
||||||
|
} catch (err) { |
||||||
|
console.log(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Update the status of the plugin in remix */ |
||||||
|
changeStatus(status: Status) { |
||||||
|
this.client.emit('statusChanged', status); |
||||||
|
} |
||||||
|
|
||||||
|
/** Highlight a part of the editor */ |
||||||
|
highlight(lineColumnPos: HighlightPosition, name: string, color: string) { |
||||||
|
return this.client.call('editor', 'highlight', lineColumnPos, name, color) |
||||||
|
} |
||||||
|
|
||||||
|
/** Remove current Hightlight */ |
||||||
|
discardHighlight() { |
||||||
|
return this.client.call('editor', 'discardHighlight') |
||||||
|
} |
||||||
|
|
||||||
|
/** Get the name of the current contract */ |
||||||
|
async getContractName(): Promise<string> { |
||||||
|
await this.client.onload() |
||||||
|
return this.client.call('fileManager', 'getCurrentFile') |
||||||
|
} |
||||||
|
|
||||||
|
/** Get the current contract file */ |
||||||
|
async getContract(): Promise<Contract> { |
||||||
|
const name = await this.getContractName() |
||||||
|
if (!name) throw new Error('No contract selected yet') |
||||||
|
const content = await this.client.call('fileManager', 'getFile', name) |
||||||
|
return { |
||||||
|
name, |
||||||
|
content, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Emit an event to Remix with compilation result */ |
||||||
|
compilationFinish(title: string, content: string, data: CompilationResult) { |
||||||
|
this.client.emit('compilationFinished', title, content, 'vyper', data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const remixClient = new RemixClient() |
||||||
|
// export const RemixClientContext = React.createContext(new RemixClient())
|
@ -0,0 +1,3 @@ |
|||||||
|
export const environment = { |
||||||
|
production: true |
||||||
|
}; |
@ -0,0 +1,6 @@ |
|||||||
|
// This file can be replaced during build by using the `fileReplacements` array.
|
||||||
|
// When building for production, this file is replaced with `environment.prod.ts`.
|
||||||
|
|
||||||
|
export const environment = { |
||||||
|
production: false |
||||||
|
}; |
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,14 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8" /> |
||||||
|
<title>Vyper</title> |
||||||
|
<base href="/" /> |
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico" /> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="root"></div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,7 @@ |
|||||||
|
import { StrictMode } from 'react'; |
||||||
|
import * as ReactDOM from 'react-dom'; |
||||||
|
|
||||||
|
|
||||||
|
import App from './app/app'; |
||||||
|
|
||||||
|
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root')); |
@ -0,0 +1,7 @@ |
|||||||
|
/** |
||||||
|
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. |
||||||
|
* |
||||||
|
* See: https://github.com/zloirock/core-js#babel
|
||||||
|
*/ |
||||||
|
import 'core-js/stable'; |
||||||
|
import 'regenerator-runtime/runtime'; |
@ -0,0 +1 @@ |
|||||||
|
/* You can add global styles to this file, and also import other style files */ |
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "../../dist/out-tsc", |
||||||
|
"types": ["node"] |
||||||
|
}, |
||||||
|
"files": [ |
||||||
|
|
||||||
|
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||||
|
"../../node_modules/@nrwl/react/typings/image.d.ts" |
||||||
|
], |
||||||
|
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||||
|
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../tsconfig.base.json", |
||||||
|
"compilerOptions": { |
||||||
|
"jsx": "react-jsx", |
||||||
|
"allowJs": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"allowSyntheticDefaultImports": true, |
||||||
|
"forceConsistentCasingInFileNames": true, |
||||||
|
"strict": true, |
||||||
|
"noImplicitReturns": true, |
||||||
|
"noFallthroughCasesInSwitch": true |
||||||
|
}, |
||||||
|
"files": [], |
||||||
|
"include": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.app.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -1,12 +0,0 @@ |
|||||||
import "remix_tests.sol"; // this import is automatically injected by Remix. |
|
||||||
|
|
||||||
contract AssertOkTest { |
|
||||||
|
|
||||||
function okPassTest() public { |
|
||||||
Assert.ok(true, "okPassTest passes"); |
|
||||||
} |
|
||||||
|
|
||||||
function okFailTest() public { |
|
||||||
Assert.ok(false, "okFailTest fails"); |
|
||||||
} |
|
||||||
} |
|
File diff suppressed because it is too large
Load Diff
@ -1,59 +1,88 @@ |
|||||||
# Remixd |
# 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 |
[![npm version](https://badge.fury.io/js/%40remix-project%2Fremixd.svg)](https://www.npmjs.com/package/@remix-project/remixd) |
||||||
`Remix IDE` (web application) and the local computer. |
[![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 |
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: |
If you were using the old one you need to: |
||||||
|
|
||||||
1. uninstall the old one: `npm uninstall -g remixd` |
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 |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
## HELP SECTION |
The remixd command is: |
||||||
|
``` |
||||||
|
> remixd |
||||||
|
``` |
||||||
|
|
||||||
|
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. |
||||||
|
|
||||||
``` |
``` |
||||||
Usage: remixd -s <shared folder> |
> remixd -h |
||||||
|
Usage: remixd [options] |
||||||
|
|
||||||
Provide a two-way connection between the local computer and Remix IDE |
Establish a two-way websocket connection between the local computer and Remix IDE for a folder |
||||||
|
|
||||||
Options: |
Options: |
||||||
-v, --version output the version number |
-v, --version output the version number |
||||||
-u, --remix-ide <url> URL of remix instance allowed to connect to this web sockect connection |
-u, --remix-ide <url> URL of remix instance allowed to connect |
||||||
-s, --shared-folder <path> Folder to share with Remix IDE |
-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) |
-r, --read-only Treat shared folder as read-only (experimental) |
||||||
-h, --help output usage information |
-h, --help output usage information |
||||||
|
|
||||||
Example: |
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). |
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. |
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. |
- 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). |
- 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. |
- 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