Merge pull request #5178 from ethereum/vyper-compiler-updates

Vyper compiler updates
pull/5201/head
Joseph Izang 4 months ago committed by GitHub
commit e1cc29edb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 109
      apps/remix-ide-e2e/src/tests/vyper_api.test.ts
  2. 4
      apps/vyper/src/app/components/CompilerButton.tsx
  3. 78
      apps/vyper/src/app/utils/compiler.tsx
  4. 20
      apps/vyper/src/app/utils/remix-client.tsx

@ -27,7 +27,7 @@ module.exports = {
.frameParent() .frameParent()
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.waitForElementVisible({ .waitForElementVisible({
selector: "//*[@data-id='workspacesSelect' and contains(.,'snekmate')]", selector: "//*[@data-id='workspacesSelect' and contains(.,'vyper')]",
locateStrategy: 'xpath', locateStrategy: 'xpath',
timeout: 120000 timeout: 120000
}) })
@ -36,37 +36,38 @@ module.exports = {
locateStrategy: 'xpath', locateStrategy: 'xpath',
timeout: 120000 timeout: 120000
}) })
.openFile('examples')
.openFile('examples/auctions')
.openFile('examples/auctions/blind_auction.vy')
}, },
// 'Add vyper file to run tests #group1': function (browser: NightwatchBrowser) {
// browser.addFile('TestBallot.sol', sources[0]['TestBallot.sol']) // '@sources': () => sources,
// 'Context menu click to compile blind_auction should succeed #group1': function (browser: NightwatchBrowser) {
// browser
// // .click('*[data-id="treeViewLitreeViewItemblind_auction.vy"]')
// // .rightClick('*[data-id="treeViewLitreeViewItemblind_auction.vy"]')
// // .waitForElementPresent('[data-id="contextMenuItemvyper"]')
// // .click('[data-id="contextMenuItemvyper"]')
// .clickLaunchIcon('vyper')
// // @ts-ignore
// .frame(0)
// .waitForElementVisible({
// selector:'[data-id="compilation-details"]',
// timeout: 120000
// })
// .click('[data-id="compilation-details"]')
// .frameParent()
// .waitForElementVisible('[data-id="copy-abi"]')
// .waitForElementVisible({
// selector: "//*[@class='variable-value' and contains(.,'highestBidder')]",
// locateStrategy: 'xpath',
// })
// }, // },
'@sources': () => sources,
'Context menu click to compile blind_auction should succeed #group1': function (browser: NightwatchBrowser) {
browser
.addFileSnekmate('blind_auction.vy', sources[0]['blindAuction'])
.click('*[data-id="treeViewLitreeViewItemblind_auction.vy"]')
.rightClick('*[data-id="treeViewLitreeViewItemblind_auction.vy"]')
.waitForElementPresent('[data-id="contextMenuItemvyper"]')
.click('[data-id="contextMenuItemvyper"]')
.clickLaunchIcon('vyper')
// @ts-ignore
.frame(0)
.waitForElementVisible({
selector:'[data-id="compilation-details"]',
timeout: 120000
})
.click('[data-id="compilation-details"]')
.frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.waitForElementVisible({
selector: "//*[@class='variable-value' and contains(.,'highestBidder')]",
locateStrategy: 'xpath',
})
},
'Compile blind_auction should success #group1': function (browser: NightwatchBrowser) { 'Compile blind_auction should success #group1': function (browser: NightwatchBrowser) {
browser browser
// @ts-ignore // @ts-ignore
.clickLaunchIcon('vyper')
.frame(0) .frame(0)
.click('[data-id="compile"]') .click('[data-id="compile"]')
.waitForElementVisible({ .waitForElementVisible({
@ -145,32 +146,32 @@ module.exports = {
}) })
}, },
'Compile Ownable contract from snekmate #group1': function (browser: NightwatchBrowser) { // 'Compile Ownable contract from snekmate #group1': function (browser: NightwatchBrowser) {
let contractAddress // let contractAddress
browser // browser
.frameParent() // .frameParent()
.clickLaunchIcon('filePanel') // .clickLaunchIcon('filePanel')
.switchWorkspace('snekmate') // .switchWorkspace('vyper')
.openFile('src') // .openFile('src')
.openFile('src/snekmate') // .openFile('src/snekmate')
.openFile('src/snekmate/auth') // .openFile('src/snekmate/auth')
.openFile('src/snekmate/auth/Ownable.vy') // .openFile('src/snekmate/auth/Ownable.vy')
.rightClick('*[data-id="treeViewLitreeViewItemsrc/snekmate/auth/Ownable.vy"]') // .rightClick('*[data-id="treeViewLitreeViewItemsrc/snekmate/auth/Ownable.vy"]')
.waitForElementVisible('*[data-id="contextMenuItemvyper"]') // .waitForElementVisible('*[data-id="contextMenuItemvyper"]')
.click('*[data-id="contextMenuItemvyper"]') // .click('*[data-id="contextMenuItemvyper"]')
.clickLaunchIcon('vyper') // .clickLaunchIcon('vyper')
// @ts-ignore // // @ts-ignore
.frame(0) // .frame(0)
.click('[data-id="compile"]') // .click('[data-id="compile"]')
.waitForElementVisible({ // .waitForElementVisible({
selector:'[data-id="compilation-details"]', // selector:'[data-id="compilation-details"]',
timeout: 60000 // timeout: 60000
}) // })
.click('[data-id="compilation-details"]') // .click('[data-id="compilation-details"]')
.frameParent() // .frameParent()
.waitForElementVisible('[data-id="copy-abi"]') // .waitForElementVisible('[data-id="copy-abi"]')
.end() // .end()
} // }
} }
const testContract = ` const testContract = `
@ -209,7 +210,7 @@ const sources = [{
'blindAuction' : { content: ` 'blindAuction' : { content: `
# Blind Auction. Adapted to Vyper from [Solidity by Example](https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst#blind-auction-1) # Blind Auction. Adapted to Vyper from [Solidity by Example](https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst#blind-auction-1)
#pragma version ^0.3.10 #pragma version >0.3.10
struct Bid: struct Bid:
blindedBid: bytes32 blindedBid: bytes32
@ -384,6 +385,6 @@ def auctionEnd():
# Transfer funds to beneficiary # Transfer funds to beneficiary
send(self.beneficiary, self.highestBid) send(self.beneficiary, self.highestBid)
`} ` }
} }
] ]

