Skip to content

Commit 17a9e63

Browse files
feat(eip7702): Add SetCode transaction type (#4336)
* fix(eip7702): Add `SetCode` transaction type * fix(eip7702): Add `Biz.executeBatch` function call * Add `AuthorizationSigner` * fix(eip7702): Fix Authorization list RLP encoding * fix(eip7702): Add `Biz.execute` and `Biz.executeBatch` tests * fix(eip7702): Add android test * [CI] Trigger CI
1 parent fb2f5fa commit 17a9e63

File tree

14 files changed

+831
-105
lines changed

14 files changed

+831
-105
lines changed

android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt

+57
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,63 @@ class TestBarz {
227227
assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}")
228228
}
229229

230+
// https://bscscan.com/tx/0x425eb17a8e1dee2fcee8352a772d83cbb069c2e03f2c5d9d00da3b3ef66ce48b
231+
@Test
232+
fun testSignEip7702EoaBatched() {
233+
val transferFunc1 = EthereumAbiFunction("transfer")
234+
transferFunc1.addParamAddress("0x2EF648D7C03412B832726fd4683E2625deA047Ba".toHexByteArray(), false)
235+
// 100_000_000_000_000
236+
transferFunc1.addParamUInt256("0x5af3107a4000".toHexByteArray(), false)
237+
val transferPayload1 = EthereumAbi.encode(transferFunc1)
238+
239+
val transferFunc2 = EthereumAbiFunction("transfer")
240+
transferFunc2.addParamAddress("0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".toHexByteArray(), false)
241+
// 500_000_000_000_000
242+
transferFunc2.addParamUInt256("0x1c6bf52634000".toHexByteArray(), false)
243+
val transferPayload2 = EthereumAbi.encode(transferFunc2)
244+
245+
val signingInput = Ethereum.SigningInput.newBuilder()
246+
signingInput.apply {
247+
privateKey = ByteString.copyFrom(PrivateKey("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7".toHexByteArray()).data())
248+
chainId = ByteString.copyFrom("0x38".toHexByteArray())
249+
nonce = ByteString.copyFrom("0x12".toHexByteArray())
250+
txMode = TransactionMode.SetCode
251+
252+
gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray())
253+
maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray())
254+
maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray())
255+
256+
transaction = Ethereum.Transaction.newBuilder().apply {
257+
batch = Ethereum.Transaction.Batch.newBuilder().apply {
258+
addAllCalls(listOf(
259+
Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply {
260+
// TWT
261+
address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003"
262+
amount = ByteString.copyFrom("0x00".toHexByteArray())
263+
payload = ByteString.copyFrom(transferPayload1)
264+
}.build(),
265+
Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply {
266+
// TWT
267+
address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003"
268+
amount = ByteString.copyFrom("0x00".toHexByteArray())
269+
payload = ByteString.copyFrom(transferPayload2)
270+
}.build()
271+
))
272+
}.build()
273+
}.build()
274+
275+
userOperationMode = Ethereum.SCAccountType.Biz
276+
eip7702Authority = Ethereum.Authority.newBuilder().apply {
277+
address = "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7"
278+
}.build()
279+
}
280+
281+
val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser())
282+
283+
assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x00b2d13719df301927ddcbdad5b6bc6214f2007c6408df883c9ea483b45e6f44")
284+
assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28")
285+
}
286+
230287
@Test
231288
fun testAuthorizationHash() {
232289
val chainId = "0x01".toHexByteArray()

rust/tw_evm/src/abi/prebuild/biz.rs

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
//
3+
// Copyright © 2017 Trust Wallet.
4+
5+
use crate::abi::contract::Contract;
6+
use crate::abi::function::Function;
7+
use crate::abi::param_token::NamedToken;
8+
use crate::abi::param_type::ParamType;
9+
use crate::abi::prebuild::ExecuteArgs;
10+
use crate::abi::token::Token;
11+
use crate::abi::{AbiError, AbiErrorKind, AbiResult};
12+
use lazy_static::lazy_static;
13+
use tw_coin_entry::error::prelude::*;
14+
use tw_memory::Data;
15+
16+
const ERC4337_BIZ_ACCOUNT_ABI: &str = include_str!("resource/erc4337.biz_account.abi.json");
17+
18+
lazy_static! {
19+
static ref ERC4337_BIZ_ACCOUNT: Contract =
20+
serde_json::from_str(ERC4337_BIZ_ACCOUNT_ABI).unwrap();
21+
}
22+
23+
pub struct BizAccount;
24+
25+
impl BizAccount {
26+
pub fn encode_execute(args: ExecuteArgs) -> AbiResult<Data> {
27+
let func = ERC4337_BIZ_ACCOUNT.function("execute")?;
28+
func.encode_input(&[
29+
Token::Address(args.to),
30+
Token::u256(args.value),
31+
Token::Bytes(args.data),
32+
])
33+
}
34+
35+
pub fn encode_execute_4337_op(args: ExecuteArgs) -> AbiResult<Data> {
36+
let func = ERC4337_BIZ_ACCOUNT.function("execute4337Op")?;
37+
func.encode_input(&[
38+
Token::Address(args.to),
39+
Token::u256(args.value),
40+
Token::Bytes(args.data),
41+
])
42+
}
43+
44+
pub fn encode_execute_batch<I>(args: I) -> AbiResult<Data>
45+
where
46+
I: IntoIterator<Item = ExecuteArgs>,
47+
{
48+
let func = ERC4337_BIZ_ACCOUNT.function("executeBatch")?;
49+
encode_batch(func, args)
50+
}
51+
52+
pub fn encode_execute_4337_ops<I>(args: I) -> AbiResult<Data>
53+
where
54+
I: IntoIterator<Item = ExecuteArgs>,
55+
{
56+
let func = ERC4337_BIZ_ACCOUNT.function("execute4337Ops")?;
57+
encode_batch(func, args)
58+
}
59+
}
60+
61+
fn encode_batch<I>(function: &Function, args: I) -> AbiResult<Data>
62+
where
63+
I: IntoIterator<Item = ExecuteArgs>,
64+
{
65+
// `tuple[]`, where each item is a tuple of (address, uint256, bytes).
66+
let array_param = function
67+
.inputs
68+
.first()
69+
.or_tw_err(AbiErrorKind::Error_internal)
70+
.context("'Biz.execute4337Ops()' should contain only one argument")?;
71+
72+
let ParamType::Array {
73+
kind: array_elem_type,
74+
} = array_param.kind.clone()
75+
else {
76+
return AbiError::err(AbiErrorKind::Error_internal).with_context(|| {
77+
format!(
78+
"'Biz.execute4337Ops()' input argument should be an array, found: {:?}",
79+
array_param.kind
80+
)
81+
});
82+
};
83+
84+
let ParamType::Tuple {
85+
params: tuple_params,
86+
} = array_elem_type.as_ref()
87+
else {
88+
return AbiError::err(AbiErrorKind::Error_internal).with_context(|| {
89+
format!(
90+
"'Biz.execute4337Ops()' input argument should be an array of tuples, found: {array_elem_type:?}",
91+
)
92+
});
93+
};
94+
95+
if tuple_params.len() != 3 {
96+
return AbiError::err(AbiErrorKind::Error_internal).with_context(|| {
97+
format!(
98+
"'Biz.execute4337Ops()' input argument should be an array of tuples with 3 elements, found: {}", tuple_params.len()
99+
)
100+
});
101+
}
102+
103+
let array_tokens = args
104+
.into_iter()
105+
.map(|call| Token::Tuple {
106+
params: vec![
107+
NamedToken::with_param_and_token(&tuple_params[0], Token::Address(call.to)),
108+
NamedToken::with_param_and_token(&tuple_params[1], Token::u256(call.value)),
109+
NamedToken::with_param_and_token(&tuple_params[2], Token::Bytes(call.data)),
110+
],
111+
})
112+
.collect();
113+
114+
function.encode_input(&[Token::array(*array_elem_type, array_tokens)])
115+
}

rust/tw_evm/src/abi/prebuild/erc4337.rs

+2-81
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,20 @@
33
// Copyright © 2017 Trust Wallet.
44

55
use crate::abi::contract::Contract;
6-
use crate::abi::param_token::NamedToken;
76
use crate::abi::param_type::ParamType;
7+
use crate::abi::prebuild::ExecuteArgs;
88
use crate::abi::token::Token;
9-
use crate::abi::{AbiError, AbiErrorKind, AbiResult};
10-
use crate::address::Address;
9+
use crate::abi::AbiResult;
1110
use lazy_static::lazy_static;
12-
use tw_coin_entry::error::prelude::{OrTWError, ResultContext};
1311
use tw_memory::Data;
14-
use tw_number::U256;
1512

1613
/// Generated via https://remix.ethereum.org
1714
/// https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol
1815
const ERC4337_SIMPLE_ACCOUNT_ABI: &str = include_str!("resource/erc4337.simple_account.abi.json");
19-
const ERC4337_BIZ_ACCOUNT_ABI: &str = include_str!("resource/erc4337.biz_account.abi.json");
2016

2117
lazy_static! {
2218
static ref ERC4337_SIMPLE_ACCOUNT: Contract =
2319
serde_json::from_str(ERC4337_SIMPLE_ACCOUNT_ABI).unwrap();
24-
static ref ERC4337_BIZ_ACCOUNT: Contract =
25-
serde_json::from_str(ERC4337_BIZ_ACCOUNT_ABI).unwrap();
26-
}
27-
28-
pub struct ExecuteArgs {
29-
pub to: Address,
30-
pub value: U256,
31-
pub data: Data,
3220
}
3321

3422
pub struct Erc4337SimpleAccount;
@@ -43,15 +31,6 @@ impl Erc4337SimpleAccount {
4331
])
4432
}
4533

