Skip to content

Commit 22ffb86

Browse files
authored
EIP-7702 Integration (#108)
1 parent b0631e7 commit 22ffb86

File tree

15 files changed

+555
-58
lines changed

15 files changed

+555
-58
lines changed

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.PHONY: run
2+
3+
run:
4+
dotnet run --project Thirdweb.Console

Thirdweb.Console/Program.Types.cs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Numerics;
2+
using Nethereum.ABI.FunctionEncoding.Attributes;
3+
4+
namespace Thirdweb.Console;
5+
6+
public class Call
7+
{
8+
[Parameter("bytes", "data", 1)]
9+
public required byte[] Data { get; set; }
10+
11+
[Parameter("address", "to", 2)]
12+
public required string To { get; set; }
13+
14+
[Parameter("uint256", "value", 3)]
15+
public required BigInteger Value { get; set; }
16+
}

Thirdweb.Console/Program.cs

+86
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,92 @@
9191

9292
#endregion
9393

94+
#region EIP-7702
95+
96+
// // Chain and contract addresses
97+
// var chainWith7702 = 911867;
98+
// var erc20ContractAddress = "0xAA462a5BE0fc5214507FDB4fB2474a7d5c69065b"; // Fake ERC20
99+
// var delegationContractAddress = "0x654F42b74885EE6803F403f077bc0409f1066c58"; // BatchCallDelegation
100+
101+
// // Initialize contracts normally
102+
// var erc20Contract = await ThirdwebContract.Create(client: client, address: erc20ContractAddress, chain: chainWith7702);
103+
// var delegationContract = await ThirdwebContract.Create(client: client, address: delegationContractAddress, chain: chainWith7702);
104+
105+
// // Initialize a (to-be) 7702 EOA
106+
// var eoaWallet = await PrivateKeyWallet.Generate(client);
107+
// var eoaWalletAddress = await eoaWallet.GetAddress();
108+
// Console.WriteLine($"EOA address: {eoaWalletAddress}");
109+
110+
// // Initialize another wallet, the "executor" that will hit the eoa's (to-be) execute function
111+
// var executorWallet = await PrivateKeyWallet.Generate(client);
112+
// var executorWalletAddress = await executorWallet.GetAddress();
113+
// Console.WriteLine($"Executor address: {executorWalletAddress}");
114+
115+
// // Fund the executor wallet
116+
// var fundingWallet = await PrivateKeyWallet.Create(client, privateKey);
117+
// var fundingHash = (await fundingWallet.Transfer(chainWith7702, executorWalletAddress, BigInteger.Parse("0.001".ToWei()))).TransactionHash;
118+
// Console.WriteLine($"Funded Executor Wallet: {fundingHash}");
119+
120+
// // Sign the authorization to make it point to the delegation contract
121+
// var authorization = await eoaWallet.SignAuthorization(chainId: chainWith7702, contractAddress: delegationContractAddress, willSelfExecute: false);
122+
// Console.WriteLine($"Authorization: {JsonConvert.SerializeObject(authorization, Formatting.Indented)}");
123+
124+
// // Execute the delegation
125+
// var tx = await ThirdwebTransaction.Create(executorWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: executorWalletAddress, authorization: authorization));
126+
// var hash = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx)).TransactionHash;
127+
// Console.WriteLine($"Authorization execution transaction hash: {hash}");
128+
129+
// // Prove that code has been deployed to the eoa
130+
// var rpc = ThirdwebRPC.GetRpcInstance(client, chainWith7702);
131+
// var code = await rpc.SendRequestAsync<string>("eth_getCode", eoaWalletAddress, "latest");
132+
// Console.WriteLine($"EOA code: {code}");
133+
134+
// // Log erc20 balance of executor before the claim
135+
// var executorBalanceBefore = await erc20Contract.ERC20_BalanceOf(executorWalletAddress);
136+
// Console.WriteLine($"Executor balance before: {executorBalanceBefore}");
137+
138+
// // Prepare the claim call
139+
// var claimCallData = erc20Contract.CreateCallData(
140+
// "claim",
141+
// new object[]
142+
// {
143+
// executorWalletAddress, // receiver
144+
// 100, // quantity
145+
// Constants.NATIVE_TOKEN_ADDRESS, // currency
146+
// 0, // pricePerToken
147+
// new object[] { Array.Empty<byte>(), BigInteger.Zero, BigInteger.Zero, Constants.ADDRESS_ZERO }, // allowlistProof
148+
// Array.Empty<byte>() // data
149+
// }
150+
// );
151+
152+
// // Embed the claim call in the execute call
153+
// var executeCallData = delegationContract.CreateCallData(
154+
// method: "execute",
155+
// parameters: new object[]
156+
// {
157+
// new List<Thirdweb.Console.Call>
158+
// {
159+
// new()
160+
// {
161+
// Data = claimCallData.HexToBytes(),
162+
// To = erc20ContractAddress,
163+
// Value = BigInteger.Zero
164+
// }
165+
// }
166+
// }
167+
// );
168+
169+
// // Execute from the executor wallet targeting the eoa which is pointing to the delegation contract
170+
// var tx2 = await ThirdwebTransaction.Create(executorWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, data: executeCallData));
171+
// var hash2 = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx2)).TransactionHash;
172+
// Console.WriteLine($"Token claim transaction hash: {hash2}");
173+
174+
// // Log erc20 balance of executor after the claim
175+
// var executorBalanceAfter = await erc20Contract.ERC20_BalanceOf(executorWalletAddress);
176+
// Console.WriteLine($"Executor balance after: {executorBalanceAfter}");
177+
178+
#endregion
179+
94180
#region Smart Ecosystem Wallet
95181

96182
// var eco = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.the-bonfire", authProvider: AuthProvider.Twitch);

Thirdweb.Tests/Thirdweb.Contracts/Thirdweb.Contracts.Tests.cs

+8
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ public async Task ReadTest_Tuple()
7878
Assert.Equal(0, result.ReturnValue2);
7979
}
8080

