Using my feathers backend, I want to programmatically compile Solidity smart contracts entered by the user in my nextjs frontend.
Since I am using TypeScript and it contains advanced features such as automatic compiler version recognition, I am using the solc-typed-ast package from ConsenSys.
The compilation (seemingly) works, but the returned bytecode is slightly different from what I receive when manually compiling the contracts using Remix. When I receive the result in my frontend and try to deploy the bytecode on Ethereum, the transaction seems to go through, but looking at the explorer the transaction has failed with status:
Warning! Error encountered during contract execution [invalid opcode: opcode 0x5f not defined]
What could be the reason for this wrong (yet, looking at the bytecode "almost correct" compilation?
This is my backend service doing the compilation:
import { Application } from '../../declarations';
import { compileSol } from 'solc-typed-ast';
/**
* A mapping from several Smart Contracts' Name to their Solidity Code or ABI
* @typedef {Object} CodeMap
* @property {string} key - The name of the Smart Contract
* @property {string} value - The Solidity Code or ABI of the Smart Contract
* @example
* {
* "Contract1": "contract Contract1 { ... }",
* "Contract2": "contract Contract2 { ... }",
* }
*/
export type CodeMap = { [key: string]: string };
/**
* Solidity Compiler Service
* This service compiles the Solidity code for a given Project using the solc-typed-ast library.
*/
export class SolidityCompiler {
...
async find(params?: Params | undefined): Promise<CompilationResult[]> {
...
// Fetch the code from the database
const codeToCompile = JSON.parse(template.code) as CodeMap;
// Initialize the compileResults array
const compileResults: CompilationResult[] = [];
try {
// Iterate over each contract in the codeToCompile map
for (const [contractName, contractCode] of Object.entries(codeToCompile)) {
// Create a temporary directory and file path for each contract
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'solidity-'));
const filePath = path.join(tempDir, `${contractName}.sol`);
// Write the contract's Solidity code to a file
fs.writeFileSync(filePath, contractCode);
try {
// Compile the Solidity code
const result = await compileSol(filePath, 'auto');
// Extract the compiled contract names
const compiledContractNames = Object.keys(result.data.contracts[filePath]);
// Iterate through compiled contracts
compiledContractNames.forEach((compiledContractName) => {
const compiledContract = result.data.contracts[filePath][compiledContractName];
// Store the result together with the compiled contract name
compileResults.push({
contractName: compiledContractName,
success: true,
abi: compiledContract.abi,
bytecode: compiledContract.evm.bytecode.object,
error: undefined,
compilerVersion: result.compilerVersion,
});
console.log(`Solidity Compiler Service: ${compiledContractName} compiled successfully`);
});
} catch (compileError: any) {
// Store possible errors together with the contract name
compileResults.push({
contractName,
success: false,
abi: undefined,
bytecode: undefined,
error: compileError.message,
});
console.error(`Solidity Compiler Service: ${contractName}`, compileError.message);
}
// Clean up the temporary file and directory
fs.unlinkSync(filePath);
fs.rmdirSync(tempDir);
}
} catch (error: any) {
throw new Error(`Solidity Compilation Error: ${error.message}`);
}
return compileResults;
}
}
The template.code
comes from my database and looks something like this:
"{\"ERC20\":\"pragma solidity ^0.5.0;\\n\\n// Flat ERC20\\n// Updated Jun 7 2019\\n\\n/**\\n * @dev Wrappers over Solidity's arithmetic operations with added overflow\\n * checks.\\n *\\n * Arithmetic operations in Solidity wrap on overflow. This can easily result\\n * in bugs, because programmers usually assume that an overflow raises an\\n * error, which is the standard behavior in high level programming languages.\\n *
SafeMathrestores this intuition by reverting the transaction when an\\n * operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n */\\nlibrary SafeMath {\\n /**\\n * @dev Returns the addition of two unsigned integers, reverting on\\n * overflow.\\n *\\n * Counterpart to Solidity's
+operator.\\n *\\n * Requirements:\\n * - Addition cannot overflow.\\n */\\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\\n uint256 c = a + b;\\n require(c >= a, \\\"SafeMath: addition overflow\\\");\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the subtraction of two unsigned integers, reverting on\\n * overflow (when the result is negative).\\n *\\n * Counterpart to Solidity's
-operator.\\n *\\n * Requirements:\\n * - Subtraction cannot overflow.\\n */\\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\\n require(b <= a, \\\"SafeMath: subtraction overflow\\\");\\n uint256 c = a - b;\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the multiplication of two unsigned integers, reverting on\\n * overflow.\\n *\\n * Counterpart to Solidity's
* operator.\\n *\\n * Requirements:\\n * - Multiplication cannot overflow.\\n */\\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\\n // benefit is lost if 'b' is also tested.\\n
0x5f
is the new PUSH0 op code introduced in solidity 0.8.20 and is not supported on all chains. Roll the compiler back to an earlier version or target a non-shanghai evm in your compilation and the error should go away.