46-
pub fn encode_execute_4337_op(args: ExecuteArgs) -> AbiResult<Data> {
47-
let func = ERC4337_BIZ_ACCOUNT.function("execute4337Op")?;
48-
func.encode_input(&[
49-
Token::Address(args.to),
50-
Token::u256(args.value),
51-
Token::Bytes(args.data),
52-
])
53-
}
54-
5534
pub fn encode_execute_batch<I>(args: I) -> AbiResult<Data>
5635
where
5736
I: IntoIterator<Item = ExecuteArgs>,
@@ -80,62 +59,4 @@ impl Erc4337SimpleAccount {
8059
Token::array(ParamType::Bytes, datas),
8160
])
8261
}
83-
84-
pub fn encode_execute_4337_ops<I>(args: I) -> AbiResult<Data>
85-
where
86-
I: IntoIterator<Item = ExecuteArgs>,
87-
{
88-
let func = ERC4337_BIZ_ACCOUNT.function("execute4337Ops")?;
89-
90-
// `tuple[]`, where each item is a tuple of (address, uint256, bytes).
91-
let array_param = func
92-
.inputs
93-
.first()
94-
.or_tw_err(AbiErrorKind::Error_internal)
95-
.context("'Biz.execute4337Ops()' should contain only one argument")?;
96-
97-
let ParamType::Array {
98-
kind: array_elem_type,
99-
} = array_param.kind.clone()
100-
else {
101-
return AbiError::err(AbiErrorKind::Error_internal).with_context(|| {
102-
format!(
103-
"'Biz.execute4337Ops()' input argument should be an array, found: {:?}",
104-
array_param.kind
105-
)
106-
});
107-
};
108-
109-
let ParamType::Tuple {
110-
params: tuple_params,
111-
} = array_elem_type.as_ref()
112-
else {
113-
return AbiError::err(AbiErrorKind::Error_internal).with_context(|| {
114-
format!(
115-
"'Biz.execute4337Ops()' input argument should be an array of tuples, found: {array_elem_type:?}",
116-
)
117-
});
118-
};
119-
120-
if tuple_params.len() != 3 {
121-
return AbiError::err(AbiErrorKind::Error_internal).with_context(|| {
122-
format!(
123-
"'Biz.execute4337Ops()' input argument should be an array of tuples with 3 elements, found: {}", tuple_params.len()
124-
)
125-
});
126-
}
127-
128-
let array_tokens = args
129-
.into_iter()
130-
.map(|call| Token::Tuple {
131-
params: vec![
132-
NamedToken::with_param_and_token(&tuple_params[0], Token::Address(call.to)),
133-
NamedToken::with_param_and_token(&tuple_params[1], Token::u256(call.value)),
134-
NamedToken::with_param_and_token(&tuple_params[2], Token::Bytes(call.data)),
135-
],
136-
})
137-
.collect();
138-
139-
func.encode_input(&[Token::array(*array_elem_type, array_tokens)])
140-
}
14162
}

rust/tw_evm/src/abi/prebuild/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,18 @@
22
//
33
// Copyright © 2017 Trust Wallet.
44

5+
use crate::address::Address;
6+
use tw_memory::Data;
7+
use tw_number::U256;
8+
9+
pub mod biz;
510
pub mod erc1155;
611
pub mod erc20;
712
pub mod erc4337;
813
pub mod erc721;
14+
15+
pub struct ExecuteArgs {
16+
pub to: Address,
17+
pub value: U256,
18+
pub data: Data,
19+
}

0 commit comments

Comments
 (0)