Skip to content

Commit

Permalink
smart account tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xFirekeeper committed Apr 9, 2024
1 parent f6d1c49 commit 27caf62
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 1 deletion.
174 changes: 174 additions & 0 deletions Thirdweb.Tests/Thirdweb.SmartAccount.Tests.cs
Original file line number Diff line number Diff line change
@@ -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<SmartAccount> 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<InvalidOperationException>(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<InvalidOperationException>(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<string>() { 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());
}
}
62 changes: 61 additions & 1 deletion Thirdweb/Thirdweb.Wallets/SmartAccount/SmartAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -88,6 +88,10 @@ public async Task<bool> IsDeployed()

public async Task<string> SendTransaction(TransactionInput transaction)
{
if (transaction == null)
{
throw new InvalidOperationException("SmartAccount.SendTransaction: Transaction input is required.");
}
var signedOp = await SignUserOp(transaction);
return await SendUserOp(signedOp);
}
Expand Down Expand Up @@ -334,6 +338,62 @@ string reqValidityEndTimestamp
return await Utils.GetTransactionReceipt(_client, _chainId, txHash);
}

public async Task<TransactionReceipt> AddAdmin(string admin)
{
var request = new SignerPermissionRequest()
{
Signer = admin,
IsAdmin = 1,
ApprovedTargets = new List<string>(),
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<TransactionReceipt> RemoveAdmin(string admin)
{
var request = new SignerPermissionRequest()
{
Signer = admin,
IsAdmin = 2,
ApprovedTargets = new List<string>(),
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<string> SignTypedDataV4(string json)
{
return _personalAccount.SignTypedDataV4(json);
Expand Down

0 comments on commit 27caf62

Please sign in to comment.