Skip to content

Commit e227ed7

Browse files
committed
feat: add Toggle Freeze Transfers procedure
1 parent cc1b21a commit e227ed7

File tree

4 files changed

+262
-0
lines changed

4 files changed

+262
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { TransferStatusCode } from '@polymathnetwork/contract-wrappers';
2+
import { Procedure } from './Procedure';
3+
import {
4+
ToggleFreezeTransfersProcedureArgs,
5+
ErrorCode,
6+
ProcedureType,
7+
PolyTransactionTag,
8+
} from '../types';
9+
import { PolymathError } from '../PolymathError';
10+
import { SecurityToken } from '../entities';
11+
import { Factories } from '../Context';
12+
13+
/**
14+
* Procedure to transfer security tokens.
15+
*/
16+
export class ToggleFreezeTransfers extends Procedure<ToggleFreezeTransfersProcedureArgs> {
17+
public type = ProcedureType.ToggleFreezeTransfers;
18+
19+
public async prepareTransactions() {
20+
const { symbol, freeze } = this.args;
21+
const { contractWrappers, currentWallet, factories } = this.context;
22+
23+
let securityToken;
24+
25+
try {
26+
securityToken = await contractWrappers.tokenFactory.getSecurityTokenInstanceFromTicker(
27+
symbol
28+
);
29+
} catch (err) {
30+
throw new PolymathError({
31+
code: ErrorCode.ProcedureValidationError,
32+
message: `There is no Security Token with symbol ${symbol}`,
33+
});
34+
}
35+
36+
const owner = await securityToken.owner();
37+
const account = await currentWallet.address();
38+
39+
if (account !== owner) {
40+
throw new PolymathError({
41+
code: ErrorCode.ProcedureValidationError,
42+
message: `You must be the owner of this Security Token to ${
43+
freeze ? 'freeze' : 'unfreeze'
44+
} the transfers`,
45+
});
46+
}
47+
48+
const isFrozen = await securityToken.transfersFrozen();
49+
50+
if (isFrozen === freeze) {
51+
throw new PolymathError({
52+
code: ErrorCode.ProcedureValidationError,
53+
message: `Security Token Transfers are already ${freeze ? 'frozen' : 'unfrozen'}`,
54+
});
55+
}
56+
57+
await this.addTransaction(
58+
freeze ? securityToken.freezeTransfers : securityToken.unfreezeTransfers,
59+
{
60+
tag: PolyTransactionTag.ToggleFreezeTransfers,
61+
resolver: createToggleFreezeTransfersResolver(factories, symbol),
62+
}
63+
)({});
64+
}
65+
}
66+
67+
export const createToggleFreezeTransfersResolver = (
68+
factories: Factories,
69+
symbol: string
70+
) => async () => {
71+
await factories.securityTokenFactory.refresh(SecurityToken.generateId({ symbol }));
72+
};
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { ImportMock, MockManager } from 'ts-mock-imports';
2+
import { spy, restore } from 'sinon';
3+
import * as contractWrappersModule from '@polymathnetwork/contract-wrappers';
4+
import { ToggleFreezeTransfers } from '../../procedures/ToggleFreezeTransfers';
5+
import { Procedure } from '../../procedures/Procedure';
6+
import * as toggleFreezeTransferModule from '../../procedures/ToggleFreezeTransfers';
7+
import { PolymathError } from '../../PolymathError';
8+
import {
9+
ErrorCode,
10+
ToggleFreezeTransfersProcedureArgs,
11+
PolyTransactionTag,
12+
ProcedureType,
13+
} from '../../types';
14+
import * as contextModule from '../../Context';
15+
import * as wrappersModule from '../../PolymathBase';
16+
import * as tokenFactoryModule from '../../testUtils/MockedTokenFactoryModule';
17+
import * as securityTokenFactoryModule from '../../entities/factories/SecurityTokenFactory';
18+
import { mockFactories } from '../../testUtils/mockFactories';
19+
import { SecurityToken } from '../../entities/SecurityToken/SecurityToken';
20+
import { Wallet } from '../../Wallet';
21+
import { Factories } from '../../Context';
22+
23+
const params: ToggleFreezeTransfersProcedureArgs = {
24+
symbol: 'TEST',
25+
freeze: true,
26+
};
27+
28+
describe('ToggleFreezeTransfers', () => {
29+
let target: ToggleFreezeTransfers;
30+
let contextMock: MockManager<contextModule.Context>;
31+
let wrappersMock: MockManager<wrappersModule.PolymathBase>;
32+
let tokenFactoryMock: MockManager<tokenFactoryModule.MockedTokenFactoryModule>;
33+
let securityTokenMock: MockManager<contractWrappersModule.SecurityToken_3_0_0>;
34+
let factoriesMockedSetup: Factories;
35+
let securityTokenFactoryMock: MockManager<securityTokenFactoryModule.SecurityTokenFactory>;
36+
37+
beforeEach(() => {
38+
contextMock = ImportMock.mockClass(contextModule, 'Context');
39+
wrappersMock = ImportMock.mockClass(wrappersModule, 'PolymathBase');
40+
tokenFactoryMock = ImportMock.mockClass(tokenFactoryModule, 'MockedTokenFactoryModule');
41+
42+
contextMock.set('contractWrappers', wrappersMock.getMockInstance());
43+
contextMock.set(
44+
'currentWallet',
45+
new Wallet({ address: () => Promise.resolve('0x0e6b236a504fce78527497e46dc90c0a6fdc9495') })
46+
);
47+
48+
wrappersMock.set('tokenFactory', tokenFactoryMock.getMockInstance());
49+
50+
securityTokenMock = ImportMock.mockClass(contractWrappersModule, 'SecurityToken_3_0_0');
51+
securityTokenMock.mock('owner', Promise.resolve('0x0e6b236a504fce78527497e46dc90c0a6fdc9495'));
52+
securityTokenMock.mock('transfersFrozen', Promise.resolve(false));
53+
tokenFactoryMock.mock(
54+
'getSecurityTokenInstanceFromTicker',
55+
securityTokenMock.getMockInstance()
56+
);
57+
58+
securityTokenFactoryMock = ImportMock.mockClass(
59+
securityTokenFactoryModule,
60+
'SecurityTokenFactory'
61+
);
62+
factoriesMockedSetup = mockFactories();
63+
factoriesMockedSetup.securityTokenFactory = securityTokenFactoryMock.getMockInstance();
64+
contextMock.set('factories', factoriesMockedSetup);
65+
66+
// Instantiate ToggleFreezeTransfers
67+
target = new ToggleFreezeTransfers(params, contextMock.getMockInstance());
68+
});
69+
70+
afterEach(() => {
71+
restore();
72+
});
73+
74+
describe('Types', () => {
75+
test('should extend procedure and have ToggleFreezeTransfers type', async () => {
76+
expect(target instanceof Procedure).toBe(true);
77+
expect(target.type).toBe(ProcedureType.ToggleFreezeTransfers);
78+
});
79+
});
80+
81+
describe('ToggleFreezeTransfers', () => {
82+
test('should throw if there is no valid security token supplied', async () => {
83+
tokenFactoryMock
84+
.mock('getSecurityTokenInstanceFromTicker')
85+
.withArgs(params.symbol)
86+
.throws();
87+
88+
await expect(target.prepareTransactions()).rejects.toThrow(
89+
new PolymathError({
90+
code: ErrorCode.ProcedureValidationError,
91+
message: `There is no Security Token with symbol ${params.symbol}`,
92+
})
93+
);
94+
});
95+
96+
test('should throw if current wallet is not the security token owner and wants set freeze mode to true', async () => {
97+
securityTokenMock.mock(
98+
'owner',
99+
Promise.resolve('0x7428a3fa68124b4EF1dc0579ea8c3E932b37BF96')
100+
);
101+
102+
await expect(target.prepareTransactions()).rejects.toThrow(
103+
new PolymathError({
104+
code: ErrorCode.ProcedureValidationError,
105+
message: `You must be the owner of this Security Token to freeze the transfers`,
106+
})
107+
);
108+
});
109+
110+
test('should throw if current wallet is not the security token owner and wants set freeze mode to false', async () => {
111+
target = new ToggleFreezeTransfers(
112+
{ ...params, freeze: false },
113+
contextMock.getMockInstance()
114+
);
115+
securityTokenMock.mock(
116+
'owner',
117+
Promise.resolve('0x7428a3fa68124b4EF1dc0579ea8c3E932b37BF96')
118+
);
119+
120+
await expect(target.prepareTransactions()).rejects.toThrow(
121+
new PolymathError({
122+
code: ErrorCode.ProcedureValidationError,
123+
message: `You must be the owner of this Security Token to unfreeze the transfers`,
124+
})
125+
);
126+
});
127+
128+
test('should throw if frozen status is already settled on true', async () => {
129+
securityTokenMock.mock('transfersFrozen', Promise.resolve(true));
130+
131+
await expect(target.prepareTransactions()).rejects.toThrow(
132+
new PolymathError({
133+
code: ErrorCode.ProcedureValidationError,
134+
message: `Security Token Transfers are already frozen`,
135+
})
136+
);
137+
});
138+
139+
test('should throw if frozen status is already settled on false', async () => {
140+
target = new ToggleFreezeTransfers(
141+
{ ...params, freeze: false },
142+
contextMock.getMockInstance()
143+
);
144+
securityTokenMock.mock('transfersFrozen', Promise.resolve(false));
145+
146+
await expect(target.prepareTransactions()).rejects.toThrow(
147+
new PolymathError({
148+
code: ErrorCode.ProcedureValidationError,
149+
message: `Security Token Transfers are already unfrozen`,
150+
})
151+
);
152+
});
153+
154+
test('should add a transaction to the queue to execute a toggle freeze transfer', async () => {
155+
const addTransactionSpy = spy(target, 'addTransaction');
156+
securityTokenMock.mock('freezeTransfers', Promise.resolve('FreezeTransfers'));
157+
158+
await target.prepareTransactions();
159+
160+
expect(
161+
addTransactionSpy.getCall(0).calledWith(securityTokenMock.getMockInstance().freezeTransfers)
162+
).toEqual(true);
163+
expect(addTransactionSpy.getCall(0).lastArg.tag).toEqual(
164+
PolyTransactionTag.ToggleFreezeTransfers
165+
);
166+
expect(addTransactionSpy.callCount).toEqual(1);
167+
});
168+
169+
test('should successfully resolve toggle freeze transfers', async () => {
170+
const refreshStub = securityTokenFactoryMock.mock('refresh', Promise.resolve());
171+
const securityTokenId = SecurityToken.generateId({ symbol: params.symbol });
172+
const resolverValue = await toggleFreezeTransferModule.createToggleFreezeTransfersResolver(
173+
factoriesMockedSetup,
174+
params.symbol
175+
)();
176+
expect(refreshStub.getCall(0).calledWithExactly(securityTokenId)).toEqual(true);
177+
expect(resolverValue).toEqual(undefined);
178+
expect(refreshStub.callCount).toEqual(1);
179+
});
180+
});
181+
});

