From 214c0037ed0164ca64e31279a1c5c6bfcd023105 Mon Sep 17 00:00:00 2001 From: Maximilian Goisser Date: Fri, 1 Feb 2019 16:03:03 +0100 Subject: [PATCH] return penalty to redeemer if revert is not possible --- contracts/gateway/EIP20CoGateway.sol | 44 ++++++++++- contracts/gateway/EIP20Gateway.sol | 4 +- .../progress_redeem_with_proof.js | 75 ++++++++++++++++++- 3 files changed, 119 insertions(+), 4 deletions(-) diff --git a/contracts/gateway/EIP20CoGateway.sol b/contracts/gateway/EIP20CoGateway.sol index 8910f67a..5ed384c6 100644 --- a/contracts/gateway/EIP20CoGateway.sol +++ b/contracts/gateway/EIP20CoGateway.sol @@ -546,8 +546,9 @@ contract EIP20CoGateway is GatewayBase { ); MessageBus.Message storage message = messages[_messageHash]; + MessageBus.MessageStatus outboxMessageStatus = + messageBox.outbox[_messageHash]; - redeemer_ = message.sender; redeemAmount_ = redeems[_messageHash].amount; MessageBus.progressOutboxWithProof( @@ -559,9 +560,50 @@ contract EIP20CoGateway is GatewayBase { MessageBus.MessageStatus(_messageStatus) ); + uint256 bountyAmount = redeems[_messageHash].bounty; (redeemer_, redeemAmount_) = progressRedeemInternal(_messageHash, message, true, bytes32(0)); + // Return revert penalty to redeemer if message is already progressed + // and can't be reverted anymore. + tryReturnPenaltyToRedeemer( + address(uint160(redeemer_)), // cast to address payable + outboxMessageStatus, + MessageBus.MessageStatus(_messageStatus), + bountyAmount + ); + } + + /** + * @notice Return the revert penalty to the redeemer. Only valid for + * a message transition from DeclaredRevocation -> Progressed. + * + * @dev Should only be called from progressRedeemWithProof. This function + * exists to avoid a stack too deep error. + * + * @param _redeemer Redeemer address. + * @param _outboxMessageStatus Message status before progressing. + * @param _inboxMessageStatus Message status after progressing. + * @param _bountyAmount Bounty amount to use for calculating penalty. + */ + function tryReturnPenaltyToRedeemer( + address payable _redeemer, + MessageBus.MessageStatus _outboxMessageStatus, + MessageBus.MessageStatus _inboxMessageStatus, + uint256 _bountyAmount + ) + private + { + if (_outboxMessageStatus != MessageBus.MessageStatus.DeclaredRevocation) { + return; + } + if (_inboxMessageStatus != MessageBus.MessageStatus.Progressed) { + return; + } + + // Penalty charged to staker for revert redeem. + uint256 penalty = penaltyFromBounty(_bountyAmount); + _redeemer.transfer(penalty); } /** diff --git a/contracts/gateway/EIP20Gateway.sol b/contracts/gateway/EIP20Gateway.sol index 8ffd929d..d565ab5f 100644 --- a/contracts/gateway/EIP20Gateway.sol +++ b/contracts/gateway/EIP20Gateway.sol @@ -498,9 +498,9 @@ contract EIP20Gateway is GatewayBase { // Return revert penalty to staker if message is already progressed // and can't be reverted anymore. tryReturnPenaltyToStaker( - message.sender, + staker_, outboxMessageStatus, - MessageBus.MessageStatus(_messageStatus), // inbox message status + MessageBus.MessageStatus(_messageStatus), bountyAmount ); } diff --git a/test/gateway/eip20_cogateway/progress_redeem_with_proof.js b/test/gateway/eip20_cogateway/progress_redeem_with_proof.js index 951354a1..166ac69c 100644 --- a/test/gateway/eip20_cogateway/progress_redeem_with_proof.js +++ b/test/gateway/eip20_cogateway/progress_redeem_with_proof.js @@ -34,7 +34,8 @@ const MessageStatusEnum = messageBus.MessageStatusEnum; contract('EIP20CoGateway.progressRedeemWithProof() ', function (accounts) { - let utilityToken, eip20CoGateway, redeemParams, bountyAmount, owner, facilitator; + let utilityToken, eip20CoGateway, redeemParams, bountyAmount, penaltyAmount, owner, facilitator; + const PENALTY_MULTIPLIER = 1.5; let setStorageRoot = async function() { @@ -81,6 +82,7 @@ contract('EIP20CoGateway.progressRedeemWithProof() ', function (accounts) { ); bountyAmount = new BN(proofData.co_gateway.constructor.bounty); + penaltyAmount = bountyAmount.muln(PENALTY_MULTIPLIER); eip20CoGateway = await EIP20CoGateway.new( proofData.co_gateway.constructor.valueToken, @@ -380,6 +382,14 @@ contract('EIP20CoGateway.progressRedeemWithProof() ', function (accounts) { it('should pass when message inbox status at target is progressed and outbox' + ' status at source is revocation declared', async function () { + await web3.eth.sendTransaction( + { + to: eip20CoGateway.address, + from: facilitator, + value: penaltyAmount, + } + ); + await eip20CoGateway.setOutboxStatus( redeemParams.messageHash, MessageStatusEnum.DeclaredRevocation, @@ -521,6 +531,69 @@ contract('EIP20CoGateway.progressRedeemWithProof() ', function (accounts) { }); + it('should return penalty to redeemer when the message status in source is ' + + 'declared revocation and in the target is progressed', async function () { + const facilitator = accounts[8]; + const redeemer = redeemParams.redeemer; + + await web3.eth.sendTransaction( + { + to: eip20CoGateway.address, + from: facilitator, + value: penaltyAmount, + } + ); + + await eip20CoGateway.setOutboxStatus( + redeemParams.messageHash, + MessageStatusEnum.DeclaredRevocation, + ); + + const initialFacilitatorEthBalance = await Utils.getBalance(facilitator); + const initialRedeemerEthBalance = await Utils.getBalance(redeemer); + const initialCoGatewayEthBalance = await Utils.getBalance(eip20CoGateway.address); + + await setStorageRoot(); + + const tx = await eip20CoGateway.progressRedeemWithProof( + redeemParams.messageHash, + proofData.gateway.progress_unstake.proof_data.storageProof[0].serializedProof, + new BN(proofData.gateway.progress_unstake.proof_data.block_number, 16), + MessageStatusEnum.Progressed, + { from: facilitator }, + ); + + const finalFacilitatorEthBalance = await Utils.getBalance(facilitator); + const finalRedeemerEthBalance = await Utils.getBalance(redeemer); + const finalCoGatewayEthBalance = await Utils.getBalance(eip20CoGateway.address); + + const expectedFinalFacilitatorETHBalance = initialFacilitatorEthBalance + .add(bountyAmount) + .subn(tx.receipt.gasUsed); + + const expectedFinalRedeemerETHBalance = initialRedeemerEthBalance + .add(penaltyAmount); + + assert.strictEqual( + finalFacilitatorEthBalance.eq(expectedFinalFacilitatorETHBalance), + true, + `Facilitator's base token balance ${finalFacilitatorEthBalance.toString(10)} should be equal to ${expectedFinalFacilitatorETHBalance.toString(10)}`, + ); + + assert.strictEqual( + finalRedeemerEthBalance.eq(expectedFinalRedeemerETHBalance), + true, + `Redeemer's base token balance ${finalRedeemerEthBalance.toString(10)} should be equal to ${expectedFinalRedeemerETHBalance.toString(10)}`, + ); + + assert.strictEqual( + finalCoGatewayEthBalance.eq(initialCoGatewayEthBalance.sub(bountyAmount).sub(penaltyAmount)), + true, + `CoGateway's base token balance ${finalCoGatewayEthBalance.toString(10)} should be equal to ${initialCoGatewayEthBalance.sub(bountyAmount).sub(penaltyAmount)}.`, + ); + + }); + it('should decrease token supply for utility token', async function () { let initialTotalSupply = await utilityToken.totalSupply.call();