@tevm/runtime
Version:
Tools for generating the Tevm contract runtime
413 lines (398 loc) • 12.6 kB
text/typescript
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')
})
})