Skip to content

Commit

Permalink
EIP-7702 Integration (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xFirekeeper authored Dec 25, 2024
1 parent b0631e7 commit 22ffb86
Show file tree
Hide file tree
Showing 15 changed files with 555 additions and 58 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.PHONY: run

run:
dotnet run --project Thirdweb.Console
16 changes: 16 additions & 0 deletions Thirdweb.Console/Program.Types.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Numerics;
using Nethereum.ABI.FunctionEncoding.Attributes;

namespace Thirdweb.Console;

public class Call
{
[Parameter("bytes", "data", 1)]
public required byte[] Data { get; set; }

[Parameter("address", "to", 2)]
public required string To { get; set; }

[Parameter("uint256", "value", 3)]
public required BigInteger Value { get; set; }
}
86 changes: 86 additions & 0 deletions Thirdweb.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,92 @@

#endregion

#region EIP-7702

// // Chain and contract addresses
// var chainWith7702 = 911867;
// var erc20ContractAddress = "0xAA462a5BE0fc5214507FDB4fB2474a7d5c69065b"; // Fake ERC20
// var delegationContractAddress = "0x654F42b74885EE6803F403f077bc0409f1066c58"; // BatchCallDelegation

// // Initialize contracts normally
// var erc20Contract = await ThirdwebContract.Create(client: client, address: erc20ContractAddress, chain: chainWith7702);
// var delegationContract = await ThirdwebContract.Create(client: client, address: delegationContractAddress, chain: chainWith7702);

// // Initialize a (to-be) 7702 EOA
// var eoaWallet = await PrivateKeyWallet.Generate(client);
// var eoaWalletAddress = await eoaWallet.GetAddress();
// Console.WriteLine($"EOA address: {eoaWalletAddress}");

// // Initialize another wallet, the "executor" that will hit the eoa's (to-be) execute function
// var executorWallet = await PrivateKeyWallet.Generate(client);
// var executorWalletAddress = await executorWallet.GetAddress();
// Console.WriteLine($"Executor address: {executorWalletAddress}");

// // Fund the executor wallet
// var fundingWallet = await PrivateKeyWallet.Create(client, privateKey);
// var fundingHash = (await fundingWallet.Transfer(chainWith7702, executorWalletAddress, BigInteger.Parse("0.001".ToWei()))).TransactionHash;
// Console.WriteLine($"Funded Executor Wallet: {fundingHash}");

// // Sign the authorization to make it point to the delegation contract
// var authorization = await eoaWallet.SignAuthorization(chainId: chainWith7702, contractAddress: delegationContractAddress, willSelfExecute: false);
// Console.WriteLine($"Authorization: {JsonConvert.SerializeObject(authorization, Formatting.Indented)}");

// // Execute the delegation
// var tx = await ThirdwebTransaction.Create(executorWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: executorWalletAddress, authorization: authorization));
// var hash = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx)).TransactionHash;
// Console.WriteLine($"Authorization execution transaction hash: {hash}");

// // Prove that code has been deployed to the eoa
// var rpc = ThirdwebRPC.GetRpcInstance(client, chainWith7702);
// var code = await rpc.SendRequestAsync<string>("eth_getCode", eoaWalletAddress, "latest");
// Console.WriteLine($"EOA code: {code}");

// // Log erc20 balance of executor before the claim
// var executorBalanceBefore = await erc20Contract.ERC20_BalanceOf(executorWalletAddress);
// Console.WriteLine($"Executor balance before: {executorBalanceBefore}");

// // Prepare the claim call
// var claimCallData = erc20Contract.CreateCallData(
// "claim",
// new object[]
// {
// executorWalletAddress, // receiver
// 100, // quantity
// Constants.NATIVE_TOKEN_ADDRESS, // currency
// 0, // pricePerToken
// new object[] { Array.Empty<byte>(), BigInteger.Zero, BigInteger.Zero, Constants.ADDRESS_ZERO }, // allowlistProof
// Array.Empty<byte>() // data
// }
// );

// // Embed the claim call in the execute call
// var executeCallData = delegationContract.CreateCallData(
// method: "execute",
// parameters: new object[]
// {
// new List<Thirdweb.Console.Call>
// {
// new()
// {
// Data = claimCallData.HexToBytes(),
// To = erc20ContractAddress,
// Value = BigInteger.Zero
// }
// }
// }
// );

// // Execute from the executor wallet targeting the eoa which is pointing to the delegation contract
// var tx2 = await ThirdwebTransaction.Create(executorWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, data: executeCallData));
// var hash2 = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx2)).TransactionHash;
// Console.WriteLine($"Token claim transaction hash: {hash2}");

// // Log erc20 balance of executor after the claim
// var executorBalanceAfter = await erc20Contract.ERC20_BalanceOf(executorWalletAddress);
// Console.WriteLine($"Executor balance after: {executorBalanceAfter}");

#endregion

#region Smart Ecosystem Wallet

// var eco = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.the-bonfire", authProvider: AuthProvider.Twitch);
Expand Down
8 changes: 8 additions & 0 deletions Thirdweb.Tests/Thirdweb.Contracts/Thirdweb.Contracts.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ public async Task ReadTest_Tuple()
Assert.Equal(0, result.ReturnValue2);
}

