Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return penalty to staker if target chain has already progressed #576

Merged
merged 6 commits into from
Feb 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not be multiplied with gas price?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gas price is set to 1 in truffle.js, which I think is the reason that it works. But yeah, in theory it would need to be multiplied with the gas price.


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);
deepesh-kn marked this conversation as resolved.
Show resolved Hide resolved
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.",
);

});
});