Skip to content

Commit

Permalink
Merge pull request #576 from hobofan/gh_409
Browse files Browse the repository at this point in the history
Return penalty to staker if target chain has already progressed
  • Loading branch information
hobofan authored Feb 5, 2019
2 parents 6d8b795 + 4019685 commit 95abdb3
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 4 deletions.
44 changes: 43 additions & 1 deletion contracts/gateway/EIP20CoGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 redeemer for revert redeem.
uint256 penalty = penaltyFromBounty(_bountyAmount);
_redeemer.transfer(penalty);
}

/**
Expand Down
49 changes: 48 additions & 1 deletion contracts/gateway/EIP20Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,8 @@ contract EIP20Gateway is GatewayBase {

// Get the message object
MessageBus.Message storage message = messages[_messageHash];
MessageBus.MessageStatus outboxMessageStatus =
messageBox.outbox[_messageHash];

MessageBus.progressOutboxWithProof(
messageBox,
Expand All @@ -485,13 +487,58 @@ contract EIP20Gateway is GatewayBase {
MessageBus.MessageStatus(_messageStatus)
);

uint256 bountyAmount = stakes[_messageHash].bounty;
(staker_, stakeAmount_) = progressStakeInternal(
_messageHash,
message,
bytes32(0),
true
);

// Return revert penalty to staker if message is already progressed
// and can't be reverted anymore.
tryReturnPenaltyToStaker(
staker_,
outboxMessageStatus,
MessageBus.MessageStatus(_messageStatus),
bountyAmount
);
}

/**
* @notice Return the revert penalty to the staker. Only valid for
* a message transition from DeclaredRevocation -> Progressed.
*
* @dev Should only be called from progressStakeWithProof. This function
* exists to avoid a stack too deep error.
*
* @param _staker Staker address.
* @param _outboxMessageStatus Message status before progressing.
* @param _inboxMessageStatus Message status after progressing.
* @param _bountyAmount Bounty amount to use for calculating penalty.
*/
function tryReturnPenaltyToStaker(
address _staker,
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 stake.
uint256 penalty = penaltyFromBounty(_bountyAmount);
// transfer the penalty amount
require(
baseToken.transfer(_staker, penalty),
"Penalty amount transfer to staker failed"
);
}

/**
Expand Down Expand Up @@ -1087,7 +1134,7 @@ contract EIP20Gateway is GatewayBase {
// Get the staker address
staker_ = _message.sender;

//Get the stake amount.
// Get the stake amount.
stakeAmount_ = stakes[_messageHash].amount;

require(
Expand Down
75 changes: 74 additions & 1 deletion test/gateway/eip20_cogateway/progress_redeem_with_proof.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
78 changes: 77 additions & 1 deletion test/gateway/eip20_gateway/progress_stake_with_proof.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const MessageStatusEnum = messageBus.MessageStatusEnum;

contract('EIP20Gateway.progressStakeWithProof()', function (accounts) {

let gateway, mockToken, baseToken, stakeData, progressStakeParams, bountyAmount;
let gateway, mockToken, baseToken, stakeData, progressStakeParams, bountyAmount, penaltyAmount;
const PENALTY_MULTIPLIER = 1.5;

let setStorageRoot = async function() {

Expand Down Expand Up @@ -123,6 +124,7 @@ contract('EIP20Gateway.progressStakeWithProof()', function (accounts) {
let burner = NullAddress;

bountyAmount = new BN(proofData.gateway.constructor.bounty);
penaltyAmount = bountyAmount.muln(PENALTY_MULTIPLIER);

gateway = await Gateway.new(
mockToken.address,
Expand Down Expand Up @@ -312,6 +314,9 @@ contract('EIP20Gateway.progressStakeWithProof()', function (accounts) {
MessageStatusEnum.DeclaredRevocation,
);

// Fund Gateway with enough tokens that it can return penalty.
await baseToken.transfer(gateway.address, penaltyAmount, { from: accounts[0] });

await setStorageRoot();

let result = await gateway.progressStakeWithProof.call(
Expand Down Expand Up @@ -542,4 +547,75 @@ contract('EIP20Gateway.progressStakeWithProof()', function (accounts) {

});

it('should return penalty to staker when the message status in source is ' +
'declared revocation and in the target is progressed', async function () {

let stakeVault = await gateway.stakeVault.call();
let caller = accounts[0];
let staker = stakeData.staker;

await gateway.setOutboxStatus(
stakeData.messageHash,
MessageStatusEnum.DeclaredRevocation,
);

// Fund Gateway with enough tokens that it can return penalty.
await baseToken.transfer(gateway.address, penaltyAmount, { from: accounts[0] });

let callerInitialBaseTokenBalance = await baseToken.balanceOf(caller);
let stakerInitialBaseTokenBalance = await baseToken.balanceOf(staker);
let gatewayInitialTokenBalance = await mockToken.balanceOf(gateway.address);
let gatewayInitialBaseTokenBalance = await baseToken.balanceOf(gateway.address);
let stakeVaultInitialTokenBalance = await mockToken.balanceOf(stakeVault);

await setStorageRoot();

await gateway.progressStakeWithProof(
progressStakeParams.messageHash,
proofData.co_gateway.progress_mint.proof_data.storageProof[0].serializedProof,
new BN(proofData.co_gateway.progress_mint.proof_data.block_number),
MessageStatusEnum.Progressed,
);

let callerFinalBaseTokenBalance = await baseToken.balanceOf(caller);
let stakerFinalBaseTokenBalance = await baseToken.balanceOf(staker);
let gatewayFinalTokenBalance = await mockToken.balanceOf(gateway.address);
let gatewayFinalBaseTokenBalance = await baseToken.balanceOf(gateway.address);
let stakeVaultFinalTokenBalance = await mockToken.balanceOf(stakeVault);

assert.strictEqual(
callerFinalBaseTokenBalance.eq(callerInitialBaseTokenBalance.add(bountyAmount)),
true,
"Bounty should be returned to caller.",
);

assert.strictEqual(
stakerFinalBaseTokenBalance.eq(stakerInitialBaseTokenBalance.add(penaltyAmount)),
true,
`Staker's base token balance ${stakerFinalBaseTokenBalance.toString(10)} must be equal to ${stakerInitialBaseTokenBalance.add(penaltyAmount).toString(10)}`,
);

assert.strictEqual(
gatewayFinalTokenBalance.eq(gatewayInitialTokenBalance.sub(stakeData.amount)),
true,
"Gateway token balance should reduced by stake amount on successful " +
"progress stake.",
);

assert.strictEqual(
gatewayFinalBaseTokenBalance.eq(
gatewayInitialBaseTokenBalance.sub(bountyAmount).sub(penaltyAmount)
),
true,
`Gateway's base token balance ${gatewayFinalBaseTokenBalance.toString(10)} must be equal to ${gatewayInitialBaseTokenBalance.sub(bountyAmount).sub(penaltyAmount).toString(10)}.`,
);

assert.strictEqual(
stakeVaultFinalTokenBalance.eq(stakeVaultInitialTokenBalance.add(stakeData.amount)),
true,
"Stake vault token balance should increase by stake amount on " +
"successful progress stake.",
);

});
});

0 comments on commit 95abdb3

Please sign in to comment.