Skip to content
This repository has been archived by the owner on Dec 13, 2024. It is now read-only.

Tx factories gas estimation #126

Merged
merged 6 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0",
"ethers": "^5.6.2",
"ethers": "^5.7.2",
"graphql": "^16.6.0",
"mocha": "^10.0.0",
"nodemon": "^2.0.16",
Expand Down
156 changes: 78 additions & 78 deletions src/AddressDriver/AddressDriverClient.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-dupe-class-members */
import type { Provider } from '@ethersproject/providers';
import type { BigNumberish, ContractTransaction, Signer } from 'ethers';
import { ethers, BigNumber, constants } from 'ethers';
Expand All @@ -15,24 +16,20 @@ import Utils from '../utils';
import { DripsErrors } from '../common/DripsError';
import type { AddressDriver } from '../../contracts';
import { IERC20__factory, AddressDriver__factory } from '../../contracts';
import {
nameOf,
isNullOrUndefined,
formatDripsReceivers,
formatSplitReceivers,
ensureSignerExists,
createFromStrings
} from '../common/internals';
import { nameOf, isNullOrUndefined, ensureSignerExists, createFromStrings } from '../common/internals';
import type { IAddressDriverTxFactory } from './AddressDriverTxFactory';
import AddressDriverTxFactory from './AddressDriverTxFactory';

