Skip to content

Commit

Permalink
feat: add display of contract disassembly in ContractDetails page (#817
Browse files Browse the repository at this point in the history
…) (#818)

Signed-off-by: Logan Nguyen <[email protected]>
Signed-off-by: Simon Viénot <[email protected]>
Co-authored-by: Logan Nguyen <[email protected]>
  • Loading branch information
svienot and quiet-node authored Dec 27, 2023
1 parent ee7b0be commit bafcf97
Show file tree
Hide file tree
Showing 16 changed files with 1,221 additions and 41 deletions.
13 changes: 13 additions & 0 deletions src/AppStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ export class AppStorage {
AppStorage.createCookie(AppStorage.COOKIE_POLICY_NAME, policy, AppStorage.COOKIE_POLICY_VALIDITY)
}

//
// display hexa opcodes in assembly code
//

private static readonly SHOW_HEXA_OPCODE_KEY = 'hexaOpcode'

public static getShowHexaOpcode(): boolean {
return this.getLocalStorageItem(this.SHOW_HEXA_OPCODE_KEY) != null
}

public static setShowHexaOpcode(newValue: boolean|null): void {
this.setLocalStorageItem(this.SHOW_HEXA_OPCODE_KEY, newValue ? "true" : null)
}

//
// Private
Expand Down
2 changes: 1 addition & 1 deletion src/components/Copyable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<template>
<div class="shy-scope" style="display: inline-block; position: relative;">
<slot name="content"/>
<div v-if="contentToCopy" id="shyCopyButton" class="shy"
<div v-if="enableCopy && contentToCopy" id="shyCopyButton" class="shy"
style="position: absolute; left: 0; top: 0; width: 100%; height: 100%">
<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.50)"></div>
<div v-if="enableCopy"
Expand Down
9 changes: 5 additions & 4 deletions src/components/DashboardCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@
<slot name="content"></slot>
</div>

<div class="columns h-is-property-text">
<div class="columns is-multiline h-is-property-text">

<div class="column">
<div class="column is-6-desktop" :class="{'is-full': !isMediumScreen}">
<slot name="leftContent"></slot>
</div>
<div class="column" :class="{'h-has-column-separator':slots.rightContent}">
<div class="column is-6-desktop" :class="{'h-has-column-separator':slots.rightContent&&isMediumScreen}">
<slot name="rightContent"></slot>
</div>

Expand All @@ -69,9 +69,10 @@ export default defineComponent({

setup() {
const isSmallScreen = inject('isSmallScreen', true)
const isMediumScreen = inject('isMediumScreen', true)
const isTouchDevice = inject('isTouchDevice', false)
const slots = useSlots()
return { isSmallScreen, isTouchDevice, slots }
return { isSmallScreen, isMediumScreen, isTouchDevice, slots }
}
})

Expand Down
47 changes: 39 additions & 8 deletions src/components/contract/ContractByteCodeSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,29 @@
</template>
</Property>
<Property id="solcVersion" :full-width="true">
<template v-slot:name>Compiler Version</template>
<template v-slot:name>Solidity Compiler Version</template>
<template v-slot:value>
<StringValue :string-value="solcVersion ?? undefined"/>
</template>
</Property>
<Property id="code" :full-width="true">
<template v-slot:name>Runtime Bytecode</template>
</Property>
<ByteCodeValue :byte-code="byteCode ?? undefined" class="mt-3"/>
<div class="columns is-multiline h-is-property-text">
<div id="bytecode" class="column is-6" :class="{'is-full': !isSmallScreen}">
<p class="has-text-weight-light">Runtime Bytecode</p>
<ByteCodeValue :byte-code="byteCode ?? undefined" class="mt-3 mb-4"/>
</div>
<div id="assembly-code" class="column is-6" :class="{'h-has-column-separator':isSmallScreen}">
<div class="is-flex is-align-items-center is-justify-content-space-between">
<p class="has-text-weight-light">Assembly Bytecode</p>
<div class="is-flex is-align-items-center is-justify-content-end">
<p class="has-text-weight-light">Show hexa opcode</p>
<label class="checkbox pt-1 ml-3">
<input type="checkbox" v-model="showHexaOpcode">
</label>
</div>
</div>
<DisassembledCodeValue :byte-code="byteCode ?? undefined" :show-hexa-opcode="showHexaOpcode"/>
</div>
</div>
</template>
</DashboardCard>