[Fact(Timeout = 120000)]
public async Task ReadTest_4Bytes()
{
var contract = await this.GetContract();
var result = await ThirdwebContract.Read<string>(contract, "0x06fdde03");
Assert.Equal("Kitty DropERC20", result);
}

[Fact(Timeout = 120000)]
public async Task ReadTest_FullSig()
{
Expand Down
28 changes: 28 additions & 0 deletions Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -866,4 +866,32 @@ public void PreprocessTypedDataJson_NestedLargeNumbers()

Assert.Equal(expectedJObject, processedJObject);
}

[Fact]
public void DecodeTransaction_1559WithAuthList()
{
var signedTxStr =
"0x04f8ca830de9fb8082011882031083025bee94ff5d95e5aa1b5af3f106079518228a92818737728080c0f85ef85c830de9fb94654f42b74885ee6803f403f077bc0409f1066c588080a0a5caed9b0c46657a452250a3279f45937940c87c45854aead6a902d99bc638f39faa58026c6b018d36b8935a42f2bcf68097c712c9f09ca014c70887678e08a980a027ecc69e66eb9e28cbe6edab10fc827fcb6d2a34cdcb89d8b6aabc6e35608692a0750d306b04a50a35de57bd6aca11f207a8dd404f9d92502ce6e3817e52f79a1c";
(var txInput, var signature) = Utils.DecodeTransaction(signedTxStr);
Assert.Equal("0xfF5D95e5aA1B5Af3F106079518228A9281873772", txInput.To);
Assert.Equal("0x", txInput.Data);
Assert.Equal(0, txInput.Value.Value);
Assert.NotNull(txInput.AuthorizationList);
_ = Assert.Single(txInput.AuthorizationList);
Assert.Equal("0x654F42b74885EE6803F403f077bc0409f1066c58", txInput.AuthorizationList[0].Address);
Assert.Equal("0xde9fb", txInput.AuthorizationList[0].ChainId);
Assert.Equal("0x0", txInput.AuthorizationList[0].Nonce);

(txInput, var signature2) = Utils.DecodeTransaction(signedTxStr.HexToBytes());
Assert.Equal("0xfF5D95e5aA1B5Af3F106079518228A9281873772", txInput.To);
Assert.Equal("0x", txInput.Data);
Assert.Equal(0, txInput.Value.Value);
Assert.NotNull(txInput.AuthorizationList);
_ = Assert.Single(txInput.AuthorizationList);
Assert.Equal("0x654F42b74885EE6803F403f077bc0409f1066c58", txInput.AuthorizationList[0].Address);
Assert.Equal("0xde9fb", txInput.AuthorizationList[0].ChainId);
Assert.Equal("0x0", txInput.AuthorizationList[0].Nonce);

Assert.Equal(signature, signature2);
}
}
72 changes: 53 additions & 19 deletions Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,16 +209,36 @@ public async Task SignTypedDataV4_Typed_NullData()
public async Task SignTransaction_Success()
{
var account = await this.GetAccount();
var transaction = new ThirdwebTransactionInput(421614)
{
From = await account.GetAddress(),
To = Constants.ADDRESS_ZERO,
// Value = new HexBigInteger(0),
Gas = new HexBigInteger(21000),
// Data = "0x",
Nonce = new HexBigInteger(99999999999),
GasPrice = new HexBigInteger(10000000000),
};
var transaction = new ThirdwebTransactionInput(
chainId: 421614,
from: await account.GetAddress(),
to: Constants.ADDRESS_ZERO,
value: 0,
gas: 21000,
data: "0x",
nonce: 99999999999,
gasPrice: 10000000000
);
var signature = await account.SignTransaction(transaction);
Assert.NotNull(signature);
}

