Skip to content
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

feat/PRO-2416/upgrade-prime-modular-accounts #155

Merged
merged 6 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## [0.13.0] - 2024-06-17

### Added Changes
- `getSDK` include a param to choose to instantiate the Prime SDK instead of the Modular SDK
- Added Etherspot Modular SDK `installModule` and `uninstallModule` to hook `useEtherspotModules`
- Added `isModular` to context `EtherspotContextProvider`

### Breaking Changes
- Etherspot Modular SDK implemented to Transaction Kit as the default `accountTemplate`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Etherspot Modular SDK implemented to Transaction Kit as the default `accountTemplate`
- Etherspot Modular SDK implemented to TransactionKit as the default `accountTemplate`

- The account template in the `EtherspotTransactionKit` component, can only be 'etherspot' if `moddular` is true.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- The account template in the `EtherspotTransactionKit` component, can only be 'etherspot' if `moddular` is true.


## [0.12.1] - 2024-05-22

### Added Changes
Expand Down
143 changes: 143 additions & 0 deletions __mocks__/@etherspot/modular-sdk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import * as EtherspotModular from '@etherspot/modular-sdk';
import { ethers } from 'ethers';

export const defaultAccountAddressModular = '0x7F30B1960D5556929B03a0339814fE903c55a347';
export const otherFactoryDefaultAccountAddressModular = '0xe383724e3bDC4753746dEC781809f8CD82010914';
export const otherAccountAddressModular = '0xAb4C67d8D7B248B2fA6B638C645466065fE8F1F1';

export class ModularSdk {
sdkChainId;
userOps = [];
nonce = ethers.BigNumber.from(1);
factoryWallet;

constructor(provider, config) {
this.sdkChainId = config.chainId;
this.factoryWallet = config.factoryWallet;
}

getCounterFactualAddress() {
if (this.factoryWallet === 'etherspotModular') {
return defaultAccountAddressModular;
}
return otherFactoryDefaultAccountAddressModular;
}

async clearUserOpsFromBatch() {
this.userOps = [];
}

async addUserOpsToBatch(userOp) {
this.userOps.push(userOp);
}

async estimate({ paymasterDetails: paymaster }) {
let maxFeePerGas = ethers.utils.parseUnits('1', 'gwei');
let maxPriorityFeePerGas = ethers.utils.parseUnits('1', 'gwei');
let callGasLimit = ethers.BigNumber.from('50000');
let signature = '0x004';

if (paymaster?.url === 'someUrl') {
maxFeePerGas = ethers.utils.parseUnits('2', 'gwei');
maxPriorityFeePerGas = ethers.utils.parseUnits('3', 'gwei');
callGasLimit = ethers.BigNumber.from('75000');
}

if (paymaster?.url === 'someUnstableUrl') {
signature = '0x0';
}

let finalGasLimit = ethers.BigNumber.from(callGasLimit);

if (this.sdkChainId === 420) {
throw new Error('Transaction reverted: chain too high');
IAmKio marked this conversation as resolved.
Show resolved Hide resolved
}

this.userOps.forEach((userOp) => {
if (userOp.to === '0xDEADBEEF') {
throw new Error('Transaction reverted: invalid address');
}
finalGasLimit = finalGasLimit.add(callGasLimit);
if (userOp.data && userOp.data !== '0x0' && userOp.data !== '0xFFF') {
finalGasLimit = finalGasLimit.add(callGasLimit);
}
});

return {
sender: defaultAccountAddressModular,
nonce: this.nonce,
initCode: '0x001',
callData: '0x002',
callGasLimit: finalGasLimit,
verificationGasLimit: ethers.BigNumber.from('25000'),
preVerificationGas: ethers.BigNumber.from('75000'),
maxFeePerGas,
maxPriorityFeePerGas,
paymasterAndData: '0x003',
signature,
};
}

totalGasEstimated({ callGasLimit, verificationGasLimit, preVerificationGas }) {
return callGasLimit.add(verificationGasLimit).add(preVerificationGas);
}

async send(userOp) {
if (this.sdkChainId === 696969) {
throw new Error('Transaction reverted: chain too hot');
IAmKio marked this conversation as resolved.
Show resolved Hide resolved
}

if (userOp.signature === '0x0') {
throw new Error('Transaction reverted: invalid signature');
}

/**
* provide fake userOp hash by increasing nonce on each send
* and add SDK chain ID to make it more unique per userOp
*/
const userOpHash = this.nonce.add(this.sdkChainId).toHexString();
this.nonce = this.nonce.add(1);

return userOpHash;
}

async installModule(moduleType, module, initData, accountAddress) {
if (!accountAddress && !defaultAccountAddressModular) {
throw new Error('No account address provided!')
}

if (!moduleType || !module) {
throw new Error('installModule props missing')
}

if (module === '0x222') {
throw new Error('module is already installed')
}

return '0x123';
}

async uninstallModule(moduleType, module, deinitData, accountAddress) {
if (module === '0x222') {
throw new Error('module is not installed')
}

if (!accountAddress && !defaultAccountAddressModular) {
throw new Error('No account address provided!')
}

if (!moduleType || !module || !deinitData) {
throw new Error('uninstallModule props missing')
}

return '0x456';
}
}