@ -1,5 +1,5 @@
import React, { Fragment, useEffect, useState } from 'react' import React, { Fragment, useEffect, useState } from 'react'
import {isVyper, compile, toStandardOutput, isCompilationError, remixClient, normalizeContractPath, compileContract, RemixClient} from '../utils' import { isVyper, compile, toStandardOutput, isCompilationError, remixClient, normalizeContractPath, compileContract, RemixClient } from '../utils'
import Button from 'react-bootstrap/Button' import Button from 'react-bootstrap/Button'
interface Props { interface Props {
@ -11,7 +11,7 @@ interface Props {
remixClient: RemixClient remixClient: RemixClient
} }
function CompilerButton({contract, setOutput, compilerUrl, resetCompilerState, output, remixClient}: Props) { function CompilerButton({ contract, setOutput, compilerUrl, resetCompilerState, output, remixClient }: Props) {
const [loadingSpinner, setLoadingSpinnerState] = useState(false) const [loadingSpinner, setLoadingSpinnerState] = useState(false)
if (!contract || !contract) { if (!contract || !contract) {

@ -45,22 +45,30 @@ export function normalizeContractPath(contractPath: string): string[] {
return [folders,resultingPath, filename] return [folders,resultingPath, filename]
} }
function parseErrorString(errorString) { function parseErrorString(errorStructure: string[]) {
// Split the string into lines // Split the string into lines
let lines = errorString.trim().split('\n') let errorType = ''
// Extract the line number and message let message = ''
let message = errorString.trim() let tline = ''
let targetLine = lines[2].split(',') errorStructure.forEach(errorMsg => {
let tline = lines[2].trim().split(' ')[1].split(':') const choppedup = errorMsg.split(': ')
errorType = choppedup[0].trim().split('\n')[1]
message = choppedup[1]
// if (errorStructure.length > 2) {
// console.log(choppedup[2].split(',')[1])
// }
// console.log(choppedup)
})
let lines = errorStructure[0].trim().split('\n')
const errorObject = { const errorObject = {
status: 'failed', status: 'failed',
message: message, message: `${errorType} - ${message}`,
column: tline[1], column: '',
line: tline[0] line: ''
} }
message = null message = null
targetLine = null // targetLine = null
lines = null lines = null
tline = null tline = null
return errorObject return errorObject
@ -109,10 +117,10 @@ const compileReturnType = (output, contract) => {
const normal = normalizeContractPath(contract)[2] const normal = normalizeContractPath(contract)[2]
const abi = temp[normal]['abi'] const abi = temp[normal]['abi']
const evm = _.merge(temp[normal]['evm']) const evm = _.merge(temp[normal]['evm'])
const dpb = evm.deployedBytecode const depByteCode = evm.deployedBytecode
const runtimeBytecode = evm.bytecode const runtimeBytecode = evm.bytecode
const methodIdentifiers = evm.methodIdentifiers const methodIdentifiers = evm.methodIdentifiers
const version = output?.compilers[0]?.version ?? '0.3.10' const version = output?.compilers[0]?.version ?? '0.4.0'
const optimized = output?.compilers[0]?.settings?.optimize ?? true const optimized = output?.compilers[0]?.settings?.optimize ?? true
const evmVersion = '' const evmVersion = ''
@ -129,7 +137,7 @@ const compileReturnType = (output, contract) => {
} = { } = {
contractName: normal, contractName: normal,
abi, abi,
bytecode: dpb, bytecode: depByteCode,
runtimeBytecode, runtimeBytecode,
ir: '', ir: '',
methodIdentifiers, methodIdentifiers,
@ -140,26 +148,32 @@ const compileReturnType = (output, contract) => {
return result return result
} }
const fixContractContent = (content: string) => { const updatePragmaDeclaration = (content: string) => {
if (content.length === 0) return const pragmaRegex = /#\s*pragma\s+[@]*version\s+([~<>!=^]+)\s*(\d+\.\d+\.\d+)/
const pragmaFound = content.includes('#pragma version ^0.3.10') const oldPragmaRegex = /#\s*pragma\s+[@]*version\s+([\^^]+)\s*(\d+\.\d+\.\d+)/
const wrongpragmaFound = content.includes('# pragma version ^0.3.10') const oldPragmaDeclaration = ['# pragma version ^0.2.16', '# pragma version ^0.3.10', '#pragma version ^0.2.16', '#pragma version ^0.3.10']
const evmVerFound = content.includes('#pragma evm-version shanghai') const pragmaFound = content.match(pragmaRegex)
const pragma = '#pragma version ^0.3.10' const oldPragmaFound = content.match(oldPragmaRegex)
const evmVer = '#pragma evm-version shanghai'
if (evmVerFound === false) { const pragma = '# pragma version ~=0.4.0'
content = `${evmVer}\n${content}`
} if (oldPragmaFound) {
if (wrongpragmaFound === true) { // oldPragmaDeclaration.forEach(declaration => {
content = content.replace('# pragma version ^0.3.10', '') // content = content.replace(declaration, '# pragma version >0.3.10')
// })
return content
} }
if (pragmaFound === false ) { if (!pragmaFound) {
content = `${pragma}\n${content}` content = `${pragma}\n\n${content}`
} }
return content return content
} }
const fixContractContent = (content: string) => {
if (content.length === 0) return
return updatePragmaDeclaration(content)
}
/** /**
* Compile the a contract * Compile the a contract
* @param url The url of the compiler * @param url The url of the compiler
@ -174,13 +188,16 @@ export async function compile(url: string, contract: Contract): Promise<any> {
throw new Error('Use extension .vy for Vyper.') throw new Error('Use extension .vy for Vyper.')
} }
const cleanedUpContent = fixContractContent(contract.content)
let contractName = contract['name'] let contractName = contract['name']
const compilePackage = { const compilePackage = {
manifest: 'ethpm/3', manifest: 'ethpm/3',
sources: { sources: {
[contractName] : { content : fixContractContent(contract.content) } [contractName] : { content : cleanedUpContent }
} }
} }
let response = await axios.post(`${url}compile`, compilePackage ) let response = await axios.post(`${url}compile`, compilePackage )
if (response.status === 404) { if (response.status === 404) {
@ -194,6 +211,7 @@ export async function compile(url: string, contract: Contract): Promise<any> {
contractName = null contractName = null
response = null response = null
let result: any let result: any
let intermediateError
const status = await (await axios.get(url + 'status/' + compileCode , { const status = await (await axios.get(url + 'status/' + compileCode , {
method: 'Get' method: 'Get'
@ -208,7 +226,9 @@ export async function compile(url: string, contract: Contract): Promise<any> {
const intermediate = await(await axios.get(url + 'exceptions/' + compileCode , { const intermediate = await(await axios.get(url + 'exceptions/' + compileCode , {
method: 'Get' method: 'Get'
})).data })).data
result = parseErrorString(intermediate[0]) // console.log('Errors found', intermediate)
result = parseErrorString(intermediate)
intermediateError = intermediate
return result return result
} }
await new Promise((resolve) => setTimeout(() => resolve({}), 3000)) await new Promise((resolve) => setTimeout(() => resolve({}), 3000))

@ -87,18 +87,18 @@ export class RemixClient extends PluginClient<any, CustomRemixApi> {
await this.call( await this.call(
'dgitApi', 'dgitApi',
'clone', 'clone',
{url: 'https://github.com/pcaversaccio/snekmate', token: null, branch: 'main', singleBranch: false, workspaceName: 'snekmate'}, { url: 'https://github.com/vyperlang/vyper', token: null, branch: 'master', singleBranch: false, workspaceName: 'vyper' },
) )
await this.call( // await this.call(
'dgitApi', // 'dgitApi',
'checkout', // 'checkout',
{ // {
ref:'v0.0.5', // ref:'v0.0.5',
force: true, // force: true,
refresh: true, // refresh: true,
} // }
) // )
this.call( this.call(
// @ts-ignore // @ts-ignore

Loading…
Cancel
Save