diff --git a/Cargo.lock b/Cargo.lock index 7c9183e873..2f2f0d7206 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addchain" @@ -471,6 +471,39 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -509,6 +542,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.3.0" @@ -529,6 +582,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.71", +] + [[package]] name = "ark-ff-macros" version = "0.3.0" @@ -554,6 +617,34 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -575,6 +666,30 @@ dependencies = [ "num-bigint 0.4.6", ] +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "ark-std" version = "0.3.0" @@ -595,6 +710,16 @@ dependencies = [ "rand", ] +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "arrayvec" version = "0.7.4" @@ -1116,7 +1241,7 @@ checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -1249,6 +1374,18 @@ dependencies = [ "spki", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "either" version = "1.13.0" @@ -1307,6 +1444,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "enumn" version = "0.1.13" @@ -1493,7 +1650,7 @@ dependencies = [ [[package]] name = "ff" version = "0.13.0" -source = "git+https://github.com/scroll-tech/ff?branch=feat/sp1#244b1098f6be1d19c5fd3f0ec60117ac2940e6ca" +source = "git+https://github.com/scroll-tech/ff?branch=feat%2Fsp1#244b1098f6be1d19c5fd3f0ec60117ac2940e6ca" dependencies = [ "bitvec", "byteorder", @@ -1505,7 +1662,7 @@ dependencies = [ [[package]] name = "ff_derive" version = "0.13.0" -source = "git+https://github.com/scroll-tech/ff?branch=feat/sp1#244b1098f6be1d19c5fd3f0ec60117ac2940e6ca" +source = "git+https://github.com/scroll-tech/ff?branch=feat%2Fsp1#244b1098f6be1d19c5fd3f0ec60117ac2940e6ca" dependencies = [ "addchain", "cfg-if", @@ -1801,6 +1958,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", +] + [[package]] name = "hashers" version = "1.0.1" @@ -2104,7 +2270,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -2288,7 +2454,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -3169,6 +3335,10 @@ dependencies = [ name = "revm-precompile" version = "11.0.0" dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", "aurora-engine-modexp", "blst", "c-kzg", @@ -3205,7 +3375,7 @@ dependencies = [ "dyn-clone", "enumn", "getrandom", - "hashbrown", + "hashbrown 0.14.5", "hex", "kzg-rs", "poseidon-bn254", @@ -3232,7 +3402,7 @@ version = "0.10.0" dependencies = [ "alloy-rlp", "hash-db", - "hashbrown", + "hashbrown 0.14.5", "hex", "indicatif", "k256", diff --git a/bins/revm-test/Cargo.toml b/bins/revm-test/Cargo.toml index 7540a00d6d..6f90e8099c 100644 --- a/bins/revm-test/Cargo.toml +++ b/bins/revm-test/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] bytes = "1.7" hex = "0.4" -revm = { path = "../../crates/revm", version = "14.0.0", default-features=false } +revm = { path = "../../crates/revm", version = "14.0.0", features=["morph-default-handler"] } microbench = "0.5" alloy-sol-macro = "0.8.0" alloy-sol-types = "0.8.0" diff --git a/bins/revm-test/src/bin/morph.rs b/bins/revm-test/src/bin/morph.rs new file mode 100644 index 0000000000..f9c8d8c5bb --- /dev/null +++ b/bins/revm-test/src/bin/morph.rs @@ -0,0 +1,363 @@ +use revm::{ + db::{CacheDB, EmptyDB}, + primitives::{ + address, bytes, keccak256, AccountInfo, Address, BlockEnv, Bytecode, Bytes, TxEnv, U256, + }, + Database, Evm, +}; + +static ERC20_DEPLOYED_CODE : Bytes = bytes!("608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b0c565b60405180910390f35b6100e660048036038101906100e19190610bc7565b610308565b6040516100f39190610c22565b60405180910390f35b61010461032b565b6040516101119190610c4c565b60405180910390f35b610134600480360381019061012f9190610c67565b610335565b6040516101419190610c22565b60405180910390f35b610152610364565b60405161015f9190610cd6565b60405180910390f35b610182600480360381019061017d9190610bc7565b61036d565b60405161018f9190610c22565b60405180910390f35b6101b260048036038101906101ad9190610cf1565b6103a4565b6040516101bf9190610c4c565b60405180910390f35b6101d06103ec565b6040516101dd9190610b0c565b60405180910390f35b61020060048036038101906101fb9190610bc7565b61047e565b60405161020d9190610c22565b60405180910390f35b610230600480360381019061022b9190610bc7565b6104f5565b60405161023d9190610c22565b60405180910390f35b610260600480360381019061025b9190610d1e565b610518565b60405161026d9190610c4c565b60405180910390f35b60606003805461028590610d8d565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190610d8d565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b60008061031361059f565b90506103208185856105a7565b600191505092915050565b6000600254905090565b60008061034061059f565b905061034d858285610770565b6103588585856107fc565b60019150509392505050565b60006006905090565b60008061037861059f565b905061039981858561038a8589610518565b6103949190610ded565b6105a7565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600480546103fb90610d8d565b80601f016020809104026020016040519081016040528092919081815260200182805461042790610d8d565b80156104745780601f1061044957610100808354040283529160200191610474565b820191906000526020600020905b81548152906001019060200180831161045757829003601f168201915b5050505050905090565b60008061048961059f565b905060006104978286610518565b9050838110156104dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d390610e93565b60405180910390fd5b6104e982868684036105a7565b60019250505092915050565b60008061050061059f565b905061050d8185856107fc565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610616576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060d90610f25565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610685576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067c90610fb7565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516107639190610c4c565b60405180910390a3505050565b600061077c8484610518565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146107f657818110156107e8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107df90611023565b60405180910390fd5b6107f584848484036105a7565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361086b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610862906110b5565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108da576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108d190611147565b60405180910390fd5b6108e5838383610a72565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561096b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610962906111d9565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610a599190610c4c565b60405180910390a3610a6c848484610a77565b50505050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610ab6578082015181840152602081019050610a9b565b60008484015250505050565b6000601f19601f8301169050919050565b6000610ade82610a7c565b610ae88185610a87565b9350610af8818560208601610a98565b610b0181610ac2565b840191505092915050565b60006020820190508181036000830152610b268184610ad3565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610b5e82610b33565b9050919050565b610b6e81610b53565b8114610b7957600080fd5b50565b600081359050610b8b81610b65565b92915050565b6000819050919050565b610ba481610b91565b8114610baf57600080fd5b50565b600081359050610bc181610b9b565b92915050565b60008060408385031215610bde57610bdd610b2e565b5b6000610bec85828601610b7c565b9250506020610bfd85828601610bb2565b9150509250929050565b60008115159050919050565b610c1c81610c07565b82525050565b6000602082019050610c376000830184610c13565b92915050565b610c4681610b91565b82525050565b6000602082019050610c616000830184610c3d565b92915050565b600080600060608486031215610c8057610c7f610b2e565b5b6000610c8e86828701610b7c565b9350506020610c9f86828701610b7c565b9250506040610cb086828701610bb2565b9150509250925092565b600060ff82169050919050565b610cd081610cba565b82525050565b6000602082019050610ceb6000830184610cc7565b92915050565b600060208284031215610d0757610d06610b2e565b5b6000610d1584828501610b7c565b91505092915050565b60008060408385031215610d3557610d34610b2e565b5b6000610d4385828601610b7c565b9250506020610d5485828601610b7c565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610da557607f821691505b602082108103610db857610db7610d5e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610df882610b91565b9150610e0383610b91565b9250828201905080821115610e1b57610e1a610dbe565b5b92915050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610e7d602583610a87565b9150610e8882610e21565b604082019050919050565b60006020820190508181036000830152610eac81610e70565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000610f0f602483610a87565b9150610f1a82610eb3565b604082019050919050565b60006020820190508181036000830152610f3e81610f02565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000610fa1602283610a87565b9150610fac82610f45565b604082019050919050565b60006020820190508181036000830152610fd081610f94565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b600061100d601d83610a87565b915061101882610fd7565b602082019050919050565b6000602082019050818103600083015261103c81611000565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b600061109f602583610a87565b91506110aa82611043565b604082019050919050565b600060208201905081810360008301526110ce81611092565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b6000611131602383610a87565b915061113c826110d5565b604082019050919050565b6000602082019050818103600083015261116081611124565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b60006111c3602683610a87565b91506111ce82611167565b604082019050919050565b600060208201905081810360008301526111f2816111b6565b905091905056fea2646970667358221220bd76a0877c61d26a928dd36a2ac3491d00e9086a429df7883853cc988a8c1cbf64736f6c63430008120033"); +const L2_FEE_VAULT: Address = address!("530000000000000000000000000000000000000a"); + +fn main() { + println!("start test eth_fee_normal"); + eth_fee_normal(); + println!("start test alt_fee_normal"); + alt_fee_normal(); +} + +fn alt_fee_normal() { + let account_from = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let account_to = address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"); + + // use erc20 gas token for txn. + let tx = TxEnv { + caller: account_from, + gas_limit: 31000u64, + transact_to: account_to.into(), + value: U256::from(1_000u64), + data: Bytes::new(), + nonce: None, + chain_id: None, + fee_token_id: Some(1), + fee_limit: None, + gas_price: U256::from(10u64.pow(9)), + ..Default::default() + }; + + // 1 ETH = 4000 USDT. + let (account_from_balance, erc20_value, erc20_value_vault, erc20_balance_evm) = + exec_alt_fee_txn(1, U256::from(1u64), U256::from(250000000u64), tx); + + assert!( + account_from_balance.to::() == 999999999999999000, + "Only 1000wei must have been transferred." + ); //Only the value 1_000 wei was transferred. + + assert!( + erc20_value.to::() == 199916000, + "Gas fees should use: 84,000" + ); //Gas fees used: 84,000 + + assert!( + erc20_value_vault.to::() == 84000, + "Gas fees should use: 84,000" + ); //recive gas fees: 84,000 + + assert!(erc20_value.eq(&erc20_balance_evm), "Gas fees used: 84,000") //Gas fees used: 84,000 +} + +fn exec_alt_fee_txn( + token_id: u16, + scale_value: U256, + price_ratio_value: U256, + tx: TxEnv, +) -> (U256, U256, U256, U256) { + let account_from = tx.caller; + let account_to = *tx.transact_to.to().unwrap(); + + let mut cache_db = CacheDB::new(EmptyDB::default()); + let token_account = address!("fab77965cAfB593Bd86E2e8073407CAb7fD2f6c4"); + let token_account_info = AccountInfo { + nonce: 0_u64, + balance: U256::from(1_000_000_000_000_000_000u128), + code_hash: keccak256(Bytes::new()), + code: Some(Bytecode::new_legacy(ERC20_DEPLOYED_CODE.clone())), + code_size: 0, + }; + // cache_db.insert_contract(token_account); + cache_db.insert_account_info(token_account, token_account_info.clone()); + + // Calculate the storage location of account_from in the _balances mapping + // Storage location of Solidity mapping = keccak256(abi.encode(key, slot)) + let balance_slot = U256::ZERO; // slot of _balances mapping in ERC20. + let mut data = [0u8; 64]; + data[12..32].copy_from_slice(account_from.as_slice()); // The address occupies 20 bytes, left-padded to 32 bytes. + data[32..64].copy_from_slice(&balance_slot.to_be_bytes::<32>()); // The slot occupies 32 bytes. + + let storage_key = keccak256(&data); + let storage_key_u256 = U256::from_be_bytes(storage_key.0); + + // Set the balance to 200000000 + let balance_value = U256::from(200000000u64); + let _ = cache_db.insert_account_storage(token_account, storage_key_u256, balance_value); + + let balance_slot = U256::ZERO; // slot of _balances mapping in ERC20. + let mut data = [0u8; 64]; + data[12..32].copy_from_slice(L2_FEE_VAULT.as_slice()); // The address occupies 20 bytes, left-padded to 32 bytes. + data[32..64].copy_from_slice(&balance_slot.to_be_bytes::<32>()); // The slot occupies 32 bytes. + + let storage_key = keccak256(&data); + let storage_key_u256_vault = U256::from_be_bytes(storage_key.0); + + // Set ERC20PriceOracle storage + let oracle_address = address!("5300000000000000000000000000000000000021"); + + // Calculate base slot for tokenRegistry[token_id] + // tokenRegistry is at slot 0 + let token_registry_slot = U256::from(151u64).to_be_bytes_vec(); + let mut token_id_bytes = [0u8; 32]; + token_id_bytes[30..32].copy_from_slice(&token_id.to_be_bytes()); + + let mut token_registry_pre_image = token_id_bytes.to_vec(); + token_registry_pre_image.extend_from_slice(&token_registry_slot); + let token_registry_base = keccak256(&token_registry_pre_image); + let token_registry_base_u256 = U256::from_be_bytes(token_registry_base.0); + + // TokenInfo struct layout: + // slot + 0: tokenAddress (address, 20 bytes) + 12 bytes padding + // slot + 1: balanceSlot (bytes32, 32 bytes) + // slot + 2: isActive (bool, 1 byte) + decimals (uint8, 1 byte) + 30 bytes padding + // slot + 3: scale (uint256, 32 bytes) + + // Set tokenAddress at slot + 0 + let token_address_value = U256::from_be_bytes(token_account.into_word().into()); + let _ = cache_db.insert_account_storage( + oracle_address, + token_registry_base_u256, + token_address_value, + ); + + // Set balanceSlot at slot + 1 (using slot 0 for ERC20 balance mapping) + let balance_slot_value = U256::ZERO; + let _ = cache_db.insert_account_storage( + oracle_address, + token_registry_base_u256 + U256::from(1), + balance_slot_value, + ); + + // Set isActive and decimals at slot + 2 + // isActive = true (1), decimals = 6 + // In storage: rightmost byte (byte 31) is isActive, byte 30 is decimals + let mut slot_2_bytes = [0u8; 32]; + slot_2_bytes[30] = 6; // decimals + slot_2_bytes[31] = 1; // isActive = true + let slot_2_value = U256::from_be_bytes(slot_2_bytes); + let _ = cache_db.insert_account_storage( + oracle_address, + token_registry_base_u256 + U256::from(2), + slot_2_value, + ); + + // Set scale at slot + 3 + let _ = cache_db.insert_account_storage( + oracle_address, + token_registry_base_u256 + U256::from(3), + scale_value, + ); + + // Set priceRatio for tokenID + // priceRatio is at slot 2 + let price_ratio_slot = U256::from(153u64).to_be_bytes_vec(); + let mut price_ratio_pre_image = token_id_bytes.to_vec(); + price_ratio_pre_image.extend_from_slice(&price_ratio_slot); + + let price_ratio_storage_slot = keccak256(&price_ratio_pre_image); + let price_ratio_storage_slot_u256 = U256::from_be_bytes(price_ratio_storage_slot.0); + + let _ = cache_db.insert_account_storage( + oracle_address, + price_ratio_storage_slot_u256, + price_ratio_value, + ); + + let acc_info = AccountInfo { + nonce: 0_u64, + balance: U256::from(1_000_000_000_000_000_000u128), + code_hash: keccak256(Bytes::new()), + code: None, + code_size: 0, + }; + cache_db.insert_account_info(account_from, acc_info.clone()); + let mut block_env = BlockEnv::default(); + block_env.coinbase = L2_FEE_VAULT; + let mut evm = Evm::builder() + .with_db(&mut cache_db) + .with_block_env(block_env) + .build(); + + // use erc20 gas token for txn. + let mut tx = tx; + + // process txn + tx.morph.is_l1_msg = false; + tx.morph.rlp_bytes = Some(Bytes::default()); + evm.context.evm.env.tx = tx; + let _ = evm.transact_commit(); + + let account_from_balance = evm + .context + .evm + .inner + .db + .load_account(account_from) + .unwrap() + .info + .balance; + println!("account_from_balance: {:?}", account_from_balance); + + let account_to_balance = evm + .context + .evm + .inner + .db + .load_account(account_to) + .unwrap() + .info + .balance; + println!("account_to_balance: {:?}", account_to_balance); + + let erc20_value = evm + .context + .evm + .db + .storage(token_account, storage_key_u256) + .unwrap_or_default(); + println!("account_from_erc20_value: {:?}", erc20_value); + + let erc20_value_vault = evm + .context + .evm + .db + .storage(token_account, storage_key_u256_vault) + .unwrap_or_default(); + + let method_id = [0x70u8, 0xa0, 0x82, 0x31]; + let mut calldata = Vec::with_capacity(36); + calldata.extend_from_slice(&method_id); + calldata.extend_from_slice(&[0u8; 12]); // Pad address to 32 bytes + calldata.extend_from_slice(account_from.as_slice()); + + let mut token_balance_tx = TxEnv { + caller: Address::default(), + gas_limit: u64::MAX, + transact_to: token_account.into(), + value: U256::ZERO, + data: Bytes::from(calldata), + nonce: None, + chain_id: None, + ..Default::default() + }; + token_balance_tx.morph.is_l1_msg = false; + token_balance_tx.morph.rlp_bytes = Some(Bytes::default()); + evm.context.evm.env.tx = token_balance_tx; + + let erc20_balance_evm = match evm.transact() { + Ok(result) => { + if result.result.is_success() { + // Parse the returned balance (32 bytes) + if let Some(output) = result.result.output() { + if output.len() >= 32 { + U256::from_be_slice(&output[..32]) + } else { + U256::ZERO + } + } else { + U256::ZERO + } + } else { + U256::ZERO + } + } + Err(_) => { + println!("get_erc20_balance error"); + U256::ZERO + } + }; + println!("account_from_erc20_value_evm: {:?}", erc20_balance_evm); + + ( + account_from_balance, + erc20_value, + erc20_value_vault, + erc20_balance_evm, + ) +} + +fn eth_fee_normal() { + let mut cache_db = CacheDB::new(EmptyDB::default()); + + let account_from = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let account_to = address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"); + + let acc_info = AccountInfo { + nonce: 0_u64, + balance: U256::from(1_000_000_000_000_000_000u128), + code_hash: keccak256(Bytes::new()), + code: None, + code_size: 0, + }; + cache_db.insert_account_info(account_from, acc_info.clone()); + + let acc_info_to = AccountInfo { + nonce: 0_u64, + balance: U256::ZERO, + code_hash: keccak256(Bytes::new()), + code: None, + code_size: 0, + }; + cache_db.insert_account_info(account_to, acc_info_to.clone()); + + let mut evm = Evm::builder().with_db(cache_db).build(); + + // use eth gas token. + let mut tx = TxEnv { + caller: account_from, + gas_limit: 21000u64, + transact_to: account_to.into(), + value: U256::from(1_000u64), + data: Bytes::new(), + nonce: None, + chain_id: None, + fee_token_id: None, + ..Default::default() + }; + + // process txn + tx.morph.is_l1_msg = false; + tx.morph.rlp_bytes = Some(Bytes::default()); + evm.context.evm.env.tx = tx; + let _ = evm.transact_commit(); + + let account_from_balance = evm + .context + .evm + .inner + .db + .load_account(account_from) + .unwrap() + .info + .balance; + println!("account_from_balance: {:?}", account_from_balance); + + let account_to_balance = evm + .context + .evm + .inner + .db + .load_account(account_to) + .unwrap() + .info + .balance; + println!("account_to_balance: {:?}", account_to_balance); + + assert!( + account_from_balance.to::() == 999999999999999000, + "Only 1000wei must have been transferred." + ); //Only the value 1_000 wei was transferred. + + assert!( + account_to_balance.to::() == 1000, + "account_to should receive 1000 wei." + ); //account_to should receive 1000 wei. +} diff --git a/bins/revme/src/cmd/statetest/models/spec.rs b/bins/revme/src/cmd/statetest/models/spec.rs index f33ad68f6e..501b49f047 100644 --- a/bins/revme/src/cmd/statetest/models/spec.rs +++ b/bins/revme/src/cmd/statetest/models/spec.rs @@ -53,7 +53,6 @@ impl SpecName { Self::ByzantiumToConstantinopleAt5 | Self::Constantinople => { panic!("Overridden with PETERSBURG") } - Self::Osaka => panic!("Osaka is not implemented"), Self::Unknown => panic!("Unknown spec"), } } diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index 80ab935d5d..a0e9a6b609 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -274,8 +274,7 @@ pub fn execute_test_suite( let bytecode = to_analysed(Bytecode::new_raw(info.code)); let acc_info = revm::primitives::AccountInfo { balance: info.balance, - #[cfg(feature = "morph")] - code_size, + code_size: 0, code_hash: keccak_code_hash, #[cfg(feature = "morph-poseidon-codehash")] poseidon_code_hash, diff --git a/crates/interpreter/src/instructions/bitwise.rs b/crates/interpreter/src/instructions/bitwise.rs index 371535b413..26f465906c 100644 --- a/crates/interpreter/src/instructions/bitwise.rs +++ b/crates/interpreter/src/instructions/bitwise.rs @@ -20,8 +20,7 @@ pub fn gt(interpreter: &mut Interpreter, _host: &mut H) { /// Implements the CLZ instruction - count leading zeros. pub fn clz(interpreter: &mut Interpreter, _host: &mut H) { - // check!(interpreter, OSAKA); - // gas!(interpreter, gas::LOW); + gas!(interpreter, gas::LOW); pop_top!(interpreter, op1); let leading_zeros = op1.leading_zeros(); diff --git a/crates/precompile/Cargo.toml b/crates/precompile/Cargo.toml index 761e5f6126..9e22b37f15 100644 --- a/crates/precompile/Cargo.toml +++ b/crates/precompile/Cargo.toml @@ -55,6 +55,11 @@ kzg-rs = { version = "0.2.2", default-features = false, optional = true } # BLS12-381 precompiles blst = { version = "0.3.13", optional = true } +ark-bls12-381 = { version = "0.5", features = ["curve"] } +ark-ec = { version = "0.5", default-features = false } +ark-ff = { version = "0.5", default-features = false } +ark-serialize = { version = "0.5", default-features = false } + # p256verify precompile p256 = { version = "0.13.2", optional = true, default-features = false, features = [ "ecdsa", @@ -71,6 +76,12 @@ rstest = "0.22.0" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" +secp256k1 = { version = ">=0.28, <=0.29", default-features = false, features = [ + "alloc", + "recovery", + "rand", + "global-context", +] } [features] default = ["std", "kzg-rs", "portable"] @@ -96,7 +107,7 @@ negate-optimism-default-handler = [ "revm-primitives/negate-optimism-default-handler", ] -morph = ["revm-primitives/morph"] +morph = ["revm-primitives/morph","secp256r1"] # Morph default handler enabled Morph handler register by default in EvmBuilder. morph-default-handler = [ "morph", diff --git a/crates/precompile/src/bls12_381/g1_add.rs b/crates/precompile/src/bls12_381/g1_add.rs index da671aab49..6ca26ded97 100644 --- a/crates/precompile/src/bls12_381/g1_add.rs +++ b/crates/precompile/src/bls12_381/g1_add.rs @@ -11,7 +11,7 @@ pub const PRECOMPILE: PrecompileWithAddress = /// BLS12_G1ADD precompile address. pub const ADDRESS: u64 = 0x0b; /// Base gas fee for BLS12-381 g1_add operation. -const BASE_GAS_FEE: u64 = 500; +const BASE_GAS_FEE: u64 = 375; /// Input length of g1_add operation. const INPUT_LENGTH: usize = 256; diff --git a/crates/precompile/src/bls12_381/g2_add.rs b/crates/precompile/src/bls12_381/g2_add.rs index 9b1e26f184..1b312f128f 100644 --- a/crates/precompile/src/bls12_381/g2_add.rs +++ b/crates/precompile/src/bls12_381/g2_add.rs @@ -11,7 +11,7 @@ pub const PRECOMPILE: PrecompileWithAddress = /// BLS12_G2ADD precompile address. pub const ADDRESS: u64 = 0x0e; /// Base gas fee for BLS12-381 g2_add operation. -const BASE_GAS_FEE: u64 = 800; +const BASE_GAS_FEE: u64 = 600; /// Input length of g2_add operation. const INPUT_LENGTH: usize = 512; diff --git a/crates/precompile/src/bls12_381/g2_mul.rs b/crates/precompile/src/bls12_381/g2_mul.rs index e39edf0751..c7c9d4b202 100644 --- a/crates/precompile/src/bls12_381/g2_mul.rs +++ b/crates/precompile/src/bls12_381/g2_mul.rs @@ -12,7 +12,7 @@ pub const PRECOMPILE: PrecompileWithAddress = /// BLS12_G2MUL precompile address. pub const ADDRESS: u64 = 0x0f; /// Base gas fee for BLS12-381 g2_mul operation. -pub(super) const BASE_GAS_FEE: u64 = 45000; +pub(super) const BASE_GAS_FEE: u64 = 22500; /// Input length of g2_mul operation. pub(super) const INPUT_LENGTH: usize = 288; diff --git a/crates/precompile/src/bls12_381/map_fp2_to_g2.rs b/crates/precompile/src/bls12_381/map_fp2_to_g2.rs index 584008ec62..c04d3bb2b1 100644 --- a/crates/precompile/src/bls12_381/map_fp2_to_g2.rs +++ b/crates/precompile/src/bls12_381/map_fp2_to_g2.rs @@ -15,7 +15,7 @@ pub const PRECOMPILE: PrecompileWithAddress = pub const ADDRESS: u64 = 0x13; /// Base gas fee for BLS12-381 map_fp2_to_g2 operation. -const BASE_GAS_FEE: u64 = 75000; +const BASE_GAS_FEE: u64 = 23800; /// Field-to-curve call expects 128 bytes as an input that is interpreted as /// an element of Fp2. Output of this call is 256 bytes and is an encoded G2 diff --git a/crates/precompile/src/bls12_381/pairing.rs b/crates/precompile/src/bls12_381/pairing.rs index a786a8b7dc..e2acbfed78 100644 --- a/crates/precompile/src/bls12_381/pairing.rs +++ b/crates/precompile/src/bls12_381/pairing.rs @@ -15,9 +15,9 @@ pub const PRECOMPILE: PrecompileWithAddress = pub const ADDRESS: u64 = 0x11; /// Multiplier gas fee for BLS12-381 pairing operation. -const PAIRING_MULTIPLIER_BASE: u64 = 43000; +const PAIRING_MULTIPLIER_BASE: u64 = 32600; /// Offset gas fee for BLS12-381 pairing operation. -const PAIRING_OFFSET_BASE: u64 = 65000; +const PAIRING_OFFSET_BASE: u64 = 37700; /// Input length of pairing operation. const INPUT_LENGTH: usize = 384; diff --git a/crates/precompile/src/bls12_381_ark.rs b/crates/precompile/src/bls12_381_ark.rs new file mode 100644 index 0000000000..85ce68dd76 --- /dev/null +++ b/crates/precompile/src/bls12_381_ark.rs @@ -0,0 +1,44 @@ +//! BLS12-381 precompiles added in [`EIP-2537`](https://eips.ethereum.org/EIPS/eip-2537) +//! For more details check modules for each precompile. +use crate::PrecompileWithAddress; + +mod arkworks; +pub mod g1_add; +pub mod g1_msm; +pub mod g2_add; +pub mod g2_msm; +pub mod map_fp2_to_g2; +pub mod map_fp_to_g1; +pub mod pairing; +mod utils; + +// Re-export type aliases for use in submodules +use crate::bls12_381_const::{FP_LENGTH, SCALAR_LENGTH}; +/// G1 point represented as two field elements (x, y coordinates) +pub type G1Point = ([u8; FP_LENGTH], [u8; FP_LENGTH]); +/// G2 point represented as four field elements (x0, x1, y0, y1 coordinates) +pub type G2Point = ( + [u8; FP_LENGTH], + [u8; FP_LENGTH], + [u8; FP_LENGTH], + [u8; FP_LENGTH], +); +/// G1 point and scalar pair for MSM operations +pub type G1PointScalar = (G1Point, [u8; SCALAR_LENGTH]); +/// G2 point and scalar pair for MSM operations +pub type G2PointScalar = (G2Point, [u8; SCALAR_LENGTH]); +pub type PairingPair = (G1Point, G2Point); + +/// Returns the BLS12-381 precompiles with their addresses. +pub fn precompiles() -> impl Iterator { + [ + g1_add::PRECOMPILE, + g1_msm::PRECOMPILE, + g2_add::PRECOMPILE, + g2_msm::PRECOMPILE, + pairing::PRECOMPILE, + map_fp_to_g1::PRECOMPILE, + map_fp2_to_g2::PRECOMPILE, + ] + .into_iter() +} diff --git a/crates/precompile/src/bls12_381_ark/arkworks.rs b/crates/precompile/src/bls12_381_ark/arkworks.rs new file mode 100644 index 0000000000..a513d4a25b --- /dev/null +++ b/crates/precompile/src/bls12_381_ark/arkworks.rs @@ -0,0 +1,530 @@ +//! BLS12-381 precompile using Arkworks BLS12-381 implementation. +use crate::bls12_381_ark::{G1Point, G2Point, PairingPair}; +use crate::{ + bls12_381_const::{FP_LENGTH, G1_LENGTH, G2_LENGTH, SCALAR_LENGTH}, + PrecompileError, +}; +use ark_bls12_381::{Bls12_381, Fq, Fq2, Fr, G1Affine, G1Projective, G2Affine, G2Projective}; +use ark_ec::{ + hashing::{curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurve}, + pairing::Pairing, + AffineRepr, CurveGroup, VariableBaseMSM, +}; +use ark_ff::{One, PrimeField, Zero}; + +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use std::vec::Vec; + +/// Reads a single `Fp` field element from the input slice. +/// +/// Takes a byte slice in Big Endian format and attempts to interpret it as an +/// elliptic curve field element. Returns an error if the bytes do not form +/// a valid field element. +/// +/// # Panics +/// +/// Panics if the input is not exactly 48 bytes long. +#[inline] +fn read_fp(input_be: &[u8]) -> Result { + assert_eq!(input_be.len(), FP_LENGTH, "input must be {FP_LENGTH} bytes"); + + let mut input_le = [0u8; FP_LENGTH]; + input_le.copy_from_slice(input_be); + + // Reverse in-place to convert from big-endian to little-endian. + input_le.reverse(); + + Fq::deserialize_uncompressed(&input_le[..]).map_err(|_| PrecompileError::NonCanonicalFp) +} + +/// Encodes an `Fp` field element into a big-endian byte array. +/// +/// # Panics +/// +/// Panics if serialization fails, which should not occur for a valid field element. +fn encode_fp(fp: &Fq) -> [u8; FP_LENGTH] { + let mut bytes = [0u8; FP_LENGTH]; + fp.serialize_uncompressed(&mut bytes[..]) + .expect("Failed to serialize field element"); + bytes.reverse(); + bytes +} + +/// Reads a Fp2 (quadratic extension field element) from the input slices. +/// +/// Parses two Fp field elements in Big Endian format for the Fp2 element. +/// +/// # Panics +/// +/// Panics if either input is not exactly 48 bytes long. +#[inline] +fn read_fp2(input_1: &[u8; FP_LENGTH], input_2: &[u8; FP_LENGTH]) -> Result { + let fp_1 = read_fp(input_1)?; + let fp_2 = read_fp(input_2)?; + + Ok(Fq2::new(fp_1, fp_2)) +} + +/// Creates a new `G1` point from the given `x` and `y` coordinates. +/// +/// Constructs a point on the G1 curve from its affine coordinates. +/// +/// Note: The point at infinity which is represented as (0,0) is +/// handled specifically. +#[inline] +fn new_g1_point_no_subgroup_check(px: Fq, py: Fq) -> Result { + if px.is_zero() && py.is_zero() { + Ok(G1Affine::zero()) + } else { + // We cannot use `G1Affine::new` because that triggers an assert if the point is not on the curve. + let point = G1Affine::new_unchecked(px, py); + if !point.is_on_curve() { + return Err(PrecompileError::Bls12381G1NotOnCurve); + } + Ok(point) + } +} + +/// Creates a new `G2` point from the given Fq2 coordinates. +/// +/// G2 points in BLS12_381 are defined over a quadratic extension field Fq2. +/// This function takes two Fq2 elements representing the x and y coordinates +/// and creates a G2 point. +/// +/// Note: The point at infinity which is represented as (0,0) is +/// handled specifically. +#[inline] +fn new_g2_point_no_subgroup_check(x: Fq2, y: Fq2) -> Result { + let point = if x.is_zero() && y.is_zero() { + G2Affine::zero() + } else { + // We cannot use `G2Affine::new` because that triggers an assert if the point is not on the curve. + let point = G2Affine::new_unchecked(x, y); + if !point.is_on_curve() { + return Err(PrecompileError::Bls12381G2NotOnCurve); + } + point + }; + + Ok(point) +} + +/// Reads a G1 point from the input slices. +/// +/// Parses a G1 point from byte slices by reading two field elements +/// representing the x and y coordinates in Big Endian format. +/// Also performs a subgroup check to ensure the point is in the correct subgroup. +/// +/// # Panics +/// +/// Panics if the inputs are not exactly 48 bytes long. +#[inline] +fn read_g1(x: &[u8; FP_LENGTH], y: &[u8; FP_LENGTH]) -> Result { + let point = read_g1_no_subgroup_check(x, y)?; + if !point.is_in_correct_subgroup_assuming_on_curve() { + return Err(PrecompileError::Bls12381G1NotInSubgroup); + } + Ok(point) +} + +/// Reads a G1 point without performing a subgroup check. +/// +/// Note: Skipping subgroup checks can introduce security issues. +/// This method should only be called if: +/// - The EIP specifies that no subgroup check should be performed +/// - One can be certain that the point is in the correct subgroup. +#[inline] +fn read_g1_no_subgroup_check( + x: &[u8; FP_LENGTH], + y: &[u8; FP_LENGTH], +) -> Result { + let px = read_fp(x)?; + let py = read_fp(y)?; + new_g1_point_no_subgroup_check(px, py) +} + +/// Encodes a G1 point into a byte array. +/// +/// Converts a G1 point to affine coordinates and serializes the x and y coordinates +/// as big-endian byte arrays. +#[inline] +fn encode_g1_point(input: &G1Affine) -> [u8; G1_LENGTH] { + let mut output = [0u8; G1_LENGTH]; + + let Some((x, y)) = input.xy() else { + return output; // Point at infinity, return all zeros + }; + + let x_encoded = encode_fp(&x); + let y_encoded = encode_fp(&y); + + // Copy the encoded values to the output + output[..FP_LENGTH].copy_from_slice(&x_encoded); + output[FP_LENGTH..].copy_from_slice(&y_encoded); + + output +} + +/// Reads a G2 point from the input slices. +/// +/// Parses a G2 point from byte slices by reading four field elements +/// representing the x and y coordinates in Big Endian format. +/// Also performs a subgroup check to ensure the point is in the correct subgroup. +#[inline] +fn read_g2( + a_x_0: &[u8; FP_LENGTH], + a_x_1: &[u8; FP_LENGTH], + a_y_0: &[u8; FP_LENGTH], + a_y_1: &[u8; FP_LENGTH], +) -> Result { + let point = read_g2_no_subgroup_check(a_x_0, a_x_1, a_y_0, a_y_1)?; + if !point.is_in_correct_subgroup_assuming_on_curve() { + return Err(PrecompileError::Bls12381G1NotInSubgroup); + } + Ok(point) +} + +/// Reads a G2 point without performing a subgroup check. +/// +/// Note: Skipping subgroup checks can introduce security issues. +/// This method should only be called if: +/// - The EIP specifies that no subgroup check should be performed +/// - One can be certain that the point is in the correct subgroup. +#[inline] +fn read_g2_no_subgroup_check( + a_x_0: &[u8; FP_LENGTH], + a_x_1: &[u8; FP_LENGTH], + a_y_0: &[u8; FP_LENGTH], + a_y_1: &[u8; FP_LENGTH], +) -> Result { + let x = read_fp2(a_x_0, a_x_1)?; + let y = read_fp2(a_y_0, a_y_1)?; + new_g2_point_no_subgroup_check(x, y) +} + +/// Encodes a G2 point into a byte array. +/// +/// Converts a G2 point to affine coordinates and serializes the coordinates +/// as big-endian byte arrays. +#[inline] +fn encode_g2_point(input: &G2Affine) -> [u8; G2_LENGTH] { + let mut output = [0u8; G2_LENGTH]; + + let Some((x, y)) = input.xy() else { + return output; // Point at infinity, return all zeros + }; + + let x_c0_encoded = encode_fp(&x.c0); + let x_c1_encoded = encode_fp(&x.c1); + let y_c0_encoded = encode_fp(&y.c0); + let y_c1_encoded = encode_fp(&y.c1); + + output[..FP_LENGTH].copy_from_slice(&x_c0_encoded); + output[FP_LENGTH..2 * FP_LENGTH].copy_from_slice(&x_c1_encoded); + output[2 * FP_LENGTH..3 * FP_LENGTH].copy_from_slice(&y_c0_encoded); + output[3 * FP_LENGTH..4 * FP_LENGTH].copy_from_slice(&y_c1_encoded); + + output +} + +/// Extracts a scalar from a byte slice representation, decoding the input as a Big Endian +/// unsigned integer. +/// +/// Note: We do not check that the scalar is a canonical Fr element, because the EIP specifies: +/// * The corresponding integer is not required to be less than or equal than main subgroup order. +#[inline] +fn read_scalar(input: &[u8]) -> Result { + if input.len() != SCALAR_LENGTH { + return Err(PrecompileError::Bls12381ScalarInputLength); + } + + Ok(Fr::from_be_bytes_mod_order(input)) +} + +/// Performs point addition on two G1 points. +#[inline] +fn p1_add_affine(p1: &G1Affine, p2: &G1Affine) -> G1Affine { + let p1_proj: G1Projective = (*p1).into(); + let p3 = p1_proj + p2; + p3.into_affine() +} + +/// Performs point addition on two G2 points. +#[inline] +fn p2_add_affine(p1: &G2Affine, p2: &G2Affine) -> G2Affine { + let p1_proj: G2Projective = (*p1).into(); + let p3 = p1_proj + p2; + p3.into_affine() +} + +/// Performs multi-scalar multiplication (MSM) for G1 points +/// +/// Takes a vector of G1 points and corresponding scalars, and returns their weighted sum +/// +/// Note: This method assumes that `g1_points` does not contain any points at infinity. +#[inline] +fn p1_msm(g1_points: Vec, scalars: Vec) -> G1Affine { + assert_eq!( + g1_points.len(), + scalars.len(), + "number of scalars should equal the number of g1 points" + ); + + if g1_points.is_empty() { + return G1Affine::zero(); + } + + if g1_points.len() == 1 { + let big_int = scalars[0].into_bigint(); + return g1_points[0].mul_bigint(big_int).into_affine(); + } + + // Perform multi-scalar multiplication + G1Projective::msm(&g1_points, &scalars) + .expect("MSM should succeed") + .into_affine() +} + +/// Performs multi-scalar multiplication (MSM) for G2 points +/// +/// Takes a vector of G2 points and corresponding scalars, and returns their weighted sum +/// +/// Note: This method assumes that `g2_points` does not contain any points at infinity. +#[inline] +fn p2_msm(g2_points: Vec, scalars: Vec) -> G2Affine { + assert_eq!( + g2_points.len(), + scalars.len(), + "number of scalars should equal the number of g2 points" + ); + + if g2_points.is_empty() { + return G2Affine::zero(); + } + + if g2_points.len() == 1 { + let big_int = scalars[0].into_bigint(); + return g2_points[0].mul_bigint(big_int).into_affine(); + } + + // Perform multi-scalar multiplication + G2Projective::msm(&g2_points, &scalars) + .expect("MSM should succeed") + .into_affine() +} + +/// Maps a field element to a G1 point +/// +/// Takes a field element (Fq) and returns the corresponding G1 point in affine form +#[inline] +fn map_fp_to_g1(fp: &Fq) -> G1Affine { + WBMap::map_to_curve(*fp) + .expect("map_to_curve is infallible") + .clear_cofactor() +} + +/// Maps a field element to a G2 point +/// +/// Takes a field element (Fq2) and returns the corresponding G2 point in affine form +#[inline] +fn map_fp2_to_g2(fp2: &Fq2) -> G2Affine { + WBMap::map_to_curve(*fp2) + .expect("map_to_curve is infallible") + .clear_cofactor() +} + +/// pairing_check performs a pairing check on a list of G1 and G2 point pairs and +/// returns true if the result is equal to the identity element. +#[inline] +pub(crate) fn pairing_check(pairs: &[(G1Affine, G2Affine)]) -> bool { + if pairs.is_empty() { + return true; + } + + let (g1_points, g2_points): (Vec, Vec) = pairs.iter().copied().unzip(); + + let pairing_result = Bls12_381::multi_pairing(&g1_points, &g2_points); + pairing_result.0.is_one() +} + +/// pairing_check_bytes performs a pairing check on a list of G1 and G2 point pairs taking byte inputs. +#[inline] +pub(crate) fn pairing_check_bytes(pairs: &[PairingPair]) -> Result { + if pairs.is_empty() { + return Ok(true); + } + + let mut parsed_pairs = Vec::with_capacity(pairs.len()); + for ((g1_x, g1_y), (g2_x_0, g2_x_1, g2_y_0, g2_y_1)) in pairs { + // Check if G1 point is zero (point at infinity) + let g1_is_zero = g1_x.iter().all(|&b| b == 0) && g1_y.iter().all(|&b| b == 0); + + // Check if G2 point is zero (point at infinity) + let g2_is_zero = g2_x_0.iter().all(|&b| b == 0) + && g2_x_1.iter().all(|&b| b == 0) + && g2_y_0.iter().all(|&b| b == 0) + && g2_y_1.iter().all(|&b| b == 0); + + // Skip this pair if either point is at infinity as it's a no-op + if g1_is_zero || g2_is_zero { + // Still need to validate the non-zero point if one exists + if !g1_is_zero { + let _ = read_g1(g1_x, g1_y)?; + } + if !g2_is_zero { + let _ = read_g2(g2_x_0, g2_x_1, g2_y_0, g2_y_1)?; + } + continue; + } + + let g1_point = read_g1(g1_x, g1_y)?; + let g2_point = read_g2(g2_x_0, g2_x_1, g2_y_0, g2_y_1)?; + parsed_pairs.push((g1_point, g2_point)); + } + + // If all pairs were filtered out, return true (identity element) + if parsed_pairs.is_empty() { + return Ok(true); + } + + Ok(pairing_check(&parsed_pairs)) +} + +// Byte-oriented versions of the functions for external API compatibility + +/// Performs point addition on two G1 points taking byte coordinates. +#[inline] +pub(crate) fn p1_add_affine_bytes( + a: G1Point, + b: G1Point, +) -> Result<[u8; G1_LENGTH], PrecompileError> { + let (a_x, a_y) = a; + let (b_x, b_y) = b; + // Parse first point + let p1 = read_g1_no_subgroup_check(&a_x, &a_y)?; + + // Parse second point + let p2 = read_g1_no_subgroup_check(&b_x, &b_y)?; + + // Perform addition + let result = p1_add_affine(&p1, &p2); + + // Encode result + Ok(encode_g1_point(&result)) +} + +/// Performs point addition on two G2 points taking byte coordinates. +#[inline] +pub(crate) fn p2_add_affine_bytes( + a: G2Point, + b: G2Point, +) -> Result<[u8; G2_LENGTH], PrecompileError> { + let (a_x_0, a_x_1, a_y_0, a_y_1) = a; + let (b_x_0, b_x_1, b_y_0, b_y_1) = b; + // Parse first point + let p1 = read_g2_no_subgroup_check(&a_x_0, &a_x_1, &a_y_0, &a_y_1)?; + + // Parse second point + let p2 = read_g2_no_subgroup_check(&b_x_0, &b_x_1, &b_y_0, &b_y_1)?; + + // Perform addition + let result = p2_add_affine(&p1, &p2); + + // Encode result + Ok(encode_g2_point(&result)) +} + +/// Maps a field element to a G1 point from bytes +#[inline] +pub(crate) fn map_fp_to_g1_bytes( + fp_bytes: &[u8; FP_LENGTH], +) -> Result<[u8; G1_LENGTH], PrecompileError> { + let fp = read_fp(fp_bytes)?; + let result = map_fp_to_g1(&fp); + Ok(encode_g1_point(&result)) +} + +/// Maps field elements to a G2 point from bytes +#[inline] +pub(crate) fn map_fp2_to_g2_bytes( + fp2_x: &[u8; FP_LENGTH], + fp2_y: &[u8; FP_LENGTH], +) -> Result<[u8; G2_LENGTH], PrecompileError> { + let fp2 = read_fp2(fp2_x, fp2_y)?; + let result = map_fp2_to_g2(&fp2); + Ok(encode_g2_point(&result)) +} + +/// Performs multi-scalar multiplication (MSM) for G1 points taking byte inputs. +#[inline] +pub(crate) fn p1_msm_bytes( + point_scalar_pairs: impl Iterator>, +) -> Result<[u8; G1_LENGTH], PrecompileError> { + let mut g1_points = Vec::new(); + let mut scalars = Vec::new(); + + // Parse all points and scalars + for pair_result in point_scalar_pairs { + let ((x, y), scalar_bytes) = pair_result?; + + // NB: MSM requires subgroup check + let point = read_g1(&x, &y)?; + + // Skip zero scalars after validating the point + if scalar_bytes.iter().all(|&b| b == 0) { + continue; + } + + let scalar = read_scalar(&scalar_bytes)?; + g1_points.push(point); + scalars.push(scalar); + } + + // Return point at infinity if no pairs were provided or all scalars were zero + if g1_points.is_empty() { + return Ok([0u8; G1_LENGTH]); + } + + // Perform MSM + let result = p1_msm(g1_points, scalars); + + // Encode result + Ok(encode_g1_point(&result)) +} + +/// Performs multi-scalar multiplication (MSM) for G2 points taking byte inputs. +#[inline] +pub(crate) fn p2_msm_bytes( + point_scalar_pairs: impl Iterator>, +) -> Result<[u8; G2_LENGTH], PrecompileError> { + let mut g2_points = Vec::new(); + let mut scalars = Vec::new(); + + // Parse all points and scalars + for pair_result in point_scalar_pairs { + let ((x_0, x_1, y_0, y_1), scalar_bytes) = pair_result?; + + // NB: MSM requires subgroup check + let point = read_g2(&x_0, &x_1, &y_0, &y_1)?; + + // Skip zero scalars after validating the point + if scalar_bytes.iter().all(|&b| b == 0) { + continue; + } + + let scalar = read_scalar(&scalar_bytes)?; + g2_points.push(point); + scalars.push(scalar); + } + + // Return point at infinity if no pairs were provided or all scalars were zero + if g2_points.is_empty() { + return Ok([0u8; G2_LENGTH]); + } + + // Perform MSM + let result = p2_msm(g2_points, scalars); + + // Encode result + Ok(encode_g2_point(&result)) +} diff --git a/crates/precompile/src/bls12_381_ark/g1_add.rs b/crates/precompile/src/bls12_381_ark/g1_add.rs new file mode 100644 index 0000000000..26002e6fd4 --- /dev/null +++ b/crates/precompile/src/bls12_381_ark/g1_add.rs @@ -0,0 +1,46 @@ +//! BLS12-381 G1 add precompile. More details in [`g1_add`] +use revm_primitives::Bytes; + +use crate::bls12_381_ark::arkworks; +use crate::bls12_381_ark::utils::{pad_g1_point, remove_g1_padding}; +use crate::PrecompileWithAddress; +use crate::{ + bls12_381_const::{G1_ADD_ADDRESS, G1_ADD_BASE_GAS_FEE, G1_ADD_INPUT_LENGTH, PADDED_G1_LENGTH}, + Precompile, PrecompileError, PrecompileOutput, PrecompileResult, +}; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G1ADD precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(G1_ADD_ADDRESS, Precompile::Standard(g1_add)); + +/// G1 addition call expects `256` bytes as an input that is interpreted as byte +/// concatenation of two G1 points (`128` bytes each). +/// Output is an encoding of addition operation result - single G1 point (`128` +/// bytes). +/// See also: +pub fn g1_add(input: &Bytes, gas_limit: u64) -> PrecompileResult { + if G1_ADD_BASE_GAS_FEE > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + if input.len() != G1_ADD_INPUT_LENGTH { + return Err(PrecompileError::Bls12381G1AddInputLength.into()); + } + + // Extract coordinates from padded input + let [a_x, a_y] = remove_g1_padding(&input[..PADDED_G1_LENGTH])?; + let [b_x, b_y] = remove_g1_padding(&input[PADDED_G1_LENGTH..])?; + + let a = (*a_x, *a_y); + let b = (*b_x, *b_y); + + let unpadded_result = arkworks::p1_add_affine_bytes(a, b)?; + + // Pad the result for EVM compatibility + let padded_result = pad_g1_point(&unpadded_result); + + Ok(PrecompileOutput::new( + G1_ADD_BASE_GAS_FEE, + padded_result.into(), + )) +} diff --git a/crates/precompile/src/bls12_381_ark/g1_msm.rs b/crates/precompile/src/bls12_381_ark/g1_msm.rs new file mode 100644 index 0000000000..79f8549ba7 --- /dev/null +++ b/crates/precompile/src/bls12_381_ark/g1_msm.rs @@ -0,0 +1,73 @@ +//! BLS12-381 G1 msm precompile. More details in [`g1_msm`] +use revm_primitives::Bytes; + +use crate::{ + bls12_381_ark::{ + arkworks::p1_msm_bytes, + utils::{pad_g1_point, remove_g1_padding}, + G1Point, + }, + bls12_381_const::{ + DISCOUNT_TABLE_G1_MSM, G1_MSM_ADDRESS, G1_MSM_BASE_GAS_FEE, G1_MSM_INPUT_LENGTH, + PADDED_G1_LENGTH, SCALAR_LENGTH, + }, + bls12_381_utils::msm_required_gas, + Precompile, PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress, +}; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G1MSM precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(G1_MSM_ADDRESS, Precompile::Standard(g1_msm)); +/// Implements EIP-2537 G1MSM precompile. +/// G1 multi-scalar-multiplication call expects `160*k` bytes as an input that is interpreted +/// as byte concatenation of `k` slices each of them being a byte concatenation +/// of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` +/// bytes). +/// Output is an encoding of multi-scalar-multiplication operation result - single G1 +/// point (`128` bytes). +/// See also: +pub fn g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let input_len = input.len(); + if input_len == 0 || input_len % G1_MSM_INPUT_LENGTH != 0 { + return Err(PrecompileError::Bls12381G1MsmInputLength.into()); + } + + let k = input_len / G1_MSM_INPUT_LENGTH; + let required_gas = msm_required_gas(k, &DISCOUNT_TABLE_G1_MSM, G1_MSM_BASE_GAS_FEE); + if required_gas > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + let mut valid_pairs_iter = (0..k).map(|i| { + let start = i * G1_MSM_INPUT_LENGTH; + let padded_g1 = &input[start..start + PADDED_G1_LENGTH]; + let scalar_bytes = &input[start + PADDED_G1_LENGTH..start + G1_MSM_INPUT_LENGTH]; + + // Remove padding from G1 point - this validates padding format + let [x, y] = remove_g1_padding(padded_g1)?; + let scalar_array: [u8; SCALAR_LENGTH] = scalar_bytes.try_into().unwrap(); + + let point: G1Point = (*x, *y); + Ok((point, scalar_array)) + }); + + let unpadded_result = p1_msm_bytes(&mut valid_pairs_iter)?; + + // Pad the result for EVM compatibility + let padded_result = pad_g1_point(&unpadded_result); + + Ok(PrecompileOutput::new(required_gas, padded_result.into())) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::primitives::{hex, Bytes}; + + #[test] + fn bls_g1multiexp_g1_not_on_curve_but_in_subgroup() { + let input = Bytes::from(hex!("000000000000000000000000000000000a2833e497b38ee3ca5c62828bf4887a9f940c9e426c7890a759c20f248c23a7210d2432f4c98a514e524b5184a0ddac00000000000000000000000000000000150772d56bf9509469f9ebcd6e47570429fd31b0e262b66d512e245c38ec37255529f2271fd70066473e393a8bead0c30000000000000000000000000000000000000000000000000000000000000000")); + let fail = g1_msm(&input, G1_MSM_BASE_GAS_FEE); + assert_eq!(fail, Err(PrecompileError::Bls12381G1NotOnCurve.into())); + } +} diff --git a/crates/precompile/src/bls12_381_ark/g2_add.rs b/crates/precompile/src/bls12_381_ark/g2_add.rs new file mode 100644 index 0000000000..b58438a93f --- /dev/null +++ b/crates/precompile/src/bls12_381_ark/g2_add.rs @@ -0,0 +1,46 @@ +//! BLS12-381 G2 add precompile. More details in [`g2_add`] +use revm_primitives::Bytes; + +use super::utils::{pad_g2_point, remove_g2_padding}; +use crate::{ + bls12_381_ark::arkworks::p2_add_affine_bytes, + bls12_381_const::{G2_ADD_ADDRESS, G2_ADD_BASE_GAS_FEE, G2_ADD_INPUT_LENGTH, PADDED_G2_LENGTH}, + Precompile, PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress, +}; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G2ADD precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(G2_ADD_ADDRESS, Precompile::Standard(g2_add)); + +/// G2 addition call expects `512` bytes as an input that is interpreted as byte +/// concatenation of two G2 points (`256` bytes each). +/// +/// Output is an encoding of addition operation result - single G2 point (`256` +/// bytes). +/// See also +pub fn g2_add(input: &Bytes, gas_limit: u64) -> PrecompileResult { + if G2_ADD_BASE_GAS_FEE > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + if input.len() != G2_ADD_INPUT_LENGTH { + return Err(PrecompileError::Bls12381G2AddInputLength.into()); + } + + // Extract coordinates from padded input + let [a_x_0, a_x_1, a_y_0, a_y_1] = remove_g2_padding(&input[..PADDED_G2_LENGTH])?; + let [b_x_0, b_x_1, b_y_0, b_y_1] = remove_g2_padding(&input[PADDED_G2_LENGTH..])?; + + let a = (*a_x_0, *a_x_1, *a_y_0, *a_y_1); + let b = (*b_x_0, *b_x_1, *b_y_0, *b_y_1); + + let unpadded_result = p2_add_affine_bytes(a, b)?; + + // Pad the result for EVM compatibility + let padded_result = pad_g2_point(&unpadded_result); + + Ok(PrecompileOutput::new( + G2_ADD_BASE_GAS_FEE, + padded_result.into(), + )) +} diff --git a/crates/precompile/src/bls12_381_ark/g2_msm.rs b/crates/precompile/src/bls12_381_ark/g2_msm.rs new file mode 100644 index 0000000000..daa5577d16 --- /dev/null +++ b/crates/precompile/src/bls12_381_ark/g2_msm.rs @@ -0,0 +1,56 @@ +//! BLS12-381 G2 msm precompile. More details in [`g2_msm`] +use super::utils::{pad_g2_point, remove_g2_padding}; +use crate::{ + bls12_381_ark::arkworks::p2_msm_bytes, + bls12_381_const::{ + DISCOUNT_TABLE_G2_MSM, G2_MSM_ADDRESS, G2_MSM_BASE_GAS_FEE, G2_MSM_INPUT_LENGTH, + PADDED_G2_LENGTH, SCALAR_LENGTH, + }, + bls12_381_utils::msm_required_gas, + Precompile, PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress, +}; +use revm_primitives::Bytes; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G2MSM precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(G2_MSM_ADDRESS, Precompile::Standard(g2_msm)); + +/// Implements EIP-2537 G2MSM precompile. +/// G2 multi-scalar-multiplication call expects `288*k` bytes as an input that is interpreted +/// as byte concatenation of `k` slices each of them being a byte concatenation +/// of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` +/// bytes). +/// Output is an encoding of multi-scalar-multiplication operation result - single G2 +/// point (`256` bytes). +/// See also: +pub fn g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let input_len = input.len(); + if input_len == 0 || input_len % G2_MSM_INPUT_LENGTH != 0 { + return Err(PrecompileError::Bls12381G2MsmInputLength.into()); + } + + let k = input_len / G2_MSM_INPUT_LENGTH; + let required_gas = msm_required_gas(k, &DISCOUNT_TABLE_G2_MSM, G2_MSM_BASE_GAS_FEE); + if required_gas > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + let mut valid_pairs_iter = (0..k).map(|i| { + let start = i * G2_MSM_INPUT_LENGTH; + let padded_g2 = &input[start..start + PADDED_G2_LENGTH]; + let scalar_bytes = &input[start + PADDED_G2_LENGTH..start + G2_MSM_INPUT_LENGTH]; + + // Remove padding from G2 point - this validates padding format + let [x_0, x_1, y_0, y_1] = remove_g2_padding(padded_g2)?; + let scalar_array: [u8; SCALAR_LENGTH] = scalar_bytes.try_into().unwrap(); + + Ok(((*x_0, *x_1, *y_0, *y_1), scalar_array)) + }); + + let unpadded_result = p2_msm_bytes(&mut valid_pairs_iter)?; + + // Pad the result for EVM compatibility + let padded_result = pad_g2_point(&unpadded_result); + + Ok(PrecompileOutput::new(required_gas, padded_result.into())) +} diff --git a/crates/precompile/src/bls12_381_ark/map_fp2_to_g2.rs b/crates/precompile/src/bls12_381_ark/map_fp2_to_g2.rs new file mode 100644 index 0000000000..4d0ecddf11 --- /dev/null +++ b/crates/precompile/src/bls12_381_ark/map_fp2_to_g2.rs @@ -0,0 +1,41 @@ +//! BLS12-381 map fp2 to g2 precompile. More details in [`map_fp2_to_g2`] +use super::utils::{pad_g2_point, remove_fp_padding}; +use crate::{ + bls12_381_ark::arkworks::map_fp2_to_g2_bytes, + bls12_381_const::{ + MAP_FP2_TO_G2_ADDRESS, MAP_FP2_TO_G2_BASE_GAS_FEE, PADDED_FP2_LENGTH, PADDED_FP_LENGTH, + }, + Precompile, PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress, +}; +use revm_primitives::Bytes; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_MAP_FP2_TO_G2 precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(MAP_FP2_TO_G2_ADDRESS, Precompile::Standard(map_fp2_to_g2)); + +/// Field-to-curve call expects 128 bytes as an input that is interpreted as +/// an element of Fp2. Output of this call is 256 bytes and is an encoded G2 +/// point. +/// See also: +pub fn map_fp2_to_g2(input: &Bytes, gas_limit: u64) -> PrecompileResult { + if MAP_FP2_TO_G2_BASE_GAS_FEE > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + if input.len() != PADDED_FP2_LENGTH { + return Err(PrecompileError::Bls12381MapFp2ToG2InputLength.into()); + } + + let input_p0_x = remove_fp_padding(&input[..PADDED_FP_LENGTH])?; + let input_p0_y = remove_fp_padding(&input[PADDED_FP_LENGTH..PADDED_FP2_LENGTH])?; + + let unpadded_result = map_fp2_to_g2_bytes(input_p0_x, input_p0_y)?; + + // Pad the result for EVM compatibility + let padded_result = pad_g2_point(&unpadded_result); + + Ok(PrecompileOutput::new( + MAP_FP2_TO_G2_BASE_GAS_FEE, + padded_result.into(), + )) +} diff --git a/crates/precompile/src/bls12_381_ark/map_fp_to_g1.rs b/crates/precompile/src/bls12_381_ark/map_fp_to_g1.rs new file mode 100644 index 0000000000..714821c2ab --- /dev/null +++ b/crates/precompile/src/bls12_381_ark/map_fp_to_g1.rs @@ -0,0 +1,50 @@ +//! BLS12-381 map fp to g1 precompile. More details in [`map_fp_to_g1`] +use super::utils::{pad_g1_point, remove_fp_padding}; +use crate::{ + bls12_381_ark::arkworks::map_fp_to_g1_bytes, + bls12_381_const::{MAP_FP_TO_G1_ADDRESS, MAP_FP_TO_G1_BASE_GAS_FEE, PADDED_FP_LENGTH}, + Precompile, PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress, +}; +use revm_primitives::Bytes; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_MAP_FP_TO_G1 precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(MAP_FP_TO_G1_ADDRESS, Precompile::Standard(map_fp_to_g1)); + +/// Field-to-curve call expects 64 bytes as an input that is interpreted as an +/// element of Fp. Output of this call is 128 bytes and is an encoded G1 point. +/// See also: +pub fn map_fp_to_g1(input: &Bytes, gas_limit: u64) -> PrecompileResult { + if MAP_FP_TO_G1_BASE_GAS_FEE > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + if input.len() != PADDED_FP_LENGTH { + return Err(PrecompileError::Bls12381MapFpToG1InputLength.into()); + } + + let input_p0 = remove_fp_padding(input)?; + + let unpadded_result = map_fp_to_g1_bytes(input_p0)?; + + // Pad the result for EVM compatibility + let padded_result = pad_g1_point(&unpadded_result); + + Ok(PrecompileOutput::new( + MAP_FP_TO_G1_BASE_GAS_FEE, + padded_result.into(), + )) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::primitives::{hex, Bytes}; + + #[test] + fn sanity_test() { + let input = Bytes::from(hex!("000000000000000000000000000000006900000000000000636f6e7472616374595a603f343061cd305a03f40239f5ffff31818185c136bc2595f2aa18e08f17")); + let fail = map_fp_to_g1(&input, MAP_FP_TO_G1_BASE_GAS_FEE); + assert_eq!(fail, Err(PrecompileError::NonCanonicalFp.into())); + } +} diff --git a/crates/precompile/src/bls12_381_ark/pairing.rs b/crates/precompile/src/bls12_381_ark/pairing.rs new file mode 100644 index 0000000000..783d2ad64e --- /dev/null +++ b/crates/precompile/src/bls12_381_ark/pairing.rs @@ -0,0 +1,67 @@ +//! BLS12-381 pairing precompile. More details in [`pairing`] +use super::{ + utils::{remove_g1_padding, remove_g2_padding}, + PairingPair, +}; +use crate::{ + bls12_381_ark::arkworks::pairing_check_bytes, + bls12_381_const::{ + PADDED_G1_LENGTH, PADDED_G2_LENGTH, PAIRING_ADDRESS, PAIRING_INPUT_LENGTH, + PAIRING_MULTIPLIER_BASE, PAIRING_OFFSET_BASE, + }, + Precompile, PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress, +}; +use revm_primitives::Bytes; +use revm_primitives::B256; +use std::vec::Vec; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_PAIRING precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(PAIRING_ADDRESS, Precompile::Standard(pairing)); + +/// Pairing call expects 384*k (k being a positive integer) bytes as an inputs +/// that is interpreted as byte concatenation of k slices. Each slice has the +/// following structure: +/// * 128 bytes of G1 point encoding +/// * 256 bytes of G2 point encoding +/// +/// Each point is expected to be in the subgroup of order q. +/// Output is 32 bytes where first 31 bytes are equal to 0x00 and the last byte +/// is 0x01 if pairing result is equal to the multiplicative identity in a pairing +/// target field and 0x00 otherwise. +/// +/// See also: +pub fn pairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let input_len = input.len(); + if input_len == 0 || input_len % PAIRING_INPUT_LENGTH != 0 { + return Err(PrecompileError::Bls12381PairingInputLength.into()); + } + + let k = input_len / PAIRING_INPUT_LENGTH; + let required_gas: u64 = PAIRING_MULTIPLIER_BASE * k as u64 + PAIRING_OFFSET_BASE; + if required_gas > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + // Collect pairs of points for the pairing check + let mut pairs: Vec = Vec::with_capacity(k); + for i in 0..k { + let encoded_g1_element = + &input[i * PAIRING_INPUT_LENGTH..i * PAIRING_INPUT_LENGTH + PADDED_G1_LENGTH]; + let encoded_g2_element = &input[i * PAIRING_INPUT_LENGTH + PADDED_G1_LENGTH + ..i * PAIRING_INPUT_LENGTH + PADDED_G1_LENGTH + PADDED_G2_LENGTH]; + + let [a_x, a_y] = remove_g1_padding(encoded_g1_element)?; + let [b_x_0, b_x_1, b_y_0, b_y_1] = remove_g2_padding(encoded_g2_element)?; + + pairs.push(((*a_x, *a_y), (*b_x_0, *b_x_1, *b_y_0, *b_y_1))); + } + + let result = pairing_check_bytes(&pairs)?; + let result = if result { 1 } else { 0 }; + + Ok(PrecompileOutput::new( + required_gas, + B256::with_last_byte(result).into(), + )) +} diff --git a/crates/precompile/src/bls12_381_ark/utils.rs b/crates/precompile/src/bls12_381_ark/utils.rs new file mode 100644 index 0000000000..74f91d2e92 --- /dev/null +++ b/crates/precompile/src/bls12_381_ark/utils.rs @@ -0,0 +1,133 @@ +//! BLS12-381 utilities for padding and unpadding of input. +use crate::{ + bls12_381_const::{ + FP_LENGTH, FP_PAD_BY, G1_LENGTH, PADDED_FP_LENGTH, PADDED_G1_LENGTH, PADDED_G2_LENGTH, + }, + PrecompileError, +}; + +/// Removes zeros with which the precompile inputs are left padded to 64 bytes. +pub(super) fn remove_fp_padding(input: &[u8]) -> Result<&[u8; FP_LENGTH], PrecompileError> { + if input.len() != PADDED_FP_LENGTH { + return Err(PrecompileError::Bls12381FpPaddingLength); + } + let (padding, unpadded) = input.split_at(FP_PAD_BY); + if !padding.iter().all(|&x| x == 0) { + return Err(PrecompileError::Bls12381FpPaddingInvalid); + } + Ok(unpadded.try_into().unwrap()) +} +/// remove_g1_padding removes the padding applied to the Fp elements that constitute the +/// encoded G1 element. +pub(super) fn remove_g1_padding(input: &[u8]) -> Result<[&[u8; FP_LENGTH]; 2], PrecompileError> { + if input.len() != PADDED_G1_LENGTH { + return Err(PrecompileError::Bls12381G1PaddingLength); + } + + let x = remove_fp_padding(&input[..PADDED_FP_LENGTH])?; + let y = remove_fp_padding(&input[PADDED_FP_LENGTH..PADDED_G1_LENGTH])?; + Ok([x, y]) +} + +/// remove_g2_padding removes the padding applied to the Fp elements that constitute the +/// encoded G2 element. +pub(super) fn remove_g2_padding(input: &[u8]) -> Result<[&[u8; FP_LENGTH]; 4], PrecompileError> { + if input.len() != PADDED_G2_LENGTH { + return Err(PrecompileError::Bls12381G2PaddingLength); + } + + let mut input_fps = [&[0; FP_LENGTH]; 4]; + for i in 0..4 { + input_fps[i] = remove_fp_padding(&input[i * PADDED_FP_LENGTH..(i + 1) * PADDED_FP_LENGTH])?; + } + Ok(input_fps) +} + +/// Pads an unpadded G1 point (96 bytes) to the EVM-compatible format (128 bytes). +/// +/// Takes a G1 point with 2 field elements of 48 bytes each and adds 16 bytes of +/// zero padding before each field element. +pub(super) fn pad_g1_point(unpadded: &[u8]) -> [u8; PADDED_G1_LENGTH] { + assert_eq!( + unpadded.len(), + G1_LENGTH, + "Invalid unpadded G1 point length" + ); + + let mut padded = [0u8; PADDED_G1_LENGTH]; + + // Copy each field element (x, y) with padding + for i in 0..2 { + padded[i * PADDED_FP_LENGTH + FP_PAD_BY..(i + 1) * PADDED_FP_LENGTH] + .copy_from_slice(&unpadded[i * FP_LENGTH..(i + 1) * FP_LENGTH]); + } + + padded +} + +/// Pads an unpadded G2 point (192 bytes) to the EVM-compatible format (256 bytes). +/// +/// Takes a G2 point with 4 field elements of 48 bytes each and adds 16 bytes of +/// zero padding before each field element. +pub(super) fn pad_g2_point(unpadded: &[u8]) -> [u8; PADDED_G2_LENGTH] { + assert_eq!( + unpadded.len(), + 4 * FP_LENGTH, + "Invalid unpadded G2 point length" + ); + + let mut padded = [0u8; PADDED_G2_LENGTH]; + + // Copy each field element (x.c0, x.c1, y.c0, y.c1) with padding + for i in 0..4 { + padded[i * PADDED_FP_LENGTH + FP_PAD_BY..(i + 1) * PADDED_FP_LENGTH] + .copy_from_slice(&unpadded[i * FP_LENGTH..(i + 1) * FP_LENGTH]); + } + + padded +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pad_g1_point_roundtrip() { + // Create test data + let mut unpadded = [0u8; G1_LENGTH]; + for (i, byte) in unpadded.iter_mut().enumerate() { + *byte = (i * 2 + 1) as u8; + } + + // Pad the point + let padded = pad_g1_point(&unpadded); + + // Remove padding + let result = remove_g1_padding(&padded).unwrap(); + + // Verify roundtrip + assert_eq!(result[0], &unpadded[0..FP_LENGTH]); + assert_eq!(result[1], &unpadded[FP_LENGTH..G1_LENGTH]); + } + + #[test] + fn test_pad_g2_point_roundtrip() { + // Create test data for G2 point (192 bytes = 4 * 48) + let mut unpadded = [0u8; 4 * FP_LENGTH]; + for (i, byte) in unpadded.iter_mut().enumerate() { + *byte = (i * 2 + 1) as u8; + } + + // Pad the point + let padded = pad_g2_point(&unpadded); + + // Remove padding + let result = remove_g2_padding(&padded).unwrap(); + + // Verify roundtrip - G2 has 4 field elements + assert_eq!(result[0], &unpadded[0..FP_LENGTH]); + assert_eq!(result[1], &unpadded[FP_LENGTH..2 * FP_LENGTH]); + assert_eq!(result[2], &unpadded[2 * FP_LENGTH..3 * FP_LENGTH]); + assert_eq!(result[3], &unpadded[3 * FP_LENGTH..4 * FP_LENGTH]); + } +} diff --git a/crates/precompile/src/bls12_381_const.rs b/crates/precompile/src/bls12_381_const.rs new file mode 100644 index 0000000000..7a27473583 --- /dev/null +++ b/crates/precompile/src/bls12_381_const.rs @@ -0,0 +1,216 @@ +//! Constants specifying the precompile addresses for each precompile in EIP-2537 + +use crate::primitives::{hex, Address}; +use crate::u64_to_address; + +/// G1 add precompile address +pub const G1_ADD_ADDRESS: Address = u64_to_address(0x0b); +/// G1 msm precompile address +pub const G1_MSM_ADDRESS: Address = u64_to_address(0x0c); +/// G2 add precompile address +pub const G2_ADD_ADDRESS: Address = u64_to_address(0x0d); +/// G2 msm precompile address +pub const G2_MSM_ADDRESS: Address = u64_to_address(0x0e); +/// Pairing precompile address +pub const PAIRING_ADDRESS: Address = u64_to_address(0x0f); +/// Map fp to g1 precompile address +pub const MAP_FP_TO_G1_ADDRESS: Address = u64_to_address(0x10); +/// Map fp2 to g2 precompile address +pub const MAP_FP2_TO_G2_ADDRESS: Address = u64_to_address(0x11); + +/// G1_ADD_BASE_GAS_FEE specifies the amount of gas needed +/// to perform the G1_ADD precompile. +pub const G1_ADD_BASE_GAS_FEE: u64 = 375; +/// G1_MSM_BASE_GAS_FEE specifies the base amount of gas needed to +/// perform the G1_MSM precompile. +/// +/// The cost to do an MSM is determined by the formula: +/// (k * G1_MSM_BASE_GAS_FEE * DISCOUNT\[k\]) // MSM_MULTIPLIER +/// where k is the number of point-scalar pairs. +/// +/// Note: If one wants to do a G1 scalar multiplication, they would call +/// this precompile with a single point and a scalar. +pub const G1_MSM_BASE_GAS_FEE: u64 = 12000; +/// MSM_MULTIPLIER specifies the division constant that is used to determine the +/// gas needed to compute an MSM. +/// +/// The cost to do an MSM is determined by the formula: +/// (k * MSM_BASE_GAS_FEE * DISCOUNT\[k\]) // MSM_MULTIPLIER +/// where k is the number of point-scalar pairs. +/// +/// Note: If `k` is more than the size of the discount table, then +/// the last value in the discount table is chosen. +pub const MSM_MULTIPLIER: u64 = 1000; +/// MAP_FP_TO_G1_BASE_GAS_FEE specifies the amount of gas needed +/// to perform the MAP_FP_TO_G1 precompile. +pub const MAP_FP_TO_G1_BASE_GAS_FEE: u64 = 5500; +/// MAP_FP2_TO_G2_BASE_GAS_FEE specifies the amount of gas needed +/// to perform the MAP_FP2_TO_G2 precompile. +pub const MAP_FP2_TO_G2_BASE_GAS_FEE: u64 = 23800; +/// G2_ADD_BASE_GAS_FEE specifies the amount of gas needed +/// to perform the G2_ADD precompile. +pub const G2_ADD_BASE_GAS_FEE: u64 = 600; +/// G2_MSM_BASE_GAS_FEE specifies the base amount of gas needed to +/// perform the G2_MSM precompile. +/// +/// The cost to do an MSM is determined by the formula: +/// (k * G2_MSM_BASE_GAS_FEE * DISCOUNT\[k\]) // MSM_MULTIPLIER +/// where k is the number of point-scalar pairs. +/// +/// Note: If one wants to do a G2 scalar multiplication, they would call +/// this precompile with a single point and a scalar. +pub const G2_MSM_BASE_GAS_FEE: u64 = 22500; +/// PAIRING_OFFSET_BASE specifies the y-intercept for the linear expression to determine +/// the amount of gas needed to perform a pairing. +/// +/// The cost to do a pairing is determined by the formula: +/// cost = PAIRING_MULTIPLIER_BASE * number_of_pairs + PAIRING_OFFSET_BASE +pub const PAIRING_OFFSET_BASE: u64 = 37700; +/// PAIRING_MULTIPLIER_BASE specifies the slope/gradient for the linear expression to determine +/// the amount of gas needed to perform a pairing. +/// +/// The cost to do a pairing is determined by the formula: +/// PAIRING_MULTIPLIER_BASE * number_of_pairs + PAIRING_OFFSET_BASE +pub const PAIRING_MULTIPLIER_BASE: u64 = 32600; + +/// Discounts table for G1 MSM as a vector of pairs `[k, discount]`. +pub static DISCOUNT_TABLE_G1_MSM: [u16; 128] = [ + 1000, 949, 848, 797, 764, 750, 738, 728, 719, 712, 705, 698, 692, 687, 682, 677, 673, 669, 665, + 661, 658, 654, 651, 648, 645, 642, 640, 637, 635, 632, 630, 627, 625, 623, 621, 619, 617, 615, + 613, 611, 609, 608, 606, 604, 603, 601, 599, 598, 596, 595, 593, 592, 591, 589, 588, 586, 585, + 584, 582, 581, 580, 579, 577, 576, 575, 574, 573, 572, 570, 569, 568, 567, 566, 565, 564, 563, + 562, 561, 560, 559, 558, 557, 556, 555, 554, 553, 552, 551, 550, 549, 548, 547, 547, 546, 545, + 544, 543, 542, 541, 540, 540, 539, 538, 537, 536, 536, 535, 534, 533, 532, 532, 531, 530, 529, + 528, 528, 527, 526, 525, 525, 524, 523, 522, 522, 521, 520, 520, 519, +]; +/// Discounts table for G2 MSM as a vector of pairs `[k, discount]`: +pub static DISCOUNT_TABLE_G2_MSM: [u16; 128] = [ + 1000, 1000, 923, 884, 855, 832, 812, 796, 782, 770, 759, 749, 740, 732, 724, 717, 711, 704, + 699, 693, 688, 683, 679, 674, 670, 666, 663, 659, 655, 652, 649, 646, 643, 640, 637, 634, 632, + 629, 627, 624, 622, 620, 618, 615, 613, 611, 609, 607, 606, 604, 602, 600, 598, 597, 595, 593, + 592, 590, 589, 587, 586, 584, 583, 582, 580, 579, 578, 576, 575, 574, 573, 571, 570, 569, 568, + 567, 566, 565, 563, 562, 561, 560, 559, 558, 557, 556, 555, 554, 553, 552, 552, 551, 550, 549, + 548, 547, 546, 545, 545, 544, 543, 542, 541, 541, 540, 539, 538, 537, 537, 536, 535, 535, 534, + 533, 532, 532, 531, 530, 530, 529, 528, 528, 527, 526, 526, 525, 524, 524, +]; + +// Constants related to the bls12-381 precompile inputs and outputs + +/// FP_LENGTH specifies the number of bytes needed to represent an +/// Fp element. This is an element in the base field of BLS12-381. +/// +/// Note: The base field is used to define G1 and G2 elements. +pub const FP_LENGTH: usize = 48; +/// PADDED_FP_LENGTH specifies the number of bytes that the EVM will use +/// to represent an Fp element according to EIP-2537. +/// +/// Note: We only need FP_LENGTH number of bytes to represent it, +/// but we pad the byte representation to be 32 byte aligned as specified in EIP 2537. +pub const PADDED_FP_LENGTH: usize = 64; + +/// G1_LENGTH specifies the number of bytes needed to represent a G1 element. +/// +/// Note: A G1 element contains 2 Fp elements. +pub const G1_LENGTH: usize = 2 * FP_LENGTH; +/// PADDED_G1_LENGTH specifies the number of bytes that the EVM will use to represent +/// a G1 element according to padding rules specified in EIP-2537. +pub const PADDED_G1_LENGTH: usize = 2 * PADDED_FP_LENGTH; + +/// FP2_LENGTH specifies the number of bytes needed to represent a Fp^2 element. +/// +/// Note: This is the quadratic extension of Fp, and by definition +/// means we need 2 Fp elements. +pub const FP2_LENGTH: usize = 2 * FP_LENGTH; + +/// PADDED_FP2_LENGTH specifies the number of bytes that the EVM will use to represent +/// a Fp^2 element according to the padding rules specified in EIP-2537. +/// +/// Note: This is the quadratic extension of Fp, and by definition +/// means we need 2 Fp elements. +pub const PADDED_FP2_LENGTH: usize = 2 * PADDED_FP_LENGTH; + +/// SCALAR_LENGTH specifies the number of bytes needed to represent an Fr element. +/// This is an element in the scalar field of BLS12-381. +/// +/// Note: Since it is already 32 byte aligned, there is no padded version of this constant. +pub const SCALAR_LENGTH: usize = 32; +/// SCALAR_LENGTH_BITS specifies the number of bits needed to represent an Fr element. +/// This is an element in the scalar field of BLS12-381. +pub const SCALAR_LENGTH_BITS: usize = SCALAR_LENGTH * 8; + +/// G1_ADD_INPUT_LENGTH specifies the number of bytes that the input to G1ADD +/// must use. +/// +/// Note: The input to the G1 addition precompile is 2 G1 elements. +pub const G1_ADD_INPUT_LENGTH: usize = 2 * PADDED_G1_LENGTH; +/// G1_MSM_INPUT_LENGTH specifies the number of bytes that each MSM input pair should have. +/// +/// Note: An MSM pair is a G1 element and a scalar. The input to the MSM precompile will have `n` +/// of these pairs. +pub const G1_MSM_INPUT_LENGTH: usize = PADDED_G1_LENGTH + SCALAR_LENGTH; + +/// G2_LENGTH specifies the number of bytes needed to represent a G2 element. +/// +/// Note: A G2 element contains 2 Fp^2 elements. +pub const G2_LENGTH: usize = 2 * FP2_LENGTH; + +/// PADDED_G2_LENGTH specifies the number of bytes that the EVM will use to represent +/// a G2 element. +/// +/// Note: A G2 element can be represented using 2 Fp^2 elements. +pub const PADDED_G2_LENGTH: usize = 2 * PADDED_FP2_LENGTH; + +/// G2_ADD_INPUT_LENGTH specifies the number of bytes that the input to G2ADD +/// must occupy. +/// +/// Note: The input to the G2 addition precompile is 2 G2 elements. +pub const G2_ADD_INPUT_LENGTH: usize = 2 * PADDED_G2_LENGTH; +/// G2_MSM_INPUT_LENGTH specifies the number of bytes that each MSM input pair should have. +/// +/// Note: An MSM pair is a G2 element and a scalar. The input to the MSM will have `n` +/// of these pairs. +pub const G2_MSM_INPUT_LENGTH: usize = PADDED_G2_LENGTH + SCALAR_LENGTH; + +/// PAIRING_INPUT_LENGTH specifies the number of bytes that each Pairing input pair should have. +/// +/// Note: An Pairing input-pair is a G2 element and a G1 element. The input to the Pairing will have `n` +/// of these pairs. +pub const PAIRING_INPUT_LENGTH: usize = PADDED_G1_LENGTH + PADDED_G2_LENGTH; + +/// FP_PAD_BY specifies the number of bytes that an FP_ELEMENT is padded by to make it 32 byte aligned. +/// +/// Note: This should be equal to PADDED_FP_LENGTH - FP_LENGTH. +pub const FP_PAD_BY: usize = 16; + +/// The trusted setup G2 point `[τ]₂` from the Ethereum KZG ceremony (compressed format) +/// Taken from: +pub const TRUSTED_SETUP_TAU_G2_BYTES: [u8; 96] = hex!( + "b5bfd7dd8cdeb128843bc287230af38926187075cbfbefa81009a2ce615ac53d2914e5870cb452d2afaaab24f3499f72185cbfee53492714734429b7b38608e23926c911cceceac9a36851477ba4c60b087041de621000edc98edada20c1def2" + ); + +#[test] +fn check_discount_table_invariant_holds() { + // Currently EIP-2537 specifies the cost for a G1/G2 scalar multiplication in two places + // in two different ways. + // + // First it explicitly says that G1 Multiplication costs 12000 Gas and G2 Multiplication costs 22500. + // + // Then it implies the above constants for G1_MSM and G2_MSM via the MSM formula: + // MSM_COST = k * MSM_BASE_GAS_FEE * DISCOUNT[k-1] // MSM_MULTIPLIER + // + // Note that when the MSM has only one point-scalar pair (scalar multiplication), we get: + // MSM_COST = MSM_BASE_GAS_FEE * DISCOUNT[0] // MSM_MULTIPLIER + // (This is because k==1) + // + // The 0th entry in the discount table for G1_MSM and G2_MSM is equal to MSM_MULTIPLIER + // so for k==1, MSM_COST = MSM_BASE_GAS_FEE + // + // For G1, MSM_BASE_GAS_FEE matches 12000 and for G2 MSM_BASE_GAS_FEE matches 22500. + // + // In this test, we check that this invariant does not change by asserting that the first value + // in the discount table is equal to the MULTIPLIER. + assert_eq!(DISCOUNT_TABLE_G1_MSM[0], MSM_MULTIPLIER as u16); + assert_eq!(DISCOUNT_TABLE_G2_MSM[0], MSM_MULTIPLIER as u16); + // Note: We could also more robustly check this by defining the G1/G2 Scalar multiplication constants + // from the EIP and checking that they equal to the value computed by `msm_required_gas` when k==1 +} diff --git a/crates/precompile/src/bls12_381_utils.rs b/crates/precompile/src/bls12_381_utils.rs new file mode 100644 index 0000000000..4b0e41c3ce --- /dev/null +++ b/crates/precompile/src/bls12_381_utils.rs @@ -0,0 +1,16 @@ +//! Utility functions for the BLS12-381 precompiles +use crate::bls12_381_const::MSM_MULTIPLIER; + +/// Implements the gas schedule for G1/G2 Multiscalar-multiplication assuming 30 +/// MGas/second, see also: +#[inline] +pub fn msm_required_gas(k: usize, discount_table: &[u16], multiplication_cost: u64) -> u64 { + if k == 0 { + return 0; + } + + let index = core::cmp::min(k - 1, discount_table.len() - 1); + let discount = discount_table[index] as u64; + + (k as u64 * discount * multiplication_cost) / MSM_MULTIPLIER +} diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index a8e6a83e20..2f2e0df612 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -23,6 +23,10 @@ pub mod secp256k1; pub mod secp256r1; pub mod utilities; +pub mod bls12_381_ark; +pub mod bls12_381_const; +pub mod bls12_381_utils; + pub use fatal_precompile::fatal_precompile; #[cfg(all(feature = "c-kzg", feature = "kzg-rs"))] @@ -254,12 +258,13 @@ impl Precompiles { pub fn emerald() -> &'static Self { static INSTANCE: OnceBox = OnceBox::new(); INSTANCE.get_or_init(|| { - let precompiles = Self::viridian().clone(); - precompiles.extend(bls12_381::precompiles()); // add BLS12-381 precompiles + let mut precompiles = Self::viridian().clone(); + precompiles.extend(bls12_381_ark::precompiles()); // add BLS12-381 precompiles precompiles.extend([ - modexp::OSAKA, // 0x05 - secp256r1::P256VERIFY_OSAKA, + modexp::EMERALD, // 0x05 ]); + #[cfg(feature = "secp256r1")] + precompiles.extend([secp256r1::P256VERIFY_OSAKA]); Box::new(precompiles) }) } diff --git a/crates/precompile/src/modexp.rs b/crates/precompile/src/modexp.rs index 0417bacce0..e3c644742b 100644 --- a/crates/precompile/src/modexp.rs +++ b/crates/precompile/src/modexp.rs @@ -29,8 +29,8 @@ pub const BERNOULLI: PrecompileWithAddress = PrecompileWithAddress( ); #[cfg(feature = "morph")] -pub const OSAKA: PrecompileWithAddress = - PrecompileWithAddress(crate::u64_to_address(5), Precompile::Standard(osaka_run)); +pub const EMERALD: PrecompileWithAddress = + PrecompileWithAddress(crate::u64_to_address(5), Precompile::Standard(emerald_run)); /// See: /// See: @@ -79,7 +79,7 @@ pub fn bernoilli_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { #[cfg(feature = "morph")] /// See: /// Gas cost of berlin is modified from byzantium. -pub fn osaka_run(input: &[u8], gas_limit: u64) -> PrecompileResult { +pub fn emerald_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { let base_len = U256::from_be_bytes(right_pad_with_offset::<32>(input, 0).into_owned()); let exp_len = U256::from_be_bytes(right_pad_with_offset::<32>(input, 32).into_owned()); let mod_len = U256::from_be_bytes(right_pad_with_offset::<32>(input, 64).into_owned()); @@ -94,7 +94,8 @@ pub fn osaka_run(input: &[u8], gas_limit: u64) -> PrecompileResult { if mod_len > MORPH_LEN_LIMIT { return Err(Error::ModexpModOverflow.into()); } - run_inner::<_, true>(input, gas_limit, 500, |a, b, c, d| { + + run_inner(input, gas_limit, 500, |a, b, c, d| { osaka_gas_calc(a, b, c, d) }) } diff --git a/crates/precompile/src/secp256r1.rs b/crates/precompile/src/secp256r1.rs index 262a2d0889..caf099bb7a 100644 --- a/crates/precompile/src/secp256r1.rs +++ b/crates/precompile/src/secp256r1.rs @@ -6,18 +6,9 @@ //! The main purpose of this precompile is to verify ECDSA signatures that use the secp256r1, or //! P256 elliptic curve. The [`P256VERIFY`] const represents the implementation of this precompile, //! with the address that it is currently deployed at. -use crate::{ - crypto, u64_to_address, Precompile, PrecompileError, PrecompileId, PrecompileOutput, - PrecompileResult, -}; -use p256::{ - ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey}, - EncodedPoint, -}; -use primitives::{alloy_primitives::B512, Bytes, B256}; - -/// Address of secp256r1 precompile. -pub const P256VERIFY_ADDRESS: u64 = 256; +use crate::{u64_to_address, Precompile, PrecompileWithAddress}; +use p256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey}; +use revm_primitives::{Bytes, PrecompileError, PrecompileOutput, PrecompileResult, B256}; /// Base gas fee for secp256r1 p256verify operation. pub const P256VERIFY_BASE_GAS_FEE: u64 = 3450; @@ -26,22 +17,18 @@ pub const P256VERIFY_BASE_GAS_FEE: u64 = 3450; pub const P256VERIFY_BASE_GAS_FEE_OSAKA: u64 = 6900; /// Returns the secp256r1 precompile with its address. -pub fn precompiles() -> impl Iterator { +pub fn precompiles() -> impl Iterator { [P256VERIFY].into_iter() } -/// [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md#specification) secp256r1 precompile. -pub const P256VERIFY: Precompile = Precompile::new( - PrecompileId::P256Verify, - u64_to_address(P256VERIFY_ADDRESS), - p256_verify, -); +/// [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212#specification) secp256r1 precompile. +pub const P256VERIFY: PrecompileWithAddress = + PrecompileWithAddress(u64_to_address(0x100), Precompile::Standard(p256_verify)); /// [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md#specification) secp256r1 precompile. -pub const P256VERIFY_OSAKA: Precompile = Precompile::new( - PrecompileId::P256Verify, - u64_to_address(P256VERIFY_ADDRESS), - p256_verify_osaka, +pub const P256VERIFY_OSAKA: PrecompileWithAddress = PrecompileWithAddress( + u64_to_address(0x100), + Precompile::Standard(p256_verify_osaka), ); /// secp256r1 precompile logic. It takes the input bytes sent to the precompile @@ -53,7 +40,7 @@ pub const P256VERIFY_OSAKA: Precompile = Precompile::new( /// | signed message hash | r | s | public key x | public key y | /// | :-----------------: | :-: | :-: | :----------: | :----------: | /// | 32 | 32 | 32 | 32 | 32 | -pub fn p256_verify(input: &[u8], gas_limit: u64) -> PrecompileResult { +pub fn p256_verify(input: &Bytes, gas_limit: u64) -> PrecompileResult { p256_verify_inner(input, gas_limit, P256VERIFY_BASE_GAS_FEE) } @@ -66,15 +53,15 @@ pub fn p256_verify(input: &[u8], gas_limit: u64) -> PrecompileResult { /// | signed message hash | r | s | public key x | public key y | /// | :-----------------: | :-: | :-: | :----------: | :----------: | /// | 32 | 32 | 32 | 32 | 32 | -pub fn p256_verify_osaka(input: &[u8], gas_limit: u64) -> PrecompileResult { +pub fn p256_verify_osaka(input: &Bytes, gas_limit: u64) -> PrecompileResult { p256_verify_inner(input, gas_limit, P256VERIFY_BASE_GAS_FEE_OSAKA) } -fn p256_verify_inner(input: &[u8], gas_limit: u64, gas_cost: u64) -> PrecompileResult { +fn p256_verify_inner(input: &Bytes, gas_limit: u64, gas_cost: u64) -> PrecompileResult { if gas_cost > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileError::OutOfGas.into()); } - let result = if verify_impl(input) { + let result = if verify_impl(input).is_some() { B256::with_last_byte(1).into() } else { Bytes::new() @@ -84,41 +71,39 @@ fn p256_verify_inner(input: &[u8], gas_limit: u64, gas_cost: u64) -> PrecompileR /// Returns `Some(())` if the signature included in the input byte slice is /// valid, `None` otherwise. -pub fn verify_impl(input: &[u8]) -> bool { +pub fn verify_impl(input: &[u8]) -> Option<()> { if input.len() != 160 { - return false; + return None; } // msg signed (msg is already the hash of the original message) - let msg = <&B256>::try_from(&input[..32]).unwrap(); + let msg = &input[..32]; // r, s: signature - let sig = <&B512>::try_from(&input[32..96]).unwrap(); + let sig = &input[32..96]; // x, y: public key - let pk = <&B512>::try_from(&input[96..160]).unwrap(); + let pk = &input[96..160]; - crypto().secp256r1_verify_signature(&msg.0, &sig.0, &pk.0) -} + // prepend 0x04 to the public key: uncompressed form + let mut uncompressed_pk = [0u8; 65]; + uncompressed_pk[0] = 0x04; + uncompressed_pk[1..].copy_from_slice(pk); -pub(crate) fn verify_signature(msg: [u8; 32], sig: [u8; 64], pk: [u8; 64]) -> Option<()> { // Can fail only if the input is not exact length. - let signature = Signature::from_slice(&sig).ok()?; - // Decode the public key bytes (x,y coordinates) using EncodedPoint - let encoded_point = EncodedPoint::from_untagged_bytes(&pk.into()); - // Create VerifyingKey from the encoded point - let public_key = VerifyingKey::from_encoded_point(&encoded_point).ok()?; + let signature = Signature::from_slice(sig).ok()?; + // Can fail if the input is not valid, so we have to propagate the error. + let public_key = VerifyingKey::from_sec1_bytes(&uncompressed_pk).ok()?; - public_key.verify_prehash(&msg, &signature).ok() + public_key.verify_prehash(msg, &signature).ok() } #[cfg(test)] mod test { use super::*; - use crate::PrecompileError; - use primitives::hex::FromHex; + use crate::primitives::{hex::FromHex, PrecompileErrors}; use rstest::rstest; #[rstest] - // Test vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors + // test vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors #[case::ok_1("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e", true)] #[case::ok_2("3fec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5", true)] #[case::ok_3("e775723953ead4a90411a02908fd1a629db584bc600664c609061f221ef6bf7c440066c8626b49daaa7bf2bcc0b74be4f7a1e3dcf0e869f1542fe821498cbf2de73ad398194129f635de4424a07ca715838aefe8fe69d1a391cfa70470795a80dd056866e6e1125aff94413921880c437c9e2570a28ced7267c8beef7e9b2d8d1547d76dfcf4bee592f5fefe10ddfb6aeb0991c5b9dbbee6ec80d11b17c0eb1a", true)] @@ -154,7 +139,10 @@ mod test { let result = p256_verify(&input, target_gas); assert!(result.is_err()); - assert_eq!(result.err(), Some(PrecompileError::OutOfGas)); + assert_eq!( + result.err(), + Some(PrecompileErrors::Error(PrecompileError::OutOfGas)) + ); } #[rstest] @@ -164,6 +152,6 @@ mod test { let input = Bytes::from_hex(input).unwrap(); let result = verify_impl(&input); - assert_eq!(result, expect_success); + assert_eq!(result.is_some(), expect_success); } } diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 13be6a974d..986c2cea9e 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -241,6 +241,7 @@ impl Env { pub fn validate_tx_against_state( &self, account: &mut Account, + token_info: (U256, U256, U256), ) -> Result<(), InvalidTransaction> { // EIP-3607: Reject transactions from senders with deployed code // This EIP is introduced after london but there was no collision in past @@ -268,9 +269,12 @@ impl Env { } } - let mut balance_check = U256::from(self.tx.gas_limit) + let gas_cost = U256::from(self.tx.gas_limit) .checked_mul(self.tx.gas_price) - .and_then(|gas_cost| gas_cost.checked_add(self.tx.value)) + .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; + + let mut balance_check = gas_cost + .checked_add(self.tx.value) .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; if SPEC::enabled(SpecId::CANCUN) { @@ -283,7 +287,17 @@ impl Env { // Check if account has enough balance for gas_limit*gas_price and value transfer. // Transfer will be done inside `*_inner` functions. - if balance_check > account.info.balance { + let lack_of_fund_for_max_fee = if self.tx.fee_token_id.unwrap_or_default() != 0 { + let mut fee_limit = self.tx.fee_limit.unwrap_or_default(); + if fee_limit.is_zero() || fee_limit > token_info.0 { + fee_limit = token_info.0 + } + let token_check = eth_to_token(gas_cost, token_info.1, token_info.2); + token_check.is_zero() || token_check > fee_limit || self.tx.value > account.info.balance + } else { + balance_check > account.info.balance + }; + if lack_of_fund_for_max_fee { cfg_if::cfg_if! { if #[cfg(not(feature = "morph"))] { if self.cfg.is_balance_check_disabled() { @@ -635,6 +649,13 @@ pub struct TxEnv { #[cfg(feature = "morph")] /// Morph fields pub morph: MorphFields, + + #[cfg(feature = "morph")] + /// For AltFeeType + pub fee_token_id: Option, + #[cfg(feature = "morph")] + /// For AltFeeType + pub fee_limit: Option, } pub enum TxType { @@ -680,6 +701,8 @@ impl Default for TxEnv { optimism: OptimismFields::default(), #[cfg(feature = "morph")] morph: MorphFields::default(), + fee_token_id: None, + fee_limit: None, } } } @@ -781,6 +804,21 @@ pub enum AnalysisKind { Analyse, } +pub fn eth_to_token(eth_amount: U256, rate: U256, token_scale: U256) -> U256 { + if rate.is_zero() { + return U256::ZERO; + } + // EthToToken token_amount = ethAmount / (tokenRate / tokenScale) = ethAmount * tokenScale / tokenRate + // Calculate: (eth_amount * token_scale) / rate + let (token_amount, remainder) = eth_amount.saturating_mul(token_scale).div_rem(rate); + // If there's a remainder, round up by adding 1 + if !remainder.is_zero() { + token_amount.saturating_add(U256::from(1)) + } else { + token_amount + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/primitives/src/precompile.rs b/crates/primitives/src/precompile.rs index d16633c250..7099926fa8 100644 --- a/crates/primitives/src/precompile.rs +++ b/crates/primitives/src/precompile.rs @@ -177,6 +177,41 @@ pub enum PrecompileError { NotImplemented, /// Catch-all variant for other errors. Other(String), + + /// Non-canonical field element + NonCanonicalFp, + /// BLS12-381 G1 point not on curve + Bls12381G1NotOnCurve, + /// BLS12-381 G1 point not in correct subgroup + Bls12381G1NotInSubgroup, + /// BLS12-381 G2 point not on curve + Bls12381G2NotOnCurve, + /// BLS12-381 G2 point not in correct subgroup + Bls12381G2NotInSubgroup, + /// BLS12-381 scalar input length error + Bls12381ScalarInputLength, + /// BLS12-381 G1 add input length error + Bls12381G1AddInputLength, + /// BLS12-381 G1 msm input length error + Bls12381G1MsmInputLength, + /// BLS12-381 G2 add input length error + Bls12381G2AddInputLength, + /// BLS12-381 G2 msm input length error + Bls12381G2MsmInputLength, + /// BLS12-381 pairing input length error + Bls12381PairingInputLength, + /// BLS12-381 map fp to g1 input length error + Bls12381MapFpToG1InputLength, + /// BLS12-381 map fp2 to g2 input length error + Bls12381MapFp2ToG2InputLength, + /// BLS12-381 padding error + Bls12381FpPaddingInvalid, + /// BLS12-381 fp padding length error + Bls12381FpPaddingLength, + /// BLS12-381 g1 padding length error + Bls12381G1PaddingLength, + /// BLS12-381 g2 padding length error + Bls12381G2PaddingLength, } impl PrecompileError { @@ -217,6 +252,23 @@ impl fmt::Display for PrecompileError { Self::BlobVerifyKzgProofFailed => "verifying blob kzg proof failed", Self::NotImplemented => "not implemented", Self::Other(s) => s, + Self::NonCanonicalFp => "non-canonical field element", + Self::Bls12381G1NotOnCurve => "bls12-381 g1 point not on curve", + Self::Bls12381G1NotInSubgroup => "bls12-381 g1 point not in correct subgroup", + Self::Bls12381G2NotOnCurve => "bls12-381 g2 point not on curve", + Self::Bls12381G2NotInSubgroup => "bls12-381 g2 point not in correct subgroup", + Self::Bls12381ScalarInputLength => "bls12-381 scalar input length error", + Self::Bls12381G1AddInputLength => "bls12-381 g1 add input length error", + Self::Bls12381G1MsmInputLength => "bls12-381 g1 msm input length error", + Self::Bls12381G2AddInputLength => "bls12-381 g2 add input length error", + Self::Bls12381G2MsmInputLength => "bls12-381 g2 msm input length error", + Self::Bls12381PairingInputLength => "bls12-381 pairing input length error", + Self::Bls12381MapFpToG1InputLength => "bls12-381 map fp to g1 input length error", + Self::Bls12381MapFp2ToG2InputLength => "bls12-381 map fp2 to g2 input length error", + Self::Bls12381FpPaddingInvalid => "bls12-381 fp 64 top bytes of input are not zero", + Self::Bls12381FpPaddingLength => "bls12-381 fp padding length error", + Self::Bls12381G1PaddingLength => "bls12-381 g1 padding length error", + Self::Bls12381G2PaddingLength => "bls12-381 g2 padding length error", }; f.write_str(s) } diff --git a/crates/primitives/src/specification.rs b/crates/primitives/src/specification.rs index eabb3f812b..c10e3a86fe 100644 --- a/crates/primitives/src/specification.rs +++ b/crates/primitives/src/specification.rs @@ -30,7 +30,6 @@ pub enum SpecId { CANCUN = 17, // Cancun 19426587 (Timestamp: 1710338135) PRAGUE = 18, // Prague TBD PRAGUE_EOF = 19, // Prague+EOF TBD - OSAKA = 20, // Osaka #[default] LATEST = u8::MAX, } @@ -212,7 +211,6 @@ impl From for &'static str { SpecId::CANCUN => "Cancun", SpecId::PRAGUE => "Prague", SpecId::PRAGUE_EOF => "PragueEOF", - SpecId::OSAKA => "Osaka", #[cfg(feature = "optimism")] SpecId::BEDROCK => "Bedrock", #[cfg(feature = "optimism")] diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 2357d3f507..bec3517ba9 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -67,7 +67,7 @@ rstest = "0.22.0" alloy-provider = "0.3" [features] -default = ["std", "kzg-rs", "portable"] +default = ["std", "kzg-rs", "portable", "morph-default-handler"] std = [ "serde?/std", "serde_json?/std", diff --git a/crates/revm/src/context/evm_context.rs b/crates/revm/src/context/evm_context.rs index 6b651758d1..e527917592 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -530,6 +530,8 @@ pub(crate) mod test_utils { error: Ok(()), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, + #[cfg(feature = "morph")] + token_fee_info: None, }, precompiles: ContextPrecompiles::default(), } @@ -545,6 +547,8 @@ pub(crate) mod test_utils { error: Ok(()), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, + #[cfg(feature = "morph")] + token_fee_info: None, }, precompiles: ContextPrecompiles::default(), } diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index ab86a9ee1f..6d54eb4b43 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "morph")] +use crate::morph::TokenFeeInfo; use crate::{ db::Database, interpreter::{ @@ -33,6 +35,9 @@ pub struct InnerEvmContext { /// Used as temporary value holder to store L1 block info. #[cfg(feature = "morph")] pub l1_block_info: Option, + /// Used as temporary value holder to store alt fee info. + #[cfg(feature = "morph")] + pub token_fee_info: Option, } impl Clone for InnerEvmContext @@ -47,6 +52,8 @@ where error: self.error.clone(), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: self.l1_block_info.clone(), + #[cfg(feature = "morph")] + token_fee_info: self.token_fee_info.clone(), } } } @@ -60,6 +67,8 @@ impl InnerEvmContext { error: Ok(()), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, + #[cfg(feature = "morph")] + token_fee_info: None, } } @@ -73,6 +82,8 @@ impl InnerEvmContext { error: Ok(()), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, + #[cfg(feature = "morph")] + token_fee_info: None, } } @@ -88,6 +99,8 @@ impl InnerEvmContext { error: Ok(()), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: self.l1_block_info, + #[cfg(feature = "morph")] + token_fee_info: self.token_fee_info, } } diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 55781adddd..984baeb5ba 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -5,13 +5,16 @@ use crate::{ interpreter::{ CallInputs, CreateInputs, EOFCreateInputs, Host, InterpreterAction, SharedMemory, }, + morph::{get_mapping_account_slot, TokenFeeInfo}, primitives::{ - specification::SpecId, BlockEnv, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, - ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, EOF_MAGIC_BYTES, + eth_to_token, specification::SpecId, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, + EnvWithHandlerCfg, ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, + EOF_MAGIC_BYTES, U256, }, Context, ContextWithHandlerCfg, Frame, FrameOrResult, FrameResult, }; use core::fmt; +use revm_interpreter::Gas; use std::{boxed::Box, vec::Vec}; /// EVM call stack limit. @@ -227,6 +230,15 @@ impl Evm<'_, EXT, DB> { /// This function will validate the transaction. #[inline] pub fn transact(&mut self) -> EVMResult { + let context = &mut self.context; + let fee_token_id = context.evm.inner.env().tx.fee_token_id.unwrap_or_default(); + if fee_token_id != 0 { + let caller = context.evm.inner.env.tx.caller; + let token_fee_info = + TokenFeeInfo::try_fetch(&mut context.evm.inner.db, fee_token_id, caller) + .map_err(EVMError::Database)?; + context.evm.inner.token_fee_info = token_fee_info; + } let initial_gas_spend = self.preverify_transaction_inner().inspect_err(|_| { self.clear(); })?; @@ -323,9 +335,9 @@ impl Evm<'_, EXT, DB> { /// Transact pre-verified transaction. fn transact_preverified_inner(&mut self, initial_gas_spend: u64) -> EVMResult { let spec_id = self.spec_id(); + let ctx = &mut self.context; let pre_exec = self.handler.pre_execution(); - // load access list and beneficiary if needed. pre_exec.load_accounts(ctx)?; @@ -334,9 +346,16 @@ impl Evm<'_, EXT, DB> { ctx.evm.set_precompiles(precompiles); // deduce caller balance with its limit. - pre_exec.deduct_caller(ctx)?; + let token_fee_info = ctx.evm.inner.token_fee_info.clone(); + if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { + self.deduct_caller_with_token(token_fee_info)?; + } else { + self.handler.pre_execution().deduct_caller(ctx)?; + } + let ctx = &mut self.context; let gas_limit = ctx.evm.env.tx.gas_limit - initial_gas_spend; + let pre_exec = self.handler.pre_execution(); // apply EIP-7702 auth list. let eip7702_gas_refund = pre_exec.apply_eip7702_auth_list(ctx)? as i64; @@ -383,12 +402,204 @@ impl Evm<'_, EXT, DB> { // calculate final refund and add EIP-7702 refund to gas. post_exec.refund(ctx, result.gas_mut(), eip7702_gas_refund); // Reimburse the caller - post_exec.reimburse_caller(ctx, result.gas())?; + + let ctx = &mut self.context; + let token_fee_info = ctx.evm.inner.token_fee_info.clone(); + if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { + self.reimburse_caller_with_token(token_fee_info, result.gas())?; + } else { + post_exec.reimburse_caller(ctx, result.gas())?; + } + + let ctx = &mut self.context; + let post_exec = self.handler.post_execution(); // Reward beneficiary - post_exec.reward_beneficiary(ctx, result.gas())?; + let token_fee_info = ctx.evm.inner.token_fee_info.clone(); + if token_fee_info.is_none() { + post_exec.reward_beneficiary(ctx, result.gas())?; + } // Returns output of transaction. post_exec.output(ctx, result) } + + pub fn deduct_caller_with_token( + &mut self, + token_info: Option, + ) -> Result<(), EVMError> { + let Some(token_info) = token_info else { + return Err(EVMError::Custom( + "[MORPH] Failed to calculate token gas.".to_string(), + )); + }; + + let ctx: &Context = &self.context; + let Some(rlp_bytes) = &ctx.evm.inner.env.tx.morph.rlp_bytes else { + return Err(EVMError::Custom( + "[MORPH] Failed to load transaction rlp_bytes.".to_string(), + )); + }; + + let tx_l1_cost = self + .context + .evm + .inner + .l1_block_info + .as_ref() + .expect("L1BlockInfo should be loaded") + .calculate_tx_l1_cost(rlp_bytes, self.spec_id()); + let gas_cost = + U256::from(ctx.evm.env.tx.gas_limit).saturating_mul(ctx.evm.env.effective_gas_price()); + let amount = tx_l1_cost + gas_cost; + let token_amount = eth_to_token(amount, token_info.price_ratio, token_info.scale); + if token_amount.is_zero() { + return Err(EVMError::Custom( + "[MORPH] Failed to calculate token gas.".to_string(), + )); + } + if token_amount > token_info.balance { + return Err(EVMError::Custom( + "[MORPH] Token balance is insufficient to pay gas.".to_string(), + )); + } + + if token_info.balance_slot.is_none() { + let _ = self.transfer_token_evm(token_info, token_amount, true)?; + } else { + let ctx = &mut self.context; + let mut acc = ctx.evm.load_account(token_info.token_address)?; + acc.mark_touch(); + transfer_token_sstore(token_info.clone(), token_amount, ctx, true)? + } + + let ctx = &mut self.context; + let mut caller_account = ctx + .evm + .inner + .journaled_state + .load_account(ctx.evm.inner.env.tx.caller, &mut ctx.evm.inner.db)?; + + if matches!(ctx.evm.inner.env.tx.transact_to, TxKind::Call(_)) { + // Nonce is already checked + caller_account.data.info.nonce = caller_account.data.info.nonce.saturating_add(1); + } + caller_account.mark_touch(); + + Ok(()) + } + + pub fn reimburse_caller_with_token( + &mut self, + token_info: Option, + gas: &Gas, + ) -> Result<(), EVMError> { + let Some(token_info) = token_info else { + return Err(EVMError::Custom( + "[MORPH] Failed to calculate token gas.".to_string(), + )); + }; + let ctx = &mut self.context; + let effective_gas_price = ctx.evm.env.effective_gas_price(); + let amount = effective_gas_price * U256::from(gas.remaining() + gas.refunded() as u64); + if amount.is_zero() { + return Ok(()); + } + let token_amount = eth_to_token(amount, token_info.price_ratio, token_info.scale); + if token_amount.is_zero() { + return Err(EVMError::Custom( + "[MORPH] Failed to calculate token reimburse.".to_string(), + )); + } + if token_info.balance_slot.is_none() { + let _ = self.transfer_token_evm(token_info, token_amount, false)?; + } else { + transfer_token_sstore(token_info.clone(), token_amount, ctx, false)?; + } + + Ok(()) + } + + fn transfer_token_evm( + &mut self, + token_info: TokenFeeInfo, + amount: U256, + forward: bool, + ) -> Result> { + let l2_fee_vault = self.context.env().block.coinbase; + let (from, to) = if forward { + (token_info.caller, l2_fee_vault) + } else { + (l2_fee_vault, token_info.caller) + }; + // Call transfer(address,uint256) method via EVM + // Method signature: transfer(address,uint256) -> 0xa9059cbb + let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; + + // Encode calldata: method_id + padded to address + amount + let mut calldata = Vec::with_capacity(68); + calldata.extend_from_slice(&method_id); + let mut address_bytes = [0u8; 32]; + address_bytes[12..32].copy_from_slice(to.as_slice()); + calldata.extend_from_slice(&address_bytes); + calldata.extend_from_slice(&amount.to_be_bytes::<32>()); + let mut tx = TxEnv { + caller: from, + gas_limit: 1_000_00u64, + gas_price: U256::ZERO, + transact_to: TxKind::Call(token_info.token_address), + value: U256::ZERO, + data: Bytes::from(calldata), + nonce: None, + chain_id: None, + ..Default::default() + }; + tx.morph.is_l1_msg = false; + tx.morph.rlp_bytes = Some(Bytes::default()); + let ctx = &mut self.context; + let exec = self.handler.execution(); + let call = exec.call(ctx, CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap())?; + match call { + FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame), + FrameOrResult::Result(result) => Ok(result), + } + } +} + +fn transfer_token_sstore( + token_info: TokenFeeInfo, + amount: U256, + ctx: &mut Context, + forward: bool, +) -> Result<(), EVMError> { + let l2_fee_vault = ctx.env().block.coinbase; + let (from, to) = if forward { + (token_info.caller, l2_fee_vault) + } else { + (l2_fee_vault, token_info.caller) + }; + // sub amount + let balance_slot = get_mapping_account_slot(token_info.balance_slot.unwrap_or_default(), from); + let balance = ctx + .evm + .sload(token_info.token_address, balance_slot) + .unwrap_or_default(); + ctx.evm.sstore( + token_info.token_address, + balance_slot, + balance.saturating_sub(amount), + )?; + + // add amount + let balance_slot = get_mapping_account_slot(token_info.balance_slot.unwrap_or_default(), to); + let balance = ctx + .evm + .sload(token_info.token_address, balance_slot) + .unwrap_or_default(); + ctx.evm.sstore( + token_info.token_address, + balance_slot, + balance.saturating_add(amount), + )?; + Ok(()) } #[cfg(test)] diff --git a/crates/revm/src/handler/mainnet/validation.rs b/crates/revm/src/handler/mainnet/validation.rs index 8aa4c86c23..00f1fa23a0 100644 --- a/crates/revm/src/handler/mainnet/validation.rs +++ b/crates/revm/src/handler/mainnet/validation.rs @@ -1,3 +1,4 @@ +use crate::primitives::U256; use revm_interpreter::gas; use crate::{ @@ -25,11 +26,31 @@ pub fn validate_tx_against_state( .journaled_state .load_code(tx_caller, &mut context.evm.inner.db)?; + if context.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 + && context.evm.inner.token_fee_info.is_none() + { + return Err(EVMError::Custom( + "[MORPH] Failed to load token_fee_info.".to_string(), + )); + } + + let token_fee_info = match &context.evm.inner.token_fee_info { + Some(fee_info) => { + if !fee_info.is_active { + return Err(EVMError::Custom( + "[MORPH]token_fee_info is not active.".to_string(), + )); + } + (fee_info.balance, fee_info.price_ratio, fee_info.scale) + } + None => (U256::ZERO, U256::ZERO, U256::ZERO), + }; + context .evm .inner .env - .validate_tx_against_state::(caller_account.data) + .validate_tx_against_state::(caller_account.data, token_fee_info) .map_err(EVMError::Transaction)?; Ok(()) diff --git a/crates/revm/src/morph.rs b/crates/revm/src/morph.rs index 2097c88845..5c6c3fdbab 100644 --- a/crates/revm/src/morph.rs +++ b/crates/revm/src/morph.rs @@ -1,7 +1,9 @@ mod handler_register; mod l1block; +pub mod token_fee; pub use crate::morph::handler_register::{ deduct_caller, load_accounts, morph_handle_register, reward_beneficiary, }; pub use crate::morph::l1block::{L1BlockInfo, L1_GAS_PRICE_ORACLE_ADDRESS}; +pub use crate::morph::token_fee::{get_mapping_account_slot, get_mapping_slot, TokenFeeInfo}; diff --git a/crates/revm/src/morph/handler_register.rs b/crates/revm/src/morph/handler_register.rs index b4b38c0847..f828826a29 100644 --- a/crates/revm/src/morph/handler_register.rs +++ b/crates/revm/src/morph/handler_register.rs @@ -1,5 +1,4 @@ //! Handler related to Morph chain - use crate::handler::mainnet; use crate::handler::mainnet::deduct_caller_inner; use crate::{ @@ -34,7 +33,6 @@ pub fn load_accounts( crate::morph::L1BlockInfo::try_fetch(&mut context.evm.inner.db, SPEC::SPEC_ID) .map_err(EVMError::Database)?; context.evm.inner.l1_block_info = Some(l1_block_info); - mainnet::load_accounts::(context) } diff --git a/crates/revm/src/morph/token_fee.rs b/crates/revm/src/morph/token_fee.rs new file mode 100644 index 0000000000..12ce4638c3 --- /dev/null +++ b/crates/revm/src/morph/token_fee.rs @@ -0,0 +1,207 @@ +use crate::primitives::{address, Address, EVMError, U256}; +use crate::primitives::{keccak256, Bytes, TxEnv, TxKind}; +use crate::{Database, Evm}; + +// https://github.com/morph-l2/morph/blob/main/contracts/contracts/l2/system/L2TokenRegistry.sol +// TokenRegistry is the storage slot for mapping(uint16 => TokenInfo) - slot 151 +const TOKEN_REGISTRY_SLOT: U256 = U256::from_limbs([151u64, 0, 0, 0]); +// PriceRatio is the storage slot for mapping(uint16 => uint256) - slot 153 +const PRICE_RATIO_SLOT: U256 = U256::from_limbs([153u64, 0, 0, 0]); +// System address for L2 token registry +pub const L2_TOKEN_REGISTRY_ADDRESS: Address = address!("5300000000000000000000000000000000000021"); + +#[derive(Clone, Debug, Default)] +pub struct TokenFeeInfo { + /// The fee token address + pub token_address: Address, + /// Whether the token is active + pub is_active: bool, + /// Token decimals + pub decimals: u8, + /// The price ratio of the token + pub price_ratio: U256, + /// The scale of the token + pub scale: U256, + /// The caller address + pub caller: Address, + /// The token balance of caller + pub balance: U256, + /// The users' erc20 balance slot + pub balance_slot: Option, +} + +impl TokenFeeInfo { + // Get the token information for gas payment from the state db. + pub fn try_fetch( + db: &mut DB, + token_id: u16, + caller: Address, + ) -> Result, DB::Error> { + // Get the base slot for this token_id in tokenRegistry mapping + let mut token_id_bytes = [0u8; 32]; + token_id_bytes[30..32].copy_from_slice(&token_id.to_be_bytes()); + let token_registry_base = get_mapping_slot(TOKEN_REGISTRY_SLOT, token_id_bytes.to_vec()); + // TokenInfo struct layout in storage (following Solidity storage packing rules): + // slot + 0: tokenAddress (address, 20 bytes) + 12 bytes padding + // slot + 1: balanceSlot (bytes32, 32 bytes) + // slot + 2: isActive (bool, 1 byte) + decimals (uint8, 1 byte) + 30 bytes padding + // slot + 3: scale (uint256, 32 bytes) + + // Read tokenAddress from slot + 0 + let slot_0 = db.storage(L2_TOKEN_REGISTRY_ADDRESS, token_registry_base)?; + let token_address = Address::from_word(slot_0.into()); + if token_address == Address::default() { + return Ok(None); + } + + // Read balanceSlot from slot + 1 + let balance_slot_value = db.storage( + L2_TOKEN_REGISTRY_ADDRESS, + token_registry_base + U256::from(1), + )?; + let token_balance_slot = if !balance_slot_value.is_zero() { + Some(balance_slot_value.saturating_sub(U256::from(1u64))) + } else { + None + }; + + // Read isActive and decimals from slot + 2 + // In big-endian representation, rightmost byte is the lowest position + // isActive is at the rightmost (byte 31), decimals is to its left (byte 30) + let slot_2 = db.storage( + L2_TOKEN_REGISTRY_ADDRESS, + token_registry_base + U256::from(2), + )?; + let slot_2_bytes = slot_2.to_be_bytes::<32>(); + let is_active = slot_2_bytes[31] != 0; + let decimals = slot_2_bytes[30]; + + // Read scale from slot + 3 + let scale = db.storage( + L2_TOKEN_REGISTRY_ADDRESS, + token_registry_base + U256::from(3), + )?; + + // Get price ratio from priceRatio mapping + let price_ratio = load_mapping_value( + db, + L2_TOKEN_REGISTRY_ADDRESS, + PRICE_RATIO_SLOT, + token_id_bytes.to_vec(), + )?; + + // Get caller's token balance + let caller_token_balance = + get_erc20_balance(db, token_address, caller, token_balance_slot)?; + let token_fee = TokenFeeInfo { + token_address, + is_active, + decimals, + price_ratio, + scale, + caller, + balance: caller_token_balance, + balance_slot: token_balance_slot, + }; + Ok(Some(token_fee)) + } +} + +/// Calculate the storage slot for a mapping value +pub fn get_mapping_slot(slot_index: U256, mut key: Vec) -> U256 { + let mut pre_image = slot_index.to_be_bytes_vec(); + key.append(&mut pre_image); + let storage_key = keccak256(key); + U256::from_be_bytes(storage_key.0) +} + +/// Calculate the account's storage slot for a mapping value +#[inline] +pub fn get_mapping_account_slot(slot_index: U256, account: Address) -> U256 { + let mut key = [0u8; 32]; + key[12..32].copy_from_slice(account.as_slice()); + get_mapping_slot(slot_index, key.to_vec()) +} + +fn load_mapping_value( + db: &mut DB, + account: Address, + slot_index: U256, + key: Vec, +) -> Result::Error> { + let storage_slot = get_mapping_slot(slot_index, key); + let storage_value = db.storage(account, storage_slot)?; + Ok(storage_value) +} + +pub(super) fn get_erc20_balance( + db: &mut DB, + token: Address, + account: Address, + token_balance_slot: Option, +) -> Result { + // If balance slot is provided, try to read directly from storage + if let Some(slot) = token_balance_slot { + let mut data = [0u8; 32]; + data[12..32].copy_from_slice(account.as_slice()); + if let Ok(balance) = load_mapping_value(db, token, slot, data.to_vec()) { + return Ok(balance); + } + } + + // Fallback: call balanceOf(address) method + // Method signature: balanceOf(address) -> 0x70a08231 + let method_id = [0x70u8, 0xa0, 0x82, 0x31]; + // Encode calldata: method_id + padded address + let mut calldata = Vec::with_capacity(36); + calldata.extend_from_slice(&method_id); + calldata.extend_from_slice(&[0u8; 12]); // Pad address to 32 bytes + calldata.extend_from_slice(account.as_slice()); + + let db: &mut dyn Database = db; + let mut evm = Evm::builder().with_db(db).build(); + let mut tx = TxEnv { + caller: Address::default(), + gas_limit: 1000000u64, + transact_to: TxKind::Call(token), + value: U256::ZERO, + data: Bytes::from(calldata), + nonce: None, + chain_id: None, + ..Default::default() + }; + tx.morph.is_l1_msg = true; + tx.morph.rlp_bytes = Some(Bytes::default()); + evm.context.evm.env.tx = tx; + + // Execute transaction and extract balance from output + match evm.transact() { + Ok(result) => { + if result.result.is_success() { + // Parse the returned balance (32 bytes) + if let Some(output) = result.result.output() { + if output.len() >= 32 { + return Ok(U256::from_be_slice(&output[..32])); + } + } + } + Ok(U256::ZERO) + } + Err(EVMError::Database(db_err)) => { + println!("get_erc20_balance db error"); + Err(db_err) + } + Err(e) => { + match &e { + EVMError::Transaction(t) => { + println!("get_erc20_balance Transaction error: {:?}", t) + } + EVMError::Header(h) => println!("get_erc20_balance Header error: {:?}", h), + EVMError::Database(_) => println!("get_erc20_balance Database error"), + EVMError::Custom(c) => println!("get_erc20_balance Custom error: {}", c), + EVMError::Precompile(p) => println!("get_erc20_balance Precompile error: {}", p), + } + Ok(U256::ZERO) + } + } +}