[Fact(Timeout = 120000)]
public async Task SignTransaction_WithAuthorizationList_Success()
{
var account = await this.GetAccount();
var authorization = await account.SignAuthorization(421614, Constants.ADDRESS_ZERO, false);
var transaction = new ThirdwebTransactionInput(
chainId: 421614,
from: await account.GetAddress(),
to: Constants.ADDRESS_ZERO,
value: 0,
gas: 21000,
data: "0x",
nonce: 99999999999,
gasPrice: 10000000000,
authorization: authorization
);
var signature = await account.SignTransaction(transaction);
Assert.NotNull(signature);
}
Expand All @@ -227,15 +247,7 @@ public async Task SignTransaction_Success()
public async Task SignTransaction_NoFrom_Success()
{
var account = await this.GetAccount();
var transaction = new ThirdwebTransactionInput(421614)
{
To = Constants.ADDRESS_ZERO,
// Value = new HexBigInteger(0),
Gas = new HexBigInteger(21000),
Data = "0x",
Nonce = new HexBigInteger(99999999999),
GasPrice = new HexBigInteger(10000000000),
};
var transaction = new ThirdwebTransactionInput(chainId: 421614, to: Constants.ADDRESS_ZERO, value: 0, gas: 21000, data: "0x", nonce: 99999999999, gasPrice: 10000000000);
var signature = await account.SignTransaction(transaction);
Assert.NotNull(signature);
}
Expand Down Expand Up @@ -469,4 +481,26 @@ public async Task Export_ReturnsPrivateKey()
Assert.NotNull(privateKey);
Assert.Equal(privateKey, await wallet.Export());
}

[Fact(Timeout = 120000)]
public async Task SignAuthorization_SelfExecution()
{
var wallet = await PrivateKeyWallet.Generate(this.Client);
var chainId = 911867;
var targetAddress = "0x654F42b74885EE6803F403f077bc0409f1066c58";

var currentNonce = await wallet.GetTransactionCount(chainId);

var authorization = await wallet.SignAuthorization(chainId: chainId, contractAddress: targetAddress, willSelfExecute: false);

Assert.Equal(chainId.NumberToHex(), authorization.ChainId);
Assert.Equal(targetAddress, authorization.Address);
Assert.True(authorization.Nonce.HexToNumber() == currentNonce);

authorization = await wallet.SignAuthorization(chainId: chainId, contractAddress: targetAddress, willSelfExecute: true);

Assert.Equal(chainId.NumberToHex(), authorization.ChainId);
Assert.Equal(targetAddress, authorization.Address);
Assert.True(authorization.Nonce.HexToNumber() == currentNonce + 1);
}
}
12 changes: 12 additions & 0 deletions Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,18 @@ public async Task SwitchNetwork_NonZkToZk_Success()
Assert.NotEqual(addy1, addy2);
}

