From e1a52e9d77f42fdc4b9098fde234a9a2a2e99c55 Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Fri, 17 Dec 2021 18:15:52 +0800 Subject: [PATCH 1/5] support eip-1559 --- modules/evm/src/lib.rs | 1 + primitives/src/evm.rs | 5 +++-- primitives/src/unchecked_extrinsic.rs | 27 +++++++++++++-------------- runtime/mandala/src/lib.rs | 9 +++++++-- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/modules/evm/src/lib.rs b/modules/evm/src/lib.rs index d7d6e87940..5a5cc0ee58 100644 --- a/modules/evm/src/lib.rs +++ b/modules/evm/src/lib.rs @@ -501,6 +501,7 @@ pub mod module { #[pallet::compact] value: BalanceOf, #[pallet::compact] gas_limit: u64, #[pallet::compact] storage_limit: u32, + #[pallet::compact] _priority_fee: BalanceOf, // checked by tx validation logic #[pallet::compact] _valid_until: T::BlockNumber, // checked by tx validation logic ) -> DispatchResultWithPostInfo { match action { diff --git a/primitives/src/evm.rs b/primitives/src/evm.rs index 5a7ad5a799..9f136cd9e0 100644 --- a/primitives/src/evm.rs +++ b/primitives/src/evm.rs @@ -88,15 +88,16 @@ pub struct EstimateResourcesRequest { #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct EthereumTransactionMessage { + pub chain_id: u64, + pub genesis: H256, pub nonce: Nonce, pub tip: Balance, + pub priority_fee: Balance, pub gas_limit: u64, pub storage_limit: u32, pub action: TransactionAction, pub value: Balance, pub input: Vec, - pub chain_id: u64, - pub genesis: H256, pub valid_until: BlockNumber, } diff --git a/primitives/src/unchecked_extrinsic.rs b/primitives/src/unchecked_extrinsic.rs index 1d8974edb0..d9638b51fe 100644 --- a/primitives/src/unchecked_extrinsic.rs +++ b/primitives/src/unchecked_extrinsic.rs @@ -23,7 +23,7 @@ use frame_support::{ traits::{ExtrinsicCall, Get}, weights::{DispatchInfo, GetDispatchInfo}, }; -use module_evm_utiltity::ethereum::{LegacyTransactionMessage, TransactionAction}; +use module_evm_utiltity::ethereum::{EIP1559TransactionMessage, TransactionAction}; use module_evm_utiltity_macro::keccak256; use scale_info::TypeInfo; use sp_core::{H160, H256, U256}; @@ -115,11 +115,6 @@ where let function = self.0.function; let eth_msg = ConvertTx::convert((function.clone(), extra.clone()))?; - if eth_msg.tip != 0 { - // Not yet supported, require zero tip - return Err(InvalidTransaction::BadProof.into()); - } - // tx_gas_price = tx_fee_per_gas + block_period << 16 + storage_entry_limit // tx_gas_limit = gas_limit + storage_entry_deposit / tx_fee_per_gas * storage_entry_limit let block_period = eth_msg.valid_until.checked_div(30).expect("divisor is non-zero; qed"); @@ -147,14 +142,16 @@ where eth_msg.storage_limit, eth_msg.gas_limit, tx_gas_limit, tx_gas_price ); - let msg = LegacyTransactionMessage { + let msg = EIP1559TransactionMessage { + chain_id: eth_msg.chain_id, nonce: eth_msg.nonce.into(), - gas_price: tx_gas_price.into(), + max_priority_fee_per_gas: eth_msg.priority_fee.into(), + max_fee_per_gas: tx_gas_price.into(), gas_limit: tx_gas_limit.into(), action: eth_msg.action, value: eth_msg.value.into(), input: eth_msg.input, - chain_id: Some(eth_msg.chain_id), + access_list: vec![], }; let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster @@ -285,6 +282,8 @@ mod tests { #[test] fn verify_eip712_should_works() { let msg = EthereumTransactionMessage { + chain_id: 595, + genesis: H256::from_str("0xc3751fc073ec83e6aa13e2be395d21b05dce0692618a129324261c80ede07d4c").unwrap(), nonce: 1, tip: 2, gas_limit: 222, @@ -292,8 +291,6 @@ mod tests { action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), value: 111, input: vec![], - chain_id: 595, - genesis: H256::from_str("0xc3751fc073ec83e6aa13e2be395d21b05dce0692618a129324261c80ede07d4c").unwrap(), valid_until: 444, }; let sign = hex_literal::hex!("acb56f12b407bd0bc8f7abefe2e2585affe28009abcb6980aa33aecb815c56b324ab60a41eff339a88631c4b0e5183427be1fcfde3c05fb9b6c71a691e977c4a1b"); @@ -344,14 +341,16 @@ mod tests { #[test] fn verify_eth_should_works() { - let msg = LegacyTransactionMessage { + let msg = EIP1559TransactionMessage { + chain_id: 595, nonce: U256::from(1), - gas_price: U256::from("0x640000006a"), + max_priority_fee_per_gas: 0, + max_fee_per_gas: U256::from("0x640000006a"), gas_limit: U256::from(21000), action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), value: U256::from(123123), input: vec![], - chain_id: Some(595), + access_list: vec![], }; let sign = hex_literal::hex!("f84345a6459785986a1b2df711fe02597d70c1393757a243f8f924ea541d2ecb51476de1aa437cd820d59e1d9836e37e643fec711fe419464e637cab592918751c"); diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 21de2aa7f5..3b3c89aaea 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1968,6 +1968,7 @@ impl Convert<(Call, SignedExtra), Result { if System::block_number() > valid_until { @@ -1985,17 +1986,21 @@ impl Convert<(Call, SignedExtra), Result = extra.6; let tip = tip.0; + if tip != priority_fee.saturating_mul(gas_limit.into()) { + return Err(InvalidTransaction::BadProof); + } Ok(EthereumTransactionMessage { + chain_id: ChainId::get(), + genesis: System::block_hash(0), nonce, tip, + priority_fee, gas_limit, storage_limit, action, value, input, - chain_id: ChainId::get(), - genesis: System::block_hash(0), valid_until, }) } From 065dfafa0287ffada3f0c60794ee940eab7cb956 Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Fri, 17 Dec 2021 18:53:55 +0800 Subject: [PATCH 2/5] add AcalaMultiSignature::Eip1559 --- modules/evm/src/lib.rs | 1 - primitives/src/evm.rs | 1 - primitives/src/signature.rs | 2 + primitives/src/unchecked_extrinsic.rs | 114 +++++++++++++++++++++++++- runtime/mandala/src/lib.rs | 5 -- 5 files changed, 112 insertions(+), 11 deletions(-) diff --git a/modules/evm/src/lib.rs b/modules/evm/src/lib.rs index 5a5cc0ee58..d7d6e87940 100644 --- a/modules/evm/src/lib.rs +++ b/modules/evm/src/lib.rs @@ -501,7 +501,6 @@ pub mod module { #[pallet::compact] value: BalanceOf, #[pallet::compact] gas_limit: u64, #[pallet::compact] storage_limit: u32, - #[pallet::compact] _priority_fee: BalanceOf, // checked by tx validation logic #[pallet::compact] _valid_until: T::BlockNumber, // checked by tx validation logic ) -> DispatchResultWithPostInfo { match action { diff --git a/primitives/src/evm.rs b/primitives/src/evm.rs index 9f136cd9e0..cf003a401a 100644 --- a/primitives/src/evm.rs +++ b/primitives/src/evm.rs @@ -92,7 +92,6 @@ pub struct EthereumTransactionMessage { pub genesis: H256, pub nonce: Nonce, pub tip: Balance, - pub priority_fee: Balance, pub gas_limit: u64, pub storage_limit: u32, pub action: TransactionAction, diff --git a/primitives/src/signature.rs b/primitives/src/signature.rs index 90daabfb54..ca00bdc699 100644 --- a/primitives/src/signature.rs +++ b/primitives/src/signature.rs @@ -37,6 +37,8 @@ pub enum AcalaMultiSignature { Ecdsa(ecdsa::Signature), // An Ethereum compatible SECP256k1 signature. Ethereum([u8; 65]), + // An Ethereum SECP256k1 signature using Eip1559 for message encoding. + Eip1559([u8; 65]), // An Ethereum SECP256k1 signature using Eip712 for message encoding. AcalaEip712([u8; 65]), } diff --git a/primitives/src/unchecked_extrinsic.rs b/primitives/src/unchecked_extrinsic.rs index d9638b51fe..412e5b6de0 100644 --- a/primitives/src/unchecked_extrinsic.rs +++ b/primitives/src/unchecked_extrinsic.rs @@ -23,7 +23,7 @@ use frame_support::{ traits::{ExtrinsicCall, Get}, weights::{DispatchInfo, GetDispatchInfo}, }; -use module_evm_utiltity::ethereum::{EIP1559TransactionMessage, TransactionAction}; +use module_evm_utiltity::ethereum::{EIP1559TransactionMessage, LegacyTransactionMessage, TransactionAction}; use module_evm_utiltity_macro::keccak256; use scale_info::TypeInfo; use sp_core::{H160, H256, U256}; @@ -138,14 +138,74 @@ where .saturating_add(eth_msg.gas_limit.into()); log::trace!( - target: "evm", "eth_msg.gas_limit: {:?}, eth_msg.storage_limit: {:?}, tx_gas_limit: {:?}, tx_gas_price: {:?}", - eth_msg.storage_limit, eth_msg.gas_limit, tx_gas_limit, tx_gas_price + target: "evm", "eth_msg.tip: {:?}, eth_msg.gas_limit: {:?}, eth_msg.storage_limit: {:?}, tx_gas_limit: {:?}, tx_gas_price: {:?}", + eth_msg.tip, eth_msg.storage_limit, eth_msg.gas_limit, tx_gas_limit, tx_gas_price + ); + + let msg = LegacyTransaction { + nonce: eth_msg.nonce.into(), + gas_price: tx_gas_price.into(), + gas_limit: tx_gas_limit.into(), + action: eth_msg.action, + value: eth_msg.value.into(), + input: eth_msg.input, + chain_id: Some(eth_msg.chain_id), + }; + + let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster + + let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; + + let acc = lookup.lookup(Address::Address20(signer.into()))?; + let expected = lookup.lookup(addr)?; + + if acc != expected { + return Err(InvalidTransaction::BadProof.into()); + } + + Ok(CheckedExtrinsic { + signed: Some((acc, extra)), + function, + }) + } + Some((addr, AcalaMultiSignature::Eip1559(sig), extra)) => { + let function = self.0.function; + let eth_msg = ConvertTx::convert((function.clone(), extra.clone()))?; + + // tx_gas_price = tx_fee_per_gas + block_period << 16 + storage_entry_limit + // tx_gas_limit = gas_limit + storage_entry_deposit / tx_fee_per_gas * storage_entry_limit + let block_period = eth_msg.valid_until.checked_div(30).expect("divisor is non-zero; qed"); + // u16: max value 0xffff * 64 = 4194240 bytes = 4MB + let storage_entry_limit: u16 = eth_msg + .storage_limit + .checked_div(64) + .expect("divisor is non-zero; qed") + .try_into() + .map_err(|_| InvalidTransaction::BadProof)?; + let storage_entry_deposit = StorageDepositPerByte::get().saturating_mul(64); + let tx_gas_price = TxFeePerGas::get() + .saturating_add((block_period << 16).into()) + .saturating_add(storage_entry_limit.into()); + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + let tx_gas_limit = storage_entry_deposit + .checked_div(TxFeePerGas::get()) + .expect("divisor is non-zero; qed") + .saturating_mul(storage_entry_limit.into()) + .saturating_add(eth_msg.gas_limit.into()); + + // tip = priority_fee * gas_limit + let priority_fee = tip.checked_div(gas_limit).unwrap_or_default(); + + log::trace!( + target: "evm", "eth_msg.tip: {:?}, eth_msg.gas_limit: {:?}, eth_msg.storage_limit: {:?}, tx_gas_limit: {:?}, tx_gas_price: {:?}", + eth_msg.tip, eth_msg.storage_limit, eth_msg.gas_limit, tx_gas_limit, tx_gas_price ); let msg = EIP1559TransactionMessage { chain_id: eth_msg.chain_id, nonce: eth_msg.nonce.into(), - max_priority_fee_per_gas: eth_msg.priority_fee.into(), + max_priority_fee_per_gas: priority_fee.into(), max_fee_per_gas: tx_gas_price.into(), gas_limit: tx_gas_limit.into(), action: eth_msg.action, @@ -341,6 +401,52 @@ mod tests { #[test] fn verify_eth_should_works() { + let msg = LegacyTransactionMessage { + nonce: U256::from(1), + gas_price: U256::from("0x640000006a"), + gas_limit: U256::from(21000), + action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), + value: U256::from(123123), + input: vec![], + chain_id: Some(595), + }; + + let sign = hex_literal::hex!("f84345a6459785986a1b2df711fe02597d70c1393757a243f8f924ea541d2ecb51476de1aa437cd820d59e1d9836e37e643fec711fe419464e637cab592918751c"); + let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); + + assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.nonce = new_msg.nonce.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_price = new_msg.gas_price.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_limit = new_msg.gas_limit.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.action = TransactionAction::Create; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.value = new_msg.value.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.input = vec![0x00]; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg; + new_msg.chain_id = None; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + } + + #[test] + fn verify_eth_1559_should_works() { let msg = EIP1559TransactionMessage { chain_id: 595, nonce: U256::from(1), diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 3b3c89aaea..9f5e1187e2 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1968,7 +1968,6 @@ impl Convert<(Call, SignedExtra), Result { if System::block_number() > valid_until { @@ -1986,16 +1985,12 @@ impl Convert<(Call, SignedExtra), Result = extra.6; let tip = tip.0; - if tip != priority_fee.saturating_mul(gas_limit.into()) { - return Err(InvalidTransaction::BadProof); - } Ok(EthereumTransactionMessage { chain_id: ChainId::get(), genesis: System::block_hash(0), nonce, tip, - priority_fee, gas_limit, storage_limit, action, From 20d65488e8b91a9b92faa8c2120234908d0da031 Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Fri, 17 Dec 2021 21:19:07 +0800 Subject: [PATCH 3/5] add eip1559 tests --- primitives/src/unchecked_extrinsic.rs | 19 +- ts-tests/tests/test-sign-eip1559.ts | 321 ++++++++++++++++++++++++++ ts-tests/tests/test-sign-eth.ts | 8 +- 3 files changed, 339 insertions(+), 9 deletions(-) create mode 100644 ts-tests/tests/test-sign-eip1559.ts diff --git a/primitives/src/unchecked_extrinsic.rs b/primitives/src/unchecked_extrinsic.rs index 412e5b6de0..f5a1c2bd1d 100644 --- a/primitives/src/unchecked_extrinsic.rs +++ b/primitives/src/unchecked_extrinsic.rs @@ -115,6 +115,11 @@ where let function = self.0.function; let eth_msg = ConvertTx::convert((function.clone(), extra.clone()))?; + if eth_msg.tip != 0 { + // Not yet supported, require zero tip + return Err(InvalidTransaction::BadProof.into()); + } + // tx_gas_price = tx_fee_per_gas + block_period << 16 + storage_entry_limit // tx_gas_limit = gas_limit + storage_entry_deposit / tx_fee_per_gas * storage_entry_limit let block_period = eth_msg.valid_until.checked_div(30).expect("divisor is non-zero; qed"); @@ -142,7 +147,7 @@ where eth_msg.tip, eth_msg.storage_limit, eth_msg.gas_limit, tx_gas_limit, tx_gas_price ); - let msg = LegacyTransaction { + let msg = LegacyTransactionMessage { nonce: eth_msg.nonce.into(), gas_price: tx_gas_price.into(), gas_limit: tx_gas_limit.into(), @@ -195,7 +200,7 @@ where .saturating_add(eth_msg.gas_limit.into()); // tip = priority_fee * gas_limit - let priority_fee = tip.checked_div(gas_limit).unwrap_or_default(); + let priority_fee = eth_msg.tip.checked_div(eth_msg.gas_limit.into()).unwrap_or_default(); log::trace!( target: "evm", "eth_msg.tip: {:?}, eth_msg.gas_limit: {:?}, eth_msg.storage_limit: {:?}, tx_gas_limit: {:?}, tx_gas_price: {:?}", @@ -450,7 +455,7 @@ mod tests { let msg = EIP1559TransactionMessage { chain_id: 595, nonce: U256::from(1), - max_priority_fee_per_gas: 0, + max_priority_fee_per_gas: U256::from(1), max_fee_per_gas: U256::from("0x640000006a"), gas_limit: U256::from(21000), action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), @@ -469,7 +474,11 @@ mod tests { assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); let mut new_msg = msg.clone(); - new_msg.gas_price = new_msg.gas_price.add(U256::one()); + new_msg.max_priority_fee_per_gas = new_msg.max_priority_fee_per_gas.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.max_fee_per_gas = new_msg.max_fee_per_gas.add(U256::one()); assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); let mut new_msg = msg.clone(); @@ -489,7 +498,7 @@ mod tests { assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); let mut new_msg = msg; - new_msg.chain_id = None; + new_msg.chain_id = new_msg.chain_id.add(1u64); assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); } } diff --git a/ts-tests/tests/test-sign-eip1559.ts b/ts-tests/tests/test-sign-eip1559.ts new file mode 100644 index 0000000000..b8d1ce23e8 --- /dev/null +++ b/ts-tests/tests/test-sign-eip1559.ts @@ -0,0 +1,321 @@ +import { expect } from "chai"; + +import { describeWithAcala, nextBlock } from "./util"; +import { Signer } from "@acala-network/bodhi"; +import { Wallet } from "@ethersproject/wallet"; +import { encodeAddress } from "@polkadot/keyring"; +import { hexToU8a, u8aConcat, stringToU8a } from "@polkadot/util"; +import { ethers, BigNumber, ContractFactory } from "ethers"; +import Erc20DemoContract from "../build/Erc20DemoContract.json" + +describeWithAcala("Acala RPC (Sign eip1559)", (context) => { + let alice: Signer; + let signer: Wallet; + let subAddr: string; + let factory: ContractFactory; + let contract: string; + + before("init", async function () { + this.timeout(15000); + [alice] = await context.provider.getWallets(); + + signer = new Wallet( + "0x0123456789012345678901234567890123456789012345678901234567890123" + ); + + subAddr = encodeAddress( + u8aConcat( + stringToU8a("evm:"), + hexToU8a(signer.address), + new Uint8Array(8).fill(0) + ) + ); + + expect(subAddr).to.equal("5EMjsczQH4R2WZaB5Svau8HWZp1aAfMqjxfv3GeLWotYSkLc"); + + await context.provider.api.tx.balances.transfer(subAddr, "10_000_000_000_000") + .signAndSend(await alice.getSubstrateAddress()); + + factory = new ethers.ContractFactory(Erc20DemoContract.abi, Erc20DemoContract.bytecode); + }); + + const bigNumDiv = (x: BigNumber, y: BigNumber) => { + const res = x.div(y); + return res.mul(y) === x + ? res + : res.add(1) + } + + it("create should sign and verify", async function () { + this.timeout(150000); + + const chain_id = +context.provider.api.consts.evm.chainId.toString() + const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber() + const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100 + const storageLimit = 20000; + const gasLimit = 2100000; + + const block_period = bigNumDiv(BigNumber.from(validUntil), BigNumber.from(30)); + const storage_entry_limit = bigNumDiv(BigNumber.from(storageLimit), BigNumber.from(64)); + const storage_byte_deposit = BigNumber.from(context.provider.api.consts.evm.storageDepositPerByte.toString()); + const storage_entry_deposit = storage_byte_deposit.mul(64); + const tx_fee_per_gas = BigNumber.from(context.provider.api.consts.evm.txFeePerGas.toString()); + const tx_gas_price = tx_fee_per_gas.add(block_period.toNumber() << 16).add(storage_entry_limit); + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + const tx_gas_limit = storage_entry_deposit.div(tx_fee_per_gas).mul(storage_entry_limit).add(gasLimit); + + const deploy = factory.getDeployTransaction(100000); + + const value = { + type: 2, // EIP-1559 + // to: "0x0000000000000000000000000000000000000000", + nonce, + gasLimit: tx_gas_limit.toNumber(), + data: deploy.data, + value: 0, + chainId: chain_id, + maxPriorityFeePerGas: 0, + maxFeePerGas: tx_gas_price.toHexString(), + } + + const signedTx = await signer.signTransaction(value) + const rawtx = ethers.utils.parseTransaction(signedTx) + + expect(rawtx).to.deep.include({ + type: 2, + chainId: 595, + nonce: 0, + maxPriorityFeePerGas: BigNumber.from(0), + maxFeePerGas: BigNumber.from(200000209209), + gasPrice: null, + gasLimit: BigNumber.from(12116000), + to: null, + value: BigNumber.from(0), + data: deploy.data, + accessList: [], + // v: 1226, + // r: '0xff8ff25480f5e1d1b38603b8fa1f10d64faf81707768dd9016fc4dd86d5474d2', + // s: '0x6c2cfd5acd5b0b820e1c107efd5e7ce2c452b81742091f43f5c793a835c8644f', + from: '0x14791697260E4c9A71f18484C9f997B308e59325', + // hash: '0x456d37c868520b362bbf5baf1b19752818eba49cc92c1a512e2e80d1ccfbc18b', + }); + + // tx data to user input + const input_storage_entry_limit = tx_gas_price.and(0xffff); + const input_storage_limit = input_storage_entry_limit.mul(64); + const input_block_period = (tx_gas_price.sub(input_storage_entry_limit).sub(tx_fee_per_gas).toNumber()) >> 16; + const input_valid_until = input_block_period * 30; + const input_gas_limit = tx_gas_limit.sub(storage_entry_deposit.div(tx_fee_per_gas).mul(input_storage_entry_limit)); + + const tx = context.provider.api.tx.evm.ethCall( + { Create: null }, + value.data, + value.value, + input_gas_limit.toNumber(), + input_storage_limit.toNumber(), + input_valid_until + ); + + const sig = ethers.utils.joinSignature({ r: rawtx.r!, s: rawtx.s, v: rawtx.v }) + + tx.addSignature(subAddr, { Eip1559: sig } as any, { + blockHash: '0x', // ignored + era: "0x00", // mortal + genesisHash: '0x', // ignored + method: "Bytes", // don't know that is this + nonce: nonce, + specVersion: 0, // ignored + tip: 0, + transactionVersion: 0, // ignored + }); + + expect(tx.toString()).to.equal( + `{ + "signature": { + "signer": { + "id": "5EMjsczQH4R2WZaB5Svau8HWZp1aAfMqjxfv3GeLWotYSkLc" + }, + "signature": { + "eip1559": "${sig}" + }, + "era": { + "immortalEra": "0x00" + }, + "nonce": 0, + "tip": 0 + }, + "method": { + "callIndex": "0xb400", + "args": { + "action": { + "create": null + }, + "input": "${deploy.data}", + "value": 0, + "gas_limit": 2100000, + "storage_limit": 20032, + "valid_until": 120 + } + } + }`.toString().replace(/\s/g, '') + ); + + await new Promise(async (resolve) => { + tx.send((result) => { + if (result.status.isFinalized || result.status.isInBlock) { + resolve(undefined); + } + }); + }); + + let current_block_number = (await context.provider.api.query.system.number()).toNumber(); + let block_hash = await context.provider.api.rpc.chain.getBlockHash(current_block_number); + const result = await context.provider.api.derive.tx.events(block_hash); + // console.log("current_block_number: ", current_block_number, " event: ", result.events.toString()); + + let event = result.events.filter(item => context.provider.api.events.evm.Created.is(item.event)); + expect(event.length).to.equal(1); + // console.log(event[0].toString()) + + // get address + contract = event[0].event.data[1].toString(); + }); + + it("call should sign and verify", async function () { + this.timeout(150000); + + const chain_id = +context.provider.api.consts.evm.chainId.toString(); + const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber(); + const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100; + const storageLimit = 1000; + const gasLimit = 210000; + + const block_period = bigNumDiv(BigNumber.from(validUntil), BigNumber.from(30)); + const storage_entry_limit = bigNumDiv(BigNumber.from(storageLimit), BigNumber.from(64)); + const storage_byte_deposit = BigNumber.from(context.provider.api.consts.evm.storageDepositPerByte.toString()); + const storage_entry_deposit = storage_byte_deposit.mul(64); + const tx_fee_per_gas = BigNumber.from(context.provider.api.consts.evm.txFeePerGas.toString()); + const tx_gas_price = tx_fee_per_gas.add(block_period.toNumber() << 16).add(storage_entry_limit); + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + const tx_gas_limit = storage_entry_deposit.div(tx_fee_per_gas).mul(storage_entry_limit).add(gasLimit); + + const receiver = '0x1111222233334444555566667777888899990000'; + const input = await factory.attach(contract).populateTransaction.transfer(receiver, 100); + + const value = { + type: 2, // EIP-1559 + to: contract, + nonce, + gasLimit: tx_gas_limit.toNumber(), + data: input.data, + value: 0, + chainId: chain_id, + maxPriorityFeePerGas: 0, + maxFeePerGas: tx_gas_price.toHexString(), + } + + const signedTx = await signer.signTransaction(value) + const rawtx = ethers.utils.parseTransaction(signedTx) + + expect(rawtx).to.deep.include({ + type: 2, + chainId: 595, + nonce: 1, + maxPriorityFeePerGas: BigNumber.from(0), + maxFeePerGas: BigNumber.from(200000208912), + gasPrice: null, + gasLimit: BigNumber.from(722000), + to: ethers.utils.getAddress(contract), + value: BigNumber.from(0), + data: input.data, + accessList: [], + // v: 1226, + // r: '0xff8ff25480f5e1d1b38603b8fa1f10d64faf81707768dd9016fc4dd86d5474d2', + // s: '0x6c2cfd5acd5b0b820e1c107efd5e7ce2c452b81742091f43f5c793a835c8644f', + from: '0x14791697260E4c9A71f18484C9f997B308e59325', + // hash: '0x456d37c868520b362bbf5baf1b19752818eba49cc92c1a512e2e80d1ccfbc18b', + }); + + // tx data to user input + const input_storage_entry_limit = tx_gas_price.and(0xffff); + const input_storage_limit = input_storage_entry_limit.mul(64); + const input_block_period = (tx_gas_price.sub(input_storage_entry_limit).sub(tx_fee_per_gas).toNumber()) >> 16; + const input_valid_until = input_block_period * 30; + const input_gas_limit = tx_gas_limit.sub(storage_entry_deposit.div(tx_fee_per_gas).mul(input_storage_entry_limit)); + + const tx = context.provider.api.tx.evm.ethCall( + { Call: value.to }, + value.data, + value.value, + input_gas_limit.toNumber(), + input_storage_limit.toNumber(), + input_valid_until + ); + + const sig = ethers.utils.joinSignature({ r: rawtx.r!, s: rawtx.s, v: rawtx.v }) + + tx.addSignature(subAddr, { Eip1559: sig } as any, { + blockHash: '0x', // ignored + era: "0x00", // mortal + genesisHash: '0x', // ignored + method: "Bytes", // don't know that is this + nonce: nonce, + specVersion: 0, // ignored + tip: 0, + transactionVersion: 0, // ignored + }); + + expect(tx.toString()).to.equal( + `{ + "signature": { + "signer": { + "id": "5EMjsczQH4R2WZaB5Svau8HWZp1aAfMqjxfv3GeLWotYSkLc" + }, + "signature": { + "eip1559": "${sig}" + }, + "era": { + "immortalEra": "0x00" + }, + "nonce": 1, + "tip": 0 + }, + "method": { + "callIndex": "0xb400", + "args": { + "action": { + "call": "${contract}" + }, + "input": "${input.data}", + "value": 0, + "gas_limit": 210000, + "storage_limit": 1024, + "valid_until": 120 + } + } + }`.toString().replace(/\s/g, '') + ); + + await new Promise(async (resolve) => { + tx.send((result) => { + if (result.status.isFinalized || result.status.isInBlock) { + resolve(undefined); + } + }); + }); + + await new Promise(async (resolve) => { + context.provider.api.tx.sudo.sudo(context.provider.api.tx.evm.deployFree(contract)).signAndSend(await alice.getSubstrateAddress(), ((result) => { + if (result.status.isFinalized || result.status.isInBlock) { + resolve(undefined); + } + })); + }); + + const erc20 = new ethers.Contract(contract, Erc20DemoContract.abi, alice); + expect((await erc20.balanceOf(signer.address)).toString()).to.equal("99900"); + expect((await erc20.balanceOf(receiver)).toString()).to.equal("100"); + }); +}); diff --git a/ts-tests/tests/test-sign-eth.ts b/ts-tests/tests/test-sign-eth.ts index 54f27dd431..5d34192dab 100644 --- a/ts-tests/tests/test-sign-eth.ts +++ b/ts-tests/tests/test-sign-eth.ts @@ -49,7 +49,7 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { it("create should sign and verify", async function () { this.timeout(150000); - const chanid = +context.provider.api.consts.evm.chainId.toString() + const chainId = +context.provider.api.consts.evm.chainId.toString() const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber() const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100 const storageLimit = 20000; @@ -74,7 +74,7 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { gasPrice: tx_gas_price.toHexString(), data: deploy.data, value: 0, - chainId: chanid, + chainId: chainId, } const signedTx = await signer.signTransaction(value) @@ -180,7 +180,7 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { it("call should sign and verify", async function () { this.timeout(150000); - const chanid = +context.provider.api.consts.evm.chainId.toString(); + const chainId = +context.provider.api.consts.evm.chainId.toString(); const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber(); const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100; const storageLimit = 1000; @@ -206,7 +206,7 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { gasPrice: tx_gas_price.toHexString(), data: input.data, value: 0, - chainId: chanid, + chainId: chainId, } const signedTx = await signer.signTransaction(value) From 33e9e500991cc7cfe16fef1b75ebce31991c8ca4 Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Fri, 17 Dec 2021 21:42:01 +0800 Subject: [PATCH 4/5] add eip1559 unit test --- primitives/src/unchecked_extrinsic.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/primitives/src/unchecked_extrinsic.rs b/primitives/src/unchecked_extrinsic.rs index f5a1c2bd1d..aa65e04e4b 100644 --- a/primitives/src/unchecked_extrinsic.rs +++ b/primitives/src/unchecked_extrinsic.rs @@ -342,6 +342,8 @@ fn verify_eip712_signature(eth_msg: EthereumTransactionMessage, sig: [u8; 65]) - #[cfg(test)] mod tests { use super::*; + use hex_literal::hex; + use module_evm_utiltity::ethereum::AccessListItem; use std::{ops::Add, str::FromStr}; #[test] @@ -358,7 +360,7 @@ mod tests { input: vec![], valid_until: 444, }; - let sign = hex_literal::hex!("acb56f12b407bd0bc8f7abefe2e2585affe28009abcb6980aa33aecb815c56b324ab60a41eff339a88631c4b0e5183427be1fcfde3c05fb9b6c71a691e977c4a1b"); + let sign = hex!("acb56f12b407bd0bc8f7abefe2e2585affe28009abcb6980aa33aecb815c56b324ab60a41eff339a88631c4b0e5183427be1fcfde3c05fb9b6c71a691e977c4a1b"); let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); assert_eq!(verify_eip712_signature(msg.clone(), sign), sender); @@ -416,7 +418,7 @@ mod tests { chain_id: Some(595), }; - let sign = hex_literal::hex!("f84345a6459785986a1b2df711fe02597d70c1393757a243f8f924ea541d2ecb51476de1aa437cd820d59e1d9836e37e643fec711fe419464e637cab592918751c"); + let sign = hex!("f84345a6459785986a1b2df711fe02597d70c1393757a243f8f924ea541d2ecb51476de1aa437cd820d59e1d9836e37e643fec711fe419464e637cab592918751c"); let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); @@ -464,11 +466,15 @@ mod tests { access_list: vec![], }; - let sign = hex_literal::hex!("f84345a6459785986a1b2df711fe02597d70c1393757a243f8f924ea541d2ecb51476de1aa437cd820d59e1d9836e37e643fec711fe419464e637cab592918751c"); + let sign = hex!("e88df53d4d66cb7a4f54ea44a44942b9b7f4fb4951525d416d3f7d24755a1f817734270872b103ac04c59d74f4dacdb8a6eff09a6638bd95dad1fa3eda921d891b"); let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); + let mut new_msg = msg.clone(); + new_msg.chain_id = new_msg.chain_id.add(1u64); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + let mut new_msg = msg.clone(); new_msg.nonce = new_msg.nonce.add(U256::one()); assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); @@ -497,8 +503,11 @@ mod tests { new_msg.input = vec![0x00]; assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - let mut new_msg = msg; - new_msg.chain_id = new_msg.chain_id.add(1u64); + let mut new_msg = msg.clone(); + new_msg.access_list = vec![AccessListItem { + address: hex!("bb9bc244d798123fde783fcc1c72d3bb8c189413").into(), + slots: vec![], + }]; assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); } } From 7d855b96ab725e57fcfb06aa902bdd67faba963a Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Fri, 17 Dec 2021 23:15:35 +0800 Subject: [PATCH 5/5] add tips --- ts-tests/tests/test-sign-eip1559.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ts-tests/tests/test-sign-eip1559.ts b/ts-tests/tests/test-sign-eip1559.ts index b8d1ce23e8..762ebb85dc 100644 --- a/ts-tests/tests/test-sign-eip1559.ts +++ b/ts-tests/tests/test-sign-eip1559.ts @@ -54,6 +54,8 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100 const storageLimit = 20000; const gasLimit = 2100000; + const priorityFee = BigNumber.from(2); + const tip = priorityFee * gasLimit; const block_period = bigNumDiv(BigNumber.from(validUntil), BigNumber.from(30)); const storage_entry_limit = bigNumDiv(BigNumber.from(storageLimit), BigNumber.from(64)); @@ -75,7 +77,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { data: deploy.data, value: 0, chainId: chain_id, - maxPriorityFeePerGas: 0, + maxPriorityFeePerGas: priorityFee.toHexString(), maxFeePerGas: tx_gas_price.toHexString(), } @@ -86,7 +88,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { type: 2, chainId: 595, nonce: 0, - maxPriorityFeePerGas: BigNumber.from(0), + maxPriorityFeePerGas: BigNumber.from(2), maxFeePerGas: BigNumber.from(200000209209), gasPrice: null, gasLimit: BigNumber.from(12116000), @@ -126,7 +128,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { method: "Bytes", // don't know that is this nonce: nonce, specVersion: 0, // ignored - tip: 0, + tip: tip, transactionVersion: 0, // ignored }); @@ -143,7 +145,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { "immortalEra": "0x00" }, "nonce": 0, - "tip": 0 + "tip": ${tip} }, "method": { "callIndex": "0xb400", @@ -190,6 +192,8 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100; const storageLimit = 1000; const gasLimit = 210000; + const priorityFee = BigNumber.from(2); + const tip = priorityFee * gasLimit; const block_period = bigNumDiv(BigNumber.from(validUntil), BigNumber.from(30)); const storage_entry_limit = bigNumDiv(BigNumber.from(storageLimit), BigNumber.from(64)); @@ -212,7 +216,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { data: input.data, value: 0, chainId: chain_id, - maxPriorityFeePerGas: 0, + maxPriorityFeePerGas: priorityFee.toHexString(), maxFeePerGas: tx_gas_price.toHexString(), } @@ -223,7 +227,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { type: 2, chainId: 595, nonce: 1, - maxPriorityFeePerGas: BigNumber.from(0), + maxPriorityFeePerGas: BigNumber.from(2), maxFeePerGas: BigNumber.from(200000208912), gasPrice: null, gasLimit: BigNumber.from(722000), @@ -263,7 +267,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { method: "Bytes", // don't know that is this nonce: nonce, specVersion: 0, // ignored - tip: 0, + tip: tip, transactionVersion: 0, // ignored }); @@ -280,7 +284,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { "immortalEra": "0x00" }, "nonce": 1, - "tip": 0 + "tip": ${tip} }, "method": { "callIndex": "0xb400",