Skip to content

Commit f3ee829

Browse files
committed
finalized implementation of optional user tx fee payment via deposits, refs omni#1
1 parent 8c70e3a commit f3ee829

File tree

3 files changed

+105
-15
lines changed

3 files changed

+105
-15
lines changed

contracts/upgradeable_contracts/BasicForeignBridge.sol

+18-7
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ contract BasicForeignBridge is EternalStorage, Validatable {
1010
using SafeMath for uint256;
1111
/// triggered when relay of deposit from HomeBridge is complete
1212
event RelayedMessage(address recipient, uint value, bytes32 transactionHash);
13+
14+
/// Anybody can relay a task from the home chain. All required proof is contained in the signatures
1315
function executeSignatures(uint8[] vs, bytes32[] rs, bytes32[] ss, bytes message) external {
1416
Message.hasEnoughValidSignatures(message, vs, rs, ss, validatorContract());
17+
processMessage(message);
18+
}
19+
20+
function processMessage(bytes message) internal returns(address) {
1521
address recipient;
1622
uint256 amount;
1723
bytes32 txHash;
@@ -26,28 +32,33 @@ contract BasicForeignBridge is EternalStorage, Validatable {
2632
} else {
2733
onFailedMessage(recipient, amount, txHash);
2834
}
35+
return recipient;
2936
}
3037

3138
function () payable public {
3239
if(msg.value > 0) {
33-
addDeposit();
40+
addFeeDepositFor(msg.sender);
3441
} else {
35-
withdrawDeposit();
42+
withdrawFeeDeposit();
3643
}
3744
}
3845

39-
function addDeposit() payable public {
40-
// equivalent to deposits[msg.sender] += msg.value;
41-
uintStorage[keccak256(abi.encodePacked("feeDeposits", msg.sender))] += msg.value;
46+
function addFeeDepositFor(address addr) payable public {
47+
uintStorage[keccak256(abi.encodePacked("feeDeposits", addr))] += msg.value;
4248
}
4349

44-
function withdrawDeposit() public {
50+
function withdrawFeeDeposit() public {
4551
uint256 withdrawAmount = uintStorage[keccak256(abi.encodePacked("feeDeposits", msg.sender))];
46-
require(withdrawAmount > 0);
52+
require(withdrawAmount > 0, "no fee deposits");
4753
delete uintStorage[keccak256(abi.encodePacked("feeDeposits", msg.sender))]; // implies setting the value to 0
4854
msg.sender.transfer(withdrawAmount); // throws on failure
4955
}
5056

57+
/// convenience method for checking current deposits of a given address
58+
function feeDepositOf(address addr) public view returns(uint256) {
59+
return uintStorage[keccak256(abi.encodePacked("feeDeposits", addr))];
60+
}
61+
5162
function onExecuteMessage(address, uint256) internal returns(bool);
5263

5364
function setRelayedMessages(bytes32 _txHash, bool _status) internal {

contracts/upgradeable_contracts/native_to_erc20/ForeignBridgeNativeToErc.sol

+29
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ contract ForeignBridgeNativeToErc is ERC677Receiver, BasicBridge, BasicForeignBr
1212

1313
/// Event created on money withdraw.
1414
event UserRequestForAffirmation(address recipient, uint256 value);
15+
event FeeWithdrawal(uint256 amount);
1516

1617
function initialize(
1718
address _validatorContract,
@@ -46,6 +47,34 @@ contract ForeignBridgeNativeToErc is ERC677Receiver, BasicBridge, BasicForeignBr
4647
return isInitialized();
4748
}
4849

50+
function executeSignaturesRecipientPays(uint8[] vs, bytes32[] rs, bytes32[] ss, bytes message) external {
51+
Message.hasEnoughValidSignatures(message, vs, rs, ss, validatorContract());
52+
address recipient = processMessage(message);
53+
54+
// check if recipient has enough deposits
55+
uint256 chargedFee = tx.gasprice * 200000; // 200k gas should always be enough for this tx
56+
require(uintStorage[keccak256(abi.encodePacked("feeDeposits", recipient))] >= chargedFee, "not enough fee deposits");
57+
// take from fee deposits
58+
uintStorage[keccak256(abi.encodePacked("feeDeposits", recipient))] -= chargedFee;
59+
uintStorage[keccak256(abi.encodePacked("feeDeposits", owner()))] += chargedFee;
60+
61+
/*
62+
* In case of an Exception in processMessage(), the recipient won't be charged for the failed tx.
63+
* This is on purpose. It's the relayer's responsibility to check if the tx would succeed before broadcasting it.
64+
* The recipient could game the relayer by having a fee deposit withdrawal tx race against the relay tx.
65+
* However there's no economic incentive to do so (relay tx would fail), thus this risk for the relayer
66+
* looks acceptable.
67+
*/
68+
}
69+
70+
function withdrawCollectedFees() external onlyIfOwnerOfProxy {
71+
uint256 amount = uintStorage[keccak256(abi.encodePacked("feeDeposits", owner()))];
72+
require(amount > 0, "nothing to claim");
73+
uintStorage[keccak256(abi.encodePacked("feeDeposits", owner()))] = 0;
74+
emit FeeWithdrawal(amount);
75+
msg.sender.transfer(amount); // throws on failure
76+
}
77+
4978
function getBridgeMode() public pure returns(bytes4 _data) {
5079
return bytes4(keccak256(abi.encodePacked("native-to-erc-core")));
5180
}

test/native_to_erc/foreign_bridge_test.js

+58-8
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ contract('ForeignBridge', async (accounts) => {
7272
})
7373
})
7474

75-
describe('#feeDeposit', async () => {
75+
describe('#user pays for tx via via fee deposit', async () => {
7676
const user1 = accounts[7]
7777
const user2 = accounts[8]
7878

79-
beforeEach(async () => {
79+
before(async () => {
8080
const owner = accounts[0];
8181
token = await POA20.new("POA ERC20 Foundation", "POA20", 18);
8282
const foreignBridgeImpl = await ForeignBridge.new();
@@ -87,19 +87,19 @@ contract('ForeignBridge', async (accounts) => {
8787
await token.transferOwnership(foreignBridge.address)
8888
})
8989

90-
it('should allow to make and withdraw deposits for fees', async () => {
90+
it('should allow to make and withdraw fee deposits', async () => {
9191
const user1BalanceBeforeDeposit = await web3.eth.getBalance(user1)
92-
let tx = await foreignBridge.sendTransaction({from: user1, value: 1}).should.be.fulfilled
92+
let tx = await foreignBridge.sendTransaction({from: user1, value: web3.toWei(1, 'ether')}).should.be.fulfilled
9393
let user1TxFees = new web3.BigNumber(gasPrice).mul(tx.receipt.gasUsed)
94-
tx = await foreignBridge.sendTransaction({from: user1, value: 3}).should.be.fulfilled
94+
tx = await foreignBridge.sendTransaction({from: user1, value: web3.toWei(3, 'ether')}).should.be.fulfilled
9595
user1TxFees = user1TxFees.add(new web3.BigNumber(gasPrice).mul(tx.receipt.gasUsed))
9696
let bridgeBalance = await web3.eth.getBalance(foreignBridge.address)
97-
bridgeBalance.should.be.bignumber.equal(4)
97+
bridgeBalance.should.be.bignumber.equal(web3.toWei(4, 'ether'))
9898

9999
const user2BalanceBeforeDeposit = await web3.eth.getBalance(user2)
100-
await foreignBridge.sendTransaction({from: user2, value: 2}).should.be.fulfilled
100+
await foreignBridge.sendTransaction({from: user2, value: web3.toWei(2, 'ether')}).should.be.fulfilled
101101
bridgeBalance = await web3.eth.getBalance(foreignBridge.address)
102-
bridgeBalance.should.be.bignumber.equal(6)
102+
bridgeBalance.should.be.bignumber.equal(web3.toWei(6, 'ether'))
103103

104104
// user1 withdraw all
105105
tx = await foreignBridge.sendTransaction({from: user1, value: 0}).should.be.fulfilled
@@ -114,6 +114,56 @@ contract('ForeignBridge', async (accounts) => {
114114
bridgeBalance = await web3.eth.getBalance(foreignBridge.address)
115115
bridgeBalance.should.be.bignumber.equal(0)
116116
})
117+
118+
it('should allow to make fee deposits on behalf of somebody else', async () => {
119+
const user1BalanceBeforeWithdraw = await web3.eth.getBalance(user1)
120+
await foreignBridge.addFeeDepositFor(user1, {from: user2, value: web3.toWei(1, 'ether')}).should.be.fulfilled
121+
let bridgeBalance = await web3.eth.getBalance(foreignBridge.address)
122+
bridgeBalance.should.be.bignumber.equal(web3.toWei(1, 'ether'))
123+
// user1 withdraw all
124+
let tx = await foreignBridge.sendTransaction({from: user1, value: 0}).should.be.fulfilled
125+
let user1TxFees = new web3.BigNumber(gasPrice).mul(tx.receipt.gasUsed)
126+
const user1BalanceAfterWithdraw = await web3.eth.getBalance(user1)
127+
user1BalanceAfterWithdraw.should.be.bignumber.equal(user1BalanceBeforeWithdraw.add(web3.toWei(1, 'ether')).sub(user1TxFees))
128+
})
129+
130+
it('should relay valid tx only if enough deposited', async () => {
131+
var recipientAccount = accounts[3];
132+
const balanceBefore = await token.balanceOf(recipientAccount)
133+
const totalSupplyBefore = await token.totalSupply()
134+
var value = web3.toBigNumber(web3.toWei(0.25, "ether"));
135+
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
136+
var message = createMessage(recipientAccount, value, transactionHash, foreignBridge.address);
137+
var signature = await sign(authorities[0], message)
138+
var vrs = signatureToVRS(signature);
139+
false.should.be.equal(await foreignBridge.relayedMessages(transactionHash))
140+
141+
await foreignBridge.executeSignaturesRecipientPays([vrs.v], [vrs.r], [vrs.s], message).should.be.rejectedWith(ERROR_MSG)
142+
143+
// deposit 1 ETH for fees
144+
await foreignBridge.sendTransaction({from: recipientAccount, value: web3.toWei(1, 'ether')}).should.be.fulfilled
145+
146+
const {logs} = await foreignBridge.executeSignaturesRecipientPays([vrs.v], [vrs.r], [vrs.s], message).should.be.fulfilled
147+
logs[0].event.should.be.equal("RelayedMessage")
148+
logs[0].args.recipient.should.be.equal(recipientAccount)
149+
logs[0].args.value.should.be.bignumber.equal(value)
150+
logs[0].args.transactionHash.should.be.equal(transactionHash);
151+
152+
const balanceAfter = await token.balanceOf(recipientAccount);
153+
const totalSupplyAfter = await token.totalSupply();
154+
balanceAfter.should.be.bignumber.equal(balanceBefore.add(value))
155+
totalSupplyAfter.should.be.bignumber.equal(totalSupplyBefore.add(value))
156+
true.should.be.equal(await foreignBridge.relayedMessages(transactionHash))
157+
})
158+
159+
it('should allow owner only to claim collected tx fees', async () => {
160+
const ownerBalanceBefore = await web3.eth.getBalance(owner);
161+
await foreignBridge.withdrawCollectedFees({from: accounts[1]}).should.be.rejected
162+
await foreignBridge.withdrawCollectedFees({from: owner}).should.be.fulfilled
163+
const ownerBalanceAfter = await web3.eth.getBalance(owner);
164+
ownerBalanceAfter.should.be.bignumber.above(ownerBalanceBefore)
165+
await foreignBridge.withdrawCollectedFees({from: owner}).should.be.rejected // nothing left to withdraw
166+
})
117167
})
118168

119169
describe('#executeSignatures', async () => {

0 commit comments

Comments
 (0)