src/procedures/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ export { ModifyMaxHolderCount } from './ModifyMaxHolderCount';
2929
export { EnablePercentageTransferManager } from './EnablePercentageTransferManager';
3030
export { ModifyMaxHolderPercentage } from './ModifyMaxHolderPercentage';
3131
export { ModifyPercentageExemptions } from './ModifyPercentageExemptions';
32+
export { ToggleFreezeTransfers } from './ToggleFreezeTransfers';

src/types/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export enum ProcedureType {
129129
ModifyMaxHolderCount = 'ModifyMaxHolderCount',
130130
ModifyMaxHolderPercentage = 'ModifyMaxHolderPercentage',
131131
ModifyPercentageExemptions = 'ModifyPercentageExemptions',
132+
ToggleFreezeTransfers = 'ToggleFreezeTransfers',
132133
}
133134

134135
export enum PolyTransactionTag {
@@ -167,6 +168,7 @@ export enum PolyTransactionTag {
167168
ChangeHolderPercentage = 'ChangeHolderPercentage',
168169
ModifyWhitelistMulti = 'ModifyWhitelistMulti',
169170
SetAllowPrimaryIssuance = 'SetAllowPrimaryIssuance',
171+
ToggleFreezeTransfers = 'ToggleFreezeTransfers',
170172
}
171173

172174
export type MaybeResolver<T> = PostTransactionResolver<T> | T;
@@ -452,6 +454,11 @@ export interface ModifyPercentageExemptionsProcedureArgs {
452454
allowPrimaryIssuance?: boolean;
453455
}
454456

457+
export interface ToggleFreezeTransfersProcedureArgs {
458+
symbol: string;
459+
freeze: boolean;
460+
}
461+
455462
export interface ProcedureArguments {
456463
[ProcedureType.ApproveErc20]: ApproveErc20ProcedureArgs;
457464
[ProcedureType.TransferErc20]: TransferErc20ProcedureArgs;
@@ -483,6 +490,7 @@ export interface ProcedureArguments {
483490
[ProcedureType.ModifyMaxHolderCount]: ModifyMaxHolderCountProcedureArgs;
484491
[ProcedureType.ModifyMaxHolderPercentage]: ModifyMaxHolderPercentageProcedureArgs;
485492
[ProcedureType.ModifyPercentageExemptions]: ModifyPercentageExemptionsProcedureArgs;
493+
[ProcedureType.ToggleFreezeTransfers]: ToggleFreezeTransfersProcedureArgs;
486494
[ProcedureType.UnnamedProcedure]: {};
487495
}
488496

0 commit comments

Comments
 (0)