Expand All @@ -102,7 +116,7 @@

<script lang="ts">

import {computed, defineComponent, inject, PropType, ref} from 'vue';
import {computed, defineComponent, inject, onMounted, PropType, ref, watch} from 'vue';
import DashboardCard from "@/components/DashboardCard.vue";
import ByteCodeValue from "@/components/values/ByteCodeValue.vue";
import StringValue from "@/components/values/StringValue.vue";
Expand All @@ -111,14 +125,26 @@ import {ContractAnalyzer} from "@/utils/analyzer/ContractAnalyzer";
import {routeManager} from "@/router";
import InfoTooltip from "@/components/InfoTooltip.vue";
import ContractVerificationDialog from "@/components/verification/ContractVerificationDialog.vue";
import DisassembledCodeValue from "@/components/values/DisassembledCodeValue.vue";
import HexaValue from "@/components/values/HexaValue.vue";
import {AppStorage} from "@/AppStorage";

const FULL_MATCH_TOOLTIP = `A Full Match indicates that the bytecode of the deployed contract is byte-by-byte the same as the compilation output of the given source code files with the settings defined in the metadata file. This means the contents of the source code files and the compilation settings are exactly the same as when the contract author compiled and deployed the contract.`
const PARTIAL_MATCH_TOOLTIP = `A Partial Match indicates that the bytecode of the deployed contract is the same as the compilation output of the given source code files except for the metadata hash. This means the deployed contract and the given source code + metadata function in the same way but there are differences in source code comments, variable names, or other metadata fields such as source paths.`

