diff --git a/source/pool/contracts/Pool.sol b/source/pool/contracts/Pool.sol index fb98d457b..fa8f8385c 100644 --- a/source/pool/contracts/Pool.sol +++ b/source/pool/contracts/Pool.sol @@ -124,6 +124,32 @@ contract Pool is IPool, Ownable2Step { emit Enable(_tree, _root); } + /** + * @notice Set previous claims for migrations + * @param _tree bytes32 + * @param _root bytes32 + * @param _accounts address[] + * @dev Only owner + */ + function enableAndSetClaimed( + bytes32 _tree, + bytes32 _root, + address[] memory _accounts + ) external override multiAdmin { + // Enable the tree if not yet enabled + if (rootsByTree[_tree] == 0) { + rootsByTree[_tree] = _root; + emit Enable(_tree, _root); + } + // Iterate and set as claimed if not yet claimed + for (uint256 i = 0; i < _accounts.length; i++) { + if (claimed[_tree][_accounts[i]] == false) { + claimed[_tree][_accounts[i]] = true; + emit UseClaim(_accounts[i], _tree); + } + } + } + /** * @notice Withdraw tokens using claims * @param _claims Claim[] a set of claims @@ -141,7 +167,6 @@ contract Pool is IPool, Ownable2Step { Claim memory _claim; bytes32 _root; - bytes32[] memory _treeList = new bytes32[](_claims.length); uint256 _totalValue = 0; // Iterate through claims to determine total value @@ -149,14 +174,14 @@ contract Pool is IPool, Ownable2Step { _claim = _claims[i]; _root = rootsByTree[_claim.tree]; - if (_root == 0) revert TreeDisabled(_claim.tree); - if (claimed[_claim.tree][msg.sender]) revert AlreadyClaimed(); + if (_root == 0) revert TreeNotEnabled(_claim.tree); + if (claimed[_claim.tree][msg.sender]) revert ClaimAlreadyUsed(); if (!verify(msg.sender, _root, _claim.value, _claim.proof)) revert ProofInvalid(_claim.tree, _root); _totalValue = _totalValue + _claim.value; claimed[_claim.tree][msg.sender] = true; - _treeList[i] = _claim.tree; + emit UseClaim(msg.sender, _claim.tree); } // Determine withdrawable amount given total value @@ -165,7 +190,7 @@ contract Pool is IPool, Ownable2Step { // Transfer withdrawable amount to recipient IERC20(_token).safeTransfer(_recipient, _amount); - emit Withdraw(_treeList, msg.sender, _token, _amount); + emit Withdraw(msg.sender, _recipient, _token, _amount); } /** diff --git a/source/pool/contracts/interfaces/IPool.sol b/source/pool/contracts/interfaces/IPool.sol index 81dcb7b16..99d504c33 100644 --- a/source/pool/contracts/interfaces/IPool.sol +++ b/source/pool/contracts/interfaces/IPool.sol @@ -15,24 +15,28 @@ interface IPool { event SetMax(uint256 max); event SetScale(uint256 scale); event UnsetAdmin(address admin); + + event UseClaim(address account, bytes32 tree); event Withdraw( - bytes32[] trees, address account, + address recipient, address token, uint256 amount ); error AddressInvalid(address); error AdminNotSet(address); - error AlreadyClaimed(); error AmountInsufficient(uint256); + error ClaimAlreadyUsed(); error ClaimsNotProvided(); error MaxTooHigh(uint256); error ProofInvalid(bytes32, bytes32); - error TreeDisabled(bytes32); + error TreeNotEnabled(bytes32); error ScaleTooHigh(uint256); error Unauthorized(); + function drainTo(address[] calldata tokens, address dest) external; + function setScale(uint256 _scale) external; function setMax(uint256 _max) external; @@ -41,15 +45,14 @@ interface IPool { function unsetAdmin(address _admin) external; - function drainTo(address[] calldata tokens, address dest) external; - - function getStatus( - address _account, - bytes32[] calldata _trees - ) external returns (bool[] memory statuses); - function enable(bytes32 _tree, bytes32 _root) external; + function enableAndSetClaimed( + bytes32 _tree, + bytes32 _root, + address[] memory _accounts + ) external; + function withdraw( Claim[] memory claims, address token, @@ -68,4 +71,9 @@ interface IPool { uint256 score, bytes32[] memory proof ) external pure returns (bool valid); + + function getStatus( + address _account, + bytes32[] calldata _trees + ) external returns (bool[] memory statuses); } diff --git a/source/pool/test/Pool.js b/source/pool/test/Pool.js index d9e601434..73586076d 100644 --- a/source/pool/test/Pool.js +++ b/source/pool/test/Pool.js @@ -71,8 +71,6 @@ describe('Pool Unit', () => { ).deploy(CLAIM_SCALE, CLAIM_MAX) await pool.deployed() - await pool.setAdmin(deployer.address) - tree = generateTreeFromData({ [alice.address]: ALICE_SCORE, [bob.address]: BOB_SCORE, @@ -86,7 +84,7 @@ describe('Pool Unit', () => { }) }) - describe('Test constructor', async () => { + describe('constructor', async () => { it('constructor sets values', async () => { const storedScale = await pool.scale() const storedMax = await pool.max() @@ -118,10 +116,11 @@ describe('Pool Unit', () => { }) }) - describe('Test admin functions', async () => { + describe('admin functions', async () => { it('enable a claim for a merkle root suceeds', async () => { const root = getRoot(tree) - expect(await pool.connect(deployer).enable(TREE, root)).to.emit( + await pool.setAdmin(alice.address) + expect(await pool.connect(alice).enable(TREE, root)).to.emit( pool, 'Enable' ) @@ -136,16 +135,17 @@ describe('Pool Unit', () => { it('enable a with the same tree overrwrites the previous root', async () => { const root = getRoot(tree) - await pool.connect(deployer).enable(TREE, root) + await pool.setAdmin(alice.address) + await pool.connect(alice).enable(TREE, root) const newRoot = getRoot(newTree) - await expect(pool.connect(deployer).enable(TREE, newRoot)).to.be.emit( + await expect(pool.connect(alice).enable(TREE, newRoot)).to.be.emit( pool, `Enable` ) }) }) - describe('Test withdraw', async () => { + describe('withdraw', async () => { it('withdraw success', async () => { await feeToken.mock.balanceOf.returns('100000') await feeToken.mock.transfer.returns(true) @@ -213,7 +213,7 @@ describe('Pool Unit', () => { bob.address ) ) - .to.be.revertedWith(`TreeDisabled`) + .to.be.revertedWith(`TreeNotEnabled`) .withArgs(TREE) const isClaimed = await pool.claimed(TREE, bob.address) @@ -257,7 +257,7 @@ describe('Pool Unit', () => { WITHDRAW_MINIMUM, bob.address ) - ).to.be.revertedWith(`AlreadyClaimed`) + ).to.be.revertedWith(`ClaimAlreadyUsed`) const isClaimed = await pool.claimed(TREE, bob.address) expect(isClaimed).to.equal(true) @@ -356,8 +356,8 @@ describe('Pool Unit', () => { }) }) - describe('Test Calculate', async () => { - it('Test calculation input and output', async () => { + describe('Calculate', async () => { + it('calculation input and output', async () => { await feeToken.mock.balanceOf.returns('100000') const amount = await pool.calculate(ALICE_SCORE, feeToken.address) @@ -365,8 +365,8 @@ describe('Pool Unit', () => { }) }) - describe('Test Verify', async () => { - it('Test verification is valid', async () => { + describe('Verify', async () => { + it('verification is valid', async () => { await pool.setAdmin(alice.address) const root = getRoot(tree) await pool.connect(alice).enable(TREE, root) @@ -376,7 +376,7 @@ describe('Pool Unit', () => { expect(isValid).to.be.equal(true) }) - it('Test verification is invalid with wrong participant', async () => { + it('verification is invalid with wrong participant', async () => { await pool.setAdmin(alice.address) const root = getRoot(tree) await pool.connect(alice).enable(TREE, root) @@ -386,7 +386,7 @@ describe('Pool Unit', () => { expect(isValid).to.be.equal(false) }) - it('Test verification is invalid with wrong scroe', async () => { + it('verification is invalid with wrong scroe', async () => { await pool.setAdmin(alice.address) const root = getRoot(tree) await pool.connect(alice).enable(TREE, root) @@ -397,21 +397,21 @@ describe('Pool Unit', () => { }) }) - describe('Test setting Scale', async () => { - it('Test setScale is successful', async () => { + describe('setting Scale', async () => { + it('setScale is successful', async () => { const scale = 77 await expect(pool.setScale(scale)).to.emit(pool, 'SetScale') expect(await pool.scale()).to.be.equal(`${scale}`) }) - it('Test setScale reverts when not owner', async () => { + it('setScale reverts when not owner', async () => { const scale = 77 await expect(pool.connect(alice).setScale(scale)).to.be.revertedWith( 'Ownable: caller is not the owner' ) }) - it('Test setScale reverts', async () => { + it('setScale reverts', async () => { const scale = 1000 await expect(pool.setScale(scale)) .to.be.revertedWith(`ScaleTooHigh`) @@ -419,21 +419,21 @@ describe('Pool Unit', () => { }) }) - describe('Test setting Max', async () => { - it('Test setMax is successful', async () => { + describe('setting Max', async () => { + it('setMax is successful', async () => { const max = 10 await expect(pool.setMax(max)).to.emit(pool, 'SetMax') expect(await pool.scale()).to.be.equal(`${max}`) }) - it('Test setMax reverts when not owner', async () => { + it('setMax reverts when not owner', async () => { const max = 10 await expect(pool.connect(alice).setMax(max)).to.be.revertedWith( 'Ownable: caller is not the owner' ) }) - it('Test setMax reverts', async () => { + it('setMax reverts', async () => { const max = 101 await expect(pool.setMax(max)) .to.be.revertedWith(`MaxTooHigh`) @@ -441,46 +441,79 @@ describe('Pool Unit', () => { }) }) - describe('Test setting admin', async () => { - it('Test setAdmin is successful', async () => { + describe('setting admin', async () => { + it('setAdmin is successful', async () => { await expect(pool.setAdmin(alice.address)).to.emit(pool, 'SetAdmin') expect(await pool.admins(alice.address)).to.be.equal(true) }) - it('Test setAdmin reverts', async () => { + it('setAdmin reverts', async () => { await expect( pool.connect(alice).setAdmin(alice.address) ).to.be.revertedWith('Ownable: caller is not the owner') }) - it('Test setAdmin reverts with zero address', async () => { + it('setAdmin reverts with zero address', async () => { await expect(pool.connect(deployer).setAdmin(ADDRESS_ZERO)) .to.be.revertedWith(`AddressInvalid`) .withArgs(ADDRESS_ZERO) }) - it('Test unsetAdmin is successful', async () => { + it('unsetAdmin is successful', async () => { await expect(pool.setAdmin(alice.address)).to.emit(pool, 'SetAdmin') await expect(pool.unsetAdmin(alice.address)).to.emit(pool, 'UnsetAdmin') expect(await pool.admins(alice.address)).to.be.equal(false) }) - it('Test unsetAdmin reverts', async () => { + it('unsetAdmin reverts', async () => { await expect(pool.setAdmin(alice.address)).to.emit(pool, 'SetAdmin') await expect( pool.connect(alice).unsetAdmin(alice.address) ).to.be.revertedWith('Ownable: caller is not the owner') }) - it('Test unsetAdmin executed by non-admin reverts', async () => { + it('unsetAdmin executed by non-admin reverts', async () => { await expect(pool.connect(deployer).unsetAdmin(alice.address)) .to.be.revertedWith(`AdminNotSet`) .withArgs(alice.address) }) }) - describe('Test drain to', async () => { - it('Test drain to is successful', async () => { + describe('migration functions', async () => { + it('set claimed as owner is successful', async () => { + await feeToken.mock.balanceOf.returns('100000') + await await pool.connect(deployer).setAdmin(alice.address) + + const root = getRoot(tree) + await pool.connect(alice).enableAndSetClaimed(TREE, root, [bob.address]) + + const proof = getProof(tree, soliditySha3(bob.address, BOB_SCORE)) + await expect( + pool.connect(bob).withdraw( + [ + { + tree: TREE, + value: BOB_SCORE, + proof, + }, + ], + feeToken.address, + WITHDRAW_MINIMUM, + bob.address + ) + ).to.be.revertedWith(`ClaimAlreadyUsed`) + }) + + it('set claimed with non-owner reverts', async () => { + await feeToken.mock.balanceOf.returns('100000') + + const root = getRoot(tree) + await expect( + pool.connect(bob).enableAndSetClaimed(TREE, root, [bob.address]) + ).to.be.revertedWith('Unauthorized') + }) + + it('drain to is successful', async () => { await feeToken.mock.balanceOf.returns('10') await feeToken.mock.transfer.returns(true) await feeToken2.mock.balanceOf.returns('10') @@ -493,7 +526,7 @@ describe('Pool Unit', () => { ).to.emit(pool, 'DrainTo') }) - it('Test drain to is only callable by owner', async () => { + it('drain to is only callable by owner', async () => { await expect( pool .connect(alice)