├── .github └── workflows │ └── project.yml ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── bridge ├── Cargo.toml ├── build.rs └── src │ ├── create.rs │ ├── invoke.rs │ ├── lib.rs │ ├── state.rs │ └── transfer.rs ├── clippy.toml ├── runtime ├── Cargo.toml ├── build.rs └── src │ ├── lib.rs │ └── state.rs ├── rust-toolchain ├── rustfmt.toml ├── scripts ├── prepare-lotus.sh └── run.sh ├── shared ├── Cargo.toml └── src │ ├── account.rs │ ├── bytecode.rs │ ├── execution.rs │ ├── instructions │ ├── arithmetic.rs │ ├── bitwise.rs │ ├── boolean.rs │ ├── call.rs │ ├── context.rs │ ├── control.rs │ ├── hash.rs │ ├── log.rs │ ├── memory.rs │ ├── mod.rs │ ├── stack.rs │ ├── storage.rs │ └── table.rs │ ├── lib.rs │ ├── memory.rs │ ├── message.rs │ ├── opcode.rs │ ├── output.rs │ ├── stack.rs │ ├── system.rs │ ├── transaction.rs │ └── uints.rs └── tests ├── Cargo.toml ├── contracts ├── simplecoin.hex └── simplecoin.sol └── src ├── bridge.rs ├── lib.rs └── runtime.rs /.github/workflows/project.yml: -------------------------------------------------------------------------------- 1 | name: Add to FVM project 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | pull_request: 7 | types: 8 | - opened 9 | 10 | jobs: 11 | add-to-project: 12 | name: Add to project 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/add-to-project@7334092eccc670a2b274d0cafbfa1e4d8208cfab 16 | with: 17 | project-url: https://github.com/orgs/filecoin-project/projects/54 18 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | wasm/ 2 | 3 | ### dotenv ### 4 | .env 5 | 6 | ### Git ### 7 | # Created by git for backups. To disable backups in Git: 8 | # $ git config --global mergetool.keepBackup false 9 | *.orig 10 | 11 | # Created by git when using merge tools for conflicts 12 | *.BACKUP.* 13 | *.BASE.* 14 | *.LOCAL.* 15 | *.REMOTE.* 16 | *_BACKUP_*.txt 17 | *_BASE_*.txt 18 | *_LOCAL_*.txt 19 | *_REMOTE_*.txt 20 | 21 | ### Linux ### 22 | *~ 23 | 24 | # temporary files which can be created if a process still has a handle open of a deleted file 25 | .fuse_hidden* 26 | 27 | # KDE directory preferences 28 | .directory 29 | 30 | # Linux trash folder which might appear on any partition or disk 31 | .Trash-* 32 | 33 | # .nfs files are created when an open file is removed but is still being accessed 34 | .nfs* 35 | 36 | ### macOS ### 37 | # General 38 | .DS_Store 39 | .AppleDouble 40 | .LSOverride 41 | 42 | # Icon must end with two \r 43 | Icon 44 | 45 | 46 | # Thumbnails 47 | ._* 48 | 49 | # Files that might appear in the root of a volume 50 | .DocumentRevisions-V100 51 | .fseventsd 52 | .Spotlight-V100 53 | .TemporaryItems 54 | .Trashes 55 | .VolumeIcon.icns 56 | .com.apple.timemachine.donotpresent 57 | 58 | # Directories potentially created on remote AFP share 59 | .AppleDB 60 | .AppleDesktop 61 | Network Trash Folder 62 | Temporary Items 63 | .apdisk 64 | 65 | ### Rust ### 66 | # Generated by Cargo 67 | # will have compiled files and executables 68 | debug/ 69 | target/ 70 | 71 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 72 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 73 | Cargo.lock 74 | 75 | # These are backup files generated by rustfmt 76 | **/*.rs.bk 77 | 78 | # MSVC Windows builds of rustc generate these, which store debugging information 79 | *.pdb 80 | 81 | ### VisualStudioCode ### 82 | .vscode/* 83 | .vscode/settings.json 84 | !.vscode/tasks.json 85 | !.vscode/launch.json 86 | !.vscode/extensions.json 87 | *.code-workspace 88 | 89 | # Local History for Visual Studio Code 90 | .history/ 91 | 92 | ### VisualStudioCode Patch ### 93 | # Ignore all local history of files 94 | .history 95 | .ionide 96 | 97 | # Support for Project snippet scope 98 | !.vscode/*.code-snippets 99 | 100 | ### Node ### 101 | # Logs 102 | logs 103 | *.log 104 | npm-debug.log* 105 | yarn-debug.log* 106 | yarn-error.log* 107 | lerna-debug.log* 108 | .pnpm-debug.log* 109 | 110 | # Diagnostic reports (https://nodejs.org/api/report.html) 111 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 112 | 113 | # Runtime data 114 | pids 115 | *.pid 116 | *.seed 117 | *.pid.lock 118 | 119 | # Directory for instrumented libs generated by jscoverage/JSCover 120 | lib-cov 121 | 122 | # Coverage directory used by tools like istanbul 123 | coverage 124 | *.lcov 125 | 126 | # nyc test coverage 127 | .nyc_output 128 | 129 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 130 | .grunt 131 | 132 | # Bower dependency directory (https://bower.io/) 133 | bower_components 134 | 135 | # node-waf configuration 136 | .lock-wscript 137 | 138 | # Compiled binary addons (https://nodejs.org/api/addons.html) 139 | build/Release 140 | 141 | # Dependency directories 142 | node_modules/ 143 | jspm_packages/ 144 | 145 | # Snowpack dependency directory (https://snowpack.dev/) 146 | web_modules/ 147 | 148 | # TypeScript cache 149 | *.tsbuildinfo 150 | 151 | # Optional npm cache directory 152 | .npm 153 | 154 | # Optional eslint cache 155 | .eslintcache 156 | 157 | # Microbundle cache 158 | .rpt2_cache/ 159 | .rts2_cache_cjs/ 160 | .rts2_cache_es/ 161 | .rts2_cache_umd/ 162 | 163 | # Optional REPL history 164 | .node_repl_history 165 | 166 | # Output of 'npm pack' 167 | *.tgz 168 | 169 | # Yarn Integrity file 170 | .yarn-integrity 171 | 172 | # dotenv environment variables file 173 | .env 174 | .env.test 175 | .env.production 176 | 177 | # parcel-bundler cache (https://parceljs.org/) 178 | .cache 179 | .parcel-cache 180 | 181 | # Next.js build output 182 | .next 183 | out 184 | 185 | # Nuxt.js build / generate output 186 | .nuxt 187 | dist 188 | 189 | # Gatsby files 190 | .cache/ 191 | # Comment in the public line in if your project uses Gatsby and not Next.js 192 | # https://nextjs.org/blog/next-9-1#public-directory-support 193 | # public 194 | 195 | # vuepress build output 196 | .vuepress/dist 197 | 198 | # Serverless directories 199 | .serverless/ 200 | 201 | # FuseBox cache 202 | .fusebox/ 203 | 204 | # DynamoDB Local files 205 | .dynamodb/ 206 | 207 | # TernJS port file 208 | .tern-port 209 | 210 | # Stores VSCode versions used for testing VSCode extensions 211 | .vscode-test 212 | 213 | # yarn v2 214 | .yarn/cache 215 | .yarn/unplugged 216 | .yarn/build-state.yml 217 | .yarn/install-state.gz 218 | .pnp.* 219 | 220 | ### Node Patch ### 221 | # Serverless Webpack directories 222 | .webpack/ 223 | 224 | # Optional stylelint cache 225 | .stylelintcache 226 | 227 | # SvelteKit build / generate output 228 | .svelte-kit 229 | 230 | ### yarn ### 231 | # https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored 232 | 233 | .yarn/* 234 | !.yarn/releases 235 | !.yarn/plugins 236 | !.yarn/sdks 237 | !.yarn/versions 238 | 239 | package-lock.json 240 | 241 | # if you are NOT using Zero-installs, then: 242 | # comment the following lines 243 | !.yarn/cache 244 | 245 | # and uncomment the following lines 246 | # .pnp.* 247 | 248 | /target 249 | /Cargo.lock 250 | 251 | ### Geth ### 252 | # .gitignore for Geth 0.4.16 253 | 254 | # caches 255 | /geth 256 | /keystore 257 | history 258 | 259 | # logs 260 | *.out 261 | *.log 262 | 263 | ### Solidity ### 264 | # Logs 265 | logs 266 | npm-debug.log* 267 | yarn-debug.log* 268 | yarn-error.log* 269 | lerna-debug.log* 270 | .pnpm-debug.log* 271 | 272 | # Diagnostic reports (https://nodejs.org/api/report.html) 273 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 274 | 275 | # Runtime data 276 | pids 277 | *.pid 278 | *.seed 279 | *.pid.lock 280 | 281 | # Directory for instrumented libs generated by jscoverage/JSCover 282 | lib-cov 283 | 284 | # Coverage directory used by tools like istanbul 285 | coverage 286 | *.lcov 287 | 288 | # nyc test coverage 289 | .nyc_output 290 | 291 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 292 | .grunt 293 | 294 | # Bower dependency directory (https://bower.io/) 295 | bower_components 296 | 297 | # node-waf configuration 298 | .lock-wscript 299 | 300 | # Compiled binary addons (https://nodejs.org/api/addons.html) 301 | build/Release 302 | 303 | # Dependency directories 304 | node_modules/ 305 | jspm_packages/ 306 | 307 | # Snowpack dependency directory (https://snowpack.dev/) 308 | web_modules/ 309 | 310 | # TypeScript cache 311 | *.tsbuildinfo 312 | 313 | # Optional npm cache directory 314 | .npm 315 | 316 | # Optional eslint cache 317 | .eslintcache 318 | 319 | # Optional stylelint cache 320 | .stylelintcache 321 | 322 | # Microbundle cache 323 | .rpt2_cache/ 324 | .rts2_cache_cjs/ 325 | .rts2_cache_es/ 326 | .rts2_cache_umd/ 327 | 328 | # Optional REPL history 329 | .node_repl_history 330 | 331 | # Output of 'npm pack' 332 | *.tgz 333 | 334 | # Yarn Integrity file 335 | .yarn-integrity 336 | 337 | # dotenv environment variable files 338 | .env 339 | .env.development.local 340 | .env.test.local 341 | .env.production.local 342 | .env.local 343 | 344 | # parcel-bundler cache (https://parceljs.org/) 345 | .cache 346 | .parcel-cache 347 | 348 | # Next.js build output 349 | .next 350 | out 351 | 352 | # Nuxt.js build / generate output 353 | .nuxt 354 | dist 355 | 356 | # Gatsby files 357 | .cache/ 358 | # Comment in the public line in if your project uses Gatsby and not Next.js 359 | # https://nextjs.org/blog/next-9-1#public-directory-support 360 | # public 361 | 362 | # vuepress build output 363 | .vuepress/dist 364 | 365 | # vuepress v2.x temp and cache directory 366 | .temp 367 | 368 | # Docusaurus cache and generated files 369 | .docusaurus 370 | 371 | # Serverless directories 372 | .serverless/ 373 | 374 | # FuseBox cache 375 | .fusebox/ 376 | 377 | # DynamoDB Local files 378 | .dynamodb/ 379 | 380 | # TernJS port file 381 | .tern-port 382 | 383 | # Stores VSCode versions used for testing VSCode extensions 384 | .vscode-test 385 | 386 | # yarn v2 387 | .yarn/cache 388 | .yarn/unplugged 389 | .yarn/build-state.yml 390 | .yarn/install-state.gz 391 | .pnp.* 392 | 393 | ### SolidityTruffle ### 394 | # depedencies 395 | node_modules 396 | 397 | # testing 398 | 399 | # production 400 | build 401 | build_webpack 402 | 403 | # misc 404 | .DS_Store 405 | npm-debug.log 406 | .truffle-solidity-loader 407 | .vagrant/** 408 | blockchain/geth/** 409 | blockchain/keystore/** 410 | blockchain/history 411 | 412 | #truffle 413 | yarn.lock 414 | package-lock.json 415 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["bridge", "runtime", "shared", "tests"] 4 | default-members = ["bridge", "runtime", "shared", "tests"] 5 | 6 | # [patch.crates-io] 7 | # fvm = { git = "https://github.com/filecoin-project/ref-fvm", branch = "karim/recover-pubkey-syscall" } 8 | # fvm_shared = { git = "https://github.com/filecoin-project/ref-fvm", branch = "karim/recover-pubkey-syscall" } 9 | # fvm_ipld_encoding = { git = "https://github.com/filecoin-project/ref-fvm", branch = "karim/recover-pubkey-syscall" } 10 | # fvm_ipld_blockstore = { git = "https://github.com/filecoin-project/ref-fvm", branch = "karim/recover-pubkey-syscall" } 11 | # fvm_sdk = { git = "https://github.com/filecoin-project/ref-fvm", branch = "karim/recover-pubkey-syscall" } 12 | # fvm_ipld_hamt = { git = "https://github.com/filecoin-project/ref-fvm", branch = "karim/recover-pubkey-syscall" } 13 | # fvm_ipld_amt = { git = "https://github.com/filecoin-project/ref-fvm", branch = "karim/recover-pubkey-syscall" } 14 | # fvm_ipld_car = { git = "https://github.com/filecoin-project/ref-fvm", branch = "karim/recover-pubkey-syscall" } 15 | 16 | [patch.crates-io] 17 | fvm = { path = "../ref-fvm/fvm" } 18 | fvm_shared = { path = "../ref-fvm/shared" } 19 | fvm_ipld_encoding = { path = "../ref-fvm/ipld/encoding" } 20 | fvm_ipld_blockstore = { path = "../ref-fvm/ipld/blockstore" } 21 | fvm_integration_tests = { path = "../ref-fvm/testing/integration" } 22 | fvm_sdk = { path = "../ref-fvm/sdk" } 23 | fvm_ipld_hamt = { path = "../ref-fvm/ipld/hamt" } 24 | fvm_ipld_amt = { path = "../ref-fvm/ipld/amt" } 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RUSTFLAGS="-C target-feature=+crt-static" 2 | 3 | all: copy 4 | .PHONY: all 5 | 6 | build: 7 | cargo build 8 | .PHONY: build 9 | 10 | copy: build 11 | @mkdir -p wasm && \ 12 | cp target/debug/wbuild/fvm-evm-bridge/fvm_evm_bridge.compact.wasm wasm/fvm_evm_bridge.compact.wasm && \ 13 | cp target/debug/wbuild/fvm-evm-runtime/fvm_evm_runtime.compact.wasm wasm/fvm_evm_runtime.compact.wasm 14 | 15 | clean: 16 | @rm -rf target && cargo clean 17 | 18 | test: copy 19 | cargo test -- --show-output 20 | 21 | lint: clean 22 | cargo fmt --all 23 | cargo clippy --all -- -D warnings -A clippy::upper_case_acronyms 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [ARCHIVED] Filecoin Virtual Machine EVM Actor prototype 2 | 3 | --- 4 | > 5 | > ### This is a deprecated repo with historical relevance 6 | > 7 | > This repo is where the EVM actor for the Filecoin Virtual Machine was incubated. 8 | > 9 | > This actor was merged into builtin-actors in the following PR: https://github.com/filecoin-project/builtin-actors/pull/517 10 | > 11 | --- 12 | 13 | This is a very early stage of the project and this readme will evolve over time to include build and deployment instrustions. 14 | 15 | ## Build to WASM 16 | 17 | In the root of the project execute the following command: 18 | 19 | ```sh 20 | $ make 21 | ``` 22 | 23 | It should produce a wasm binary in `./wasm/fil_actor_evm.compact.wasm` that containst the EVM runtime actor in optimized release mode. 24 | 25 | ## Running tests 26 | 27 | In the root of the project execute the following command: 28 | 29 | ```sh 30 | $ make test 31 | ``` 32 | 33 | it will compile all actors to wasm and then run them inside a simulated FVM environment and excercise all test cases. 34 | 35 | ## Design Overview 36 | 37 | ### Opcodes 38 | 39 | The EVM runtime in this actor implement opcodes and their semantics as of the London hard fork. 40 | 41 | ### Memory 42 | 43 | Handling of EVM `MSTORE`, `MLOAD` and `MSIZE` opcodes is implemented by the `Memory` module. This module is responsible for the volatile memory that is available only during contract execution and reclaimed afterwards by the system. 44 | 45 | The basic unit of allocation is a _Page_ which is 64KB, to map 1:1 to WASM memory model of allocating memory in pages of 64KB. 46 | 47 | Every contract execution context begins with one memory page allocated and it grows to more pages if the contract requests more memory than the current reserved amount. 48 | 49 | Currently there is no limit on the EVM side on how much memory a contract may reserve. This is limited by the wasmtime configuration in FVM. 50 | 51 | ### Persistance 52 | 53 | Handling of EVM `SSTORE` and `SLOAD` opcodes is implemented in terms of reads and writes to _IPLD Hamt_. EVM defines the concept of cold and warm memory access, where first access to a given address is considered cold that is more expensive and subsequent reads or writes to that memory address are considered warm and incur lower gas cost. This comes from [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930). Filecoin does not have a notion of warm and cold storage access so this is kind of meaningless to us in general and is only kept there for now to keep EVM gas accounting accurate. This will likely go in future iterations and all state access will be treated equally. 54 | 55 | All contract runtime state is persisted in a `Hamt::<_, U256, U256>` mapping and its root `Cid` is stored in the `state` field of the contract state. This Cid is conceptially equivalent to Ethereum's state root field of a contract account. This data structure may mutate throught contract's lifetime and the root Cid of the mapping gets updated after every successfull transaction that performed writes. 56 | 57 | Bytecode is an immutable part of the state that is created in EVM runtime's constructor as a result of executing the init EVM bytecode in the creation transaction. 58 | 59 | Any change to the contract state will invoke `sself::set_root` and update the state tree roof of the contract actor. This syscall is called only once at the end of a successful non-reverted transaction. 60 | 61 | The entire contract state is represented using the following structure: 62 | 63 | ```rust 64 | pub struct ContractState { 65 | pub bridge: FilecoinAddress, 66 | pub bytecode: Cid, 67 | pub state: Cid, 68 | pub self_address: H160, 69 | } 70 | ``` 71 | 72 | A reference to the bridge actor is stored in every EVM contract for resolving FIL addresses of other actors in cross-contract calls. 73 | 74 | 75 | ### Platform Interface 76 | 77 | 78 | 79 | ### Transactions 80 | 81 | ### Addressing and Registry 82 | 83 | Currently there is a component called _Registry_ that is responsible for translating EVM addresses to FVM addresses. EVM transactions and inter-contract calls must use EVM addresses which are the first 20 bytes of keccak hash of their secp256k1 public key. This is incompatible with Filecoin addresses that are first 20 bytes of Blake2B hash of the account public key. 84 | 85 | The registry contains a _Hamt_ IPLD structure keyed by `H160` values (Eth address). Each key maps to the following structure: 86 | 87 | ```rust 88 | pub struct EthereumAccount { 89 | pub nonce: u64, 90 | pub balance: U256, 91 | pub kind: AccountKind, 92 | } 93 | 94 | pub enum AccountKind { 95 | ExternallyOwned { 96 | fil_account: Option, 97 | }, 98 | Contract { 99 | fil_account: FileCoinAddress 100 | }, 101 | } 102 | ``` 103 | 104 | Externally owned addresses don't always have a known mapping to their Filecoin equivalent. This mapping is discovered only once a transaction is issued from that account. This mapping discovery process relies on `ecrecover` to recover the public key used for sigining a transaction and then hasing it using _Blake2B_ to get a FIL address that is controlled by the same private key as the EVM account. 105 | 106 | Contract accounts _always_ maps to a known FIL address because contract creation occurs on the registry and the robust address is returned by the EVM Runtime actor constructor. 107 | 108 | In later iterations we are planning on removing the registry commonent and replace it with univeral addresses, but this is still under design and discussion. 109 | 110 | ## Common Scenatios 111 | 112 | ### Contract Deployment 113 | 114 | ### Contract Invocation 115 | 116 | ### Delegate Call 117 | 118 | ### Cross Contract Calls 119 | -------------------------------------------------------------------------------- /bridge/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fvm-evm-bridge" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | authors = ["Protocol Labs", "Filecoin Core Devs"] 7 | keywords = ["filecoin", "web3", "wasm", "evm"] 8 | 9 | [lib] 10 | doctest = false 11 | 12 | [dependencies] 13 | fvm_ipld_blockstore = "0.1.1" 14 | fvm_ipld_encoding = "0.2.2" 15 | fvm_ipld_hamt = "0.5.1" 16 | fvm_sdk = "2.0.0-alpha.1" 17 | fvm_shared = { version = "0.8.0", default-features = false } 18 | fil_actors_runtime = { git = "https://github.com/filecoin-project/builtin-actors.git", branch = "experimental/fvm-m2", features = [ 19 | "fil-actor", 20 | ] } 21 | 22 | fvm-evm = { path = "../shared" } 23 | 24 | cid = { version = "0.8.5", default-features = false } 25 | multihash = { version = "0.16.2", default-features = false } 26 | 27 | serde = { version = "1.0", features = ["derive"] } 28 | serde_tuple = "0.5" 29 | anyhow = "1.0" 30 | num-traits = "0.2.15" 31 | num-derive = "0.3.3" 32 | sha3 = { version = "0.10", default-features = false } 33 | rlp = { version = "0.5.1", default-features = false } 34 | 35 | [build-dependencies] 36 | wasm-builder = "3.0.1" 37 | 38 | [dev-dependencies] 39 | rand = "0.8" 40 | rand_chacha = "0.3" 41 | anyhow = "1.0.52" 42 | hex = "0.4.3" 43 | 44 | [features] 45 | default = ["fil-actor"] 46 | fil-actor = [] 47 | -------------------------------------------------------------------------------- /bridge/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | wasm_builder::WasmBuilder::new() 3 | .with_current_project() 4 | .import_memory() 5 | .append_to_rust_flags("-Ctarget-feature=+crt-static") 6 | .append_to_rust_flags("-Cpanic=abort") 7 | .append_to_rust_flags("-Coverflow-checks=true") 8 | .append_to_rust_flags("-Clto=true") 9 | .append_to_rust_flags("-Copt-level=z") 10 | .build() 11 | } 12 | -------------------------------------------------------------------------------- /bridge/src/create.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state, 3 | anyhow::anyhow, 4 | cid::Cid, 5 | fil_actors_runtime::{runtime::Runtime, ActorError}, 6 | fvm_evm::{ 7 | execute, 8 | AccountKind, 9 | Bytecode, 10 | EthereumAccount, 11 | EvmContractRuntimeConstructor, 12 | ExecutionState, 13 | Message, 14 | SignedTransaction, 15 | StatusCode, 16 | System, 17 | H160, 18 | U256, 19 | }, 20 | fvm_ipld_blockstore::Blockstore, 21 | fvm_ipld_encoding::{from_slice, RawBytes}, 22 | fvm_ipld_hamt::Hamt, 23 | fvm_shared::{address::Address, bigint::BigInt}, 24 | rlp::RlpStream, 25 | serde_tuple::{Deserialize_tuple, Serialize_tuple}, 26 | sha3::{Digest, Keccak256}, 27 | }; 28 | 29 | const INIT_ACTOR_EXEC_METHOD_NUM: u64 = 2; 30 | 31 | /// Init actor Exec Params 32 | #[derive(Serialize_tuple, Deserialize_tuple)] 33 | pub struct ExecParams { 34 | pub code_cid: Cid, 35 | pub constructor_params: RawBytes, 36 | } 37 | 38 | /// Init actor Exec Return value 39 | #[derive(Serialize_tuple, Deserialize_tuple)] 40 | pub struct ExecReturn { 41 | /// ID based address for created actor 42 | pub id_address: Address, 43 | /// Reorg safe address for actor 44 | pub robust_address: Address, 45 | } 46 | 47 | /// Determine the address of the newly created contract 48 | fn compute_contract_address(tx: &SignedTransaction) -> Result { 49 | let mut rlp = RlpStream::new(); 50 | rlp.append(&tx.sender_address()?); 51 | rlp.append(&tx.nonce()); 52 | Ok(H160::from_slice(&Keccak256::digest(rlp.as_raw())[12..])) 53 | } 54 | 55 | /// This is invoked when a transaction is sent to the ZERO address 56 | /// and has an input bytes. If there is no input bytes then it means 57 | /// that it is a simple burn. 58 | pub fn create_contract( 59 | rt: &mut RT, 60 | tx: SignedTransaction, 61 | ) -> anyhow::Result 62 | where 63 | BS: Blockstore, 64 | RT: Runtime, 65 | { 66 | // Create a temporary contract state that will be used to store 67 | // results of constructor execution, then assigned as the state 68 | // root of a new EVM actor 69 | let state_cid = Hamt::<_, U256, U256>::new(rt.store()).flush()?; 70 | 71 | // The address of the bridge is always passed as a constructor 72 | // parameter to newly created EVM actors, so it knows where to 73 | // query for other EVM accounts. This cannot be hardcoded because 74 | // the deployment code cid of the bridge is not known at compile time. 75 | let bridge_addr = Address::new_id(fvm_sdk::message::receiver()); 76 | 77 | // Create an instance of the platform abstraction layer with it's state 78 | // rooted at the temporary contract state. 79 | let system = System::new(state_cid, rt, bridge_addr, H160::zero(), &tx)?; 80 | 81 | // compute the potential contract address if the 82 | // constructor runs successfully to completion. 83 | let contract_address = compute_contract_address(&tx)?; 84 | 85 | // the initial balance of the newly created contract 86 | let endowment = tx.value(); 87 | 88 | let message: Message = tx.try_into()?; 89 | 90 | // create new execution context around this transaction 91 | let mut exec_state = ExecutionState::new(&message); 92 | 93 | // identify bytecode valid jump destinations 94 | let bytecode = Bytecode::new(&message.input_data).map_err(|e| anyhow!(e))?; 95 | 96 | // invoke the contract constructor 97 | let exec_status = execute(&bytecode, &mut exec_state, &system) 98 | .map_err(|e| ActorError::unspecified(format!("EVM execution error: {e:?}")))?; 99 | 100 | if !exec_status.reverted 101 | && exec_status.status_code == StatusCode::Success 102 | && !exec_status.output_data.is_empty() 103 | { 104 | // load global bridge HAMT 105 | let mut bridge_state = state::BridgeState::load(rt)?; 106 | let mut bridge_accounts_map = bridge_state.accounts(rt)?; 107 | 108 | // todo: support counterfactual deployments. 109 | if !bridge_accounts_map.contains_key(&contract_address)? { 110 | // constructor ran to completion successfully and returned 111 | // the resulting bytecode. 112 | let bytecode = exec_status.output_data.clone(); 113 | 114 | // this data will be used to intantiate a new EVM actor 115 | // instance. Use the state populated by the EVM constructor 116 | // code and the returned resulting bytecode. Also keep 117 | // a reference to the bridge address on every EVM actor. 118 | let runtime_params = EvmContractRuntimeConstructor { 119 | bytecode, 120 | initial_state: system.flush_state()?, 121 | registry: bridge_addr, 122 | address: contract_address, 123 | }; 124 | 125 | fvm_sdk::debug::log(format!( 126 | "Bridge thinks that runtime Cid is {:?}", 127 | bridge_state.runtime_cid() 128 | )); 129 | 130 | // Params to the builtin InitActor#Exec method 131 | let init_actor_params = ExecParams { 132 | code_cid: *bridge_state.runtime_cid(), 133 | constructor_params: RawBytes::serialize(runtime_params)?, 134 | }; 135 | 136 | let init_actor_params = RawBytes::serialize(init_actor_params)?; 137 | 138 | // let the Init Actor create a new address 139 | let init_output = rt.send( 140 | *fil_actors_runtime::INIT_ACTOR_ADDR, 141 | INIT_ACTOR_EXEC_METHOD_NUM, 142 | init_actor_params, 143 | BigInt::default(), 144 | )?; 145 | 146 | // the init actor should return the address of the new contract 147 | let init_output: ExecReturn = from_slice(&init_output)?; 148 | 149 | // store the EVM to FVM account mapping 150 | bridge_accounts_map.set(contract_address, EthereumAccount { 151 | nonce: 0, 152 | balance: endowment, 153 | kind: AccountKind::Contract { 154 | fil_account: init_output.robust_address, 155 | }, 156 | })?; 157 | 158 | // save accoutns state updates 159 | bridge_state.update_accounts(&mut bridge_accounts_map)?; 160 | 161 | // return newly created contract address 162 | Ok(RawBytes::serialize(contract_address)?) 163 | } else { 164 | unimplemented!("Not implemented yet"); 165 | } 166 | } else { 167 | // todo: more precise error handling 168 | Err(anyhow!(ActorError::illegal_argument(format!( 169 | "EVM constructor failed: {exec_status:?}" 170 | )))) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /bridge/src/invoke.rs: -------------------------------------------------------------------------------- 1 | use { 2 | fil_actors_runtime::runtime::Runtime, 3 | fvm_evm::SignedTransaction, 4 | fvm_ipld_blockstore::Blockstore, 5 | fvm_ipld_encoding::RawBytes, 6 | }; 7 | 8 | pub fn invoke_contract( 9 | _rt: &mut RT, 10 | _tx: SignedTransaction, 11 | ) -> anyhow::Result 12 | where 13 | BS: Blockstore, 14 | RT: Runtime, 15 | { 16 | todo!() 17 | } 18 | -------------------------------------------------------------------------------- /bridge/src/lib.rs: -------------------------------------------------------------------------------- 1 | use { 2 | cid::Cid, 3 | create::create_contract, 4 | fil_actors_runtime::{ 5 | actor_error, 6 | runtime::{ActorCode, Runtime}, 7 | ActorError, 8 | INIT_ACTOR_ADDR, 9 | }, 10 | fvm_evm::TransactionAction, 11 | fvm_ipld_blockstore::Blockstore, 12 | fvm_ipld_encoding::{from_slice, RawBytes}, 13 | fvm_sdk::debug, 14 | fvm_shared::{MethodNum, METHOD_CONSTRUCTOR}, 15 | invoke::invoke_contract, 16 | num_derive::FromPrimitive, 17 | num_traits::FromPrimitive, 18 | transfer::transfer_tokens, 19 | }; 20 | 21 | mod create; 22 | mod invoke; 23 | mod state; 24 | mod transfer; 25 | 26 | #[cfg(feature = "fil-actor")] 27 | fil_actors_runtime::wasm_trampoline!(BridgeActor); 28 | 29 | #[derive(FromPrimitive)] 30 | #[repr(u64)] 31 | pub enum Method { 32 | Constructor = METHOD_CONSTRUCTOR, 33 | ProcessTransaction = 2, 34 | } 35 | 36 | pub struct BridgeActor; 37 | impl BridgeActor { 38 | pub fn constructor(rt: &mut RT, runtime_cid: Cid) -> Result<(), ActorError> 39 | where 40 | BS: Blockstore, 41 | RT: Runtime, 42 | { 43 | rt.validate_immediate_caller_is(std::iter::once(&*INIT_ACTOR_ADDR))?; 44 | 45 | // Initialize the global state of the bridge to an empty map. 46 | // todo: in later iterations initialize with precompiles. 47 | state::BridgeState::create(rt, &runtime_cid) 48 | .map_err(|e| ActorError::illegal_argument(format!("{e:?}")))?; 49 | Ok(()) 50 | } 51 | 52 | /// This is the entry point to interacting with the bridge from RPC nodes 53 | pub fn process_transaction( 54 | rt: &mut RT, 55 | rlp: &[u8], 56 | ) -> Result 57 | where 58 | BS: Blockstore, 59 | RT: Runtime, 60 | { 61 | rt.validate_immediate_caller_accept_any()?; 62 | 63 | let transaction = fvm_evm::SignedTransaction::try_from(rlp) 64 | .map_err(|e| ActorError::illegal_argument(format!("{e:?}")))?; 65 | debug::log(format!("FVM transaction: {transaction:#?}")); 66 | 67 | match transaction.action() { 68 | TransactionAction::Call(_) => invoke_contract(rt, transaction), 69 | TransactionAction::Create => { 70 | if transaction.input().is_empty() { 71 | transfer_tokens(rt, transaction) // transaction is burning tokens 72 | } else { 73 | // transaction is creating new contract 74 | create_contract(rt, transaction) 75 | } 76 | } 77 | } 78 | .map_err(|e| ActorError::unspecified(format!("EVM Error: {e:?}"))) 79 | } 80 | } 81 | 82 | impl ActorCode for BridgeActor { 83 | fn invoke_method( 84 | rt: &mut RT, 85 | method: MethodNum, 86 | params: &RawBytes, 87 | ) -> Result 88 | where 89 | BS: Blockstore + Clone, 90 | RT: Runtime, 91 | { 92 | match FromPrimitive::from_u64(method) { 93 | Some(Method::Constructor) => { 94 | let runtime_cid: Cid = from_slice(¶ms)?; 95 | Self::constructor(rt, runtime_cid)?; 96 | Ok(RawBytes::default()) 97 | } 98 | Some(Method::ProcessTransaction) => { 99 | let rlp: Vec = from_slice(¶ms)?; 100 | Self::process_transaction(rt, &rlp) 101 | } 102 | None => Err(actor_error!(unhandled_message; "Invalid method")), 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /bridge/src/state.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anyhow::Context, 3 | cid::Cid, 4 | fil_actors_runtime::runtime::Runtime, 5 | fvm_evm::{EthereumAccount, H160}, 6 | fvm_ipld_blockstore::Blockstore, 7 | fvm_ipld_encoding::{to_vec, Cbor, CborStore, DAG_CBOR}, 8 | fvm_ipld_hamt::Hamt, 9 | fvm_sdk::{ipld, sself}, 10 | multihash::Code, 11 | serde_tuple::{Deserialize_tuple, Serialize_tuple}, 12 | }; 13 | 14 | #[derive(Serialize_tuple, Deserialize_tuple, Clone, Debug)] 15 | pub struct BridgeState { 16 | /// Populated during construction 17 | runtime_cid: Cid, 18 | 19 | /// Hamt H160 -> EthereumAccount 20 | accounts: Cid, 21 | } 22 | 23 | impl Cbor for BridgeState {} 24 | 25 | impl BridgeState { 26 | pub fn create(rt: &RT, runtime_cid: &Cid) -> anyhow::Result<(Self, Cid)> 27 | where 28 | BS: Blockstore, 29 | RT: Runtime, 30 | { 31 | let instance = BridgeState { 32 | runtime_cid: *runtime_cid, 33 | accounts: Hamt::<_, EthereumAccount, H160>::new(rt.store()).flush()?, 34 | }; 35 | 36 | let serialized = to_vec(&instance)?; 37 | let cid = ipld::put(Code::Blake2b256.into(), 32, DAG_CBOR, serialized.as_slice())?; 38 | sself::set_root(&cid)?; 39 | 40 | Ok((instance, cid)) 41 | } 42 | 43 | pub fn load(rt: &RT) -> anyhow::Result 44 | where 45 | BS: Blockstore, 46 | RT: Runtime, 47 | { 48 | rt.store() 49 | .get_cbor(&sself::root()?)? 50 | .context("bridge state not initialized") 51 | } 52 | 53 | pub fn accounts<'r, BS, RT>( 54 | &self, 55 | rt: &'r RT, 56 | ) -> anyhow::Result> 57 | where 58 | BS: Blockstore, 59 | RT: Runtime, 60 | { 61 | Ok(Hamt::<_, EthereumAccount, H160>::load( 62 | &self.accounts, 63 | rt.store(), 64 | )?) 65 | } 66 | 67 | pub fn runtime_cid(&self) -> &Cid { 68 | &self.runtime_cid 69 | } 70 | 71 | pub fn update_accounts( 72 | &mut self, 73 | accounts: &mut Hamt, 74 | ) -> anyhow::Result<()> { 75 | self.accounts = accounts.flush()?; 76 | Ok(sself::set_root(&ipld::put( 77 | Code::Blake2b256.into(), 78 | 32, 79 | DAG_CBOR, 80 | &to_vec(self)?, 81 | )?)?) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /bridge/src/transfer.rs: -------------------------------------------------------------------------------- 1 | use { 2 | fil_actors_runtime::runtime::Runtime, 3 | fvm_evm::SignedTransaction, 4 | fvm_ipld_blockstore::Blockstore, 5 | fvm_ipld_encoding::RawBytes, 6 | }; 7 | 8 | pub fn transfer_tokens( 9 | _rt: &mut RT, 10 | _tx: SignedTransaction, 11 | ) -> anyhow::Result 12 | where 13 | BS: Blockstore, 14 | RT: Runtime, 15 | { 16 | todo!() 17 | } 18 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | assign_op_pattern = "allow" -------------------------------------------------------------------------------- /runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fvm-evm-runtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | authors = ["Protocol Labs", "Filecoin Core Devs"] 7 | keywords = ["filecoin", "web3", "wasm", "evm"] 8 | 9 | [lib] 10 | doctest = false 11 | 12 | [dependencies] 13 | fvm-evm = { path = "../shared", default-features = false } 14 | anyhow = "1" 15 | cid = { version = "0.8.5", default-features = false } 16 | multihash = { version = "0.16.2", default-features = false } 17 | serde = { version = "1.0", features = ["derive"] } 18 | serde_tuple = "0.5" 19 | bytes = { version = "1.1.0", default-features = false } 20 | num-traits = "0.2.15" 21 | num-derive = "0.3.3" 22 | fvm_ipld_blockstore = "0.1.1" 23 | fvm_ipld_encoding = "0.2.2" 24 | fvm_ipld_hamt = "0.5.1" 25 | fvm_sdk = "2.0.0-alpha.1" 26 | fvm_shared = { version = "0.8.0", default-features = false } 27 | fil_actors_runtime = { git = "https://github.com/filecoin-project/builtin-actors.git", branch = "experimental/fvm-m2", features = [ 28 | "fil-actor", 29 | ] } 30 | 31 | 32 | [build-dependencies] 33 | wasm-builder = "3.0.1" 34 | 35 | [dev-dependencies] 36 | libsecp256k1 = { version = "0.7" } 37 | rand = "0.8" 38 | rand_chacha = "0.3" 39 | anyhow = "1.0.52" 40 | 41 | [features] 42 | default = ["fil-actor"] 43 | fil-actor = [] 44 | -------------------------------------------------------------------------------- /runtime/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | wasm_builder::WasmBuilder::new() 3 | .with_current_project() 4 | .import_memory() 5 | .append_to_rust_flags("-Ctarget-feature=+crt-static") 6 | .append_to_rust_flags("-Cpanic=abort") 7 | .append_to_rust_flags("-Coverflow-checks=true") 8 | .append_to_rust_flags("-Clto=true") 9 | .append_to_rust_flags("-Copt-level=z") 10 | .build() 11 | } 12 | -------------------------------------------------------------------------------- /runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::state::ContractState, 3 | fil_actors_runtime::{ 4 | actor_error, 5 | runtime::{ActorCode, Runtime}, 6 | ActorError, 7 | }, 8 | fvm_evm::EvmContractRuntimeConstructor, 9 | fvm_ipld_blockstore::Blockstore, 10 | fvm_ipld_encoding::{from_slice, RawBytes}, 11 | fvm_shared::{MethodNum, METHOD_CONSTRUCTOR}, 12 | num_derive::FromPrimitive, 13 | num_traits::FromPrimitive, 14 | }; 15 | 16 | mod state; 17 | 18 | #[cfg(feature = "fil-actor")] 19 | fil_actors_runtime::wasm_trampoline!(EvmRuntimeActor); 20 | 21 | /// Maximum allowed EVM bytecode size. 22 | /// The contract code size limit is 24kB. 23 | const MAX_CODE_SIZE: usize = 0x6000; 24 | 25 | #[derive(FromPrimitive)] 26 | #[repr(u64)] 27 | pub enum Method { 28 | Constructor = METHOD_CONSTRUCTOR, 29 | InvokeContract = 2, 30 | GetStorageValue = 3, 31 | GetCodeHash = 4, 32 | GetCodeSize = 5, 33 | } 34 | 35 | pub struct EvmRuntimeActor; 36 | impl EvmRuntimeActor { 37 | pub fn constructor( 38 | rt: &mut RT, 39 | args: &EvmContractRuntimeConstructor, 40 | ) -> Result<(), ActorError> 41 | where 42 | BS: Blockstore, 43 | RT: Runtime, 44 | { 45 | fvm_sdk::debug::log(format!( 46 | "Inside FVM Runtime actor constructor! params: {args:?}" 47 | )); 48 | rt.validate_immediate_caller_accept_any()?; 49 | 50 | if args.bytecode.len() > MAX_CODE_SIZE { 51 | return Err(ActorError::illegal_argument(format!( 52 | "EVM byte code length ({}) is exceeding the maximum allowed of {MAX_CODE_SIZE}", 53 | args.bytecode.len() 54 | ))); 55 | } 56 | 57 | if args.bytecode.is_empty() { 58 | return Err(ActorError::illegal_argument("no bytecode provided".into())); 59 | } 60 | 61 | if args.initial_state == cid::Cid::default() { 62 | return Err(ActorError::illegal_state( 63 | "EVM Actor must be initialized to some initial state".into(), 64 | )); 65 | } 66 | 67 | ContractState::new( 68 | &args.bytecode, 69 | args.registry, 70 | args.address, 71 | args.initial_state, 72 | ) 73 | .map_err(|e| ActorError::illegal_state(e.to_string()))?; 74 | 75 | Ok(()) 76 | } 77 | 78 | pub fn invoke_contract(rt: &mut RT) -> Result 79 | where 80 | BS: Blockstore, 81 | RT: Runtime, 82 | { 83 | rt.validate_immediate_caller_accept_any()?; 84 | // let state: ContractState = rt.state()?; 85 | // let message = Message { 86 | // kind: fvm_evm::CallKind::Call, 87 | // is_static: false, 88 | // depth: 1, 89 | // gas: 2100, 90 | // recipient: H160::zero(), 91 | // sender: H160::zero(), 92 | // input_data: Bytes::new(), 93 | // value: U256::zero(), 94 | // }; 95 | 96 | // let bytecode: Vec<_> = from_slice(&ipld::get(&state.bytecode).map_err(|e| { 97 | // ActorError::illegal_state(format!("failed to load bytecode: {e:?}")) 98 | // })?) 99 | // .map_err(|e| ActorError::unspecified(format!("failed to load bytecode: 100 | // {e:?}")))?; 101 | 102 | // // EVM contract bytecode 103 | // let bytecode = Bytecode::new(&bytecode) 104 | // .map_err(|e| ActorError::unspecified(format!("invalid bytecode: {e:?}")))?; 105 | 106 | // // the execution state of the EVM, stack, heap, etc. 107 | // let mut runtime = ExecutionState::new(&message); 108 | 109 | // // the interface between the EVM interpretter and the FVM system 110 | // let mut system = System::new(state.state, rt, state.bridge, 111 | // state.self_address) .map_err(|e| 112 | // ActorError::unspecified(format!("failed to create runtime: {e:?}")))?; 113 | 114 | // // invoke the bytecode using the current state and the platform interface 115 | // let output = execute(&bytecode, &mut runtime, &mut system) 116 | // .map_err(|e| ActorError::unspecified(format!("contract execution error: 117 | // {e:?}")))?; 118 | 119 | // log(format!("evm output: {output:?}")); 120 | Ok(RawBytes::default()) 121 | } 122 | 123 | pub fn get_storage_value(rt: &mut RT) -> Result 124 | where 125 | BS: Blockstore, 126 | RT: Runtime, 127 | { 128 | rt.validate_immediate_caller_accept_any()?; 129 | todo!() 130 | } 131 | 132 | pub fn get_code_hash(rt: &mut RT) -> Result 133 | where 134 | BS: Blockstore, 135 | RT: Runtime, 136 | { 137 | rt.validate_immediate_caller_accept_any()?; 138 | todo!() 139 | } 140 | 141 | pub fn get_code_size(rt: &mut RT) -> Result 142 | where 143 | BS: Blockstore, 144 | RT: Runtime, 145 | { 146 | rt.validate_immediate_caller_accept_any()?; 147 | todo!() 148 | } 149 | } 150 | 151 | impl ActorCode for EvmRuntimeActor { 152 | fn invoke_method( 153 | rt: &mut RT, 154 | method: MethodNum, 155 | params: &RawBytes, 156 | ) -> Result 157 | where 158 | BS: Blockstore + Clone, 159 | RT: Runtime, 160 | { 161 | match FromPrimitive::from_u64(method) { 162 | Some(Method::Constructor) => { 163 | Self::constructor(rt, &from_slice(¶ms)?)?; 164 | Ok(RawBytes::default()) 165 | } 166 | Some(Method::InvokeContract) => Self::invoke_contract(rt), 167 | Some(Method::GetStorageValue) => Self::get_storage_value(rt), 168 | Some(Method::GetCodeHash) => Self::get_code_hash(rt), 169 | Some(Method::GetCodeSize) => Self::get_code_size(rt), 170 | None => Err(actor_error!(unhandled_message; "Invalid method")), 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /runtime/src/state.rs: -------------------------------------------------------------------------------- 1 | use { 2 | cid::Cid, 3 | fvm_evm::{abort, H160}, 4 | fvm_ipld_encoding::{to_vec, Cbor, RawBytes, DAG_CBOR}, 5 | fvm_sdk::{ipld, sself}, 6 | fvm_shared::address::Address, 7 | multihash::Code, 8 | serde_tuple::{Deserialize_tuple, Serialize_tuple}, 9 | }; 10 | 11 | /// Data stored by an EVM contract. 12 | /// This runs on the fvm-evm-runtime actor code cid. 13 | #[derive(Debug, Serialize_tuple, Deserialize_tuple)] 14 | pub struct ContractState { 15 | /// Address of the bridge actor that stores EVM <--> FVM 16 | /// account mappings. 17 | pub bridge: Address, 18 | 19 | /// The EVM contract bytecode resulting from calling the 20 | /// initialization code by the constructor. 21 | pub bytecode: Cid, 22 | 23 | /// The EVM contract state dictionary. 24 | /// All eth contract state is a map of U256 -> U256 values. 25 | /// 26 | /// HAMT 27 | pub state: Cid, 28 | 29 | /// EVM address of the current contract 30 | pub self_address: H160, 31 | } 32 | 33 | impl Cbor for ContractState {} 34 | 35 | impl ContractState { 36 | /// Called by the actor constructor during the creation of a new 37 | /// EVM contract. This method will execute the initialization code 38 | /// and store the contract bytecode, and the EVM constructor state 39 | /// in the state HAMT. 40 | pub fn new( 41 | bytecode: &(impl AsRef<[u8]> + ?Sized), 42 | bridge: Address, 43 | self_address: H160, 44 | initial_state: Cid, 45 | ) -> anyhow::Result { 46 | let this = Self { 47 | bridge, 48 | self_address, 49 | bytecode: ipld::put( 50 | Code::Blake2b256.into(), 51 | 32, 52 | DAG_CBOR, 53 | &RawBytes::serialize(bytecode.as_ref())?, 54 | )?, 55 | state: initial_state, 56 | }; 57 | 58 | sself::set_root(&ipld::put( 59 | Code::Blake2b256.into(), 60 | 32, 61 | DAG_CBOR, 62 | &RawBytes::serialize(&this)?, 63 | )?)?; 64 | Ok(this) 65 | } 66 | 67 | pub fn _save(&self) -> Cid { 68 | let serialized = match to_vec(self) { 69 | Ok(s) => s, 70 | Err(err) => { 71 | abort!(USR_SERIALIZATION, "failed to serialize state: {:?}", err) 72 | } 73 | }; 74 | let cid = 75 | match ipld::put(Code::Blake2b256.into(), 32, DAG_CBOR, serialized.as_slice()) { 76 | Ok(cid) => cid, 77 | Err(err) => { 78 | abort!(USR_SERIALIZATION, "failed to store initial state: {:}", err) 79 | } 80 | }; 81 | if let Err(err) = sself::set_root(&cid) { 82 | abort!(USR_ILLEGAL_STATE, "failed to set root ciid: {:}", err); 83 | } 84 | cid 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | edition = "2021" 3 | fn_single_line = false 4 | format_code_in_doc_comments = true 5 | format_strings = true 6 | imports_layout = "HorizontalVertical" 7 | imports_granularity = "One" 8 | normalize_comments = true 9 | normalize_doc_attributes = true 10 | reorder_imports = true 11 | reorder_impl_items = true 12 | group_imports = "StdExternalCrate" 13 | use_try_shorthand = true 14 | wrap_comments = true 15 | max_width = 90 16 | overflow_delimited_expr = true 17 | remove_nested_parens = true 18 | reorder_modules = true 19 | unstable_features = true 20 | use_field_init_shorthand = true 21 | -------------------------------------------------------------------------------- /scripts/prepare-lotus.sh: -------------------------------------------------------------------------------- 1 | export LOTUS_PATH=~/.lotus-local-net 2 | export LOTUS_MINER_PATH=~/.lotus-miner-local-net 3 | export LOTUS_SKIP_GENESIS_CHECK=_yes_ 4 | export CGO_CFLAGS_ALLOW="-D__BLST_PORTABLE__" 5 | export CGO_CFLAGS="-D__BLST_PORTABLE__" 6 | 7 | rm -rf ~/.lotus 8 | rm -rf ~/.genesis-sectors 9 | rm -rf $LOTUS_PATH 10 | rm -rf $LOTUS_MINER_PATH 11 | 12 | lotus fetch-params 2048 13 | lotus-seed pre-seal --sector-size 2KiB --num-sectors 2 14 | lotus-seed genesis new ~/lotus-localnet.json 15 | lotus-seed genesis add-miner ~/lotus-localnet.json ~/.genesis-sectors/pre-seal-t01000.json 16 | 17 | lotus daemon --lotus-make-genesis=devgen.car --genesis-template=~/lotus-localnet.json --bootstrap=false & 18 | sleep 10 && 19 | lotus wallet import --as-default ~/.genesis-sectors/pre-seal-t01000.key && 20 | lotus-miner init --genesis-miner --actor=t01000 --sector-size=2KiB --pre-sealed-sectors=~/.genesis-sectors --pre-sealed-metadata=~/.genesis-sectors/pre-seal-t01000.json --nosync && 21 | lotus-miner run --nosync && 22 | wait 23 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | LOTUS_PATH=~/.lotus-local-net 2 | LOTUS_MINER_PATH=~/.lotus-miner-local-net 3 | LOTUS_SKIP_GENESIS_CHECK=_yes_ 4 | CGO_CFLAGS_ALLOW="-D__BLST_PORTABLE__" 5 | CGO_CFLAGS="-D__BLST_PORTABLE__" 6 | 7 | echo "Building actor..." 8 | cargo build 9 | 10 | CID=$(lotus chain install-actor target/debug/wbuild/fil_actor_evm/fil_actor_evm.compact.wasm | sed -n 's,^Actor Code CID: ,,p') 11 | echo "CodeID: $CID" 12 | 13 | CREATE_PARAMS=$(echo "EVM Bytecode - 123\c" | base64) 14 | ADDRESS=$(lotus chain create-actor $CID $CREATE_PARAMS | sed -n 's,^Robust Address: ,,p') 15 | echo "Actor Address: $ADDRESS" 16 | 17 | echo "invoking method get_bytecode.." 18 | RETURN=$(lotus chain invoke $ADDRESS 2 | tail -1 | base64 --decode) 19 | echo "Result: $RETURN" -------------------------------------------------------------------------------- /shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fvm-evm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | authors = ["Protocol Labs", "Filecoin Core Devs"] 7 | keywords = ["filecoin", "web3", "wasm", "evm"] 8 | 9 | [lib] 10 | doctest = false 11 | 12 | [dependencies] 13 | hex = "0.4.3" 14 | anyhow = "1" 15 | serde_tuple = "0.5" 16 | serde = { version = "1.0", features = ["derive"] } 17 | cid = { version = "0.8.5", default-features = false } 18 | uint = { version = "0.9.3", default-features = false } 19 | impl-serde = { version = "0.3.2", default-features = false } 20 | fixed-hash = { version = "0.7.0", default-features = false } 21 | arrayvec = { version = "0.7.2", features = ["serde"] } 22 | bytes = { version = "1.1.0", features = ["serde"], default-features = false } 23 | derive_more = "0.99" 24 | strum = "0.24" 25 | strum_macros = "0.24" 26 | sha3 = { version = "0.10", default-features = false } 27 | rlp = { version = "0.5.1", default-features = false } 28 | fvm_ipld_blockstore = "0.1.1" 29 | fvm_ipld_encoding = "0.2.2" 30 | fvm_ipld_hamt = "0.5.1" 31 | fvm_sdk = "2.0.0-alpha.1" 32 | fvm_shared = { version = "0.8.0", default-features = false } 33 | fil_actors_runtime = { git = "https://github.com/filecoin-project/builtin-actors.git", branch = "experimental/fvm-m2", features = [ 34 | "fil-actor", 35 | ] } 36 | 37 | [dev-dependencies] 38 | libsecp256k1 = { version = "0.7.0", features = ["static-context"] } 39 | hex-literal = "0.3.4" 40 | fvm_shared = { version = "0.8.0", default-features = false, features = [ 41 | "crypto", 42 | ] } 43 | -------------------------------------------------------------------------------- /shared/src/account.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::U256, 3 | fvm_shared::address::Address as FileCoinAddress, 4 | serde::{Deserialize, Serialize}, 5 | serde_tuple::{Deserialize_tuple, Serialize_tuple}, 6 | }; 7 | 8 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 9 | pub enum AccountKind { 10 | /// A user account controlled by a private key. 11 | /// 12 | /// Has no contract code or state. May optionally 13 | /// be linked to FIL account. 14 | ExternallyOwned { 15 | fil_account: Option, 16 | }, 17 | 18 | /// A contract account. 19 | /// 20 | /// Has state and bytecode but no corresponding private key. 21 | /// Always known for contract accounts, because all contract 22 | /// creation goes through the registry. 23 | Contract { 24 | fil_account: FileCoinAddress 25 | }, 26 | } 27 | 28 | impl Default for AccountKind { 29 | fn default() -> Self { 30 | Self::ExternallyOwned { fil_account: None } 31 | } 32 | } 33 | 34 | /// Represents an account in the EVM world addressable by a 160 bit address. 35 | /// 36 | /// An account can be either an externally owned account (EOA) that is 37 | /// controlled by a private key or a contract address. The fact that ethereum 38 | /// addresses are hashes of the public key, makes it impossible to distinguish 39 | /// between EOA and contract accounts. 40 | /// 41 | /// If an account is an EOA it is optionally linked to a FileCoin account, 42 | /// in that case, the balance of that account is the sum of balances on the 43 | /// FVM side (if known) and EVM side, and the nonce value is the higher of 44 | /// the two. The mapping between FVM and EVM addresses is a manual step and 45 | /// not all ETH addresses will have their FVM secp256k1 equivalent. 46 | /// 47 | /// If an account is a contract account, then it may be linked to an FVM 48 | /// account that is an Actor object constructed with the contract bytecode 49 | /// and has its CodeCID equal to the runtime wasm bytecode. 50 | #[derive( 51 | Debug, Clone, Copy, Default, PartialEq, Serialize_tuple, Deserialize_tuple, 52 | )] 53 | pub struct EthereumAccount { 54 | /// For EOA it could be this value of the FIL account nonce, 55 | /// whichever is greater. If this account is not linked to a FIL 56 | /// account, then this nonce value alone is used as the nonce. 57 | pub nonce: u64, 58 | 59 | /// The FIL balance of this account. 60 | /// 61 | /// For EAO if the corresponding FIL account is known then the balance is the 62 | /// sum of the two balances, otherwise this value alone is the balance. 63 | pub balance: U256, 64 | 65 | /// Type of the Ethereum account. 66 | /// 67 | /// Contract accounts have a state object (HAMT) and a state root, 68 | /// EOA may optionally have a link to a FIL address. 69 | pub kind: AccountKind, 70 | } 71 | -------------------------------------------------------------------------------- /shared/src/bytecode.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{output::StatusCode, opcode::OpCode}, 3 | std::ops::Deref, 4 | }; 5 | 6 | pub struct Bytecode<'c> { 7 | code: &'c [u8], 8 | jumpdest: Vec, 9 | } 10 | 11 | impl<'c> Bytecode<'c> { 12 | pub fn new(bytecode: &'c [u8]) -> Result { 13 | // only jumps to those addresses are valid. This is a security 14 | // feature by EVM to disallow jumps to arbitary code addresses. 15 | // todo: create the jumpdest table only once during initial contract deployment 16 | let mut jumpdest = vec![false; bytecode.len()]; 17 | let mut i = 0; 18 | while i < bytecode.len() { 19 | if bytecode[i] == OpCode::JUMPDEST.code { 20 | jumpdest[i] = true; 21 | i += 1; 22 | } else if bytecode[i] >= OpCode::PUSH1.code && bytecode[i] <= OpCode::PUSH32.code { 23 | i += (bytecode[i] - OpCode::PUSH1.code) as usize + 2; 24 | } else { 25 | i += 1; 26 | } 27 | } 28 | 29 | Ok(Self { 30 | code: bytecode, 31 | jumpdest, 32 | }) 33 | } 34 | 35 | /// Checks if the EVM is allowed to jump to this location. 36 | /// 37 | /// This location must begin with a JUMPDEST opcode that 38 | /// marks a valid jump destination 39 | pub fn valid_jump_destination(&self, offset: usize) -> bool { 40 | offset < self.jumpdest.len() && self.jumpdest[offset] 41 | } 42 | } 43 | 44 | impl<'c> Deref for Bytecode<'c> { 45 | type Target = [u8]; 46 | 47 | fn deref(&self) -> &'c Self::Target { 48 | self.code 49 | } 50 | } 51 | 52 | impl<'c> AsRef<[u8]> for Bytecode<'c> { 53 | fn as_ref(&self) -> &'c [u8] { 54 | self.code 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /shared/src/execution.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | bytecode::Bytecode, 4 | instructions::{ 5 | arithmetic, 6 | bitwise, 7 | boolean, 8 | call, 9 | context, 10 | control, 11 | hash, 12 | log::log, 13 | memory, 14 | stack::{self, dup, push, push1, push32, swap}, 15 | storage, 16 | }, 17 | memory::Memory, 18 | message::{CallKind, Message}, 19 | opcode::OpCode, 20 | output::StatusCode, 21 | stack::Stack, 22 | system::System, 23 | Output, 24 | }, 25 | bytes::Bytes, 26 | fvm_ipld_blockstore::Blockstore, 27 | }; 28 | 29 | /// EVM execution runtime. 30 | #[derive(Clone, Debug)] 31 | pub struct ExecutionState<'m> { 32 | pub gas_left: i64, 33 | pub stack: Stack, 34 | pub memory: Memory, 35 | pub message: &'m Message, 36 | pub return_data: Bytes, 37 | pub output_data: Bytes, 38 | } 39 | 40 | impl<'m> ExecutionState<'m> { 41 | pub fn new(message: &'m Message) -> Self { 42 | Self { 43 | gas_left: message.gas, 44 | stack: Stack::default(), 45 | memory: Memory::default(), 46 | message, 47 | return_data: Default::default(), 48 | output_data: Bytes::new(), 49 | } 50 | } 51 | } 52 | 53 | pub fn execute<'r, BS: Blockstore>( 54 | bytecode: &Bytecode, 55 | runtime: &mut ExecutionState, 56 | system: &'r System<'r, BS>, 57 | ) -> Result { 58 | let mut pc = 0; // program counter 59 | let mut reverted = false; 60 | 61 | loop { 62 | if pc >= bytecode.len() { 63 | break; 64 | } 65 | 66 | let op = OpCode::try_from(bytecode[pc])?; 67 | match op { 68 | OpCode::STOP => break, 69 | OpCode::ADD => arithmetic::add(&mut runtime.stack), 70 | OpCode::MUL => arithmetic::mul(&mut runtime.stack), 71 | OpCode::SUB => arithmetic::sub(&mut runtime.stack), 72 | OpCode::DIV => arithmetic::div(&mut runtime.stack), 73 | OpCode::SDIV => arithmetic::sdiv(&mut runtime.stack), 74 | OpCode::MOD => arithmetic::modulo(&mut runtime.stack), 75 | OpCode::SMOD => arithmetic::smod(&mut runtime.stack), 76 | OpCode::ADDMOD => arithmetic::addmod(&mut runtime.stack), 77 | OpCode::MULMOD => arithmetic::mulmod(&mut runtime.stack), 78 | OpCode::EXP => arithmetic::exp(runtime)?, 79 | OpCode::SIGNEXTEND => arithmetic::signextend(&mut runtime.stack), 80 | OpCode::LT => boolean::lt(&mut runtime.stack), 81 | OpCode::GT => boolean::gt(&mut runtime.stack), 82 | OpCode::SLT => boolean::slt(&mut runtime.stack), 83 | OpCode::SGT => boolean::sgt(&mut runtime.stack), 84 | OpCode::EQ => boolean::eq(&mut runtime.stack), 85 | OpCode::ISZERO => boolean::iszero(&mut runtime.stack), 86 | OpCode::AND => boolean::and(&mut runtime.stack), 87 | OpCode::OR => boolean::or(&mut runtime.stack), 88 | OpCode::XOR => boolean::xor(&mut runtime.stack), 89 | OpCode::NOT => boolean::not(&mut runtime.stack), 90 | OpCode::BYTE => bitwise::byte(&mut runtime.stack), 91 | OpCode::SHL => bitwise::shl(&mut runtime.stack), 92 | OpCode::SHR => bitwise::shr(&mut runtime.stack), 93 | OpCode::SAR => bitwise::sar(&mut runtime.stack), 94 | OpCode::KECCAK256 => hash::keccak256(runtime)?, 95 | OpCode::ADDRESS => context::address(runtime, system)?, 96 | OpCode::BALANCE => storage::balance(runtime, system)?, 97 | OpCode::CALLER => context::caller(runtime, system)?, 98 | OpCode::CALLVALUE => context::call_value(runtime, system), 99 | OpCode::CALLDATALOAD => call::calldataload(runtime), 100 | OpCode::CALLDATASIZE => call::calldatasize(runtime), 101 | OpCode::CALLDATACOPY => call::calldatacopy(runtime)?, 102 | OpCode::CODESIZE => call::codesize(&mut runtime.stack, bytecode.as_ref()), 103 | OpCode::CODECOPY => call::codecopy(runtime, bytecode.as_ref())?, 104 | OpCode::EXTCODESIZE => storage::extcodesize(runtime, system)?, 105 | OpCode::EXTCODECOPY => memory::extcodecopy(runtime, system)?, 106 | OpCode::RETURNDATASIZE => control::returndatasize(runtime), 107 | OpCode::RETURNDATACOPY => control::returndatacopy(runtime)?, 108 | OpCode::EXTCODEHASH => storage::extcodehash(runtime, system)?, 109 | OpCode::BLOCKHASH => context::blockhash(runtime, system)?, 110 | OpCode::ORIGIN => context::origin(runtime, system), 111 | OpCode::COINBASE => context::coinbase(runtime, system)?, 112 | OpCode::GASPRICE => context::gas_price(runtime, system)?, 113 | OpCode::TIMESTAMP => context::timestamp(runtime, system)?, 114 | OpCode::NUMBER => context::block_number(runtime, system)?, 115 | OpCode::DIFFICULTY => context::difficulty(runtime, system)?, 116 | OpCode::GASLIMIT => context::gas_limit(runtime, system)?, 117 | OpCode::CHAINID => context::chain_id(runtime, system)?, 118 | OpCode::BASEFEE => context::base_fee(runtime, system)?, 119 | OpCode::SELFBALANCE => storage::selfbalance(runtime, system)?, 120 | OpCode::POP => stack::pop(&mut runtime.stack), 121 | OpCode::MLOAD => memory::mload(runtime)?, 122 | OpCode::MSTORE => memory::mstore(runtime)?, 123 | OpCode::MSTORE8 => memory::mstore8(runtime)?, 124 | OpCode::JUMP => { 125 | pc = control::jump(&mut runtime.stack, &bytecode)?; 126 | continue; // don't increment PC after the jump 127 | } 128 | OpCode::JUMPI => { 129 | // conditional jump 130 | if let Some(dest) = control::jumpi(&mut runtime.stack, &bytecode)? { 131 | pc = dest; // condition met, set program counter 132 | continue; // don't increment PC after jump 133 | } 134 | } 135 | OpCode::PC => control::pc(&mut runtime.stack, pc), 136 | OpCode::MSIZE => memory::msize(runtime), 137 | OpCode::SLOAD => storage::sload(runtime, system)?, 138 | OpCode::SSTORE => storage::sstore(runtime, system)?, 139 | OpCode::GAS => control::gas(runtime), 140 | OpCode::JUMPDEST => {} // marker opcode for valid jumps addresses 141 | OpCode::PUSH1 => pc += push1(&mut runtime.stack, bytecode[pc + 1]), 142 | OpCode::PUSH2 => pc += push::<2>(&mut runtime.stack, &bytecode[pc + 1..]), 143 | OpCode::PUSH3 => pc += push::<3>(&mut runtime.stack, &bytecode[pc + 1..]), 144 | OpCode::PUSH4 => pc += push::<4>(&mut runtime.stack, &bytecode[pc + 1..]), 145 | OpCode::PUSH5 => pc += push::<5>(&mut runtime.stack, &bytecode[pc + 1..]), 146 | OpCode::PUSH6 => pc += push::<6>(&mut runtime.stack, &bytecode[pc + 1..]), 147 | OpCode::PUSH7 => pc += push::<7>(&mut runtime.stack, &bytecode[pc + 1..]), 148 | OpCode::PUSH8 => pc += push::<8>(&mut runtime.stack, &bytecode[pc + 1..]), 149 | OpCode::PUSH9 => pc += push::<9>(&mut runtime.stack, &bytecode[pc + 1..]), 150 | OpCode::PUSH10 => pc += push::<10>(&mut runtime.stack, &bytecode[pc + 1..]), 151 | OpCode::PUSH11 => pc += push::<11>(&mut runtime.stack, &bytecode[pc + 1..]), 152 | OpCode::PUSH12 => pc += push::<12>(&mut runtime.stack, &bytecode[pc + 1..]), 153 | OpCode::PUSH13 => pc += push::<13>(&mut runtime.stack, &bytecode[pc + 1..]), 154 | OpCode::PUSH14 => pc += push::<14>(&mut runtime.stack, &bytecode[pc + 1..]), 155 | OpCode::PUSH15 => pc += push::<15>(&mut runtime.stack, &bytecode[pc + 1..]), 156 | OpCode::PUSH16 => pc += push::<16>(&mut runtime.stack, &bytecode[pc + 1..]), 157 | OpCode::PUSH17 => pc += push::<17>(&mut runtime.stack, &bytecode[pc + 1..]), 158 | OpCode::PUSH18 => pc += push::<18>(&mut runtime.stack, &bytecode[pc + 1..]), 159 | OpCode::PUSH19 => pc += push::<19>(&mut runtime.stack, &bytecode[pc + 1..]), 160 | OpCode::PUSH20 => pc += push::<20>(&mut runtime.stack, &bytecode[pc + 1..]), 161 | OpCode::PUSH21 => pc += push::<21>(&mut runtime.stack, &bytecode[pc + 1..]), 162 | OpCode::PUSH22 => pc += push::<22>(&mut runtime.stack, &bytecode[pc + 1..]), 163 | OpCode::PUSH23 => pc += push::<23>(&mut runtime.stack, &bytecode[pc + 1..]), 164 | OpCode::PUSH24 => pc += push::<24>(&mut runtime.stack, &bytecode[pc + 1..]), 165 | OpCode::PUSH25 => pc += push::<25>(&mut runtime.stack, &bytecode[pc + 1..]), 166 | OpCode::PUSH26 => pc += push::<26>(&mut runtime.stack, &bytecode[pc + 1..]), 167 | OpCode::PUSH27 => pc += push::<27>(&mut runtime.stack, &bytecode[pc + 1..]), 168 | OpCode::PUSH28 => pc += push::<28>(&mut runtime.stack, &bytecode[pc + 1..]), 169 | OpCode::PUSH29 => pc += push::<29>(&mut runtime.stack, &bytecode[pc + 1..]), 170 | OpCode::PUSH30 => pc += push::<30>(&mut runtime.stack, &bytecode[pc + 1..]), 171 | OpCode::PUSH31 => pc += push::<31>(&mut runtime.stack, &bytecode[pc + 1..]), 172 | OpCode::PUSH32 => pc += push32(&mut runtime.stack, &bytecode[pc + 1..]), 173 | OpCode::DUP1 => dup::<1>(&mut runtime.stack), 174 | OpCode::DUP2 => dup::<2>(&mut runtime.stack), 175 | OpCode::DUP3 => dup::<3>(&mut runtime.stack), 176 | OpCode::DUP4 => dup::<4>(&mut runtime.stack), 177 | OpCode::DUP5 => dup::<5>(&mut runtime.stack), 178 | OpCode::DUP6 => dup::<6>(&mut runtime.stack), 179 | OpCode::DUP7 => dup::<7>(&mut runtime.stack), 180 | OpCode::DUP8 => dup::<8>(&mut runtime.stack), 181 | OpCode::DUP9 => dup::<9>(&mut runtime.stack), 182 | OpCode::DUP10 => dup::<10>(&mut runtime.stack), 183 | OpCode::DUP11 => dup::<11>(&mut runtime.stack), 184 | OpCode::DUP12 => dup::<12>(&mut runtime.stack), 185 | OpCode::DUP13 => dup::<13>(&mut runtime.stack), 186 | OpCode::DUP14 => dup::<14>(&mut runtime.stack), 187 | OpCode::DUP15 => dup::<15>(&mut runtime.stack), 188 | OpCode::DUP16 => dup::<16>(&mut runtime.stack), 189 | OpCode::SWAP1 => swap::<1>(&mut runtime.stack), 190 | OpCode::SWAP2 => swap::<2>(&mut runtime.stack), 191 | OpCode::SWAP3 => swap::<3>(&mut runtime.stack), 192 | OpCode::SWAP4 => swap::<4>(&mut runtime.stack), 193 | OpCode::SWAP5 => swap::<5>(&mut runtime.stack), 194 | OpCode::SWAP6 => swap::<6>(&mut runtime.stack), 195 | OpCode::SWAP7 => swap::<7>(&mut runtime.stack), 196 | OpCode::SWAP8 => swap::<8>(&mut runtime.stack), 197 | OpCode::SWAP9 => swap::<9>(&mut runtime.stack), 198 | OpCode::SWAP10 => swap::<10>(&mut runtime.stack), 199 | OpCode::SWAP11 => swap::<11>(&mut runtime.stack), 200 | OpCode::SWAP12 => swap::<12>(&mut runtime.stack), 201 | OpCode::SWAP13 => swap::<13>(&mut runtime.stack), 202 | OpCode::SWAP14 => swap::<14>(&mut runtime.stack), 203 | OpCode::SWAP15 => swap::<15>(&mut runtime.stack), 204 | OpCode::SWAP16 => swap::<16>(&mut runtime.stack), 205 | OpCode::LOG0 => log(runtime, system, 0)?, 206 | OpCode::LOG1 => log(runtime, system, 1)?, 207 | OpCode::LOG2 => log(runtime, system, 2)?, 208 | OpCode::LOG3 => log(runtime, system, 3)?, 209 | OpCode::LOG4 => log(runtime, system, 4)?, 210 | OpCode::CREATE => storage::create(runtime, system, false)?, 211 | OpCode::CREATE2 => storage::create(runtime, system, true)?, 212 | OpCode::CALL => call::call(runtime, system, CallKind::Call, false)?, 213 | OpCode::CALLCODE => call::call(runtime, system, CallKind::CallCode, false)?, 214 | OpCode::DELEGATECALL => call::call(runtime, system, CallKind::DelegateCall, false)?, 215 | OpCode::STATICCALL => call::call(runtime, system, CallKind::Call, true)?, 216 | OpCode::RETURN | OpCode::REVERT => { 217 | control::ret(runtime)?; 218 | reverted = op == OpCode::REVERT; 219 | break; 220 | } 221 | OpCode::INVALID => return Err(StatusCode::InvalidInstruction), 222 | OpCode::SELFDESTRUCT => storage::selfdestruct(runtime, system)?, 223 | _ => return Err(StatusCode::UndefinedInstruction), 224 | } 225 | 226 | pc += 1; // advance 227 | } 228 | 229 | Ok(Output { 230 | reverted, 231 | status_code: StatusCode::Success, 232 | gas_left: runtime.gas_left, 233 | output_data: runtime.output_data.clone(), 234 | }) 235 | } 236 | -------------------------------------------------------------------------------- /shared/src/instructions/arithmetic.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | execution::ExecutionState, 3 | output::StatusCode, 4 | stack::Stack, 5 | uints::{i256_div, i256_mod, log2floor, u256_high, u256_low}, 6 | U256, 7 | U512, 8 | }; 9 | 10 | #[inline] 11 | pub fn add(stack: &mut Stack) { 12 | let a = stack.pop(); 13 | let b = stack.pop(); 14 | stack.push(a.overflowing_add(b).0); 15 | } 16 | 17 | #[inline] 18 | pub fn mul(stack: &mut Stack) { 19 | let a = stack.pop(); 20 | let b = stack.pop(); 21 | stack.push(a.overflowing_mul(b).0); 22 | } 23 | 24 | #[inline] 25 | pub fn sub(stack: &mut Stack) { 26 | let a = stack.pop(); 27 | let b = stack.pop(); 28 | stack.push(a.overflowing_sub(b).0); 29 | } 30 | 31 | #[inline] 32 | pub fn div(stack: &mut Stack) { 33 | let a = stack.pop(); 34 | let b = stack.get_mut(0); 35 | if *b == U256::zero() { 36 | *b = U256::zero() 37 | } else { 38 | *b = a / *b 39 | } 40 | } 41 | 42 | #[inline] 43 | pub fn sdiv(stack: &mut Stack) { 44 | let a = stack.pop(); 45 | let b = stack.pop(); 46 | let v = i256_div(a, b); 47 | stack.push(v); 48 | } 49 | 50 | #[inline] 51 | pub fn modulo(stack: &mut Stack) { 52 | let a = stack.pop(); 53 | let b = stack.get_mut(0); 54 | *b = if *b == U256::zero() { 55 | U256::zero() 56 | } else { 57 | a % *b 58 | }; 59 | } 60 | 61 | #[inline] 62 | pub fn smod(stack: &mut Stack) { 63 | let a = stack.pop(); 64 | let b = stack.get_mut(0); 65 | 66 | if *b == U256::zero() { 67 | *b = U256::zero() 68 | } else { 69 | *b = i256_mod(a, *b); 70 | }; 71 | } 72 | 73 | #[inline] 74 | pub fn addmod(stack: &mut Stack) { 75 | let a = stack.pop(); 76 | let b = stack.pop(); 77 | let c = stack.pop(); 78 | 79 | let v = if c == U256::zero() { 80 | U256::zero() 81 | } else { 82 | let mut a_be = [0u8; 32]; 83 | let mut b_be = [0u8; 32]; 84 | let mut c_be = [0u8; 32]; 85 | 86 | a.to_big_endian(&mut a_be); 87 | b.to_big_endian(&mut b_be); 88 | c.to_big_endian(&mut c_be); 89 | 90 | let a = U512::from_big_endian(&a_be); 91 | let b = U512::from_big_endian(&b_be); 92 | let c = U512::from_big_endian(&c_be); 93 | 94 | let v = a + b % c; 95 | let mut v_be = [0u8; 64]; 96 | v.to_big_endian(&mut v_be); 97 | U256::from_big_endian(&v_be) 98 | }; 99 | 100 | stack.push(v); 101 | } 102 | 103 | #[inline] 104 | pub fn mulmod(stack: &mut Stack) { 105 | let a = stack.pop(); 106 | let b = stack.pop(); 107 | let c = stack.pop(); 108 | 109 | let v = if c == U256::zero() { 110 | U256::zero() 111 | } else { 112 | let mut a_be = [0u8; 32]; 113 | let mut b_be = [0u8; 32]; 114 | let mut c_be = [0u8; 32]; 115 | 116 | a.to_big_endian(&mut a_be); 117 | b.to_big_endian(&mut b_be); 118 | c.to_big_endian(&mut c_be); 119 | 120 | let a = U512::from_big_endian(&a_be); 121 | let b = U512::from_big_endian(&b_be); 122 | let c = U512::from_big_endian(&c_be); 123 | 124 | let v = a * b % c; 125 | let mut v_be = [0u8; 64]; 126 | v.to_big_endian(&mut v_be); 127 | U256::from_big_endian(&v_be) 128 | }; 129 | 130 | stack.push(v); 131 | } 132 | 133 | #[inline] 134 | pub fn signextend(stack: &mut Stack) { 135 | let a = stack.pop(); 136 | let b = stack.get_mut(0); 137 | 138 | if a < U256::from(32) { 139 | let bit_index = (8 * u256_low(a) as u8 + 7) as u16; 140 | let hi = u256_high(*b); 141 | let lo = u256_low(*b); 142 | let bit = if bit_index > 0x7f { hi } else { lo } & (1 << (bit_index % 128)) != 0; 143 | let mask = (U256::from(1) << bit_index) - U256::from(1); 144 | *b = if bit { *b | !mask } else { *b & mask } 145 | } 146 | } 147 | 148 | #[inline] 149 | pub fn exp(state: &mut ExecutionState) -> Result<(), StatusCode> { 150 | let mut base = state.stack.pop(); 151 | let mut power = state.stack.pop(); 152 | 153 | if power > U256::zero() { 154 | let factor = 50; 155 | let additional_gas = factor * (log2floor(power) / 8 + 1); 156 | state.gas_left -= additional_gas as i64; 157 | if state.gas_left < 0 { 158 | return Err(StatusCode::OutOfGas); 159 | } 160 | } 161 | 162 | let mut v = U256::from(1); 163 | 164 | while power > U256::zero() { 165 | if (power & U256::from(1)) != U256::zero() { 166 | v = v.overflowing_mul(base).0; 167 | } 168 | power >>= 1; 169 | base = base.overflowing_mul(base).0; 170 | } 171 | 172 | state.stack.push(v); 173 | 174 | Ok(()) 175 | } 176 | -------------------------------------------------------------------------------- /shared/src/instructions/bitwise.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | stack::Stack, 3 | uints::{i256_sign, two_compl, u256_high, u256_low, Sign}, 4 | U256, 5 | }; 6 | 7 | #[inline] 8 | pub fn byte(stack: &mut Stack) { 9 | let i = stack.pop(); 10 | let x = stack.get_mut(0); 11 | 12 | if i >= U256::from(32) { 13 | *x = U256::zero(); 14 | return; 15 | } 16 | 17 | let mut i = u256_low(i); 18 | 19 | let x_word = if i >= 16 { 20 | i -= 16; 21 | u256_low(*x) 22 | } else { 23 | u256_high(*x) 24 | }; 25 | 26 | *x = U256::from((x_word >> (120 - i * 8)) & 0xFF); 27 | } 28 | 29 | #[inline] 30 | pub fn shl(stack: &mut Stack) { 31 | let shift = stack.pop(); 32 | let value = stack.get_mut(0); 33 | 34 | if *value == U256::zero() || shift >= U256::from(256) { 35 | *value = U256::zero(); 36 | } else { 37 | *value <<= U256::from(shift) 38 | }; 39 | } 40 | 41 | #[inline] 42 | pub fn shr(stack: &mut Stack) { 43 | let shift = stack.pop(); 44 | let value = stack.get_mut(0); 45 | 46 | if *value == U256::zero() || shift >= U256::from(256) { 47 | *value = U256::zero() 48 | } else { 49 | *value >>= U256::from(shift) 50 | }; 51 | } 52 | 53 | #[inline] 54 | pub fn sar(stack: &mut Stack) { 55 | let shift = stack.pop(); 56 | let mut value = stack.pop(); 57 | 58 | let value_sign = i256_sign::(&mut value); 59 | 60 | stack.push(if value == U256::zero() || shift >= U256::from(256) { 61 | match value_sign { 62 | // value is 0 or >=1, pushing 0 63 | Sign::Plus | Sign::Zero => U256::zero(), 64 | // value is <0, pushing -1 65 | Sign::Minus => two_compl(U256::from(1)), 66 | } 67 | } else { 68 | let shift = shift.as_u128(); 69 | 70 | match value_sign { 71 | Sign::Plus | Sign::Zero => value >> shift, 72 | Sign::Minus => { 73 | let shifted = ((value.overflowing_sub(U256::from(1)).0) >> shift) 74 | .overflowing_add(U256::from(1)) 75 | .0; 76 | two_compl(shifted) 77 | } 78 | } 79 | }); 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use {super::*, crate::uints::u128_words_to_u256}; 85 | 86 | #[test] 87 | fn test_instruction_byte() { 88 | let value = U256::from_big_endian(&(1u8..=32u8).map(|x| 5 * x).collect::>()); 89 | 90 | for i in 0u16..32 { 91 | let mut stack = Stack::new(); 92 | stack.push(value); 93 | stack.push(U256::from(i)); 94 | 95 | byte(&mut stack); 96 | let result = stack.pop(); 97 | 98 | assert_eq!(result, U256::from(5 * (i + 1))); 99 | } 100 | 101 | let mut stack = Stack::new(); 102 | stack.push(value); 103 | stack.push(U256::from(100u128)); 104 | 105 | byte(&mut stack); 106 | let result = stack.pop(); 107 | assert_eq!(result, U256::zero()); 108 | 109 | let mut stack = Stack::new(); 110 | stack.push(value); 111 | stack.push(u128_words_to_u256(1, 0)); 112 | 113 | byte(&mut stack); 114 | let result = stack.pop(); 115 | assert_eq!(result, U256::zero()); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /shared/src/instructions/boolean.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{stack::Stack, uints::i256_cmp, U256}, 3 | std::cmp::Ordering, 4 | }; 5 | 6 | #[inline] 7 | pub fn lt(stack: &mut Stack) { 8 | let a = stack.pop(); 9 | let b = stack.get_mut(0); 10 | 11 | *b = if a.lt(b) { U256::from(1) } else { U256::zero() } 12 | } 13 | 14 | #[inline] 15 | pub fn gt(stack: &mut Stack) { 16 | let a = stack.pop(); 17 | let b = stack.get_mut(0); 18 | 19 | *b = if a.gt(b) { U256::from(1) } else { U256::zero() } 20 | } 21 | 22 | #[inline] 23 | pub(crate) fn slt(stack: &mut Stack) { 24 | let a = stack.pop(); 25 | let b = stack.get_mut(0); 26 | 27 | *b = if i256_cmp(a, *b) == Ordering::Less { 28 | U256::from(1) 29 | } else { 30 | U256::zero() 31 | } 32 | } 33 | 34 | #[inline] 35 | pub(crate) fn sgt(stack: &mut Stack) { 36 | let a = stack.pop(); 37 | let b = stack.get_mut(0); 38 | 39 | *b = if i256_cmp(a, *b) == Ordering::Greater { 40 | U256::from(1) 41 | } else { 42 | U256::zero() 43 | } 44 | } 45 | 46 | #[inline] 47 | pub fn eq(stack: &mut Stack) { 48 | let a = stack.pop(); 49 | let b = stack.get_mut(0); 50 | 51 | *b = if a.eq(b) { U256::from(1) } else { U256::zero() } 52 | } 53 | 54 | #[inline] 55 | pub fn iszero(stack: &mut Stack) { 56 | let a = stack.get_mut(0); 57 | *a = if *a == U256::zero() { 58 | U256::from(1) 59 | } else { 60 | U256::zero() 61 | } 62 | } 63 | 64 | #[inline] 65 | pub(crate) fn and(stack: &mut Stack) { 66 | let a = stack.pop(); 67 | let b = stack.get_mut(0); 68 | *b = a & *b; 69 | } 70 | 71 | #[inline] 72 | pub(crate) fn or(stack: &mut Stack) { 73 | let a = stack.pop(); 74 | let b = stack.get_mut(0); 75 | *b = a | *b; 76 | } 77 | 78 | #[inline] 79 | pub(crate) fn xor(stack: &mut Stack) { 80 | let a = stack.pop(); 81 | let b = stack.get_mut(0); 82 | *b = a ^ *b; 83 | } 84 | 85 | #[inline] 86 | pub(crate) fn not(stack: &mut Stack) { 87 | let v = stack.get_mut(0); 88 | *v = !*v; 89 | } 90 | -------------------------------------------------------------------------------- /shared/src/instructions/call.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::memory::{get_memory_region, num_words}, 3 | crate::{ 4 | execution::ExecutionState, 5 | message::CallKind, 6 | output::StatusCode, 7 | stack::Stack, 8 | system::System, 9 | U256, 10 | }, 11 | fvm_ipld_blockstore::Blockstore, 12 | }; 13 | 14 | #[inline] 15 | pub fn calldataload(state: &mut ExecutionState) { 16 | let index = state.stack.pop(); 17 | let input_len = state.message.input_data.len(); 18 | 19 | state.stack.push({ 20 | if index > U256::from(input_len) { 21 | U256::zero() 22 | } else { 23 | let index_usize = index.as_usize(); 24 | let end = core::cmp::min(index_usize + 32, input_len); 25 | 26 | let mut data = [0; 32]; 27 | data[..end - index_usize] 28 | .copy_from_slice(&state.message.input_data[index_usize..end]); 29 | 30 | U256::from_big_endian(&data) 31 | } 32 | }); 33 | } 34 | 35 | #[inline] 36 | pub fn calldatasize(state: &mut ExecutionState) { 37 | state.stack.push( 38 | u128::try_from(state.message.input_data.len()) 39 | .unwrap() 40 | .into(), 41 | ); 42 | } 43 | 44 | #[inline] 45 | pub fn calldatacopy(state: &mut ExecutionState) -> Result<(), StatusCode> { 46 | let mem_index = state.stack.pop(); 47 | let input_index = state.stack.pop(); 48 | let size = state.stack.pop(); 49 | 50 | let region = 51 | get_memory_region(state, mem_index, size).map_err(|_| StatusCode::OutOfGas)?; 52 | 53 | if let Some(region) = ®ion { 54 | let copy_cost = num_words(region.size.get()) * 3; 55 | state.gas_left -= copy_cost as i64; 56 | if state.gas_left < 0 { 57 | return Err(StatusCode::OutOfGas); 58 | } 59 | 60 | let input_len = U256::from(state.message.input_data.len()); 61 | let src = core::cmp::min(input_len, input_index); 62 | let copy_size = core::cmp::min(size, input_len - src).as_usize(); 63 | let src = src.as_usize(); 64 | 65 | if copy_size > 0 { 66 | state.memory[region.offset..region.offset + copy_size] 67 | .copy_from_slice(&state.message.input_data[src..src + copy_size]); 68 | } 69 | 70 | if region.size.get() > copy_size { 71 | state.memory[region.offset + copy_size..region.offset + region.size.get()].fill(0); 72 | } 73 | } 74 | 75 | Ok(()) 76 | } 77 | 78 | #[inline] 79 | pub fn codesize(stack: &mut Stack, code: &[u8]) { 80 | stack.push(U256::from(code.len())) 81 | } 82 | 83 | #[inline] 84 | pub fn codecopy(state: &mut ExecutionState, code: &[u8]) -> Result<(), StatusCode> { 85 | let mem_index = state.stack.pop(); 86 | let input_index = state.stack.pop(); 87 | let size = state.stack.pop(); 88 | 89 | let region = 90 | get_memory_region(state, mem_index, size).map_err(|_| StatusCode::OutOfGas)?; 91 | 92 | if let Some(region) = region { 93 | let src = core::cmp::min(U256::from(code.len()), input_index).as_usize(); 94 | let copy_size = core::cmp::min(region.size.get(), code.len() - src); 95 | 96 | let copy_cost = num_words(region.size.get()) * 3; 97 | state.gas_left -= copy_cost as i64; 98 | if state.gas_left < 0 { 99 | return Err(StatusCode::OutOfGas); 100 | } 101 | 102 | if copy_size > 0 { 103 | state.memory[region.offset..region.offset + copy_size] 104 | .copy_from_slice(&code[src..src + copy_size]); 105 | } 106 | 107 | if region.size.get() > copy_size { 108 | state.memory[region.offset + copy_size..region.offset + region.size.get()].fill(0); 109 | } 110 | } 111 | 112 | Ok(()) 113 | } 114 | 115 | #[inline] 116 | pub fn call<'r, BS: Blockstore>( 117 | _state: &mut ExecutionState, 118 | _platform: &'r System<'r, BS>, 119 | _kind: CallKind, 120 | _is_static: bool, 121 | ) -> Result<(), StatusCode> { 122 | todo!(); 123 | } 124 | -------------------------------------------------------------------------------- /shared/src/instructions/context.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | execution::ExecutionState, 4 | output::StatusCode, 5 | system::System, 6 | uints::address_to_u256, 7 | }, 8 | fvm_ipld_blockstore::Blockstore, 9 | }; 10 | 11 | #[inline] 12 | pub fn blockhash<'r, BS: Blockstore>( 13 | _state: &mut ExecutionState, 14 | _platform: &'r System<'r, BS>, 15 | ) -> Result<(), StatusCode> { 16 | todo!() 17 | } 18 | 19 | #[inline] 20 | pub fn caller<'r, BS: Blockstore>( 21 | _state: &mut ExecutionState, 22 | _platform: &'r System<'r, BS>, 23 | ) -> Result<(), StatusCode> { 24 | todo!() 25 | } 26 | 27 | #[inline] 28 | pub fn call_value<'r, BS: Blockstore>( 29 | state: &mut ExecutionState, 30 | _platform: &'r System<'r, BS>, 31 | ) { 32 | state.stack.push(state.message.value); 33 | } 34 | 35 | #[inline] 36 | pub fn address<'r, BS: Blockstore>( 37 | _state: &mut ExecutionState, 38 | _platform: &'r System<'r, BS>, 39 | ) -> Result<(), StatusCode> { 40 | todo!() 41 | } 42 | 43 | #[inline] 44 | pub fn origin<'r, BS: Blockstore>( 45 | state: &mut ExecutionState, 46 | platform: &'r System<'r, BS>, 47 | ) { 48 | state 49 | .stack 50 | .push(address_to_u256(platform.transaction_context().tx_origin)) 51 | } 52 | 53 | #[inline] 54 | pub fn coinbase<'r, BS: Blockstore>( 55 | _state: &mut ExecutionState, 56 | _platform: &'r System<'r, BS>, 57 | ) -> Result<(), StatusCode> { 58 | todo!() 59 | } 60 | 61 | #[inline] 62 | pub fn gas_price<'r, BS: Blockstore>( 63 | _state: &mut ExecutionState, 64 | _platform: &'r System<'r, BS>, 65 | ) -> Result<(), StatusCode> { 66 | todo!() 67 | } 68 | 69 | #[inline] 70 | pub fn timestamp<'r, BS: Blockstore>( 71 | _state: &mut ExecutionState, 72 | _platform: &'r System<'r, BS>, 73 | ) -> Result<(), StatusCode> { 74 | todo!() 75 | } 76 | 77 | #[inline] 78 | pub fn block_number<'r, BS: Blockstore>( 79 | _state: &mut ExecutionState, 80 | _platform: &'r System<'r, BS>, 81 | ) -> Result<(), StatusCode> { 82 | todo!() 83 | } 84 | 85 | #[inline] 86 | pub fn difficulty<'r, BS: Blockstore>( 87 | _state: &mut ExecutionState, 88 | _platform: &'r System<'r, BS>, 89 | ) -> Result<(), StatusCode> { 90 | todo!() 91 | } 92 | 93 | #[inline] 94 | pub fn gas_limit<'r, BS: Blockstore>( 95 | _state: &mut ExecutionState, 96 | _platform: &'r System<'r, BS>, 97 | ) -> Result<(), StatusCode> { 98 | todo!() 99 | } 100 | 101 | #[inline] 102 | pub fn chain_id<'r, BS: Blockstore>( 103 | _state: &mut ExecutionState, 104 | _platform: &'r System<'r, BS>, 105 | ) -> Result<(), StatusCode> { 106 | todo!() 107 | } 108 | 109 | #[inline] 110 | pub fn base_fee<'r, BS: Blockstore>( 111 | _state: &mut ExecutionState, 112 | _platform: &'r System<'r, BS>, 113 | ) -> Result<(), StatusCode> { 114 | todo!() 115 | } 116 | -------------------------------------------------------------------------------- /shared/src/instructions/control.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::memory::{get_memory_region, num_words}, 3 | crate::{ 4 | bytecode::Bytecode, 5 | execution::ExecutionState, 6 | output::StatusCode, 7 | stack::Stack, 8 | U256, 9 | }, 10 | }; 11 | 12 | #[inline] 13 | pub fn ret(state: &mut ExecutionState) -> Result<(), StatusCode> { 14 | let offset = *state.stack.get(0); 15 | let size = *state.stack.get(1); 16 | 17 | if let Some(region) = super::memory::get_memory_region(state, offset, size) 18 | .map_err(|_| StatusCode::OutOfGas)? 19 | { 20 | state.output_data = state.memory[region.offset..region.offset + region.size.get()] 21 | .to_vec() 22 | .into(); 23 | } 24 | 25 | Ok(()) 26 | } 27 | 28 | #[inline] 29 | pub fn returndatasize(state: &mut ExecutionState) { 30 | state.stack.push(U256::from(state.return_data.len())); 31 | } 32 | 33 | #[inline] 34 | pub fn returndatacopy(state: &mut ExecutionState) -> Result<(), StatusCode> { 35 | let mem_index = state.stack.pop(); 36 | let input_index = state.stack.pop(); 37 | let size = state.stack.pop(); 38 | 39 | let region = 40 | get_memory_region(state, mem_index, size).map_err(|_| StatusCode::OutOfGas)?; 41 | 42 | if input_index > U256::from(state.return_data.len()) { 43 | return Err(StatusCode::InvalidMemoryAccess); 44 | } 45 | let src = input_index.as_usize(); 46 | 47 | if src + region.as_ref().map(|r| r.size.get()).unwrap_or(0) > state.return_data.len() { 48 | return Err(StatusCode::InvalidMemoryAccess); 49 | } 50 | 51 | if let Some(region) = region { 52 | let copy_cost = num_words(region.size.get()) * 3; 53 | state.gas_left -= copy_cost as i64; 54 | if state.gas_left < 0 { 55 | return Err(StatusCode::OutOfGas); 56 | } 57 | 58 | state.memory[region.offset..region.offset + region.size.get()] 59 | .copy_from_slice(&state.return_data[src..src + region.size.get()]); 60 | } 61 | 62 | Ok(()) 63 | } 64 | 65 | #[inline] 66 | pub fn gas(state: &mut ExecutionState) { 67 | state.stack.push(U256::from(state.gas_left)) 68 | } 69 | 70 | #[inline] 71 | pub fn pc(stack: &mut Stack, pc: usize) { 72 | stack.push(U256::from(pc)) 73 | } 74 | 75 | #[inline] 76 | pub fn jump(stack: &mut Stack, bytecode: &Bytecode) -> Result { 77 | let dst = stack.pop().as_usize(); 78 | if !bytecode.valid_jump_destination(dst) { 79 | return Err(StatusCode::BadJumpDestination); 80 | } 81 | Ok(dst) 82 | } 83 | 84 | #[inline] 85 | pub fn jumpi( 86 | stack: &mut Stack, 87 | bytecode: &Bytecode, 88 | ) -> Result, StatusCode> { 89 | if *stack.get(1) != U256::zero() { 90 | let ret = Ok(Some(jump(stack, bytecode)?)); 91 | stack.pop(); 92 | ret 93 | } else { 94 | stack.pop(); 95 | stack.pop(); 96 | 97 | Ok(None) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /shared/src/instructions/hash.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::memory::{get_memory_region, num_words}, 3 | crate::{execution::ExecutionState, output::StatusCode, U256}, 4 | sha3::{Digest, Keccak256}, 5 | }; 6 | 7 | pub fn keccak256(state: &mut ExecutionState) -> Result<(), StatusCode> { 8 | let index = state.stack.pop(); 9 | let size = state.stack.pop(); 10 | 11 | let region = get_memory_region(state, index, size) // 12 | .map_err(|_| StatusCode::OutOfGas)?; 13 | 14 | state.stack.push(U256::from_big_endian(&*Keccak256::digest( 15 | if let Some(region) = region { 16 | let w = num_words(region.size.get()); 17 | let cost = w * 6; 18 | state.gas_left -= cost as i64; 19 | if state.gas_left < 0 { 20 | return Err(StatusCode::OutOfGas); 21 | } 22 | 23 | &state.memory[region.offset..region.offset + region.size.get()] 24 | } else { 25 | &[] 26 | }, 27 | ))); 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /shared/src/instructions/log.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{execution::ExecutionState, output::StatusCode, system::System}, 3 | fvm_ipld_blockstore::Blockstore, 4 | }; 5 | 6 | #[inline] 7 | pub fn log<'r, BS: Blockstore>( 8 | _state: &mut ExecutionState, 9 | _platform: &'r System<'r, BS>, 10 | _num_topics: usize, 11 | ) -> Result<(), StatusCode> { 12 | todo!() 13 | } 14 | -------------------------------------------------------------------------------- /shared/src/instructions/memory.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{execution::ExecutionState, output::StatusCode, system::System, U256}, 3 | fvm_ipld_blockstore::Blockstore, 4 | std::num::NonZeroUsize, 5 | }; 6 | 7 | /// The size of the EVM 256-bit word in bytes. 8 | const WORD_SIZE: usize = 32; 9 | 10 | pub struct MemoryRegion { 11 | pub offset: usize, 12 | pub size: NonZeroUsize, 13 | } 14 | 15 | /// Returns number of words what would fit to provided number of bytes, 16 | /// i.e. it rounds up the number bytes to number of words. 17 | #[inline] 18 | pub fn num_words(size_in_bytes: usize) -> usize { 19 | (size_in_bytes + (WORD_SIZE - 1)) / WORD_SIZE 20 | } 21 | 22 | #[inline] 23 | fn grow_memory(state: &mut ExecutionState, new_size: usize) -> Result<(), ()> { 24 | let new_words = num_words(new_size); 25 | let current_words = state.memory.len() / WORD_SIZE; 26 | let new_cost = 3 * new_words + new_words * new_words / 512; 27 | let current_cost = 3 * current_words + current_words * current_words / 512; 28 | let cost = new_cost - current_cost; 29 | 30 | state.gas_left -= cost as i64; 31 | 32 | if state.gas_left < 0 { 33 | return Err(()); 34 | } 35 | 36 | state.memory.grow((new_words * WORD_SIZE) as usize); 37 | 38 | Ok(()) 39 | } 40 | 41 | #[inline] 42 | fn get_memory_region_u64( 43 | state: &mut ExecutionState, 44 | offset: U256, 45 | size: NonZeroUsize, 46 | ) -> Result { 47 | if offset > U256::from(u32::MAX) { 48 | return Err(()); 49 | } 50 | 51 | let new_size = offset.as_usize() + size.get(); 52 | let current_size = state.memory.len(); 53 | if new_size > current_size { 54 | grow_memory(state, new_size)?; 55 | } 56 | 57 | Ok(MemoryRegion { 58 | offset: offset.as_usize(), 59 | size, 60 | }) 61 | } 62 | 63 | #[inline] 64 | pub fn get_memory_region( 65 | state: &mut ExecutionState, 66 | offset: U256, 67 | size: U256, 68 | ) -> Result, ()> { 69 | if size == U256::zero() { 70 | return Ok(None); 71 | } 72 | 73 | if size > U256::from(u32::MAX) { 74 | return Err(()); 75 | } 76 | 77 | get_memory_region_u64(state, offset, NonZeroUsize::new(size.as_usize()).unwrap()) 78 | .map(Some) 79 | } 80 | 81 | #[inline] 82 | pub fn mload(state: &mut ExecutionState) -> Result<(), StatusCode> { 83 | let index = state.stack.pop(); 84 | 85 | let region = get_memory_region_u64(state, index, NonZeroUsize::new(WORD_SIZE).unwrap()) 86 | .map_err(|_| StatusCode::OutOfGas)?; 87 | let value = U256::from_big_endian( 88 | &state.memory[region.offset..region.offset + region.size.get()], 89 | ); 90 | 91 | state.stack.push(value); 92 | 93 | Ok(()) 94 | } 95 | 96 | #[inline] 97 | pub fn mstore(state: &mut ExecutionState) -> Result<(), StatusCode> { 98 | let index = state.stack.pop(); 99 | let value = state.stack.pop(); 100 | 101 | let region = get_memory_region_u64(state, index, NonZeroUsize::new(WORD_SIZE).unwrap()) 102 | .map_err(|_| StatusCode::OutOfGas)?; 103 | 104 | let mut bytes = [0u8; WORD_SIZE]; 105 | value.to_big_endian(&mut bytes); 106 | state.memory[region.offset..region.offset + WORD_SIZE].copy_from_slice(&bytes); 107 | 108 | Ok(()) 109 | } 110 | 111 | #[inline] 112 | pub fn mstore8(state: &mut ExecutionState) -> Result<(), StatusCode> { 113 | let index = state.stack.pop(); 114 | let value = state.stack.pop(); 115 | 116 | let region = get_memory_region_u64(state, index, NonZeroUsize::new(1).unwrap()) 117 | .map_err(|_| StatusCode::OutOfGas)?; 118 | 119 | let value = (value.low_u32() & 0xff) as u8; 120 | 121 | state.memory[region.offset] = value; 122 | 123 | Ok(()) 124 | } 125 | 126 | #[inline] 127 | pub fn msize(state: &mut ExecutionState) { 128 | state 129 | .stack 130 | .push(u64::try_from(state.memory.len()).unwrap().into()); 131 | } 132 | 133 | pub fn extcodecopy<'r, BS: Blockstore>( 134 | _state: &mut ExecutionState, 135 | _platform: &'r System<'r, BS>, 136 | ) -> Result<(), StatusCode> { 137 | todo!(); 138 | } 139 | -------------------------------------------------------------------------------- /shared/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod arithmetic; 2 | pub mod bitwise; 3 | pub mod boolean; 4 | pub mod call; 5 | pub mod context; 6 | pub mod control; 7 | pub mod hash; 8 | pub mod log; 9 | pub mod memory; 10 | pub mod stack; 11 | pub mod storage; 12 | -------------------------------------------------------------------------------- /shared/src/instructions/stack.rs: -------------------------------------------------------------------------------- 1 | use crate::{stack::Stack, U256}; 2 | 3 | #[inline] 4 | pub(crate) fn push(stack: &mut Stack, code: &[u8]) -> usize { 5 | stack.push(U256::from_big_endian(&code[..LEN])); 6 | LEN 7 | } 8 | 9 | #[inline] 10 | pub(crate) fn push1(stack: &mut Stack, v: u8) -> usize { 11 | stack.push(v.into()); 12 | 1 13 | } 14 | 15 | #[inline] 16 | pub(crate) fn push32(stack: &mut Stack, code: &[u8]) -> usize { 17 | stack.push(U256::from_big_endian(&code[0..32])); 18 | 32 19 | } 20 | 21 | #[inline] 22 | pub(crate) fn dup(stack: &mut Stack) { 23 | stack.push(*stack.get(HEIGHT - 1)); 24 | } 25 | 26 | #[inline] 27 | pub(crate) fn swap(stack: &mut Stack) { 28 | stack.swap_top(HEIGHT); 29 | } 30 | 31 | #[inline] 32 | pub(crate) fn pop(stack: &mut Stack) { 33 | stack.pop(); 34 | } 35 | -------------------------------------------------------------------------------- /shared/src/instructions/storage.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | execution::ExecutionState, 4 | output::StatusCode, 5 | system::{AccessStatus, StorageStatus, System}, 6 | }, 7 | fvm_ipld_blockstore::Blockstore, 8 | }; 9 | 10 | pub(crate) const COLD_SLOAD_COST: u16 = 2100; 11 | pub(crate) const _COLD_ACCOUNT_ACCESS_COST: u16 = 2600; 12 | pub(crate) const WARM_STORAGE_READ_COST: u16 = 100; 13 | 14 | #[inline(always)] 15 | fn ok_or_out_of_gas(gas_left: i64) -> Result<(), StatusCode> { 16 | match gas_left >= 0 { 17 | true => Ok(()), 18 | false => Err(StatusCode::OutOfGas), 19 | } 20 | } 21 | 22 | #[inline] 23 | pub fn sload<'r, BS: Blockstore>( 24 | _state: &mut ExecutionState, 25 | _platform: &'r System<'r, BS>, 26 | ) -> Result<(), StatusCode> { 27 | todo!(); 28 | } 29 | 30 | #[inline] 31 | pub fn sstore<'r, BS: Blockstore>( 32 | state: &mut ExecutionState, 33 | platform: &'r System<'r, BS>, 34 | ) -> Result<(), StatusCode> { 35 | if state.message.is_static { 36 | return Err(StatusCode::StaticModeViolation); 37 | } 38 | 39 | if state.gas_left <= 2300 { 40 | return Err(StatusCode::OutOfGas); 41 | } 42 | 43 | let location = state.stack.pop(); 44 | let value = state.stack.pop(); 45 | 46 | let mut cost = 0; 47 | if platform.access_storage(state.message.recipient, location) == AccessStatus::Cold { 48 | cost = COLD_SLOAD_COST; 49 | } 50 | 51 | cost = match platform.set_storage(state.message.recipient, location, value)? { 52 | StorageStatus::Unchanged | StorageStatus::ModifiedAgain => { 53 | cost + WARM_STORAGE_READ_COST 54 | } 55 | StorageStatus::Modified | StorageStatus::Deleted => cost + 5000 - COLD_SLOAD_COST, 56 | StorageStatus::Added => cost + 20000, 57 | }; 58 | 59 | state.gas_left -= i64::from(cost); 60 | ok_or_out_of_gas(state.gas_left) 61 | } 62 | 63 | #[inline] 64 | pub fn balance<'r, BS: Blockstore>( 65 | _state: &mut ExecutionState, 66 | _platform: &'r System<'r, BS>, 67 | ) -> Result<(), StatusCode> { 68 | todo!() 69 | } 70 | 71 | #[inline] 72 | pub fn selfbalance<'r, BS: Blockstore>( 73 | _state: &mut ExecutionState, 74 | _platform: &'r System<'r, BS>, 75 | ) -> Result<(), StatusCode> { 76 | todo!() 77 | } 78 | 79 | #[inline] 80 | pub fn extcodesize<'r, BS: Blockstore>( 81 | _state: &mut ExecutionState, 82 | _platform: &'r System<'r, BS>, 83 | ) -> Result<(), StatusCode> { 84 | todo!() 85 | } 86 | 87 | pub fn extcodehash<'r, BS: Blockstore>( 88 | _state: &mut ExecutionState, 89 | _platform: &'r System<'r, BS>, 90 | ) -> Result<(), StatusCode> { 91 | todo!(); 92 | } 93 | 94 | #[inline] 95 | pub fn create<'r, BS: Blockstore>( 96 | _state: &mut ExecutionState, 97 | _platform: &'r System<'r, BS>, 98 | _create2: bool, 99 | ) -> Result<(), StatusCode> { 100 | todo!() 101 | } 102 | 103 | #[inline] 104 | pub fn selfdestruct<'r, BS: Blockstore>( 105 | _state: &mut ExecutionState, 106 | _platform: &'r System<'r, BS>, 107 | ) -> Result<(), StatusCode> { 108 | todo!() 109 | } 110 | -------------------------------------------------------------------------------- /shared/src/instructions/table.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filecoin-project/fvm-evm/86e75b570ea9463439efa4b0d2b8c7547341545a/shared/src/instructions/table.rs -------------------------------------------------------------------------------- /shared/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Shared types between the EVM address registry and EVM runtime actors 2 | 3 | mod account; 4 | mod bytecode; 5 | mod execution; 6 | mod instructions; 7 | mod memory; 8 | mod message; 9 | mod opcode; 10 | mod output; 11 | mod stack; 12 | mod system; 13 | mod transaction; 14 | pub mod uints; 15 | 16 | pub use { 17 | account::{AccountKind, EthereumAccount}, 18 | bytecode::Bytecode, 19 | execution::{execute, ExecutionState}, 20 | message::{CallKind, Message, EvmContractRuntimeConstructor}, 21 | output::{Output, StatusCode}, 22 | system::System, 23 | transaction::{ 24 | SignedTransaction, 25 | Transaction, 26 | TransactionAction, 27 | TransactionRecoveryId, 28 | TransactionSignature, 29 | }, 30 | uints::{H160, H256, U256, U512}, 31 | }; 32 | 33 | #[macro_export] 34 | macro_rules! abort { 35 | ($code:ident, $msg:literal $(, $ex:expr)*) => { 36 | fvm_sdk::vm::abort( 37 | fvm_shared::error::ExitCode::$code.value(), 38 | Some(format!($msg, $($ex,)*).as_str()), 39 | ) 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /shared/src/memory.rs: -------------------------------------------------------------------------------- 1 | use { 2 | bytes::BytesMut, 3 | derive_more::{Deref, DerefMut}, 4 | }; 5 | 6 | const PAGE_SIZE: usize = 4 * 1024; 7 | 8 | #[derive(Clone, Debug, Deref, DerefMut)] 9 | pub struct Memory(BytesMut); 10 | 11 | impl Default for Memory { 12 | fn default() -> Self { 13 | Self(BytesMut::with_capacity(PAGE_SIZE)) 14 | } 15 | } 16 | 17 | impl Memory { 18 | #[inline] 19 | pub fn grow(&mut self, size: usize) { 20 | let cap = self.0.capacity(); 21 | if size > cap { 22 | let required_pages = (size + PAGE_SIZE - 1) / PAGE_SIZE; 23 | self.0.reserve((PAGE_SIZE * required_pages) - self.0.len()); 24 | } 25 | self.0.resize(size, 0); 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use super::*; 32 | 33 | #[test] 34 | fn grow() { 35 | let mut mem = Memory::default(); 36 | mem.grow(PAGE_SIZE * 2 + 1); 37 | assert_eq!(mem.len(), PAGE_SIZE * 2 + 1); 38 | assert_eq!(mem.capacity(), PAGE_SIZE * 3); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /shared/src/message.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{transaction::TransactionAction, SignedTransaction, H160, U256}, 3 | bytes::Bytes, 4 | fil_actors_runtime::ActorError, 5 | std::fmt::Debug, 6 | }; 7 | 8 | /// The kind of call-like instruction. 9 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 10 | pub enum CallKind { 11 | Call, 12 | DelegateCall, 13 | CallCode, 14 | Create, 15 | Create2 { salt: U256 }, 16 | } 17 | 18 | /// The message describing an EVM call, 19 | /// including a zero-depth call from transaction origin. 20 | #[derive(Clone, Debug, PartialEq)] 21 | pub struct Message { 22 | /// The kind of the call. For zero-depth calls `CallKind::Call` SHOULD be 23 | /// used. 24 | pub kind: CallKind, 25 | 26 | /// Static call mode. 27 | pub is_static: bool, 28 | 29 | /// The call depth. 30 | pub depth: i32, 31 | 32 | /// The amount of gas for message execution. 33 | pub gas: i64, 34 | 35 | /// The destination (recipient) of the message. 36 | pub recipient: H160, 37 | 38 | /// The sender of the message. 39 | pub sender: H160, 40 | 41 | /// Message input data. 42 | pub input_data: Bytes, 43 | 44 | /// The amount of Ether transferred with the message. 45 | pub value: U256, 46 | } 47 | 48 | impl TryFrom for Message { 49 | type Error = ActorError; 50 | 51 | fn try_from(tx: SignedTransaction) -> Result { 52 | Ok(Message { 53 | kind: match tx.transaction.action() { 54 | TransactionAction::Call(_) => CallKind::Call, 55 | TransactionAction::Create => CallKind::Create, 56 | }, 57 | is_static: false, 58 | depth: 0, 59 | gas: tx.transaction.gas_limit() as i64, 60 | recipient: match tx.transaction.action() { 61 | TransactionAction::Call(addr) => addr, 62 | TransactionAction::Create => H160::zero(), 63 | }, 64 | sender: tx.sender_address()?, 65 | input_data: tx.transaction.input(), 66 | value: tx.transaction.value(), 67 | }) 68 | } 69 | } 70 | 71 | /// This type is used to construct a new instance of an EVM contract. 72 | /// Instances of this type are created by the bridge actor after a successful 73 | /// invocation of EVM contract constructor. 74 | #[derive(serde_tuple::Serialize_tuple, serde_tuple::Deserialize_tuple)] 75 | pub struct EvmContractRuntimeConstructor { 76 | pub initial_state: cid::Cid, 77 | pub bytecode: bytes::Bytes, 78 | pub registry: fvm_shared::address::Address, 79 | pub address: H160, 80 | } 81 | 82 | impl Debug for EvmContractRuntimeConstructor { 83 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 84 | f.debug_struct("EvmContractRuntimeConstructor") 85 | .field("initial_state", &self.initial_state) 86 | .field("bytecode", &hex::encode(&self.bytecode)) 87 | .field("registry", &self.registry) 88 | .field("address", &self.address) 89 | .finish() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /shared/src/opcode.rs: -------------------------------------------------------------------------------- 1 | //! EVM Opcodes as of Berlin Hard Fork 2 | //! 3 | //! On filecoin we will never have to replay blocks that are older 4 | //! than the release date of the FVM-EVM runtime, so supporting 5 | //! historic behavior is not needed. 6 | 7 | use crate::output::StatusCode; 8 | 9 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 10 | pub struct OpCode { 11 | /// the byte representing the opcode in binary 12 | pub code: u8, 13 | 14 | /// cost of executing the opcode, subtracted from the 15 | /// total gas limit when running bytecode. 16 | pub price: u16, 17 | 18 | /// The number of stack items the instruction accesses during execution. 19 | pub stack_height_required: u8, 20 | 21 | /// The stack height change caused by the instruction execution. Can be 22 | /// negative. 23 | pub stack_height_change: i8, 24 | 25 | /// Human readable name of the opcode. 26 | pub name: &'static str, 27 | } 28 | 29 | impl From for u8 { 30 | fn from(op: OpCode) -> Self { 31 | op.code 32 | } 33 | } 34 | 35 | impl PartialEq for OpCode { 36 | fn eq(&self, other: &u8) -> bool { 37 | self.code == *other 38 | } 39 | } 40 | 41 | const _COLD_SLOAD_COST: u16 = 2100; 42 | const _COLD_ACCOUNT_ACCESS_COST: u16 = 2600; 43 | const WARM_STORAGE_READ_COST: u16 = 100; 44 | 45 | impl OpCode { 46 | pub const ADD: OpCode = OpCode { 47 | code: 0x01, 48 | price: 3, 49 | stack_height_required: 2, 50 | stack_height_change: -1, 51 | name: "ADD", 52 | }; 53 | pub const ADDMOD: OpCode = OpCode { 54 | code: 0x08, 55 | price: 8, 56 | stack_height_required: 3, 57 | stack_height_change: -2, 58 | name: "ADDMOD", 59 | }; 60 | pub const ADDRESS: OpCode = OpCode { 61 | code: 0x30, 62 | price: 2, 63 | stack_height_required: 0, 64 | stack_height_change: 1, 65 | name: "ADDRESS", 66 | }; 67 | pub const AND: OpCode = OpCode { 68 | code: 0x16, 69 | price: 3, 70 | stack_height_required: 2, 71 | stack_height_change: -1, 72 | name: "AND", 73 | }; 74 | pub const BALANCE: OpCode = OpCode { 75 | code: 0x31, 76 | price: WARM_STORAGE_READ_COST, 77 | stack_height_required: 1, 78 | stack_height_change: 0, 79 | name: "BALANCE", 80 | }; 81 | pub const BASEFEE: OpCode = OpCode { 82 | code: 0x48, 83 | price: 2, 84 | stack_height_required: 0, 85 | stack_height_change: 1, 86 | name: "BASEFEE", 87 | }; 88 | pub const BLOCKHASH: OpCode = OpCode { 89 | code: 0x40, 90 | price: 20, 91 | stack_height_required: 1, 92 | stack_height_change: 0, 93 | name: "BLOCKHASH", 94 | }; 95 | pub const BYTE: OpCode = OpCode { 96 | code: 0x1a, 97 | price: 3, 98 | stack_height_required: 2, 99 | stack_height_change: -1, 100 | name: "BYTE", 101 | }; 102 | pub const CALL: OpCode = OpCode { 103 | code: 0xf1, 104 | price: WARM_STORAGE_READ_COST, 105 | stack_height_required: 7, 106 | stack_height_change: -6, 107 | name: "CALL", 108 | }; 109 | pub const CALLCODE: OpCode = OpCode { 110 | code: 0xf2, 111 | price: WARM_STORAGE_READ_COST, 112 | stack_height_required: 7, 113 | stack_height_change: -6, 114 | name: "CALLCODE", 115 | }; 116 | pub const CALLDATACOPY: OpCode = OpCode { 117 | code: 0x37, 118 | price: 3, 119 | stack_height_required: 3, 120 | stack_height_change: -3, 121 | name: "CALLDATACOPY", 122 | }; 123 | pub const CALLDATALOAD: OpCode = OpCode { 124 | code: 0x35, 125 | price: 3, 126 | stack_height_required: 1, 127 | stack_height_change: 0, 128 | name: "CALLDATALOAD", 129 | }; 130 | pub const CALLDATASIZE: OpCode = OpCode { 131 | code: 0x36, 132 | price: 2, 133 | stack_height_required: 0, 134 | stack_height_change: 1, 135 | name: "CALLDATASIZE", 136 | }; 137 | pub const CALLER: OpCode = OpCode { 138 | code: 0x33, 139 | price: 2, 140 | stack_height_required: 0, 141 | stack_height_change: 1, 142 | name: "CALLER", 143 | }; 144 | pub const CALLVALUE: OpCode = OpCode { 145 | code: 0x34, 146 | price: 2, 147 | stack_height_required: 0, 148 | stack_height_change: 1, 149 | name: "CALLVALUE", 150 | }; 151 | pub const CHAINID: OpCode = OpCode { 152 | code: 0x46, 153 | price: 2, 154 | stack_height_required: 0, 155 | stack_height_change: 1, 156 | name: "CHAINID", 157 | }; 158 | pub const CODECOPY: OpCode = OpCode { 159 | code: 0x39, 160 | price: 3, 161 | stack_height_required: 3, 162 | stack_height_change: -3, 163 | name: "CODECOPY", 164 | }; 165 | pub const CODESIZE: OpCode = OpCode { 166 | code: 0x38, 167 | price: 2, 168 | stack_height_required: 0, 169 | stack_height_change: 1, 170 | name: "CODESIZE", 171 | }; 172 | pub const COINBASE: OpCode = OpCode { 173 | code: 0x41, 174 | price: 2, 175 | stack_height_required: 0, 176 | stack_height_change: 1, 177 | name: "COINBASE", 178 | }; 179 | pub const CREATE: OpCode = OpCode { 180 | code: 0xf0, 181 | price: 32000, 182 | stack_height_required: 3, 183 | stack_height_change: -2, 184 | name: "CREATE", 185 | }; 186 | pub const CREATE2: OpCode = OpCode { 187 | code: 0xf5, 188 | price: 32000, 189 | stack_height_required: 4, 190 | stack_height_change: -3, 191 | name: "CREATE2", 192 | }; 193 | pub const DELEGATECALL: OpCode = OpCode { 194 | code: 0xf4, 195 | price: WARM_STORAGE_READ_COST, 196 | stack_height_required: 6, 197 | stack_height_change: -5, 198 | name: "DELEGATECALL", 199 | }; 200 | pub const DIFFICULTY: OpCode = OpCode { 201 | code: 0x44, 202 | price: 2, 203 | stack_height_required: 0, 204 | stack_height_change: 1, 205 | name: "DIFFICULTY", 206 | }; 207 | pub const DIV: OpCode = OpCode { 208 | code: 0x04, 209 | price: 5, 210 | stack_height_required: 2, 211 | stack_height_change: -1, 212 | name: "DIV", 213 | }; 214 | pub const DUP1: OpCode = OpCode { 215 | code: 0x80, 216 | price: 3, 217 | stack_height_required: 1, 218 | stack_height_change: 1, 219 | name: "DUP1", 220 | }; 221 | pub const DUP10: OpCode = OpCode { 222 | code: 0x89, 223 | price: 3, 224 | stack_height_required: 10, 225 | stack_height_change: 1, 226 | name: "DUP10", 227 | }; 228 | pub const DUP11: OpCode = OpCode { 229 | code: 0x8a, 230 | price: 3, 231 | stack_height_required: 11, 232 | stack_height_change: 1, 233 | name: "DUP11", 234 | }; 235 | pub const DUP12: OpCode = OpCode { 236 | code: 0x8b, 237 | price: 3, 238 | stack_height_required: 12, 239 | stack_height_change: 1, 240 | name: "DUP12", 241 | }; 242 | pub const DUP13: OpCode = OpCode { 243 | code: 0x8c, 244 | price: 3, 245 | stack_height_required: 13, 246 | stack_height_change: 1, 247 | name: "DUP13", 248 | }; 249 | pub const DUP14: OpCode = OpCode { 250 | code: 0x8d, 251 | price: 3, 252 | stack_height_required: 14, 253 | stack_height_change: 1, 254 | name: "DUP14", 255 | }; 256 | pub const DUP15: OpCode = OpCode { 257 | code: 0x8e, 258 | price: 3, 259 | stack_height_required: 15, 260 | stack_height_change: 1, 261 | name: "DUP15", 262 | }; 263 | pub const DUP16: OpCode = OpCode { 264 | code: 0x8f, 265 | price: 3, 266 | stack_height_required: 16, 267 | stack_height_change: 1, 268 | name: "DUP16", 269 | }; 270 | pub const DUP2: OpCode = OpCode { 271 | code: 0x81, 272 | price: 3, 273 | stack_height_required: 2, 274 | stack_height_change: 1, 275 | name: "DUP2", 276 | }; 277 | pub const DUP3: OpCode = OpCode { 278 | code: 0x82, 279 | price: 3, 280 | stack_height_required: 3, 281 | stack_height_change: 1, 282 | name: "DUP3", 283 | }; 284 | pub const DUP4: OpCode = OpCode { 285 | code: 0x83, 286 | price: 3, 287 | stack_height_required: 4, 288 | stack_height_change: 1, 289 | name: "DUP4", 290 | }; 291 | pub const DUP5: OpCode = OpCode { 292 | code: 0x84, 293 | price: 3, 294 | stack_height_required: 5, 295 | stack_height_change: 1, 296 | name: "DUP5", 297 | }; 298 | pub const DUP6: OpCode = OpCode { 299 | code: 0x85, 300 | price: 3, 301 | stack_height_required: 6, 302 | stack_height_change: 1, 303 | name: "DUP6", 304 | }; 305 | pub const DUP7: OpCode = OpCode { 306 | code: 0x86, 307 | price: 3, 308 | stack_height_required: 7, 309 | stack_height_change: 1, 310 | name: "DUP7", 311 | }; 312 | pub const DUP8: OpCode = OpCode { 313 | code: 0x87, 314 | price: 3, 315 | stack_height_required: 8, 316 | stack_height_change: 1, 317 | name: "DUP8", 318 | }; 319 | pub const DUP9: OpCode = OpCode { 320 | code: 0x88, 321 | price: 3, 322 | stack_height_required: 9, 323 | stack_height_change: 1, 324 | name: "DUP9", 325 | }; 326 | pub const EQ: OpCode = OpCode { 327 | code: 0x14, 328 | price: 3, 329 | stack_height_required: 2, 330 | stack_height_change: -1, 331 | name: "EQ", 332 | }; 333 | pub const EXP: OpCode = OpCode { 334 | code: 0x0a, 335 | price: 10, 336 | stack_height_required: 2, 337 | stack_height_change: -1, 338 | name: "EXP", 339 | }; 340 | pub const EXTCODECOPY: OpCode = OpCode { 341 | code: 0x3c, 342 | price: WARM_STORAGE_READ_COST, 343 | stack_height_required: 4, 344 | stack_height_change: -4, 345 | name: "EXTCODECOPY", 346 | }; 347 | pub const EXTCODEHASH: OpCode = OpCode { 348 | code: 0x3f, 349 | price: WARM_STORAGE_READ_COST, 350 | stack_height_required: 1, 351 | stack_height_change: 0, 352 | name: "EXTCODEHASH", 353 | }; 354 | pub const EXTCODESIZE: OpCode = OpCode { 355 | code: 0x3b, 356 | price: WARM_STORAGE_READ_COST, 357 | stack_height_required: 1, 358 | stack_height_change: 0, 359 | name: "EXTCODESIZE", 360 | }; 361 | pub const GAS: OpCode = OpCode { 362 | code: 0x5a, 363 | price: 2, 364 | stack_height_required: 0, 365 | stack_height_change: 1, 366 | name: "GAS", 367 | }; 368 | pub const GASLIMIT: OpCode = OpCode { 369 | code: 0x45, 370 | price: 2, 371 | stack_height_required: 0, 372 | stack_height_change: 1, 373 | name: "GASLIMIT", 374 | }; 375 | pub const GASPRICE: OpCode = OpCode { 376 | code: 0x3a, 377 | price: 2, 378 | stack_height_required: 0, 379 | stack_height_change: 1, 380 | name: "GASPRICE", 381 | }; 382 | pub const GT: OpCode = OpCode { 383 | code: 0x11, 384 | price: 3, 385 | stack_height_required: 2, 386 | stack_height_change: -1, 387 | name: "GT", 388 | }; 389 | pub const INVALID: OpCode = OpCode { 390 | code: 0xfe, 391 | price: 0, 392 | stack_height_required: 0, 393 | stack_height_change: 0, 394 | name: "INVALID", 395 | }; 396 | pub const ISZERO: OpCode = OpCode { 397 | code: 0x15, 398 | price: 3, 399 | stack_height_required: 1, 400 | stack_height_change: 0, 401 | name: "ISZERO", 402 | }; 403 | pub const JUMP: OpCode = OpCode { 404 | code: 0x56, 405 | price: 8, 406 | stack_height_required: 1, 407 | stack_height_change: -1, 408 | name: "JUMP", 409 | }; 410 | pub const JUMPDEST: OpCode = OpCode { 411 | code: 0x5b, 412 | price: 1, 413 | stack_height_required: 0, 414 | stack_height_change: 0, 415 | name: "JUMPDEST", 416 | }; 417 | pub const JUMPI: OpCode = OpCode { 418 | code: 0x57, 419 | price: 10, 420 | stack_height_required: 2, 421 | stack_height_change: -2, 422 | name: "JUMPI", 423 | }; 424 | pub const KECCAK256: OpCode = OpCode { 425 | code: 0x20, 426 | price: 30, 427 | stack_height_required: 2, 428 | stack_height_change: -1, 429 | name: "KECCAK256", 430 | }; 431 | pub const LOG0: OpCode = OpCode { 432 | code: 0xa0, 433 | price: 375, 434 | stack_height_required: 2, 435 | stack_height_change: -2, 436 | name: "LOG0", 437 | }; 438 | pub const LOG1: OpCode = OpCode { 439 | code: 0xa1, 440 | price: 2 * 375, 441 | stack_height_required: 3, 442 | stack_height_change: -3, 443 | name: "LOG1", 444 | }; 445 | pub const LOG2: OpCode = OpCode { 446 | code: 0xa2, 447 | price: 3 * 375, 448 | stack_height_required: 4, 449 | stack_height_change: -4, 450 | name: "LOG2", 451 | }; 452 | pub const LOG3: OpCode = OpCode { 453 | code: 0xa3, 454 | price: 4 * 375, 455 | stack_height_required: 5, 456 | stack_height_change: -5, 457 | name: "LOG3", 458 | }; 459 | pub const LOG4: OpCode = OpCode { 460 | code: 0xa4, 461 | price: 5 * 375, 462 | stack_height_required: 6, 463 | stack_height_change: -6, 464 | name: "LOG4", 465 | }; 466 | pub const LT: OpCode = OpCode { 467 | code: 0x10, 468 | price: 3, 469 | stack_height_required: 2, 470 | stack_height_change: -1, 471 | name: "LT", 472 | }; 473 | pub const MLOAD: OpCode = OpCode { 474 | code: 0x51, 475 | price: 3, 476 | stack_height_required: 1, 477 | stack_height_change: 0, 478 | name: "MLOAD", 479 | }; 480 | pub const MOD: OpCode = OpCode { 481 | code: 0x06, 482 | price: 5, 483 | stack_height_required: 2, 484 | stack_height_change: -1, 485 | name: "MOD", 486 | }; 487 | pub const MSIZE: OpCode = OpCode { 488 | code: 0x59, 489 | price: 2, 490 | stack_height_required: 0, 491 | stack_height_change: 1, 492 | name: "MSIZE", 493 | }; 494 | pub const MSTORE: OpCode = OpCode { 495 | code: 0x52, 496 | price: 3, 497 | stack_height_required: 2, 498 | stack_height_change: -2, 499 | name: "MSTORE", 500 | }; 501 | pub const MSTORE8: OpCode = OpCode { 502 | code: 0x53, 503 | price: 3, 504 | stack_height_required: 2, 505 | stack_height_change: -2, 506 | name: "MSTORE8", 507 | }; 508 | pub const MUL: OpCode = OpCode { 509 | code: 0x02, 510 | price: 5, 511 | stack_height_required: 2, 512 | stack_height_change: -1, 513 | name: "MUL", 514 | }; 515 | pub const MULMOD: OpCode = OpCode { 516 | code: 0x09, 517 | price: 8, 518 | stack_height_required: 3, 519 | stack_height_change: -2, 520 | name: "MULMOD", 521 | }; 522 | pub const NOT: OpCode = OpCode { 523 | code: 0x19, 524 | price: 3, 525 | stack_height_required: 1, 526 | stack_height_change: 0, 527 | name: "NOT", 528 | }; 529 | pub const NUMBER: OpCode = OpCode { 530 | code: 0x43, 531 | price: 2, 532 | stack_height_required: 0, 533 | stack_height_change: 1, 534 | name: "NUMBER", 535 | }; 536 | pub const OR: OpCode = OpCode { 537 | code: 0x17, 538 | price: 3, 539 | stack_height_required: 2, 540 | stack_height_change: -1, 541 | name: "OR", 542 | }; 543 | pub const ORIGIN: OpCode = OpCode { 544 | code: 0x32, 545 | price: 2, 546 | stack_height_required: 0, 547 | stack_height_change: 1, 548 | name: "ORIGIN", 549 | }; 550 | pub const PC: OpCode = OpCode { 551 | code: 0x58, 552 | price: 2, 553 | stack_height_required: 0, 554 | stack_height_change: 1, 555 | name: "PC", 556 | }; 557 | pub const POP: OpCode = OpCode { 558 | code: 0x50, 559 | price: 2, 560 | stack_height_required: 1, 561 | stack_height_change: -1, 562 | name: "POP", 563 | }; 564 | pub const PUSH1: OpCode = OpCode { 565 | code: 0x60, 566 | price: 3, 567 | stack_height_required: 0, 568 | stack_height_change: 1, 569 | name: "PUSH1", 570 | }; 571 | pub const PUSH10: OpCode = OpCode { 572 | code: 0x69, 573 | price: 3, 574 | stack_height_required: 0, 575 | stack_height_change: 1, 576 | name: "PUSH10", 577 | }; 578 | pub const PUSH11: OpCode = OpCode { 579 | code: 0x6a, 580 | price: 3, 581 | stack_height_required: 0, 582 | stack_height_change: 1, 583 | name: "PUSH11", 584 | }; 585 | pub const PUSH12: OpCode = OpCode { 586 | code: 0x6b, 587 | price: 3, 588 | stack_height_required: 0, 589 | stack_height_change: 1, 590 | name: "PUSH12", 591 | }; 592 | pub const PUSH13: OpCode = OpCode { 593 | code: 0x6c, 594 | price: 3, 595 | stack_height_required: 0, 596 | stack_height_change: 1, 597 | name: "PUSH13", 598 | }; 599 | pub const PUSH14: OpCode = OpCode { 600 | code: 0x6d, 601 | price: 3, 602 | stack_height_required: 0, 603 | stack_height_change: 1, 604 | name: "PUSH14", 605 | }; 606 | pub const PUSH15: OpCode = OpCode { 607 | code: 0x6e, 608 | price: 3, 609 | stack_height_required: 0, 610 | stack_height_change: 1, 611 | name: "PUSH15", 612 | }; 613 | pub const PUSH16: OpCode = OpCode { 614 | code: 0x6f, 615 | price: 3, 616 | stack_height_required: 0, 617 | stack_height_change: 1, 618 | name: "PUSH16", 619 | }; 620 | pub const PUSH17: OpCode = OpCode { 621 | code: 0x70, 622 | price: 3, 623 | stack_height_required: 0, 624 | stack_height_change: 1, 625 | name: "PUSH17", 626 | }; 627 | pub const PUSH18: OpCode = OpCode { 628 | code: 0x71, 629 | price: 3, 630 | stack_height_required: 0, 631 | stack_height_change: 1, 632 | name: "PUSH18", 633 | }; 634 | pub const PUSH19: OpCode = OpCode { 635 | code: 0x72, 636 | price: 3, 637 | stack_height_required: 0, 638 | stack_height_change: 1, 639 | name: "PUSH19", 640 | }; 641 | pub const PUSH2: OpCode = OpCode { 642 | code: 0x61, 643 | price: 3, 644 | stack_height_required: 0, 645 | stack_height_change: 1, 646 | name: "PUSH2", 647 | }; 648 | pub const PUSH20: OpCode = OpCode { 649 | code: 0x73, 650 | price: 3, 651 | stack_height_required: 0, 652 | stack_height_change: 1, 653 | name: "PUSH20", 654 | }; 655 | pub const PUSH21: OpCode = OpCode { 656 | code: 0x74, 657 | price: 3, 658 | stack_height_required: 0, 659 | stack_height_change: 1, 660 | name: "PUSH21", 661 | }; 662 | pub const PUSH22: OpCode = OpCode { 663 | code: 0x75, 664 | price: 3, 665 | stack_height_required: 0, 666 | stack_height_change: 1, 667 | name: "PUSH22", 668 | }; 669 | pub const PUSH23: OpCode = OpCode { 670 | code: 0x76, 671 | price: 3, 672 | stack_height_required: 0, 673 | stack_height_change: 1, 674 | name: "PUSH23", 675 | }; 676 | pub const PUSH24: OpCode = OpCode { 677 | code: 0x77, 678 | price: 3, 679 | stack_height_required: 0, 680 | stack_height_change: 1, 681 | name: "PUSH24", 682 | }; 683 | pub const PUSH25: OpCode = OpCode { 684 | code: 0x78, 685 | price: 3, 686 | stack_height_required: 0, 687 | stack_height_change: 1, 688 | name: "PUSH25", 689 | }; 690 | pub const PUSH26: OpCode = OpCode { 691 | code: 0x79, 692 | price: 3, 693 | stack_height_required: 0, 694 | stack_height_change: 1, 695 | name: "PUSH26", 696 | }; 697 | pub const PUSH27: OpCode = OpCode { 698 | code: 0x7a, 699 | price: 3, 700 | stack_height_required: 0, 701 | stack_height_change: 1, 702 | name: "PUSH27", 703 | }; 704 | pub const PUSH28: OpCode = OpCode { 705 | code: 0x7b, 706 | price: 3, 707 | stack_height_required: 0, 708 | stack_height_change: 1, 709 | name: "PUSH28", 710 | }; 711 | pub const PUSH29: OpCode = OpCode { 712 | code: 0x7c, 713 | price: 3, 714 | stack_height_required: 0, 715 | stack_height_change: 1, 716 | name: "PUSH29", 717 | }; 718 | pub const PUSH3: OpCode = OpCode { 719 | code: 0x62, 720 | price: 3, 721 | stack_height_required: 0, 722 | stack_height_change: 1, 723 | name: "PUSH3", 724 | }; 725 | pub const PUSH30: OpCode = OpCode { 726 | code: 0x7d, 727 | price: 3, 728 | stack_height_required: 0, 729 | stack_height_change: 1, 730 | name: "PUSH30", 731 | }; 732 | pub const PUSH31: OpCode = OpCode { 733 | code: 0x7e, 734 | price: 3, 735 | stack_height_required: 0, 736 | stack_height_change: 1, 737 | name: "PUSH31", 738 | }; 739 | pub const PUSH32: OpCode = OpCode { 740 | code: 0x7f, 741 | price: 3, 742 | stack_height_required: 0, 743 | stack_height_change: 1, 744 | name: "PUSH32", 745 | }; 746 | pub const PUSH4: OpCode = OpCode { 747 | code: 0x63, 748 | price: 3, 749 | stack_height_required: 0, 750 | stack_height_change: 1, 751 | name: "PUSH4", 752 | }; 753 | pub const PUSH5: OpCode = OpCode { 754 | code: 0x64, 755 | price: 3, 756 | stack_height_required: 0, 757 | stack_height_change: 1, 758 | name: "PUSH5", 759 | }; 760 | pub const PUSH6: OpCode = OpCode { 761 | code: 0x65, 762 | price: 3, 763 | stack_height_required: 0, 764 | stack_height_change: 1, 765 | name: "PUSH6", 766 | }; 767 | pub const PUSH7: OpCode = OpCode { 768 | code: 0x66, 769 | price: 3, 770 | stack_height_required: 0, 771 | stack_height_change: 1, 772 | name: "PUSH7", 773 | }; 774 | pub const PUSH8: OpCode = OpCode { 775 | code: 0x67, 776 | price: 3, 777 | stack_height_required: 0, 778 | stack_height_change: 1, 779 | name: "PUSH8", 780 | }; 781 | pub const PUSH9: OpCode = OpCode { 782 | code: 0x68, 783 | price: 3, 784 | stack_height_required: 0, 785 | stack_height_change: 1, 786 | name: "PUSH9", 787 | }; 788 | pub const RETURN: OpCode = OpCode { 789 | code: 0xf3, 790 | price: 0, 791 | stack_height_required: 2, 792 | stack_height_change: -2, 793 | name: "RETURN", 794 | }; 795 | pub const RETURNDATACOPY: OpCode = OpCode { 796 | code: 0x3e, 797 | price: 3, 798 | stack_height_required: 3, 799 | stack_height_change: -3, 800 | name: "RETURNDATACOPY", 801 | }; 802 | pub const RETURNDATASIZE: OpCode = OpCode { 803 | code: 0x3d, 804 | price: 2, 805 | stack_height_required: 0, 806 | stack_height_change: 1, 807 | name: "RETURNDATASIZE", 808 | }; 809 | pub const REVERT: OpCode = OpCode { 810 | code: 0xfd, 811 | price: 0, 812 | stack_height_required: 2, 813 | stack_height_change: -2, 814 | name: "REVERT", 815 | }; 816 | pub const SAR: OpCode = OpCode { 817 | code: 0x1d, 818 | price: 3, 819 | stack_height_required: 2, 820 | stack_height_change: -1, 821 | name: "SAR", 822 | }; 823 | pub const SDIV: OpCode = OpCode { 824 | code: 0x05, 825 | price: 5, 826 | stack_height_required: 2, 827 | stack_height_change: -1, 828 | name: "SDIV", 829 | }; 830 | pub const SELFBALANCE: OpCode = OpCode { 831 | code: 0x47, 832 | price: 5, 833 | stack_height_required: 0, 834 | stack_height_change: 1, 835 | name: "SELFBALANCE", 836 | }; 837 | pub const SELFDESTRUCT: OpCode = OpCode { 838 | code: 0xff, 839 | price: 5000, 840 | stack_height_required: 1, 841 | stack_height_change: -1, 842 | name: "SELFDESTRUCT", 843 | }; 844 | pub const SGT: OpCode = OpCode { 845 | code: 0x13, 846 | price: 3, 847 | stack_height_required: 2, 848 | stack_height_change: -1, 849 | name: "SGT", 850 | }; 851 | pub const SHL: OpCode = OpCode { 852 | code: 0x1b, 853 | price: 3, 854 | stack_height_required: 2, 855 | stack_height_change: -1, 856 | name: "SHL", 857 | }; 858 | pub const SHR: OpCode = OpCode { 859 | code: 0x1c, 860 | price: 3, 861 | stack_height_required: 2, 862 | stack_height_change: -1, 863 | name: "SHR", 864 | }; 865 | pub const SIGNEXTEND: OpCode = OpCode { 866 | code: 0x0b, 867 | price: 5, 868 | stack_height_required: 2, 869 | stack_height_change: -1, 870 | name: "SIGNEXTEND", 871 | }; 872 | pub const SLOAD: OpCode = OpCode { 873 | code: 0x54, 874 | price: WARM_STORAGE_READ_COST, 875 | stack_height_required: 1, 876 | stack_height_change: 0, 877 | name: "SLOAD", 878 | }; 879 | pub const SLT: OpCode = OpCode { 880 | code: 0x12, 881 | price: 3, 882 | stack_height_required: 2, 883 | stack_height_change: -1, 884 | name: "SLT", 885 | }; 886 | pub const SMOD: OpCode = OpCode { 887 | code: 0x07, 888 | price: 5, 889 | stack_height_required: 2, 890 | stack_height_change: -1, 891 | name: "SMOD", 892 | }; 893 | pub const SSTORE: OpCode = OpCode { 894 | code: 0x55, 895 | price: 0, 896 | stack_height_required: 2, 897 | stack_height_change: -2, 898 | name: "SSTORE", 899 | }; 900 | pub const STATICCALL: OpCode = OpCode { 901 | code: 0xfa, 902 | price: WARM_STORAGE_READ_COST, 903 | stack_height_required: 6, 904 | stack_height_change: -5, 905 | name: "STATICCALL", 906 | }; 907 | pub const STOP: OpCode = OpCode { 908 | code: 0x00, 909 | price: 0, 910 | stack_height_required: 0, 911 | stack_height_change: 0, 912 | name: "STOP", 913 | }; 914 | pub const SUB: OpCode = OpCode { 915 | code: 0x03, 916 | price: 3, 917 | stack_height_required: 2, 918 | stack_height_change: -1, 919 | name: "SUB", 920 | }; 921 | pub const SWAP1: OpCode = OpCode { 922 | code: 0x90, 923 | price: 3, 924 | stack_height_required: 2, 925 | stack_height_change: 0, 926 | name: "SWAP1", 927 | }; 928 | pub const SWAP10: OpCode = OpCode { 929 | code: 0x99, 930 | price: 3, 931 | stack_height_required: 11, 932 | stack_height_change: 0, 933 | name: "SWAP10", 934 | }; 935 | pub const SWAP11: OpCode = OpCode { 936 | code: 0x9a, 937 | price: 3, 938 | stack_height_required: 12, 939 | stack_height_change: 0, 940 | name: "SWAP11", 941 | }; 942 | pub const SWAP12: OpCode = OpCode { 943 | code: 0x9b, 944 | price: 3, 945 | stack_height_required: 13, 946 | stack_height_change: 0, 947 | name: "SWAP12", 948 | }; 949 | pub const SWAP13: OpCode = OpCode { 950 | code: 0x9c, 951 | price: 3, 952 | stack_height_required: 14, 953 | stack_height_change: 0, 954 | name: "SWAP13", 955 | }; 956 | pub const SWAP14: OpCode = OpCode { 957 | code: 0x9d, 958 | price: 3, 959 | stack_height_required: 15, 960 | stack_height_change: 0, 961 | name: "SWAP14", 962 | }; 963 | pub const SWAP15: OpCode = OpCode { 964 | code: 0x9e, 965 | price: 3, 966 | stack_height_required: 16, 967 | stack_height_change: 0, 968 | name: "SWAP15", 969 | }; 970 | pub const SWAP16: OpCode = OpCode { 971 | code: 0x9f, 972 | price: 3, 973 | stack_height_required: 17, 974 | stack_height_change: 0, 975 | name: "SWAP16", 976 | }; 977 | pub const SWAP2: OpCode = OpCode { 978 | code: 0x91, 979 | price: 3, 980 | stack_height_required: 3, 981 | stack_height_change: 0, 982 | name: "SWAP2", 983 | }; 984 | pub const SWAP3: OpCode = OpCode { 985 | code: 0x92, 986 | price: 3, 987 | stack_height_required: 4, 988 | stack_height_change: 0, 989 | name: "SWAP3", 990 | }; 991 | pub const SWAP4: OpCode = OpCode { 992 | code: 0x93, 993 | price: 3, 994 | stack_height_required: 5, 995 | stack_height_change: 0, 996 | name: "SWAP4", 997 | }; 998 | pub const SWAP5: OpCode = OpCode { 999 | code: 0x94, 1000 | price: 3, 1001 | stack_height_required: 6, 1002 | stack_height_change: 0, 1003 | name: "SWAP5", 1004 | }; 1005 | pub const SWAP6: OpCode = OpCode { 1006 | code: 0x95, 1007 | price: 3, 1008 | stack_height_required: 7, 1009 | stack_height_change: 0, 1010 | name: "SWAP6", 1011 | }; 1012 | pub const SWAP7: OpCode = OpCode { 1013 | code: 0x96, 1014 | price: 3, 1015 | stack_height_required: 8, 1016 | stack_height_change: 0, 1017 | name: "SWAP7", 1018 | }; 1019 | pub const SWAP8: OpCode = OpCode { 1020 | code: 0x97, 1021 | price: 3, 1022 | stack_height_required: 9, 1023 | stack_height_change: 0, 1024 | name: "SWAP8", 1025 | }; 1026 | pub const SWAP9: OpCode = OpCode { 1027 | code: 0x98, 1028 | price: 3, 1029 | stack_height_required: 10, 1030 | stack_height_change: 0, 1031 | name: "SWAP9", 1032 | }; 1033 | pub const TIMESTAMP: OpCode = OpCode { 1034 | code: 0x42, 1035 | price: 2, 1036 | stack_height_required: 0, 1037 | stack_height_change: 1, 1038 | name: "TIMESTAMP", 1039 | }; 1040 | pub const XOR: OpCode = OpCode { 1041 | code: 0x18, 1042 | price: 3, 1043 | stack_height_required: 2, 1044 | stack_height_change: -1, 1045 | name: "XOR", 1046 | }; 1047 | } 1048 | 1049 | impl std::fmt::Display for OpCode { 1050 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 1051 | write!(f, "{}", self.name) 1052 | } 1053 | } 1054 | 1055 | impl TryFrom for OpCode { 1056 | type Error = StatusCode; 1057 | 1058 | fn try_from(value: u8) -> Result { 1059 | // todo: optimize and turn it into a jump table 1060 | const OPCODES: [OpCode; 143] = [ 1061 | OpCode::STOP, 1062 | OpCode::ADD, 1063 | OpCode::MUL, 1064 | OpCode::SUB, 1065 | OpCode::DIV, 1066 | OpCode::SDIV, 1067 | OpCode::MOD, 1068 | OpCode::SMOD, 1069 | OpCode::ADDMOD, 1070 | OpCode::MULMOD, 1071 | OpCode::EXP, 1072 | OpCode::SIGNEXTEND, 1073 | OpCode::LT, 1074 | OpCode::GT, 1075 | OpCode::SLT, 1076 | OpCode::SGT, 1077 | OpCode::EQ, 1078 | OpCode::ISZERO, 1079 | OpCode::AND, 1080 | OpCode::OR, 1081 | OpCode::XOR, 1082 | OpCode::NOT, 1083 | OpCode::BYTE, 1084 | OpCode::SHL, 1085 | OpCode::SHR, 1086 | OpCode::SAR, 1087 | OpCode::KECCAK256, 1088 | OpCode::ADDRESS, 1089 | OpCode::BALANCE, 1090 | OpCode::ORIGIN, 1091 | OpCode::CALLER, 1092 | OpCode::CALLVALUE, 1093 | OpCode::CALLDATALOAD, 1094 | OpCode::CALLDATASIZE, 1095 | OpCode::CALLDATACOPY, 1096 | OpCode::CODESIZE, 1097 | OpCode::CODECOPY, 1098 | OpCode::GASPRICE, 1099 | OpCode::EXTCODESIZE, 1100 | OpCode::EXTCODECOPY, 1101 | OpCode::RETURNDATASIZE, 1102 | OpCode::RETURNDATACOPY, 1103 | OpCode::EXTCODEHASH, 1104 | OpCode::BLOCKHASH, 1105 | OpCode::COINBASE, 1106 | OpCode::TIMESTAMP, 1107 | OpCode::NUMBER, 1108 | OpCode::DIFFICULTY, 1109 | OpCode::GASLIMIT, 1110 | OpCode::CHAINID, 1111 | OpCode::SELFBALANCE, 1112 | OpCode::BASEFEE, 1113 | OpCode::POP, 1114 | OpCode::MLOAD, 1115 | OpCode::MSTORE, 1116 | OpCode::MSTORE8, 1117 | OpCode::SLOAD, 1118 | OpCode::SSTORE, 1119 | OpCode::JUMP, 1120 | OpCode::JUMPI, 1121 | OpCode::PC, 1122 | OpCode::MSIZE, 1123 | OpCode::GAS, 1124 | OpCode::JUMPDEST, 1125 | OpCode::PUSH1, 1126 | OpCode::PUSH2, 1127 | OpCode::PUSH3, 1128 | OpCode::PUSH4, 1129 | OpCode::PUSH5, 1130 | OpCode::PUSH6, 1131 | OpCode::PUSH7, 1132 | OpCode::PUSH8, 1133 | OpCode::PUSH9, 1134 | OpCode::PUSH10, 1135 | OpCode::PUSH11, 1136 | OpCode::PUSH12, 1137 | OpCode::PUSH13, 1138 | OpCode::PUSH14, 1139 | OpCode::PUSH15, 1140 | OpCode::PUSH16, 1141 | OpCode::PUSH17, 1142 | OpCode::PUSH18, 1143 | OpCode::PUSH19, 1144 | OpCode::PUSH20, 1145 | OpCode::PUSH21, 1146 | OpCode::PUSH22, 1147 | OpCode::PUSH23, 1148 | OpCode::PUSH24, 1149 | OpCode::PUSH25, 1150 | OpCode::PUSH26, 1151 | OpCode::PUSH27, 1152 | OpCode::PUSH28, 1153 | OpCode::PUSH29, 1154 | OpCode::PUSH30, 1155 | OpCode::PUSH31, 1156 | OpCode::PUSH32, 1157 | OpCode::DUP1, 1158 | OpCode::DUP2, 1159 | OpCode::DUP3, 1160 | OpCode::DUP4, 1161 | OpCode::DUP5, 1162 | OpCode::DUP6, 1163 | OpCode::DUP7, 1164 | OpCode::DUP8, 1165 | OpCode::DUP9, 1166 | OpCode::DUP10, 1167 | OpCode::DUP11, 1168 | OpCode::DUP12, 1169 | OpCode::DUP13, 1170 | OpCode::DUP14, 1171 | OpCode::DUP15, 1172 | OpCode::DUP16, 1173 | OpCode::SWAP1, 1174 | OpCode::SWAP2, 1175 | OpCode::SWAP3, 1176 | OpCode::SWAP4, 1177 | OpCode::SWAP5, 1178 | OpCode::SWAP6, 1179 | OpCode::SWAP7, 1180 | OpCode::SWAP8, 1181 | OpCode::SWAP9, 1182 | OpCode::SWAP10, 1183 | OpCode::SWAP11, 1184 | OpCode::SWAP12, 1185 | OpCode::SWAP13, 1186 | OpCode::SWAP14, 1187 | OpCode::SWAP15, 1188 | OpCode::SWAP16, 1189 | OpCode::LOG0, 1190 | OpCode::LOG1, 1191 | OpCode::LOG2, 1192 | OpCode::LOG3, 1193 | OpCode::LOG4, 1194 | OpCode::CREATE, 1195 | OpCode::CALL, 1196 | OpCode::CALLCODE, 1197 | OpCode::RETURN, 1198 | OpCode::DELEGATECALL, 1199 | OpCode::CREATE2, 1200 | OpCode::STATICCALL, 1201 | OpCode::REVERT, 1202 | OpCode::INVALID, 1203 | OpCode::SELFDESTRUCT, 1204 | ]; 1205 | 1206 | for op in OPCODES { 1207 | if op == value { 1208 | return Ok(op); 1209 | } 1210 | } 1211 | 1212 | Err(StatusCode::UndefinedInstruction) 1213 | } 1214 | } 1215 | -------------------------------------------------------------------------------- /shared/src/output.rs: -------------------------------------------------------------------------------- 1 | use { 2 | bytes::Bytes, 3 | fvm_ipld_encoding::Cbor, 4 | serde::{Deserialize, Serialize}, 5 | strum_macros::Display, 6 | }; 7 | 8 | /// Output of EVM execution. 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub struct Output { 11 | /// EVM exited with this status code. 12 | pub status_code: StatusCode, 13 | /// How much gas was left after execution 14 | pub gas_left: i64, 15 | /// Output data returned. 16 | pub output_data: Bytes, 17 | // indicates if revert was requested 18 | pub reverted: bool, 19 | } 20 | 21 | /// Message status code. 22 | #[must_use] 23 | #[derive(Clone, Debug, Display, PartialEq, Serialize, Deserialize)] 24 | pub enum StatusCode { 25 | /// Execution finished with success. 26 | #[strum(serialize = "success")] 27 | Success, 28 | 29 | /// Generic execution failure. 30 | #[strum(serialize = "failure")] 31 | Failure, 32 | 33 | /// Execution terminated with REVERT opcode. 34 | #[strum(serialize = "revert")] 35 | Revert, 36 | 37 | /// The execution has run out of gas. 38 | #[strum(serialize = "out of gas")] 39 | OutOfGas, 40 | 41 | /// The designated INVALID instruction has been hit during execution. 42 | /// 43 | /// [EIP-141](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-141.md) 44 | /// defines the instruction 0xfe as INVALID instruction to indicate execution 45 | /// abortion coming from high-level languages. This status code is reported 46 | /// in case this INVALID instruction has been encountered. 47 | #[strum(serialize = "invalid instruction")] 48 | InvalidInstruction, 49 | 50 | /// An undefined instruction has been encountered. 51 | #[strum(serialize = "undefined instruction")] 52 | UndefinedInstruction, 53 | 54 | /// The execution has attempted to put more items on the EVM stack 55 | /// than the specified limit. 56 | #[strum(serialize = "stack overflow")] 57 | StackOverflow, 58 | 59 | /// Execution of an opcode has required more items on the EVM stack. 60 | #[strum(serialize = "stack underflow")] 61 | StackUnderflow, 62 | 63 | /// Execution has violated the jump destination restrictions. 64 | #[strum(serialize = "bad jump destination")] 65 | BadJumpDestination, 66 | 67 | /// Tried to read outside memory bounds. 68 | /// 69 | /// An example is RETURNDATACOPY reading past the available buffer. 70 | #[strum(serialize = "invalid memory access")] 71 | InvalidMemoryAccess, 72 | 73 | /// Call depth has exceeded the limit (if any) 74 | #[strum(serialize = "call depth exceeded")] 75 | CallDepthExceeded, 76 | 77 | /// Tried to execute an operation which is restricted in static mode. 78 | #[strum(serialize = "static mode violation")] 79 | StaticModeViolation, 80 | 81 | /// A call to a precompiled or system contract has ended with a failure. 82 | /// 83 | /// An example: elliptic curve functions handed invalid EC points. 84 | #[strum(serialize = "precompile failure")] 85 | PrecompileFailure, 86 | 87 | /// Contract validation has failed. 88 | #[strum(serialize = "contract validation failure")] 89 | ContractValidationFailure, 90 | 91 | /// An argument to a state accessing method has a value outside of the 92 | /// accepted range of values. 93 | #[strum(serialize = "argument out of range")] 94 | ArgumentOutOfRange, 95 | 96 | /// The caller does not have enough funds for value transfer. 97 | #[strum(serialize = "insufficient balance")] 98 | InsufficientBalance, 99 | 100 | /// EVM implementation generic internal error. 101 | #[strum(serialize = "internal error")] 102 | InternalError(String), 103 | } 104 | 105 | impl Cbor for StatusCode {} 106 | -------------------------------------------------------------------------------- /shared/src/stack.rs: -------------------------------------------------------------------------------- 1 | use {arrayvec::ArrayVec, crate::U256, serde::Serialize}; 2 | 3 | /// Ethereum Yellow Paper (9.1) 4 | pub const MAX_STACK_SIZE: usize = 1024; 5 | 6 | /// EVM stack. 7 | #[derive(Clone, Debug, Default, Serialize)] 8 | pub struct Stack(pub ArrayVec); 9 | 10 | impl Stack { 11 | #[inline] 12 | pub const fn new() -> Self { 13 | Self(ArrayVec::new_const()) 14 | } 15 | 16 | #[inline] 17 | const fn get_pos(&self, pos: usize) -> usize { 18 | self.len() - 1 - pos 19 | } 20 | 21 | #[inline] 22 | pub fn get(&self, pos: usize) -> &U256 { 23 | let pos = self.get_pos(pos); 24 | self.0.get(pos).unwrap() 25 | } 26 | 27 | #[inline] 28 | pub fn get_mut(&mut self, pos: usize) -> &mut U256 { 29 | let pos = self.get_pos(pos); 30 | self.0.get_mut(pos).unwrap() 31 | } 32 | 33 | #[inline(always)] 34 | pub const fn len(&self) -> usize { 35 | self.0.len() 36 | } 37 | 38 | #[inline(always)] 39 | pub fn is_empty(&self) -> bool { 40 | self.len() == 0 41 | } 42 | 43 | #[inline] 44 | pub fn push(&mut self, v: U256) { 45 | self.0.push(v) 46 | } 47 | 48 | #[inline] 49 | pub fn pop(&mut self) -> U256 { 50 | self.0.pop().unwrap() 51 | } 52 | 53 | #[inline] 54 | pub fn swap_top(&mut self, pos: usize) { 55 | let top = self.0.len() - 1; 56 | let pos = self.get_pos(pos); 57 | self.0.swap(top, pos); 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use super::*; 64 | 65 | #[test] 66 | fn stack() { 67 | let mut stack = Stack::default(); 68 | 69 | let items: [u128; 4] = [0xde, 0xad, 0xbe, 0xef]; 70 | 71 | for (i, item) in items.iter().copied().enumerate() { 72 | stack.push(item.into()); 73 | assert_eq!(stack.len(), i + 1); 74 | } 75 | 76 | assert_eq!(*stack.get(2), U256::from(0xad)); 77 | assert_eq!(stack.pop(), U256::from(0xef)); 78 | assert_eq!(*stack.get(2), U256::from(0xde)); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /shared/src/system.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{message::Message, output::StatusCode, Output, SignedTransaction, H160, U256}, 3 | bytes::Bytes, 4 | cid::Cid, 5 | fil_actors_runtime::{runtime::Runtime, ActorError}, 6 | fvm_ipld_blockstore::Blockstore, 7 | fvm_ipld_hamt::Hamt, 8 | fvm_shared::address::Address, 9 | std::{cell::RefCell, collections::HashSet}, 10 | }; 11 | 12 | /// Info sourced from the current transaction and block 13 | #[derive(Clone, Debug)] 14 | pub struct TransactionContext { 15 | /// The transaction gas price. 16 | pub tx_gas_price: U256, 17 | /// The transaction origin account. 18 | pub tx_origin: H160, 19 | /// The miner of the block. 20 | pub block_coinbase: H160, 21 | /// The block number. 22 | pub block_number: u64, 23 | /// The block timestamp. 24 | pub block_timestamp: u64, 25 | /// The block gas limit. 26 | pub block_gas_limit: u64, 27 | /// The block difficulty. 28 | pub block_difficulty: U256, 29 | /// The blockchain's ChainID. 30 | pub chain_id: U256, 31 | /// The block base fee per gas (EIP-1559, EIP-3198). 32 | pub block_base_fee: U256, 33 | } 34 | 35 | /// State access status (EIP-2929). 36 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 37 | pub enum AccessStatus { 38 | Cold, 39 | Warm, 40 | } 41 | 42 | impl Default for AccessStatus { 43 | fn default() -> Self { 44 | Self::Cold 45 | } 46 | } 47 | 48 | #[derive(Clone, Copy, Debug)] 49 | pub enum StorageStatus { 50 | /// The value of a storage item has been left unchanged: 0 -> 0 and X -> X. 51 | Unchanged, 52 | /// The value of a storage item has been modified: X -> Y. 53 | Modified, 54 | /// A storage item has been modified after being modified before: X -> Y -> Z. 55 | ModifiedAgain, 56 | /// A new storage item has been added: 0 -> X. 57 | Added, 58 | /// A storage item has been deleted: X -> 0. 59 | Deleted, 60 | } 61 | 62 | #[derive(Clone, Debug, PartialEq)] 63 | pub enum Call<'a> { 64 | Call(&'a Message), 65 | Create(&'a Message), 66 | } 67 | 68 | /// Platform Abstraction Layer 69 | /// that bridges the FVM world to EVM world 70 | pub struct System<'r, BS: Blockstore> { 71 | state: RefCell>, 72 | access_list: RefCell>, 73 | _bridge: Address, 74 | self_address: H160, 75 | context: TransactionContext, 76 | } 77 | 78 | impl<'r, BS: Blockstore> System<'r, BS> { 79 | pub fn new>( 80 | state_cid: Cid, 81 | runtime: &'r RT, 82 | bridge: Address, 83 | self_address: H160, 84 | tx: &SignedTransaction, 85 | ) -> anyhow::Result { 86 | Ok(Self { 87 | context: TransactionContext { 88 | tx_gas_price: tx.gas_price(), 89 | tx_origin: tx.sender_address()?, 90 | block_coinbase: H160::zero(), // todo 91 | block_number: 0, // todo 92 | block_timestamp: 0, // todo 93 | block_gas_limit: 30000000, // todo 94 | block_difficulty: U256::zero(), // todo 95 | chain_id: tx.chain_id().unwrap_or_default().into(), 96 | block_base_fee: U256::zero(), // todo 97 | }, 98 | _bridge: bridge, 99 | self_address, 100 | access_list: RefCell::new(HashSet::new()), 101 | state: RefCell::new(Hamt::load(&state_cid, runtime.store())?), 102 | }) 103 | } 104 | } 105 | 106 | impl<'r, BS: Blockstore> System<'r, BS> { 107 | pub fn flush_state(&self) -> Result { 108 | self 109 | .state 110 | .borrow_mut() 111 | .flush() 112 | .map_err(|e| ActorError::illegal_state(e.to_string())) 113 | } 114 | 115 | /// Check if an account exists. 116 | pub fn account_exists(&self, _address: H160) -> bool { 117 | todo!() 118 | } 119 | 120 | /// Get value of a storage key. 121 | /// 122 | /// Returns `Ok(U256::zero())` if does not exist. 123 | pub fn get_storage(&self, _address: H160, _key: U256) -> U256 { 124 | todo!(); 125 | } 126 | 127 | /// Set value of a storage key. 128 | pub fn set_storage( 129 | &self, 130 | address: H160, 131 | key: U256, 132 | value: U256, 133 | ) -> Result { 134 | fvm_sdk::debug::log(format!( 135 | "setting storage for {address:?} @ {key} to {value}" 136 | )); 137 | if address == self.self_address { 138 | let mut storage_status = StorageStatus::Added; 139 | let prev_value = self 140 | .state 141 | .borrow() 142 | .get(&key) 143 | .map_err(|e| StatusCode::InternalError(e.to_string()))? 144 | .cloned(); 145 | 146 | if let Some(v) = prev_value { 147 | if v == value { 148 | storage_status = StorageStatus::Unchanged; 149 | } else { 150 | storage_status = StorageStatus::Modified; 151 | } 152 | } 153 | 154 | if value == U256::zero() { 155 | self 156 | .state 157 | .borrow_mut() 158 | .delete(&key) 159 | .map_err(|e| StatusCode::InternalError(e.to_string()))?; 160 | storage_status = StorageStatus::Deleted; 161 | } else { 162 | self 163 | .state 164 | .borrow_mut() 165 | .set(key, value) 166 | .map_err(|e| StatusCode::InternalError(e.to_string()))?; 167 | } 168 | 169 | Ok(storage_status) 170 | } else { 171 | unimplemented!("setting storage across contracts is not supported yet") 172 | } 173 | } 174 | 175 | /// Get balance of an account. 176 | /// 177 | /// Returns `Ok(0)` if account does not exist. 178 | pub fn get_balance(&self, _address: H160) -> U256 { 179 | todo!() 180 | } 181 | 182 | /// Get code size of an account. 183 | /// 184 | /// Returns `Ok(0)` if account does not exist. 185 | pub fn get_code_size(&self, _address: H160) -> U256 { 186 | todo!() 187 | } 188 | 189 | /// Get code hash of an account. 190 | /// 191 | /// Returns `Ok(0)` if account does not exist. 192 | pub fn get_code_hash(&self, _address: H160) -> U256 { 193 | todo!(); 194 | } 195 | 196 | /// Copy code of an account. 197 | /// 198 | /// Returns `Ok(0)` if offset is invalid. 199 | pub fn copy_code(&self, _address: H160, _offset: usize, _buffer: &mut [u8]) -> usize { 200 | todo!() 201 | } 202 | 203 | /// Self-destruct account. 204 | pub fn selfdestruct(&self, _address: H160, _beneficiary: H160) { 205 | todo!() 206 | } 207 | 208 | /// Call to another account. 209 | pub fn call(&self, _msg: Call) -> Output { 210 | todo!(); 211 | } 212 | 213 | /// Get block hash. 214 | /// 215 | /// Returns `Ok(U256::zero())` if block does not exist. 216 | pub fn get_block_hash(&self, _block_number: u64) -> U256 { 217 | todo!(); 218 | } 219 | 220 | /// Emit a log. 221 | pub fn emit_log(&self, _address: H160, _data: Bytes, _topics: &[U256]) { 222 | todo!(); 223 | } 224 | 225 | /// Mark account as warm, return previous access status. 226 | /// 227 | /// Returns `Ok(AccessStatus::Cold)` if account does not exist. 228 | pub fn access_account(&self, _address: H160) -> AccessStatus { 229 | todo!(); 230 | } 231 | 232 | /// Mark storage key as warm, return previous access status. 233 | /// 234 | /// Returns `Ok(AccessStatus::Cold)` if account does not exist. 235 | pub fn access_storage(&self, address: H160, key: U256) -> AccessStatus { 236 | if address == self.self_address { 237 | if self.access_list.borrow().contains(&key) { 238 | AccessStatus::Warm 239 | } else { 240 | self.access_list.borrow_mut().insert(key); 241 | AccessStatus::Cold 242 | } 243 | } else { 244 | unimplemented!("cross-contract storage access is not supported yet"); 245 | } 246 | } 247 | 248 | /// Return context information about the current transaction and current block 249 | pub fn transaction_context(&self) -> &TransactionContext { 250 | &self.context 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /shared/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{H160, H256, U256}, 3 | bytes::Bytes, 4 | fil_actors_runtime::ActorError, 5 | fvm_shared::crypto::signature::SECP_PUB_LEN, 6 | rlp::{DecoderError, Rlp, RlpStream}, 7 | sha3::{Digest, Keccak256}, 8 | std::{fmt::Debug, ops::Deref}, 9 | }; 10 | 11 | #[derive(Debug, PartialEq, Eq, Clone)] 12 | pub enum TransactionAction { 13 | Call(H160), 14 | Create, 15 | } 16 | 17 | #[derive(Debug, PartialEq, Eq)] 18 | pub struct AccessListItem { 19 | pub address: H160, 20 | pub slots: Vec, 21 | } 22 | 23 | pub enum Transaction { 24 | Legacy { 25 | chain_id: Option, 26 | nonce: u64, 27 | gas_price: U256, 28 | gas_limit: u64, 29 | action: TransactionAction, 30 | value: U256, 31 | input: Bytes, 32 | }, 33 | EIP2930 { 34 | chain_id: u64, 35 | nonce: u64, 36 | gas_price: U256, 37 | gas_limit: u64, 38 | action: TransactionAction, 39 | value: U256, 40 | input: Bytes, 41 | access_list: Vec, 42 | }, 43 | EIP1559 { 44 | chain_id: u64, 45 | nonce: u64, 46 | max_priority_fee_per_gas: U256, 47 | max_fee_per_gas: U256, 48 | gas_limit: u64, 49 | action: TransactionAction, 50 | value: U256, 51 | input: Bytes, 52 | access_list: Vec, 53 | }, 54 | } 55 | 56 | #[derive(Debug)] 57 | pub struct TransactionRecoveryId(pub u64); 58 | 59 | #[derive(Debug)] 60 | pub struct TransactionSignature { 61 | pub v: TransactionRecoveryId, 62 | pub r: H256, 63 | pub s: H256, 64 | } 65 | 66 | #[derive(Debug)] 67 | pub struct SignedTransaction { 68 | pub transaction: Transaction, 69 | pub signature: TransactionSignature, 70 | } 71 | 72 | impl rlp::Encodable for TransactionAction { 73 | fn rlp_append(&self, s: &mut RlpStream) { 74 | match self { 75 | Self::Call(address) => { 76 | s.encoder().encode_value(&address[..]); 77 | } 78 | Self::Create => s.encoder().encode_value(&[]), 79 | } 80 | } 81 | } 82 | 83 | impl rlp::Decodable for TransactionAction { 84 | fn decode(rlp: &Rlp) -> Result { 85 | if rlp.is_empty() { 86 | if rlp.is_data() { 87 | Ok(TransactionAction::Create) 88 | } else { 89 | Err(DecoderError::RlpExpectedToBeData) 90 | } 91 | } else { 92 | Ok(TransactionAction::Call(rlp.as_val()?)) 93 | } 94 | } 95 | } 96 | 97 | impl rlp::Encodable for AccessListItem { 98 | fn rlp_append(&self, s: &mut RlpStream) { 99 | s.begin_list(2); 100 | s.append(&self.address); 101 | s.append_list(&self.slots); 102 | } 103 | } 104 | 105 | impl rlp::Decodable for AccessListItem { 106 | fn decode(rlp: &Rlp) -> Result { 107 | Ok(Self { 108 | address: rlp.val_at(0)?, 109 | slots: rlp.list_at(1)?, 110 | }) 111 | } 112 | } 113 | 114 | impl Transaction { 115 | /// Calculates the hash of the transaction fields without the signature. 116 | /// This value is the input to the signing function and the signature 117 | /// is caluculated over this hash and the sender private key. 118 | pub fn hash(&self) -> H256 { 119 | let mut s = RlpStream::new(); 120 | match self { 121 | Transaction::Legacy { 122 | chain_id, 123 | nonce, 124 | gas_price, 125 | gas_limit, 126 | action, 127 | value, 128 | input, 129 | } => { 130 | if let Some(chain_id) = chain_id { 131 | s.begin_list(9); 132 | s.append(nonce); 133 | s.append(gas_price); 134 | s.append(gas_limit); 135 | s.append(action); 136 | s.append(value); 137 | s.append(input); 138 | s.append(chain_id); 139 | s.append(&0_u8); 140 | s.append(&0_u8); 141 | } else { 142 | s.begin_list(6); 143 | s.append(nonce); 144 | s.append(gas_limit); 145 | s.append(gas_limit); 146 | s.append(action); 147 | s.append(value); 148 | s.append(input); 149 | } 150 | } 151 | Transaction::EIP2930 { 152 | chain_id, 153 | nonce, 154 | gas_price, 155 | gas_limit, 156 | action, 157 | value, 158 | input, 159 | access_list, 160 | } => { 161 | s.append_raw(&[1u8], 0); 162 | s.begin_list(8); 163 | s.append(chain_id); 164 | s.append(nonce); 165 | s.append(gas_price); 166 | s.append(gas_limit); 167 | s.append(action); 168 | s.append(value); 169 | s.append(input); 170 | s.append_list(access_list); 171 | } 172 | Transaction::EIP1559 { 173 | chain_id, 174 | nonce, 175 | max_priority_fee_per_gas, 176 | max_fee_per_gas, 177 | gas_limit, 178 | action, 179 | value, 180 | input, 181 | access_list, 182 | } => { 183 | s.append_raw(&[2u8], 0); 184 | s.begin_list(9); 185 | s.append(chain_id); 186 | s.append(nonce); 187 | s.append(max_priority_fee_per_gas); 188 | s.append(max_fee_per_gas); 189 | s.append(gas_limit); 190 | s.append(action); 191 | s.append(value); 192 | s.append(input); 193 | s.append_list(access_list); 194 | } 195 | }; 196 | 197 | H256::from_slice(Keccak256::digest(s.as_raw()).as_slice()) 198 | } 199 | 200 | pub fn nonce(&self) -> u64 { 201 | *match self { 202 | Transaction::Legacy { nonce, .. } => nonce, 203 | Transaction::EIP2930 { nonce, .. } => nonce, 204 | Transaction::EIP1559 { nonce, .. } => nonce, 205 | } 206 | } 207 | 208 | pub fn chain_id(&self) -> Option { 209 | match self { 210 | Transaction::Legacy { chain_id, .. } => *chain_id, 211 | Transaction::EIP2930 { chain_id, .. } => Some(*chain_id), 212 | Transaction::EIP1559 { chain_id, .. } => Some(*chain_id), 213 | } 214 | } 215 | 216 | pub fn gas_price(&self) -> U256 { 217 | *match self { 218 | Transaction::Legacy { gas_price, .. } => gas_price, 219 | Transaction::EIP2930 { gas_price, .. } => gas_price, 220 | Transaction::EIP1559 { 221 | max_fee_per_gas, .. 222 | } => max_fee_per_gas, 223 | } 224 | } 225 | 226 | pub fn gas_limit(&self) -> u64 { 227 | *match self { 228 | Transaction::Legacy { gas_limit, .. } => gas_limit, 229 | Transaction::EIP2930 { gas_limit, .. } => gas_limit, 230 | Transaction::EIP1559 { gas_limit, .. } => gas_limit, 231 | } 232 | } 233 | 234 | pub fn action(&self) -> TransactionAction { 235 | match self { 236 | Transaction::Legacy { action, .. } => action, 237 | Transaction::EIP2930 { action, .. } => action, 238 | Transaction::EIP1559 { action, .. } => action, 239 | } 240 | .clone() 241 | } 242 | 243 | pub fn input(&self) -> Bytes { 244 | match self { 245 | Transaction::Legacy { input, .. } => input, 246 | Transaction::EIP2930 { input, .. } => input, 247 | Transaction::EIP1559 { input, .. } => input, 248 | } 249 | .clone() 250 | } 251 | 252 | pub fn value(&self) -> U256 { 253 | *match self { 254 | Transaction::Legacy { value, .. } => value, 255 | Transaction::EIP2930 { value, .. } => value, 256 | Transaction::EIP1559 { value, .. } => value, 257 | } 258 | } 259 | } 260 | 261 | impl Deref for TransactionRecoveryId { 262 | type Target = u64; 263 | 264 | fn deref(&self) -> &u64 { 265 | &self.0 266 | } 267 | } 268 | 269 | impl TransactionRecoveryId { 270 | pub fn odd_y_parity(&self) -> u8 { 271 | if self.0 == 27 || self.0 == 28 || self.0 > 36 { 272 | ((self.0 - 1) % 2) as u8 273 | } else { 274 | 4 275 | } 276 | } 277 | 278 | pub fn chain_id(&self) -> Option { 279 | if self.0 > 36 { 280 | Some((self.0 - 35) / 2) 281 | } else { 282 | None 283 | } 284 | } 285 | } 286 | 287 | impl TryFrom<&[u8]> for SignedTransaction { 288 | type Error = DecoderError; 289 | 290 | fn try_from(value: &[u8]) -> Result { 291 | if value.is_empty() { 292 | return Err(DecoderError::RlpIsTooShort); 293 | } 294 | 295 | match value[0] { 296 | 0x01 => parse_eip2930_transaction(value), 297 | 0x02 => parse_eip1559_transaction(value), 298 | _ => parse_legacy_transaction(value), 299 | } 300 | } 301 | } 302 | 303 | impl Deref for SignedTransaction { 304 | type Target = Transaction; 305 | 306 | fn deref(&self) -> &Self::Target { 307 | &self.transaction 308 | } 309 | } 310 | 311 | impl SignedTransaction { 312 | /// Creates RLP serialized representation of the transaction. 313 | /// This value is the input to the hash function that is used 314 | /// to calculate the final transaction hash as it appears on 315 | /// blockchain explorers. This representation can be sent directly 316 | /// to ETH nodes and to the FVM-EVM bridge 317 | pub fn serialize(&self) -> Vec { 318 | let mut s = RlpStream::new(); 319 | match &self.transaction { 320 | Transaction::Legacy { 321 | nonce, 322 | gas_price, 323 | gas_limit, 324 | action, 325 | value, 326 | input, 327 | .. 328 | } => { 329 | s.begin_list(9); 330 | s.append(nonce); 331 | s.append(gas_price); 332 | s.append(gas_limit); 333 | s.append(action); 334 | s.append(value); 335 | s.append(input); 336 | s.append(&self.signature.v.0); 337 | s.append(&self.signature.r); 338 | s.append(&self.signature.s); 339 | } 340 | Transaction::EIP2930 { 341 | chain_id, 342 | nonce, 343 | gas_price, 344 | gas_limit, 345 | action, 346 | value, 347 | input, 348 | access_list, 349 | } => { 350 | s.append_raw(&[1u8], 0); 351 | s.begin_list(11); 352 | s.append(chain_id); 353 | s.append(nonce); 354 | s.append(gas_price); 355 | s.append(gas_limit); 356 | s.append(action); 357 | s.append(value); 358 | s.append(input); 359 | s.append_list(access_list); 360 | s.append(&self.signature.v.0); 361 | s.append(&self.signature.r); 362 | s.append(&self.signature.s); 363 | } 364 | Transaction::EIP1559 { 365 | chain_id, 366 | nonce, 367 | max_priority_fee_per_gas, 368 | max_fee_per_gas, 369 | gas_limit, 370 | action, 371 | value, 372 | input, 373 | access_list, 374 | } => { 375 | s.append_raw(&[2u8], 0); 376 | s.begin_list(12); 377 | s.append(chain_id); 378 | s.append(nonce); 379 | s.append(max_priority_fee_per_gas); 380 | s.append(max_fee_per_gas); 381 | s.append(gas_limit); 382 | s.append(action); 383 | s.append(value); 384 | s.append(input); 385 | s.append_list(access_list); 386 | s.append(&self.signature.v.0); 387 | s.append(&self.signature.r); 388 | s.append(&self.signature.s); 389 | } 390 | }; 391 | s.as_raw().to_vec() 392 | } 393 | 394 | pub fn hash(&self) -> H256 { 395 | H256::from_slice(Keccak256::digest(&self.serialize()).as_slice()) 396 | } 397 | 398 | /// The secp256k1 public key of the transaction sender. 399 | /// 400 | /// This public key can used to derive the equivalent Filecoin account 401 | pub fn sender_public_key(&self) -> Result<[u8; SECP_PUB_LEN], ActorError> { 402 | let mut sig = [0u8; 65]; 403 | sig[..32].copy_from_slice(self.signature.r.as_bytes()); 404 | sig[32..64].copy_from_slice(self.signature.s.as_bytes()); 405 | 406 | if matches!(self.transaction, Transaction::Legacy { .. }) { 407 | sig[64] = self.signature.v.odd_y_parity(); 408 | } else { 409 | sig[64] = self.signature.v.0 as u8; 410 | } 411 | 412 | #[cfg(not(test))] // use a syscall to fvm 413 | return fvm_sdk::crypto::recover_secp_public_key( 414 | &self.transaction.hash().to_fixed_bytes(), 415 | &sig, 416 | ) 417 | .map_err(|e| { 418 | ActorError::illegal_argument(format!("failed to recover public key: {e:?}")) 419 | }); 420 | 421 | #[cfg(test)] 422 | // invoke the recovery impl directly as there is not FVM running this code 423 | return Ok( 424 | fvm_shared::crypto::signature::ops::recover_secp_public_key( 425 | &self.transaction.hash().to_fixed_bytes(), 426 | &sig, 427 | ) 428 | .unwrap() 429 | .serialize(), 430 | ); 431 | } 432 | 433 | /// Ethereum sender address which is 20-bytes trimmed keccak256(pubkey) 434 | pub fn sender_address(&self) -> Result { 435 | let pubkey = self.sender_public_key()?; 436 | let address_slice = &Keccak256::digest(&pubkey[1..])[12..]; 437 | Ok(H160::from_slice(address_slice)) 438 | } 439 | } 440 | 441 | /// rlp([nonce, gasPrice, gasLimit, to, value, data, init, v, r, s]) 442 | fn parse_legacy_transaction(bytes: &[u8]) -> Result { 443 | let rlp = Rlp::new(bytes); 444 | 445 | if rlp.item_count()? != 9 { 446 | return Err(DecoderError::RlpIncorrectListLen); 447 | } 448 | 449 | let signature = TransactionSignature { 450 | v: TransactionRecoveryId(rlp.val_at(6)?), 451 | r: rlp.val_at(7)?, 452 | s: rlp.val_at(8)?, 453 | }; 454 | 455 | Ok(SignedTransaction { 456 | transaction: Transaction::Legacy { 457 | chain_id: signature.v.chain_id(), 458 | nonce: rlp.val_at(0)?, 459 | gas_price: rlp.val_at(1)?, 460 | gas_limit: rlp.val_at(2)?, 461 | action: rlp.val_at(3)?, 462 | value: rlp.val_at(4)?, 463 | input: rlp.val_at(5)?, 464 | }, 465 | signature, 466 | }) 467 | } 468 | 469 | /// 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, 470 | /// accessList, signatureYParity, signatureR, signatureS]) 471 | fn parse_eip2930_transaction(bytes: &[u8]) -> Result { 472 | let rlp = Rlp::new(&bytes[1..]); 473 | 474 | if rlp.item_count()? != 11 { 475 | return Err(DecoderError::RlpIncorrectListLen); 476 | } 477 | 478 | let signature = TransactionSignature { 479 | v: TransactionRecoveryId(rlp.val_at(8)?), 480 | r: rlp.val_at(9)?, 481 | s: rlp.val_at(10)?, 482 | }; 483 | 484 | Ok(SignedTransaction { 485 | transaction: Transaction::EIP2930 { 486 | chain_id: rlp.val_at(0)?, 487 | nonce: rlp.val_at(1)?, 488 | gas_price: rlp.val_at(2)?, 489 | gas_limit: rlp.val_at(3)?, 490 | action: rlp.val_at(4)?, 491 | value: rlp.val_at(5)?, 492 | input: rlp.val_at(6)?, 493 | access_list: rlp.list_at(7)?, 494 | }, 495 | signature, 496 | }) 497 | } 498 | 499 | /// 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, 500 | /// gas_limit, destination, amount, data, access_list, signature_y_parity, 501 | /// signature_r, signature_s]) 502 | fn parse_eip1559_transaction(bytes: &[u8]) -> Result { 503 | let rlp = Rlp::new(&bytes[1..]); 504 | 505 | if rlp.item_count()? != 12 { 506 | return Err(DecoderError::RlpIncorrectListLen); 507 | } 508 | 509 | Ok(SignedTransaction { 510 | signature: TransactionSignature { 511 | v: TransactionRecoveryId(rlp.val_at(9)?), 512 | r: rlp.val_at(10)?, 513 | s: rlp.val_at(11)?, 514 | }, 515 | transaction: Transaction::EIP1559 { 516 | chain_id: rlp.val_at(0)?, 517 | nonce: rlp.val_at(1)?, 518 | max_priority_fee_per_gas: rlp.val_at(2)?, 519 | max_fee_per_gas: rlp.val_at(3)?, 520 | gas_limit: rlp.val_at(4)?, 521 | action: rlp.val_at(5)?, 522 | value: rlp.val_at(6)?, 523 | input: rlp.val_at(7)?, 524 | access_list: rlp.list_at(8)?, 525 | }, 526 | }) 527 | } 528 | 529 | impl Debug for Transaction { 530 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 531 | match self { 532 | Self::Legacy { 533 | chain_id, 534 | nonce, 535 | gas_price, 536 | gas_limit, 537 | action, 538 | value, 539 | input, 540 | } => f 541 | .debug_struct("Legacy") 542 | .field("chain_id", chain_id) 543 | .field("nonce", nonce) 544 | .field("gas_price", gas_price) 545 | .field("gas_limit", gas_limit) 546 | .field("action", action) 547 | .field("value", value) 548 | .field("input", &hex::encode(&input)) 549 | .finish(), 550 | Self::EIP2930 { 551 | chain_id, 552 | nonce, 553 | gas_price, 554 | gas_limit, 555 | action, 556 | value, 557 | input, 558 | access_list, 559 | } => f 560 | .debug_struct("EIP2930") 561 | .field("chain_id", chain_id) 562 | .field("nonce", nonce) 563 | .field("gas_price", gas_price) 564 | .field("gas_limit", gas_limit) 565 | .field("action", action) 566 | .field("value", value) 567 | .field("input", &hex::encode(&input)) 568 | .field("access_list", access_list) 569 | .finish(), 570 | Self::EIP1559 { 571 | chain_id, 572 | nonce, 573 | max_priority_fee_per_gas, 574 | max_fee_per_gas, 575 | gas_limit, 576 | action, 577 | value, 578 | input, 579 | access_list, 580 | } => f 581 | .debug_struct("EIP1559") 582 | .field("chain_id", chain_id) 583 | .field("nonce", nonce) 584 | .field("max_priority_fee_per_gas", max_priority_fee_per_gas) 585 | .field("max_fee_per_gas", max_fee_per_gas) 586 | .field("gas_limit", gas_limit) 587 | .field("action", action) 588 | .field("value", value) 589 | .field("input", &hex::encode(&input)) 590 | .field("access_list", access_list) 591 | .finish(), 592 | } 593 | } 594 | } 595 | 596 | #[cfg(test)] 597 | mod tests { 598 | use { 599 | crate::{ 600 | transaction::{AccessListItem, Transaction, TransactionAction}, 601 | SignedTransaction, 602 | H160, 603 | H256, 604 | U256, 605 | }, 606 | hex_literal::hex, 607 | }; 608 | 609 | #[test] 610 | fn decode_legacy_transaction() { 611 | // https://etherscan.io/tx/0x3741aea434dc6e9e740be0113af4bac372fcdd2fa2188409c93c9405cbdcaaf0 612 | let raw = hex!( 613 | "f9016b0885113abe69b38302895c947a250d5630b4cf539739df2c5dacb4c659f2488d80b90 614 | 1044a25d94a00000000000000000000000000000000000000000000000022b1c8c1227a0000 615 | 000000000000000000000000000000000000000000000003f0a59430f92a924400000000000 616 | 000000000000000000000000000000000000000000000000000a00000000000000000000000 617 | 0012021043bbaab3b71b2217655787a13d24cf618b000000000000000000000000000000000 618 | 00000000000000000000000603c6a1e00000000000000000000000000000000000000000000 619 | 00000000000000000002000000000000000000000000fe9a29ab92522d14fc65880d8172142 620 | 61d8479ae000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc225 621 | a01df6c364ee7d2b684bbb6e3892fee69a1bc4fc487222b003ea57ec1596884916a01e1643f 622 | de193fde5e6be4ae0b2d4c4669560132a6dc87b6404d5c0cdc743fee6 623 | " 624 | ); 625 | 626 | let transaction = SignedTransaction::try_from(&raw[..]).unwrap(); 627 | 628 | // test sender recovery 629 | assert_eq!( 630 | H160::from_slice(&hex!("12021043bbaab3b71b2217655787a13d24cf618b")), 631 | transaction.sender_address().unwrap() 632 | ); 633 | 634 | // test transaction hash computation: 635 | assert_eq!( 636 | H256::from_slice(&hex!( 637 | "3741aea434dc6e9e740be0113af4bac372fcdd2fa2188409c93c9405cbdcaaf0" 638 | )), 639 | transaction.hash() 640 | ); 641 | 642 | // test decoded fields 643 | if let Transaction::Legacy { 644 | chain_id, 645 | nonce, 646 | gas_price, 647 | gas_limit, 648 | action, 649 | value, 650 | input, 651 | } = transaction.transaction 652 | { 653 | assert_eq!(Some(1), chain_id); 654 | assert_eq!(8, nonce); 655 | assert_eq!(U256::from(74000001459u64), gas_price); 656 | assert_eq!(166236, gas_limit); 657 | assert_eq!(U256::zero(), value); 658 | 659 | assert_eq!( 660 | TransactionAction::Call(H160::from_slice(&hex!( 661 | "7a250d5630b4cf539739df2c5dacb4c659f2488d" 662 | ))), 663 | action 664 | ); 665 | 666 | assert_eq!( 667 | &hex!( 668 | "4a25d94a00000000000000000000000000000000000000000000000022b1c8c1227a 669 | 0000000000000000000000000000000000000000000000000003f0a59430f92a9244 670 | 00000000000000000000000000000000000000000000000000000000000000a00000 671 | 0000000000000000000012021043bbaab3b71b2217655787a13d24cf618b00000000 672 | 000000000000000000000000000000000000000000000000603c6a1e000000000000 673 | 00000000000000000000000000000000000000000000000000020000000000000000 674 | 00000000fe9a29ab92522d14fc65880d817214261d8479ae00000000000000000000 675 | 0000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" 676 | ) as &[u8], 677 | &input 678 | ); 679 | } else { 680 | assert!(false, "decoded into wrong transaction type"); 681 | } 682 | } 683 | 684 | #[test] 685 | fn decode_eip2930_transaction() { 686 | // https://etherscan.io/tx/0xfbf20efe99271206c0f5b497a92bee2e66f8bf9991e07648935194f17610b36e 687 | let raw = hex!( 688 | "01f8bb01808522ecb25c008307a120942a48420d75777af4c99970c0ed3c25effd1c08b 689 | e80843ccfd60bf84ff794fbfed54d426217bf75d2ce86622c1e5faf16b0a6e1a00000000 690 | 000000000000000000000000000000000000000000000000000000000d694d9db270c1b5 691 | e3bd161e8c8503c55ceabee709552c080a03057d1077af1fc48bdfe2a8eac03caf686145 692 | b52342e77ad6982566fe39e0691a00507044aa767a50dc926d0daa4dd616b1e5a8d2e578 693 | 1df5bc9feeee5a5139d61" 694 | ); 695 | 696 | let transaction = SignedTransaction::try_from(&raw[..]).unwrap(); 697 | 698 | // test if the right transaction type was detected 699 | assert!(matches!( 700 | transaction.transaction, 701 | Transaction::EIP2930 { .. } 702 | )); 703 | 704 | // test transaction hash computation: 705 | assert_eq!( 706 | H256::from_slice(&hex!( 707 | "fbf20efe99271206c0f5b497a92bee2e66f8bf9991e07648935194f17610b36e" 708 | )), 709 | transaction.hash() 710 | ); 711 | 712 | // test sender recovery 713 | assert_eq!( 714 | H160::from_slice(&hex!("4e2b6cc39e22026d8ce21214646a657ab7eb92b3")), 715 | transaction.sender_address().unwrap() 716 | ); 717 | 718 | if let Transaction::EIP2930 { 719 | chain_id, 720 | nonce, 721 | gas_price, 722 | gas_limit, 723 | action, 724 | value, 725 | input, 726 | access_list, 727 | } = transaction.transaction 728 | { 729 | assert_eq!(1, chain_id); 730 | assert_eq!(0, nonce); 731 | assert_eq!(U256::from(150000000000u64), gas_price); 732 | assert_eq!(500000, gas_limit); 733 | assert_eq!(U256::zero(), value); 734 | assert_eq!( 735 | TransactionAction::Call(H160::from_slice(&hex!( 736 | "2a48420d75777af4c99970c0ed3c25effd1c08be" 737 | ))), 738 | action 739 | ); 740 | assert_eq!(&hex!("3ccfd60b") as &[u8], &input); 741 | assert_eq!( 742 | vec![ 743 | AccessListItem { 744 | address: H160::from_slice(&hex!("fbfed54d426217bf75d2ce86622c1e5faf16b0a6")), 745 | slots: vec![H256::from_slice(&hex!( 746 | "0000000000000000000000000000000000000000000000000000000000000000" 747 | ))] 748 | }, 749 | AccessListItem { 750 | address: H160::from_slice(&hex!("d9db270c1b5e3bd161e8c8503c55ceabee709552")), 751 | slots: vec![] 752 | } 753 | ], 754 | access_list 755 | ) 756 | } else { 757 | assert!(false, "decoded into wrong transaction type"); 758 | } 759 | } 760 | 761 | #[test] 762 | fn decode_eip1559_transaction() { 763 | // https://etherscan.io/tx/0x734678f719001015c5b5f5cbac6a9210ede7ee6ce63e746ff2e9eecda3ab68c7 764 | let raw = hex!( 765 | "02f8720104843b9aca008504eb6480bc82520894f76c5b19e86c256 766 | 482f4aad1dae620a0c3ac0cd68717699d954d540080c080a05a5206a8e0486b8e101bcf 767 | 4ed5b290df24a4d54f1ca752c859fa19c291244b98a0177166d96fd69db70628d99855b 768 | 400c8a149b2254c211a0a00645830f5338218" 769 | ); 770 | 771 | let transaction = SignedTransaction::try_from(&raw[..]).unwrap(); 772 | 773 | // test if the right transaction type was detected 774 | assert!(matches!( 775 | transaction.transaction, 776 | Transaction::EIP1559 { .. } 777 | )); 778 | 779 | // test transaction hash computation: 780 | assert_eq!( 781 | H256::from_slice(&hex!( 782 | "734678f719001015c5b5f5cbac6a9210ede7ee6ce63e746ff2e9eecda3ab68c7" 783 | )), 784 | transaction.hash() 785 | ); 786 | 787 | // test sender recovery 788 | assert_eq!( 789 | H160::from_slice(&hex!("d882fab949fe224befd0e85afcc5f13d67980102")), 790 | transaction.sender_address().unwrap() 791 | ); 792 | 793 | if let Transaction::EIP1559 { 794 | chain_id, 795 | nonce, 796 | max_priority_fee_per_gas, 797 | max_fee_per_gas, 798 | gas_limit, 799 | action, 800 | value, 801 | input, 802 | access_list, 803 | } = transaction.transaction 804 | { 805 | assert_eq!(1, chain_id); 806 | assert_eq!(4, nonce); 807 | assert_eq!(21000, gas_limit); 808 | assert_eq!(U256::from(6590050000000000u64), value); 809 | assert_eq!(U256::from(1000000000), max_priority_fee_per_gas); 810 | assert_eq!(U256::from(21129101500u64), max_fee_per_gas); 811 | assert_eq!(&[] as &[u8], &input); 812 | assert_eq!(Vec::::new(), access_list); 813 | assert_eq!( 814 | TransactionAction::Call(H160::from_slice(&hex!( 815 | "f76c5b19e86c256482f4aad1dae620a0c3ac0cd6" 816 | ))), 817 | action 818 | ); 819 | } else { 820 | assert!(false, "decoded into wrong transaction type"); 821 | } 822 | } 823 | } 824 | -------------------------------------------------------------------------------- /shared/src/uints.rs: -------------------------------------------------------------------------------- 1 | use { 2 | fixed_hash::construct_fixed_hash, 3 | impl_serde::{impl_fixed_hash_serde, impl_uint_serde}, 4 | std::cmp::Ordering, 5 | uint::construct_uint, 6 | }; 7 | 8 | construct_uint! { pub struct U256(4); } // ethereum word size 9 | construct_uint! { pub struct U512(8); } // used for addmod and mulmod opcodes 10 | 11 | construct_fixed_hash! { pub struct H160(20); } // ethereum address 12 | construct_fixed_hash! { pub struct H256(32); } // Keccak256 13 | 14 | // make ETH uints serde serializable, 15 | // so it can work with Hamt and other 16 | // IPLD structures seamlessly 17 | impl_uint_serde!(U256, 4); 18 | impl_uint_serde!(U512, 8); 19 | impl_fixed_hash_serde!(H160, 20); 20 | impl_fixed_hash_serde!(H256, 32); 21 | 22 | macro_rules! impl_hamt_hash { 23 | ($type:ident) => { 24 | impl fvm_ipld_hamt::Hash for $type { 25 | fn hash(&self, state: &mut H) { 26 | self.0.hash(state); 27 | } 28 | } 29 | }; 30 | } 31 | 32 | fn zeroless_view(v: &impl AsRef<[u8]>) -> &[u8] { 33 | let v = v.as_ref(); 34 | &v[v.iter().take_while(|&&b| b == 0).count()..] 35 | } 36 | 37 | macro_rules! impl_rlp_codec_hash { 38 | ($type:ident) => { 39 | impl rlp::Encodable for $type { 40 | fn rlp_append(&self, s: &mut rlp::RlpStream) { 41 | let bytes = self.as_fixed_bytes(); 42 | s.encoder().encode_value(&bytes[..]); 43 | } 44 | } 45 | impl rlp::Decodable for $type { 46 | fn decode(rlp: &rlp::Rlp) -> Result { 47 | rlp 48 | .decoder() 49 | .decode_value(|bytes| Ok($type::from_slice(bytes))) 50 | } 51 | } 52 | }; 53 | } 54 | 55 | macro_rules! impl_rlp_codec_uint { 56 | ($type:ident, $bytes_len: expr) => { 57 | impl rlp::Encodable for $type { 58 | fn rlp_append(&self, s: &mut rlp::RlpStream) { 59 | let mut bytes = [0u8; $bytes_len]; 60 | self.to_big_endian(&mut bytes); 61 | let zbytes = zeroless_view(&bytes); 62 | s.encoder().encode_value(&zbytes); 63 | } 64 | } 65 | impl rlp::Decodable for $type { 66 | fn decode(rlp: &rlp::Rlp) -> Result { 67 | rlp 68 | .decoder() 69 | .decode_value(|bytes| Ok($type::from_big_endian(bytes))) 70 | } 71 | } 72 | }; 73 | } 74 | 75 | // Hamt support 76 | impl_hamt_hash!(H160); 77 | impl_hamt_hash!(H256); 78 | 79 | impl_hamt_hash!(U256); 80 | impl_hamt_hash!(U512); 81 | 82 | // RLP Support 83 | impl_rlp_codec_hash!(H160); 84 | impl_rlp_codec_hash!(H256); 85 | impl_rlp_codec_uint!(U256, 32); 86 | impl_rlp_codec_uint!(U512, 64); 87 | 88 | #[inline(always)] 89 | pub fn u256_high(val: U256) -> u128 { 90 | let mut bytes = [0u8; 32]; 91 | val.to_big_endian(&mut bytes); 92 | u128::from_be_bytes(bytes[0..16].try_into().unwrap()) 93 | } 94 | 95 | #[inline(always)] 96 | pub fn u256_low(val: U256) -> u128 { 97 | let mut bytes = [0u8; 32]; 98 | val.to_big_endian(&mut bytes); 99 | u128::from_be_bytes(bytes[16..32].try_into().unwrap()) 100 | } 101 | 102 | #[inline(always)] 103 | pub fn u128_words_to_u256(high: u128, low: u128) -> U256 { 104 | let high = high.to_be_bytes(); 105 | let low = low.to_be_bytes(); 106 | let bytes = high.into_iter().chain(low.into_iter()).collect::>(); 107 | U256::from_big_endian(&bytes) 108 | } 109 | 110 | 111 | #[inline] 112 | fn _u256_to_address(v: U256) -> H160 { 113 | let mut bytes = [0u8; 32]; 114 | v.to_big_endian(&mut bytes); 115 | H160::from_slice(&bytes) 116 | } 117 | 118 | #[inline] 119 | pub fn address_to_u256(v: H160) -> U256 { 120 | U256::from_big_endian(v.as_bytes()) 121 | } 122 | 123 | const SIGN_BITMASK_U128: u128 = 0x8000_0000_0000_0000_0000_0000_0000_0000; 124 | const FLIPH_BITMASK_U128: u128 = 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF; 125 | 126 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 127 | pub enum Sign { 128 | Plus, 129 | Minus, 130 | Zero, 131 | } 132 | 133 | #[inline] 134 | pub fn log2floor(value: U256) -> u64 { 135 | debug_assert!(value != U256::zero()); 136 | let mut l: u64 = 256; 137 | for v in [u256_high(value), u256_low(value)] { 138 | if v == 0 { 139 | l -= 128; 140 | } else { 141 | l -= v.leading_zeros() as u64; 142 | if l == 0 { 143 | return l; 144 | } else { 145 | return l - 1; 146 | } 147 | } 148 | } 149 | l 150 | } 151 | 152 | #[inline(always)] 153 | pub fn two_compl(op: U256) -> U256 { 154 | !op + U256::from(1) 155 | } 156 | 157 | #[inline(always)] 158 | fn two_compl_mut(op: &mut U256) { 159 | *op = two_compl(*op); 160 | } 161 | 162 | #[inline(always)] 163 | pub fn i256_sign(val: &mut U256) -> Sign { 164 | if u256_high(*val) & SIGN_BITMASK_U128 == 0 { 165 | if *val == U256::zero() { 166 | Sign::Zero 167 | } else { 168 | Sign::Plus 169 | } 170 | } else { 171 | if DO_TWO_COMPL { 172 | two_compl_mut(val); 173 | } 174 | Sign::Minus 175 | } 176 | } 177 | 178 | #[inline(always)] 179 | pub fn i256_div(mut first: U256, mut second: U256) -> U256 { 180 | let min_negative_value: U256 = u128_words_to_u256(SIGN_BITMASK_U128, 0); 181 | let second_sign = i256_sign::(&mut second); 182 | if second_sign == Sign::Zero { 183 | return U256::zero(); 184 | } 185 | let first_sign = i256_sign::(&mut first); 186 | if first_sign == Sign::Minus && first == min_negative_value && second == U256::from(1) { 187 | return two_compl(min_negative_value); 188 | } 189 | 190 | let mut d = first / second; 191 | 192 | u256_remove_sign(&mut d); 193 | 194 | if d == U256::zero() { 195 | return U256::zero(); 196 | } 197 | 198 | match (first_sign, second_sign) { 199 | (Sign::Zero, Sign::Plus) 200 | | (Sign::Plus, Sign::Zero) 201 | | (Sign::Zero, Sign::Zero) 202 | | (Sign::Plus, Sign::Plus) 203 | | (Sign::Minus, Sign::Minus) => d, 204 | (Sign::Zero, Sign::Minus) 205 | | (Sign::Plus, Sign::Minus) 206 | | (Sign::Minus, Sign::Zero) 207 | | (Sign::Minus, Sign::Plus) => two_compl(d), 208 | } 209 | } 210 | 211 | #[inline(always)] 212 | pub fn i256_mod(mut first: U256, mut second: U256) -> U256 { 213 | let first_sign = i256_sign::(&mut first); 214 | if first_sign == Sign::Zero { 215 | return U256::zero(); 216 | } 217 | 218 | let _ = i256_sign::(&mut second); 219 | let mut r = first % second; 220 | u256_remove_sign(&mut r); 221 | if r == U256::zero() { 222 | return U256::zero(); 223 | } 224 | if first_sign == Sign::Minus { 225 | two_compl(r) 226 | } else { 227 | r 228 | } 229 | } 230 | 231 | #[inline(always)] 232 | pub fn i256_cmp(mut first: U256, mut second: U256) -> Ordering { 233 | let first_sign = i256_sign::(&mut first); 234 | let second_sign = i256_sign::(&mut second); 235 | match (first_sign, second_sign) { 236 | (Sign::Zero, Sign::Zero) => Ordering::Equal, 237 | (Sign::Zero, Sign::Plus) => Ordering::Less, 238 | (Sign::Zero, Sign::Minus) => Ordering::Greater, 239 | (Sign::Minus, Sign::Zero) => Ordering::Less, 240 | (Sign::Minus, Sign::Plus) => Ordering::Less, 241 | (Sign::Minus, Sign::Minus) => first.cmp(&second), 242 | (Sign::Plus, Sign::Minus) => Ordering::Greater, 243 | (Sign::Plus, Sign::Zero) => Ordering::Greater, 244 | (Sign::Plus, Sign::Plus) => first.cmp(&second), 245 | } 246 | } 247 | 248 | #[inline(always)] 249 | fn u256_remove_sign(val: &mut U256) { 250 | let low = u256_low(*val); 251 | let mut high = u256_high(*val); 252 | high &= FLIPH_BITMASK_U128; 253 | *val = u128_words_to_u256(high, low) 254 | } 255 | 256 | #[cfg(test)] 257 | mod tests { 258 | use {super::*, core::num::Wrapping}; 259 | 260 | #[test] 261 | fn div_i256() { 262 | let min_negative_value: U256 = u128_words_to_u256(SIGN_BITMASK_U128, 0); 263 | 264 | assert_eq!(Wrapping(i8::MIN) / Wrapping(-1), Wrapping(i8::MIN)); 265 | assert_eq!(i8::MAX / -1, -i8::MAX); 266 | 267 | let one = U256::from(1); 268 | let one_hundred = U256::from(100); 269 | let fifty = U256::from(50); 270 | let _fifty_sign = Sign::Plus; 271 | let two = U256::from(2); 272 | let neg_one_hundred = U256::from(100); 273 | let _neg_one_hundred_sign = Sign::Minus; 274 | let minus_one = U256::from(1); 275 | let max_value = U256::from(2).pow(255.into()) - 1; 276 | let neg_max_value = U256::from(2).pow(255.into()) - 1; 277 | 278 | assert_eq!(i256_div(min_negative_value, minus_one), min_negative_value); 279 | assert_eq!(i256_div(min_negative_value, one), min_negative_value); 280 | assert_eq!(i256_div(max_value, one), max_value); 281 | assert_eq!(i256_div(max_value, minus_one), neg_max_value); 282 | assert_eq!(i256_div(one_hundred, minus_one), neg_one_hundred); 283 | assert_eq!(i256_div(one_hundred, two), fifty); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fvm-evm-tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | authors = ["Protocol Labs", "Filecoin Core Devs"] 7 | keywords = ["filecoin", "web3", "wasm", "evm"] 8 | 9 | [lib] 10 | doctest = false 11 | 12 | [dependencies] 13 | hex = "0.4.3" 14 | anyhow = "1" 15 | rand = "0.8.5" 16 | serde = { version = "1.0", features = ["derive"] } 17 | cid = { version = "0.8.5", default-features = false } 18 | libsecp256k1 = { version = "0.7.0", features = ["static-context"] } 19 | fvm-evm = { path = "../shared" } 20 | log = "0.4" 21 | pretty_env_logger = "0.4" 22 | fvm_ipld_blockstore = "0.1.1" 23 | fvm_ipld_encoding = "0.2.2" 24 | fvm_shared = { version = "0.8.0" } 25 | fvm = { version = "2.0.0-alpha.1", default-features = false } 26 | # We should do this with a patch, but we can't because it's not published to crates.io and 27 | # https://github.com/rust-lang/cargo/issues/5478 28 | #fvm_integration_tests = { git = "https://github.com/filecoin-project/ref-fvm", branch = "karim/recover-pubkey-syscall" } 29 | fvm_integration_tests = { path = "../../ref-fvm/testing/integration" } 30 | 31 | [dependencies.wasmtime] 32 | version = "0.37.0" 33 | default-features = false 34 | features = ["cranelift", "parallel-compilation"] 35 | 36 | [features] 37 | default = ["fvm/testing", "fvm_shared/testing"] 38 | -------------------------------------------------------------------------------- /tests/contracts/simplecoin.hex: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610549806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b91906102d1565b6100d6565b60405161006d919061036f565b60405180910390f35b610090600480360381019061008b91906102fa565b6100f4565b60405161009d9190610354565b60405180910390f35b6100c060048036038101906100bb91906102d1565b61025f565b6040516100cd919061036f565b60405180910390f35b600060026100e38361025f565b6100ed91906103e0565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610193919061043a565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e8919061038a565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c919061036f565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6000813590506102b6816104e5565b92915050565b6000813590506102cb816104fc565b92915050565b6000602082840312156102e357600080fd5b60006102f1848285016102a7565b91505092915050565b6000806040838503121561030d57600080fd5b600061031b858286016102a7565b925050602061032c858286016102bc565b9150509250929050565b61033f81610480565b82525050565b61034e816104ac565b82525050565b60006020820190506103696000830184610336565b92915050565b60006020820190506103846000830184610345565b92915050565b6000610395826104ac565b91506103a0836104ac565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156103d5576103d46104b6565b5b828201905092915050565b60006103eb826104ac565b91506103f6836104ac565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561042f5761042e6104b6565b5b828202905092915050565b6000610445826104ac565b9150610450836104ac565b925082821015610463576104626104b6565b5b828203905092915050565b60006104798261048c565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6104ee8161046e565b81146104f957600080fd5b50565b610505816104ac565b811461051057600080fd5b5056fea2646970667358221220fbdafe87ea1e2ef996f14b0af9247a3d6860d49549e1113d1e49b870e130a57364736f6c63430008030033 -------------------------------------------------------------------------------- /tests/contracts/simplecoin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.25 <0.8.15; 3 | 4 | contract SimpleCoin { 5 | mapping (address => uint) balances; 6 | 7 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 8 | 9 | constructor() { 10 | balances[tx.origin] = 10000; 11 | } 12 | 13 | function sendCoin(address receiver, uint amount) public returns(bool sufficient) { 14 | if (balances[msg.sender] < amount) return false; 15 | balances[msg.sender] -= amount; 16 | balances[receiver] += amount; 17 | emit Transfer(msg.sender, receiver, amount); 18 | return true; 19 | } 20 | 21 | function getBalanceInEth(address addr) public view returns(uint){ 22 | return getBalance(addr) * 2; 23 | } 24 | 25 | function getBalance(address addr) public view returns(uint) { 26 | return balances[addr]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/src/bridge.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{sign_evm_transaction, EVMTester}, 3 | anyhow::Result, 4 | fvm_evm::{Transaction, TransactionAction}, 5 | fvm_ipld_encoding::RawBytes, 6 | libsecp256k1::SecretKey, 7 | }; 8 | 9 | const PROCESS_TRANSACTION_METHOD_NUM: u64 = 2; 10 | 11 | #[test] 12 | fn deploy_contract() -> Result<()> { 13 | pretty_env_logger::init(); 14 | log::trace!("deploying EVM contract..."); 15 | let mut tester = EVMTester::new::<1>()?; 16 | log::trace!("Created EVM Tester"); 17 | // create the bridge actor and instantiate it with the evm runtime code CID. 18 | let output = tester.construct_actor( 19 | EVMTester::BRIDGE_ACTOR_ADDRESS, 20 | RawBytes::serialize(tester.runtime_code_cid())?, 21 | )?; 22 | 23 | // bridge constructor does not return anything 24 | assert_eq!(RawBytes::default(), output); 25 | 26 | let create_tx = Transaction::Legacy { 27 | chain_id: Some(8889), 28 | nonce: 1, 29 | gas_price: 150000000000u64.into(), 30 | gas_limit: 500000, 31 | action: TransactionAction::Create, 32 | value: 0.into(), 33 | input: hex::decode(include_str!("../contracts/simplecoin.hex")) 34 | .unwrap() 35 | .into(), 36 | }; 37 | 38 | let seckey = SecretKey::random(&mut rand::thread_rng()); 39 | let signed_tx = sign_evm_transaction(create_tx, seckey); 40 | let raw_tx = signed_tx.serialize(); 41 | 42 | tester.invoke_actor( 43 | tester.accounts()[0].1, 44 | EVMTester::BRIDGE_ACTOR_ADDRESS, 45 | PROCESS_TRANSACTION_METHOD_NUM, 46 | RawBytes::serialize(raw_tx)?, 47 | )?; 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anyhow::Result, 3 | cid::Cid, 4 | fvm::executor::{ApplyKind, Executor}, 5 | fvm_evm::{ 6 | SignedTransaction, 7 | Transaction, 8 | TransactionRecoveryId, 9 | TransactionSignature, 10 | H256, 11 | }, 12 | fvm_integration_tests::tester::{Account, Tester}, 13 | fvm_ipld_blockstore::MemoryBlockstore, 14 | fvm_ipld_encoding::{Cbor, RawBytes}, 15 | fvm_shared::{ 16 | address::Address, 17 | bigint::{BigInt, Zero}, 18 | message::Message, 19 | state::StateTreeVersion, 20 | version::NetworkVersion, 21 | MethodNum, 22 | METHOD_CONSTRUCTOR, 23 | }, 24 | libsecp256k1::{sign, Message as SecpMessage, SecretKey}, 25 | serde::{Deserialize, Serialize}, 26 | std::{ 27 | collections::{hash_map::Entry, HashMap}, 28 | fs, 29 | }, 30 | }; 31 | 32 | #[cfg(test)] 33 | mod bridge; 34 | 35 | #[cfg(test)] 36 | mod runtime; 37 | 38 | pub struct EVMTester { 39 | _bridge_code_cid: Cid, 40 | runtime_code_cid: Cid, 41 | instance: Tester, 42 | accounts: Vec, 43 | sequences: HashMap, 44 | } 45 | 46 | // constants 47 | impl EVMTester { 48 | pub const BRIDGE_ACTOR_ADDRESS: Address = Address::new_id(10002); 49 | pub const BRIDGE_ACTOR_WASM_PATH: &'static str = "../wasm/fvm_evm_bridge.compact.wasm"; 50 | pub const INIT_ACTOR_ADDRESS: Address = Address::new_id(1); 51 | pub const RUNTIME_ACTOR_ADDRESS: Address = Address::new_id(10001); 52 | pub const RUNTIME_ACTOR_WASM_PATH: &'static str = 53 | "../wasm/fvm_evm_runtime.compact.wasm"; 54 | } 55 | 56 | impl EVMTester { 57 | /// Creates an FVM simulator with both actors loaded from WASM 58 | /// ready to execute messages from external sources 59 | pub fn new() -> Result { 60 | let mut instance = Tester::new( 61 | NetworkVersion::V16, 62 | StateTreeVersion::V4, 63 | MemoryBlockstore::default(), 64 | )?; 65 | 66 | let accounts = instance.create_accounts::()?.to_vec(); 67 | 68 | let runtime_actor_wasm = fs::read(Self::RUNTIME_ACTOR_WASM_PATH)?; 69 | let bridge_actor_wasm = fs::read(Self::BRIDGE_ACTOR_WASM_PATH)?; 70 | 71 | #[derive(Debug, Serialize, Deserialize)] 72 | struct State { 73 | empty: bool, 74 | } 75 | 76 | let empty_state = State { empty: true }; 77 | let state_root = instance.set_state(&empty_state)?; 78 | 79 | let runtime_code_cid = instance.set_actor_from_bin( 80 | &runtime_actor_wasm, 81 | state_root, 82 | Self::RUNTIME_ACTOR_ADDRESS, 83 | BigInt::zero(), 84 | )?; 85 | 86 | let bridge_code_cid = instance.set_actor_from_bin( 87 | &bridge_actor_wasm, 88 | state_root, 89 | Self::BRIDGE_ACTOR_ADDRESS, 90 | BigInt::zero(), 91 | )?; 92 | 93 | instance.instantiate_machine()?; 94 | 95 | Ok(Self { 96 | accounts, 97 | _bridge_code_cid: bridge_code_cid, 98 | runtime_code_cid, 99 | instance, 100 | sequences: HashMap::new(), 101 | }) 102 | } 103 | 104 | pub fn runtime_code_cid(&self) -> &Cid { 105 | &self.runtime_code_cid 106 | } 107 | 108 | pub fn accounts(&self) -> &[Account] { 109 | &self.accounts 110 | } 111 | 112 | pub fn send_message( 113 | &mut self, 114 | from: Address, 115 | to: Address, 116 | method_num: MethodNum, 117 | params: RawBytes, 118 | kind: ApplyKind, 119 | ) -> Result { 120 | let sequence = match self.sequences.entry(from) { 121 | Entry::Occupied(mut o) => { 122 | *o.get_mut() += 1; 123 | *o.get() 124 | } 125 | Entry::Vacant(v) => *v.insert(0), 126 | }; 127 | let message = Message { 128 | from, 129 | to, 130 | gas_limit: 10000000000, 131 | method_num, 132 | params, 133 | sequence, 134 | ..Message::default() 135 | }; 136 | 137 | let message_len = message.marshal_cbor()?; 138 | let output = match self.instance.executor { 139 | Some(ref mut executor) => { 140 | let ret = executor.execute_message(message, kind, message_len.len())?; 141 | if ret.msg_receipt.exit_code.is_success() { 142 | ret.msg_receipt.return_data 143 | } else { 144 | return Err(anyhow::anyhow!( 145 | "message failed with exit code {} ({:?})", 146 | ret.msg_receipt.exit_code, 147 | ret.failure_info 148 | )); 149 | } 150 | } 151 | None => return Err(anyhow::anyhow!("executor not initialized")), 152 | }; 153 | 154 | Ok(output) 155 | } 156 | 157 | pub fn send_explicit_message( 158 | &mut self, 159 | from: Address, 160 | to: Address, 161 | method: MethodNum, 162 | params: RawBytes, 163 | ) -> Result { 164 | self.send_message(from, to, method, params, ApplyKind::Explicit) 165 | } 166 | 167 | pub fn send_implicit_message( 168 | &mut self, 169 | from: Address, 170 | to: Address, 171 | method: MethodNum, 172 | params: RawBytes, 173 | ) -> Result { 174 | self.send_message(from, to, method, params, ApplyKind::Implicit) 175 | } 176 | 177 | pub fn construct_actor( 178 | &mut self, 179 | address: Address, 180 | params: RawBytes, 181 | ) -> Result { 182 | self.send_implicit_message( 183 | Self::INIT_ACTOR_ADDRESS, 184 | address, 185 | METHOD_CONSTRUCTOR, 186 | params, 187 | ) 188 | } 189 | 190 | pub fn invoke_actor( 191 | &mut self, 192 | caller: Address, 193 | address: Address, 194 | method: MethodNum, 195 | params: RawBytes, 196 | ) -> Result { 197 | self.send_explicit_message(caller, address, method, params) 198 | } 199 | } 200 | 201 | pub fn sign_evm_transaction( 202 | transaction: Transaction, 203 | seckey: SecretKey, 204 | ) -> SignedTransaction { 205 | let hash = transaction.hash(); 206 | let (signature, recovery_id) = 207 | sign(&SecpMessage::parse(&hash.as_fixed_bytes()), &seckey); 208 | let recovery_id = recovery_id.serialize() as u64; 209 | let signature = TransactionSignature { 210 | v: TransactionRecoveryId(match transaction { 211 | Transaction::Legacy { .. } => match transaction.chain_id() { 212 | Some(chain_id) => chain_id * 2 + 35 + recovery_id, 213 | None => recovery_id, 214 | }, 215 | Transaction::EIP2930 { .. } => recovery_id, 216 | Transaction::EIP1559 { .. } => recovery_id, 217 | }), 218 | r: H256::from_slice(&signature.r.b32()), 219 | s: H256::from_slice(&signature.s.b32()), 220 | }; 221 | 222 | SignedTransaction { 223 | transaction, 224 | signature, 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /tests/src/runtime.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::EVMTester, 3 | anyhow::Result, 4 | fvm_evm::{EthereumAccount, H160, U256}, 5 | fvm_ipld_encoding::{from_slice, to_vec, RawBytes}, 6 | }; 7 | 8 | #[test] 9 | #[ignore] 10 | fn cross_contract_smoke() -> Result<()> { 11 | const RETREIVE_METHOD_NUM: u64 = 2; 12 | const CONSTRUCT_ZERO_ACCOUNT_NUM: u64 = 2; 13 | 14 | let mut tester = EVMTester::new::<1>()?; 15 | 16 | // construct registry 17 | let output = 18 | tester.construct_actor(EVMTester::BRIDGE_ACTOR_ADDRESS, RawBytes::default())?; 19 | 20 | // registry constructor does not return anything 21 | assert_eq!(RawBytes::default(), output); 22 | 23 | let runtime_params = (vec![1u8; 2], EVMTester::BRIDGE_ACTOR_ADDRESS); 24 | 25 | let output = tester.construct_actor( 26 | EVMTester::RUNTIME_ACTOR_ADDRESS, 27 | RawBytes::new(to_vec(&runtime_params)?), 28 | )?; 29 | 30 | // runtime constructor returns nothing 31 | assert_eq!(RawBytes::default(), output); 32 | 33 | // make sure that the zero address is empty 34 | // before invoking the cross-contract call 35 | let eth_account = from_slice(&tester.invoke_actor( 36 | tester.accounts()[0].1, 37 | EVMTester::BRIDGE_ACTOR_ADDRESS, 38 | RETREIVE_METHOD_NUM, 39 | RawBytes::serialize(H160::zero())?, 40 | )?)?; 41 | 42 | // not present, should return a synthesized empty/unused account 43 | assert_eq!(EthereumAccount::default(), eth_account); 44 | 45 | // invoke a runtime method that invokes 46 | // the registry and creates a new entry 47 | // for the zero address. 48 | let output = tester.invoke_actor( 49 | tester.accounts()[0].1, 50 | EVMTester::RUNTIME_ACTOR_ADDRESS, 51 | CONSTRUCT_ZERO_ACCOUNT_NUM, 52 | RawBytes::default(), 53 | )?; 54 | assert_eq!(RawBytes::default(), output); 55 | 56 | // now query again, it should have the inserted value 57 | let eth_account = from_slice(&tester.invoke_actor( 58 | tester.accounts()[0].1, 59 | EVMTester::BRIDGE_ACTOR_ADDRESS, 60 | RETREIVE_METHOD_NUM, 61 | RawBytes::serialize(H160::zero())?, 62 | )?)?; 63 | 64 | assert_ne!(EthereumAccount::default(), eth_account); 65 | assert_eq!(5, eth_account.nonce); 66 | assert_eq!(U256::from(999), eth_account.balance); 67 | 68 | Ok(()) 69 | } 70 | --------------------------------------------------------------------------------