/**
* A client for managing Drips accounts identified by Ethereum addresses.
* @see {@link https://github.com/radicle-dev/drips-contracts/blob/master/src/AddressDriver.sol AddressDriver} contract.
*/
export default class AddressDriverClient {
#provider!: Provider;
#driver!: AddressDriver;
#driverAddress!: string;
#provider!: Provider;
#signer: Signer | undefined;
#txFactory!: IAddressDriverTxFactory;

/** Returns the client's `provider`. */
public get provider(): Provider {
Expand All @@ -58,54 +55,67 @@ export default class AddressDriverClient {
// TODO: Update the supported chains documentation comments.
/**
* Creates a new immutable `AddressDriverClient` instance.
* @param {Provider} provider The network provider. It cannot be changed after creation.
* @param provider The network provider. It cannot be changed after creation.
*
* The `provider` must be connected to one of the following supported networks:
* - 'goerli': chain ID `5`
* - 'polygon-mumbai': chain ID `80001`
* @param {Signer} signer The singer used to sign transactions. It cannot be changed after creation.
* @param signer The singer used to sign transactions. It cannot be changed after creation.
*
* **Important**: If the `signer` is _not_ connected to a provider it will try to connect to the `provider`, else it will use the `signer.provider`.
* @param {string|undefined} customDriverAddress Overrides the `NFTDriver` contract address.
* @param customDriverAddress Overrides the `AddressDriver` contract address.
* If it's `undefined` (default value), the address will be automatically selected based on the `provider`'s network.
* @returns A `Promise` which resolves to the new client instance.
* @throws {@link DripsErrors.clientInitializationError} if the client initialization fails.
* @throws {@link DripsErrors.initializationError} if the client initialization fails.
*/
public static async create(
provider: Provider,
signer?: Signer,
customDriverAddress?: string
): Promise<AddressDriverClient>;
public static async create(
provider: Provider,
signer?: Signer,
customDriverAddress?: string,
txFactory?: IAddressDriverTxFactory
): Promise<AddressDriverClient>;
public static async create(
provider: Provider,
signer?: Signer,
customDriverAddress?: string,
txFactory?: IAddressDriverTxFactory
): Promise<AddressDriverClient> {
try {
await validateClientProvider(provider, Utils.Network.SUPPORTED_CHAINS);
await validateClientProvider(provider, Utils.Network.SUPPORTED_CHAINS);

if (signer) {
await validateClientSigner(signer);

if (!signer.provider) {
// eslint-disable-next-line no-param-reassign
signer = signer.connect(provider);
}
if (signer) {
if (!signer.provider) {
// eslint-disable-next-line no-param-reassign
signer = signer.connect(provider);
}

const network = await provider.getNetwork();
const driverAddress = customDriverAddress ?? Utils.Network.configs[network.chainId].CONTRACT_ADDRESS_DRIVER;
await validateClientSigner(signer, Utils.Network.SUPPORTED_CHAINS);
}

const client = new AddressDriverClient();
const network = await provider.getNetwork();
const driverAddress = customDriverAddress ?? Utils.Network.configs[network.chainId].CONTRACT_ADDRESS_DRIVER;

client.#signer = signer;
client.#provider = provider;
client.#driverAddress = driverAddress;
client.#driver = AddressDriver__factory.connect(driverAddress, signer ?? provider);
return client;
} catch (error: any) {
throw DripsErrors.clientInitializationError(`Could not create 'AddressDriverClient': ${error.message}`);
const client = new AddressDriverClient();

client.#signer = signer;
client.#provider = provider;
client.#driverAddress = driverAddress;
client.#driver = AddressDriver__factory.connect(driverAddress, signer ?? provider);

if (signer) {
client.#txFactory = txFactory || (await AddressDriverTxFactory.create(signer, customDriverAddress));
}

return client;
}

/**
* Returns the remaining number of tokens the `AddressDriver` contract is allowed to spend on behalf of the user for the given ERC20 token.
* @param {string} tokenAddress The ERC20 token address.
* @param tokenAddress The ERC20 token address.
*
* It must preserve amounts, so if some amount of tokens is transferred to
* an address, then later the same amount must be transferrable from that address.
Expand All @@ -131,7 +141,7 @@ export default class AddressDriverClient {

/**
* Sets the maximum allowance value for the `AddressDriver` contract over the user's tokens for the given ERC20 token.
* @param {string} tokenAddress The ERC20 token address.
* @param tokenAddress The ERC20 token address.
*
* It must preserve amounts, so if some amount of tokens is transferred to
* an address, then later the same amount must be transferrable from that address.
Expand Down Expand Up @@ -170,7 +180,7 @@ export default class AddressDriverClient {

/**
* Returns the user ID for a given address.
* @param {string} userAddress The user address.
* @param userAddress The user address.
* @returns A `Promise` which resolves to the user ID.
* @throws {@link DripsErrors.addressError} if the `userAddress` address is not valid.
*/
Expand All @@ -184,14 +194,14 @@ export default class AddressDriverClient {

/**
* Collects the received and already split funds and transfers them from the `DripsHub` contract to an address.
* @param {string} tokenAddress The ERC20 token address.
* @param tokenAddress The ERC20 token address.
*
* It must preserve amounts, so if some amount of tokens is transferred to
* an address, then later the same amount must be transferrable from that address.
* Tokens which rebase the holders' balances, collect taxes on transfers,
* or impose any restrictions on holding or transferring tokens are not supported.
* If you use such tokens in the protocol, they can get stuck or lost.
* @param {string} transferToAddress The address to send collected funds to.
* @param transferToAddress The address to send collected funds to.
* @returns A `Promise` which resolves to the contract transaction.
* @throws {@link DripsErrors.addressError} if `tokenAddress` or `transferToAddress` is not valid.
* @throws {@link DripsErrors.signerMissingError} if the provider's signer is missing.
Expand All @@ -200,29 +210,31 @@ export default class AddressDriverClient {
ensureSignerExists(this.#signer);
validateCollectInput(tokenAddress, transferToAddress);

return this.#driver.collect(tokenAddress, transferToAddress);
const tx = await this.#txFactory.collect(tokenAddress, transferToAddress);

return this.#signer.sendTransaction(tx);
}

/**
* Gives funds to the receiver.
* The receiver can collect them immediately.
* Transfers funds from the user's wallet to the `DripsHub` contract.
* @param {string} receiverUserId The receiver user ID.
* @param {string} tokenAddress The ERC20 token address.
* @param receiverUserId The receiver user ID.
* @param tokenAddress The ERC20 token address.
*
* It must preserve amounts, so if some amount of tokens is transferred to
* an address, then later the same amount must be transferrable from that address.
* Tokens which rebase the holders' balances, collect taxes on transfers,
* or impose any restrictions on holding or transferring tokens are not supported.
* If you use such tokens in the protocol, they can get stuck or lost.
* @param {BigNumberish} amount The amount to give (in the smallest unit, e.g., Wei). It must be greater than `0`.
* @param amount The amount to give (in the smallest unit, e.g., Wei). It must be greater than `0`.
* @returns A `Promise` which resolves to the contract transaction.
* @throws {@link DripsErrors.argumentMissingError} if the `receiverUserId` is missing.
* @throws {@link DripsErrors.addressError} if the `tokenAddress` is not valid.
* @throws {@link DripsErrors.argumentError} if the `amount` is less than or equal to `0`.
* @throws {@link DripsErrors.signerMissingError} if the provider's signer is missing.
*/
public give(receiverUserId: string, tokenAddress: string, amount: BigNumberish): Promise<ContractTransaction> {
public async give(receiverUserId: string, tokenAddress: string, amount: BigNumberish): Promise<ContractTransaction> {
ensureSignerExists(this.#signer);

if (isNullOrUndefined(receiverUserId)) {
Expand All @@ -242,12 +254,14 @@ export default class AddressDriverClient {
);
}

return this.#driver.give(receiverUserId, tokenAddress, amount);
const tx = await this.#txFactory.give(receiverUserId, tokenAddress, amount);

return this.#signer.sendTransaction(tx);
}

/**
* Sets the Splits configuration.
* @param {SplitsReceiverStruct[]} receivers The splits receivers (max `200`).
* @param receivers The splits receivers (max `200`).
* Each splits receiver will be getting `weight / TOTAL_SPLITS_WEIGHT` share of the funds.
* Duplicate receivers are not allowed and will only be processed once.
* Pass an empty array if you want to clear all receivers.
Expand All @@ -257,11 +271,13 @@ export default class AddressDriverClient {
* @throws {@link DripsErrors.splitsReceiverError} if any of the `receivers` is not valid.
* @throws {@link DripsErrors.signerMissingError} if the provider's signer is missing.
*/
public setSplits(receivers: SplitsReceiverStruct[]): Promise<ContractTransaction> {
public async setSplits(receivers: SplitsReceiverStruct[]): Promise<ContractTransaction> {
ensureSignerExists(this.#signer);
validateSplitsReceivers(receivers);

return this.#driver.setSplits(formatSplitReceivers(receivers));
const tx = await this.#txFactory.setSplits(receivers);

return this.#signer.sendTransaction(tx);
}

/**
Expand All @@ -274,15 +290,15 @@ export default class AddressDriverClient {
* Tokens which rebase the holders' balances, collect taxes on transfers,
* or impose any restrictions on holding or transferring tokens are not supported.
* If you use such tokens in the protocol, they can get stuck or lost.
* @param {DripsReceiverStruct[]} currentReceivers The drips receivers that were set in the last drips update.
* @param currentReceivers The drips receivers that were set in the last drips update.
* Pass an empty array if this is the first update.
*
* **Tip**: you might want to use `DripsSubgraphClient.getCurrentDripsReceivers` to easily retrieve the list of current receivers.
* @param {DripsReceiverStruct[]} newReceivers The new drips receivers (max `100`).
* @param newReceivers The new drips receivers (max `100`).
* Duplicate receivers are not allowed and will only be processed once.
* Pass an empty array if you want to clear all receivers.
* @param {string} transferToAddress The address to send funds to in case of decreasing balance.
* @param {BigNumberish} balanceDelta The drips balance change to be applied:
* @param transferToAddress The address to send funds to in case of decreasing balance.
* @param balanceDelta The drips balance change to be applied:
* - Positive to add funds to the drips balance.
* - Negative to remove funds from the drips balance.
* - `0` to leave drips balance as is (default value).
Expand Down Expand Up @@ -316,59 +332,43 @@ export default class AddressDriverClient {
balanceDelta
);

const formattedCurrentReceivers = formatDripsReceivers(currentReceivers);
const formattedNewReceivers = formatDripsReceivers(newReceivers);

const estimatedGasFees = (
await this.#driver.estimateGas.setDrips(
tokenAddress,
formattedCurrentReceivers,
balanceDelta,
formattedNewReceivers,
0,
0,
transferToAddress
)
).toNumber();

const gasLimit = Math.ceil(estimatedGasFees + estimatedGasFees * 0.2);

return this.#driver.setDrips(
const tx = await this.#txFactory.setDrips(
tokenAddress,
formattedCurrentReceivers,
currentReceivers,
balanceDelta,
formattedNewReceivers,
newReceivers,
0,
0,
transferToAddress,
{
gasLimit
}
transferToAddress
);

return this.#signer.sendTransaction(tx);
}

/**
* Emits the user's metadata.
* The key and the value are _not_ standardized by the protocol, it's up to the user to establish and follow conventions to ensure compatibility with the consumers.
* @param {UserMetadata[]} userMetadata The list of user metadata. Note that a metadata `key` needs to be 32bytes.
* @param userMetadata The list of user metadata. Note that a metadata `key` needs to be 32bytes.
*
* **Tip**: you might want to use `Utils.UserMetadata.createFromStrings` to easily create metadata instances from `string` inputs.
* @returns A `Promise` which resolves to the contract transaction.
* @throws {@link DripsErrors.argumentError} if any of the metadata entries is not valid.
* @throws {@link DripsErrors.signerMissingError} if the provider's signer is missing.
*/
public emitUserMetadata(userMetadata: UserMetadata[]): Promise<ContractTransaction> {
public async emitUserMetadata(userMetadata: UserMetadata[]): Promise<ContractTransaction> {
ensureSignerExists(this.#signer);
validateEmitUserMetadataInput(userMetadata);

const userMetadataAsBytes = userMetadata.map((m) => createFromStrings(m.key, m.value));

return this.#driver.emitUserMetadata(userMetadataAsBytes);
const tx = await this.#txFactory.emitUserMetadata(userMetadataAsBytes);

return this.#signer.sendTransaction(tx);
}

/**
* Returns a user's address given a user ID.
* @param {string} userId The user ID.
* @param userId The user ID.
* @returns The user's address.
*/
public static getUserAddress = (userId: string): string => {
Expand Down
Loading