Skip to content

Commit eff2854

Browse files
Adrien HamelinTair Asim
Adrien Hamelin
and
Tair Asim
authored
feat: reflect code, callvalue and calldata in url to be able to share… (#96)
Reflect code, callvalue and calldata in sharable permalinks Fix the incorrect value for Yul/Solidity (unit was ignored) Fix logging with hopefully the proper way now Co-authored-by: Tair Asim <[email protected]>
1 parent 0f0419d commit eff2854

File tree

5 files changed

+155
-48
lines changed

5 files changed

+155
-48
lines changed

components/Editor/index.tsx

+121-47
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,34 @@ import React, {
99
RefObject,
1010
} from 'react'
1111

12+
import { encode, decode } from '@kunigi/string-compression'
1213
import cn from 'classnames'
14+
import copy from 'copy-to-clipboard'
1315
import { BN } from 'ethereumjs-util'
16+
import { useRouter } from 'next/router'
1417
import Select, { OnChangeValue } from 'react-select'
1518
import SCEditor from 'react-simple-code-editor'
1619

1720
import { EthereumContext } from 'context/ethereumContext'
1821
import { SettingsContext, Setting } from 'context/settingsContext'
1922

23+
import { getAbsoluteURL } from 'util/browser'
2024
import {
2125
getTargetEvmVersion,
2226
compilerSemVer,
2327
getBytecodeFromMnemonic,
2428
} from 'util/compiler'
25-
import { codeHighlight, isEmpty, isFullHex, isHex } from 'util/string'
29+
import {
30+
codeHighlight,
31+
isEmpty,
32+
isFullHex,
33+
isHex,
34+
objToQueryString,
35+
} from 'util/string'
2636

2737
import examples from 'components/Editor/examples'
2838
import InstructionList from 'components/Editor/Instructions'
29-
import { Button, Input } from 'components/ui'
39+
import { Button, Input, Icon } from 'components/ui'
3040

3141
import Console from './Console'
3242
import ExecutionState from './ExecutionState'
@@ -53,6 +63,7 @@ const unitOptions = Object.keys(ValueUnit).map((value) => ({
5363

5464
const Editor = ({ readOnly = false }: Props) => {
5565
const { settingsLoaded, getSetting, setSetting } = useContext(SettingsContext)
66+
const router = useRouter()
5667

5768
const {
5869
deployContract,
@@ -82,49 +93,94 @@ const Editor = ({ readOnly = false }: Props) => {
8293
const [callValue, setCallValue] = useState('')
8394
const [unit, setUnit] = useState(ValueUnit.Wei as string)
8495

85-
const handleWorkerMessage = (event: MessageEvent) => {
86-
const { code: byteCode, warning, error } = event.data
96+
const log = useCallback(
97+
(line: string, type = 'info') => {
98+
// See https://blog.logrocket.com/a-guide-to-usestate-in-react-ecb9952e406c/
99+
setOutput((previous) => {
100+
const cloned = previous.map((x) => ({ ...x }))
101+
cloned.push({ type, message: line })
102+
return cloned
103+
})
104+
},
105+
[setOutput],
106+
)
87107

88-
if (error) {
89-
log(error, 'error')
90-
setIsCompiling(false)
91-
return
92-
}
108+
const getCallValue = useCallback(() => {
109+
const _callValue = new BN(callValue)
93110

94-
if (warning) {
95-
log(warning, 'warn')
111+
if (unit === ValueUnit.Gwei) {
112+
_callValue.imul(new BN('1000000000'))
113+
} else if (unit === ValueUnit.Finney) {
114+
_callValue.imul(new BN('1000000000000000'))
115+
} else if (unit === ValueUnit.Ether) {
116+
_callValue.imul(new BN('1000000000000000000'))
96117
}
97118

98-
log('Compilation successful')
119+
return _callValue
120+
}, [callValue, unit])
99121

100-
try {
101-
deployContract(byteCode, new BN(callValue)).then((tx) => {
102-
loadInstructions(byteCode)
122+
const handleWorkerMessage = useCallback(
123+
(event: MessageEvent) => {
124+
const { code: byteCode, warning, error } = event.data
125+
126+
if (error) {
127+
log(error, 'error')
103128
setIsCompiling(false)
104-
startTransaction(byteCode, tx)
105-
})
106-
} catch (error) {
107-
log((error as Error).message, 'error')
108-
setIsCompiling(false)
109-
}
110-
}
129+
return
130+
}
111131

112-
const log = useCallback(
113-
(line: string, type = 'info') => {
114-
output.push({ type, message: line })
115-
setOutput(output)
132+
if (warning) {
133+
log(warning, 'warn')
134+
}
135+
136+
log('Compilation successful')
137+
138+
try {
139+
const _callValue = getCallValue()
140+
deployContract(byteCode, _callValue).then((tx) => {
141+
loadInstructions(byteCode)
142+
setIsCompiling(false)
143+
startTransaction(byteCode, tx)
144+
})
145+
} catch (error) {
146+
log((error as Error).message, 'error')
147+
setIsCompiling(false)
148+
}
116149
},
117-
[output, setOutput],
150+
[
151+
log,
152+
setIsCompiling,
153+
deployContract,
154+
loadInstructions,
155+
startTransaction,
156+
getCallValue,
157+
],
118158
)
119159

120160
useEffect(() => {
121-
const initialCodeType: CodeType =
122-
getSetting(Setting.EditorCodeType) || CodeType.Yul
161+
const query = router.query
123162

124-
setCodeType(initialCodeType)
125-
setCode(examples[initialCodeType][0])
163+
if ('callValue' in query && 'unit' in query) {
164+
setCallValue(query.callValue as string)
165+
setUnit(query.unit as string)
166+
}
167+
168+
if ('callData' in query) {
169+
setCallData(query.callData as string)
170+
}
171+
172+
if ('codeType' in query && 'code' in query) {
173+
setCodeType(query.codeType as string)
174+
setCode(JSON.parse('{"a":' + decode(query.code as string) + '}').a)
175+
} else {
176+
const initialCodeType: CodeType =
177+
getSetting(Setting.EditorCodeType) || CodeType.Yul
178+
179+
setCodeType(initialCodeType)
180+
setCode(examples[initialCodeType][0])
181+
}
126182
// eslint-disable-next-line react-hooks/exhaustive-deps
127-
}, [settingsLoaded])
183+
}, [settingsLoaded && router.isReady])
128184

129185
useEffect(() => {
130186
solcWorkerRef.current = new Worker(
@@ -218,18 +274,10 @@ const Editor = ({ readOnly = false }: Props) => {
218274
return
219275
}
220276

221-
const _callData = Buffer.from(callData.substr(2), 'hex')
222-
const _callValue = new BN(callValue)
223-
224-
if (unit === ValueUnit.Gwei) {
225-
_callValue.imul(new BN('1000000000'))
226-
} else if (unit === ValueUnit.Finney) {
227-
_callValue.imul(new BN('1000000000000000'))
228-
} else if (unit === ValueUnit.Ether) {
229-
_callValue.imul(new BN('1000000000000000000'))
230-
}
231-
232277
try {
278+
const _callData = Buffer.from(callData.substr(2), 'hex')
279+
const _callValue = getCallValue()
280+
233281
if (codeType === CodeType.Mnemonic) {
234282
const bytecode = getBytecodeFromMnemonic(code, opcodes)
235283
loadInstructions(bytecode)
@@ -267,12 +315,25 @@ const Editor = ({ readOnly = false }: Props) => {
267315
selectedFork,
268316
callData,
269317
callValue,
270-
unit,
271318
loadInstructions,
272319
log,
273320
startExecution,
321+
getCallValue,
274322
])
275323

324+
const handleCopyPermalink = useCallback(() => {
325+
const params = {
326+
callValue,
327+
unit,
328+
callData,
329+
codeType,
330+
code: encodeURIComponent(encode(JSON.stringify(code))),
331+
}
332+
333+
copy(`${getAbsoluteURL('/playground?')}${objToQueryString(params)}`)
334+
log('Link to current code, calldata and value copied to clipboard')
335+
}, [callValue, unit, callData, codeType, code, log])
336+
276337
const isRunDisabled = useMemo(() => {
277338
return compiling || isEmpty(code)
278339
}, [compiling, code])
@@ -328,8 +389,8 @@ const Editor = ({ readOnly = false }: Props) => {
328389
/>
329390
</div>
330391

331-
<div className="flex items-center justify-between px-4 py-2 md:border-r border-gray-200 dark:border-black-500">
332-
<div className="flex flex-row gap-x-4">
392+
<div className="flex flex-col md:flex-row md:items-center md:justify-between px-4 py-4 md:py-2 md:border-r border-gray-200 dark:border-black-500">
393+
<div className="flex flex-col md:flex-row md:gap-x-4 gap-y-2 md:gap-y-0 mb-4 md:mb-0">
333394
{isCallDataActive && (
334395
<Input
335396
placeholder="Calldata in HEX"
@@ -358,13 +419,26 @@ const Editor = ({ readOnly = false }: Props) => {
358419
classNamePrefix="select"
359420
menuPlacement="auto"
360421
/>
422+
423+
<Button
424+
onClick={handleCopyPermalink}
425+
transparent
426+
padded={false}
427+
>
428+
<span
429+
className="inline-block mr-4 select-all"
430+
data-tip="Share permalink"
431+
>
432+
<Icon name="links-line" className="text-indigo-500 mr-2" />
433+
</span>
434+
</Button>
361435
</div>
362436

363437
<Button
364438
onClick={handleRun}
365439
disabled={isRunDisabled}
366440
size="sm"
367-
className="ml-3"
441+
contentClassName="justify-center"
368442
>
369443
Run
370444
</Button>

components/ui/Button.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type Props = {
88
href?: string
99
external?: boolean
1010
className?: string
11+
contentClassName?: string
1112
transparent?: boolean
1213
outline?: boolean
1314
padded?: boolean
@@ -19,6 +20,7 @@ type Props = {
1920
export const Button: React.FC<Props> = ({
2021
children,
2122
className,
23+
contentClassName,
2224
href,
2325
external,
2426
disabled,
@@ -56,7 +58,9 @@ export const Button: React.FC<Props> = ({
5658
data-for={tooltipIdPrefixed}
5759
{...rest}
5860
>
59-
<div className="flex items-center">{children}</div>
61+
<div className={cn('flex items-center', contentClassName)}>
62+
{children}
63+
</div>
6064
{tooltip && tooltipId && (
6165
<ReactTooltip
6266
className="tooltip"

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
"@ethereumjs/common": "^2.6.0",
1717
"@ethereumjs/tx": "^3.4.0",
1818
"@ethereumjs/vm": "^5.6.0",
19+
"@kunigi/string-compression": "1.0.2",
1920
"@mdx-js/loader": "^1.6.22",
2021
"@mdx-js/react": "^1.6.22",
2122
"classnames": "^2.3.1",
23+
"copy-to-clipboard": "^3.3.1",
2224
"ethereumjs-util": "^7.1.3",
2325
"gray-matter": "^4.0.3",
2426
"highlight.js": "^11.3.1",

util/string.ts

+10
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,13 @@ export const fromBuffer = (buf: Buffer) => {
7070
export const toKeyIndex = (prefix: string, index: number) => {
7171
return [prefix, index].join('-')
7272
}
73+
74+
/**
75+
* Creates a query string from an object.
76+
*/
77+
export const objToQueryString = (params: any) => {
78+
return Object.keys(params)
79+
.map((key) => (!isEmpty(params[key]) ? key + '=' + params[key] : null))
80+
.filter((param) => param !== null)
81+
.join('&')
82+
}

yarn.lock

+17
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,11 @@
517517
resolved "https://package-cluster.production.groovehq.com/@humanwhocodes%2fobject-schema/-/object-schema-1.2.0.tgz"
518518
integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==
519519

520+
521+
version "1.0.2"
522+
resolved "https://registry.yarnpkg.com/@kunigi/string-compression/-/string-compression-1.0.2.tgz#aed3d6d29ba170d51079562328ced905d6a7d6da"
523+
integrity sha512-rB2OpMgWOe9J7KQhEmLFniA2lwJm9v3hoGSWuMihitKcoOiY/dLn5GoXOXbeQV+pui6IuVSuKoa8jbYoQhhewA==
524+
520525
"@mdx-js/loader@^1.6.22":
521526
version "1.6.22"
522527
resolved "https://package-cluster.production.groovehq.com/@mdx-js%2floader/-/loader-1.6.22.tgz#d9e8fe7f8185ff13c9c8639c048b123e30d322c4"
@@ -1776,6 +1781,13 @@ convert-source-map@~1.1.0:
17761781
resolved "https://package-cluster.production.groovehq.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860"
17771782
integrity sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=
17781783

1784+
copy-to-clipboard@^3.3.1:
1785+
version "3.3.1"
1786+
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
1787+
integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
1788+
dependencies:
1789+
toggle-selection "^1.0.6"
1790+
17791791
core-js-pure@^3.0.1, core-js-pure@^3.16.0:
17801792
version "3.18.3"
17811793
resolved "https://package-cluster.production.groovehq.com/core-js-pure/-/core-js-pure-3.18.3.tgz"
@@ -5671,6 +5683,11 @@ to-regex-range@^5.0.1:
56715683
dependencies:
56725684
is-number "^7.0.0"
56735685

5686+
toggle-selection@^1.0.6:
5687+
version "1.0.6"
5688+
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
5689+
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
5690+
56745691
56755692
version "1.0.0"
56765693
resolved "https://package-cluster.production.groovehq.com/toidentifier/-/toidentifier-1.0.0.tgz"

0 commit comments

Comments
 (0)