81+
[Fact(Timeout = 120000)]
82+
public async Task ReadTest_4Bytes()
83+
{
84+
var contract = await this.GetContract();
85+
var result = await ThirdwebContract.Read<string>(contract, "0x06fdde03");
86+
Assert.Equal("Kitty DropERC20", result);
87+
}
88+
8189
[Fact(Timeout = 120000)]
8290
public async Task ReadTest_FullSig()
8391
{

Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs

+28
Original file line numberDiff line numberDiff line change
@@ -866,4 +866,32 @@ public void PreprocessTypedDataJson_NestedLargeNumbers()
866866

867867
Assert.Equal(expectedJObject, processedJObject);
868868
}
869+
870+
[Fact]
871+
public void DecodeTransaction_1559WithAuthList()
872+
{
873+
var signedTxStr =
874+
"0x04f8ca830de9fb8082011882031083025bee94ff5d95e5aa1b5af3f106079518228a92818737728080c0f85ef85c830de9fb94654f42b74885ee6803f403f077bc0409f1066c588080a0a5caed9b0c46657a452250a3279f45937940c87c45854aead6a902d99bc638f39faa58026c6b018d36b8935a42f2bcf68097c712c9f09ca014c70887678e08a980a027ecc69e66eb9e28cbe6edab10fc827fcb6d2a34cdcb89d8b6aabc6e35608692a0750d306b04a50a35de57bd6aca11f207a8dd404f9d92502ce6e3817e52f79a1c";
875+
(var txInput, var signature) = Utils.DecodeTransaction(signedTxStr);
876+
Assert.Equal("0xfF5D95e5aA1B5Af3F106079518228A9281873772", txInput.To);
877+
Assert.Equal("0x", txInput.Data);
878+
Assert.Equal(0, txInput.Value.Value);
879+
Assert.NotNull(txInput.AuthorizationList);
880+
_ = Assert.Single(txInput.AuthorizationList);
881+
Assert.Equal("0x654F42b74885EE6803F403f077bc0409f1066c58", txInput.AuthorizationList[0].Address);
882+
Assert.Equal("0xde9fb", txInput.AuthorizationList[0].ChainId);
883+
Assert.Equal("0x0", txInput.AuthorizationList[0].Nonce);
884+
885+
(txInput, var signature2) = Utils.DecodeTransaction(signedTxStr.HexToBytes());
886+
Assert.Equal("0xfF5D95e5aA1B5Af3F106079518228A9281873772", txInput.To);
887+
Assert.Equal("0x", txInput.Data);
888+
Assert.Equal(0, txInput.Value.Value);
889+
Assert.NotNull(txInput.AuthorizationList);
890+
_ = Assert.Single(txInput.AuthorizationList);
891+
Assert.Equal("0x654F42b74885EE6803F403f077bc0409f1066c58", txInput.AuthorizationList[0].Address);
892+
Assert.Equal("0xde9fb", txInput.AuthorizationList[0].ChainId);
893+
Assert.Equal("0x0", txInput.AuthorizationList[0].Nonce);
894+
895+
Assert.Equal(signature, signature2);
896+
}
869897
}

Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs

