Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Barz] Execute batch #3257

Merged
merged 16 commits into from
Jun 26, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class TestBarz {
val publicKey = PublicKey(publicKeyData, PublicKeyType.NIST256P1EXTENDED)
val verificationFacet = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf"
val result = WCBarz.getInitCode(factoryAddress, publicKey, verificationFacet)
assertEquals(Numeric.toHexString(result), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02")
assertEquals(Numeric.toHexString(result), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000")
}

// https://testnet.bscscan.com/address/0x5d51930e0ce5cc08a67a1763fadb66892c0994d1
Expand Down Expand Up @@ -142,8 +142,63 @@ class TestBarz {

val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser())

assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0xb0920a70eee3309563cf9a83ad170cca7905e1170720c55c52c566b41f8daf83");
assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x548c13a0bb87981d04a3a24a78ad5e4ba8d0afbf3cfe9311250e07b54cd38937");

assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc510000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392ae041bfbdbaa0cff9234a0c8f64df97b7218\",\"signature\":\"0xfcaecd5a66ef2d36a54437ce94185ef21a056dae4af66158e71cac84329050e570def91812915e059b08640cec070818aaef19fc8278e045d7b3e1f460869e581b\",\"verificationGasLimit\":\"3000000\"}");
assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392ae041bfbdbaa0cff9234a0c8f64df97b7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}")
}

// https://testnet.bscscan.com/tx/0x872f709815a9f79623a349f2f16d93b52c4d5136967bab53a586f045edbe9203
@Test
fun testSignR1BatchedTransferAccountDeployed() {
val approveFunc = EthereumAbiFunction("approve")
approveFunc.addParamAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".toHexByteArray(), false)
approveFunc.addParamUInt256("0x8AC7230489E80000".toHexByteArray(), false)
val approveCall = EthereumAbi.encode(approveFunc)

val transferFunc = EthereumAbiFunction("transfer")
transferFunc.addParamAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".toHexByteArray(), false)
transferFunc.addParamUInt256("0x8AC7230489E80000".toHexByteArray(), false)
val transferCall = EthereumAbi.encode(transferFunc)

val signingInput = Ethereum.SigningInput.newBuilder()
signingInput.apply {
privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data())
chainId = ByteString.copyFrom("0x61".toHexByteArray())
nonce = ByteString.copyFrom("0x03".toHexByteArray())
txMode = TransactionMode.UserOp

gasLimit = ByteString.copyFrom("0x015A61".toHexByteArray())
maxFeePerGas = ByteString.copyFrom("0x02540BE400".toHexByteArray())
maxInclusionFeePerGas = ByteString.copyFrom("0x02540BE400".toHexByteArray())

userOperation = Ethereum.UserOperation.newBuilder().apply {
entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
sender = "0x1e6c542ebc7c960c6a155a9094db838cef842cf5"
preVerificationGas = ByteString.copyFrom("0xDAFC".toHexByteArray())
verificationGasLimit = ByteString.copyFrom("0x07F7C4".toHexByteArray())
}.build()

transaction = Ethereum.Transaction.newBuilder().apply {
batch = Ethereum.Transaction.Batch.newBuilder().apply {
addAllCalls(listOf(
Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply {
address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266"
amount = ByteString.copyFrom("0x00".toHexByteArray())
payload = ByteString.copyFrom(approveCall)
}.build(),
Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply {
address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266"
amount = ByteString.copyFrom("0x00".toHexByteArray())
payload = ByteString.copyFrom(transferCall)
}.build()
))
}.build()
}.build()
}

val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser())

assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab");
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\"}")
}
}
3 changes: 1 addition & 2 deletions src/Ethereum/Barz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ std::string getCounterfactualAddress(const Proto::ContractAddressInput input) {
}

Data getInitCode(const std::string& factoryAddress, const PublicKey& publicKey, const std::string& verificationFacet) {
const auto rawPublicKey = subData(publicKey.bytes, 1);
auto createAccountFunc = Ethereum::ABI::Function("createAccount", ParamCollection{
std::make_shared<Ethereum::ABI::ParamAddress>(parse_hex(verificationFacet)),
std::make_shared<Ethereum::ABI::ParamByteArray>(rawPublicKey),
std::make_shared<Ethereum::ABI::ParamByteArray>(publicKey.bytes),
std::make_shared<Ethereum::ABI::ParamUInt256>(0)});
Data createAccountFuncEncoded;
createAccountFunc.encode(createAccountFuncEncoded);
Expand Down
23 changes: 23 additions & 0 deletions src/Ethereum/ERC4337.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,28 @@ Data getERC4337ExecuteBytecode(const Data& toAddress, const uint256_t& value, co
return executeFuncEncoded;
}

// https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol#L65
Data getERC4337ExecuteBatchBytecode(const std::vector<Data>& toAddresses, const std::vector<uint256_t>& amounts, const std::vector<Data>& payloads) {
auto addressesParam = ABI::ParamArray();
for (const auto& toAddress: toAddresses) {
addressesParam.addParam(std::make_shared<ABI::ParamAddress>(toAddress));
}
auto valuesParam = ABI::ParamArray();
for (const auto& amount: amounts) {
valuesParam.addParam(std::make_shared<ABI::ParamUInt256>(amount));
}
auto payloadsParam = ABI::ParamArray();
for (const auto& payload: payloads) {
payloadsParam.addParam(std::make_shared<ABI::ParamByteArray>(payload));
}
auto executeFunc = ABI::Function("executeBatch", ParamCollection{
std::make_shared<ABI::ParamArray>(addressesParam),
std::make_shared<ABI::ParamArray>(valuesParam),
std::make_shared<ABI::ParamArray>(payloadsParam)});
Data executeFuncEncoded;
executeFunc.encode(executeFuncEncoded);
return executeFuncEncoded;
}


} // namespace TW::Ethereum
1 change: 1 addition & 0 deletions src/Ethereum/ERC4337.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
namespace TW::Ethereum {

Data getERC4337ExecuteBytecode(const Data& toAddress, const uint256_t& value, const Data& data);
Data getERC4337ExecuteBatchBytecode(const std::vector<Data>& toAddresses, const std::vector<uint256_t>& values, const std::vector<Data>& payloads);

}
35 changes: 35 additions & 0 deletions src/Ethereum/Signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,41 @@ std::shared_ptr<TransactionBase> Signer::build(const Proto::SigningInput& input)
}
}

