Skip to content

EIP-7702 Integration #108

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

Merged
merged 8 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading