diff --git a/contracts/registry/UsernameRegistrar.sol b/contracts/registry/UsernameRegistrar.sol index ed32ed0..1e64827 100644 --- a/contracts/registry/UsernameRegistrar.sol +++ b/contracts/registry/UsernameRegistrar.sol @@ -35,7 +35,7 @@ contract UsernameRegistrar is Controlled, ApproveAndCallFallBack { bytes32 public ensNode; uint256 public price; RegistrarState public state; - uint256 private reserveAmount; + uint256 public reserveAmount; struct Account { uint256 balance; @@ -181,9 +181,10 @@ contract UsernameRegistrar is Controlled, ApproveAndCallFallBack { /** * @notice secretly reserve the slashing reward to `msg.sender` - * @param _secret keccak256(abi.encodePacked(namehash, owner, creationTime)) + * @param _secret keccak256(abi.encodePacked(namehash, creationTime)) */ function reserveSlash(bytes32 _secret) external { + require(reservedSlashers[_secret] == address(0), "Already Reserved"); reservedSlashers[_secret] = msg.sender; } @@ -367,7 +368,7 @@ contract UsernameRegistrar is Controlled, ApproveAndCallFallBack { } /** - * @notice Withdraw tokens wrongly sent to the contract. + * @notice Withdraw not reserved tokens * @param _token Address of ERC20 withdrawing excess, or address(0) if want ETH. * @param _beneficiary Address to send the funds. **/ @@ -388,7 +389,7 @@ contract UsernameRegistrar is Controlled, ApproveAndCallFallBack { require(amount > reserveAmount, "Is not excess"); amount -= reserveAmount; } else { - require(amount > 0, "Is not excess"); + require(amount > 0, "No balance"); } excessToken.transfer(_beneficiary, amount); } @@ -483,7 +484,7 @@ contract UsernameRegistrar is Controlled, ApproveAndCallFallBack { /** * @notice returns address that reserved slashing of an account * @param _label Username hash. - * @return Exact time when username can be released. + * @return Reserved Slasher **/ function getReservedSlasher(bytes32 _label) external @@ -492,11 +493,26 @@ contract UsernameRegistrar is Controlled, ApproveAndCallFallBack { { bytes32 namehash = keccak256(abi.encodePacked(ensNode, _label)); uint256 creationTime = accounts[_label].creationTime; - address owner = ensRegistry.owner(namehash); - bytes32 secret = keccak256(abi.encodePacked(namehash, owner, creationTime)); + bytes32 secret = keccak256(abi.encodePacked(namehash, creationTime)); reservedSlasher = reservedSlashers[secret]; } + /** + * @notice calculate reward part an account could payout on slash + * @param _label Username hash. + * @return Part of reward + **/ + function getSlashRewardPart(bytes32 _label) + external + view + returns(uint256 partReward) + { + uint256 balance = accounts[_label].balance; + if (balance > 0) { + partReward = balance / 3; + } + } + /** * @notice Support for "approveAndCall". Callable only by `token()`. * @param _from Who approved. @@ -661,14 +677,20 @@ contract UsernameRegistrar is Controlled, ApproveAndCallFallBack { if (amountToTransfer > 0) { reserveAmount -= amountToTransfer; - address receiver = msg.sender; - bytes32 secret = keccak256(abi.encodePacked(namehash, owner, creationTime)); + uint256 partialDeposit = amountToTransfer / 3; + amountToTransfer = partialDeposit * 2; // reserve 1/3 to network (controller) + address slasher = msg.sender; + bytes32 secret = keccak256(abi.encodePacked(namehash, creationTime)); address reservedSlasher = reservedSlashers[secret]; if (reservedSlasher != address(0)) { delete reservedSlashers[secret]; - receiver = reservedSlasher; + if (reservedSlasher != msg.sender) { + amountToTransfer -= partialDeposit; // 1/3 goes to msg.sender + require(token.transfer(msg.sender, partialDeposit), "Error in transfer."); + } + slasher = reservedSlasher; // reservedSlasher will receive the amountToTransfer instead of msg.sender } - require(token.transfer(receiver, amountToTransfer), "Error in transfer."); + require(token.transfer(slasher, amountToTransfer), "Error in transfer."); } emit UsernameOwner(namehash, address(0)); } diff --git a/test/usernameregistrar.spec.js b/test/usernameregistrar.spec.js index 8002e7d..ce88d51 100644 --- a/test/usernameregistrar.spec.js +++ b/test/usernameregistrar.spec.js @@ -813,20 +813,22 @@ contract('UsernameRegistrar', function () { const usernameHash = namehash.hash(username + '.' + registry.registry); const registrant = accountsArr[1]; const slasher = accountsArr[2]; + const label = web3Utils.sha3(username); await TestToken.methods.mint(registry.price).send({from: registrant}); await TestToken.methods.approve(UsernameRegistrar.address, registry.price).send({from: registrant}); await UsernameRegistrar.methods.register( - web3Utils.sha3(username), + label, utils.zeroAddress, utils.zeroBytes32, utils.zeroBytes32 ).send({from: registrant}); await utils.increaseTime(20000) + const partReward = await UsernameRegistrar.methods.getSlashRewardPart(label).call(); assert.equal(await ens.methods.owner(usernameHash).call(), registrant); const initialSlasherBalance = await TestToken.methods.balanceOf(slasher).call(); await UsernameRegistrar.methods.slashSmallUsername(username).send({from: slasher}) //TODO: check events - assert.equal(await TestToken.methods.balanceOf(slasher).call(), (+initialSlasherBalance)+(+registry.price)); + assert.equal(await TestToken.methods.balanceOf(slasher).call(), (+initialSlasherBalance)+((+partReward)*2)); assert.equal(await ens.methods.owner(usernameHash).call(), utils.zeroAddress); }); @@ -872,7 +874,7 @@ contract('UsernameRegistrar', function () { }); describe('reserveSlash()', function() { - it('should send funds to reserver', async() =>{ + it('should send 1/3 funds to reserver and 1/3 of funds to caller', async() =>{ const username = 'c'; const label = web3Utils.sha3(username); const usernameHash = namehash.hash(username + '.' + registry.registry); @@ -889,15 +891,48 @@ contract('UsernameRegistrar', function () { ).send({from: registrant}); await utils.increaseTime(20000) assert.equal(await ens.methods.owner(usernameHash).call(), registrant); - const initialSlasherBalance = await TestToken.methods.balanceOf(slashReserver).call(); + const partReward = await UsernameRegistrar.methods.getSlashRewardPart(label).call(); + const initialSlashReserverBalance = await TestToken.methods.balanceOf(slashReserver).call(); + const initialSlashCallerBalance = await TestToken.methods.balanceOf(slashCaller).call(); const creationTime = await UsernameRegistrar.methods.getCreationTime(label).call(); - const secret = web3Utils.soliditySha3(usernameHash, registrant, creationTime); + const secret = web3Utils.soliditySha3(usernameHash, creationTime); assert.equal(await UsernameRegistrar.methods.getReservedSlasher(label).call(), utils.zeroAddress); await UsernameRegistrar.methods.reserveSlash(secret).send({from: slashReserver}); assert.equal(await UsernameRegistrar.methods.getReservedSlasher(label).call(), slashReserver); await UsernameRegistrar.methods.slashSmallUsername(username).send({from: slashCaller}) //TODO: check events - assert.equal(await TestToken.methods.balanceOf(slashReserver).call(), (+initialSlasherBalance)+(+registry.price)); + assert.equal(await TestToken.methods.balanceOf(slashReserver).call(), (+initialSlashReserverBalance)+(+partReward)); + assert.equal(await TestToken.methods.balanceOf(slashCaller).call(), (+initialSlashCallerBalance)+(+partReward)); + assert.equal(await ens.methods.owner(usernameHash).call(), utils.zeroAddress); + }); + + + it('should send 2/3 funds to reserver as is also the caller', async() =>{ + const username = 'c'; + const label = web3Utils.sha3(username); + const usernameHash = namehash.hash(username + '.' + registry.registry); + const registrant = accountsArr[1]; + const slashReserverCaller = accountsArr[2]; + await TestToken.methods.mint(registry.price).send({from: registrant}); + await TestToken.methods.approve(UsernameRegistrar.address, registry.price).send({from: registrant}); + await UsernameRegistrar.methods.register( + web3Utils.sha3(username), + utils.zeroAddress, + utils.zeroBytes32, + utils.zeroBytes32 + ).send({from: registrant}); + await utils.increaseTime(20000) + assert.equal(await ens.methods.owner(usernameHash).call(), registrant); + const partReward = await UsernameRegistrar.methods.getSlashRewardPart(label).call(); + const initialSlashReserverBalance = await TestToken.methods.balanceOf(slashReserverCaller).call(); + const creationTime = await UsernameRegistrar.methods.getCreationTime(label).call(); + const secret = web3Utils.soliditySha3(usernameHash, creationTime); + assert.equal(await UsernameRegistrar.methods.getReservedSlasher(label).call(), utils.zeroAddress); + await UsernameRegistrar.methods.reserveSlash(secret).send({from: slashReserverCaller}); + assert.equal(await UsernameRegistrar.methods.getReservedSlasher(label).call(), slashReserverCaller); + await UsernameRegistrar.methods.slashSmallUsername(username).send({from: slashReserverCaller}) + //TODO: check events + assert.equal(await TestToken.methods.balanceOf(slashReserverCaller).call(), (+initialSlashReserverBalance)+(+partReward*2)); assert.equal(await ens.methods.owner(usernameHash).call(), utils.zeroAddress); }); });