case Proto::Transaction::kBatch: {
switch (input.tx_mode()) {
case Proto::TransactionMode::Legacy:
default:
return nullptr;
case Proto::TransactionMode::Enveloped: // Eip1559
return nullptr;
case Proto::TransactionMode::UserOp:
std::vector<Data> addresses;
std::vector<uint256_t> amounts;
std::vector<Data> payloads;
for (int i=0; i < input.transaction().batch().calls().size(); i++) {
addresses.push_back(addressStringToData(input.transaction().batch().calls()[i].address()));
amounts.push_back(load(input.transaction().batch().calls()[i].amount()));
payloads.push_back(Data(input.transaction().batch().calls()[i].payload().begin(), input.transaction().batch().calls()[i].payload().end()));
}

return UserOperation::buildBatch(
entryPointAddress,
senderAddress,
addresses,
amounts,
nonce,
gasLimit,
verificationGasLimit,
maxFeePerGas,
maxInclusionFeePerGas,
preVerificationGas,
paymasterAndData,
initCode,
payloads
);
}
}

case Proto::Transaction::kContractGeneric:
default: {
switch (input.tx_mode()) {
Expand Down
19 changes: 19 additions & 0 deletions src/Ethereum/Transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,25 @@ UserOperation::buildNativeTransfer(const Data& entryPointAddress, const Data& se
paymasterAndData);
}

UserOperationPtr
UserOperation::buildBatch(const Data& entryPointAddress, const Data& senderAddress,
const std::vector<Data>& toAddresses, const std::vector<uint256_t>& amounts, const uint256_t& nonce,
const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas,
const Data paymasterAndData, const Data& initCode, const std::vector<Data>& payloads) {
return std::make_shared<UserOperation>(
entryPointAddress,
senderAddress,
nonce,
initCode,
gasLimit,
verificationGasLimit,
maxFeePerGas,
maxInclusionFeePerGas,
preVerificationGas,
Ethereum::getERC4337ExecuteBatchBytecode(toAddresses, amounts, payloads),
paymasterAndData);
}

UserOperationPtr
UserOperation::buildERC20Transfer(const Data& entryPointAddress, const Data& senderAddress,
const Data& tokenContract, const Data& toAddress, const uint256_t& amount, const uint256_t& nonce,
Expand Down
7 changes: 7 additions & 0 deletions src/Ethereum/Transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ class UserOperation: public TransactionTyped {
const Data& toAddress, const uint256_t& amount, const uint256_t& nonce,
const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas,
const Data& paymasterAndData = {}, const Data& initCode = {}, const Data& payload = {});

// Create a batched transaction for ERC-4337 wallets
static UserOperationPtr buildBatch(const Data& entryPointAddress, const Data& senderAddress,
const std::vector<Data>& toAddresses, const std::vector<uint256_t>& amounts, const uint256_t& nonce,
const uint256_t& gasLimit, const uint256_t& verificationGasLimit, const uint256_t& maxFeePerGas, const uint256_t& maxInclusionFeePerGas, const uint256_t& preVerificationGas,
const Data paymasterAndData = {}, const Data& initCode = {}, const std::vector<Data>& payloads = {});

// Create an ERC20 token transfer transaction
static UserOperationPtr buildERC20Transfer(const Data& entryPointAddress, const Data& senderAddress,
const Data& tokenContract, const Data& toAddress, const uint256_t& amount, const uint256_t& nonce,
Expand Down
17 changes: 17 additions & 0 deletions src/proto/Ethereum.proto
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ message Transaction {
bytes data = 2;
}

// Batched transaction for ERC-4337 wallets
message Batch {
message BatchedCall {
// Recipient addresses.
string address = 1;

// Amounts to send in wei (uint256, serialized little endian)
bytes amount = 2;

// Contract call payloads data
bytes payload = 3;
}

repeated BatchedCall calls = 1;
}

// Payload transfer
oneof transaction_oneof {
Transfer transfer = 1;
Expand All @@ -80,6 +96,7 @@ message Transaction {
ERC721Transfer erc721_transfer = 4;
ERC1155Transfer erc1155_transfer = 5;
ContractGeneric contract_generic = 6;
Batch batch = 7;
}
}

Expand Down
Loading