+53-19
Original file line numberDiff line numberDiff line change
@@ -209,16 +209,36 @@ public async Task SignTypedDataV4_Typed_NullData()
209209
public async Task SignTransaction_Success()
210210
{
211211
var account = await this.GetAccount();
212-
var transaction = new ThirdwebTransactionInput(421614)
213-
{
214-
From = await account.GetAddress(),
215-
To = Constants.ADDRESS_ZERO,
216-
// Value = new HexBigInteger(0),
217-
Gas = new HexBigInteger(21000),
218-
// Data = "0x",
219-
Nonce = new HexBigInteger(99999999999),
220-
GasPrice = new HexBigInteger(10000000000),
221-
};
212+
var transaction = new ThirdwebTransactionInput(
213+
chainId: 421614,
214+
from: await account.GetAddress(),
215+
to: Constants.ADDRESS_ZERO,
216+
value: 0,
217+
gas: 21000,
218+
data: "0x",
219+
nonce: 99999999999,
220+
gasPrice: 10000000000
221+
);
222+
var signature = await account.SignTransaction(transaction);
223+
Assert.NotNull(signature);
224+
}
225+
226+
[Fact(Timeout = 120000)]
227+
public async Task SignTransaction_WithAuthorizationList_Success()
228+
{
229+
var account = await this.GetAccount();
230+
var authorization = await account.SignAuthorization(421614, Constants.ADDRESS_ZERO, false);
231+
var transaction = new ThirdwebTransactionInput(
232+
chainId: 421614,
233+
from: await account.GetAddress(),
234+
to: Constants.ADDRESS_ZERO,
235+
value: 0,
236+
gas: 21000,
237+
data: "0x",
238+
nonce: 99999999999,
239+
gasPrice: 10000000000,
240+
authorization: authorization
241+
);
222242
var signature = await account.SignTransaction(transaction);
223243
Assert.NotNull(signature);
224244
}
@@ -227,15 +247,7 @@ public async Task SignTransaction_Success()
227247
public async Task SignTransaction_NoFrom_Success()
228248
{
229249
var account = await this.GetAccount();
230-
var transaction = new ThirdwebTransactionInput(421614)
231-
{
232-
To = Constants.ADDRESS_ZERO,
233-
// Value = new HexBigInteger(0),
234-
Gas = new HexBigInteger(21000),
235-
Data = "0x",
236-
Nonce = new HexBigInteger(99999999999),
237-
GasPrice = new HexBigInteger(10000000000),
238-
};
250+
var transaction = new ThirdwebTransactionInput(chainId: 421614, to: Constants.ADDRESS_ZERO, value: 0, gas: 21000, data: "0x", nonce: 99999999999, gasPrice: 10000000000);
239251
var signature = await account.SignTransaction(transaction);
240252
Assert.NotNull(signature);
241253
}
@@ -469,4 +481,26 @@ public async Task Export_ReturnsPrivateKey()
469481
Assert.NotNull(privateKey);
470482
Assert.Equal(privateKey, await wallet.Export());
471483
}
484+
485+
[Fact(Timeout = 120000)]
486+
public async Task SignAuthorization_SelfExecution()
487+
{
488+
var wallet = await PrivateKeyWallet.Generate(this.Client);
489+
var chainId = 911867;
490+
var targetAddress = "0x654F42b74885EE6803F403f077bc0409f1066c58";
491+
492+
var currentNonce = await wallet.GetTransactionCount(chainId);
493+
494+
var authorization = await wallet.SignAuthorization(chainId: chainId, contractAddress: targetAddress, willSelfExecute: false);
495+
496+
Assert.Equal(chainId.NumberToHex(), authorization.ChainId);
497+
Assert.Equal(targetAddress, authorization.Address);
498+
Assert.True(authorization.Nonce.HexToNumber() == currentNonce);
499+
500+
authorization = await wallet.SignAuthorization(chainId: chainId, contractAddress: targetAddress, willSelfExecute: true);
501+
502+
Assert.Equal(chainId.NumberToHex(), authorization.ChainId);
503+
Assert.Equal(targetAddress, authorization.Address);
504+
Assert.True(authorization.Nonce.HexToNumber() == currentNonce + 1);
505+
}
472506
}

Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs

+12
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,18 @@ public async Task SwitchNetwork_NonZkToZk_Success()
364364
Assert.NotEqual(addy1, addy2);
365365
}
366366

