Skip to content

Commit

Permalink
refactoring all repeated function bodies to the EVMAuthenticator supe…
Browse files Browse the repository at this point in the history
…r class
  • Loading branch information
Viterbo committed Oct 28, 2023
1 parent 9198a2a commit 192ecd4
Show file tree
Hide file tree
Showing 7 changed files with 450 additions and 602 deletions.
8 changes: 4 additions & 4 deletions src/antelope/stores/balances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const useBalancesStore = defineStore(store_name, {
// We need to get the value ready to overwrite immediately and therefore avoid the blink
const wrapTokens = chain_settings.getWrappedSystemToken();
const authenticator = account.authenticator as EVMAuthenticator;
const wrapBalance = await authenticator.getERC20TokenBalance(account.account, wrapTokens.address);
const wrapBalance = await authenticator.getERC20TokenBalance(account.account, wrapTokens.address as addressString);

// now we call the indexer
const newBalances = await chain_settings.getBalances(account.account);
Expand Down Expand Up @@ -166,7 +166,7 @@ export const useBalancesStore = defineStore(store_name, {
const authenticator = account.authenticator as EVMAuthenticator;
const promises = tokens
.filter(token => token.address !== chain_settings.getSystemToken().address)
.map(token => authenticator.getERC20TokenBalance(account.account, token.address)
.map(token => authenticator.getERC20TokenBalance(account.account, token.address as addressString)
.then((balanceBn: BigNumber) => {
this.processBalanceForToken(label, token, balanceBn);
}).catch((error) => {
Expand Down Expand Up @@ -303,7 +303,7 @@ export const useBalancesStore = defineStore(store_name, {
this.setWagmiTokenTransferConfig(config, label);
this.setWagmiSystemTokenTransferConfig(null, label);
},
async transferTokens(token: TokenClass, to: string, amount: BigNumber, memo?: string): Promise<TransactionResponse> {
async transferTokens(token: TokenClass, to: addressString, amount: BigNumber, memo?: string): Promise<TransactionResponse> {
const funcname = 'transferTokens';
this.trace(funcname, token, to, amount.toString(), memo);
const label = CURRENT_CONTEXT;
Expand Down Expand Up @@ -413,7 +413,7 @@ export const useBalancesStore = defineStore(store_name, {
settings: EVMChainSettings,
account: EvmAccountModel,
token: TokenClass,
to: string,
to: addressString,
amount: BigNumber,
): Promise<EvmTransactionResponse | SendTransactionResult> {
this.trace('transferEVMTokens', settings, account, token, to, amount.toString());
Expand Down
308 changes: 275 additions & 33 deletions src/antelope/wallets/authenticators/EVMAuthenticator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useChainStore } from 'src/antelope/stores/chain';
import { useEVMStore } from 'src/antelope/stores/evm';
import { createTraceFunction, isTracingAll, useFeedbackStore } from 'src/antelope/stores/feedback';
import { usePlatformStore } from 'src/antelope/stores/platform';
import { AntelopeError, EvmABI, EvmFunctionParam, EvmTransactionResponse, ExceptionError, TokenClass, addressString } from 'src/antelope/types';
import { AntelopeError, EvmABI, EvmFunctionParam, EvmTransactionResponse, ExceptionError, TokenClass, addressString, erc20Abi, escrowAbiWithdraw, stlosAbiDeposit, stlosAbiWithdraw, wtlosAbiDeposit, wtlosAbiWithdraw } from 'src/antelope/types';

export abstract class EVMAuthenticator {

Expand All @@ -21,41 +21,21 @@ export abstract class EVMAuthenticator {
this.trace = createTraceFunction(name);
useFeedbackStore().setDebug(name, isTracingAll());
}
abstract getName(): string;
abstract logout(): Promise<void>;
abstract getSystemTokenBalance(address: addressString | string): Promise<BigNumber>;
abstract getERC20TokenBalance(address: addressString | string, tokenAddress: addressString | string): Promise<BigNumber>;
abstract signCustomTransaction(contract: string, abi: EvmABI, parameters: EvmFunctionParam[], value?: BigNumber): Promise<EvmTransactionResponse | WriteContractResult>;
abstract transferTokens(token: TokenClass, amount: BigNumber, to: addressString | string): Promise<EvmTransactionResponse | SendTransactionResult | WriteContractResult>;
abstract prepareTokenForTransfer(token: TokenClass | null, amount: BigNumber, to: string): Promise<void>;
abstract wrapSystemToken(amount: BigNumber): Promise<EvmTransactionResponse | WriteContractResult>;
abstract unwrapSystemToken(amount: BigNumber): Promise<EvmTransactionResponse | WriteContractResult>;
abstract stakeSystemTokens(amount: BigNumber): Promise<EvmTransactionResponse | WriteContractResult>;
abstract unstakeSystemTokens(amount: BigNumber): Promise<EvmTransactionResponse | WriteContractResult>;
abstract withdrawUnstakedTokens(): Promise<EvmTransactionResponse | WriteContractResult>;
abstract isConnectedTo(chainId: string): Promise<boolean>;
abstract externalProvider(): Promise<ethers.providers.ExternalProvider>;
abstract web3Provider(): Promise<ethers.providers.Web3Provider>;
abstract getSigner(): Promise<ethers.Signer>;

// to easily clone the authenticator
abstract newInstance(label: string): EVMAuthenticator;

// indicates the authenticator is ready to transfer tokens
readyForTransfer(): boolean {
return true;
}

// returns the associated account address acording to the label
getAccountAddress(): addressString {
return useAccountStore().getAccount(this.label).account as addressString;
}

// returns the associated chain settings acording to the label
getChainSettings(): EVMChainSettings {
return (useChainStore().getChain(this.label).settings as EVMChainSettings);
/**
* This method should be redefined on the derived class to perform any logout action if needed.
*/
async logout(): Promise<void> {
this.trace('logout');
}

/**
* This method MUST be implemented on the derived class to perform any login action if needed.
* However, the subclass may call this super class implementation to ensure the chain to connect to
* is the same as the one the used wallet is connected to.
* @param network network to connect to
* @returns the account address of the user
*/
async login(network: string): Promise<addressString | null> {
this.trace('login', network);
const chain = useChainStore();
Expand Down Expand Up @@ -111,4 +91,266 @@ export abstract class EVMAuthenticator {
const correctChainId = useChainStore().getChain(this.label).settings.getChainId();
return this.isConnectedTo(correctChainId);
}

/**
* This method should be used to create a new instance of the same authenticator type but with a different label/context.
* @param label new label/context for the new instance which identifies the account and the connected network
*/
abstract newInstance(label: string): EVMAuthenticator;

// ----- getters -----
abstract getName(): string;

/**
* This method returns the balance of the system token for the given address using the authenticator's provider.
* The authenticator may reimplement this method to use a different provider if needed.
* @param address address of the account to get the balance of
* @returns the balance of the system token for the given address
*/
async getSystemTokenBalance(address: addressString | string): Promise<ethers.BigNumber> {
this.trace('getSystemTokenBalance', address);
try {
const provider = await this.web3Provider();
if (provider) {
return provider.getBalance(address);
} else {
throw new AntelopeError('antelope.evm.error_no_provider');
}
} catch (e) {
console.error('getSystemTokenBalance', e, address);
throw e;
}
}

/**
* This method returns the balance of the given ERC20 token for the given address using the authenticator's provider.
* The authenticator may reimplement this method to use a different provider if needed.
* @param address address of the account to get the balance of
* @param token address of the ERC20 token to get the balance of
* @returns the balance of the given ERC20 token for the given address
*/
async getERC20TokenBalance(address: addressString | string, token: addressString): Promise<ethers.BigNumber> {
this.trace('getERC20TokenBalance', [address, token]);
try {
const provider = await this.web3Provider();
if (provider) {
const erc20Contract = new ethers.Contract(token, erc20Abi, provider);
const balance = await erc20Contract.balanceOf(address);
return balance;
} else {
throw new AntelopeError('antelope.evm.error_no_provider');
}
} catch (e) {
console.error('getERC20TokenBalance', e, address, token);
throw e;
}
}

abstract isConnectedTo(chainId: string): Promise<boolean>;
abstract externalProvider(): Promise<ethers.providers.ExternalProvider>;
abstract web3Provider(): Promise<ethers.providers.Web3Provider>;

async getSigner(): Promise<ethers.Signer> {
const web3Provider = await this.web3Provider();
return web3Provider.getSigner();
}

// Common auxiliary functions -----

/**
* This is a simple getter for the associated account address according to the label
* @returns the account address
*/
getAccountAddress(): addressString {
return useAccountStore().getAccount(this.label).account as addressString;
}

/**
* This is a simple getter for the associated chain settings according to the label
*/
getChainSettings(): EVMChainSettings {
return (useChainStore().getChain(this.label).settings as EVMChainSettings);
}

// Support for wagmi particularities ---

/**
* This method prepares the token for transfer.
* TODO: This is only needed for WalletConnect. We need to refactor this implementation to remove this particularity.
* @param token identifier of the token to transfer
* @param amount amount of tokens to transfer
* @param to address of the recipient
*/
async prepareTokenForTransfer(token: TokenClass | null, amount: ethers.BigNumber, to: string): Promise<void> {
this.trace('prepareTokenForTransfer', [token], amount, to);
}

/**
* This method indicates the authenticator is ready to transfer tokens.
* TODO: This is only needed for WalletConnect. We need to refactor this implementation to remove this particularity.
*/
readyForTransfer(): boolean {
return true;
}

// ----- Signing transactions -----
/**
* This is the main method to sign any transaction and MUST be implemented on the derived class.
* @param contract address of the contract to be called
* @param abi ABI of the specific function on the contract to be called
* @param parameters list of parameters to be passed to the function
* @param value amount of system tokens to send with the transaction if any
*/
abstract signCustomTransaction(contract: string, abi: EvmABI, parameters: EvmFunctionParam[], value?: BigNumber): Promise<EvmTransactionResponse | WriteContractResult>;

/**
* This method is used to send system tokens and MUST be implemented on the derived class since it depends on the authenticator.
* @param to address of the recipient
* @param value amount of system tokens to send
*/
abstract sendSystemToken(to: string, value: ethers.BigNumber): Promise<EvmTransactionResponse | SendTransactionResult | WriteContractResult>;

/**
* This method creates a Transaction to transfer (system of ERC20) tokens
* @param token identifier of the token to transfer
* @param amount amount of tokens to transfer
* @param to address of the recipient
* @returns transaction response with the hash and a wait() method to wait confirmation
*/
async transferTokens(token: TokenClass, amount: ethers.BigNumber, to: addressString): Promise<ethers.providers.TransactionResponse | WriteContractResult | SendTransactionResult> {
this.trace('transferTokens', token, amount, to);

// prepare variables
const value = amount.toHexString();
const transferAbi = erc20Abi.filter(abi => abi.name === 'transfer');

if (token.isSystem) {
return await this.sendSystemToken(to, amount);
} else {
return this.signCustomTransaction(
token.address,
transferAbi,
[to, value],
);
}
}

/**
* This method creates a Transaction to wrap system tokens into ERC20 tokens
* @param amount amount of system tokens to wrap
* @returns transaction response with the hash and a wait() method to wait confirmation
*/
async wrapSystemToken(amount: BigNumber): Promise<EvmTransactionResponse | WriteContractResult> {
this.trace('wrapSystemToken', amount);

// prepare variables
const chainSettings = this.getChainSettings();
const wrappedSystemTokenContractAddress = chainSettings.getWrappedSystemToken().address as addressString;

return this.signCustomTransaction(
wrappedSystemTokenContractAddress,
wtlosAbiDeposit,
[],
amount,
).catch((error) => {
throw this.handleCatchError(error as never);
});
}

/**
* This method creates a Transaction to unwrap ERC20 tokens into system tokens
* @param amount amount of system tokens to unwrap
* @returns transaction response with the hash and a wait() method to wait confirmation
*/
async unwrapSystemToken(amount: BigNumber): Promise<EvmTransactionResponse | WriteContractResult> {
this.trace('unwrapSystemToken', amount.toString());

// prepare variables
const chainSettings = this.getChainSettings();
const wrappedSystemTokenContractAddress = chainSettings.getWrappedSystemToken().address as addressString;
const value = amount.toHexString();

return this.signCustomTransaction(
wrappedSystemTokenContractAddress,
wtlosAbiWithdraw,
[value],
).catch((error) => {
throw this.handleCatchError(error as never);
});
}

/**
* This method creates a Transaction to stake system tokens
* @param amount amount of system tokens to stake
* @returns transaction response with the hash and a wait() method to wait confirmation
*/
async stakeSystemTokens(amount: BigNumber): Promise<EvmTransactionResponse | WriteContractResult> {
this.trace('stakeSystemTokens', amount.toString());

// prepare variables
const chainSettings = this.getChainSettings();
const stakedSystemTokenContractAddress = chainSettings.getStakedSystemToken().address as addressString;

return this.signCustomTransaction(
stakedSystemTokenContractAddress,
stlosAbiDeposit,
[],
amount,
).catch((error) => {
throw this.handleCatchError(error as never);
});
}

/**
* This method creates a Transaction to unstake system tokens
* @param amount amount of system tokens to unstake
* @returns transaction response with the hash and a wait() method to wait confirmation
*/
async unstakeSystemTokens(amount: BigNumber): Promise<EvmTransactionResponse | WriteContractResult> {
this.trace('unstakeSystemTokens', amount.toString());

// prepare variables
const chainSettings = this.getChainSettings();
const stakedSystemTokenContractAddress = chainSettings.getStakedSystemToken().address as addressString;
const value = amount.toHexString();
const from = this.getAccountAddress();

return this.signCustomTransaction(
stakedSystemTokenContractAddress,
stlosAbiWithdraw,
[value, from, from],
).catch((error) => {
throw this.handleCatchError(error as never);
});
}

/**
* This method creates a Transaction to withdraw all unblocked staked tokens
* @returns transaction response with the hash and a wait() method to wait confirmation
*/
async withdrawUnstakedTokens() : Promise<EvmTransactionResponse | WriteContractResult> {
this.trace('withdrawUnstakedTokens');

// prepare variables
const chainSettings = this.getChainSettings();
const escrowContractAddress = chainSettings.getEscrowContractAddress();

return this.signCustomTransaction(
escrowContractAddress,
escrowAbiWithdraw,
[],
).catch((error) => {
throw this.handleCatchError(error as never);
});
}

/**
* This method creates and throws an AntelopeError with the corresponding message.
* It is useful to handle specific error codes that may indicate a particular known error situation.
* Also is useful to detect when the user cancelled the transaction, which should be handled as a rejection instead of an error.
* @param error catch error to be handled
*/
abstract handleCatchError(error: never): AntelopeError;


}
Loading

0 comments on commit 192ecd4

Please sign in to comment.