Skip to content

Commit

Permalink
fix(account): estimate fee transfered from provider to account class
Browse files Browse the repository at this point in the history
  • Loading branch information
MilGard91 committed Mar 21, 2022
1 parent ce674ca commit 93e7dd9
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 109 deletions.
58 changes: 38 additions & 20 deletions __tests__/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ describe('deploy and test Wallet', () => {
await defaultProvider.waitForTransaction(dappResponse.transaction_hash);
});

test('estimate fee', async () => {
const { amount, unit } = await account.estimateFee({
contractAddress: erc20Address,
entrypoint: 'transfer',
calldata: [erc20.address, '10'],
});
expect(typeof amount).toBe('number');
expect(typeof unit).toBe('string');
});

test('same wallet address', () => {
expect(account.address).toBe(account.address);
});
Expand All @@ -65,11 +75,15 @@ describe('deploy and test Wallet', () => {
});

test('execute by wallet owner', async () => {
const { code, transaction_hash } = await account.execute({
contractAddress: erc20Address,
entrypoint: 'transfer',
calldata: [toBN(erc20Address).toString(), '10'],
});
const { code, transaction_hash } = await account.execute(
{
contractAddress: erc20Address,
entrypoint: 'transfer',
calldata: [erc20.address, '10'],
},
undefined,
{ maxFee: '0' }
);

expect(code).toBe('TRANSACTION_RECEIVED');
await defaultProvider.waitForTransaction(transaction_hash);
Expand All @@ -78,7 +92,7 @@ describe('deploy and test Wallet', () => {
test('read balance of wallet after transfer', async () => {
const { res } = await erc20.balance_of(account.address);

expect(number.toBN(res as string).toString()).toStrictEqual(number.toBN(990).toString());
expect(res).toStrictEqual(toBN(990));
});

test('execute with custom nonce', async () => {
Expand All @@ -91,29 +105,33 @@ describe('deploy and test Wallet', () => {
{
contractAddress: erc20Address,
entrypoint: 'transfer',
calldata: [toBN(erc20Address).toString(), '10'],
calldata: [account.address, '10'],
},
undefined,
{ nonce }
{ nonce, maxFee: '0' }
);

expect(code).toBe('TRANSACTION_RECEIVED');
await defaultProvider.waitForTransaction(transaction_hash);
});

test('execute multiple transactions', async () => {
const { code, transaction_hash } = await account.execute([
{
contractAddress: dapp.address,
entrypoint: 'set_number',
calldata: ['47'],
},
{
contractAddress: dapp.address,
entrypoint: 'increase_number',
calldata: ['10'],
},
]);
const { code, transaction_hash } = await account.execute(
[
{
contractAddress: dapp.address,
entrypoint: 'set_number',
calldata: ['47'],
},
{
contractAddress: dapp.address,
entrypoint: 'increase_number',
calldata: ['10'],
},
],
undefined,
{ maxFee: '0' }
);

expect(code).toBe('TRANSACTION_RECEIVED');
await defaultProvider.waitForTransaction(transaction_hash);
Expand Down
31 changes: 0 additions & 31 deletions __tests__/accountContract.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Contract, defaultProvider, ec, hash, number, stark } from '../src';
import { transformCallsToMulticallArrays } from '../src/utils/transaction';
import { compiledArgentAccount, compiledErc20 } from './fixtures';

describe('getStarkAccountFromPrivateKey()', () => {
Expand Down Expand Up @@ -64,36 +63,6 @@ describe('deploy and test Wallet', () => {

expect(res).toStrictEqual(number.toBN(1000));
});

test('execute by wallet owner', async () => {
const nonce = (await accountContract.get_nonce()).nonce.toString();

const calls = [
{ contractAddress: erc20Address, entrypoint: 'transfer', calldata: [erc20Address, '10'] },
];
const msgHash = hash.hashMulticall(accountContract.address, calls, nonce, '0');

const { callArray, calldata } = transformCallsToMulticallArrays(calls);

const signature = ec.sign(starkKeyPair, msgHash);
// eslint-disable-next-line no-underscore-dangle
const { code, transaction_hash } = await accountContract.__execute__(
callArray,
calldata,
nonce,
signature
);

expect(code).toBe('TRANSACTION_RECEIVED');

await defaultProvider.waitForTransaction(transaction_hash);
});

test('read balance of wallet after transfer', async () => {
const { res } = await erc20.balance_of(accountContract.address);

expect(number.toBN(res as string).toString()).toStrictEqual(number.toBN(990).toString());
});
});

test('build tx', async () => {
Expand Down
2 changes: 2 additions & 0 deletions __tests__/constancts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const ACCOUNT_ADDRESS = '0x4e5f6f6020ffa7e5761cbe96e932b7c1cbbcace70c0fe22767c5f0abf851a17';
export const PRIVATE_KEY = '0x0529868a6dc774b36342024c207686af16e84fc157c5eeb3a9d31fa13c0d44c5';
37 changes: 12 additions & 25 deletions __tests__/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@ import { Account, Contract, ContractFactory, Provider, defaultProvider, ec, star
import { getSelectorFromName } from '../src/utils/hash';
import { BigNumberish, toBN } from '../src/utils/number';
import { compileCalldata } from '../src/utils/stark';
import {
compiledArgentAccount,
compiledErc20,
compiledMulticall,
compiledTypeTransformation,
} from './fixtures';
import { ACCOUNT_ADDRESS, PRIVATE_KEY } from './constancts';
import { compiledErc20, compiledMulticall, compiledTypeTransformation } from './fixtures';

describe('class Contract {}', () => {
const wallet = stark.randomAddress();
Expand Down Expand Up @@ -50,10 +46,8 @@ describe('class Contract {}', () => {
expect(res).toHaveProperty('signature');
});

test('estimate gas fee for `mint`', async () => {
const res = await erc20.estimateFee.mint(wallet, '10');
expect(res).toHaveProperty('amount');
expect(res).toHaveProperty('unit');
test('estimate gas fee for `mint` should fail when connected to the provider', async () => {
expect(erc20.estimateFee.mint(wallet, '10')).rejects.toThrow();
});

test('read initial balance of that account', async () => {
Expand Down Expand Up @@ -210,26 +204,13 @@ describe('class Contract {}', () => {
});

describe('Contract interaction with Account', () => {
const privateKey = stark.randomAddress();

const starkKeyPair = ec.getKeyPair(privateKey);
const starkKeyPub = ec.getStarkKey(starkKeyPair);
const starkKeyPair = ec.getKeyPair(PRIVATE_KEY);
let account: Account;
let erc20: Contract;
let erc20Address: string;

beforeAll(async () => {
const accountResponse = await defaultProvider.deployContract({
contract: compiledArgentAccount,
addressSalt: starkKeyPub,
});
const contract = new Contract(compiledArgentAccount.abi, accountResponse.address);
expect(accountResponse.code).toBe('TRANSACTION_RECEIVED');

const initializeResponse = await contract.initialize(starkKeyPub, '0');
expect(initializeResponse.code).toBe('TRANSACTION_RECEIVED');

account = new Account(defaultProvider, accountResponse.address, starkKeyPair);
account = new Account(defaultProvider, ACCOUNT_ADDRESS, starkKeyPair);

const erc20Response = await defaultProvider.deployContract({
contract: compiledErc20,
Expand Down Expand Up @@ -257,6 +238,12 @@ describe('class Contract {}', () => {
expect(erc20.providerOrAccount instanceof Account);
});

test('estimate gas fee for `mint`', async () => {
const res = await erc20.estimateFee.mint(wallet, '10');
expect(res).toHaveProperty('amount');
expect(res).toHaveProperty('unit');
});

test('read balance of wallet', async () => {
const { res } = await erc20.balance_of(account.address);

Expand Down
29 changes: 25 additions & 4 deletions src/account/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Abi,
AddTransactionResponse,
Call,
EstimateFeeResponse,
InvocationsDetails,
InvokeFunctionTransaction,
KeyPair,
Expand Down Expand Up @@ -44,6 +45,25 @@ export class Account extends Provider implements AccountInterface {
return toHex(toBN(result[0]));
}

public async estimateFee(calls: Call | Call[]): Promise<EstimateFeeResponse> {
const transactions = Array.isArray(calls) ? calls : [calls];
const nonce = await this.getNonce();
const signerDetails = {
walletAddress: this.address,
nonce: toBN(nonce),
maxFee: toBN('0'),
};
const signature = await this.signer.signTransaction(transactions, signerDetails);

const calldata = [...fromCallsToExecuteCalldata(transactions), signerDetails.nonce.toString()];
return this.fetchEndpoint('estimate_fee', undefined, {
contract_address: this.address,
entry_point_selector: getSelectorFromName('__execute__'),
calldata,
signature: bigNumberishArrayToDecimalStringArray(signature),
});
}

/**
* Invoke execute function in account contract
*
Expand All @@ -58,23 +78,24 @@ export class Account extends Provider implements AccountInterface {
transactionsDetail: InvocationsDetails = {}
): Promise<AddTransactionResponse> {
const transactions = Array.isArray(calls) ? calls : [calls];

const nonce = toBN(transactionsDetail.nonce ?? (await this.getNonce()));
const maxFee = transactionsDetail.maxFee ?? (await this.estimateFee(transactions)).amount;
const signerDetails = {
walletAddress: this.address,
nonce: toBN(transactionsDetail.nonce ?? (await this.getNonce())),
maxFee: toBN(transactionsDetail.maxFee ?? '0'),
nonce,
maxFee,
};

const signature = await this.signer.signTransaction(transactions, signerDetails, abis);

const calldata = [...fromCallsToExecuteCalldata(transactions), signerDetails.nonce.toString()];

return this.fetchEndpoint('add_transaction', undefined, {
type: 'INVOKE_FUNCTION',
contract_address: this.address,
entry_point_selector: getSelectorFromName('__execute__'),
calldata,
signature: bigNumberishArrayToDecimalStringArray(signature),
max_fee: toHex(toBN(maxFee)),
});
}

Expand Down
15 changes: 15 additions & 0 deletions src/account/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
AddTransactionResponse,
Call,
DeployContractPayload,
EstimateFeeResponse,
Invocation,
InvocationsDetails,
Signature,
} from '../types';
Expand All @@ -28,6 +30,19 @@ export abstract class AccountInterface extends ProviderInterface {
abi?: Abi
): Promise<AddTransactionResponse>;

/**
* Estimate Fee for a method on starknet
*
* @param invocation the invocation object containing:
* - contractAddress - the address of the contract
* - entrypoint - the entrypoint of the contract
* - calldata - (defaults to []) the calldata
* - signature - (defaults to []) the signature
*
* @returns response from addTransaction
*/
public abstract estimateFee(invocation: Invocation): Promise<EstimateFeeResponse>;

/**
* Invoke execute function in account contract
*
Expand Down
20 changes: 15 additions & 5 deletions src/contract/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ContractFunction,
FunctionAbi,
Invocation,
Overrides,
ParsedStruct,
Result,
StructAbi,
Expand Down Expand Up @@ -540,10 +541,12 @@ export class Contract implements ContractInterface {
}
return acc;
}, 0);
const signature = [];

const overrides: Overrides = {};
if (args.length === inputsLength + 1 && Array.isArray(args[args.length - 1])) {
signature.push(...args.pop());
Object.assign(overrides, args.pop());
}

if (args.length !== inputsLength) {
throw Error(
`Invalid number of arguments, expected ${inputsLength} arguments, but got ${args.length}`
Expand All @@ -558,11 +561,15 @@ export class Contract implements ContractInterface {
entrypoint: method,
};
if ('execute' in this.providerOrAccount) {
return this.providerOrAccount.execute(invocation);
return this.providerOrAccount.execute(invocation, undefined, {
maxFee: overrides.maxFee,
nonce: overrides.nonce,
});
}

return this.providerOrAccount.invokeFunction({
...invocation,
signature,
signature: overrides.signature || [],
});
}

Expand Down Expand Up @@ -602,7 +609,10 @@ export class Contract implements ContractInterface {
// validate method and args
this.validateMethodAndArgs('INVOKE', method, args);
const invocation = this.populateTransaction[method](...args);
return this.providerOrAccount.estimateFee(invocation);
if ('estimateFee' in this.providerOrAccount) {
return this.providerOrAccount.estimateFee(invocation);
}
throw Error('Contract must be connected to the account contract to estimate');
}

public populate(method: string, args: Array<any> = []): Invocation {
Expand Down
10 changes: 0 additions & 10 deletions src/provider/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
CompiledContract,
DeployContractPayload,
Endpoints,
EstimateFeeResponse,
GetBlockResponse,
GetCodeResponse,
GetContractAddressesResponse,
Expand Down Expand Up @@ -339,15 +338,6 @@ export class Provider implements ProviderInterface {
});
}

public estimateFee(invocation: Invocation): Promise<EstimateFeeResponse> {
return this.fetchEndpoint('estimate_fee', undefined, {
contract_address: invocation.contractAddress,
entry_point_selector: getSelectorFromName(invocation.entrypoint),
calldata: bigNumberishArrayToDecimalStringArray(invocation.calldata ?? []),
signature: bigNumberishArrayToDecimalStringArray(invocation.signature ?? []),
});
}

public async waitForTransaction(txHash: BigNumberish, retryInterval: number = 8000) {
let onchain = false;
await wait(retryInterval);
Expand Down
Loading

0 comments on commit 93e7dd9

Please sign in to comment.