367+
[Fact(Timeout = 120000)]
368+
public async Task SignAuthorization_WithPrivateKeyWallet_Success()
369+
{
370+
var smartWallet = await SmartWallet.Create(personalWallet: await PrivateKeyWallet.Generate(this.Client), chainId: 421614);
371+
var smartWalletSigner = await smartWallet.GetPersonalWallet();
372+
var signature1 = await smartWallet.SignAuthorization(chainId: 421614, contractAddress: Constants.ADDRESS_ZERO, willSelfExecute: true);
373+
var signature2 = await smartWalletSigner.SignAuthorization(chainId: 421614, contractAddress: Constants.ADDRESS_ZERO, willSelfExecute: true);
374+
Assert.Equal(signature1.ChainId, signature2.ChainId);
375+
Assert.Equal(signature1.Address, signature2.Address);
376+
Assert.Equal(signature1.Nonce, signature2.Nonce);
377+
}
378+
367379
// [Fact(Timeout = 120000)]
368380
// public async Task MultiChainTransaction_Success()
369381
// {

Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs

+16-14
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,6 @@ internal static (string callData, Function function) EncodeFunctionCall(Thirdweb
182182
{
183183
var contractRaw = new Contract(null, contract.Abi, contract.Address);
184184
var function = GetFunctionMatchSignature(contractRaw, method, parameters);
185-
if (function == null)
186-
{
187-
if (method.Contains('('))
188-
{
189-
var canonicalSignature = ExtractCanonicalSignature(method);
190-
var selector = Nethereum.Util.Sha3Keccack.Current.CalculateHash(canonicalSignature)[..8];
191-
function = contractRaw.GetFunctionBySignature(selector);
192-
}
193-
else
194-
{
195-
throw new ArgumentException("Method signature not found in contract ABI.");
196-
}
197-
}
198185
return (function.GetData(parameters), function);
199186
}
200187

@@ -207,6 +194,11 @@ internal static (string callData, Function function) EncodeFunctionCall(Thirdweb
207194
/// <returns>The matching function, or null if no match is found.</returns>
208195
private static Function GetFunctionMatchSignature(Contract contract, string functionName, params object[] args)
209196
{
197+
if (functionName.StartsWith("0x"))
198+
{
199+
return contract.GetFunctionBySignature(functionName);
200+
}
201+
210202
var abi = contract.ContractBuilder.ContractABI;
211203
var functions = abi.Functions;
212204
var paramsCount = args?.Length ?? 0;
@@ -218,7 +210,17 @@ private static Function GetFunctionMatchSignature(Contract contract, string func
218210
return contract.GetFunctionBySignature(sha);
219211
}
220212
}
221-
return null;
213+
214+
if (functionName.Contains('('))
215+
{
216+
var canonicalSignature = ExtractCanonicalSignature(functionName);
217+
var selector = Utils.HashMessage(canonicalSignature)[..8];
218+
return contract.GetFunctionBySignature(selector);
219+
}
220+
else
221+
{
222+
throw new ArgumentException("Method signature not found in contract ABI.");
223+
}
222224
}
223225

224226
/// <summary>

Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs

+12-4
Original file line numberDiff line numberDiff line change
@@ -261,17 +261,24 @@ public static async Task<string> Simulate(ThirdwebTransaction transaction)
261261
public static async Task<BigInteger> EstimateGasLimit(ThirdwebTransaction transaction)
262262
{
263263
var rpc = ThirdwebRPC.GetRpcInstance(transaction._wallet.Client, transaction.Input.ChainId.Value);
264-
265-
if (await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false))
264+
var isZkSync = await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false);
265+
BigInteger divider = isZkSync
266+
? 7
267+
: transaction.Input.AuthorizationList == null
268+
? 5
269+
: 3;
270+
BigInteger baseGas;
271+
if (isZkSync)
266272
{
267273
var hex = (await rpc.SendRequestAsync<JToken>("zks_estimateFee", transaction.Input).ConfigureAwait(false))["gas_limit"].ToString();
268-
return new HexBigInteger(hex).Value * 10 / 5;
274+
baseGas = hex.HexToNumber();
269275
}
270276
else
271277
{
272278
var hex = await rpc.SendRequestAsync<string>("eth_estimateGas", transaction.Input).ConfigureAwait(false);
273-
return new HexBigInteger(hex).Value * 10 / 7;
279+
baseGas = hex.HexToNumber();
274280
}
281+
return baseGas * 10 / divider;
275282
}
276283

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

358365
var rpc = ThirdwebRPC.GetRpcInstance(transaction._wallet.Client, transaction.Input.ChainId.Value);
359366
string hash;
367+
360368
if (await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false) && transaction.Input.ZkSync.HasValue)
361369
{
362370
var zkTx = await ConvertToZkSyncTransaction(transaction).ConfigureAwait(false);

0 commit comments

Comments
 (0)