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

support terminate state channel with current state #428

Merged
merged 2 commits into from
Nov 3, 2024
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
58 changes: 44 additions & 14 deletions contracts/StateChannel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter {
emit ChannelCheckpoint(query.channelId, query.spent, query.isFinal);

// update channel state
_settlement(query, false);
_settlement(query.channelId, query.spent, query.isFinal);
}

/**
Expand Down Expand Up @@ -421,7 +421,36 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter {
emit ChannelTerminate(query.channelId, query.spent, expiration, isIndexer);

// update channel state.
_settlement(query, false);
_settlement(query.channelId, query.spent, query.isFinal);
}

function terminateWithCurrentState(uint256 channelId) external {
ChannelState storage state = channels[channelId];

// check sender
bool isIndexer = msg.sender == state.indexer;
bool isConsumer = msg.sender == state.consumer;
if (!isIndexer && !isConsumer) {
address controller = IIndexerRegistry(
settings.getContractAddress(SQContracts.IndexerRegistry)
).getController(state.indexer);
isIndexer = msg.sender == controller;
}
if (_isContract(state.consumer)) {
isConsumer = IConsumer(state.consumer).checkSender(channelId, msg.sender);
}
require(isIndexer || isConsumer, 'G008');

// set state to terminate
state.status = ChannelStatus.Terminating;
uint256 expiration = block.timestamp + terminateExpiration;
state.terminatedAt = expiration;
state.terminateByIndexer = isIndexer;

emit ChannelTerminate(channelId, state.spent, expiration, isIndexer);

// update channel state.
_settlement(channelId, state.spent, false);
}

/**
Expand Down Expand Up @@ -451,7 +480,7 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter {
_checkStateSign(query.channelId, payload, query.indexerSign, query.consumerSign);

// update channel state
_settlement(query, true);
_settlement(query.channelId, query.spent, true);
}

/**
Expand Down Expand Up @@ -519,21 +548,22 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter {
}

/// @notice Settlement the new state
function _settlement(QueryState calldata query, bool finalize) private {
function _settlement(uint256 channelId, uint256 spent, bool isFinal) private {
// update channel state
uint256 amount = query.spent - channels[query.channelId].spent;
ChannelState storage state = channels[channelId];
uint256 amount = spent - state.spent;

if (channels[query.channelId].total > query.spent) {
channels[query.channelId].spent = query.spent;
if (state.total > spent) {
state.spent = spent;
} else {
amount = channels[query.channelId].total - channels[query.channelId].spent;
channels[query.channelId].spent = channels[query.channelId].total;
amount = state.total - state.spent;
state.spent = state.total;
}

// reward pool
if (amount > 0) {
address indexer = channels[query.channelId].indexer;
bytes32 deploymentId = channels[query.channelId].deploymentId;
address indexer = state.indexer;
bytes32 deploymentId = state.deploymentId;
// rewards pool is deprecated
// address rewardPoolAddress = settings.getContractAddress(SQContracts.RewardsPool);
// IERC20(settings.getContractAddress(SQContracts.SQToken)).approve(
Expand All @@ -559,12 +589,12 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter {
amount,
eraManager.safeUpdateAndGetEra()
);
emit ChannelLabor2(query.channelId, deploymentId, indexer, amount);
emit ChannelLabor2(channelId, deploymentId, indexer, amount);
}

// finalise channel if meet the requirements
if (finalize || query.isFinal) {
_finalize(query.channelId);
if (isFinal) {
_finalize(channelId);
}
}

Expand Down
13 changes: 13 additions & 0 deletions publish/ABI/StateChannel.json
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "channelId",
"type": "uint256"
}
],
"name": "terminateWithCurrentState",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
Expand Down
41 changes: 41 additions & 0 deletions test/StateChannel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,47 @@ describe('StateChannel Contract', () => {
expect(state.status).to.equal(0);
});

it('terminate State Channel with current onchain state', async () => {
await stateChannel.setTerminateExpiration(5); // 5s

const channelId = ethers.utils.randomBytes(32);
await openChannel(
stateChannel,
channelId,
deploymentId,
runner,
consumer,
etherParse('1'),
etherParse('0.1'),
60
);

const query1 = await buildQueryState(channelId, runner, consumer, etherParse('0.1'), false);
await stateChannel.connect(runner).checkpoint(query1);
let state1 = await stateChannel.channel(channelId);
expect(state1.spent).to.equal(etherParse('0.1'));

await expect(stateChannel.connect(runner).terminateWithCurrentState(channelId)).to.emit(
stateChannel,
'ChannelTerminate'
);
state1 = await stateChannel.channel(channelId);
expect(state1.status).to.equal(2); // Terminate

await expect(stateChannel.claim(channelId)).to.be.revertedWith('SC008');

await delay(6);
await stateChannel.claim(channelId);

const balance2 = await token.balanceOf(consumer.address);
expect(balance2).to.equal(etherParse('4.9'));

await startNewEra(eraManager);
await rewardsHelper.connect(runner).indexerCatchup(runner.address);
const indexerReward = await rewardsDistributor.userRewards(runner.address, runner.address);

expect(indexerReward).to.eq(etherParse('0.1'));
});
/**
* when only one indexer in the pool and that indexer unregistered,
* channel can still be terminated, consumer can claim the channel token
Expand Down
Loading