export default defineComponent({
name: 'ContractByteCodeSection',

components: {ContractVerificationDialog, InfoTooltip, Property, StringValue, ByteCodeValue, DashboardCard},
components: {
HexaValue,
DisassembledCodeValue,
ContractVerificationDialog,
InfoTooltip,
Property,
StringValue,
ByteCodeValue,
DashboardCard
},

props: {
contractAnalyzer: {
Expand Down Expand Up @@ -154,6 +180,10 @@ export default defineComponent({

const tooltipText = computed(() => isFullMatch.value ? FULL_MATCH_TOOLTIP : PARTIAL_MATCH_TOOLTIP)

const showHexaOpcode = ref(false)
onMounted(() => showHexaOpcode.value = AppStorage.getShowHexaOpcode())
watch(showHexaOpcode, () => AppStorage.setShowHexaOpcode(showHexaOpcode.value ? showHexaOpcode.value : null))

return {
isTouchDevice,
isSmallScreen,
Expand All @@ -169,7 +199,8 @@ export default defineComponent({
contractId: props.contractAnalyzer.contractId,
byteCodeAnalyzer: props.contractAnalyzer.byteCodeAnalyzer,
verifyDidComplete,
isFullMatch
isFullMatch,
showHexaOpcode,
}
}
});
Expand Down
54 changes: 33 additions & 21 deletions src/components/values/ByteCodeValue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@

<template>

<textarea v-if="textValue"
v-model="textValue"
readonly rows="4"
style="width:100%; font-family: novamonoregular,monospace"></textarea>
<div v-if="textValue" id="bytecode"
class="pl-1 mt-2 mr-1 code-data-box">
<HexaValue :byte-string="textValue" :copyable="false"/>
</div>

<span v-else-if="initialLoading"/>
<span v-else-if="initialLoading"/>

<span v-else class="has-text-grey">None</span>
<span v-else class="has-text-grey">None</span>

</template>

Expand All @@ -43,22 +43,24 @@

import {defineComponent, inject, ref, watch} from 'vue';
import {initialLoadingKey} from "@/AppKeys";
import HexaValue from "@/components/values/HexaValue.vue";

export default defineComponent({
name: 'ByteCodeValue',

props: {
byteCode: String,
},

setup(props) {
const textValue = ref(props.byteCode)
watch(() => props.byteCode, () => {
textValue.value = props.byteCode
})
const initialLoading = inject(initialLoadingKey, ref(false))
return { textValue, initialLoading }
}
name: 'ByteCodeValue',
components: {HexaValue},

props: {
byteCode: String,
},

setup(props) {
const textValue = ref(props.byteCode)
watch(() => props.byteCode, () => {
textValue.value = props.byteCode
})
const initialLoading = inject(initialLoadingKey, ref(false))
return {textValue, initialLoading}
}
});

</script>
Expand All @@ -67,4 +69,14 @@ export default defineComponent({
<!-- STYLE -->
<!-- --------------------------------------------------------------------------------------------------------------- -->

<style/>
<style>

.code-data-box {
border: 0.5px solid dimgrey;
gap: 0.42rem;
max-height: 20rem;
overflow-y: auto;
min-height: 5rem
}

</style>
107 changes: 107 additions & 0 deletions src/components/values/DisassembledCodeValue.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<!--
-
- Hedera Mirror Node Explorer
-
- Copyright (C) 2021 - 2023 Hedera Hashgraph, LLC
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
-->

<!-- --------------------------------------------------------------------------------------------------------------- -->
<!-- TEMPLATE -->
<!-- --------------------------------------------------------------------------------------------------------------- -->

<template>
<div v-if="disassembly" id="disassembly" class="mt-2 p-2 is-flex analyzed-data-box">
<div v-for="opcode in disassembly" v-if="disassembly && disassembly.length > 0" :key="opcode.index16">
<OpcodeValue :opcode="opcode" :show-hexa-opcode="showHexaOpcode"/>
</div>
<p v-else class="has-text-grey is-italic has-text-weight-medium">{{ disassembledError }}</p>
</div>

<span v-else-if="initialLoading"/>

<span v-else class="has-text-grey">None</span>

</template>

<!-- --------------------------------------------------------------------------------------------------------------- -->
<!-- SCRIPT -->
<!-- --------------------------------------------------------------------------------------------------------------- -->

<script lang="ts">

import {computed, defineComponent, inject, ref} from 'vue';
import {Disassembler} from '@/utils/bytecode_tools/disassembler/BytecodeDisassembler'
import {DisassembledOpcodeOutput} from '@/utils/bytecode_tools/disassembler/utils/helpers';
import OpcodeValue from "@/components/values/OpcodeValue.vue";
import {initialLoadingKey} from "@/AppKeys";

export default defineComponent({
name: 'DisassembledCodeValue',
components: {OpcodeValue},

props: {
byteCode: {
type: String,
default: ""
},
showHexaOpcode: {
type: Boolean,
default: false
}
},

setup(props) {
const initialLoading = inject(initialLoadingKey, ref(false))

const isValidBytecode = computed(() => {
const BYTECODE_REGEX = /^(0x)?([0-9a-fA-F]{2})+$/;
return BYTECODE_REGEX.test(props.byteCode)
})

const disassembly = computed<DisassembledOpcodeOutput[] | null>(
() => isValidBytecode ? Disassembler.disassemble(props.byteCode) : null)

const disassembledError = computed<string | null>(() =>
isValidBytecode ? null : (props.byteCode === "" ? "No data found..." : "Invalid bytecode")
)

return {
initialLoading,
disassembly,
disassembledError,
}
}
});

</script>

<!-- --------------------------------------------------------------------------------------------------------------- -->
<!-- STYLE -->
<!-- --------------------------------------------------------------------------------------------------------------- -->

<style>

.analyzed-data-box {
border: 0.5px solid dimgrey;
gap: 0.42rem;
flex-direction: column;
max-height: 20rem;
overflow-y: auto;
font-family: novamonoregular, monospace;
min-height: 5rem
}

</style>
6 changes: 5 additions & 1 deletion src/components/values/HexaValue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export default defineComponent({
wordWrapSmall: {
type: Number,
default: null
},
copyable: {
type:Boolean,
default: true
}
},

Expand Down Expand Up @@ -101,7 +105,7 @@ export default defineComponent({
}

const isCopyEnabled = computed(() => {
return (normByteString.value?.length ?? 0) >= 1
return props.copyable && (normByteString.value?.length ?? 0) >= 1
})

// 4)
Expand Down
Loading

0 comments on commit bafcf97

Please sign in to comment.