Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for Vyper support #1781

Merged
merged 2 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 151 additions & 2 deletions packages/lib-sourcify/test/functions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ import { Metadata, MissingSources } from '../src/lib/types';
import WrongMetadata from './sources/WrongMetadata/metadata.json';
import SimplyLog from './sources/WrongMetadata/SimplyLog.json';
import earlyCompilerInput from './sources/json-input/pre-v0.4.0/input.json';
import { keccak256 } from 'ethers';
import { solc } from './utils';
import { id, keccak256 } from 'ethers';
import { solc, vyperCompiler } from './utils';
import { fetchWithBackoff } from '../src/lib/utils';
import nock from 'nock';
import path from 'path';
import fs from 'fs';
import { VyperCheckedContract, VyperJsonInput, VyperSettings } from '../src';
import Sinon from 'sinon';

describe('Verify Solidity Compiler', () => {
it('Should fetch latest SolcJS compiler', async () => {
Expand Down Expand Up @@ -141,6 +145,16 @@ describe('Verify Solidity Compiler', () => {
});

describe('Checked contract', () => {
let sandbox: sinon.SinonSandbox;

beforeEach(() => {
sandbox = Sinon.createSandbox();
});

afterEach(() => {
sandbox.restore();
});

it('Should return null after failed performFetch', async () => {
expect(await performFetch('httpx://')).to.equal(null);
});
Expand Down Expand Up @@ -245,4 +259,139 @@ describe('Checked contract', () => {
'0x8e7a1207ba791693fd76c6cf3e99908f53b8c67a5ae9f7b4ab628c74901711c9',
);
});
it('Should mock a metadata object for a Vyper contract', async () => {
const contractFolderPath = path.join(
__dirname,
'sources',
'Vyper',
'testcontract',
);
const contractFileName = 'test.vy';
const contractName = 'test';
const vyperContent = await fs.promises.readFile(
path.join(contractFolderPath, contractFileName),
);
const vyperVersion = '0.3.10+commit.91361694';
const vyperSettings = {
evmVersion: 'istanbul',
outputSelection: {
'*': ['evm.bytecode'],
},
} as VyperSettings;

const checkedContract = new VyperCheckedContract(
vyperCompiler,
vyperVersion,
contractFileName,
contractName,
vyperSettings,
{
[contractFileName]: vyperContent.toString(),
},
);

expect(checkedContract.metadata).to.deep.equal({
compiler: { version: vyperVersion },
language: 'Vyper',
output: {
abi: [],
devdoc: { kind: 'dev', methods: {} },
userdoc: { kind: 'user', methods: {} },
},
settings: {
...vyperSettings,
compilationTarget: { [contractFileName]: contractName },
},
sources: {
[contractFileName]: {
content: vyperContent.toString(),
keccak256: id(vyperContent.toString()),
},
},
version: 1,
});
});

it('Should throw an error on recompilation for a wrong name of a Vyper contract', async () => {
const contractFolderPath = path.join(
__dirname,
'sources',
'Vyper',
'testcontract',
);
const contractFileName = 'test.vy';
const wrongContractName = 'tes';
const vyperContent = await fs.promises.readFile(
path.join(contractFolderPath, contractFileName),
);
const vyperVersion = '0.3.10+commit.91361694';
const vyperSettings = {
evmVersion: 'istanbul',
outputSelection: {
'*': ['evm.bytecode'],
},
} as VyperSettings;

const checkedContract = new VyperCheckedContract(
vyperCompiler,
vyperVersion,
contractFileName,
wrongContractName,
vyperSettings,
{
[contractFileName]: vyperContent.toString(),
},
);

try {
await checkedContract.recompile();
expect.fail('Should have thrown an error');
} catch (e: any) {
expect(e.message).to.equal('Compiler error');
}
});

it('Should provide a correct VyperJsonInput to the vyper compiler', async () => {
const compileSpy = sandbox.spy(vyperCompiler, 'compile');

const contractFolderPath = path.join(
__dirname,
'sources',
'Vyper',
'testcontract',
);
const contractFileName = 'test.vy';
const contractName = 'test';
const vyperContent = await fs.promises.readFile(
path.join(contractFolderPath, contractFileName),
);
const vyperVersion = '0.3.10+commit.91361694';
const vyperSettings = {
evmVersion: 'istanbul',
outputSelection: {
'*': ['evm.bytecode'],
},
} as VyperSettings;

const checkedContract = new VyperCheckedContract(
vyperCompiler,
vyperVersion,
contractFileName,
contractName,
vyperSettings,
{
[contractFileName]: vyperContent.toString(),
},
);

await checkedContract.recompile();

const expectedVyperJsonInput = {
language: 'Vyper',
sources: { [contractFileName]: { content: vyperContent.toString() } },
settings: vyperSettings,
} as VyperJsonInput;
expect(compileSpy.calledWith(vyperVersion, expectedVyperJsonInput)).to.be
.true;
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"abi": [
{
"stateMutability": "view",
"type": "function",
"name": "helloWorld",
"inputs": [],
"outputs": [{ "name": "", "type": "string" }]
}
],
"bytecode": "0x61008f61000f60003961008f6000f360003560e01c63c605f76c8118610084573461008a57602080608052600c6040527f48656c6c6f20567970657221000000000000000000000000000000000000000060605260408160800181518152602082015160208201528051806020830101601f82600003163682375050601f19601f8251602001011690509050810190506080f35b60006000fd5b600080fd84188f8000a16576797065728300030a0012"
}
11 changes: 11 additions & 0 deletions packages/lib-sourcify/test/sources/Vyper/testcontract2/test.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# @version >=0.3.2

# @notice Simple greeting contract

# @notice Returns the string "Hello Vyper!"
# @notice The @external decorator means this function can only be called by external parties ie. by other contracts or by a wallet making a transaction
# @notice The @view decorator means that this function can read the contract state but not alter it. Cannot consume gas.
@external
@view
def helloWorld() -> String[24]:
return "Hello Vyper!"
118 changes: 83 additions & 35 deletions packages/lib-sourcify/test/verification.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,43 +568,91 @@ describe('lib-sourcify tests', () => {
});
});

it('should verify a vyper contract', async () => {
const contractFolderPath = path.join(
__dirname,
'sources',
'Vyper',
'contract',
);
const { contractAddress } = await deployFromAbiAndBytecode(
signer,
contractFolderPath,
[],
);
const vyperContent = await fs.promises.readFile(
path.join(__dirname, 'sources', 'Vyper', 'contract', 'test.vy'),
);
const checkedContract = new VyperCheckedContract(
vyperCompiler,
'0.3.10+commit.91361694',
'test.vy',
'test',
{
evmVersion: 'istanbul',
outputSelection: {
'*': ['evm.bytecode'],
describe('Vyper', () => {
it('should verify a vyper contract', async () => {
const contractFolderPath = path.join(
__dirname,
'sources',
'Vyper',
'testcontract',
);
const contractFileName = 'test.vy';
const vyperContent = await fs.promises.readFile(
path.join(contractFolderPath, contractFileName),
);

const { contractAddress } = await deployFromAbiAndBytecode(
signer,
contractFolderPath,
[],
);
const checkedContract = new VyperCheckedContract(
vyperCompiler,
'0.3.10+commit.91361694',
contractFileName,
contractFileName.split('.')[0],
{
evmVersion: 'istanbul',
outputSelection: {
'*': ['evm.bytecode'],
},
},
},
{
'test.vy': vyperContent.toString(),
},
);
{
[contractFileName]: vyperContent.toString(),
},
);
const match = await verifyDeployed(
checkedContract,
sourcifyChainHardhat,
contractAddress,
);

const match = await verifyDeployed(
checkedContract,
sourcifyChainHardhat,
contractAddress,
);
console.log(match, checkedContract);
expectMatch(match, 'partial', contractAddress);
});

it('should fail to verify a different Vyper contract', async () => {
const contractFolderPath = path.join(
__dirname,
'sources',
'Vyper',
'testcontract',
);
const { contractAddress } = await deployFromAbiAndBytecode(
signer,
contractFolderPath,
[],
);

const contractFileName = 'test.vy';
const wrongContractContent = await fs.promises.readFile(
path.join(
__dirname,
'sources',
'Vyper',
'testcontract2',
contractFileName,
),
);
const checkedContract = new VyperCheckedContract(
vyperCompiler,
'0.3.10+commit.91361694',
contractFileName,
contractFileName.split('.')[0],
{
evmVersion: 'istanbul',
outputSelection: {
'*': ['evm.bytecode'],
},
},
{
[contractFileName]: wrongContractContent.toString(),
},
);

await expect(
verifyDeployed(checkedContract, sourcifyChainHardhat, contractAddress),
).to.be.rejectedWith("The deployed and recompiled bytecode don't match.");
});
});

describe('Unit tests', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@ import {
VyperCheckedContract,
IVyperCompiler,
StringMap,
VyperSettings,
} from "@ethereum-sourcify/lib-sourcify";
import { NotFoundError, BadRequestError } from "../../../../../common/errors";
import { getResponseMatchFromMatch } from "../../../../common";
import { Services } from "../../../../services/services";
import { ChainRepository } from "../../../../../sourcify-chain-repository";
import logger from "../../../../../common/logger";

export type VerifyVyperRequest = Request & {
export type VerifyVyperRequest = Omit<Request, "body"> & {
body: {
files: Record<string, string>;
compilerVersion: string;
compilerSettings: string;
compilerSettings: VyperSettings;
contractPath: string;
contractName: string;
address: string;
creatorTxHash: string;
chain: string;
};
};

Expand All @@ -30,7 +34,7 @@ export async function verifyVyper(
const chainRepository = req.app.get("chainRepository") as ChainRepository;

const inputFiles = extractFiles(req);
if (!inputFiles) {
if (!inputFiles || !inputFiles.length) {
const msg =
"Couldn't extract files from the request. Please make sure you have added files";
throw new NotFoundError(msg);
Expand Down
Loading