export const isWalletProvider = EtherspotModular.isWalletProvider;

export const Factory = EtherspotModular.Factory;

export const EtherspotBundler = jest.fn();

export default EtherspotModular;
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderHook, render, waitFor, act } from '@testing-library/react';
import { renderHook, render, waitFor } from '@testing-library/react';
import { act } from 'react';
import { ethers } from 'ethers';

import { useEtherspotTransactions, EtherspotTransactionKit, EtherspotBatches, EtherspotBatch, EtherspotTokenTransferTransaction } from '../../src';
Expand All @@ -24,7 +25,7 @@ describe('EtherspotTokenTransferTransaction', () => {

it('throws error if wrong receiver address provided', async () => {
await expect(async () => {
await act(() => {
await act(() => {
render(
<EtherspotTransactionKit provider={provider}>
<EtherspotBatches>
Expand Down
95 changes: 95 additions & 0 deletions __tests__/hooks/useEtherspotModules.test.js
IAmKio marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { renderHook, waitFor } from '@testing-library/react';
import { ethers } from 'ethers';

// hooks
import { useEtherspotModules, EtherspotTransactionKit } from '../../src';
import { MODULE_TYPE } from '@etherspot/modular-sdk/dist/sdk/common';

const ethersProvider = new ethers.providers.JsonRpcProvider('http://localhost:8545', 'sepolia'); // replace with your node's RPC URL
const provider = new ethers.Wallet.createRandom().connect(ethersProvider);

const moduleAddress = '0x111';
const initData = ethers.utils.defaultAbiCoder.encode(
["address", "bytes"],
['0x0000000000000000000000000000000000000001', '0x00']
);
const deInitData = ethers.utils.defaultAbiCoder.encode(
["address", "bytes"],
['0x0000000000000000000000000000000000000001', '0x00']
);

describe('useEtherspotModules()', () => {
it('install one module', async () => {
const wrapper = ({ children }) => (
<EtherspotTransactionKit provider={provider}>
{children}
</EtherspotTransactionKit>
);

const { result } = renderHook(({ chainId }) => useEtherspotModules(chainId), {
initialProps: { chainId: 1 },
wrapper,
});


// wait for balances to be fetched for chain ID 1
await waitFor(() => expect(result.current).not.toBeNull());

const installModuleMissingProps = await result.current.installModule(MODULE_TYPE.VALIDATOR)
.catch((e) => {
console.error(e);
return `${e}`
})
expect(installModuleMissingProps).toBe('Error: Failed to install module: Error: installModule props missing');

const installModuleAlreadyInstalled = await result.current.installModule(MODULE_TYPE.VALIDATOR, '0x222')
.catch((e) => {
console.error(e);
return `${e}`
})

expect(installModuleAlreadyInstalled).toBe('Error: Failed to install module: Error: module is already installed');

const installOneModule = await result.current.installModule(MODULE_TYPE.VALIDATOR, moduleAddress, initData);
expect(installOneModule).toBe('0x123')
});

it('uninstall one module', async () => {
const wrapper = ({ children }) => (
<EtherspotTransactionKit provider={provider}>
{children}
</EtherspotTransactionKit>
);

const { result } = renderHook(({ chainId }) => useEtherspotModules(chainId), {
initialProps: { chainId: 1 },
wrapper,
});

// wait for balances to be fetched for chain ID 1
await waitFor(() => expect(result.current).not.toBeNull());

const uninstallModuleNotInstalled = await result.current.uninstallModule(MODULE_TYPE.VALIDATOR, '0x222', deInitData)
.catch((e) => {
console.error(e);
return `${e}`
})

expect(uninstallModuleNotInstalled).toBe('Error: Failed to uninstall module: Error: module is not installed');

const installOneModule = await result.current.installModule(MODULE_TYPE.VALIDATOR, moduleAddress, initData);
expect(installOneModule).toBe('0x123');

const uninstallModulePropsMissing = await result.current.uninstallModule(moduleAddress)
.catch((e) => {
console.error(e);
return `${e}`
})
expect(uninstallModulePropsMissing).toBe('Error: Failed to uninstall module: Error: uninstallModule props missing');


const uninstallOneModule = await result.current.uninstallModule(MODULE_TYPE.VALIDATOR, moduleAddress, deInitData);
expect(uninstallOneModule).toBe('0x456');

});
})
3 changes: 2 additions & 1 deletion __tests__/hooks/useEtherspotTransactions.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderHook, render, act, waitFor } from '@testing-library/react';
import { renderHook, render, waitFor } from '@testing-library/react';
import { act } from 'react';
import { ethers } from 'ethers';

// hooks
Expand Down
13 changes: 8 additions & 5 deletions __tests__/hooks/useWalletAddress.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { Factory } from '@etherspot/prime-sdk';

// hooks
import { EtherspotTransactionKit, useWalletAddress } from '../../src';
import {
defaultAccountAddressModular,
} from '../../__mocks__/@etherspot/modular-sdk';
import {
defaultAccountAddress,
otherFactoryDefaultAccountAddress,
Expand All @@ -17,7 +20,7 @@ const providerWalletAddress = provider.address;
describe('useWalletAddress()', () => {
it('returns default type wallet address if no provided type', async () => {
const wrapper = ({ children }) => (
<EtherspotTransactionKit provider={provider}>
<EtherspotTransactionKit provider={provider} accountTemplate='etherspot'>
{children}
</EtherspotTransactionKit>
);
Expand All @@ -41,10 +44,10 @@ describe('useWalletAddress()', () => {
});

await waitFor(() => expect(result.current).not.toBe(undefined));
expect(result.current).toEqual(defaultAccountAddress);
expect(result.current).toEqual(defaultAccountAddressModular);

rerender({ providerType: 'provider' });
await waitFor(() => expect(result.current).not.toBe(defaultAccountAddress));
await waitFor(() => expect(result.current).not.toBe(defaultAccountAddressModular));
expect(result.current).toEqual(providerWalletAddress);
});

Expand All @@ -61,7 +64,7 @@ describe('useWalletAddress()', () => {
});

await waitFor(() => expect(result.current).not.toBe(undefined));
expect(result.current).toEqual(defaultAccountAddress);
expect(result.current).toEqual(defaultAccountAddressModular);

rerender({ providerType: 'whatever' });
await waitFor(() => expect(result.current).toBe(undefined));
Expand All @@ -79,7 +82,7 @@ describe('useWalletAddress()', () => {
});

await waitFor(() => expect(resultNoAccountTemplate.current).not.toBe(undefined));
expect(resultNoAccountTemplate.current).toEqual(defaultAccountAddress);
expect(resultNoAccountTemplate.current).toEqual(defaultAccountAddressModular);

const { result: resultWithAccountTemplate } = renderHook(() => useWalletAddress(), {
wrapper: createWrapper({ accountTemplate: Factory.SIMPLE_ACCOUNT }),
Expand Down
Loading
Loading