Skip to content

Commit

Permalink
Fix alternative receiver behaviour for amb-erc-to-erc (#408)
Browse files Browse the repository at this point in the history
  • Loading branch information
k1rill-fedoseev committed Apr 21, 2020
1 parent c8632cd commit 75c3f95
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,16 @@ contract BasicAMBErc677ToErc677 is
return mediatorContractOnOtherSide();
}

function passMessage(address _from, uint256 _value) internal {
/**
* @dev Constructs and passes a message to the AMB bridge contract.
* Message represents a call of handleBridgedTokens(receiver, value, nonce) on the other side mediator contract.
* @param _from adddress of sender, if bridge operation failes, tokens will be returned to this address
* @param _receiver adddress of receiver on the other side, will eventually receive bridged tokens
* @param _value bridged amount of tokens
*/
function passMessage(address _from, address _receiver, uint256 _value) internal {
bytes4 methodSelector = this.handleBridgedTokens.selector;
bytes memory data = abi.encodeWithSelector(methodSelector, _from, _value, nonce());
bytes memory data = abi.encodeWithSelector(methodSelector, _receiver, _value, nonce());

bytes32 dataHash = keccak256(data);
setMessageHashValue(dataHash, _value);
Expand Down Expand Up @@ -231,6 +238,12 @@ contract BasicAMBErc677ToErc677 is
}
}

