From 27caf6200c67246f01b265f160f2feeac1579d6c Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 9 Apr 2024 21:42:08 +0300 Subject: [PATCH 1/4] smart account tests --- Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs | 174 ++++++++++++++++++ .../SmartAccount/SmartAccount.cs | 62 ++++++- 2 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs diff --git a/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs b/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs new file mode 100644 index 0000000..1785141 --- /dev/null +++ b/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs @@ -0,0 +1,174 @@ +using System.Numerics; +using Nethereum.Hex.HexTypes; +using Nethereum.RPC.Eth.DTOs; + +namespace Thirdweb.Tests; + +public class SmartAccountTests : BaseTests +{ + public SmartAccountTests(ITestOutputHelper output) + : base(output) { } + + private async Task GetSmartAccount() + { + var client = new ThirdwebClient(secretKey: _secretKey); + var privateKeyAccount = new PrivateKeyAccount(client, _testPrivateKey); + await privateKeyAccount.Connect(); + var smartAccount = new SmartAccount(client, personalAccount: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614); + await smartAccount.Connect(); + return smartAccount; + } + + [Fact] + public async Task Initialization_Success() + { + var account = await GetSmartAccount(); + Assert.NotNull(await account.GetAddress()); + } + + [Fact] + public async Task Initialization_Fail() + { + var client = new ThirdwebClient(secretKey: _secretKey); + var privateKeyAccount = new PrivateKeyAccount(client, _testPrivateKey); + var smartAccount = new SmartAccount(client, personalAccount: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614); + var ex = await Assert.ThrowsAsync(smartAccount.Connect); + Assert.Equal("SmartAccount.Connect: Personal account must be connected.", ex.Message); + } + + [Fact] + public async Task IsDeployed_True() + { + var account = await GetSmartAccount(); + Assert.True(await account.IsDeployed()); + } + + [Fact] + public async Task IsDeployed_False() + { + var client = new ThirdwebClient(secretKey: _secretKey); + var privateKeyAccount = new PrivateKeyAccount(client, _testPrivateKey); + await privateKeyAccount.Connect(); + var smartAccount = new SmartAccount( + client, + personalAccount: privateKeyAccount, + factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", + gasless: true, + chainId: 421614, + accountAddressOverride: "0x75A4e181286F5767c38dFBE65fe1Ad4793aCB642" // vanity + ); + await smartAccount.Connect(); + Assert.False(await smartAccount.IsDeployed()); + } + + [Fact] + public async Task SendTransaction_Success() + { + var account = await GetSmartAccount(); + var tx = await account.SendTransaction( + new TransactionInput() + { + From = await account.GetAddress(), + To = await account.GetAddress(), + Value = new HexBigInteger(BigInteger.Parse("0")), + } + ); + Assert.NotNull(tx); + } + + [Fact] + public async Task SendTransaction_Fail() + { + var account = await GetSmartAccount(); + var ex = await Assert.ThrowsAsync(async () => await account.SendTransaction(null)); + Assert.Equal("SmartAccount.SendTransaction: Transaction input is required.", ex.Message); + } + + [Fact] + public async Task GetAddress() + { + var account = await GetSmartAccount(); + var address = await account.GetAddress(); + Assert.NotNull(address); + } + + [Fact] + public async Task GetAddress_WithOverride() + { + var client = new ThirdwebClient(secretKey: _secretKey); + var privateKeyAccount = new PrivateKeyAccount(client, _testPrivateKey); + await privateKeyAccount.Connect(); + var smartAccount = new SmartAccount( + client, + personalAccount: privateKeyAccount, + factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", + gasless: true, + chainId: 421614, + accountAddressOverride: "0x75A4e181286F5767c38dFBE65fe1Ad4793aCB642" // vanity + ); + await smartAccount.Connect(); + var address = await smartAccount.GetAddress(); + Assert.Equal("0x75A4e181286F5767c38dFBE65fe1Ad4793aCB642", address); + } + + [Fact] + public async Task PersonalSign() // This is the only different signing mechanism for smart wallets, also tests isValidSignature + { + var account = await GetSmartAccount(); + var sig = await account.PersonalSign("Hello, world!"); + Assert.NotNull(sig); + } + + [Fact] + public async Task CreateSessionKey() + { + var account = await GetSmartAccount(); + var receipt = await account.CreateSessionKey( + signerAddress: "0x253d077C45A3868d0527384e0B34e1e3088A3908", + approvedTargets: new List() { Constants.ADDRESS_ZERO }, + nativeTokenLimitPerTransactionInWei: "0", + permissionStartTimestamp: "0", + permissionEndTimestamp: (Utils.GetUnixTimeStampNow() + 86400).ToString(), + reqValidityStartTimestamp: "0", + reqValidityEndTimestamp: Utils.GetUnixTimeStampIn10Years().ToString() + ); + Assert.NotNull(receipt); + Assert.NotNull(receipt.TransactionHash); + } + + [Fact] + public async Task AddAdmin() + { + var account = await GetSmartAccount(); + var receipt = await account.AddAdmin("0x039d7D195f6f8537003fFC19e86cd91De5e9C431"); + Assert.NotNull(receipt); + Assert.NotNull(receipt.TransactionHash); + } + + [Fact] + public async Task RemoveAdmin() + { + var account = await GetSmartAccount(); + var receipt = await account.RemoveAdmin("0x039d7D195f6f8537003fFC19e86cd91De5e9C431"); + Assert.NotNull(receipt); + Assert.NotNull(receipt.TransactionHash); + } + + [Fact] + public async Task IsConnected() + { + var account = await GetSmartAccount(); + Assert.True(await account.IsConnected()); + + await account.Disconnect(); + Assert.False(await account.IsConnected()); + } + + [Fact] + public async Task Disconnect() + { + var account = await GetSmartAccount(); + await account.Disconnect(); + Assert.False(await account.IsConnected()); + } +} diff --git a/Thirdweb/Thirdweb.Wallets/SmartAccount/SmartAccount.cs b/Thirdweb/Thirdweb.Wallets/SmartAccount/SmartAccount.cs index d7742bf..4b3628b 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartAccount/SmartAccount.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartAccount/SmartAccount.cs @@ -56,7 +56,7 @@ public async Task Connect() { if (!await _personalAccount.IsConnected()) { - throw new Exception("SmartAccount.Connect: Personal account must be connected."); + throw new InvalidOperationException("SmartAccount.Connect: Personal account must be connected."); } _entryPointContract = new ThirdwebContract( @@ -88,6 +88,10 @@ public async Task IsDeployed() public async Task SendTransaction(TransactionInput transaction) { + if (transaction == null) + { + throw new InvalidOperationException("SmartAccount.SendTransaction: Transaction input is required."); + } var signedOp = await SignUserOp(transaction); return await SendUserOp(signedOp); } @@ -334,6 +338,62 @@ string reqValidityEndTimestamp return await Utils.GetTransactionReceipt(_client, _chainId, txHash); } + public async Task AddAdmin(string admin) + { + var request = new SignerPermissionRequest() + { + Signer = admin, + IsAdmin = 1, + ApprovedTargets = new List(), + NativeTokenLimitPerTransaction = 0, + PermissionStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, + PermissionEndTimestamp = Utils.GetUnixTimeStampIn10Years(), + ReqValidityStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, + ReqValidityEndTimestamp = Utils.GetUnixTimeStampIn10Years(), + Uid = Guid.NewGuid().ToByteArray() + }; + + var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", _chainId, await GetAddress(), request, _personalAccount); + var data = new Contract(null, _accountContract.Abi, _accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToByteArray()); + var txInput = new TransactionInput() + { + From = await GetAddress(), + To = _accountContract.Address, + Value = new HexBigInteger(0), + Data = data + }; + var txHash = await SendTransaction(txInput); + return await Utils.GetTransactionReceipt(_client, _chainId, txHash); + } + + public async Task RemoveAdmin(string admin) + { + var request = new SignerPermissionRequest() + { + Signer = admin, + IsAdmin = 2, + ApprovedTargets = new List(), + NativeTokenLimitPerTransaction = 0, + PermissionStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, + PermissionEndTimestamp = Utils.GetUnixTimeStampIn10Years(), + ReqValidityStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, + ReqValidityEndTimestamp = Utils.GetUnixTimeStampIn10Years(), + Uid = Guid.NewGuid().ToByteArray() + }; + + var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", _chainId, await GetAddress(), request, _personalAccount); + var data = new Contract(null, _accountContract.Abi, _accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToByteArray()); + var txInput = new TransactionInput() + { + From = await GetAddress(), + To = _accountContract.Address, + Value = new HexBigInteger(0), + Data = data + }; + var txHash = await SendTransaction(txInput); + return await Utils.GetTransactionReceipt(_client, _chainId, txHash); + } + public Task SignTypedDataV4(string json) { return _personalAccount.SignTypedDataV4(json); From 87576af3f87093b04edfe9b07fbb9b1ea25821da Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 9 Apr 2024 22:20:33 +0300 Subject: [PATCH 2/4] extra 712 and personal acc --- Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs | 9 ++++++ Thirdweb.Tests/Thirdweb.Wallets.Tests.cs | 32 ++++++++++++++++++- .../SmartAccount/SmartAccount.cs | 5 +++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs b/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs index 1785141..bfeea9f 100644 --- a/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs @@ -92,6 +92,15 @@ public async Task GetAddress() Assert.NotNull(address); } + [Fact] + public async Task GetPersonalAccount() + { + var account = await GetSmartAccount(); + var personalAccount = await account.GetPersonalAccount(); + Assert.NotNull(personalAccount); + _ = Assert.IsType(personalAccount); + } + [Fact] public async Task GetAddress_WithOverride() { diff --git a/Thirdweb.Tests/Thirdweb.Wallets.Tests.cs b/Thirdweb.Tests/Thirdweb.Wallets.Tests.cs index 478ea04..f103573 100644 --- a/Thirdweb.Tests/Thirdweb.Wallets.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Wallets.Tests.cs @@ -105,9 +105,39 @@ public async Task SignTypedDataV4_Typed() { var wallet = await GetWallet(); var typedData = EIP712.GetTypedDefinition_SmartAccount_AccountMessage("Account", "1", 421614, await wallet.GetAddress()); - var accountMessage = new AccountAbstraction.AccountMessage { Message = System.Text.Encoding.UTF8.GetBytes("Hello, world!") }; + var accountMessage = new AccountAbstraction.AccountMessage { Message = System.Text.Encoding.UTF8.GetBytes("Hello, world!").HashPrefixedMessage() }; var signature = await wallet.SignTypedDataV4(accountMessage, typedData); Assert.NotNull(signature); + + var signerAcc = await ((SmartAccount)wallet.ActiveAccount).GetPersonalAccount(); + var gen1 = await EIP712.GenerateSignature_SmartAccount_AccountMessage( + "Account", + "1", + 421614, + await wallet.GetAddress(), + System.Text.Encoding.UTF8.GetBytes("Hello, world!").HashPrefixedMessage(), + signerAcc + ); + Assert.Equal(gen1, signature); + + var req = new AccountAbstraction.SignerPermissionRequest() + { + Signer = await wallet.GetAddress(), + IsAdmin = 0, + ApprovedTargets = new List() { Constants.ADDRESS_ZERO }, + NativeTokenLimitPerTransaction = 0, + PermissionStartTimestamp = 0, + ReqValidityStartTimestamp = 0, + PermissionEndTimestamp = 0, + Uid = new byte[32] + }; + + var typedData2 = EIP712.GetTypedDefinition_SmartAccount("Account", "1", 421614, await wallet.GetAddress()); + var signature2 = await wallet.SignTypedDataV4(req, typedData2); + Assert.NotNull(signature2); + + var gen2 = await EIP712.GenerateSignature_SmartAccount("Account", "1", 421614, await wallet.GetAddress(), req, signerAcc); + Assert.Equal(gen2, signature2); } [Fact] diff --git a/Thirdweb/Thirdweb.Wallets/SmartAccount/SmartAccount.cs b/Thirdweb/Thirdweb.Wallets/SmartAccount/SmartAccount.cs index 4b3628b..5ac0ec5 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartAccount/SmartAccount.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartAccount/SmartAccount.cs @@ -239,6 +239,11 @@ internal async Task ForceDeploy() _ = await Utils.GetTransactionReceipt(_client, _chainId, txHash); } + public Task GetPersonalAccount() + { + return Task.FromResult(_personalAccount); + } + public Task GetAddress() { return Task.FromResult(_accountContract.Address); From 83d9323fa912dd74166062bb116ed6578c11e828 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 9 Apr 2024 22:21:49 +0300 Subject: [PATCH 3/4] unused types --- .../Thirdweb.AccountAbstraction/AATypes.cs | 18 ------------------ .../BundlerClient.cs | 6 ------ 2 files changed, 24 deletions(-) diff --git a/Thirdweb/Thirdweb.Wallets/SmartAccount/Thirdweb.AccountAbstraction/AATypes.cs b/Thirdweb/Thirdweb.Wallets/SmartAccount/Thirdweb.AccountAbstraction/AATypes.cs index 2570745..3e9bf72 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartAccount/Thirdweb.AccountAbstraction/AATypes.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartAccount/Thirdweb.AccountAbstraction/AATypes.cs @@ -69,16 +69,6 @@ public class ExecuteFunction : FunctionMessage public virtual byte[] Calldata { get; set; } } - [Function("createAccount", "address")] - public class CreateAccountFunction : FunctionMessage - { - [Parameter("address", "_admin", 1)] - public virtual string Admin { get; set; } - - [Parameter("bytes", "_data", 2)] - public virtual byte[] Data { get; set; } - } - public class EthEstimateUserOperationGasResponse { public string PreVerificationGas { get; set; } @@ -86,14 +76,6 @@ public class EthEstimateUserOperationGasResponse public string CallGasLimit { get; set; } } - public class EthGetUserOperationByHashResponse - { - public string entryPoint { get; set; } - public string transactionHash { get; set; } - public string blockHash { get; set; } - public string blockNumber { get; set; } - } - public class EthGetUserOperationReceiptResponse { public TransactionReceipt receipt { get; set; } diff --git a/Thirdweb/Thirdweb.Wallets/SmartAccount/Thirdweb.AccountAbstraction/BundlerClient.cs b/Thirdweb/Thirdweb.Wallets/SmartAccount/Thirdweb.AccountAbstraction/BundlerClient.cs index 30615c5..0a00caa 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartAccount/Thirdweb.AccountAbstraction/BundlerClient.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartAccount/Thirdweb.AccountAbstraction/BundlerClient.cs @@ -10,12 +10,6 @@ public static class BundlerClient { // Bundler requests - public static async Task EthGetUserOperationByHash(ThirdwebClient client, string bundlerUrl, object requestId, string userOpHash) - { - var response = await BundlerRequest(client, bundlerUrl, requestId, "eth_getUserOperationByHash", userOpHash); - return JsonConvert.DeserializeObject(response.Result.ToString()); - } - public static async Task EthGetUserOperationReceipt(ThirdwebClient client, string bundlerUrl, object requestId, string userOpHash) { var response = await BundlerRequest(client, bundlerUrl, requestId, "eth_getUserOperationReceipt", userOpHash); From 4f2f92cdf8df5a22504de04e8553816abbfdce45 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 9 Apr 2024 22:32:57 +0300 Subject: [PATCH 4/4] Update Thirdweb.SmartAccount.Tests.cs --- Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs b/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs index bfeea9f..4d2c508 100644 --- a/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs @@ -76,6 +76,25 @@ public async Task SendTransaction_Success() Assert.NotNull(tx); } + [Fact] + public async Task SendTransaction_ClientBundleId_Success() + { + var client = new ThirdwebClient(clientId: _clientIdBundleIdOnly, bundleId: _bundleIdBundleIdOnly); + var privateKeyAccount = new PrivateKeyAccount(client, _testPrivateKey); + await privateKeyAccount.Connect(); + var smartAccount = new SmartAccount(client, personalAccount: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614); + await smartAccount.Connect(); + var tx = await smartAccount.SendTransaction( + new TransactionInput() + { + From = await smartAccount.GetAddress(), + To = await smartAccount.GetAddress(), + Value = new HexBigInteger(BigInteger.Parse("0")), + } + ); + Assert.NotNull(tx); + } + [Fact] public async Task SendTransaction_Fail() {