commit
5d00926897
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"presets": ["@nrwl/react/babel"], |
||||||
|
"plugins": [] |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
{ |
||||||
|
"env": { |
||||||
|
"browser": true, |
||||||
|
"es6": true |
||||||
|
}, |
||||||
|
"extends": "../../../.eslintrc", |
||||||
|
"globals": { |
||||||
|
"Atomics": "readonly", |
||||||
|
"SharedArrayBuffer": "readonly" |
||||||
|
}, |
||||||
|
"parserOptions": { |
||||||
|
"ecmaVersion": 11, |
||||||
|
"sourceType": "module" |
||||||
|
}, |
||||||
|
"rules": { |
||||||
|
"standard/no-callback-literal": "off" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
# remix-ui-publish-to-storage |
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev). |
||||||
|
|
||||||
|
## Running unit tests |
||||||
|
|
||||||
|
Run `nx test remix-ui-publish-to-storage` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1 @@ |
|||||||
|
export * from './lib/publish-to-storage' |
@ -0,0 +1,110 @@ |
|||||||
|
import React, { useEffect, useState } from 'react' // eslint-disable-line
|
||||||
|
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
|
||||||
|
import { RemixUiPublishToStorageProps } from './types' // eslint-disable-line
|
||||||
|
import { publishToIPFS } from './publishToIPFS' |
||||||
|
import { publishToSwarm } from './publishOnSwarm' |
||||||
|
|
||||||
|
export const PublishToStorage = (props: RemixUiPublishToStorageProps) => { |
||||||
|
const { storage, fileProvider, fileManager, contract, resetStorage } = props |
||||||
|
const [state, setState] = useState({ |
||||||
|
modal: { |
||||||
|
title: '', |
||||||
|
message: null, |
||||||
|
hide: true, |
||||||
|
okLabel: '', |
||||||
|
okFn: null, |
||||||
|
cancelLabel: '', |
||||||
|
cancelFn: null |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const storageService = async () => { |
||||||
|
if ((contract.metadata === undefined || contract.metadata.length === 0)) { |
||||||
|
modal('Publish To Storage', 'This contract may be abstract, may not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.') |
||||||
|
} else { |
||||||
|
if (storage === 'swarm') { |
||||||
|
try { |
||||||
|
const result = await publishToSwarm(contract, fileManager) |
||||||
|
|
||||||
|
modal(`Published ${contract.name}'s Metadata`, publishMessage(result.uploaded)) |
||||||
|
// triggered each time there's a new verified publish (means hash correspond)
|
||||||
|
fileProvider.addExternal('swarm/' + result.item.hash, result.item.content) |
||||||
|
} catch (err) { |
||||||
|
let parseError = err |
||||||
|
try { |
||||||
|
parseError = JSON.stringify(err) |
||||||
|
} catch (e) {} |
||||||
|
modal('Swarm Publish Failed', publishMessageFailed(storage, parseError)) |
||||||
|
} |
||||||
|
} else { |
||||||
|
try { |
||||||
|
const result = await publishToIPFS(contract, fileManager) |
||||||
|
|
||||||
|
modal(`Published ${contract.name}'s Metadata`, publishMessage(result.uploaded)) |
||||||
|
// triggered each time there's a new verified publish (means hash correspond)
|
||||||
|
fileProvider.addExternal('ipfs/' + result.item.hash, result.item.content) |
||||||
|
} catch (err) { |
||||||
|
modal('IPFS Publish Failed', publishMessageFailed(storage, err)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (storage) { |
||||||
|
storageService() |
||||||
|
} |
||||||
|
}, [storage]) |
||||||
|
|
||||||
|
const publishMessage = (uploaded) => ( |
||||||
|
<span> Metadata of "{contract.name.toLowerCase()}" was published successfully. <br /> |
||||||
|
<pre> |
||||||
|
<div> |
||||||
|
{ uploaded.map((value, index) => <div key={index}><b>{ value.filename }</b> : <pre>{ value.output.url }</pre></div>) } |
||||||
|
</div> |
||||||
|
</pre> |
||||||
|
</span> |
||||||
|
) |
||||||
|
|
||||||
|
const publishMessageFailed = (storage, err) => ( |
||||||
|
<span>Failed to publish metadata file to { storage }, please check the { storage } gateways is available. <br /> |
||||||
|
{err} |
||||||
|
</span> |
||||||
|
) |
||||||
|
|
||||||
|
const handleHideModal = () => { |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, modal: { ...prevState.modal, hide: true, message: null } } |
||||||
|
}) |
||||||
|
resetStorage() |
||||||
|
} |
||||||
|
|
||||||
|
const modal = async (title: string, message: string | JSX.Element) => { |
||||||
|
await setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
modal: { |
||||||
|
...prevState.modal, |
||||||
|
hide: false, |
||||||
|
message, |
||||||
|
title |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<ModalDialog |
||||||
|
id='publishToStorage' |
||||||
|
title={ state.modal.title } |
||||||
|
message={ state.modal.message } |
||||||
|
hide={ state.modal.hide } |
||||||
|
okLabel='OK' |
||||||
|
okFn={() => {}} |
||||||
|
handleHide={ handleHideModal }> |
||||||
|
{ (typeof state.modal.message !== 'string') && state.modal.message } |
||||||
|
</ModalDialog> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default PublishToStorage |
@ -0,0 +1,109 @@ |
|||||||
|
import swarm from 'swarmgw' |
||||||
|
|
||||||
|
const swarmgw = swarm() |
||||||
|
|
||||||
|
export const publishToSwarm = async (contract, fileManager) => { |
||||||
|
// gather list of files to publish
|
||||||
|
const sources = [] |
||||||
|
let metadata |
||||||
|
const item = { content: null, hash: null } |
||||||
|
const uploaded = [] |
||||||
|
|
||||||
|
try { |
||||||
|
metadata = JSON.parse(contract.metadata) |
||||||
|
} catch (e) { |
||||||
|
throw new Error(e) |
||||||
|
} |
||||||
|
|
||||||
|
if (metadata === undefined) { |
||||||
|
throw new Error('No metadata') |
||||||
|
} |
||||||
|
|
||||||
|
await Promise.all(Object.keys(metadata.sources).map(fileName => { |
||||||
|
// find hash
|
||||||
|
let hash = null |
||||||
|
try { |
||||||
|
// 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) { |
||||||
|
throw new Error('Error while extracting the hash from metadata.json') |
||||||
|
} |
||||||
|
|
||||||
|
fileManager.fileProviderOf(fileName).get(fileName, (error, content) => { |
||||||
|
if (error) { |
||||||
|
console.log(error) |
||||||
|
} else { |
||||||
|
sources.push({ |
||||||
|
content: content, |
||||||
|
hash: hash, |
||||||
|
filename: fileName |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
})) |
||||||
|
// publish the list of sources in order, fail if any failed
|
||||||
|
|
||||||
|
await Promise.all(sources.map(async (item) => { |
||||||
|
try { |
||||||
|
const result = await swarmVerifiedPublish(item.content, item.hash) |
||||||
|
|
||||||
|
try { |
||||||
|
item.hash = result.url.match('bzz-raw://(.+)')[1] |
||||||
|
} catch (e) { |
||||||
|
item.hash = '<Metadata inconsistency> - ' + item.fileName |
||||||
|
} |
||||||
|
item.output = result |
||||||
|
uploaded.push(item) |
||||||
|
// TODO this is a fix cause Solidity metadata does not contain the right swarm hash (poc 0.3)
|
||||||
|
metadata.sources[item.filename].urls[0] = result.url |
||||||
|
} catch (error) { |
||||||
|
throw new Error(error) |
||||||
|
} |
||||||
|
})) |
||||||
|
|
||||||
|
const metadataContent = JSON.stringify(metadata) |
||||||
|
try { |
||||||
|
const result = await swarmVerifiedPublish(metadataContent, '') |
||||||
|
|
||||||
|
try { |
||||||
|
contract.metadataHash = result.url.match('bzz-raw://(.+)')[1] |
||||||
|
} catch (e) { |
||||||
|
contract.metadataHash = '<Metadata inconsistency> - metadata.json' |
||||||
|
} |
||||||
|
item.content = metadataContent |
||||||
|
item.hash = contract.metadataHash |
||||||
|
uploaded.push({ |
||||||
|
content: contract.metadata, |
||||||
|
hash: contract.metadataHash, |
||||||
|
filename: 'metadata.json', |
||||||
|
output: result |
||||||
|
}) |
||||||
|
} catch (error) { |
||||||
|
throw new Error(error) |
||||||
|
} |
||||||
|
|
||||||
|
return { uploaded, item } |
||||||
|
} |
||||||
|
|
||||||
|
const swarmVerifiedPublish = async (content, expectedHash): Promise<Record<string, any>> => { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
swarmgw.put(content, function (err, ret) { |
||||||
|
if (err) { |
||||||
|
reject(err) |
||||||
|
} else if (expectedHash && ret !== expectedHash) { |
||||||
|
resolve({ message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + ret, hash: ret }) |
||||||
|
} else { |
||||||
|
resolve({ message: 'ok', url: 'bzz-raw://' + ret, hash: ret }) |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
import IpfsClient from 'ipfs-mini' |
||||||
|
|
||||||
|
const ipfsNodes = [ |
||||||
|
new IpfsClient({ host: 'ipfs.komputing.org', port: 443, protocol: 'https' }), |
||||||
|
new IpfsClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' }), |
||||||
|
new IpfsClient({ host: '127.0.0.1', port: 5001, protocol: 'http' }) |
||||||
|
] |
||||||
|
|
||||||
|
export const publishToIPFS = async (contract, fileManager) => { |
||||||
|
// gather list of files to publish
|
||||||
|
const sources = [] |
||||||
|
let metadata |
||||||
|
const item = { content: null, hash: null } |
||||||
|
const uploaded = [] |
||||||
|
|
||||||
|
try { |
||||||
|
metadata = JSON.parse(contract.metadata) |
||||||
|
} catch (e) { |
||||||
|
throw new Error(e) |
||||||
|
} |
||||||
|
|
||||||
|
if (metadata === undefined) { |
||||||
|
throw new Error('No metadata') |
||||||
|
} |
||||||
|
|
||||||
|
await Promise.all(Object.keys(metadata.sources).map(fileName => { |
||||||
|
// find hash
|
||||||
|
let hash = null |
||||||
|
try { |
||||||
|
// 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) { |
||||||
|
throw new Error('Error while extracting the hash from metadata.json') |
||||||
|
} |
||||||
|
|
||||||
|
fileManager.fileProviderOf(fileName).get(fileName, (error, content) => { |
||||||
|
if (error) { |
||||||
|
console.log(error) |
||||||
|
} else { |
||||||
|
sources.push({ |
||||||
|
content: content, |
||||||
|
hash: hash, |
||||||
|
filename: fileName |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
})) |
||||||
|
// publish the list of sources in order, fail if any failed
|
||||||
|
await Promise.all(sources.map(async (item) => { |
||||||
|
try { |
||||||
|
const result = await ipfsVerifiedPublish(item.content, item.hash) |
||||||
|
|
||||||
|
try { |
||||||
|
item.hash = result.url.match('dweb:/ipfs/(.+)')[1] |
||||||
|
} catch (e) { |
||||||
|
item.hash = '<Metadata inconsistency> - ' + item.fileName |
||||||
|
} |
||||||
|
item.output = result |
||||||
|
uploaded.push(item) |
||||||
|
} catch (error) { |
||||||
|
throw new Error(error) |
||||||
|
} |
||||||
|
})) |
||||||
|
const metadataContent = JSON.stringify(metadata) |
||||||
|
|
||||||
|
try { |
||||||
|
const result = await ipfsVerifiedPublish(metadataContent, '') |
||||||
|
|
||||||
|
try { |
||||||
|
contract.metadataHash = result.url.match('dweb:/ipfs/(.+)')[1] |
||||||
|
} catch (e) { |
||||||
|
contract.metadataHash = '<Metadata inconsistency> - metadata.json' |
||||||
|
} |
||||||
|
item.content = metadataContent |
||||||
|
item.hash = contract.metadataHash |
||||||
|
uploaded.push({ |
||||||
|
content: contract.metadata, |
||||||
|
hash: contract.metadataHash, |
||||||
|
filename: 'metadata.json', |
||||||
|
output: result |
||||||
|
}) |
||||||
|
} catch (error) { |
||||||
|
throw new Error(error) |
||||||
|
} |
||||||
|
|
||||||
|
return { uploaded, item } |
||||||
|
} |
||||||
|
|
||||||
|
const ipfsVerifiedPublish = async (content, expectedHash) => { |
||||||
|
try { |
||||||
|
const results = await severalGatewaysPush(content) |
||||||
|
|
||||||
|
if (expectedHash && results !== expectedHash) { |
||||||
|
return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + results, hash: results } |
||||||
|
} else { |
||||||
|
return { message: 'ok', url: 'dweb:/ipfs/' + results, hash: results } |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
throw new Error(error) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const severalGatewaysPush = (content) => { |
||||||
|
const invert = p => new Promise((resolve, reject) => p.then(reject).catch(resolve)) // Invert res and rej
|
||||||
|
const promises = ipfsNodes.map((node) => invert(node.add(content))) |
||||||
|
|
||||||
|
return invert(Promise.all(promises)) |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
export interface RemixUiPublishToStorageProps { |
||||||
|
storage: string, |
||||||
|
fileProvider: any, |
||||||
|
fileManager: any, |
||||||
|
contract: any, |
||||||
|
resetStorage: () => void |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../../tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"jsx": "react", |
||||||
|
"allowJs": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"allowSyntheticDefaultImports": true |
||||||
|
}, |
||||||
|
"files": [], |
||||||
|
"include": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.lib.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"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,4 @@ |
|||||||
|
{ |
||||||
|
"presets": ["@nrwl/react/babel"], |
||||||
|
"plugins": [] |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"env": { |
||||||
|
"browser": true, |
||||||
|
"es6": true |
||||||
|
}, |
||||||
|
"extends": "../../../.eslintrc", |
||||||
|
"globals": { |
||||||
|
"Atomics": "readonly", |
||||||
|
"SharedArrayBuffer": "readonly" |
||||||
|
}, |
||||||
|
"parserOptions": { |
||||||
|
"ecmaVersion": 11, |
||||||
|
"sourceType": "module" |
||||||
|
}, |
||||||
|
"rules": { |
||||||
|
"no-unused-vars": "off", |
||||||
|
"@typescript-eslint/no-unused-vars": "error" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
# remix-ui-renderer |
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev). |
||||||
|
|
||||||
|
## Running unit tests |
||||||
|
|
||||||
|
Run `nx test remix-ui-renderer` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1 @@ |
|||||||
|
export * from './lib/renderer' |
@ -0,0 +1,47 @@ |
|||||||
|
.remixui_sol.success, |
||||||
|
.remixui_sol.error, |
||||||
|
.remixui_sol.warning { |
||||||
|
white-space: pre-line; |
||||||
|
word-wrap: break-word; |
||||||
|
cursor: pointer; |
||||||
|
position: relative; |
||||||
|
margin: 0.5em 0 1em 0; |
||||||
|
border-radius: 5px; |
||||||
|
line-height: 20px; |
||||||
|
padding: 8px 15px; |
||||||
|
} |
||||||
|
|
||||||
|
.remixui_sol.success pre, |
||||||
|
.remixui_sol.error pre, |
||||||
|
.remixui_sol.warning pre { |
||||||
|
white-space: pre-line; |
||||||
|
overflow-y: hidden; |
||||||
|
background-color: transparent; |
||||||
|
margin: 0; |
||||||
|
font-size: 12px; |
||||||
|
border: 0 none; |
||||||
|
padding: 0; |
||||||
|
border-radius: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.remixui_sol.success .close, |
||||||
|
.remixui_sol.error .close, |
||||||
|
.remixui_sol.warning .close { |
||||||
|
white-space: pre-line; |
||||||
|
font-weight: bold; |
||||||
|
position: absolute; |
||||||
|
color: hsl(0, 0%, 0%); /* black in style-guide.js */ |
||||||
|
top: 0; |
||||||
|
right: 0; |
||||||
|
padding: 0.5em; |
||||||
|
} |
||||||
|
|
||||||
|
.remixui_sol.error { |
||||||
|
} |
||||||
|
|
||||||
|
.remixui_sol.warning { |
||||||
|
} |
||||||
|
|
||||||
|
.remixui_sol.success { |
||||||
|
/* background-color: // styles.rightPanel.message_Success_BackgroundColor; */ |
||||||
|
} |
@ -0,0 +1,127 @@ |
|||||||
|
import React, { useEffect, useState } from 'react' //eslint-disable-line
|
||||||
|
import './renderer.css' |
||||||
|
interface RendererProps { |
||||||
|
message: any; |
||||||
|
opt?: any, |
||||||
|
plugin: any, |
||||||
|
editor: any, |
||||||
|
config: any, |
||||||
|
fileManager: any |
||||||
|
} |
||||||
|
|
||||||
|
export const Renderer = ({ message, opt = {}, editor, config, fileManager, plugin }: RendererProps) => { |
||||||
|
const [messageText, setMessageText] = useState(null) |
||||||
|
const [editorOptions, setEditorOptions] = useState({ |
||||||
|
useSpan: false, |
||||||
|
type: '', |
||||||
|
errFile: '' |
||||||
|
}) |
||||||
|
const [classList] = useState(opt.type === 'error' ? 'alert alert-danger' : 'alert alert-warning') |
||||||
|
const [close, setClose] = useState(false) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!message) return |
||||||
|
let text |
||||||
|
|
||||||
|
if (typeof message === 'string') { |
||||||
|
text = message |
||||||
|
} else if (message.innerText) { |
||||||
|
text = message.innerText |
||||||
|
} |
||||||
|
|
||||||
|
// ^ e.g:
|
||||||
|
// browser/gm.sol: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.6.12
|
||||||
|
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0/contracts/introspection/IERC1820Registry.sol:3:1: ParserError: Source file requires different compiler version (current compiler is 0.7.4+commit.3f05b770.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version
|
||||||
|
let positionDetails = getPositionDetails(text) |
||||||
|
const options = opt |
||||||
|
|
||||||
|
if (!positionDetails.errFile || (opt.errorType && opt.errorType === positionDetails.errFile)) { |
||||||
|
// Updated error reported includes '-->' before file details
|
||||||
|
const errorDetails = text.split('-->') |
||||||
|
// errorDetails[1] will have file details
|
||||||
|
if (errorDetails.length > 1) positionDetails = getPositionDetails(errorDetails[1]) |
||||||
|
} |
||||||
|
options.errLine = positionDetails.errLine |
||||||
|
options.errCol = positionDetails.errCol |
||||||
|
options.errFile = positionDetails.errFile.trim() |
||||||
|
|
||||||
|
if (!opt.noAnnotations && opt.errFile) { |
||||||
|
addAnnotation(opt.errFile, { |
||||||
|
row: opt.errLine, |
||||||
|
column: opt.errCol, |
||||||
|
text: text, |
||||||
|
type: opt.type |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
setMessageText(text) |
||||||
|
setEditorOptions(options) |
||||||
|
setClose(false) |
||||||
|
}, [message]) |
||||||
|
|
||||||
|
const getPositionDetails = (msg: any) => { |
||||||
|
const result = { } as Record<string, number | string> |
||||||
|
|
||||||
|
// To handle some compiler warning without location like SPDX license warning etc
|
||||||
|
if (!msg.includes(':')) return { errLine: -1, errCol: -1, errFile: msg } |
||||||
|
|
||||||
|
// extract line / column
|
||||||
|
let pos = msg.match(/^(.*?):([0-9]*?):([0-9]*?)?/) |
||||||
|
result.errLine = pos ? parseInt(pos[2]) - 1 : -1 |
||||||
|
result.errCol = pos ? parseInt(pos[3]) : -1 |
||||||
|
|
||||||
|
// extract file
|
||||||
|
pos = msg.match(/^(https:.*?|http:.*?|.*?):/) |
||||||
|
result.errFile = pos ? pos[1] : '' |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
const addAnnotation = (file, error) => { |
||||||
|
if (file === config.get('currentFile')) { |
||||||
|
plugin.call('editor', 'addAnnotation', error, file) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const handleErrorClick = (opt) => { |
||||||
|
if (opt.click) { |
||||||
|
opt.click(message) |
||||||
|
} else if (opt.errFile !== undefined && opt.errLine !== undefined && opt.errCol !== undefined) { |
||||||
|
_errorClick(opt.errFile, opt.errLine, opt.errCol) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const handleClose = () => { |
||||||
|
setClose(true) |
||||||
|
} |
||||||
|
|
||||||
|
const _errorClick = (errFile, errLine, errCol) => { |
||||||
|
if (errFile !== config.get('currentFile')) { |
||||||
|
// TODO: refactor with this._components.contextView.jumpTo
|
||||||
|
const provider = fileManager.fileProviderOf(errFile) |
||||||
|
if (provider) { |
||||||
|
provider.exists(errFile).then(() => { |
||||||
|
fileManager.open(errFile) |
||||||
|
editor.gotoLine(errLine, errCol) |
||||||
|
}).catch(error => { |
||||||
|
if (error) return console.log(error) |
||||||
|
}) |
||||||
|
} |
||||||
|
} else { |
||||||
|
editor.gotoLine(errLine, errCol) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
{ |
||||||
|
messageText && !close && ( |
||||||
|
<div className={`sol ${editorOptions.type} ${classList}`} data-id={editorOptions.errFile} onClick={() => handleErrorClick(editorOptions)}> |
||||||
|
{ editorOptions.useSpan ? <span> { messageText } </span> : <pre><span>{ messageText }</span></pre> } |
||||||
|
<div className="close" data-id="renderer" onClick={handleClose}> |
||||||
|
<i className="fas fa-times"></i> |
||||||
|
</div> |
||||||
|
</div>) |
||||||
|
} |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../../tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"jsx": "react", |
||||||
|
"allowJs": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"allowSyntheticDefaultImports": true |
||||||
|
}, |
||||||
|
"files": [], |
||||||
|
"include": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.lib.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"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,4 @@ |
|||||||
|
{ |
||||||
|
"presets": ["@nrwl/react/babel"], |
||||||
|
"plugins": [] |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"env": { |
||||||
|
"browser": true, |
||||||
|
"es6": true |
||||||
|
}, |
||||||
|
"extends": "../../../.eslintrc", |
||||||
|
"globals": { |
||||||
|
"Atomics": "readonly", |
||||||
|
"SharedArrayBuffer": "readonly" |
||||||
|
}, |
||||||
|
"parserOptions": { |
||||||
|
"ecmaVersion": 11, |
||||||
|
"sourceType": "module" |
||||||
|
}, |
||||||
|
"rules": { |
||||||
|
"no-unused-vars": "off", |
||||||
|
"@typescript-eslint/no-unused-vars": "error" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
# remix-ui-solidity-compiler |
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev). |
||||||
|
|
||||||
|
## Running unit tests |
||||||
|
|
||||||
|
Run `nx test remix-ui-solidity-compiler` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1,2 @@ |
|||||||
|
export * from './lib/solidity-compiler' |
||||||
|
export * from './lib/logic' |
@ -0,0 +1,57 @@ |
|||||||
|
import React from 'react' |
||||||
|
|
||||||
|
export const setEditorMode = (mode: string) => { |
||||||
|
return { |
||||||
|
type: 'SET_EDITOR_MODE', |
||||||
|
payload: mode |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const resetEditorMode = () => (dispatch: React.Dispatch<any>) => { |
||||||
|
dispatch({ |
||||||
|
type: 'RESET_EDITOR_MODE' |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export const setCompilerMode = (mode: string, ...args) => { |
||||||
|
return { |
||||||
|
type: 'SET_COMPILER_MODE', |
||||||
|
payload: { mode, args } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const resetCompilerMode = () => (dispatch: React.Dispatch<any>) => { |
||||||
|
dispatch({ |
||||||
|
type: 'RESET_COMPILER_MODE' |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export const listenToEvents = (editor, compileTabLogic) => (dispatch: React.Dispatch<any>) => { |
||||||
|
editor.event.register('sessionSwitched', () => { |
||||||
|
dispatch(setEditorMode('sessionSwitched')) |
||||||
|
}) |
||||||
|
|
||||||
|
compileTabLogic.event.on('startingCompilation', () => { |
||||||
|
dispatch(setCompilerMode('startingCompilation')) |
||||||
|
}) |
||||||
|
|
||||||
|
compileTabLogic.compiler.event.register('compilationDuration', (speed) => { |
||||||
|
dispatch(setCompilerMode('compilationDuration', speed)) |
||||||
|
}) |
||||||
|
|
||||||
|
editor.event.register('contentChanged', () => { |
||||||
|
dispatch(setEditorMode('contentChanged')) |
||||||
|
}) |
||||||
|
|
||||||
|
compileTabLogic.compiler.event.register('loadingCompiler', () => { |
||||||
|
dispatch(setCompilerMode('compilationDuration')) |
||||||
|
}) |
||||||
|
|
||||||
|
compileTabLogic.compiler.event.register('compilerLoaded', () => { |
||||||
|
dispatch(setCompilerMode('compilerLoaded')) |
||||||
|
}) |
||||||
|
|
||||||
|
compileTabLogic.compiler.event.register('compilationFinished', (success, data, source) => { |
||||||
|
dispatch(setCompilerMode('compilationFinished', success, data, source)) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,582 @@ |
|||||||
|
import React, { useEffect, useState, useRef, useReducer } from 'react' // eslint-disable-line
|
||||||
|
import semver from 'semver' |
||||||
|
import { CompilerContainerProps, ConfigurationSettings } from './types' |
||||||
|
import * as helper from '../../../../../apps/remix-ide/src/lib/helper' |
||||||
|
import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promisedMiniXhr } from '@remix-project/remix-solidity' // @ts-ignore
|
||||||
|
import { compilerReducer, compilerInitialState } from './reducers/compiler' |
||||||
|
import { resetEditorMode, listenToEvents } from './actions/compiler' |
||||||
|
|
||||||
|
import './css/style.css' |
||||||
|
|
||||||
|
export const CompilerContainer = (props: CompilerContainerProps) => { |
||||||
|
const { editor, config, queryParams, compileTabLogic, tooltip, modal, compiledFileName, setHardHatCompilation, updateCurrentVersion, isHardHatProject, configurationSettings } = props // eslint-disable-line
|
||||||
|
const [state, setState] = useState({ |
||||||
|
hideWarnings: false, |
||||||
|
autoCompile: false, |
||||||
|
optimise: false, |
||||||
|
compileTimeout: null, |
||||||
|
timeout: 300, |
||||||
|
allversions: [], |
||||||
|
customVersions: [], |
||||||
|
selectedVersion: null, |
||||||
|
defaultVersion: 'soljson-v0.8.4+commit.c7e474f2.js', // this default version is defined: in makeMockCompiler (for browser test)
|
||||||
|
selectedLanguage: '', |
||||||
|
runs: '', |
||||||
|
compiledFileName: '', |
||||||
|
includeNightlies: false, |
||||||
|
language: '', |
||||||
|
evmVersion: '' |
||||||
|
}) |
||||||
|
const compileIcon = useRef(null) |
||||||
|
const warningIcon = useRef(null) |
||||||
|
const promptMessageInput = useRef(null) |
||||||
|
const [hhCompilation, sethhCompilation] = useState(false) |
||||||
|
const [compilerContainer, dispatch] = useReducer(compilerReducer, compilerInitialState) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
fetchAllVersion((allversions, selectedVersion, isURL) => { |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, allversions } |
||||||
|
}) |
||||||
|
if (isURL) _updateVersionSelector(state.defaultVersion, selectedVersion) |
||||||
|
else { |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, selectedVersion } |
||||||
|
}) |
||||||
|
updateCurrentVersion(selectedVersion) |
||||||
|
_updateVersionSelector(selectedVersion) |
||||||
|
} |
||||||
|
}) |
||||||
|
const currentFileName = config.get('currentFile') |
||||||
|
|
||||||
|
currentFile(currentFileName) |
||||||
|
listenToEvents(editor, compileTabLogic)(dispatch) |
||||||
|
}, []) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (compileTabLogic && compileTabLogic.compiler) { |
||||||
|
setState(prevState => { |
||||||
|
const params = queryParams.get() |
||||||
|
const optimize = params.optimize === 'false' ? false : params.optimize === 'true' ? true : null |
||||||
|
const runs = params.runs |
||||||
|
const evmVersion = params.evmVersion |
||||||
|
|
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
hideWarnings: config.get('hideWarnings') || false, |
||||||
|
autoCompile: config.get('autoCompile') || false, |
||||||
|
includeNightlies: config.get('includeNightlies') || false, |
||||||
|
optimise: (optimize !== null) && (optimize !== undefined) ? optimize : config.get('optimise') || false, |
||||||
|
runs: (runs !== null) && (runs !== 'null') && (runs !== undefined) && (runs !== 'undefined') ? runs : 200, |
||||||
|
evmVersion: (evmVersion !== null) && (evmVersion !== 'null') && (evmVersion !== undefined) && (evmVersion !== 'undefined') ? evmVersion : 'default' |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
}, [compileTabLogic]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, compiledFileName } |
||||||
|
}) |
||||||
|
}, [compiledFileName]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (compilerContainer.compiler.mode) { |
||||||
|
switch (compilerContainer.compiler.mode) { |
||||||
|
case 'startingCompilation': |
||||||
|
startingCompilation() |
||||||
|
break |
||||||
|
case 'compilationDuration': |
||||||
|
compilationDuration(compilerContainer.compiler.args[0]) |
||||||
|
break |
||||||
|
case 'loadingCompiler': |
||||||
|
loadingCompiler() |
||||||
|
break |
||||||
|
case 'compilerLoaded': |
||||||
|
compilerLoaded() |
||||||
|
break |
||||||
|
case 'compilationFinished': |
||||||
|
compilationFinished() |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
}, [compilerContainer.compiler.mode]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (compilerContainer.editor.mode) { |
||||||
|
switch (compilerContainer.editor.mode) { |
||||||
|
case 'sessionSwitched': |
||||||
|
sessionSwitched() |
||||||
|
resetEditorMode()(dispatch) |
||||||
|
break |
||||||
|
case 'contentChanged': |
||||||
|
contentChanged() |
||||||
|
resetEditorMode()(dispatch) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
}, [compilerContainer.editor.mode]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (configurationSettings) { |
||||||
|
setConfiguration(configurationSettings) |
||||||
|
} |
||||||
|
}, [configurationSettings]) |
||||||
|
|
||||||
|
// fetching both normal and wasm builds and creating a [version, baseUrl] map
|
||||||
|
const fetchAllVersion = async (callback) => { |
||||||
|
let selectedVersion, allVersionsWasm, isURL |
||||||
|
let allVersions = [{ path: 'builtin', longVersion: 'latest local version - 0.7.4' }] |
||||||
|
// fetch normal builds
|
||||||
|
const binRes: any = await promisedMiniXhr(`${baseURLBin}/list.json`) |
||||||
|
// fetch wasm builds
|
||||||
|
const wasmRes: any = await promisedMiniXhr(`${baseURLWasm}/list.json`) |
||||||
|
if (binRes.event.type === 'error' && wasmRes.event.type === 'error') { |
||||||
|
selectedVersion = 'builtin' |
||||||
|
return callback(allVersions, selectedVersion) |
||||||
|
} |
||||||
|
try { |
||||||
|
const versions = JSON.parse(binRes.json).builds.slice().reverse() |
||||||
|
|
||||||
|
allVersions = [...allVersions, ...versions] |
||||||
|
selectedVersion = state.defaultVersion |
||||||
|
if (queryParams.get().version) selectedVersion = queryParams.get().version |
||||||
|
// Check if version is a URL and corresponding filename starts with 'soljson'
|
||||||
|
if (selectedVersion.startsWith('https://')) { |
||||||
|
const urlArr = selectedVersion.split('/') |
||||||
|
|
||||||
|
if (urlArr[urlArr.length - 1].startsWith('soljson')) isURL = true |
||||||
|
} |
||||||
|
if (wasmRes.event.type !== 'error') { |
||||||
|
allVersionsWasm = JSON.parse(wasmRes.json).builds.slice().reverse() |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
tooltip('Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload. Error: ' + e) |
||||||
|
} |
||||||
|
// replace in allVersions those compiler builds which exist in allVersionsWasm with new once
|
||||||
|
if (allVersionsWasm && allVersions) { |
||||||
|
allVersions.forEach((compiler, index) => { |
||||||
|
const wasmIndex = allVersionsWasm.findIndex(wasmCompiler => { return wasmCompiler.longVersion === compiler.longVersion }) |
||||||
|
if (wasmIndex !== -1) { |
||||||
|
allVersions[index] = allVersionsWasm[wasmIndex] |
||||||
|
pathToURL[compiler.path] = baseURLWasm |
||||||
|
} else { |
||||||
|
pathToURL[compiler.path] = baseURLBin |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
callback(allVersions, selectedVersion, isURL) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Update the compilation button with the name of the current file |
||||||
|
*/ |
||||||
|
const currentFile = (name = '') => { |
||||||
|
if (name && name !== '') { |
||||||
|
_setCompilerVersionFromPragma(name) |
||||||
|
} |
||||||
|
const compiledFileName = name.split('/').pop() |
||||||
|
|
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, compiledFileName } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// Load solc compiler version according to pragma in contract file
|
||||||
|
const _setCompilerVersionFromPragma = (filename: string) => { |
||||||
|
if (!state.allversions) return |
||||||
|
compileTabLogic.fileManager.readFile(filename).then(data => { |
||||||
|
const pragmaArr = data.match(/(pragma solidity (.+?);)/g) |
||||||
|
if (pragmaArr && pragmaArr.length === 1) { |
||||||
|
const pragmaStr = pragmaArr[0].replace('pragma solidity', '').trim() |
||||||
|
const pragma = pragmaStr.substring(0, pragmaStr.length - 1) |
||||||
|
const releasedVersions = state.allversions.filter(obj => !obj.prerelease).map(obj => obj.version) |
||||||
|
const allVersions = state.allversions.map(obj => _retrieveVersion(obj.version)) |
||||||
|
const currentCompilerName = _retrieveVersion(state.selectedVersion) |
||||||
|
// contains only numbers part, for example '0.4.22'
|
||||||
|
const pureVersion = _retrieveVersion() |
||||||
|
// is nightly build newer than the last release
|
||||||
|
const isNewestNightly = currentCompilerName.includes('nightly') && semver.gt(pureVersion, releasedVersions[0]) |
||||||
|
// checking if the selected version is in the pragma range
|
||||||
|
const isInRange = semver.satisfies(pureVersion, pragma) |
||||||
|
// checking if the selected version is from official compilers list(excluding custom versions) and in range or greater
|
||||||
|
const isOfficial = allVersions.includes(currentCompilerName) |
||||||
|
if (isOfficial && (!isInRange && !isNewestNightly)) { |
||||||
|
const compilerToLoad = semver.maxSatisfying(releasedVersions, pragma) |
||||||
|
const compilerPath = state.allversions.filter(obj => !obj.prerelease && obj.version === compilerToLoad)[0].path |
||||||
|
if (state.selectedVersion !== compilerPath) { |
||||||
|
setState((prevState) => { |
||||||
|
return { ...prevState, selectedVersion: compilerPath } |
||||||
|
}) |
||||||
|
_updateVersionSelector(compilerPath) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const isSolFileSelected = (currentFile = '') => { |
||||||
|
if (!currentFile) currentFile = config.get('currentFile') |
||||||
|
if (!currentFile) return false |
||||||
|
const extention = currentFile.substr(currentFile.length - 3, currentFile.length) |
||||||
|
return extention.toLowerCase() === 'sol' || extention.toLowerCase() === 'yul' |
||||||
|
} |
||||||
|
|
||||||
|
const sessionSwitched = () => { |
||||||
|
if (!compileIcon.current) return |
||||||
|
scheduleCompilation() |
||||||
|
} |
||||||
|
|
||||||
|
const startingCompilation = () => { |
||||||
|
if (!compileIcon.current) return |
||||||
|
compileIcon.current.setAttribute('title', 'compiling...') |
||||||
|
compileIcon.current.classList.remove('remixui_bouncingIcon') |
||||||
|
compileIcon.current.classList.add('remixui_spinningIcon') |
||||||
|
} |
||||||
|
|
||||||
|
const compilationDuration = (speed: number) => { |
||||||
|
if (!warningIcon.current) return |
||||||
|
if (speed > 1000) { |
||||||
|
const msg = `Last compilation took ${speed}ms. We suggest to turn off autocompilation.` |
||||||
|
|
||||||
|
warningIcon.current.setAttribute('title', msg) |
||||||
|
warningIcon.current.style.visibility = 'visible' |
||||||
|
} else { |
||||||
|
warningIcon.current.style.visibility = 'hidden' |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const contentChanged = () => { |
||||||
|
if (!compileIcon.current) return |
||||||
|
scheduleCompilation() |
||||||
|
compileIcon.current.classList.add('remixui_bouncingIcon') // @TODO: compileView tab
|
||||||
|
} |
||||||
|
|
||||||
|
const loadingCompiler = () => { |
||||||
|
if (!compileIcon.current) return |
||||||
|
compileIcon.current.setAttribute('title', 'compiler is loading, please wait a few moments.') |
||||||
|
compileIcon.current.classList.add('remixui_spinningIcon') |
||||||
|
warningIcon.current.style.visibility = 'hidden' |
||||||
|
_updateLanguageSelector() |
||||||
|
} |
||||||
|
|
||||||
|
const compilerLoaded = () => { |
||||||
|
if (!compileIcon.current) return |
||||||
|
compileIcon.current.setAttribute('title', '') |
||||||
|
compileIcon.current.classList.remove('remixui_spinningIcon') |
||||||
|
if (state.autoCompile) compile() |
||||||
|
} |
||||||
|
|
||||||
|
const compilationFinished = () => { |
||||||
|
if (!compileIcon.current) return |
||||||
|
compileIcon.current.setAttribute('title', 'idle') |
||||||
|
compileIcon.current.classList.remove('remixui_spinningIcon') |
||||||
|
compileIcon.current.classList.remove('remixui_bouncingIcon') |
||||||
|
} |
||||||
|
|
||||||
|
const scheduleCompilation = () => { |
||||||
|
if (!state.autoCompile) return |
||||||
|
if (state.compileTimeout) window.clearTimeout(state.compileTimeout) |
||||||
|
const compileTimeout = window.setTimeout(() => { |
||||||
|
state.autoCompile && compile() |
||||||
|
}, state.timeout) |
||||||
|
|
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, compileTimeout } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const compile = () => { |
||||||
|
const currentFile = config.get('currentFile') |
||||||
|
|
||||||
|
if (!isSolFileSelected()) return |
||||||
|
|
||||||
|
_setCompilerVersionFromPragma(currentFile) |
||||||
|
compileTabLogic.runCompiler() |
||||||
|
} |
||||||
|
|
||||||
|
const _retrieveVersion = (version?) => { |
||||||
|
if (!version) version = state.selectedVersion |
||||||
|
if (version === 'builtin') version = state.defaultVersion |
||||||
|
return semver.coerce(version) ? semver.coerce(version).version : '' |
||||||
|
} |
||||||
|
|
||||||
|
const _updateVersionSelector = (version, customUrl = '') => { |
||||||
|
// update selectedversion of previous one got filtered out
|
||||||
|
let selectedVersion = version |
||||||
|
if (!selectedVersion || !_shouldBeAdded(selectedVersion)) { |
||||||
|
selectedVersion = state.defaultVersion |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, selectedVersion } |
||||||
|
}) |
||||||
|
} |
||||||
|
updateCurrentVersion(selectedVersion) |
||||||
|
queryParams.update({ version: selectedVersion }) |
||||||
|
let url |
||||||
|
|
||||||
|
if (customUrl !== '') { |
||||||
|
selectedVersion = customUrl |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, selectedVersion, customVersions: [...state.customVersions, selectedVersion] } |
||||||
|
}) |
||||||
|
updateCurrentVersion(selectedVersion) |
||||||
|
url = customUrl |
||||||
|
queryParams.update({ version: selectedVersion }) |
||||||
|
} else if (selectedVersion === 'builtin') { |
||||||
|
let location: string | Location = window.document.location |
||||||
|
let path = location.pathname |
||||||
|
if (!path.startsWith('/')) path = '/' + path |
||||||
|
location = `${location.protocol}//${location.host}${path}assets/js` |
||||||
|
if (location.endsWith('index.html')) location = location.substring(0, location.length - 10) |
||||||
|
if (!location.endsWith('/')) location += '/' |
||||||
|
url = location + 'soljson.js' |
||||||
|
} else { |
||||||
|
if (selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(selectedVersion)) { |
||||||
|
return console.log('loading ' + selectedVersion + ' not allowed') |
||||||
|
} |
||||||
|
url = `${urlFromVersion(selectedVersion)}` |
||||||
|
} |
||||||
|
|
||||||
|
// Workers cannot load js on "file:"-URLs and we get a
|
||||||
|
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
|
||||||
|
// resort to non-worker version in that case.
|
||||||
|
if (selectedVersion !== 'builtin' && canUseWorker(selectedVersion)) { |
||||||
|
compileTabLogic.compiler.loadVersion(true, url) |
||||||
|
} else { |
||||||
|
compileTabLogic.compiler.loadVersion(false, url) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const _shouldBeAdded = (version) => { |
||||||
|
return !version.includes('nightly') || |
||||||
|
(version.includes('nightly') && state.includeNightlies) |
||||||
|
} |
||||||
|
|
||||||
|
const promptCompiler = () => { |
||||||
|
// custom url https://solidity-blog.s3.eu-central-1.amazonaws.com/data/08preview/soljson.js
|
||||||
|
modal('Add a custom compiler', promptMessage('URL'), 'OK', addCustomCompiler, 'Cancel', () => {}) |
||||||
|
} |
||||||
|
|
||||||
|
const promptMessage = (message) => { |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<span>{ message }</span> |
||||||
|
<input type="text" data-id="modalDialogCustomPromptCompiler" className="form-control" ref={promptMessageInput} /> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const addCustomCompiler = () => { |
||||||
|
const url = promptMessageInput.current.value |
||||||
|
|
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, selectedVersion: url } |
||||||
|
}) |
||||||
|
_updateVersionSelector(state.defaultVersion, url) |
||||||
|
} |
||||||
|
|
||||||
|
const handleLoadVersion = (value) => { |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, selectedVersion: value } |
||||||
|
}) |
||||||
|
updateCurrentVersion(value) |
||||||
|
_updateVersionSelector(value) |
||||||
|
_updateLanguageSelector() |
||||||
|
} |
||||||
|
|
||||||
|
const _updateLanguageSelector = () => { |
||||||
|
// This is the first version when Yul is available
|
||||||
|
if (!semver.valid(_retrieveVersion()) || semver.lt(_retrieveVersion(), 'v0.5.7+commit.6da8b019.js')) { |
||||||
|
handleLanguageChange('Solidity') |
||||||
|
compileTabLogic.setLanguage('Solidity') |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const handleAutoCompile = (e) => { |
||||||
|
const checked = e.target.checked |
||||||
|
|
||||||
|
config.set('autoCompile', checked) |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, autoCompile: checked } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const handleOptimizeChange = (value) => { |
||||||
|
const checked = !!value |
||||||
|
|
||||||
|
config.set('optimise', checked) |
||||||
|
compileTabLogic.setOptimize(checked) |
||||||
|
if (compileTabLogic.optimize) { |
||||||
|
compileTabLogic.setRuns(parseInt(state.runs)) |
||||||
|
} else { |
||||||
|
compileTabLogic.setRuns(200) |
||||||
|
} |
||||||
|
state.autoCompile && compile() |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, optimise: checked } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const onChangeRuns = (value) => { |
||||||
|
const runs = value |
||||||
|
|
||||||
|
compileTabLogic.setRuns(parseInt(runs)) |
||||||
|
state.autoCompile && compile() |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, runs } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const handleHideWarningsChange = (e) => { |
||||||
|
const checked = e.target.checked |
||||||
|
|
||||||
|
config.set('hideWarnings', checked) |
||||||
|
state.autoCompile && compile() |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, hideWarnings: checked } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const handleNightliesChange = (e) => { |
||||||
|
const checked = e.target.checked |
||||||
|
|
||||||
|
config.set('includeNightlies', checked) |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, includeNightlies: checked } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const handleLanguageChange = (value) => { |
||||||
|
compileTabLogic.setLanguage(value) |
||||||
|
state.autoCompile && compile() |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, language: value } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const handleEvmVersionChange = (value) => { |
||||||
|
if (!value) return |
||||||
|
let v = value |
||||||
|
if (v === 'default') { |
||||||
|
v = null |
||||||
|
} |
||||||
|
compileTabLogic.setEvmVersion(v) |
||||||
|
state.autoCompile && compile() |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, evmVersion: value } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const updatehhCompilation = (event) => { |
||||||
|
const checked = event.target.checked |
||||||
|
|
||||||
|
sethhCompilation(checked) |
||||||
|
setHardHatCompilation(checked) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
The following functions map with the above event handlers. |
||||||
|
They are an external API for modifying the compiler configuration. |
||||||
|
*/ |
||||||
|
const setConfiguration = (settings: ConfigurationSettings) => { |
||||||
|
handleLoadVersion(`soljson-v${settings.version}.js`) |
||||||
|
handleEvmVersionChange(settings.evmVersion) |
||||||
|
handleLanguageChange(settings.language) |
||||||
|
handleOptimizeChange(settings.optimize) |
||||||
|
onChangeRuns(settings.runs) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<section> |
||||||
|
<article> |
||||||
|
<header className='remixui_compilerSection border-bottom'> |
||||||
|
<div className="mb-2"> |
||||||
|
<label className="remixui_compilerLabel form-check-label" htmlFor="versionSelector"> |
||||||
|
Compiler |
||||||
|
<button className="far fa-plus-square border-0 p-0 mx-2 btn-sm" onClick={promptCompiler} title="Add a custom compiler with URL"></button> |
||||||
|
</label> |
||||||
|
<select value={ state.selectedVersion || state.defaultVersion } onChange={(e) => handleLoadVersion(e.target.value) } className="custom-select" id="versionSelector" disabled={state.allversions.length <= 0}> |
||||||
|
{ state.allversions.length <= 0 && <option disabled data-id={state.selectedVersion === state.defaultVersion ? 'selected' : ''}>{ state.defaultVersion }</option> } |
||||||
|
{ state.allversions.length <= 0 && <option disabled data-id={state.selectedVersion === 'builtin' ? 'selected' : ''}>builtin</option> } |
||||||
|
{ state.customVersions.map((url, i) => <option key={i} data-id={state.selectedVersion === url ? 'selected' : ''} value={url}>custom</option>)} |
||||||
|
{ state.allversions.map((build, i) => { |
||||||
|
return _shouldBeAdded(build.longVersion) |
||||||
|
? <option key={i} value={build.path} data-id={state.selectedVersion === build.path ? 'selected' : ''}>{build.longVersion}</option> |
||||||
|
: null |
||||||
|
}) |
||||||
|
} |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
<div className="mb-2 remixui_nightlyBuilds custom-control custom-checkbox"> |
||||||
|
<input className="mr-2 custom-control-input" id="nightlies" type="checkbox" onChange={handleNightliesChange} checked={state.includeNightlies} /> |
||||||
|
<label htmlFor="nightlies" data-id="compilerNightliesBuild" className="form-check-label custom-control-label">Include nightly builds</label> |
||||||
|
</div> |
||||||
|
<div className="mb-2"> |
||||||
|
<label className="remixui_compilerLabel form-check-label" htmlFor="compilierLanguageSelector">Language</label> |
||||||
|
<select onChange={(e) => handleLanguageChange(e.target.value)} value={state.language} className="custom-select" id="compilierLanguageSelector" title="Available since v0.5.7"> |
||||||
|
<option value='Solidity'>Solidity</option> |
||||||
|
<option value='Yul'>Yul</option> |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
<div className="mb-2"> |
||||||
|
<label className="remixui_compilerLabel form-check-label" htmlFor="evmVersionSelector">EVM Version</label> |
||||||
|
<select value={state.evmVersion} onChange={(e) => handleEvmVersionChange(e.target.value)} className="custom-select" id="evmVersionSelector"> |
||||||
|
<option data-id={state.evmVersion === 'default' ? 'selected' : ''} value="default">compiler default</option> |
||||||
|
<option data-id={state.evmVersion === 'muirGlacier' ? 'selected' : ''} value="muirGlacier">muirGlacier</option> |
||||||
|
<option data-id={state.evmVersion === 'istanbul' ? 'selected' : ''} value="istanbul">istanbul</option> |
||||||
|
<option data-id={state.evmVersion === 'petersburg' ? 'selected' : ''} value="petersburg">petersburg</option> |
||||||
|
<option data-id={state.evmVersion === 'constantinople' ? 'selected' : ''} value="constantinople">constantinople</option> |
||||||
|
<option data-id={state.evmVersion === 'byzantium' ? 'selected' : ''} value="byzantium">byzantium</option> |
||||||
|
<option data-id={state.evmVersion === 'spuriousDragon' ? 'selected' : ''} value="spuriousDragon">spuriousDragon</option> |
||||||
|
<option data-id={state.evmVersion === 'tangerineWhistle' ? 'selected' : ''} value="tangerineWhistle">tangerineWhistle</option> |
||||||
|
<option data-id={state.evmVersion === 'homestead' ? 'selected' : ''} value="homestead">homestead</option> |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
<div className="mt-3"> |
||||||
|
<p className="mt-2 remixui_compilerLabel">Compiler Configuration</p> |
||||||
|
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox"> |
||||||
|
<input className="remixui_autocompile custom-control-input" type="checkbox" onChange={handleAutoCompile} data-id="compilerContainerAutoCompile" id="autoCompile" title="Auto compile" checked={state.autoCompile} /> |
||||||
|
<label className="form-check-label custom-control-label" htmlFor="autoCompile">Auto compile</label> |
||||||
|
</div> |
||||||
|
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox"> |
||||||
|
<div className="justify-content-between align-items-center d-flex"> |
||||||
|
<input onChange={(e) => { handleOptimizeChange(e.target.checked) }} className="custom-control-input" id="optimize" type="checkbox" checked={state.optimise} /> |
||||||
|
<label className="form-check-label custom-control-label" htmlFor="optimize">Enable optimization</label> |
||||||
|
<input |
||||||
|
min="1" |
||||||
|
className="custom-select ml-2 remixui_runs" |
||||||
|
id="runs" |
||||||
|
placeholder="200" |
||||||
|
value={state.runs} |
||||||
|
type="number" |
||||||
|
title="Estimated number of times each opcode of the deployed code will be executed across the life-time of the contract." |
||||||
|
onChange={(e) => onChangeRuns(e.target.value)} |
||||||
|
disabled={!state.optimise} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox"> |
||||||
|
<input className="remixui_autocompile custom-control-input" onChange={handleHideWarningsChange} id="hideWarningsBox" type="checkbox" title="Hide warnings" checked={state.hideWarnings} /> |
||||||
|
<label className="form-check-label custom-control-label" htmlFor="hideWarningsBox">Hide warnings</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{ |
||||||
|
isHardHatProject && <div className="mt-2 remixui_compilerConfig custom-control custom-checkbox"> |
||||||
|
<input className="remixui_autocompile custom-control-input" onChange={updatehhCompilation} id="enableHardhat" type="checkbox" title="Enable Hardhat Compilation" checked={hhCompilation} /> |
||||||
|
<label className="form-check-label custom-control-label" htmlFor="enableHardhat">Enable Hardhat Compilation</label> |
||||||
|
</div> |
||||||
|
} |
||||||
|
<button id="compileBtn" data-id="compilerContainerCompileBtn" className="btn btn-primary btn-block remixui_disabled mt-3" title="Compile" onClick={compile} disabled={!state.compiledFileName || (state.compiledFileName && !isSolFileSelected(state.compiledFileName))}> |
||||||
|
<span> |
||||||
|
<i ref={warningIcon} title="Compilation Slow" style={{ visibility: 'hidden' }} className="remixui_warnCompilationSlow fas fa-exclamation-triangle" aria-hidden="true"></i> |
||||||
|
{ warningIcon.current && warningIcon.current.style.visibility === 'hidden' && <i ref={compileIcon} className="fas fa-sync remixui_icon" aria-hidden="true"></i> } |
||||||
|
Compile { state.compiledFileName || '<no file selected>' } |
||||||
|
</span> |
||||||
|
</button> |
||||||
|
</header> |
||||||
|
</article> |
||||||
|
</section> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CompilerContainer |
@ -0,0 +1,238 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' // eslint-disable-line
|
||||||
|
import { ContractSelectionProps } from './types' |
||||||
|
import { PublishToStorage } from '@remix-ui/publish-to-storage' // eslint-disable-line
|
||||||
|
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' // eslint-disable-line
|
||||||
|
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
|
||||||
|
|
||||||
|
import './css/style.css' |
||||||
|
|
||||||
|
export const ContractSelection = (props: ContractSelectionProps) => { |
||||||
|
const { contractMap, fileProvider, fileManager, contractsDetails, modal } = props |
||||||
|
const [contractList, setContractList] = useState([]) |
||||||
|
const [selectedContract, setSelectedContract] = useState('') |
||||||
|
const [storage, setStorage] = useState(null) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const contractList = contractMap ? Object.keys(contractMap).map((key) => ({ |
||||||
|
name: key, |
||||||
|
file: getFileName(contractMap[key].file) |
||||||
|
})) : [] |
||||||
|
|
||||||
|
setContractList(contractList) |
||||||
|
if (contractList.length) setSelectedContract(contractList[0].name) |
||||||
|
}, [contractMap, contractsDetails]) |
||||||
|
|
||||||
|
const resetStorage = () => { |
||||||
|
setStorage('') |
||||||
|
} |
||||||
|
|
||||||
|
// Return the file name of a path: ex "browser/ballot.sol" -> "ballot.sol"
|
||||||
|
const getFileName = (path) => { |
||||||
|
const part = path.split('/') |
||||||
|
|
||||||
|
return part[part.length - 1] |
||||||
|
} |
||||||
|
|
||||||
|
const handleContractChange = (contractName: string) => { |
||||||
|
setSelectedContract(contractName) |
||||||
|
} |
||||||
|
|
||||||
|
const handlePublishToStorage = (type) => { |
||||||
|
setStorage(type) |
||||||
|
} |
||||||
|
|
||||||
|
const copyABI = () => { |
||||||
|
return copyContractProperty('abi') |
||||||
|
} |
||||||
|
|
||||||
|
const copyContractProperty = (property) => { |
||||||
|
let content = getContractProperty(property) |
||||||
|
if (!content) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
if (typeof content !== 'string') { |
||||||
|
content = JSON.stringify(content, null, '\t') |
||||||
|
} |
||||||
|
} catch (e) {} |
||||||
|
|
||||||
|
return content |
||||||
|
} |
||||||
|
|
||||||
|
const getContractProperty = (property) => { |
||||||
|
if (!selectedContract) throw new Error('No contract compiled yet') |
||||||
|
const contractProperties = contractsDetails[selectedContract] |
||||||
|
|
||||||
|
if (contractProperties && contractProperties[property]) return contractProperties[property] |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
const renderData = (item, key: string | number, keyPath: string) => { |
||||||
|
const data = extractData(item) |
||||||
|
const children = (data.children || []).map((child) => renderData(child.value, child.key, keyPath + '/' + child.key)) |
||||||
|
|
||||||
|
if (children && children.length > 0) { |
||||||
|
return ( |
||||||
|
<TreeViewItem id={`treeViewItem${key}`} key={keyPath} label={ |
||||||
|
<div className="d-flex mt-2 flex-row remixui_label_item"> |
||||||
|
<label className="small font-weight-bold pr-1 remixui_label_key">{ key }:</label> |
||||||
|
<label className="m-0 remixui_label_value">{ typeof data.self === 'boolean' ? `${data.self}` : data.self }</label> |
||||||
|
</div> |
||||||
|
}> |
||||||
|
<TreeView id={`treeView${key}`} key={keyPath}> |
||||||
|
{children} |
||||||
|
</TreeView> |
||||||
|
</TreeViewItem> |
||||||
|
) |
||||||
|
} else { |
||||||
|
return <TreeViewItem id={key.toString()} key={keyPath} label={ |
||||||
|
<div className="d-flex mt-2 flex-row remixui_label_item"> |
||||||
|
<label className="small font-weight-bold pr-1 remixui_label_key">{ key }:</label> |
||||||
|
<label className="m-0 remixui_label_value">{ typeof data.self === 'boolean' ? `${data.self}` : data.self }</label> |
||||||
|
</div> |
||||||
|
} /> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const extractData = (item) => { |
||||||
|
const ret = { children: null, self: null } |
||||||
|
|
||||||
|
if (item instanceof Array) { |
||||||
|
ret.children = item.map((item, index) => ({ key: index, value: item })) |
||||||
|
ret.self = '' |
||||||
|
} else if (item instanceof Object) { |
||||||
|
ret.children = Object.keys(item).map((key) => ({ key: key, value: item[key] })) |
||||||
|
ret.self = '' |
||||||
|
} else { |
||||||
|
ret.self = item |
||||||
|
ret.children = [] |
||||||
|
} |
||||||
|
return ret |
||||||
|
} |
||||||
|
|
||||||
|
const insertValue = (details, propertyName) => { |
||||||
|
let node |
||||||
|
if (propertyName === 'web3Deploy' || propertyName === 'name' || propertyName === 'Assembly') { |
||||||
|
node = <pre>{ details[propertyName] }</pre> |
||||||
|
} else if (propertyName === 'abi' || propertyName === 'metadata') { |
||||||
|
if (details[propertyName] !== '') { |
||||||
|
try { |
||||||
|
node = <div> |
||||||
|
{ (typeof details[propertyName] === 'object') |
||||||
|
? <TreeView id="treeView"> |
||||||
|
{ |
||||||
|
Object.keys(details[propertyName]).map((innerkey) => renderData(details[propertyName][innerkey], innerkey, innerkey)) |
||||||
|
} |
||||||
|
</TreeView> : <TreeView id="treeView"> |
||||||
|
{ |
||||||
|
Object.keys(JSON.parse(details[propertyName])).map((innerkey) => renderData(JSON.parse(details[propertyName])[innerkey], innerkey, innerkey)) |
||||||
|
} |
||||||
|
</TreeView> |
||||||
|
} |
||||||
|
</div> // catch in case the parsing fails.
|
||||||
|
} catch (e) { |
||||||
|
node = <div>Unable to display "${propertyName}": ${e.message}</div> |
||||||
|
} |
||||||
|
} else { |
||||||
|
node = <div> - </div> |
||||||
|
} |
||||||
|
} else { |
||||||
|
node = <div>{JSON.stringify(details[propertyName], null, 4)}</div> |
||||||
|
} |
||||||
|
return <pre className="remixui_value">{node || ''}</pre> |
||||||
|
} |
||||||
|
|
||||||
|
const details = () => { |
||||||
|
if (!selectedContract) throw new Error('No contract compiled yet') |
||||||
|
|
||||||
|
const help = { |
||||||
|
Assembly: 'Assembly opcodes describing the contract including corresponding solidity source code', |
||||||
|
Opcodes: 'Assembly opcodes describing the contract', |
||||||
|
'Runtime Bytecode': 'Bytecode storing the state and being executed during normal contract call', |
||||||
|
bytecode: 'Bytecode being executed during contract creation', |
||||||
|
functionHashes: 'List of declared function and their corresponding hash', |
||||||
|
gasEstimates: 'Gas estimation for each function call', |
||||||
|
metadata: 'Contains all informations related to the compilation', |
||||||
|
metadataHash: 'Hash representing all metadata information', |
||||||
|
abi: 'ABI: describing all the functions (input/output params, scope, ...)', |
||||||
|
name: 'Name of the compiled contract', |
||||||
|
swarmLocation: 'Swarm url where all metadata information can be found (contract needs to be published first)', |
||||||
|
web3Deploy: 'Copy/paste this code to any JavaScript/Web3 console to deploy this contract' |
||||||
|
} |
||||||
|
const contractProperties = contractsDetails[selectedContract] || {} |
||||||
|
const log = <div className="remixui_detailsJSON"> |
||||||
|
{ |
||||||
|
Object.keys(contractProperties).map((propertyName, index) => { |
||||||
|
const copyDetails = <span className="remixui_copyDetails"><CopyToClipboard content={contractProperties[propertyName]} direction='top' /></span> |
||||||
|
const questionMark = <span className="remixui_questionMark"><i title={ help[propertyName] } className="fas fa-question-circle" aria-hidden="true"></i></span> |
||||||
|
|
||||||
|
return (<div className="remixui_log" key={index}> |
||||||
|
<div className="remixui_key">{ propertyName } { copyDetails } { questionMark }</div> |
||||||
|
{ insertValue(contractProperties, propertyName) } |
||||||
|
</div>) |
||||||
|
}) |
||||||
|
} |
||||||
|
</div> |
||||||
|
|
||||||
|
modal(selectedContract, log, 'Close', null) |
||||||
|
} |
||||||
|
|
||||||
|
const copyBytecode = () => { |
||||||
|
return copyContractProperty('bytecode') |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
// define swarm logo
|
||||||
|
<> |
||||||
|
{ contractList.length |
||||||
|
? <section className="remixui_compilerSection pt-3"> |
||||||
|
{/* Select Compiler Version */} |
||||||
|
<div className="mb-3"> |
||||||
|
<label className="remixui_compilerLabel form-check-label" htmlFor="compiledContracts">Contract</label> |
||||||
|
<select onChange={(e) => handleContractChange(e.target.value)} value={selectedContract} data-id="compiledContracts" id="compiledContracts" className="custom-select"> |
||||||
|
{ contractList.map(({ name, file }, index) => <option value={name} key={index}>{name} ({file})</option>)} |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
<article className="mt-2 pb-0"> |
||||||
|
<button id="publishOnSwarm" className="btn btn-secondary btn-block" title="Publish on Swarm" onClick={() => { handlePublishToStorage('swarm') }}> |
||||||
|
<span>Publish on Swarm</span> |
||||||
|
<img id="swarmLogo" className="remixui_storageLogo ml-2" src="assets/img/swarm.webp" /> |
||||||
|
</button> |
||||||
|
<button id="publishOnIpfs" className="btn btn-secondary btn-block" title="Publish on Ipfs" onClick={() => { handlePublishToStorage('ipfs') }}> |
||||||
|
<span>Publish on Ipfs</span> |
||||||
|
<img id="ipfsLogo" className="remixui_storageLogo ml-2" src="assets/img/ipfs.webp" /> |
||||||
|
</button> |
||||||
|
<button data-id="compilation-details" className="btn btn-secondary btn-block" title="Display Contract Details" onClick={() => { details() }}> |
||||||
|
Compilation Details |
||||||
|
</button> |
||||||
|
{/* Copy to Clipboard */} |
||||||
|
<div className="remixui_contractHelperButtons"> |
||||||
|
<div className="input-group"> |
||||||
|
<div className="btn-group" role="group" aria-label="Copy to Clipboard"> |
||||||
|
<CopyToClipboard title="Copy ABI to clipboard" content={copyABI()} direction='top'> |
||||||
|
<button className="btn remixui_copyButton" title="Copy ABI to clipboard"> |
||||||
|
<i className="remixui_copyIcon far fa-copy" aria-hidden="true"></i> |
||||||
|
<span>ABI</span> |
||||||
|
</button> |
||||||
|
</CopyToClipboard> |
||||||
|
<CopyToClipboard title="Copy ABI to clipboard" content={copyBytecode()} direction='top'> |
||||||
|
<button className="btn remixui_copyButton" title="Copy Bytecode to clipboard"> |
||||||
|
<i className="remixui_copyIcon far fa-copy" aria-hidden="true"></i> |
||||||
|
<span>Bytecode</span> |
||||||
|
</button> |
||||||
|
</CopyToClipboard> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</article> |
||||||
|
</section> : <section className="remixui_container clearfix"><article className="px-2 mt-2 pb-0 d-flex w-100"> |
||||||
|
<span className="mt-2 mx-3 w-100 alert alert-warning" role="alert">No Contract Compiled Yet</span> |
||||||
|
</article></section> |
||||||
|
} |
||||||
|
<PublishToStorage storage={storage} fileManager={fileManager} fileProvider={fileProvider} contract={contractsDetails[selectedContract]} resetStorage={resetStorage} /> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default ContractSelection |
@ -0,0 +1,234 @@ |
|||||||
|
.remixui_title { |
||||||
|
font-size: 1.1em; |
||||||
|
font-weight: bold; |
||||||
|
margin-bottom: 1em; |
||||||
|
} |
||||||
|
.remixui_panicError { |
||||||
|
color: red; |
||||||
|
font-size: 20px; |
||||||
|
} |
||||||
|
.remixui_crow { |
||||||
|
display: flex; |
||||||
|
overflow: auto; |
||||||
|
clear: both; |
||||||
|
padding: .2em; |
||||||
|
} |
||||||
|
.remixui_checkboxText { |
||||||
|
font-weight: normal; |
||||||
|
} |
||||||
|
.remixui_crow label { |
||||||
|
cursor:pointer; |
||||||
|
} |
||||||
|
.remixui_crowNoFlex { |
||||||
|
overflow: auto; |
||||||
|
clear: both; |
||||||
|
} |
||||||
|
.remixui_info { |
||||||
|
padding: 10px; |
||||||
|
word-break: break-word; |
||||||
|
} |
||||||
|
.remixui_contract { |
||||||
|
display: block; |
||||||
|
margin: 3% 0; |
||||||
|
} |
||||||
|
.remixui_nightlyBuilds { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
.remixui_autocompileContainer { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
.remixui_runs { |
||||||
|
width: 40%; |
||||||
|
} |
||||||
|
.remixui_hideWarningsContainer { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
.remixui_autocompile {} |
||||||
|
.remixui_autocompileTitle { |
||||||
|
font-weight: bold; |
||||||
|
margin: 1% 0; |
||||||
|
} |
||||||
|
.remixui_autocompileText { |
||||||
|
margin: 1% 0; |
||||||
|
font-size: 12px; |
||||||
|
overflow: hidden; |
||||||
|
word-break: normal; |
||||||
|
line-height: initial; |
||||||
|
} |
||||||
|
.remixui_warnCompilationSlow { |
||||||
|
margin-left: 1%; |
||||||
|
} |
||||||
|
.remixui_compilerConfig { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
.remixui_compilerConfig label { |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
.remixui_compilerSection { |
||||||
|
padding: 12px 24px 16px; |
||||||
|
} |
||||||
|
.remixui_compilerLabel { |
||||||
|
margin-bottom: 2px; |
||||||
|
font-size: 11px; |
||||||
|
line-height: 12px; |
||||||
|
text-transform: uppercase; |
||||||
|
} |
||||||
|
.remixui_copyButton { |
||||||
|
padding: 6px; |
||||||
|
font-weight: bold; |
||||||
|
font-size: 11px; |
||||||
|
line-height: 15px; |
||||||
|
} |
||||||
|
.remixui_name { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
.remixui_size { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
.remixui_checkboxes { |
||||||
|
display: flex; |
||||||
|
width: 100%; |
||||||
|
justify-content: space-between; |
||||||
|
flex-wrap: wrap; |
||||||
|
} |
||||||
|
.remixui_compileButton { |
||||||
|
width: 100%; |
||||||
|
margin: 15px 0 10px 0; |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
.remixui_container { |
||||||
|
margin: 0; |
||||||
|
margin-bottom: 2%; |
||||||
|
} |
||||||
|
.remixui_optimizeContainer { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
.remixui_noContractAlert { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
.remixui_contractHelperButtons { |
||||||
|
margin-top: 6px; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
float: right; |
||||||
|
} |
||||||
|
.remixui_copyToClipboard { |
||||||
|
font-size: 1rem; |
||||||
|
} |
||||||
|
.remixui_copyIcon { |
||||||
|
margin-right: 5px; |
||||||
|
} |
||||||
|
.remixui_log { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
margin-bottom: 5%; |
||||||
|
overflow: visible; |
||||||
|
} |
||||||
|
.remixui_key { |
||||||
|
margin-right: 5px; |
||||||
|
text-transform: uppercase; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
.remixui_value { |
||||||
|
display: flex; |
||||||
|
width: 100%; |
||||||
|
margin-top: 1.5%; |
||||||
|
} |
||||||
|
.remixui_questionMark { |
||||||
|
margin-left: 2%; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
.remixui_questionMark:hover { |
||||||
|
} |
||||||
|
.remixui_detailsJSON { |
||||||
|
padding: 8px 0; |
||||||
|
border: none; |
||||||
|
} |
||||||
|
.remixui_icon { |
||||||
|
margin-right: 0.3em; |
||||||
|
} |
||||||
|
.remixui_errorBlobs { |
||||||
|
padding-left: 5px; |
||||||
|
padding-right: 5px; |
||||||
|
word-break: break-word; |
||||||
|
} |
||||||
|
.remixui_storageLogo { |
||||||
|
width: 20px; |
||||||
|
height: 20px; |
||||||
|
} |
||||||
|
.remixui_spinningIcon { |
||||||
|
display: inline-block; |
||||||
|
position: relative; |
||||||
|
animation: spin 2s infinite linear; |
||||||
|
-moz-animation: spin 2s infinite linear; |
||||||
|
-o-animation: spin 2s infinite linear; |
||||||
|
-webkit-animation: spin 2s infinite linear; |
||||||
|
} |
||||||
|
@keyframes spin { |
||||||
|
0% { transform: rotate(0deg); } |
||||||
|
100% { transform: rotate(360deg); } |
||||||
|
} |
||||||
|
@-webkit-keyframes spin { |
||||||
|
0% { transform: rotate(0deg); } |
||||||
|
100% { transform: rotate(360deg); } |
||||||
|
} |
||||||
|
@-moz-keyframes spin { |
||||||
|
0% { transform: rotate(0deg); } |
||||||
|
100% { transform: rotate(360deg); } |
||||||
|
} |
||||||
|
@-o-keyframes spin { |
||||||
|
0% { transform: rotate(0deg); } |
||||||
|
100% { transform: rotate(360deg); } |
||||||
|
} |
||||||
|
@-ms-keyframes spin { |
||||||
|
0% { transform: rotate(0deg); } |
||||||
|
100% { transform: rotate(360deg); } |
||||||
|
} |
||||||
|
|
||||||
|
.remixui_bouncingIcon { |
||||||
|
display: inline-block; |
||||||
|
position: relative; |
||||||
|
-moz-animation: bounce 2s infinite linear; |
||||||
|
-o-animation: bounce 2s infinite linear; |
||||||
|
-webkit-animation: bounce 2s infinite linear; |
||||||
|
animation: bounce 2s infinite linear; |
||||||
|
} |
||||||
|
|
||||||
|
@-webkit-keyframes bounce { |
||||||
|
0% { top: 0; } |
||||||
|
50% { top: -0.2em; } |
||||||
|
70% { top: -0.3em; } |
||||||
|
100% { top: 0; } |
||||||
|
} |
||||||
|
@-moz-keyframes bounce { |
||||||
|
0% { top: 0; } |
||||||
|
50% { top: -0.2em; } |
||||||
|
70% { top: -0.3em; } |
||||||
|
100% { top: 0; } |
||||||
|
} |
||||||
|
@-o-keyframes bounce { |
||||||
|
0% { top: 0; } |
||||||
|
50% { top: -0.2em; } |
||||||
|
70% { top: -0.3em; } |
||||||
|
100% { top: 0; } |
||||||
|
} |
||||||
|
@-ms-keyframes bounce { |
||||||
|
0% { top: 0; } |
||||||
|
50% { top: -0.2em; } |
||||||
|
70% { top: -0.3em; } |
||||||
|
100% { top: 0; } |
||||||
|
} |
||||||
|
@keyframes bounce { |
||||||
|
0% { top: 0; } |
||||||
|
50% { top: -0.2em; } |
||||||
|
70% { top: -0.3em; } |
||||||
|
100% { top: 0; } |
||||||
|
} |
@ -0,0 +1,128 @@ |
|||||||
|
import { Plugin } from '@remixproject/engine' |
||||||
|
|
||||||
|
const packageJson = require('../../../../../../package.json') |
||||||
|
const Compiler = require('@remix-project/remix-solidity').Compiler |
||||||
|
const EventEmitter = require('events') |
||||||
|
const profile = { |
||||||
|
name: 'solidity-logic', |
||||||
|
displayName: 'Solidity compiler logic', |
||||||
|
description: 'Compile solidity contracts - Logic', |
||||||
|
version: packageJson.version |
||||||
|
} |
||||||
|
export class CompileTab extends Plugin { |
||||||
|
public compiler |
||||||
|
public optimize |
||||||
|
public runs |
||||||
|
public evmVersion: string |
||||||
|
public compilerImport |
||||||
|
public event |
||||||
|
|
||||||
|
constructor (public queryParams, public fileManager, public editor, public config, public fileProvider, public contentImport) { |
||||||
|
super(profile) |
||||||
|
this.event = new EventEmitter() |
||||||
|
this.compiler = new Compiler((url, cb) => this.call('contentImport', 'resolveAndSave', url).then((result) => cb(null, result)).catch((error) => cb(error.message))) |
||||||
|
} |
||||||
|
|
||||||
|
init () { |
||||||
|
this.optimize = this.queryParams.get().optimize |
||||||
|
this.optimize = this.optimize === 'true' |
||||||
|
this.queryParams.update({ optimize: this.optimize }) |
||||||
|
this.compiler.set('optimize', this.optimize) |
||||||
|
|
||||||
|
this.runs = this.queryParams.get().runs |
||||||
|
this.runs = this.runs && this.runs !== 'undefined' ? this.runs : 200 |
||||||
|
this.queryParams.update({ runs: this.runs }) |
||||||
|
this.compiler.set('runs', this.runs) |
||||||
|
|
||||||
|
this.evmVersion = this.queryParams.get().evmVersion |
||||||
|
if (this.evmVersion === 'undefined' || this.evmVersion === 'null' || !this.evmVersion) { |
||||||
|
this.evmVersion = null |
||||||
|
} |
||||||
|
this.queryParams.update({ evmVersion: this.evmVersion }) |
||||||
|
this.compiler.set('evmVersion', this.evmVersion) |
||||||
|
} |
||||||
|
|
||||||
|
setOptimize (newOptimizeValue) { |
||||||
|
this.optimize = newOptimizeValue |
||||||
|
this.queryParams.update({ optimize: this.optimize }) |
||||||
|
this.compiler.set('optimize', this.optimize) |
||||||
|
} |
||||||
|
|
||||||
|
setRuns (runs) { |
||||||
|
this.runs = runs |
||||||
|
this.queryParams.update({ runs: this.runs }) |
||||||
|
this.compiler.set('runs', this.runs) |
||||||
|
} |
||||||
|
|
||||||
|
setEvmVersion (newEvmVersion) { |
||||||
|
this.evmVersion = newEvmVersion |
||||||
|
this.queryParams.update({ evmVersion: this.evmVersion }) |
||||||
|
this.compiler.set('evmVersion', this.evmVersion) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the compiler to using Solidity or Yul (default to Solidity) |
||||||
|
* @params lang {'Solidity' | 'Yul'} ... |
||||||
|
*/ |
||||||
|
setLanguage (lang) { |
||||||
|
this.compiler.set('language', lang) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Compile a specific file of the file manager |
||||||
|
* @param {string} target the path to the file to compile |
||||||
|
*/ |
||||||
|
compileFile (target) { |
||||||
|
if (!target) throw new Error('No target provided for compiliation') |
||||||
|
const provider = this.fileManager.fileProviderOf(target) |
||||||
|
if (!provider) throw new Error(`cannot compile ${target}. Does not belong to any explorer`) |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
provider.get(target, (error, content) => { |
||||||
|
if (error) return reject(error) |
||||||
|
const sources = { [target]: { content } } |
||||||
|
this.event.emit('startingCompilation') |
||||||
|
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
|
||||||
|
setTimeout(() => { this.compiler.compile(sources, target); resolve(true) }, 100) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async isHardhatProject () { |
||||||
|
if (this.fileManager.mode === 'localhost') { |
||||||
|
return await this.fileManager.exists('hardhat.config.js') |
||||||
|
} else return false |
||||||
|
} |
||||||
|
|
||||||
|
runCompiler (hhCompilation) { |
||||||
|
try { |
||||||
|
if (this.fileManager.mode === 'localhost' && hhCompilation) { |
||||||
|
const { currentVersion, optimize, runs } = this.compiler.state |
||||||
|
if (currentVersion) { |
||||||
|
const fileContent = `module.exports = {
|
||||||
|
solidity: '${currentVersion.substring(0, currentVersion.indexOf('+commit'))}', |
||||||
|
settings: { |
||||||
|
optimizer: { |
||||||
|
enabled: ${optimize}, |
||||||
|
runs: ${runs} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
` |
||||||
|
const configFilePath = 'remix-compiler.config.js' |
||||||
|
this.fileManager.setFileContent(configFilePath, fileContent) |
||||||
|
this.call('hardhat', 'compile', configFilePath).then((result) => { |
||||||
|
this.call('terminal', 'log', { type: 'info', value: result }) |
||||||
|
}).catch((error) => { |
||||||
|
this.call('terminal', 'log', { type: 'error', value: error }) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
this.fileManager.saveCurrentFile() |
||||||
|
this.event.emit('removeAnnotations') |
||||||
|
var currentFile = this.config.get('currentFile') |
||||||
|
return this.compileFile(currentFile) |
||||||
|
} catch (err) { |
||||||
|
console.error(err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
'use strict' |
||||||
|
import * as remixLib from '@remix-project/remix-lib' |
||||||
|
|
||||||
|
const txHelper = remixLib.execution.txHelper |
||||||
|
|
||||||
|
export class CompilerAbstract { |
||||||
|
public languageversion: string |
||||||
|
public data: Record<string, any> |
||||||
|
public source: Record<string, any> |
||||||
|
|
||||||
|
constructor (languageversion, data, source) { |
||||||
|
this.languageversion = languageversion |
||||||
|
this.data = data |
||||||
|
this.source = source // source code
|
||||||
|
} |
||||||
|
|
||||||
|
getContracts () { |
||||||
|
return this.data.contracts |
||||||
|
} |
||||||
|
|
||||||
|
getContract (name) { |
||||||
|
return txHelper.getContract(name, this.data.contracts) |
||||||
|
} |
||||||
|
|
||||||
|
visitContracts (calllback) { |
||||||
|
return txHelper.visitContracts(this.data.contracts, calllback) |
||||||
|
} |
||||||
|
|
||||||
|
getData () { |
||||||
|
return this.data |
||||||
|
} |
||||||
|
|
||||||
|
getAsts () { |
||||||
|
return this.data.sources // ast
|
||||||
|
} |
||||||
|
|
||||||
|
getSourceName (fileIndex) { |
||||||
|
if (this.data && this.data.sources) { |
||||||
|
return Object.keys(this.data.sources)[fileIndex] |
||||||
|
} else if (Object.keys(this.source.sources).length === 1) { |
||||||
|
// if we don't have ast, we return the only one filename present.
|
||||||
|
const sourcesArray = Object.keys(this.source.sources) |
||||||
|
return sourcesArray[0] |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
getSourceCode () { |
||||||
|
return this.source |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
'use strict' |
||||||
|
import { canUseWorker, urlFromVersion } from './compiler-utils' |
||||||
|
import { Compiler } from '@remix-project/remix-solidity' |
||||||
|
import { CompilerAbstract } from './compiler-abstract' |
||||||
|
|
||||||
|
export const compile = async (compilationTargets, settings, contentResolverCallback) => { |
||||||
|
return new Promise((resolve) => { |
||||||
|
const compiler = new Compiler(contentResolverCallback) |
||||||
|
compiler.set('evmVersion', settings.evmVersion) |
||||||
|
compiler.set('optimize', settings.optimize) |
||||||
|
compiler.set('language', settings.language) |
||||||
|
compiler.set('runs', settings.runs) |
||||||
|
compiler.loadVersion(canUseWorker(settings.version), urlFromVersion(settings.version)) |
||||||
|
compiler.event.register('compilationFinished', (success, compilationData, source) => { |
||||||
|
resolve(new CompilerAbstract(settings.version, compilationData, source)) |
||||||
|
}) |
||||||
|
compiler.event.register('compilerLoaded', () => compiler.compile(compilationTargets, '')) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
const semver = require('semver') |
||||||
|
const minixhr = require('minixhr') |
||||||
|
/* global Worker */ |
||||||
|
|
||||||
|
export const baseURLBin = 'https://binaries.soliditylang.org/bin' |
||||||
|
export const baseURLWasm = 'https://binaries.soliditylang.org/wasm' |
||||||
|
|
||||||
|
export const pathToURL = {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the URL of the given compiler version |
||||||
|
* @param version is the version of compiler with or without 'soljson-v' prefix and .js postfix |
||||||
|
*/ |
||||||
|
export function urlFromVersion (version) { |
||||||
|
if (!version.startsWith('soljson-v')) version = 'soljson-v' + version |
||||||
|
if (!version.endsWith('.js')) version = version + '.js' |
||||||
|
return `${pathToURL[version]}/${version}` |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if the worker can be used to load a compiler. |
||||||
|
* checks a compiler whitelist, browser support and OS. |
||||||
|
*/ |
||||||
|
export function canUseWorker (selectedVersion) { |
||||||
|
const version = semver.coerce(selectedVersion) |
||||||
|
const isNightly = selectedVersion.includes('nightly') |
||||||
|
return browserSupportWorker() && ( |
||||||
|
// All compiler versions (including nightlies) after 0.6.3 are wasm compiled
|
||||||
|
semver.gt(version, '0.6.3') || |
||||||
|
// Only releases are wasm compiled starting with 0.3.6
|
||||||
|
(semver.gte(version, '0.3.6') && !isNightly) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function browserSupportWorker () { |
||||||
|
return document.location.protocol !== 'file:' && Worker !== undefined |
||||||
|
} |
||||||
|
|
||||||
|
// returns a promise for minixhr
|
||||||
|
export function promisedMiniXhr (url) { |
||||||
|
return new Promise((resolve) => { |
||||||
|
minixhr(url, (json, event) => { |
||||||
|
resolve({ json, event }) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,119 @@ |
|||||||
|
'use strict' |
||||||
|
import * as solcTranslate from 'solc/translate' |
||||||
|
import * as remixLib from '@remix-project/remix-lib' |
||||||
|
|
||||||
|
const txHelper = remixLib.execution.txHelper |
||||||
|
|
||||||
|
export function parseContracts (contractName, contract, source) { |
||||||
|
const detail: Record<string, any> = {} |
||||||
|
|
||||||
|
detail.name = contractName |
||||||
|
detail.metadata = contract.metadata |
||||||
|
if (contract.evm.bytecode.object) { |
||||||
|
detail.bytecode = contract.evm.bytecode.object |
||||||
|
} |
||||||
|
|
||||||
|
detail.abi = contract.abi |
||||||
|
|
||||||
|
if (contract.evm.bytecode.object) { |
||||||
|
detail.bytecode = contract.evm.bytecode |
||||||
|
detail.web3Deploy = gethDeploy(contractName.toLowerCase(), contract.abi, contract.evm.bytecode.object) |
||||||
|
|
||||||
|
detail.metadataHash = retrieveMetadataHash(contract.evm.bytecode.object) |
||||||
|
if (detail.metadataHash) { |
||||||
|
detail.swarmLocation = 'bzzr://' + detail.metadataHash |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
detail.functionHashes = {} |
||||||
|
for (const fun in contract.evm.methodIdentifiers) { |
||||||
|
detail.functionHashes[contract.evm.methodIdentifiers[fun]] = fun |
||||||
|
} |
||||||
|
|
||||||
|
detail.gasEstimates = formatGasEstimates(contract.evm.gasEstimates) |
||||||
|
|
||||||
|
detail.devdoc = contract.devdoc |
||||||
|
detail.userdoc = contract.userdoc |
||||||
|
|
||||||
|
if (contract.evm.deployedBytecode && contract.evm.deployedBytecode.object.length > 0) { |
||||||
|
detail['Runtime Bytecode'] = contract.evm.deployedBytecode |
||||||
|
} |
||||||
|
|
||||||
|
if (source && contract.assembly !== null) { |
||||||
|
detail.Assembly = solcTranslate.prettyPrintLegacyAssemblyJSON(contract.evm.legacyAssembly, source.content) |
||||||
|
} |
||||||
|
|
||||||
|
return detail |
||||||
|
} |
||||||
|
|
||||||
|
const retrieveMetadataHash = function (bytecode) { |
||||||
|
var match = /a165627a7a72305820([0-9a-f]{64})0029$/.exec(bytecode) |
||||||
|
if (!match) { |
||||||
|
match = /a265627a7a72305820([0-9a-f]{64})6c6578706572696d656e74616cf50037$/.exec(bytecode) |
||||||
|
} |
||||||
|
if (match) { |
||||||
|
return match[1] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const gethDeploy = function (contractName, jsonInterface, bytecode) { |
||||||
|
let code = '' |
||||||
|
const funABI = txHelper.getConstructorInterface(jsonInterface) |
||||||
|
|
||||||
|
funABI.inputs.forEach(function (inp) { |
||||||
|
code += 'var ' + inp.name + ' = /* var of type ' + inp.type + ' here */ ;\n' |
||||||
|
}) |
||||||
|
|
||||||
|
contractName = contractName.replace(/[:./]/g, '_') |
||||||
|
code += 'var ' + contractName + 'Contract = new web3.eth.Contract(' + JSON.stringify(jsonInterface).replace('\n', '') + ');' + |
||||||
|
'\nvar ' + contractName + ' = ' + contractName + 'Contract.deploy({' + |
||||||
|
"\n data: '0x" + bytecode + "', " + |
||||||
|
'\n arguments: [' |
||||||
|
|
||||||
|
funABI.inputs.forEach(function (inp) { |
||||||
|
code += '\n ' + inp.name + ',' |
||||||
|
}) |
||||||
|
|
||||||
|
code += '\n ]' + |
||||||
|
'\n}).send({' + |
||||||
|
'\n from: web3.eth.accounts[0], ' + |
||||||
|
"\n gas: '4700000'" + |
||||||
|
'\n }, function (e, contract){' + |
||||||
|
'\n console.log(e, contract);' + |
||||||
|
"\n if (typeof contract.address !== 'undefined') {" + |
||||||
|
"\n console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);" + |
||||||
|
'\n }' + |
||||||
|
'\n })' |
||||||
|
|
||||||
|
return code |
||||||
|
} |
||||||
|
|
||||||
|
const formatGasEstimates = function (data) { |
||||||
|
if (!data) return {} |
||||||
|
if (data.creation === undefined && data.external === undefined && data.internal === undefined) return {} |
||||||
|
|
||||||
|
const gasToText = function (g) { |
||||||
|
return g === null ? 'unknown' : g |
||||||
|
} |
||||||
|
|
||||||
|
const ret: Record<string, any> = {} |
||||||
|
let fun |
||||||
|
if ('creation' in data) { |
||||||
|
ret.Creation = data.creation |
||||||
|
} |
||||||
|
|
||||||
|
if ('external' in data) { |
||||||
|
ret.External = {} |
||||||
|
for (fun in data.external) { |
||||||
|
ret.External[fun] = gasToText(data.external[fun]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ('internal' in data) { |
||||||
|
ret.Internal = {} |
||||||
|
for (fun in data.internal) { |
||||||
|
ret.Internal[fun] = gasToText(data.internal[fun]) |
||||||
|
} |
||||||
|
} |
||||||
|
return ret |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
export * from './compileTabLogic' |
||||||
|
export * from './compiler-abstract' |
||||||
|
export * from './compiler-helpers' |
||||||
|
export * from './compiler-utils' |
||||||
|
export * from './contract-parser' |
@ -0,0 +1,63 @@ |
|||||||
|
interface Action { |
||||||
|
type: string; |
||||||
|
payload: Record<string, any>; |
||||||
|
} |
||||||
|
|
||||||
|
export const compilerInitialState = { |
||||||
|
compiler: { |
||||||
|
mode: '', |
||||||
|
args: null |
||||||
|
}, |
||||||
|
editor: { |
||||||
|
mode: '' |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const compilerReducer = (state = compilerInitialState, action: Action) => { |
||||||
|
switch (action.type) { |
||||||
|
case 'SET_COMPILER_MODE': { |
||||||
|
return { |
||||||
|
...state, |
||||||
|
compiler: { |
||||||
|
...state.compiler, |
||||||
|
mode: action.payload.mode, |
||||||
|
args: action.payload.args || null |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
case 'RESET_COMPILER_MODE': { |
||||||
|
return { |
||||||
|
...state, |
||||||
|
compiler: { |
||||||
|
...state.compiler, |
||||||
|
mode: '', |
||||||
|
args: null |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
case 'SET_EDITOR_MODE': { |
||||||
|
return { |
||||||
|
...state, |
||||||
|
editor: { |
||||||
|
...state.editor, |
||||||
|
mode: action.payload |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
case 'RESET_EDITOR_MODE': { |
||||||
|
return { |
||||||
|
...state, |
||||||
|
editor: { |
||||||
|
...state.editor, |
||||||
|
mode: '' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
default: |
||||||
|
throw new Error() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,115 @@ |
|||||||
|
import React, { useState } from 'react' // eslint-disable-line
|
||||||
|
import { SolidityCompilerProps } from './types' |
||||||
|
import { CompilerContainer } from './compiler-container' // eslint-disable-line
|
||||||
|
import { ContractSelection } from './contract-selection' // eslint-disable-line
|
||||||
|
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
|
||||||
|
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
|
||||||
|
import { Renderer } from '@remix-ui/renderer' // eslint-disable-line
|
||||||
|
|
||||||
|
import './css/style.css' |
||||||
|
|
||||||
|
export const SolidityCompiler = (props: SolidityCompilerProps) => { |
||||||
|
const { plugin, plugin: { editor, config, queryParams, compileTabLogic, currentFile, fileProvider, fileManager, contractsDetails, contractMap, compileErrors, isHardHatProject, setHardHatCompilation, configurationSettings } } = props |
||||||
|
const [state, setState] = useState({ |
||||||
|
contractsDetails: {}, |
||||||
|
eventHandlers: {}, |
||||||
|
loading: false, |
||||||
|
compileTabLogic: null, |
||||||
|
compiler: null, |
||||||
|
toasterMsg: '', |
||||||
|
modal: { |
||||||
|
hide: true, |
||||||
|
title: '', |
||||||
|
message: null, |
||||||
|
okLabel: '', |
||||||
|
okFn: () => {}, |
||||||
|
cancelLabel: '', |
||||||
|
cancelFn: () => {}, |
||||||
|
handleHide: null |
||||||
|
} |
||||||
|
}) |
||||||
|
const [currentVersion, setCurrentVersion] = useState('') |
||||||
|
|
||||||
|
const toast = (message: string) => { |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, toasterMsg: message } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const updateCurrentVersion = (value) => { |
||||||
|
setCurrentVersion(value) |
||||||
|
plugin.setSelectedVersion(value) |
||||||
|
} |
||||||
|
|
||||||
|
const modal = async (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { |
||||||
|
await setState(prevState => { |
||||||
|
return { |
||||||
|
...prevState, |
||||||
|
modal: { |
||||||
|
...prevState.modal, |
||||||
|
hide: false, |
||||||
|
message, |
||||||
|
title, |
||||||
|
okLabel, |
||||||
|
okFn, |
||||||
|
cancelLabel, |
||||||
|
cancelFn |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const handleHideModal = () => { |
||||||
|
setState(prevState => { |
||||||
|
return { ...prevState, modal: { ...state.modal, hide: true, message: null } } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const panicMessage = (message: string) => ( |
||||||
|
<div> |
||||||
|
<i className="fas fa-exclamation-circle remixui_panicError" aria-hidden="true"></i> |
||||||
|
The compiler returned with the following internal error: <br /> <b>{message}.<br /> |
||||||
|
The compiler might be in a non-sane state, please be careful and do not use further compilation data to deploy to mainnet. |
||||||
|
It is heavily recommended to use another browser not affected by this issue (Firefox is known to not be affected).</b><br /> |
||||||
|
Please join <a href="https://gitter.im/ethereum/remix" target="blank" >remix gitter channel</a> for more information. |
||||||
|
</div> |
||||||
|
) |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div id="compileTabView"> |
||||||
|
<CompilerContainer editor={editor} config={config} queryParams={queryParams} compileTabLogic={compileTabLogic} tooltip={toast} modal={modal} compiledFileName={currentFile} setHardHatCompilation={setHardHatCompilation.bind(plugin)} updateCurrentVersion={updateCurrentVersion} isHardHatProject={isHardHatProject} configurationSettings={configurationSettings} /> |
||||||
|
<ContractSelection contractMap={contractMap} fileProvider={fileProvider} fileManager={fileManager} contractsDetails={contractsDetails} modal={modal} /> |
||||||
|
<div className="remixui_errorBlobs p-4" data-id="compiledErrors"> |
||||||
|
<span data-id={`compilationFinishedWith_${currentVersion}`}></span> |
||||||
|
{ compileErrors.error && <Renderer message={compileErrors.error.formattedMessage || compileErrors.error} plugin={plugin} opt={{ type: compileErrors.error.severity || 'error', errorType: compileErrors.error.type }} config={config} editor={editor} fileManager={fileManager} /> } |
||||||
|
{ compileErrors.error && (compileErrors.error.mode === 'panic') && modal('Error', panicMessage(compileErrors.error.formattedMessage), 'Close', null) } |
||||||
|
{ compileErrors.errors && compileErrors.errors.length && compileErrors.errors.map((err, index) => { |
||||||
|
if (config.get('hideWarnings')) { |
||||||
|
if (err.severity !== 'warning') { |
||||||
|
return <Renderer key={index} message={err.formattedMessage} plugin={plugin} opt={{ type: err.severity, errorType: err.type }} config={config} editor={editor} fileManager={fileManager} /> |
||||||
|
} |
||||||
|
} else { |
||||||
|
return <Renderer key={index} message={err.formattedMessage} plugin={plugin} opt={{ type: err.severity, errorType: err.type }} config={config} editor={editor} fileManager={fileManager} /> |
||||||
|
} |
||||||
|
}) } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<Toaster message={state.toasterMsg} /> |
||||||
|
<ModalDialog |
||||||
|
id='workspacesModalDialog' |
||||||
|
title={ state.modal.title } |
||||||
|
message={ state.modal.message } |
||||||
|
hide={ state.modal.hide } |
||||||
|
okLabel={ state.modal.okLabel } |
||||||
|
okFn={ state.modal.okFn } |
||||||
|
cancelLabel={ state.modal.cancelLabel } |
||||||
|
cancelFn={ state.modal.cancelFn } |
||||||
|
handleHide={ handleHideModal }> |
||||||
|
{ (typeof state.modal.message !== 'string') && state.modal.message } |
||||||
|
</ModalDialog> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default SolidityCompiler |
@ -0,0 +1,54 @@ |
|||||||
|
export interface SolidityCompilerProps { |
||||||
|
plugin: { |
||||||
|
contractMap: { |
||||||
|
file: string |
||||||
|
} | Record<string, any> |
||||||
|
compileErrors: any, |
||||||
|
isHardHatProject: boolean, |
||||||
|
queryParams: any, |
||||||
|
compileTabLogic: any, |
||||||
|
currentFile: string, |
||||||
|
contractsDetails: Record<string, any>, |
||||||
|
editor: any, |
||||||
|
config: any, |
||||||
|
fileProvider: any, |
||||||
|
fileManager: any, |
||||||
|
contentImport: any, |
||||||
|
call: (...args) => void |
||||||
|
on: (...args) => void, |
||||||
|
setHardHatCompilation: (value: boolean) => void, |
||||||
|
setSelectedVersion: (value: string) => void, |
||||||
|
configurationSettings: ConfigurationSettings |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
export interface CompilerContainerProps { |
||||||
|
editor: any, |
||||||
|
config: any, |
||||||
|
queryParams: any, |
||||||
|
compileTabLogic: any, |
||||||
|
tooltip: (message: string | JSX.Element) => void, |
||||||
|
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, |
||||||
|
compiledFileName: string, |
||||||
|
setHardHatCompilation: (value: boolean) => void, |
||||||
|
updateCurrentVersion: any, |
||||||
|
isHardHatProject: boolean, |
||||||
|
configurationSettings: ConfigurationSettings |
||||||
|
} |
||||||
|
export interface ContractSelectionProps { |
||||||
|
contractMap: { |
||||||
|
file: string |
||||||
|
} | Record<string, any>, |
||||||
|
fileManager: any, |
||||||
|
fileProvider: any, |
||||||
|
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, |
||||||
|
contractsDetails: Record<string, any> |
||||||
|
} |
||||||
|
|
||||||
|
export interface ConfigurationSettings { |
||||||
|
version: string, |
||||||
|
evmVersion: string, |
||||||
|
language: string, |
||||||
|
optimize: boolean, |
||||||
|
runs: string |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../../tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"jsx": "react", |
||||||
|
"allowJs": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"allowSyntheticDefaultImports": true |
||||||
|
}, |
||||||
|
"files": [], |
||||||
|
"include": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.lib.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"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"] |
||||||
|
} |
Loading…
Reference in new issue