/**
* @dev Fixes locked tokens, that were out of execution limits during the call to handleBridgedTokens
* @param txHash reference transaction hash for bridge operation that was out of execution limits
* @param unlockOnForeign true if fixed tokens should be unlocked to the other side of the bridge
* @param valueToUnlock unlocked amount of tokens, should be less than maxPerTx() and saved txAboveLimitsValue
*/
function fixAssetsAboveLimits(bytes32 txHash, bool unlockOnForeign, uint256 valueToUnlock)
external
onlyIfUpgradeabilityOwner
Expand All @@ -249,7 +262,7 @@ contract BasicAMBErc677ToErc677 is
setFixedAssets(txHash);
}
if (unlockOnForeign) {
passMessage(recipient, valueToUnlock);
passMessage(recipient, recipient, valueToUnlock);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,20 @@ contract ForeignAMBErc677ToErc677 is BasicAMBErc677ToErc677 {
erc677token().transfer(_recipient, value);
}

/**
* @dev Executes action on deposit of bridged tokens
* @param _from address of tokens sender
* @param _value requsted amount of bridged tokens
* @param _data alternative receiver, if specified
*/
function bridgeSpecificActionsOnTokenTransfer(
ERC677, /* _token */
address _from,
uint256 _value,
bytes _data
) internal {
if (!lock()) {
passMessage(chooseReceiver(_from, _data), _value);
passMessage(_from, chooseReceiver(_from, _data), _value);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract ForeignStakeTokenMediator is BasicStakeTokenMediator {
bytes _data
) internal {
if (!lock()) {
passMessage(chooseReceiver(_from, _data), _value);
passMessage(_from, chooseReceiver(_from, _data), _value);
}
}

Expand All @@ -48,7 +48,7 @@ contract ForeignStakeTokenMediator is BasicStakeTokenMediator {
function _transferWithOptionalMint(address _recipient, uint256 _value) internal {
IBurnableMintableERC677Token token = IBurnableMintableERC677Token(erc677token());
uint256 balance = token.balanceOf(address(this));
if (_recipient != address(0) && balance == 0) {
if (balance == 0) {
token.mint(_recipient, _value);
} else if (balance < _value) {
token.mint(address(this), _value - balance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ contract HomeAMBErc677ToErc677 is BasicAMBErc677ToErc677 {
IBurnableMintableERC677Token(erc677token()).mint(_recipient, value);
}

/**
* @dev Executes action on withdrawal of bridged tokens
* @param _token address of token contract
* @param _from address of tokens sender
* @param _value requsted amount of bridged tokens
* @param _data alternative receiver, if specified
*/
function bridgeSpecificActionsOnTokenTransfer(ERC677 _token, address _from, uint256 _value, bytes _data) internal {
if (!lock()) {
IBurnableMintableERC677Token(_token).burn(_value);
passMessage(chooseReceiver(_from, _data), _value);
passMessage(_from, chooseReceiver(_from, _data), _value);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ contract HomeStakeTokenMediator is BasicStakeTokenMediator, HomeStakeTokenFeeMan

if (address(_blockRewardContract()) == address(0)) {
// in case if block reward contract is not configured, the fee is not collected
passMessage(chooseReceiver(_from, _data), _value);
passMessage(_from, chooseReceiver(_from, _data), _value);
} else {
// when block reward contract is defined, the calculated fee is subtracted from the original value
uint256 fee = calculateFee(_value);
passMessage(chooseReceiver(_from, _data), _value.sub(fee));
passMessage(_from, chooseReceiver(_from, _data), _value.sub(fee));
if (fee > 0) {
// the fee itself is distributed later in the block reward contract
_blockRewardContract().addBridgeTokenRewardReceivers(fee);
Expand Down
84 changes: 83 additions & 1 deletion test/amb_erc677_to_erc677/AMBErc677ToErc677Behavior.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function shouldBehaveLikeBasicAMBErc677ToErc677(otherSideMediatorContract, accou
let erc677Token
const owner = accounts[0]
const user = accounts[1]
const user2 = accounts[2]
describe('initialize', () => {
beforeEach(async () => {
bridgeContract = await AMBMock.new()
Expand Down Expand Up @@ -498,7 +499,6 @@ function shouldBehaveLikeBasicAMBErc677ToErc677(otherSideMediatorContract, accou
describe('relayTokens', () => {
let contract
let erc20Token
const user2 = accounts[2]
beforeEach(async function() {
bridgeContract = await AMBMock.new()
await bridgeContract.setMaxGasPerTx(maxGasPerTx)
Expand Down Expand Up @@ -992,6 +992,88 @@ function shouldBehaveLikeBasicAMBErc677ToErc677(otherSideMediatorContract, accou
expect(event[0].returnValues.value).to.be.equal(oneEther.toString())
})
})
describe('fixFailedMessage for alternative receiver', () => {
let dataHash
let contract
beforeEach(async function() {
bridgeContract = await AMBMock.new()
await bridgeContract.setMaxGasPerTx(maxGasPerTx)
mediatorContract = await otherSideMediatorContract.new()
erc677Token = await ERC677BridgeToken.new('test', 'TST', 18)
await erc677Token.mint(user, twoEthers, { from: owner }).should.be.fulfilled

contract = this.bridge

await contract.initialize(
bridgeContract.address,
mediatorContract.address,
erc677Token.address,
[dailyLimit, maxPerTx, minPerTx],
[executionDailyLimit, executionMaxPerTx],
maxGasPerTx,
decimalShiftZero,
owner
).should.be.fulfilled
await erc677Token.transferOwnership(contract.address)

expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(twoEthers)
expect(await erc677Token.totalSupply()).to.be.bignumber.equal(twoEthers)

// User transfer tokens
const transferTx = await erc677Token.transferAndCall(contract.address, oneEther, user2, { from: user }).should.be
.fulfilled

expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(oneEther)

const events = await getEvents(bridgeContract, { event: 'MockedEvent' })
expect(events.length).to.be.equal(1)
const data = `0x${events[0].returnValues.encodedData.substr(
148,
events[0].returnValues.encodedData.length - 148
)}`

// Bridge calls mediator from other side
await bridgeContract.executeMessageCall(
contract.address,
contract.address,
data,
transferTx.tx,
100
).should.be.fulfilled

expect(await bridgeContract.messageCallStatus(transferTx.tx)).to.be.equal(false)

// mediator from other side should use this dataHash to request fix the failed message
dataHash = await bridgeContract.failedMessageDataHash(transferTx.tx)
})
it('should fix burnt/locked tokens', async () => {
// Given
expect(await contract.messageHashFixed(dataHash)).to.be.equal(false)

// When
const fixData = await contract.contract.methods.fixFailedMessage(dataHash).encodeABI()

await bridgeContract.executeMessageCall(
contract.address,
mediatorContract.address,
fixData,
exampleTxHash,
1000000
).should.be.fulfilled

// Then
expect(await bridgeContract.messageCallStatus(exampleTxHash)).to.be.equal(true)
expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(twoEthers)
expect(await erc677Token.totalSupply()).to.be.bignumber.equal(twoEthers)
expect(await contract.messageHashFixed(dataHash)).to.be.equal(true)

const event = await getEvents(contract, { event: 'FailedMessageFixed' })
expect(event.length).to.be.equal(1)
expect(event[0].returnValues.dataHash).to.be.equal(dataHash)
expect(event[0].returnValues.recipient).to.be.equal(user)
expect(event[0].returnValues.value).to.be.equal(oneEther.toString())
})
})
describe('#claimTokens', () => {
it('should be able to claim tokens', async function() {
const contract = this.proxyContract
Expand Down

0 comments on commit 75c3f95

Please sign in to comment.