Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compliance modules fixes & improvements #228

Merged
merged 7 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Change Log
All notable changes to this project will be documented in this file.

## [4.1.6]

### Added

- Added **removeTimeTransferLimit** function to TimeTransferLimitsModule, allows removing time transfer limits for the given limitTime.
- Added **batchSetTimeTransferLimit** and **batchRemoveTimeTransferLimit** functions to TimeTransferLimitsModule, allows setting and removing multiple time transfer limits at once.

## [4.1.5]

### Added
Expand Down
91 changes: 76 additions & 15 deletions contracts/compliance/modular/modules/TimeTransfersLimitsModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,24 @@ contract TimeTransfersLimitsModule is AbstractModuleUpgradeable {
/**
* this event is emitted whenever a transfer limit is updated for the given compliance address and limit time
* the event is emitted by 'setTimeTransferLimit'.
* compliance`is the compliance contract address
* compliance is the compliance contract address
* _limitValue is the new limit value for the given limit time
* _limitTime is the period of time of the limit
*/
event TimeTransferLimitUpdated(address indexed compliance, uint32 limitTime, uint256 limitValue);

/**
* this event is emitted whenever a transfer limit is removed for the given compliance address and limit time
* the event is emitted by 'removeTimeTransferLimit'.
* compliance is the compliance contract address
* _limitTime is the period of time of the limit
*/
event TimeTransferLimitRemoved(address indexed compliance, uint32 limitTime);

error LimitsArraySizeExceeded(address compliance, uint arraySize);

error LimitTimeNotFound(address compliance, uint limitTime);

/**
* @dev initializes the contract and sets the initial state.
* @notice This function should only be called once during the contract deployment.
Expand All @@ -113,24 +123,25 @@ contract TimeTransfersLimitsModule is AbstractModuleUpgradeable {
}

/**
* @dev Sets the limit of tokens allowed to be transferred in the given time frame.
* @param _limit The limit time and value
* @dev Sets multiple limit of tokens allowed to be transferred in the given time frame.
* @param _limits The array of limit time and values
* Only the owner of the Compliance smart contract can call this function
*/
function setTimeTransferLimit(Limit calldata _limit) external onlyComplianceCall {
bool limitIsAttributed = limitValues[msg.sender][_limit.limitTime].attributedLimit;
uint8 limitCount = uint8(transferLimits[msg.sender].length);
if (!limitIsAttributed && limitCount >= 4) {
revert LimitsArraySizeExceeded(msg.sender, limitCount);
}
if (!limitIsAttributed && limitCount < 4) {
transferLimits[msg.sender].push(_limit);
limitValues[msg.sender][_limit.limitTime] = IndexLimit(true, limitCount);
} else {
transferLimits[msg.sender][limitValues[msg.sender][_limit.limitTime].limitIndex] = _limit;
function batchSetTimeTransferLimit(Limit[] calldata _limits) external {
for (uint256 i = 0; i < _limits.length; i++) {
setTimeTransferLimit(_limits[i]);
}
}

emit TimeTransferLimitUpdated(msg.sender, _limit.limitTime, _limit.limitValue);
/**
* @dev Removes multiple limits for the given limit time values.
* @param _limitTimes The array of limit times
* Only the owner of the Compliance smart contract can call this function
**/
function batchRemoveTimeTransferLimit(uint32[] calldata _limitTimes) external {
for (uint256 i = 0; i < _limitTimes.length; i++) {
removeTimeTransferLimit(_limitTimes[i]);
}
}

/**
Expand Down Expand Up @@ -208,6 +219,56 @@ contract TimeTransfersLimitsModule is AbstractModuleUpgradeable {
return true;
}

/**
* @dev Sets the limit of tokens allowed to be transferred in the given time frame.
* @param _limit The limit time and value
* Only the owner of the Compliance smart contract can call this function
*/
function setTimeTransferLimit(Limit calldata _limit) public onlyComplianceCall {
bool limitIsAttributed = limitValues[msg.sender][_limit.limitTime].attributedLimit;
uint8 limitCount = uint8(transferLimits[msg.sender].length);
if (!limitIsAttributed && limitCount >= 4) {
revert LimitsArraySizeExceeded(msg.sender, limitCount);
}
if (!limitIsAttributed && limitCount < 4) {
transferLimits[msg.sender].push(_limit);
limitValues[msg.sender][_limit.limitTime] = IndexLimit(true, limitCount);
} else {
transferLimits[msg.sender][limitValues[msg.sender][_limit.limitTime].limitIndex] = _limit;
}

emit TimeTransferLimitUpdated(msg.sender, _limit.limitTime, _limit.limitValue);
}

/**
* @dev Removes the limit for the given limit time value.
* @param _limitTime The limit time
* Only the owner of the Compliance smart contract can call this function
*/
function removeTimeTransferLimit(uint32 _limitTime) public onlyComplianceCall {
bool limitFound = false;
uint256 index;
for (uint256 i = 0; i < transferLimits[msg.sender].length; i++) {
if (transferLimits[msg.sender][i].limitTime == _limitTime) {
limitFound = true;
index = i;
break;
}
}

if (!limitFound) {
revert LimitTimeNotFound(msg.sender, _limitTime);
}

if (transferLimits[msg.sender].length > 1 && index != transferLimits[msg.sender].length - 1) {
transferLimits[msg.sender][index] = transferLimits[msg.sender][transferLimits[msg.sender].length - 1];
}

transferLimits[msg.sender].pop();
delete limitValues[msg.sender][_limitTime];
emit TimeTransferLimitRemoved(msg.sender, _limitTime);
}

/**
* @dev See {IModule-name}.
*/
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tokenysolutions/t-rex",
"version": "4.1.5",
"version": "4.1.6",
"description": "A fully compliant environment for the issuance and use of tokenized securities.",
"main": "index.js",
"directories": {
Expand Down
185 changes: 185 additions & 0 deletions test/compliances/module-time-transfer-limits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,191 @@ describe('Compliance Module: TimeTransferLimits', () => {
});
});

describe('.batchSetTimeTransferLimit', () => {
describe('when calling directly', () => {
it('should revert', async () => {
const context = await loadFixture(deployTimeTransferLimitsFixture);

await expect(
context.contracts.complianceModule.batchSetTimeTransferLimit([
{ limitTime: 1, limitValue: 100 },
{ limitTime: 2, limitValue: 200 },
]),
).to.revertedWith('only bound compliance can call');
});
});

describe('when calling via compliance', () => {
it('should create the limits', async () => {
const context = await loadFixture(deployTimeTransferLimitsFixture);

const tx = await context.contracts.compliance.callModuleFunction(
new ethers.utils.Interface([
'function batchSetTimeTransferLimit(tuple(uint32 limitTime, uint256 limitValue)[] _limits)',
]).encodeFunctionData('batchSetTimeTransferLimit', [
[
{ limitTime: 1, limitValue: 100 },
{ limitTime: 2, limitValue: 200 },
],
]),
context.contracts.complianceModule.address,
);

await expect(tx)
.to.emit(context.contracts.complianceModule, 'TimeTransferLimitUpdated')
.withArgs(context.contracts.compliance.address, 1, 100)
.to.emit(context.contracts.complianceModule, 'TimeTransferLimitUpdated')
.withArgs(context.contracts.compliance.address, 2, 200);
});
});
});

describe('.removeTimeTransferLimit', () => {
describe('when calling directly', () => {
it('should revert', async () => {
const context = await loadFixture(deployTimeTransferLimitsFixture);

await expect(context.contracts.complianceModule.removeTimeTransferLimit(10)).to.revertedWith('only bound compliance can call');
});
});

describe('when calling via compliance', () => {
describe('when limit time is missing', () => {
it('should revert', async () => {
const context = await loadFixture(deployTimeTransferLimitsFixture);

await expect(
context.contracts.compliance.callModuleFunction(
new ethers.utils.Interface(['function removeTimeTransferLimit(uint32 _limitTime)']).encodeFunctionData('removeTimeTransferLimit', [10]),
context.contracts.complianceModule.address,
),
).to.be.revertedWithCustomError(context.contracts.complianceModule, `LimitTimeNotFound`);
});
});

describe('when limit time exist', () => {
describe('when limit time is the last element', () => {
it('should remove the limit', async () => {
const context = await loadFixture(deployTimeTransferLimitsFixture);

await context.contracts.compliance.callModuleFunction(
new ethers.utils.Interface([
'function batchSetTimeTransferLimit(tuple(uint32 limitTime, uint256 limitValue)[] _limit)',
]).encodeFunctionData('batchSetTimeTransferLimit', [
[
{ limitTime: 1, limitValue: 100 },
{ limitTime: 2, limitValue: 200 },
{ limitTime: 3, limitValue: 300 },
],
]),
context.contracts.complianceModule.address,
);

const tx = await context.contracts.compliance.callModuleFunction(
new ethers.utils.Interface(['function removeTimeTransferLimit(uint32 _limitTime)']).encodeFunctionData('removeTimeTransferLimit', [3]),
context.contracts.complianceModule.address,
);

await expect(tx)
.to.emit(context.contracts.complianceModule, 'TimeTransferLimitRemoved')
.withArgs(context.contracts.compliance.address, 3);

const limits = await context.contracts.complianceModule.getTimeTransferLimits(context.suite.compliance.address);
expect(limits.length).to.be.eq(2);
expect(limits[0].limitTime).to.be.eq(1);
expect(limits[0].limitValue).to.be.eq(100);
expect(limits[1].limitTime).to.be.eq(2);
expect(limits[1].limitValue).to.be.eq(200);
});
});

describe('when limit time is not the last element', () => {
it('should remove the limit', async () => {
const context = await loadFixture(deployTimeTransferLimitsFixture);

await context.contracts.compliance.callModuleFunction(
new ethers.utils.Interface([
'function batchSetTimeTransferLimit(tuple(uint32 limitTime, uint256 limitValue)[] _limit)',
]).encodeFunctionData('batchSetTimeTransferLimit', [
[
{ limitTime: 1, limitValue: 100 },
{ limitTime: 2, limitValue: 200 },
{ limitTime: 3, limitValue: 300 },
],
]),
context.contracts.complianceModule.address,
);

const tx = await context.contracts.compliance.callModuleFunction(
new ethers.utils.Interface(['function removeTimeTransferLimit(uint32 _limitTime)']).encodeFunctionData('removeTimeTransferLimit', [2]),
context.contracts.complianceModule.address,
);

await expect(tx)
.to.emit(context.contracts.complianceModule, 'TimeTransferLimitRemoved')
.withArgs(context.contracts.compliance.address, 2);

const limits = await context.contracts.complianceModule.getTimeTransferLimits(context.suite.compliance.address);
expect(limits.length).to.be.eq(2);
expect(limits[0].limitTime).to.be.eq(1);
expect(limits[0].limitValue).to.be.eq(100);
expect(limits[1].limitTime).to.be.eq(3);
expect(limits[1].limitValue).to.be.eq(300);
});
});
});
});
});

describe('.batchRemoveTimeTransferLimit', () => {
describe('when calling directly', () => {
it('should revert', async () => {
const context = await loadFixture(deployTimeTransferLimitsFixture);

await expect(context.contracts.complianceModule.batchRemoveTimeTransferLimit([10, 20])).to.revertedWith('only bound compliance can call');
});
});

describe('when calling via compliance', () => {
it('should remove the limits', async () => {
const context = await loadFixture(deployTimeTransferLimitsFixture);

await context.contracts.compliance.callModuleFunction(
new ethers.utils.Interface(['function batchSetTimeTransferLimit(tuple(uint32 limitTime, uint256 limitValue)[] _limit)']).encodeFunctionData(
'batchSetTimeTransferLimit',
[
[
{ limitTime: 1, limitValue: 100 },
{ limitTime: 2, limitValue: 200 },
{ limitTime: 3, limitValue: 300 },
],
],
),
context.contracts.complianceModule.address,
);

const tx = await context.contracts.compliance.callModuleFunction(
new ethers.utils.Interface(['function batchRemoveTimeTransferLimit(uint32[] _limitTimes)']).encodeFunctionData(
'batchRemoveTimeTransferLimit',
[[1, 3]],
),
context.contracts.complianceModule.address,
);

await expect(tx)
.to.emit(context.contracts.complianceModule, 'TimeTransferLimitRemoved')
.withArgs(context.contracts.compliance.address, 1)
.to.emit(context.contracts.complianceModule, 'TimeTransferLimitRemoved')
.withArgs(context.contracts.compliance.address, 3);

const limits = await context.contracts.complianceModule.getTimeTransferLimits(context.suite.compliance.address);
expect(limits.length).to.be.eq(1);
expect(limits[0].limitTime).to.be.eq(2);
expect(limits[0].limitValue).to.be.eq(200);
});
});
});

describe('.getTimeTransferLimits', () => {
describe('when there is no time transfer limit', () => {
it('should return empty array', async () => {
Expand Down
Loading