Skip to content

Commit

Permalink
feat: add scripts to manage collector tokens (#125)
Browse files Browse the repository at this point in the history
* feat: add hardhat scripts to add/remove/get tokens from the collector
* doc: update readme to include task description
* feat: change the removeToken script not to require the token index
  • Loading branch information
antomor authored Feb 27, 2023
1 parent 3d14eb0 commit db853e9
Show file tree
Hide file tree
Showing 10 changed files with 362 additions and 4 deletions.
49 changes: 49 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,55 @@ Example:
npx hardhat collector:change-partners --collector-address "<0x9b91c655AaE10E6cd0a941Aa90A6e7aa97FB02F4" --partner-config "partner-shares.json" --gas-limit "200000" --network regtest
```

#### Add collector token

Pre-requirements:
- the collector we want to change must be deployed. See the [related section](#collector-deployment) to deploy a collector.

Usage:

```bash
npx hardhat collector:addToken --collector-address "<collector_address>" --token-address "<token_address>" --network regtest
```

Example:
```bash
npx hardhat collector:addToken --collector-address "0xeFb80DB9E2d943A492Bd988f4c619495cA815643" --token-address "0xfD1dda8C3BC734Bc1C8e71F69F25BFBEe9cE9535" --network regtest
```

#### Get collector tokens

Pre-requirements:
- the collector we want to change must be deployed. See the [related section](#collector-deployment) to deploy a collector.

Usage:

```bash
npx hardhat collector:getTokens --collector-address "<collector_address>" --network regtest
```

Example:
```bash
npx hardhat collector:getTokens --collector-address "0xeFb80DB9E2d943A492Bd988f4c619495cA815643" --network regtest
```

#### Remove collector token

Pre-requirements:
- the collector we want to change must be deployed. See the [related section](#collector-deployment) to deploy a collector;
- the token that we want to remove should have no balance for the collector we're modifying;

Usage:

```bash
npx hardhat collector:removeToken --collector-address "<collector_address>" --token-address "<token_address>" --network regtest
```

Example:
```bash
npx hardhat collector:removeToken --collector-address "0xeFb80DB9E2d943A492Bd988f4c619495cA815643" --token-address "0x39B12C05E8503356E3a7DF0B7B33efA4c054C409" --network regtest
```

### Addresses

Each time the smart contracts are deployed, the `contract-addresses.json` file is updated. This file contains all contracts addresses for the network they were selected to be deployed on.
Expand Down
48 changes: 46 additions & 2 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,22 @@ import {
ChangePartnerSharesArg,
} from './tasks/changePartnerShares';
import { deploy } from './tasks/deploy';
import { deployCollector, DeployCollectorArg } from './tasks/deployCollector';
import {
deployCollector,
DeployCollectorArg,
} from './tasks/collector/deployCollector';
import { getAllowedTokens } from './tasks/getAllowedTokens';
import { removeTokens } from './tasks/removeTokens';
import { withdraw, WithdrawSharesArg } from './tasks/withdraw';
import {
addTokenToCollector,
ManageCollectorTokenArgs,
} from './tasks/collector/addToken';
import {
getCollectorTokens,
GetCollectorTokensArgs,
} from './tasks/collector/getTokens';
import { removeTokenFromCollector } from './tasks/collector/removeToken';
dotenv.config();

const DEFAULT_MNEMONIC =
Expand Down Expand Up @@ -148,14 +160,46 @@ task('collector:withdraw', 'Withdraws funds from a collector contract')
await withdraw(taskArgs, hre);
});

task(
'collector:addToken',
'Allow the collector to receive payments in additional tokens'
)
.addParam('collectorAddress', 'address of the collector contract to modify')
.addParam(
'tokenAddress',
'address of the token we want to allow in the collector'
)
.setAction(async (taskArgs: ManageCollectorTokenArgs, hre) => {
await addTokenToCollector(taskArgs, hre);
});

task('collector:getTokens', 'Retrieve tokens managed by the collector')
.addParam('collectorAddress', 'address of the collector contract to modify')
.setAction(async (taskArgs: GetCollectorTokensArgs, hre) => {
await getCollectorTokens(taskArgs, hre);
});

task(
'collector:removeToken',
'Remove a token from the ones that the collector can accept to receive payments'
)
.addParam('collectorAddress', 'address of the collector contract to modify')
.addParam(
'tokenAddress',
'address of the token we want to remove in the collector'
)
.setAction(async (taskArgs: ManageCollectorTokenArgs, hre) => {
await removeTokenFromCollector(taskArgs, hre);
});

task('remove-tokens', 'Removes a list of tokens')
.addPositionalParam('tokenlist', 'list of tokens')
.setAction(async (taskArgs: { tokenlist: string }, hre) => {
await removeTokens(taskArgs, hre);
});

task('collector:change-partners', 'Change collector partners')
.addParam('collectorAddress', 'address of the collector we want to modify')
.addParam('collectorAddress', 'address of the collector contract to modify')
.addParam(
'partnerConfig',
'path of the file that includes the partner shares configuration'
Expand Down
22 changes: 22 additions & 0 deletions tasks/collector/addToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';

export type ManageCollectorTokenArgs = {
collectorAddress: string;
tokenAddress: string;
};

export const addTokenToCollector = async (
{ collectorAddress, tokenAddress }: ManageCollectorTokenArgs,
{ ethers }: HardhatRuntimeEnvironment
) => {
const collector = await ethers.getContractAt('Collector', collectorAddress);

try {
await collector.addToken(tokenAddress);
} catch (error) {
console.error(
`Error adding token with address ${tokenAddress} to allowed tokens on Collector ${collectorAddress}`
);
throw error;
}
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as fs from 'fs';

import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { Collector } from '../typechain-types';
import { Collector } from '../../typechain-types';

export const DEFAULT_CONFIG_FILE_NAME = 'deploy-collector.input.json';
export const DEFAULT_OUTPUT_FILE_NAME = 'revenue-sharing-addresses.json';
Expand Down
22 changes: 22 additions & 0 deletions tasks/collector/getTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { ManageCollectorTokenArgs } from './addToken';

export type GetCollectorTokensArgs = Omit<
ManageCollectorTokenArgs,
'tokenAddress'
>;

export const getCollectorTokens = async (
{ collectorAddress }: GetCollectorTokensArgs,
{ ethers }: HardhatRuntimeEnvironment
) => {
const collector = await ethers.getContractAt('Collector', collectorAddress);

try {
const tokens = await collector.getTokens();
console.log('Allowed Tokens:', tokens);
} catch (error) {
console.error(`Error retrieving tokens from Collector ${collectorAddress}`);
throw error;
}
};
26 changes: 26 additions & 0 deletions tasks/collector/removeToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { ManageCollectorTokenArgs } from './addToken';

export const removeTokenFromCollector = async (
{ collectorAddress, tokenAddress }: ManageCollectorTokenArgs,
{ ethers }: HardhatRuntimeEnvironment
) => {
const collector = await ethers.getContractAt('Collector', collectorAddress);

try {
const tokens = await collector.getTokens();
const tokenIndex = tokens.findIndex((token) => token === tokenAddress);
if (tokenIndex < 0) {
throw new Error(
`Token with address ${tokenAddress} not found. Please verify the tokens managed by the Collector ${collectorAddress}`
);
}
console.log(`Token found with index ${tokenIndex}`);
await collector.removeToken(tokenAddress, tokenIndex);
} catch (error) {
console.error(
`Error removing token with address ${tokenAddress} from Collector ${collectorAddress}`
);
throw error;
}
};
54 changes: 54 additions & 0 deletions test/tasks/collector/addToken.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { expect, use } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import * as hre from 'hardhat';
import { ethers } from 'hardhat';
import sinon from 'sinon';
import {
ManageCollectorTokenArgs,
addTokenToCollector,
} from '../../../tasks/collector/addToken';
import { Collector } from '../../../typechain-types';

use(chaiAsPromised);

describe('Script to add tokens to collector', function () {
describe('addToken', function () {
const taskArgs: ManageCollectorTokenArgs = {
collectorAddress: '0x06c85B7EA1AA2d030E1a747B3d8d15D5845fd714',
tokenAddress: '0x145845fd06c85B7EA1AA2d030E1a747B3d8d15D7',
};

afterEach(function () {
sinon.restore();
});

it('should add a token when no tokens are managed', async function () {
const addToken = sinon.spy();
const fakeCollector = {
addToken,
} as unknown as Collector;
sinon.stub(ethers, 'getContractAt').resolves(fakeCollector);
await expect(
addTokenToCollector(taskArgs, hre),
'addTokenToCollector rejected'
).not.to.be.rejected;
expect(addToken.called, 'Collector.addToken was not called').to.be.true;
});

it('should fail if the token is already managed', async function () {
const expectedError = new Error('Token already managed');
const addToken = sinon.spy(() => {
throw expectedError;
});
const fakeCollector = {
addToken,
} as unknown as Collector;
sinon.stub(ethers, 'getContractAt').resolves(fakeCollector);
await expect(
addTokenToCollector(taskArgs, hre),
'addTokenToCollector did not reject'
).to.be.rejectedWith(expectedError);
expect(addToken.called, 'Collector.addToken was not called').to.be.true;
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
deployCollector,
DeployCollectorArg,
OutputConfig,
} from '../../tasks/deployCollector';
} from '../../../tasks/collector/deployCollector';

use(smock.matchers);
use(chaiAsPromised);
Expand Down
63 changes: 63 additions & 0 deletions test/tasks/collector/getTokens.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect, use } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import * as hre from 'hardhat';
import { ethers } from 'hardhat';
import sinon, { SinonSpy } from 'sinon';
import {
getCollectorTokens,
GetCollectorTokensArgs,
} from '../../../tasks/collector/getTokens';
import { Collector } from '../../../typechain-types';

use(chaiAsPromised);

describe('Script to retrieve the collector tokens', function () {
describe('getTokens', function () {
const taskArgs: GetCollectorTokensArgs = {
collectorAddress: '0x06c85B7EA1AA2d030E1a747B3d8d15D5845fd714',
};
const tokens = ['0x123abc', '0xabc123'];
let consoleLogSpy: SinonSpy;

beforeEach(function () {
consoleLogSpy = sinon.spy(console, 'log');
});

afterEach(function () {
sinon.restore();
});

it('should get the tokens managed by the collector', async function () {
const getTokens = sinon.stub().returns(Promise.resolve(tokens));
const fakeCollector = {
getTokens,
} as unknown as Collector;
sinon.stub(ethers, 'getContractAt').resolves(fakeCollector);
await expect(
getCollectorTokens(taskArgs, hre),
'getCollectorTokens rejected'
).not.to.be.rejected;
expect(getTokens.called, 'Collector.getTokens was not called').to.be.true;
expect(
consoleLogSpy.calledWithExactly('Allowed Tokens:', tokens),
'Console.log was not called with the expected arguments'
).to.be.true;
});

it('should fail if the getTokens task raises an error', async function () {
const expectedError = new Error('Token already managed');
const getTokens = sinon.spy(() => {
throw expectedError;
});
const stubContract = {
getTokens,
} as unknown as Collector;
sinon.stub(ethers, 'getContractAt').resolves(stubContract);
await expect(
getCollectorTokens(taskArgs, hre),
'getCollectorTokens did not reject'
).to.be.rejectedWith(expectedError);
expect(consoleLogSpy.called, 'Console.log was called').to.be.false;
});
});
});
Loading

0 comments on commit db853e9

Please sign in to comment.