[Fact(Timeout = 120000)]
public async Task SignAuthorization_WithPrivateKeyWallet_Success()
{
var smartWallet = await SmartWallet.Create(personalWallet: await PrivateKeyWallet.Generate(this.Client), chainId: 421614);
var smartWalletSigner = await smartWallet.GetPersonalWallet();
var signature1 = await smartWallet.SignAuthorization(chainId: 421614, contractAddress: Constants.ADDRESS_ZERO, willSelfExecute: true);
var signature2 = await smartWalletSigner.SignAuthorization(chainId: 421614, contractAddress: Constants.ADDRESS_ZERO, willSelfExecute: true);
Assert.Equal(signature1.ChainId, signature2.ChainId);
Assert.Equal(signature1.Address, signature2.Address);
Assert.Equal(signature1.Nonce, signature2.Nonce);
}

// [Fact(Timeout = 120000)]
// public async Task MultiChainTransaction_Success()
// {
Expand Down
30 changes: 16 additions & 14 deletions Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,6 @@ internal static (string callData, Function function) EncodeFunctionCall(Thirdweb
{
var contractRaw = new Contract(null, contract.Abi, contract.Address);
var function = GetFunctionMatchSignature(contractRaw, method, parameters);
if (function == null)
{
if (method.Contains('('))
{
var canonicalSignature = ExtractCanonicalSignature(method);
var selector = Nethereum.Util.Sha3Keccack.Current.CalculateHash(canonicalSignature)[..8];
function = contractRaw.GetFunctionBySignature(selector);
}
else
{
throw new ArgumentException("Method signature not found in contract ABI.");
}
}
return (function.GetData(parameters), function);
}

Expand All @@ -207,6 +194,11 @@ internal static (string callData, Function function) EncodeFunctionCall(Thirdweb
/// <returns>The matching function, or null if no match is found.</returns>
private static Function GetFunctionMatchSignature(Contract contract, string functionName, params object[] args)
{
if (functionName.StartsWith("0x"))
{
return contract.GetFunctionBySignature(functionName);
}

var abi = contract.ContractBuilder.ContractABI;
var functions = abi.Functions;
var paramsCount = args?.Length ?? 0;
Expand All @@ -218,7 +210,17 @@ private static Function GetFunctionMatchSignature(Contract contract, string func
return contract.GetFunctionBySignature(sha);
}
}
return null;

if (functionName.Contains('('))
{
var canonicalSignature = ExtractCanonicalSignature(functionName);
var selector = Utils.HashMessage(canonicalSignature)[..8];
return contract.GetFunctionBySignature(selector);
}
else
{
throw new ArgumentException("Method signature not found in contract ABI.");
}
}

/// <summary>
Expand Down
16 changes: 12 additions & 4 deletions Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,17 +261,24 @@ public static async Task<string> Simulate(ThirdwebTransaction transaction)
public static async Task<BigInteger> EstimateGasLimit(ThirdwebTransaction transaction)
{
var rpc = ThirdwebRPC.GetRpcInstance(transaction._wallet.Client, transaction.Input.ChainId.Value);

if (await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false))
var isZkSync = await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false);
BigInteger divider = isZkSync
? 7
: transaction.Input.AuthorizationList == null
? 5
: 3;
BigInteger baseGas;
if (isZkSync)
{
var hex = (await rpc.SendRequestAsync<JToken>("zks_estimateFee", transaction.Input).ConfigureAwait(false))["gas_limit"].ToString();
return new HexBigInteger(hex).Value * 10 / 5;
baseGas = hex.HexToNumber();
}
else
{
var hex = await rpc.SendRequestAsync<string>("eth_estimateGas", transaction.Input).ConfigureAwait(false);
return new HexBigInteger(hex).Value * 10 / 7;
baseGas = hex.HexToNumber();
}
return baseGas * 10 / divider;
}

/// <summary>
Expand Down Expand Up @@ -357,6 +364,7 @@ public static async Task<string> Send(ThirdwebTransaction transaction)

var rpc = ThirdwebRPC.GetRpcInstance(transaction._wallet.Client, transaction.Input.ChainId.Value);
string hash;

if (await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false) && transaction.Input.ZkSync.HasValue)
{
var zkTx = await ConvertToZkSyncTransaction(transaction).ConfigureAwait(false);
Expand Down
Loading

0 comments on commit 22ffb86

Please sign in to comment.