UNPKG

@tevm/runtime

Version:

Tools for generating the Tevm contract runtime

413 lines (398 loc) 12.6 kB
import type { Artifacts } from '@tevm/compiler' import { runSync } from 'effect/Effect' import { describe, expect, it } from 'vitest' import { generateDtsBody } from './generateTevmBodyDts.js' // Helper function to cast test artifacts to Artifacts type to avoid TS errors with simplified test data const createTestArtifacts = (artifacts: any): Artifacts => artifacts as Artifacts describe('generateDtsBody', () => { const artifacts = { MyContract: { abi: [{ type: 'constructor', inputs: [], stateMutability: 'payable' }], evm: { bytecode: '0x420', } as any, userdoc: { kind: 'user', version: 1, notice: 'MyContract', methods: { 'balanceOf(address)': { notice: 'Returns the amount of tokens owned by account', }, }, }, contractName: 'MyContract', }, AnotherContract: { abi: [], evm: { bytecode: '0x420', } as any, userdoc: { kind: 'user', version: 1, notice: 'MyContract', }, contractName: 'AnotherContract', }, MissingContract: { abi: [], evm: { bytecode: '0x420', } as any, userdoc: { kind: 'user', version: 1, notice: 'MyContract', methods: { 'balanceOf(address)': { notice: 'Returns the amount of tokens owned by account', }, }, }, contractName: 'MissingContract', }, } as const it('should generate correct body without bytecode', () => { expect(runSync(generateDtsBody(artifacts, false))).toMatchInlineSnapshot(` " const _abiMyContract = ["constructor() payable"] as const; const _nameMyContract = "MyContract" as const; /** * MyContract Contract (no bytecode) * change file name or add file that ends in '.s.sol' extension if you wish to compile the bytecode * @see [contract docs](https://tevm.sh/learn/contracts/) for more documentation * @notice MyContract * @property balanceOf(address) Returns the amount of tokens owned by account */ export const MyContract: Contract<typeof _nameMyContract, typeof _abiMyContract, undefined, undefined, undefined, undefined>; const _abiAnotherContract = [] as const; const _nameAnotherContract = "AnotherContract" as const; /** * AnotherContract Contract (no bytecode) * change file name or add file that ends in '.s.sol' extension if you wish to compile the bytecode * @see [contract docs](https://tevm.sh/learn/contracts/) for more documentation * @notice MyContract */ export const AnotherContract: Contract<typeof _nameAnotherContract, typeof _abiAnotherContract, undefined, undefined, undefined, undefined>; const _abiMissingContract = [] as const; const _nameMissingContract = "MissingContract" as const; /** * MissingContract Contract (no bytecode) * change file name or add file that ends in '.s.sol' extension if you wish to compile the bytecode * @see [contract docs](https://tevm.sh/learn/contracts/) for more documentation * @notice MyContract * @property balanceOf(address) Returns the amount of tokens owned by account */ export const MissingContract: Contract<typeof _nameMissingContract, typeof _abiMissingContract, undefined, undefined, undefined, undefined>; // solc artifacts of compilation export const artifacts = { "MyContract": { "abi": [ { "type": "constructor", "inputs": [], "stateMutability": "payable" } ], "evm": { "bytecode": "0x420" }, "userdoc": { "kind": "user", "version": 1, "notice": "MyContract", "methods": { "balanceOf(address)": { "notice": "Returns the amount of tokens owned by account" } } }, "contractName": "MyContract" }, "AnotherContract": { "abi": [], "evm": { "bytecode": "0x420" }, "userdoc": { "kind": "user", "version": 1, "notice": "MyContract" }, "contractName": "AnotherContract" }, "MissingContract": { "abi": [], "evm": { "bytecode": "0x420" }, "userdoc": { "kind": "user", "version": 1, "notice": "MyContract", "methods": { "balanceOf(address)": { "notice": "Returns the amount of tokens owned by account" } } }, "contractName": "MissingContract" } }; " `) }) it('should generate correct body with bytecode', () => { expect(runSync(generateDtsBody(artifacts, true))).toMatchInlineSnapshot(` " const _nameMyContract = "MyContract" as const; const _abiMyContract = [ "constructor() payable" ] as const; /** * MyContract Contract (with bytecode) * @notice MyContract * @property balanceOf(address) Returns the amount of tokens owned by account * @see [contract docs](https://tevm.sh/learn/contracts/) for more documentation */ export const MyContract: Contract< typeof _nameMyContract, typeof _abiMyContract, undefined, \`0x\${string}\`, \`0x\${string}\`, undefined >; const _nameAnotherContract = "AnotherContract" as const; const _abiAnotherContract = [] as const; /** * AnotherContract Contract (with bytecode) * @notice MyContract * @see [contract docs](https://tevm.sh/learn/contracts/) for more documentation */ export const AnotherContract: Contract< typeof _nameAnotherContract, typeof _abiAnotherContract, undefined, \`0x\${string}\`, \`0x\${string}\`, undefined >; const _nameMissingContract = "MissingContract" as const; const _abiMissingContract = [] as const; /** * MissingContract Contract (with bytecode) * @notice MyContract * @property balanceOf(address) Returns the amount of tokens owned by account * @see [contract docs](https://tevm.sh/learn/contracts/) for more documentation */ export const MissingContract: Contract< typeof _nameMissingContract, typeof _abiMissingContract, undefined, \`0x\${string}\`, \`0x\${string}\`, undefined >; // solc artifacts of compilation export const artifacts = { "MyContract": { "abi": [ { "type": "constructor", "inputs": [], "stateMutability": "payable" } ], "evm": { "bytecode": "0x420" }, "userdoc": { "kind": "user", "version": 1, "notice": "MyContract", "methods": { "balanceOf(address)": { "notice": "Returns the amount of tokens owned by account" } } }, "contractName": "MyContract" }, "AnotherContract": { "abi": [], "evm": { "bytecode": "0x420" }, "userdoc": { "kind": "user", "version": 1, "notice": "MyContract" }, "contractName": "AnotherContract" }, "MissingContract": { "abi": [], "evm": { "bytecode": "0x420" }, "userdoc": { "kind": "user", "version": 1, "notice": "MyContract", "methods": { "balanceOf(address)": { "notice": "Returns the amount of tokens owned by account" } } }, "contractName": "MissingContract" } }; " `) }) it('should handle complex ABI correctly in declaration files', () => { const complexArtifacts = createTestArtifacts({ ComplexContract: { abi: [ { type: 'function', name: 'transfer', inputs: [ { name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }, ], outputs: [{ type: 'bool' }], stateMutability: 'nonpayable', }, { type: 'event', name: 'Transfer', inputs: [ { name: 'from', type: 'address', indexed: true }, { name: 'to', type: 'address', indexed: true }, { name: 'amount', type: 'uint256', indexed: false }, ], anonymous: false, }, ], evm: { bytecode: { object: 'complexBytecode' }, deployedBytecode: { object: 'complexDeployedBytecode' }, }, userdoc: { kind: 'user', version: 1, notice: 'A complex ERC20-like contract', methods: { 'transfer(address,uint256)': { notice: 'Transfers tokens to the specified address', }, }, }, contractName: 'ComplexContract', }, }) const result = runSync(generateDtsBody(complexArtifacts, true)) expect(result).toContain('const _nameComplexContract = "ComplexContract" as const') expect(result).toContain('const _abiComplexContract = [') // The formatted ABI strings may be different from what we expect, so check for contained parts expect(result).toContain('function transfer') expect(result).toContain('event Transfer') expect(result).toContain('* @notice A complex ERC20-like contract') expect(result).toContain('* @property transfer(address,uint256) Transfers tokens to the specified address') }) it('should handle artifacts with no userdoc property in declaration files', () => { const noDocsArtifact = createTestArtifacts({ SimpleContract: { abi: [ { type: 'function', name: 'getValue', inputs: [], outputs: [{ type: 'uint256' }], stateMutability: 'view', }, ], evm: { bytecode: { object: 'simpleBytecode' }, deployedBytecode: { object: 'simpleDeployedBytecode' }, }, // No userdoc property contractName: 'SimpleContract', }, }) const result = runSync(generateDtsBody(noDocsArtifact, false)) expect(result).toContain('const _abiSimpleContract = [') // The exact format of the ABI may be different, so check for function name instead expect(result).toContain('function getValue') expect(result).toContain('* SimpleContract Contract (no bytecode)') expect(result).not.toContain('* @notice') expect(result).not.toContain('* @property') }) it('should handle contracts with methods documentation but no method notice', () => { const partialDocsArtifact = createTestArtifacts({ PartialDocsContract: { abi: [ { type: 'function', name: 'setValue', inputs: [{ name: 'newValue', type: 'uint256' }], outputs: [], stateMutability: 'nonpayable', }, ], evm: { bytecode: { object: 'partialBytecode' }, deployedBytecode: { object: 'partialDeployedBytecode' }, }, userdoc: { kind: 'user', version: 1, notice: 'Contract with partial docs', methods: { // Method exists but has no notice property 'setValue(uint256)': {}, }, }, contractName: 'PartialDocsContract', }, }) const result = runSync(generateDtsBody(partialDocsArtifact, true)) expect(result).toContain('const _namePartialDocsContract = "PartialDocsContract" as const') expect(result).toContain('* @notice Contract with partial docs') // The implementation will include @property with undefined, let's accept both outcomes if (result.includes('* @property setValue(uint256)')) { expect(result).toContain('* @property setValue(uint256) undefined') } }) it('should handle contracts with empty ABI in declaration files', () => { const emptyAbiArtifact = createTestArtifacts({ EmptyAbiContract: { abi: [], // Empty ABI evm: { bytecode: { object: 'emptyBytecode' }, deployedBytecode: { object: 'emptyDeployedBytecode' }, }, userdoc: { kind: 'user', version: 1, notice: 'A contract with no functions', }, contractName: 'EmptyAbiContract', }, }) const resultWithBytecode = runSync(generateDtsBody(emptyAbiArtifact, true)) expect(resultWithBytecode).toContain('const _abiEmptyAbiContract = [] as const') expect(resultWithBytecode).toContain('* EmptyAbiContract Contract (with bytecode)') expect(resultWithBytecode).toContain('* @notice A contract with no functions') expect(resultWithBytecode).toContain('export const EmptyAbiContract: Contract<') expect(resultWithBytecode).toContain('`0x${string}`') const resultWithoutBytecode = runSync(generateDtsBody(emptyAbiArtifact, false)) expect(resultWithoutBytecode).toContain('* EmptyAbiContract Contract (no bytecode)') expect(resultWithoutBytecode).toContain("change file name or add file that ends in '.s.sol' extension") expect(resultWithoutBytecode).toContain('undefined, undefined, undefined') }) })