diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0d5939bd2032..2109063bc67b 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -45,6 +45,8 @@ import ( _ "github.com/ethereum/go-ethereum/eth/tracers/js" _ "github.com/ethereum/go-ethereum/eth/tracers/native" + _ "github.com/ethereum/go-ethereum/precompile/registry" + "github.com/urfave/cli/v2" ) diff --git a/contracts/contracts/ExampleSum3.sol b/contracts/contracts/ExampleSum3.sol new file mode 100644 index 000000000000..2088618b2e82 --- /dev/null +++ b/contracts/contracts/ExampleSum3.sol @@ -0,0 +1,21 @@ +pragma solidity ^0.8.0; + +import "hardhat/console.sol"; + +address constant PRECOMPILED_SUM3_CONTRACT_ADDRESS = address(0x0300000000000000000000000000000000000000); + +interface ISum3 { + function calcSum3(uint256 a, uint256 b, uint256 c) external; + function getSum3() external view returns (uint256 result); +} + +contract ExampleSum3 { + function calcSum3(uint256 a, uint256 b, uint256 c) public { + ISum3(PRECOMPILED_SUM3_CONTRACT_ADDRESS).calcSum3(a,b,c); + } + + function getSum3() public view returns (uint256) { + uint256 result = ISum3(PRECOMPILED_SUM3_CONTRACT_ADDRESS).getSum3(); + return result; + } +} diff --git a/contracts/test/sum3.ts b/contracts/test/sum3.ts new file mode 100644 index 000000000000..2fa67dc1c9c8 --- /dev/null +++ b/contracts/test/sum3.ts @@ -0,0 +1,44 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import {BaseContract, Contract} from "ethers"; +import {HardhatEthersSigner} from "@nomicfoundation/hardhat-ethers/src/signers"; + +const PRECOMPILED_SUM3_CONTRACT_ADDRESS: string = "0x0300000000000000000000000000000000000000"; + +const sum3ContractABI: string[] = [ + "function calcSum3(uint256 a, uint256 b, uint256 c) external", + "function getSum3() external view returns (uint256)", +]; + +describe("Testing precompiled sum3 contract", function() { + it("Should properly calculate sum of 3 numbers (direct call to precompiled contract)", async function () { + const sum3Contract: Contract = new ethers.Contract(PRECOMPILED_SUM3_CONTRACT_ADDRESS, sum3ContractABI, ethers.provider); + const signers: HardhatEthersSigner[] = await ethers.getSigners(); + const sum3ContractWithSigner: BaseContract = sum3Contract.connect(signers[0]); + + await sum3ContractWithSigner.calcSum3(2, 3, 4); + let actual: string = await sum3Contract.getSum3(); + let expected: string = "0x0000000000000000000000000000000000000000000000000000000000000009"; + expect(actual).to.equal(expected); + + await sum3ContractWithSigner.calcSum3(3, 5, 7); + actual = await sum3Contract.getSum3(); + expected = "0x000000000000000000000000000000000000000000000000000000000000000f" + expect(actual).to.equal(expected); + }) + + it("Should properly calculate sum of 3 numbers (call to example contract)", async function () { + const ExampleSum3 = await ethers.getContractFactory("ExampleSum3"); + const exampleSum3 = await ExampleSum3.deploy(); + + await exampleSum3.calcSum3(2, 3, 4); + let actual: string = await exampleSum3.getSum3(); + let expected: string = "0x0000000000000000000000000000000000000000000000000000000000000009"; + expect(actual).to.equal(expected); + + await exampleSum3.calcSum3(3, 5, 7); + actual = await exampleSum3.getSum3(); + expected = "0x000000000000000000000000000000000000000000000000000000000000000f"; + expect(actual).to.equal(expected); + }) +}) diff --git a/contracts/typechain-types/ExampleSum3.sol/ExampleSum3.ts b/contracts/typechain-types/ExampleSum3.sol/ExampleSum3.ts new file mode 100644 index 000000000000..4cc50fe9e691 --- /dev/null +++ b/contracts/typechain-types/ExampleSum3.sol/ExampleSum3.ts @@ -0,0 +1,103 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumberish, + BytesLike, + FunctionFragment, + Result, + Interface, + ContractRunner, + ContractMethod, + Listener, +} from "ethers"; +import type { + TypedContractEvent, + TypedDeferredTopicFilter, + TypedEventLog, + TypedListener, + TypedContractMethod, +} from "../common"; + +export interface ExampleSum3Interface extends Interface { + getFunction(nameOrSignature: "calcSum3" | "getSum3"): FunctionFragment; + + encodeFunctionData( + functionFragment: "calcSum3", + values: [BigNumberish, BigNumberish, BigNumberish] + ): string; + encodeFunctionData(functionFragment: "getSum3", values?: undefined): string; + + decodeFunctionResult(functionFragment: "calcSum3", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "getSum3", data: BytesLike): Result; +} + +export interface ExampleSum3 extends BaseContract { + connect(runner?: ContractRunner | null): ExampleSum3; + waitForDeployment(): Promise; + + interface: ExampleSum3Interface; + + queryFilter( + event: TCEvent, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + queryFilter( + filter: TypedDeferredTopicFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + + on( + event: TCEvent, + listener: TypedListener + ): Promise; + on( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + once( + event: TCEvent, + listener: TypedListener + ): Promise; + once( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + listeners( + event: TCEvent + ): Promise>>; + listeners(eventName?: string): Promise>; + removeAllListeners( + event?: TCEvent + ): Promise; + + calcSum3: TypedContractMethod< + [a: BigNumberish, b: BigNumberish, c: BigNumberish], + [void], + "nonpayable" + >; + + getSum3: TypedContractMethod<[], [bigint], "view">; + + getFunction( + key: string | FunctionFragment + ): T; + + getFunction( + nameOrSignature: "calcSum3" + ): TypedContractMethod< + [a: BigNumberish, b: BigNumberish, c: BigNumberish], + [void], + "nonpayable" + >; + getFunction( + nameOrSignature: "getSum3" + ): TypedContractMethod<[], [bigint], "view">; + + filters: {}; +} diff --git a/contracts/typechain-types/ExampleSum3.sol/ISum3.ts b/contracts/typechain-types/ExampleSum3.sol/ISum3.ts new file mode 100644 index 000000000000..9e5fd2d574cf --- /dev/null +++ b/contracts/typechain-types/ExampleSum3.sol/ISum3.ts @@ -0,0 +1,103 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumberish, + BytesLike, + FunctionFragment, + Result, + Interface, + ContractRunner, + ContractMethod, + Listener, +} from "ethers"; +import type { + TypedContractEvent, + TypedDeferredTopicFilter, + TypedEventLog, + TypedListener, + TypedContractMethod, +} from "../common"; + +export interface ISum3Interface extends Interface { + getFunction(nameOrSignature: "calcSum3" | "getSum3"): FunctionFragment; + + encodeFunctionData( + functionFragment: "calcSum3", + values: [BigNumberish, BigNumberish, BigNumberish] + ): string; + encodeFunctionData(functionFragment: "getSum3", values?: undefined): string; + + decodeFunctionResult(functionFragment: "calcSum3", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "getSum3", data: BytesLike): Result; +} + +export interface ISum3 extends BaseContract { + connect(runner?: ContractRunner | null): ISum3; + waitForDeployment(): Promise; + + interface: ISum3Interface; + + queryFilter( + event: TCEvent, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + queryFilter( + filter: TypedDeferredTopicFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + + on( + event: TCEvent, + listener: TypedListener + ): Promise; + on( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + once( + event: TCEvent, + listener: TypedListener + ): Promise; + once( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + listeners( + event: TCEvent + ): Promise>>; + listeners(eventName?: string): Promise>; + removeAllListeners( + event?: TCEvent + ): Promise; + + calcSum3: TypedContractMethod< + [a: BigNumberish, b: BigNumberish, c: BigNumberish], + [void], + "nonpayable" + >; + + getSum3: TypedContractMethod<[], [bigint], "view">; + + getFunction( + key: string | FunctionFragment + ): T; + + getFunction( + nameOrSignature: "calcSum3" + ): TypedContractMethod< + [a: BigNumberish, b: BigNumberish, c: BigNumberish], + [void], + "nonpayable" + >; + getFunction( + nameOrSignature: "getSum3" + ): TypedContractMethod<[], [bigint], "view">; + + filters: {}; +} diff --git a/contracts/typechain-types/ExampleSum3.sol/index.ts b/contracts/typechain-types/ExampleSum3.sol/index.ts new file mode 100644 index 000000000000..3bd537b59f6a --- /dev/null +++ b/contracts/typechain-types/ExampleSum3.sol/index.ts @@ -0,0 +1,5 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export type { ExampleSum3 } from "./ExampleSum3"; +export type { ISum3 } from "./ISum3"; diff --git a/contracts/typechain-types/factories/ExampleSum3.sol/ExampleSum3__factory.ts b/contracts/typechain-types/factories/ExampleSum3.sol/ExampleSum3__factory.ts new file mode 100644 index 000000000000..3cc4060d54e0 --- /dev/null +++ b/contracts/typechain-types/factories/ExampleSum3.sol/ExampleSum3__factory.ts @@ -0,0 +1,100 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import { + Contract, + ContractFactory, + ContractTransactionResponse, + Interface, +} from "ethers"; +import type { Signer, ContractDeployTransaction, ContractRunner } from "ethers"; +import type { NonPayableOverrides } from "../../common"; +import type { + ExampleSum3, + ExampleSum3Interface, +} from "../../ExampleSum3.sol/ExampleSum3"; + +const _abi = [ + { + inputs: [ + { + internalType: "uint256", + name: "a", + type: "uint256", + }, + { + internalType: "uint256", + name: "b", + type: "uint256", + }, + { + internalType: "uint256", + name: "c", + type: "uint256", + }, + ], + name: "calcSum3", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "getSum3", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; + +const _bytecode = + "0x608060405234801561001057600080fd5b506102f3806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80638e2a5b251461003b578063a104464e14610057575b600080fd5b610055600480360381019061005091906101c7565b610075565b005b61005f6100fd565b60405161006c9190610229565b60405180910390f35b73030000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16638e2a5b258484846040518463ffffffff1660e01b81526004016100c693929190610244565b600060405180830381600087803b1580156100e057600080fd5b505af11580156100f4573d6000803e3d6000fd5b50505050505050565b60008073030000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a104464e6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561015f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101839190610290565b90508091505090565b600080fd5b6000819050919050565b6101a481610191565b81146101af57600080fd5b50565b6000813590506101c18161019b565b92915050565b6000806000606084860312156101e0576101df61018c565b5b60006101ee868287016101b2565b93505060206101ff868287016101b2565b9250506040610210868287016101b2565b9150509250925092565b61022381610191565b82525050565b600060208201905061023e600083018461021a565b92915050565b6000606082019050610259600083018661021a565b610266602083018561021a565b610273604083018461021a565b949350505050565b60008151905061028a8161019b565b92915050565b6000602082840312156102a6576102a561018c565b5b60006102b48482850161027b565b9150509291505056fea2646970667358221220809168385427697f8571a2fa81d6ffd9fcb93182e02b234996ae8c93bb63b97664736f6c63430008130033"; + +type ExampleSum3ConstructorParams = + | [signer?: Signer] + | ConstructorParameters; + +const isSuperArgs = ( + xs: ExampleSum3ConstructorParams +): xs is ConstructorParameters => xs.length > 1; + +export class ExampleSum3__factory extends ContractFactory { + constructor(...args: ExampleSum3ConstructorParams) { + if (isSuperArgs(args)) { + super(...args); + } else { + super(_abi, _bytecode, args[0]); + } + } + + override getDeployTransaction( + overrides?: NonPayableOverrides & { from?: string } + ): Promise { + return super.getDeployTransaction(overrides || {}); + } + override deploy(overrides?: NonPayableOverrides & { from?: string }) { + return super.deploy(overrides || {}) as Promise< + ExampleSum3 & { + deploymentTransaction(): ContractTransactionResponse; + } + >; + } + override connect(runner: ContractRunner | null): ExampleSum3__factory { + return super.connect(runner) as ExampleSum3__factory; + } + + static readonly bytecode = _bytecode; + static readonly abi = _abi; + static createInterface(): ExampleSum3Interface { + return new Interface(_abi) as ExampleSum3Interface; + } + static connect(address: string, runner?: ContractRunner | null): ExampleSum3 { + return new Contract(address, _abi, runner) as unknown as ExampleSum3; + } +} diff --git a/contracts/typechain-types/factories/ExampleSum3.sol/ISum3__factory.ts b/contracts/typechain-types/factories/ExampleSum3.sol/ISum3__factory.ts new file mode 100644 index 000000000000..ed5f8092ee2a --- /dev/null +++ b/contracts/typechain-types/factories/ExampleSum3.sol/ISum3__factory.ts @@ -0,0 +1,55 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Interface, type ContractRunner } from "ethers"; +import type { ISum3, ISum3Interface } from "../../ExampleSum3.sol/ISum3"; + +const _abi = [ + { + inputs: [ + { + internalType: "uint256", + name: "a", + type: "uint256", + }, + { + internalType: "uint256", + name: "b", + type: "uint256", + }, + { + internalType: "uint256", + name: "c", + type: "uint256", + }, + ], + name: "calcSum3", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "getSum3", + outputs: [ + { + internalType: "uint256", + name: "result", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; + +export class ISum3__factory { + static readonly abi = _abi; + static createInterface(): ISum3Interface { + return new Interface(_abi) as ISum3Interface; + } + static connect(address: string, runner?: ContractRunner | null): ISum3 { + return new Contract(address, _abi, runner) as unknown as ISum3; + } +} diff --git a/contracts/typechain-types/factories/ExampleSum3.sol/index.ts b/contracts/typechain-types/factories/ExampleSum3.sol/index.ts new file mode 100644 index 000000000000..ce09fd5778a6 --- /dev/null +++ b/contracts/typechain-types/factories/ExampleSum3.sol/index.ts @@ -0,0 +1,5 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export { ExampleSum3__factory } from "./ExampleSum3__factory"; +export { ISum3__factory } from "./ISum3__factory"; diff --git a/contracts/typechain-types/factories/index.ts b/contracts/typechain-types/factories/index.ts index fbd664ae492e..8666cfd29d12 100644 --- a/contracts/typechain-types/factories/index.ts +++ b/contracts/typechain-types/factories/index.ts @@ -1,4 +1,5 @@ /* Autogenerated file. Do not edit manually. */ /* tslint:disable */ /* eslint-disable */ +export * as exampleSum3Sol from "./ExampleSum3.sol"; export { ExampleStatelessSum3__factory } from "./ExampleStatelessSum3__factory"; diff --git a/contracts/typechain-types/hardhat.d.ts b/contracts/typechain-types/hardhat.d.ts index 912ab3cd3e0b..8c0c7ba8e59e 100644 --- a/contracts/typechain-types/hardhat.d.ts +++ b/contracts/typechain-types/hardhat.d.ts @@ -17,23 +17,59 @@ declare module "hardhat/types/runtime" { name: "ExampleStatelessSum3", signerOrOptions?: ethers.Signer | FactoryOptions ): Promise; + getContractFactory( + name: "ExampleSum3", + signerOrOptions?: ethers.Signer | FactoryOptions + ): Promise; + getContractFactory( + name: "ISum3", + signerOrOptions?: ethers.Signer | FactoryOptions + ): Promise; getContractAt( name: "ExampleStatelessSum3", address: string | ethers.Addressable, signer?: ethers.Signer ): Promise; + getContractAt( + name: "ExampleSum3", + address: string | ethers.Addressable, + signer?: ethers.Signer + ): Promise; + getContractAt( + name: "ISum3", + address: string | ethers.Addressable, + signer?: ethers.Signer + ): Promise; deployContract( name: "ExampleStatelessSum3", signerOrOptions?: ethers.Signer | DeployContractOptions ): Promise; + deployContract( + name: "ExampleSum3", + signerOrOptions?: ethers.Signer | DeployContractOptions + ): Promise; + deployContract( + name: "ISum3", + signerOrOptions?: ethers.Signer | DeployContractOptions + ): Promise; deployContract( name: "ExampleStatelessSum3", args: any[], signerOrOptions?: ethers.Signer | DeployContractOptions ): Promise; + deployContract( + name: "ExampleSum3", + args: any[], + signerOrOptions?: ethers.Signer | DeployContractOptions + ): Promise; + deployContract( + name: "ISum3", + args: any[], + signerOrOptions?: ethers.Signer | DeployContractOptions + ): Promise; // default types getContractFactory( diff --git a/contracts/typechain-types/index.ts b/contracts/typechain-types/index.ts index f6d0e21d3f4c..36d0bdee6d7d 100644 --- a/contracts/typechain-types/index.ts +++ b/contracts/typechain-types/index.ts @@ -1,6 +1,12 @@ /* Autogenerated file. Do not edit manually. */ /* tslint:disable */ /* eslint-disable */ +import type * as exampleSum3Sol from "./ExampleSum3.sol"; +export type { exampleSum3Sol }; export type { ExampleStatelessSum3 } from "./ExampleStatelessSum3"; export * as factories from "./factories"; export { ExampleStatelessSum3__factory } from "./factories/ExampleStatelessSum3__factory"; +export type { ExampleSum3 } from "./ExampleSum3.sol/ExampleSum3"; +export { ExampleSum3__factory } from "./factories/ExampleSum3.sol/ExampleSum3__factory"; +export type { ISum3 } from "./ExampleSum3.sol/ISum3"; +export { ISum3__factory } from "./factories/ExampleSum3.sol/ISum3__factory"; diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 412ad632bf9c..346c26ab3aae 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/bn256" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/precompile/contract" "golang.org/x/crypto/ripemd160" ) @@ -46,67 +47,67 @@ type PrecompiledContract interface { // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // contracts used in the Frontier and Homestead releases. -var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, +var PrecompiledContractsHomestead = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), } // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum // contracts used in the Byzantium release. -var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, - common.BytesToAddress([]byte{6}): &bn256AddByzantium{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{}, - common.BytesToAddress([]byte{8}): &bn256PairingByzantium{}, +var PrecompiledContractsByzantium = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: false}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddByzantium{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulByzantium{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingByzantium{}), } // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum // contracts used in the Istanbul release. -var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, +var PrecompiledContractsIstanbul = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: false}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), } // PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum // contracts used in the Berlin release. -var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, +var PrecompiledContractsBerlin = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), } // PrecompiledContractsCancun contains the default set of pre-compiled Ethereum // contracts used in the Cancun release. -var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, - common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{}, +var PrecompiledContractsCancun = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), + common.BytesToAddress([]byte{0x0a}): newWrappedPrecompiledContract(&kzgPointEvaluation{}), } // PrecompiledContractsBLS contains the set of pre-compiled Ethereum diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go new file mode 100644 index 000000000000..9960c46b9880 --- /dev/null +++ b/core/vm/contracts_stateful.go @@ -0,0 +1,46 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package vm + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/precompile/contract" +) + +// wrappedPrecompiledContract implements StatefulPrecompiledContract by wrapping stateless native precompiled contracts +// in Ethereum. +type wrappedPrecompiledContract struct { + p PrecompiledContract +} + +// newWrappedPrecompiledContract returns a wrapped version of [PrecompiledContract] to be executed according to the StatefulPrecompiledContract +// interface. +func newWrappedPrecompiledContract(p PrecompiledContract) contract.StatefulPrecompiledContract { + return &wrappedPrecompiledContract{p: p} +} + +// Run implements the StatefulPrecompiledContract interface +func (w *wrappedPrecompiledContract) Run( + accessibleState contract.AccessibleState, + caller common.Address, + addr common.Address, + input []byte, + suppliedGas uint64, + readOnly bool, +) (ret []byte, err error) { + return w.p.Run(input) +} + +// RunStatefulPrecompiledContract confirms runs [precompile] with the specified parameters. +func RunStatefulPrecompiledContract( + precompile contract.StatefulPrecompiledContract, + accessibleState contract.AccessibleState, + caller common.Address, + addr common.Address, + input []byte, + suppliedGas uint64, + readOnly bool, +) (ret []byte, err error) { + return precompile.Run(accessibleState, caller, addr, input, suppliedGas, readOnly) +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 088b18aaa4ff..8ea949576b86 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -24,6 +24,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/precompile/contract" + "github.com/ethereum/go-ethereum/precompile/modules" "github.com/holiman/uint256" ) @@ -37,8 +39,8 @@ type ( GetHashFunc func(uint64) common.Hash ) -func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { - var precompiles map[common.Address]PrecompiledContract +func (evm *EVM) precompile(addr common.Address) (contract.StatefulPrecompiledContract, bool) { + var precompiles map[common.Address]contract.StatefulPrecompiledContract switch { case evm.chainRules.IsCancun: precompiles = PrecompiledContractsCancun @@ -51,8 +53,20 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { default: precompiles = PrecompiledContractsHomestead } + + // Check the existing precompiles first p, ok := precompiles[addr] - return p, ok + if ok { + return p, true + } + + // Otherwise, check for the additionally configured precompiles. + module, ok := modules.GetPrecompileModuleByAddress(addr) + if !ok { + return nil, false + } + + return module.Contract, true } // BlockContext provides the EVM with auxiliary information. Once provided @@ -167,6 +181,11 @@ func (evm *EVM) Cancelled() bool { return evm.abort.Load() } +// GetStateDB returns the evm's StateDB +func (evm *EVM) GetStateDB() contract.StateDB { + return evm.StateDB +} + // Interpreter returns the current interpreter func (evm *EVM) Interpreter() *EVMInterpreter { return evm.interpreter @@ -224,7 +243,8 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } if isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + // TODO(yevhenii): deduct gas? + ret, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly) } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -287,7 +307,8 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + // TODO(yevhenii): deduct gas? + ret, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly) } else { addrCopy := addr // Initialise a new contract and set the code that is to be used by the EVM. @@ -332,7 +353,8 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + // TODO(yevhenii): deduct gas? + ret, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly) } else { addrCopy := addr // Initialise a new contract and make initialise the delegate values @@ -381,7 +403,8 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + // TODO(yevhenii): deduct gas? + ret, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly) } else { // At this point, we use a copy of address. If we don't, the go compiler will // leak the 'contract' to the outer scope, and make allocation for 'contract' diff --git a/core/vm/extended_contracts.go b/core/vm/extended_contracts.go index 15b1b3c18e29..786350fd94b7 100644 --- a/core/vm/extended_contracts.go +++ b/core/vm/extended_contracts.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/bn256" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/precompile/contract" "golang.org/x/crypto/ripemd160" ) @@ -46,72 +47,72 @@ type PrecompiledContract interface { // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // contracts used in the Frontier and Homestead releases. -var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{0x0b}): &sum3{}, +var PrecompiledContractsHomestead = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{0x0b}): newWrappedPrecompiledContract(&sum3{}), } // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum // contracts used in the Byzantium release. -var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, - common.BytesToAddress([]byte{6}): &bn256AddByzantium{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{}, - common.BytesToAddress([]byte{8}): &bn256PairingByzantium{}, - common.BytesToAddress([]byte{0x0b}): &sum3{}, +var PrecompiledContractsByzantium = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: false}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddByzantium{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulByzantium{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingByzantium{}), + common.BytesToAddress([]byte{0x0b}): newWrappedPrecompiledContract(&sum3{}), } // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum // contracts used in the Istanbul release. -var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, - common.BytesToAddress([]byte{0x0b}): &sum3{}, +var PrecompiledContractsIstanbul = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: false}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), + common.BytesToAddress([]byte{0x0b}): newWrappedPrecompiledContract(&sum3{}), } // PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum // contracts used in the Berlin release. -var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, - common.BytesToAddress([]byte{0x0b}): &sum3{}, +var PrecompiledContractsBerlin = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), + common.BytesToAddress([]byte{0x0b}): newWrappedPrecompiledContract(&sum3{}), } // PrecompiledContractsCancun contains the default set of pre-compiled Ethereum // contracts used in the Cancun release. -var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, - common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{}, - common.BytesToAddress([]byte{0x0b}): &sum3{}, +var PrecompiledContractsCancun = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), + common.BytesToAddress([]byte{0x0a}): newWrappedPrecompiledContract(&kzgPointEvaluation{}), + common.BytesToAddress([]byte{0x0b}): newWrappedPrecompiledContract(&sum3{}), } // PrecompiledContractsBLS contains the set of pre-compiled Ethereum diff --git a/precompile/contract/contract.go b/precompile/contract/contract.go new file mode 100644 index 000000000000..3ff041cf9929 --- /dev/null +++ b/precompile/contract/contract.go @@ -0,0 +1,90 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package contract + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + SelectorLen = 4 +) + +type RunStatefulPrecompileFunc func( + accessibleState AccessibleState, + caller common.Address, + addr common.Address, + input []byte, + suppliedGas uint64, + readOnly bool, +) (ret []byte, err error) + +// StatefulPrecompileFunction defines a function implemented by a stateful precompile +type StatefulPrecompileFunction struct { + // selector is the 4 byte function selector for this function + selector []byte + // execute is performed when this function is selected + execute RunStatefulPrecompileFunc +} + +// NewStatefulPrecompileFunction creates a stateful precompile function with the given arguments +func NewStatefulPrecompileFunction(selector []byte, execute RunStatefulPrecompileFunc) *StatefulPrecompileFunction { + return &StatefulPrecompileFunction{ + selector: selector, + execute: execute, + } +} + +// statefulPrecompileWithFunctionSelectors implements StatefulPrecompiledContract by using 4 byte function selectors to pass +// off responsibilities to internal execution functions. +// Note: because we only ever read from [functions] there no lock is required to make it thread-safe. +type statefulPrecompileWithFunctionSelectors struct { + functions map[string]*StatefulPrecompileFunction +} + +// NewStatefulPrecompileContract generates new StatefulPrecompile using [functions] as the available functions and [fallback] +// as an optional fallback if there is no input data. Note: the selector of [fallback] will be ignored, so it is required to be left empty. +func NewStatefulPrecompileContract(functions []*StatefulPrecompileFunction) (StatefulPrecompiledContract, error) { + // Construct the contract and populate [functions]. + contract := &statefulPrecompileWithFunctionSelectors{ + functions: make(map[string]*StatefulPrecompileFunction), + } + for _, function := range functions { + _, exists := contract.functions[string(function.selector)] + if exists { + return nil, fmt.Errorf("cannot create stateful precompile with duplicated function selector: %q", function.selector) + } + contract.functions[string(function.selector)] = function + } + + return contract, nil +} + +// Run selects the function using the 4 byte function selector at the start of the input and executes the underlying function on the +// given arguments. +func (s *statefulPrecompileWithFunctionSelectors) Run( + accessibleState AccessibleState, + caller common.Address, + addr common.Address, + input []byte, + suppliedGas uint64, + readOnly bool, +) (ret []byte, err error) { + // Otherwise, an unexpected input size will result in an error. + if len(input) < SelectorLen { + return nil, fmt.Errorf("missing function selector to precompile - input length (%d)", len(input)) + } + + // Use the function selector to grab the correct function + selector := input[:SelectorLen] + functionInput := input[SelectorLen:] + function, ok := s.functions[string(selector)] + if !ok { + return nil, fmt.Errorf("invalid function selector %#x", selector) + } + + return function.execute(accessibleState, caller, addr, functionInput, suppliedGas, readOnly) +} diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go new file mode 100644 index 000000000000..3b6bd9ac4e64 --- /dev/null +++ b/precompile/contract/interfaces.go @@ -0,0 +1,87 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Defines the interface for the configuration and execution of a precompile contract +package contract + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +// StatefulPrecompiledContract is the interface for executing a precompiled contract +type StatefulPrecompiledContract interface { + // Run executes the precompiled contract. + Run( + accessibleState AccessibleState, + caller common.Address, + addr common.Address, + input []byte, + suppliedGas uint64, + readOnly bool, + ) (ret []byte, err error) +} + +// AccessibleState defines the interface exposed to stateful precompile contracts +type AccessibleState interface { + GetStateDB() StateDB +} + +// StateDB is an EVM database for full state querying. +type StateDB interface { + CreateAccount(common.Address) + + SubBalance(common.Address, *big.Int) + AddBalance(common.Address, *big.Int) + GetBalance(common.Address) *big.Int + + GetNonce(common.Address) uint64 + SetNonce(common.Address, uint64) + + GetCodeHash(common.Address) common.Hash + GetCode(common.Address) []byte + SetCode(common.Address, []byte) + GetCodeSize(common.Address) int + + AddRefund(uint64) + SubRefund(uint64) + GetRefund() uint64 + + GetCommittedState(common.Address, common.Hash) common.Hash + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + + GetTransientState(addr common.Address, key common.Hash) common.Hash + SetTransientState(addr common.Address, key, value common.Hash) + + SelfDestruct(common.Address) + HasSelfDestructed(common.Address) bool + + Selfdestruct6780(common.Address) + + // Exist reports whether the given account exists in state. + // Notably this should also return true for self-destructed accounts. + Exist(common.Address) bool + // Empty returns whether the given account is empty. Empty + // is defined according to EIP161 (balance = nonce = code = 0). + Empty(common.Address) bool + + AddressInAccessList(addr common.Address) bool + SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) + // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddAddressToAccessList(addr common.Address) + // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddSlotToAccessList(addr common.Address, slot common.Hash) + Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) + + RevertToSnapshot(int) + Snapshot() int + + AddLog(*types.Log) + AddPreimage(common.Hash, []byte) +} diff --git a/precompile/contract/utils.go b/precompile/contract/utils.go new file mode 100644 index 000000000000..eb5b86e1aee0 --- /dev/null +++ b/precompile/contract/utils.go @@ -0,0 +1,10 @@ +package contract + +import ( + "github.com/ethereum/go-ethereum/crypto" +) + +func CalculateFunctionSelector(functionSignature string) []byte { + hash := crypto.Keccak256([]byte(functionSignature)) + return hash[:4] +} diff --git a/precompile/contracts/sum3/contract.go b/precompile/contracts/sum3/contract.go new file mode 100644 index 000000000000..9744848d96cd --- /dev/null +++ b/precompile/contracts/sum3/contract.go @@ -0,0 +1,110 @@ +package sum3 + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/precompile/contract" +) + +// Singleton StatefulPrecompiledContract. +var ( + Sum3Precompile = createSum3Precompile() +) + +var ( + sumKey = common.BytesToHash([]byte("sumKey")) +) + +func StoreSum(stateDB vm.StateDB, sum *big.Int) { + valuePadded := common.LeftPadBytes(sum.Bytes(), common.HashLength) + valueHash := common.BytesToHash(valuePadded) + + stateDB.SetState(ContractAddress, sumKey, valueHash) +} + +func GetSum(stateDB vm.StateDB) (*big.Int, error) { + value := stateDB.GetState(ContractAddress, sumKey) + if len(value.Bytes()) == 0 { + return big.NewInt(0), nil + } + + var sum big.Int + sum.SetBytes(value.Bytes()) + + return &sum, nil +} + +func calcSum3( + accessibleState contract.AccessibleState, + caller common.Address, + addr common.Address, + input []byte, + suppliedGas uint64, + readOnly bool, +) (ret []byte, err error) { + // Set the nonce of the precompile's address (as is done when a contract is created) to ensure + // that it is marked as non-empty and will not be cleaned up when the statedb is finalized. + accessibleState.GetStateDB().SetNonce(ContractAddress, 1) + // Set the code of the precompile's address to a non-zero length byte slice to ensure that the precompile + // can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure + // that it does not attempt to invoke a non-existent contract. + accessibleState.GetStateDB().SetCode(ContractAddress, []byte{0x1}) + + if len(input) != 96 { + return nil, fmt.Errorf("unexpected input length, want: 96, got: %v", len(input)) + } + + var a, b, c, rez big.Int + a.SetBytes(input[:32]) + b.SetBytes(input[32:64]) + c.SetBytes(input[64:96]) + rez.Add(&a, &b) + rez.Add(&rez, &c) + + StoreSum(accessibleState.GetStateDB(), &rez) + + packedOutput := make([]byte, 0) + return packedOutput, nil +} + +func getSum3( + accessibleState contract.AccessibleState, + caller common.Address, + addr common.Address, + input []byte, + suppliedGas uint64, + readOnly bool, +) (ret []byte, err error) { + sum, err := GetSum(accessibleState.GetStateDB()) + if err != nil { + return nil, err + } + + packedOutput := common.LeftPadBytes(sum.Bytes(), 32) + return packedOutput, nil +} + +// createSum3Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile. +func createSum3Precompile() contract.StatefulPrecompiledContract { + var functions []*contract.StatefulPrecompileFunction + + functions = append(functions, contract.NewStatefulPrecompileFunction( + contract.CalculateFunctionSelector("calcSum3(uint256,uint256,uint256)"), + calcSum3, + )) + + functions = append(functions, contract.NewStatefulPrecompileFunction( + contract.CalculateFunctionSelector("getSum3()"), + getSum3, + )) + + // Construct the contract with no fallback function. + statefulContract, err := contract.NewStatefulPrecompileContract(functions) + if err != nil { + panic(err) + } + return statefulContract +} diff --git a/precompile/contracts/sum3/module.go b/precompile/contracts/sum3/module.go new file mode 100644 index 000000000000..269b791709fc --- /dev/null +++ b/precompile/contracts/sum3/module.go @@ -0,0 +1,25 @@ +package sum3 + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/precompile/modules" +) + +// ContractAddress is the defined address of the precompile contract. +// This should be unique across all precompile contracts. +// See precompile/registry/registry.go for registered precompile contracts and more information. +var ContractAddress = common.HexToAddress("0x0300000000000000000000000000000000000000") + +// Module is the precompile module. It is used to register the precompile contract. +var Module = modules.Module{ + Address: ContractAddress, + Contract: Sum3Precompile, +} + +func init() { + // Register the precompile module. + // Each precompile contract registers itself through [RegisterModule] function. + if err := modules.RegisterModule(Module); err != nil { + panic(err) + } +} diff --git a/precompile/modules/module.go b/precompile/modules/module.go new file mode 100644 index 000000000000..9898e071a626 --- /dev/null +++ b/precompile/modules/module.go @@ -0,0 +1,33 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package modules + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/precompile/contract" +) + +type Module struct { + // Address returns the address where the stateful precompile is accessible. + Address common.Address + // Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract when + // this config is enabled. + Contract contract.StatefulPrecompiledContract +} + +type moduleArray []Module + +func (u moduleArray) Len() int { + return len(u) +} + +func (u moduleArray) Swap(i, j int) { + u[i], u[j] = u[j], u[i] +} + +func (m moduleArray) Less(i, j int) bool { + return bytes.Compare(m[i].Address.Bytes(), m[j].Address.Bytes()) < 0 +} diff --git a/precompile/modules/registerer.go b/precompile/modules/registerer.go new file mode 100644 index 000000000000..9a70fed2cddc --- /dev/null +++ b/precompile/modules/registerer.go @@ -0,0 +1,50 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package modules + +import ( + "fmt" + "sort" + + "github.com/ethereum/go-ethereum/common" +) + +var ( + // registeredModules is a list of Module to preserve order + // for deterministic iteration + registeredModules = make([]Module, 0) +) + +// RegisterModule registers a stateful precompile module +func RegisterModule(stm Module) error { + address := stm.Address + + for _, registeredModule := range registeredModules { + if registeredModule.Address == address { + return fmt.Errorf("address %s already used by a stateful precompile", address) + } + } + // sort by address to ensure deterministic iteration + registeredModules = insertSortedByAddress(registeredModules, stm) + return nil +} + +func GetPrecompileModuleByAddress(address common.Address) (Module, bool) { + for _, stm := range registeredModules { + if stm.Address == address { + return stm, true + } + } + return Module{}, false +} + +func RegisteredModules() []Module { + return registeredModules +} + +func insertSortedByAddress(data []Module, stm Module) []Module { + data = append(data, stm) + sort.Sort(moduleArray(data)) + return data +} diff --git a/precompile/registry/registry.go b/precompile/registry/registry.go new file mode 100644 index 000000000000..fea14621c94c --- /dev/null +++ b/precompile/registry/registry.go @@ -0,0 +1,11 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Module to facilitate the registration of precompiles and their configuration. +package registry + +// Force imports of each precompile to ensure each precompile's init function runs and registers itself +// with the registry. +import ( + _ "github.com/ethereum/go-ethereum/precompile/contracts/sum3" +)