Merge pull request #83 from paritytech/snd-issue-82
100% test coverage, issue 82, refactoring, documentation
This commit is contained in:
commit
99e9f18461
|
@ -1,48 +1,59 @@
|
|||
pragma solidity ^0.4.17;
|
||||
|
||||
|
||||
library Authorities {
|
||||
function contains(address[] self, address value) internal pure returns (bool) {
|
||||
for (uint i = 0; i < self.length; i++) {
|
||||
if (self[i] == value) {
|
||||
/// general helpers.
|
||||
/// `internal` so they get compiled into contracts using them.
|
||||
library Helpers {
|
||||
/// returns whether `array` contains `value`.
|
||||
function addressArrayContains(address[] array, address value) internal pure returns (bool) {
|
||||
for (uint i = 0; i < array.length; i++) {
|
||||
if (array[i] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Library used only to test Signer library via rpc calls
|
||||
library SignerTest {
|
||||
function signer(bytes signature, bytes message) public pure returns (address) {
|
||||
return Signer.signer(signature, message);
|
||||
// returns the digits of `inputValue` as a string.
|
||||
// example: `uintToString(12345678)` returns `"12345678"`
|
||||
function uintToString(uint inputValue) internal pure returns (string) {
|
||||
// figure out the length of the resulting string
|
||||
uint length = 0;
|
||||
uint currentValue = inputValue;
|
||||
do {
|
||||
length++;
|
||||
currentValue /= 10;
|
||||
} while (currentValue != 0);
|
||||
// allocate enough memory
|
||||
bytes memory result = new bytes(length);
|
||||
// construct the string backwards
|
||||
uint i = length - 1;
|
||||
currentValue = inputValue;
|
||||
do {
|
||||
result[i--] = byte(48 + currentValue % 10);
|
||||
currentValue /= 10;
|
||||
} while (currentValue != 0);
|
||||
return string(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
library Utils {
|
||||
function toString(uint256 inputValue) internal pure returns (string str) {
|
||||
// it is used only for small numbers
|
||||
bytes memory reversed = new bytes(8);
|
||||
uint workingValue = inputValue;
|
||||
uint i = 0;
|
||||
while (workingValue != 0) {
|
||||
uint remainder = workingValue % 10;
|
||||
workingValue = workingValue / 10;
|
||||
reversed[i++] = byte(48 + remainder);
|
||||
}
|
||||
bytes memory s = new bytes(i);
|
||||
for (uint j = 0; j < i; j++) {
|
||||
s[j] = reversed[i - j - 1];
|
||||
}
|
||||
str = string(s);
|
||||
/// Library used only to test Helpers library via rpc calls
|
||||
library HelpersTest {
|
||||
function addressArrayContains(address[] array, address value) public pure returns (bool) {
|
||||
return Helpers.addressArrayContains(array, value);
|
||||
}
|
||||
|
||||
function uintToString(uint256 inputValue) public pure returns (string str) {
|
||||
return Helpers.uintToString(inputValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
library Signer {
|
||||
function signer(bytes signature, bytes message) internal pure returns (address) {
|
||||
// helpers for message signing.
|
||||
// `internal` so they get compiled into contracts using them.
|
||||
library MessageSigning {
|
||||
function recoverAddressFromSignedMessage(bytes signature, bytes message) internal pure returns (address) {
|
||||
require(signature.length == 65);
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
|
@ -53,19 +64,88 @@ library Signer {
|
|||
s := mload(add(signature, 0x40))
|
||||
v := mload(add(signature, 0x60))
|
||||
}
|
||||
return ecrecover(hash(message), uint8(v), r, s);
|
||||
return ecrecover(hashMessage(message), uint8(v), r, s);
|
||||
}
|
||||
|
||||
function hash(bytes message) internal pure returns (bytes32) {
|
||||
function hashMessage(bytes message) internal pure returns (bytes32) {
|
||||
bytes memory prefix = "\x19Ethereum Signed Message:\n";
|
||||
return keccak256(prefix, Utils.toString(message.length), message);
|
||||
return keccak256(prefix, Helpers.uintToString(message.length), message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Library used only to test MessageSigning library via rpc calls
|
||||
library MessageSigningTest {
|
||||
function recoverAddressFromSignedMessage(bytes signature, bytes message) public pure returns (address) {
|
||||
return MessageSigning.recoverAddressFromSignedMessage(signature, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
library Message {
|
||||
// layout of message :: bytes:
|
||||
// offset 0: 32 bytes :: uint (little endian) - message length
|
||||
// offset 32: 20 bytes :: address - recipient address
|
||||
// offset 52: 32 bytes :: uint (little endian) - value
|
||||
// offset 84: 32 bytes :: bytes32 - transaction hash
|
||||
|
||||
// bytes 1 to 32 are 0 because message length is stored as little endian.
|
||||
// mload always reads 32 bytes.
|
||||
// so we can and have to start reading recipient at offset 20 instead of 32.
|
||||
// if we were to read at 32 the address would contain part of value and be corrupted.
|
||||
// when reading from offset 20 mload will read 12 zero bytes followed
|
||||
// by the 20 recipient address bytes and correctly convert it into an address.
|
||||
// this saves some storage/gas over the alternative solution
|
||||
// which is padding address to 32 bytes and reading recipient at offset 32.
|
||||
// for more details see discussion in:
|
||||
// https://github.com/paritytech/parity-bridge/issues/61
|
||||
|
||||
function getRecipient(bytes message) internal pure returns (address) {
|
||||
address recipient;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
recipient := mload(add(message, 20))
|
||||
}
|
||||
return recipient;
|
||||
}
|
||||
|
||||
function getValue(bytes message) internal pure returns (uint) {
|
||||
uint value;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
value := mload(add(message, 52))
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function getTransactionHash(bytes message) internal pure returns (bytes32) {
|
||||
bytes32 hash;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
hash := mload(add(message, 84))
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Library used only to test Message library via rpc calls
|
||||
library MessageTest {
|
||||
function getRecipient(bytes message) public pure returns (address) {
|
||||
return Message.getRecipient(message);
|
||||
}
|
||||
|
||||
function getValue(bytes message) public pure returns (uint) {
|
||||
return Message.getValue(message);
|
||||
}
|
||||
|
||||
function getTransactionHash(bytes message) public pure returns (bytes32) {
|
||||
return Message.getTransactionHash(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contract HomeBridge {
|
||||
using Authorities for address[];
|
||||
|
||||
/// Number of authorities signatures required to withdraw the money.
|
||||
///
|
||||
/// Must be lesser than number of authorities.
|
||||
|
@ -92,15 +172,15 @@ contract HomeBridge {
|
|||
|
||||
/// Multisig authority validation
|
||||
modifier allAuthorities(uint8[] v, bytes32[] r, bytes32[] s, bytes message) {
|
||||
var hash = Signer.hash(message);
|
||||
var hash = MessageSigning.hashMessage(message);
|
||||
var used = new address[](requiredSignatures);
|
||||
|
||||
require(requiredSignatures <= v.length);
|
||||
|
||||
for (uint i = 0; i < requiredSignatures; i++) {
|
||||
var a = ecrecover(hash, v[i], r[i], s[i]);
|
||||
require(authorities.contains(a));
|
||||
require(!used.contains(a));
|
||||
require(Helpers.addressArrayContains(authorities, a));
|
||||
require(!Helpers.addressArrayContains(used, a));
|
||||
used[i] = a;
|
||||
}
|
||||
_;
|
||||
|
@ -125,55 +205,11 @@ contract HomeBridge {
|
|||
Deposit(msg.sender, msg.value);
|
||||
}
|
||||
|
||||
// layout of message :: bytes:
|
||||
// offset 0: 32 bytes :: uint (little endian) - message length
|
||||
// offset 32: 20 bytes :: address - recipient address
|
||||
// offset 52: 32 bytes :: uint (little endian) - value
|
||||
// offset 84: 32 bytes :: bytes32 - transaction hash
|
||||
|
||||
// bytes 1 to 32 are 0 because message length is stored as little endian.
|
||||
// mload always reads 32 bytes.
|
||||
// so we can and have to start reading recipient at offset 20 instead of 32.
|
||||
// if we were to read at 32 the address would contain part of value and be corrupted.
|
||||
// when reading from offset 20 mload will read 12 zero bytes followed
|
||||
// by the 20 recipient address bytes and correctly convert it into an address.
|
||||
// this saves some storage/gas over the alternative solution
|
||||
// which is padding address to 32 bytes and reading recipient at offset 32.
|
||||
// for more details see discussion in:
|
||||
// https://github.com/paritytech/parity-bridge/issues/61
|
||||
|
||||
function getRecipientFromMessage(bytes message) public pure returns (address) {
|
||||
address recipient;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
recipient := mload(add(message, 20))
|
||||
}
|
||||
return recipient;
|
||||
}
|
||||
|
||||
function getValueFromMessage(bytes message) public pure returns (uint) {
|
||||
uint value;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
value := mload(add(message, 52))
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function getTransactionHashFromMessage(bytes message) public pure returns (bytes32) {
|
||||
bytes32 hash;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
hash := mload(add(message, 84))
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// to be called by authorities to check
|
||||
/// whether they withdraw message should be relayed or whether it
|
||||
/// is too low to cover the cost of calling withdraw and can be ignored
|
||||
function isMessageValueSufficientToCoverRelay(bytes message) public view returns (bool) {
|
||||
return getValueFromMessage(message) > getWithdrawRelayCost();
|
||||
return Message.getValue(message) > getWithdrawRelayCost();
|
||||
}
|
||||
|
||||
/// an upper bound to the cost of relaying a withdraw by calling HomeBridge.withdraw
|
||||
|
@ -191,9 +227,9 @@ contract HomeBridge {
|
|||
/// NOTE that anyone can call withdraw provided they have the message and required signatures!
|
||||
function withdraw(uint8[] v, bytes32[] r, bytes32[] s, bytes message) public allAuthorities(v, r, s, message) {
|
||||
require(message.length == 84);
|
||||
address recipient = getRecipientFromMessage(message);
|
||||
uint value = getValueFromMessage(message);
|
||||
bytes32 hash = getTransactionHashFromMessage(message);
|
||||
address recipient = Message.getRecipient(message);
|
||||
uint value = Message.getValue(message);
|
||||
bytes32 hash = Message.getTransactionHash(message);
|
||||
|
||||
// The following two statements guard against reentry into this function.
|
||||
// Duplicated withdraw or reentry.
|
||||
|
@ -223,8 +259,6 @@ contract HomeBridge {
|
|||
|
||||
|
||||
contract ForeignBridge {
|
||||
using Authorities for address[];
|
||||
|
||||
struct SignaturesCollection {
|
||||
/// Signed message.
|
||||
bytes message;
|
||||
|
@ -277,7 +311,7 @@ contract ForeignBridge {
|
|||
|
||||
/// Multisig authority validation
|
||||
modifier onlyAuthority() {
|
||||
require(authorities.contains(msg.sender));
|
||||
require(Helpers.addressArrayContains(authorities, msg.sender));
|
||||
_;
|
||||
}
|
||||
|
||||
|
@ -291,7 +325,7 @@ contract ForeignBridge {
|
|||
var hash = keccak256(recipient, value, transactionHash);
|
||||
|
||||
// Duplicated deposits
|
||||
require(!deposits[hash].contains(msg.sender));
|
||||
require(!Helpers.addressArrayContains(deposits[hash], msg.sender));
|
||||
|
||||
deposits[hash].push(msg.sender);
|
||||
// TODO: this may cause troubles if requriedSignatures len is changed
|
||||
|
@ -342,14 +376,14 @@ contract ForeignBridge {
|
|||
/// foreign transaction hash (bytes32) // to avoid transaction duplication
|
||||
function submitSignature(bytes signature, bytes message) public onlyAuthority() {
|
||||
// Validate submited signatures
|
||||
require(Signer.signer(signature, message) == msg.sender);
|
||||
require(MessageSigning.recoverAddressFromSignedMessage(signature, message) == msg.sender);
|
||||
|
||||
// Valid withdraw message must have 84 bytes
|
||||
require(message.length == 84);
|
||||
var hash = keccak256(message);
|
||||
|
||||
// Duplicated signatures
|
||||
require(!signatures[hash].signed.contains(msg.sender));
|
||||
require(!Helpers.addressArrayContains(signatures[hash].signed, msg.sender));
|
||||
signatures[hash].message = message;
|
||||
signatures[hash].signed.push(msg.sender);
|
||||
signatures[hash].signatures.push(signature);
|
||||
|
|
|
@ -105,6 +105,23 @@ contract('ForeignBridge', function(accounts) {
|
|||
})
|
||||
})
|
||||
|
||||
it("should not allow non-authorities to execute deposit", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var userAccount = accounts[2];
|
||||
var value = web3.toWei(1, "ether");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(userAccount, value, hash, { from: userAccount });
|
||||
}).then(function(result) {
|
||||
assert(false, "should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
|
||||
it("should ignore misbehaving authority when confirming deposit", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 2;
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
// solidity Helpers library
|
||||
var Helpers = artifacts.require("HelpersTest");
|
||||
// testing helpers
|
||||
var helpers = require("./helpers/helpers");
|
||||
|
||||
contract("Helpers", function() {
|
||||
it("`addressArrayContains` should function correctly", function() {
|
||||
var addresses = [
|
||||
"0xd4f04f18d253f831e5b9bcfde7f20450562e03da",
|
||||
"0x46ee1abbcd7215364174f84c3cbc4770d45966e9",
|
||||
"0x5ef98710ff315ded660fe757bf7a861114287c1e",
|
||||
];
|
||||
var otherAddress = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
var library;
|
||||
return Helpers.new().then(function(instance) {
|
||||
library = instance;
|
||||
|
||||
return library.addressArrayContains.call([], otherAddress);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, false, "should return false for empty array");
|
||||
|
||||
return library.addressArrayContains.call([otherAddress], otherAddress);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, true, "should return true for singleton array containing value");
|
||||
|
||||
return library.addressArrayContains.call([addresses[0]], addresses[1]);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, false, "should return false for singleton array not containing value");
|
||||
|
||||
return library.addressArrayContains.call(addresses, addresses[0]);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, true);
|
||||
|
||||
return library.addressArrayContains.call(addresses, addresses[1]);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, true);
|
||||
|
||||
return library.addressArrayContains.call(addresses, addresses[2]);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, true);
|
||||
|
||||
return library.addressArrayContains.call(addresses, otherAddress);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, false);
|
||||
})
|
||||
})
|
||||
|
||||
it("`uintToString` should convert int to string", function() {
|
||||
var numbersFrom1To100 = helpers.range(1, 101);
|
||||
var library;
|
||||
return Helpers.new().then(function(instance) {
|
||||
library = instance;
|
||||
|
||||
return library.uintToString.call(0)
|
||||
}).then(function(result) {
|
||||
assert.equal(result, "0");
|
||||
|
||||
return Promise.all(numbersFrom1To100.map(function(number) {
|
||||
return library.uintToString.call(number);
|
||||
}));
|
||||
}).then(function(result) {
|
||||
assert.deepEqual(result, numbersFrom1To100.map(function(number) {
|
||||
return number.toString();
|
||||
}), "should convert numbers from 1 to 100 correctly");
|
||||
|
||||
return library.uintToString.estimateGas(1);
|
||||
}).then(function(result) {
|
||||
console.log("estimated gas cost of Helpers.uintToString(1)", result);
|
||||
|
||||
return library.uintToString.call(1234)
|
||||
}).then(function(result) {
|
||||
assert.equal(result, "1234");
|
||||
|
||||
return library.uintToString.call(12345678)
|
||||
}).then(function(result) {
|
||||
assert.equal(result, "12345678");
|
||||
|
||||
return library.uintToString.estimateGas(12345678)
|
||||
}).then(function(result) {
|
||||
console.log("estimated gas cost of Helpers.uintToString(12345678)", result);
|
||||
|
||||
return library.uintToString.call(web3.toBigNumber("131242344353464564564574574567456"));
|
||||
}).then(function(result) {
|
||||
assert.equal(result, "131242344353464564564574574567456");
|
||||
})
|
||||
})
|
||||
})
|
|
@ -71,3 +71,34 @@ function getBalances(addresses) {
|
|||
})
|
||||
}
|
||||
module.exports.getBalances = getBalances;
|
||||
|
||||
|
||||
// returns hex string of the bytes of the message
|
||||
// composed from `recipient`, `value` and `transactionHash`
|
||||
// that is relayed from `foreign` to `home` on withdraw
|
||||
function createMessage(recipient, value, transactionHash) {
|
||||
web3._extend.utils.isBigNumber(value);
|
||||
recipient = strip0x(recipient);
|
||||
assert.equal(recipient.length, 20 * 2);
|
||||
|
||||
transactionHash = strip0x(transactionHash);
|
||||
assert.equal(transactionHash.length, 32 * 2);
|
||||
|
||||
var value = strip0x(bigNumberToPaddedBytes32(value));
|
||||
assert.equal(value.length, 64);
|
||||
var message = "0x" + recipient + value + transactionHash;
|
||||
var expectedMessageLength = (20 + 32 + 32) * 2 + 2;
|
||||
assert.equal(message.length, expectedMessageLength);
|
||||
return message;
|
||||
}
|
||||
module.exports.createMessage = createMessage;
|
||||
|
||||
// returns array of integers progressing from `start` up to, but not including, `end`
|
||||
function range(start, end) {
|
||||
var result = [];
|
||||
for (var i = start; i < end; i++) {
|
||||
result.push(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
module.exports.range = range;
|
||||
|
|
|
@ -67,53 +67,6 @@ contract('HomeBridge', function(accounts) {
|
|||
})
|
||||
})
|
||||
|
||||
function createMessage(recipient, value, transactionHash) {
|
||||
web3._extend.utils.isBigNumber(value);
|
||||
recipient = helpers.strip0x(recipient);
|
||||
assert.equal(recipient.length, 20 * 2);
|
||||
|
||||
transactionHash = helpers.strip0x(transactionHash);
|
||||
assert.equal(transactionHash.length, 32 * 2);
|
||||
|
||||
var value = helpers.strip0x(helpers.bigNumberToPaddedBytes32(value));
|
||||
assert.equal(value.length, 64);
|
||||
var message = "0x" + recipient + value + transactionHash;
|
||||
var expectedMessageLength = (20 + 32 + 32) * 2 + 2;
|
||||
assert.equal(message.length, expectedMessageLength);
|
||||
return message;
|
||||
}
|
||||
|
||||
it("should get message parts correctly", function() {
|
||||
var homeBridge;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var estimatedGasCostOfWithdraw = 0;
|
||||
var recipientAccount = accounts[3];
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
|
||||
var message = createMessage(recipientAccount, value, transactionHash);
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
authorities,
|
||||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
homeBridge = instance;
|
||||
|
||||
return homeBridge.getRecipientFromMessage(message);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, recipientAccount);
|
||||
|
||||
return homeBridge.getValueFromMessage(message);
|
||||
}).then(function(result) {
|
||||
assert(result.equals(value));
|
||||
|
||||
return homeBridge.getTransactionHashFromMessage(message);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, transactionHash);
|
||||
})
|
||||
})
|
||||
|
||||
it("isMessageValueSufficientToCoverRelay should work correctly", function() {
|
||||
var homeBridge;
|
||||
var gasPrice;
|
||||
|
@ -150,12 +103,12 @@ contract('HomeBridge', function(accounts) {
|
|||
}).then(function(result) {
|
||||
assert(result.equals(estimatedWeiCostOfWithdraw), "getWithdrawRelayCost should return correct value");
|
||||
|
||||
var message = createMessage(recipientAccount, estimatedWeiCostOfWithdraw, transactionHash);
|
||||
var message = helpers.createMessage(recipientAccount, estimatedWeiCostOfWithdraw, transactionHash);
|
||||
return homeBridge.isMessageValueSufficientToCoverRelay(message);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, false, "exactly estimatedWeiCostOfWithdraw is not sufficient value");
|
||||
|
||||
var message = createMessage(recipientAccount, estimatedWeiCostOfWithdraw.plus(1), transactionHash);
|
||||
var message = helpers.createMessage(recipientAccount, estimatedWeiCostOfWithdraw.plus(1), transactionHash);
|
||||
return homeBridge.isMessageValueSufficientToCoverRelay(message);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, true, "estimatedWeiCostOfWithdraw + 1 is sufficient value");
|
||||
|
@ -171,7 +124,7 @@ contract('HomeBridge', function(accounts) {
|
|||
var userAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var message = createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
|
@ -233,7 +186,7 @@ contract('HomeBridge', function(accounts) {
|
|||
var recipientAccount = accounts[3];
|
||||
var chargerAccount = accounts[4];
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var message = createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
|
@ -309,7 +262,7 @@ contract('HomeBridge', function(accounts) {
|
|||
var recipientAccount = accounts[3];
|
||||
var chargerAccount = accounts[4];
|
||||
var value = estimatedGasCostOfWithdraw;
|
||||
var message = createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
|
@ -356,8 +309,8 @@ contract('HomeBridge', function(accounts) {
|
|||
var userAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var message1 = createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message2 = createMessage(recipientAccount, value, "0x038c79eb958a13aa71996bac27c628f33f227288bd27d5e157b97e55e08fd2b3");
|
||||
var message1 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message2 = helpers.createMessage(recipientAccount, value, "0x038c79eb958a13aa71996bac27c628f33f227288bd27d5e157b97e55e08fd2b3");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
|
@ -413,8 +366,8 @@ contract('HomeBridge', function(accounts) {
|
|||
var userAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var message1 = createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message2 = createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message1 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message2 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
|
@ -470,7 +423,7 @@ contract('HomeBridge', function(accounts) {
|
|||
var userAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var message = createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
|
@ -505,7 +458,9 @@ contract('HomeBridge', function(accounts) {
|
|||
var userAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var message = createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
// make message too short
|
||||
message = message.substr(0, 83);
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
|
@ -513,6 +468,7 @@ contract('HomeBridge', function(accounts) {
|
|||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
homeBridge = instance;
|
||||
|
||||
// "charge" HomeBridge so we can withdraw later
|
||||
return homeBridge.sendTransaction({
|
||||
value: value,
|
||||
|
@ -523,18 +479,104 @@ contract('HomeBridge', function(accounts) {
|
|||
}).then(function(result) {
|
||||
signature = result;
|
||||
var vrs = helpers.signatureToVRS(signature);
|
||||
|
||||
return homeBridge.withdraw(
|
||||
[vrs.v],
|
||||
[vrs.r],
|
||||
[vrs.s],
|
||||
// change message length to 83
|
||||
message.substr(0, 83),
|
||||
{from: authorities[0]}
|
||||
message,
|
||||
// anyone can call withdraw (provided they have the message and required signatures)
|
||||
{from: userAccount}
|
||||
);
|
||||
}).then(function(result) {
|
||||
assert(false, "withdraw should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
|
||||
it("withdraw should fail if not enough signatures are provided", function() {
|
||||
var homeBridge;
|
||||
var signature;
|
||||
var requiredSignatures = 2;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var estimatedGasCostOfWithdraw = 0;
|
||||
var userAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
authorities,
|
||||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
homeBridge = instance;
|
||||
|
||||
// "charge" HomeBridge so we can withdraw later
|
||||
return homeBridge.sendTransaction({
|
||||
value: value,
|
||||
from: userAccount
|
||||
})
|
||||
}).then(function(result) {
|
||||
return helpers.sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
signature = result;
|
||||
var vrs = helpers.signatureToVRS(signature);
|
||||
|
||||
return homeBridge.withdraw(
|
||||
[vrs.v],
|
||||
[vrs.r],
|
||||
[vrs.s],
|
||||
message,
|
||||
// anyone can call withdraw (provided they have the message and required signatures)
|
||||
{from: userAccount}
|
||||
);
|
||||
}).then(function(result) {
|
||||
assert(false, "should fail");
|
||||
}, function (err) {
|
||||
// nothing
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
|
||||
it("withdraw should fail if duplicate signature is provided", function() {
|
||||
var homeBridge;
|
||||
var signature;
|
||||
var requiredSignatures = 2;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var estimatedGasCostOfWithdraw = 0;
|
||||
var userAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
authorities,
|
||||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
homeBridge = instance;
|
||||
|
||||
// "charge" HomeBridge so we can withdraw later
|
||||
return homeBridge.sendTransaction({
|
||||
value: value,
|
||||
from: userAccount
|
||||
})
|
||||
}).then(function(result) {
|
||||
return helpers.sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
signature = result;
|
||||
var vrs = helpers.signatureToVRS(signature);
|
||||
|
||||
return homeBridge.withdraw(
|
||||
[vrs.v, vrs.v],
|
||||
[vrs.r, vrs.r],
|
||||
[vrs.s, vrs.s],
|
||||
message,
|
||||
// anyone can call withdraw (provided they have the message and required signatures)
|
||||
{from: userAccount}
|
||||
);
|
||||
}).then(function(result) {
|
||||
assert(false, "should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
var Message = artifacts.require("MessageTest");
|
||||
var helpers = require("./helpers/helpers");
|
||||
|
||||
contract("Message", function() {
|
||||
var recipientAccount = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
|
||||
var message = helpers.createMessage(recipientAccount, value, transactionHash);
|
||||
|
||||
it("should extract value", function() {
|
||||
return Message.new().then(function(instance) {
|
||||
return instance.getValue.call(message)
|
||||
}).then(function(result) {
|
||||
assert(result.equals(value));
|
||||
})
|
||||
})
|
||||
|
||||
it("should extract recipient", function() {
|
||||
return Message.new().then(function(instance) {
|
||||
return instance.getRecipient.call(message)
|
||||
}).then(function(result) {
|
||||
assert.equal(result, recipientAccount);
|
||||
})
|
||||
})
|
||||
|
||||
it("should extract transactionHash", function() {
|
||||
return Message.new().then(function(instance) {
|
||||
return instance.getTransactionHash.call(message)
|
||||
}).then(function(result) {
|
||||
assert.equal(result, transactionHash);
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,40 @@
|
|||
var MessageSigning = artifacts.require("MessageSigningTest");
|
||||
|
||||
contract("MessageSigning", function() {
|
||||
it("should recover address from signed message", function() {
|
||||
var signature = "0xb585c41f3cceb2ff9b5c033f2edbefe93415bde365489c989bad8cef3b18e38148a13e100608a29735d709fe708926d37adcecfffb32b1d598727028a16df5db1b";
|
||||
var message = "0xdeadbeaf";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return MessageSigning.new().then(function(instance) {
|
||||
return instance.recoverAddressFromSignedMessage.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert.equal(account, result);
|
||||
})
|
||||
})
|
||||
|
||||
it("should recover address from long signed message", function() {
|
||||
var signature = "0x3c9158597e22fa43fcc6636399c560441808e1d8496de0108e401a2ad71022b15d1191cf3c96e06759601c8e00ce7f03f350c12b19d0a8ba3ab3c07a71063f2b1c";
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return MessageSigning.new().then(function(instance) {
|
||||
return instance.recoverAddressFromSignedMessage.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert.equal(account, result);
|
||||
})
|
||||
})
|
||||
|
||||
it("should fail to recover address from signature that is too short", function() {
|
||||
var signature = "0x3c9158597e22fa43fcc6636399c560441808e1d8496de0108e401a2ad71022b15d1191cf3c96e06759601c8e00ce7f03f350c12b19d0a8ba3ab3c07a71063f2b";
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return MessageSigning.new().then(function(instance) {
|
||||
return instance.recoverAddressFromSignedMessage.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert(false, "should fail because signature is too short");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,27 +0,0 @@
|
|||
var Signer = artifacts.require("SignerTest");
|
||||
|
||||
contract("Signer", function() {
|
||||
it("should validate signature", function() {
|
||||
var signature = "0xb585c41f3cceb2ff9b5c033f2edbefe93415bde365489c989bad8cef3b18e38148a13e100608a29735d709fe708926d37adcecfffb32b1d598727028a16df5db1b";
|
||||
var message = "0xdeadbeaf";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return Signer.new().then(function(instance) {
|
||||
return instance.signer.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert.equal(account, result);
|
||||
})
|
||||
})
|
||||
|
||||
it("should validate signature for long message", function() {
|
||||
var signature = "0x3c9158597e22fa43fcc6636399c560441808e1d8496de0108e401a2ad71022b15d1191cf3c96e06759601c8e00ce7f03f350c12b19d0a8ba3ab3c07a71063f2b1c";
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return Signer.new().then(function(instance) {
|
||||
return instance.signer.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert.equal(account, result);
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue