Merge pull request #83 from paritytech/snd-issue-82

100% test coverage, issue 82, refactoring, documentation
This commit is contained in:
Marek Kotewicz 2018-01-15 11:49:00 +01:00 committed by GitHub
commit 99e9f18461
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 437 additions and 180 deletions

View File

@ -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);

View File

@ -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;

87
truffle/test/helpers.js Normal file
View File

@ -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");
})
})
})

View File

@ -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;

View File

@ -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) {
})
})
})

33
truffle/test/message.js Normal file
View File

@ -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);
})
})
})

View File

@ -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) {
})
})
})

View File

@ -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);
})
})
})