Skip to content

Commit

Permalink
feat(locking): withdraw (#15)
Browse files Browse the repository at this point in the history
* feat(locking): withdraw

* assert updatedBatch field

* test: seq nonce

* test: remove curBatchState
  • Loading branch information
ericlee42 authored Apr 30, 2024
1 parent fe54d31 commit c5c7bdd
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 1 deletion.
24 changes: 24 additions & 0 deletions contracts/LockingInfo.sol
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,30 @@ contract LockingInfo is ILockingInfo, OwnableUpgradeable {
emit LockUpdate(_seqId, _nonce, _locked);
}

/**
* @dev withdrawLocking is to withdraw locking
* @param _seqId the sequencer id
* @param _owner the sequencer owner address
* @param _nonce the sequencer nonce
* @param _amount amount to withdraw
* @param _locked the locked amount of the sequencer at last
*/
function withdrawLocking(
uint256 _seqId,
address _owner,
uint256 _nonce,
uint256 _amount,
uint256 _locked
) external override OnlyManager {
require(_amount > 0 && _locked >= minLock, "invalid amount");
// update current locked amount
totalLocked -= _amount;

IERC20(l1Token).safeTransfer(_owner, _amount);
emit Withdraw(_seqId, _amount);
emit LockUpdate(_seqId, _nonce, _locked);
}

/**
* @dev initializeUnlock the first step to unlock
* current reward will be distributed
Expand Down
31 changes: 31 additions & 0 deletions contracts/LockingPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,37 @@ contract LockingPool is ILockingPool, PausableUpgradeable, SequencerInfo {
}
}

/**
* @dev withdraw allow sequencer operator to withdraw the locking
* @param _seqId the id of your sequencer
* @param _amount amount to withdraw
*/
function withdraw(
uint256 _seqId,
uint256 _amount
) external whenNotPaused whitelistRequired {
Sequencer storage seq = sequencers[_seqId];
if (seq.status != Status.Active) {
revert SeqNotActive();
}

if (seq.owner != msg.sender) {
revert NotSeqOwner();
}

// owner can only withdraw once in the current reward period
require(curBatchState.id > seq.updatedBatch, "withdraw throttle");

uint256 locked = seq.amount - _amount;
uint256 nonce = seq.nonce + 1;

seq.nonce = nonce;
seq.amount = locked;
seq.updatedBatch = curBatchState.id;

escrow.withdrawLocking(_seqId, seq.owner, nonce, _amount, locked);
}

/**
* @dev batchSubmitRewards Allow to submit L2 sequencer block information, and attach Metis reward tokens for reward distribution
* @param _batchId The batchId that submitted the reward is that
Expand Down
17 changes: 16 additions & 1 deletion contracts/interfaces/ILockingInfo.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,20 @@ interface ILockingInfo {
);

/**
* @dev Emitted when the sequencer increase lock amoun in 'relock()'.
* @dev Emitted when the sequencer increase lock amount in 'relock()'.
* @param sequencerId unique integer to identify a sequencer.
* @param amount locking new amount
* @param total the total locking amount
*/
event Relocked(uint256 indexed sequencerId, uint256 amount, uint256 total);

/**
* @dev Emitted when the sequencer reduce lock amount in 'withdraw()'.
* @param sequencerId unique integer to identify a sequencer.
* @param amount withdraw new amount
*/
event Withdraw(uint256 indexed sequencerId, uint256 amount);

/**
* @dev Emitted when sequencer relocking in 'relock()'.
* @param sequencerId unique integer to identify a sequencer.
Expand Down Expand Up @@ -153,6 +160,14 @@ interface ILockingInfo {
uint256 _fromReward
) external;

function withdrawLocking(
uint256 _seqId,
address _owner,
uint256 _nonce,
uint256 _amount,
uint256 _locked
) external;

function initializeUnlock(
uint256 _seqId,
uint256 _reward,
Expand Down
92 changes: 92 additions & 0 deletions ts-src/test/LockingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,98 @@ describe("locking", async () => {
expect(newAmount, "newAmount").to.be.eq(minLock + relock);
});

it("withdrawLocking", async () => {
const {
lockingInfo,
lockingPool,
whitelised,
unwhitelist,
admin,
mpc,
metisToken,
} = await loadFixture(fixture);

const [wallet0, wallet1] = whitelised;
const [wallet3] = unwhitelist;
const minLock = 1n;
await lockingInfo.setMinLock(minLock);
await lockingPool.updateMpc(mpc);
await metisToken.approve(await lockingInfo.getAddress(), ethers.MaxUint256);
await lockingInfo.setRewardPayer(admin);

const wallet0Pubkey = trimPubKeyPrefix(wallet0.signingKey.publicKey);
await lockingPool.connect(wallet0).lockFor(wallet0, minLock, wallet0Pubkey);
const seqId = 1n;

const withdrawAmount = 1n;

await expect(
lockingPool.connect(wallet3).withdraw(3, withdrawAmount),
"NotWhitelisted",
).to.be.revertedWithCustomError(lockingPool, "NotWhitelisted");

await expect(
lockingPool.connect(wallet1).withdraw(2, withdrawAmount),
"SeqNotActive",
).to.be.revertedWithCustomError(lockingPool, "SeqNotActive");

await expect(
lockingPool.connect(wallet1).withdraw(seqId, withdrawAmount),
"NotSeqOwner",
).to.be.revertedWithCustomError(lockingPool, "NotSeqOwner");

await expect(
lockingPool.connect(wallet0).withdraw(seqId, withdrawAmount),
"throttle",
).to.be.revertedWith("withdraw throttle");

await lockingPool
.connect(mpc)
.batchSubmitRewards(2n, 1n, 2n, [wallet0], [1]);

await expect(
lockingPool.connect(wallet0).withdraw(seqId, 0),
"zero withdraw",
).to.be.revertedWith("invalid amount");

await expect(
lockingPool.connect(wallet0).withdraw(seqId, minLock),
"locking < minLock",
).to.be.revertedWith("invalid amount");

// starts from 1, and add 1 after the relock
let seqNonce = 2;
const relock = 3n;
await lockingPool.connect(wallet0).relock(seqId, relock, false);

seqNonce++;
const locking = minLock + relock - withdrawAmount;
await expect(
await lockingPool.connect(wallet0).withdraw(seqId, withdrawAmount),
"withdraw",
)
.to.be.emit(lockingInfo, "Withdraw")
.withArgs(seqId, withdrawAmount)
.and.to.be.emit(lockingInfo, "LockUpdate")
.withArgs(seqId, seqNonce, locking)
.and.to.be.emit(metisToken, "Transfer")
.withArgs(await lockingInfo.getAddress(), wallet0, withdrawAmount);

await expect(
lockingPool.connect(wallet0).withdraw(seqId, withdrawAmount),
"throttle again",
).to.be.revertedWith("withdraw throttle");

const {
nonce: newNonce,
amount: newAmount,
updatedBatch: newBatchId,
} = await lockingPool.sequencers(seqId);
expect(newNonce, "newNonce").to.be.eq(seqNonce);
expect(newBatchId, "newBatchId").to.be.eq(2n);
expect(newAmount, "newAmount").to.be.eq(locking);
});

it("relock/withReward", async () => {
const { admin, lockingInfo, lockingPool, whitelised, metisToken, mpc } =
await loadFixture(fixture);
Expand Down

0 comments on commit c5c7bdd

Please sign in to comment.