add more openzeppelin-solidity library contracts
This commit is contained in:
76
contracts/ECRecovery.sol
Normal file
76
contracts/ECRecovery.sol
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title Eliptic curve signature operations
|
||||||
|
*
|
||||||
|
* @dev Based on https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d
|
||||||
|
*
|
||||||
|
* TODO Remove this library once solidity supports passing a signature to ecrecover.
|
||||||
|
* See https://github.com/ethereum/solidity/issues/864
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
library ECRecovery {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Recover signer address from a message by using their signature
|
||||||
|
* @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
|
||||||
|
* @param sig bytes signature, the signature is generated using web3.eth.sign()
|
||||||
|
*/
|
||||||
|
function recover(bytes32 hash, bytes sig)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (address)
|
||||||
|
{
|
||||||
|
bytes32 r;
|
||||||
|
bytes32 s;
|
||||||
|
uint8 v;
|
||||||
|
|
||||||
|
// Check the signature length
|
||||||
|
if (sig.length != 65) {
|
||||||
|
return (address(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divide the signature in r, s and v variables
|
||||||
|
// ecrecover takes the signature parameters, and the only way to get them
|
||||||
|
// currently is to use assembly.
|
||||||
|
// solium-disable-next-line security/no-inline-assembly
|
||||||
|
assembly {
|
||||||
|
r := mload(add(sig, 32))
|
||||||
|
s := mload(add(sig, 64))
|
||||||
|
v := byte(0, mload(add(sig, 96)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version of signature should be 27 or 28, but 0 and 1 are also possible versions
|
||||||
|
if (v < 27) {
|
||||||
|
v += 27;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the version is correct return the signer address
|
||||||
|
if (v != 27 && v != 28) {
|
||||||
|
return (address(0));
|
||||||
|
} else {
|
||||||
|
// solium-disable-next-line arg-overflow
|
||||||
|
return ecrecover(hash, v, r, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toEthSignedMessageHash
|
||||||
|
* @dev prefix a bytes32 value with "\x19Ethereum Signed Message:"
|
||||||
|
* @dev and hash the result
|
||||||
|
*/
|
||||||
|
function toEthSignedMessageHash(bytes32 hash)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (bytes32)
|
||||||
|
{
|
||||||
|
// 32 is the length in bytes of hash,
|
||||||
|
// enforced by the type signature above
|
||||||
|
return keccak256(
|
||||||
|
"\x19Ethereum Signed Message:\n32",
|
||||||
|
hash
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
contracts/MerkleProof.sol
Normal file
35
contracts/MerkleProof.sol
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @title MerkleProof
|
||||||
|
* @dev Merkle proof verification
|
||||||
|
* @note Based on https://github.com/ameensol/merkle-tree-solidity/blob/master/src/MerkleProof.sol
|
||||||
|
*/
|
||||||
|
library MerkleProof {
|
||||||
|
/*
|
||||||
|
* @dev Verifies a Merkle proof proving the existence of a leaf in a Merkle tree. Assumes that each pair of leaves
|
||||||
|
* and each pair of pre-images is sorted.
|
||||||
|
* @param _proof Merkle proof containing sibling hashes on the branch from the leaf to the root of the Merkle tree
|
||||||
|
* @param _root Merkle root
|
||||||
|
* @param _leaf Leaf of Merkle tree
|
||||||
|
*/
|
||||||
|
function verifyProof(bytes32[] _proof, bytes32 _root, bytes32 _leaf) internal pure returns (bool) {
|
||||||
|
bytes32 computedHash = _leaf;
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < _proof.length; i++) {
|
||||||
|
bytes32 proofElement = _proof[i];
|
||||||
|
|
||||||
|
if (computedHash < proofElement) {
|
||||||
|
// Hash(current computed hash + current element of the proof)
|
||||||
|
computedHash = keccak256(computedHash, proofElement);
|
||||||
|
} else {
|
||||||
|
// Hash(current element of the proof + current computed hash)
|
||||||
|
computedHash = keccak256(proofElement, computedHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the computed hash (root) is equal to the provided root
|
||||||
|
return computedHash == _root;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
contracts/math/Math.sol
Normal file
24
contracts/math/Math.sol
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title Math
|
||||||
|
* @dev Assorted math operations
|
||||||
|
*/
|
||||||
|
library Math {
|
||||||
|
function max64(uint64 a, uint64 b) internal pure returns (uint64) {
|
||||||
|
return a >= b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function min64(uint64 a, uint64 b) internal pure returns (uint64) {
|
||||||
|
return a < b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function max256(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
|
return a >= b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function min256(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
|
return a < b ? a : b;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
contracts/mocks/ECRecoveryMock.sol
Normal file
25
contracts/mocks/ECRecoveryMock.sol
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
|
||||||
|
import "../ECRecovery.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract ECRecoveryMock {
|
||||||
|
using ECRecovery for bytes32;
|
||||||
|
|
||||||
|
function recover(bytes32 hash, bytes sig)
|
||||||
|
public
|
||||||
|
pure
|
||||||
|
returns (address)
|
||||||
|
{
|
||||||
|
return hash.recover(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toEthSignedMessageHash(bytes32 hash)
|
||||||
|
public
|
||||||
|
pure
|
||||||
|
returns (bytes32)
|
||||||
|
{
|
||||||
|
return hash.toEthSignedMessageHash();
|
||||||
|
}
|
||||||
|
}
|
||||||
26
contracts/mocks/MathMock.sol
Normal file
26
contracts/mocks/MathMock.sol
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
|
||||||
|
import "../../contracts/math/Math.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract MathMock {
|
||||||
|
uint64 public result64;
|
||||||
|
uint256 public result256;
|
||||||
|
|
||||||
|
function max64(uint64 a, uint64 b) public {
|
||||||
|
result64 = Math.max64(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function min64(uint64 a, uint64 b) public {
|
||||||
|
result64 = Math.min64(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function max256(uint256 a, uint256 b) public {
|
||||||
|
result256 = Math.max256(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function min256(uint256 a, uint256 b) public {
|
||||||
|
result256 = Math.min256(a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
contracts/mocks/MerkleProofWrapper.sol
Normal file
11
contracts/mocks/MerkleProofWrapper.sol
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
import { MerkleProof } from "../MerkleProof.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract MerkleProofWrapper {
|
||||||
|
|
||||||
|
function verifyProof(bytes32[] _proof, bytes32 _root, bytes32 _leaf) public pure returns (bool) {
|
||||||
|
return MerkleProof.verifyProof(_proof, _root, _leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
contracts/mocks/PullPaymentMock.sol
Normal file
17
contracts/mocks/PullPaymentMock.sol
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
|
||||||
|
import "../payment/PullPayment.sol";
|
||||||
|
|
||||||
|
|
||||||
|
// mock class using PullPayment
|
||||||
|
contract PullPaymentMock is PullPayment {
|
||||||
|
|
||||||
|
function PullPaymentMock() public payable { }
|
||||||
|
|
||||||
|
// test helper function to call asyncSend
|
||||||
|
function callSend(address dest, uint256 amount) public {
|
||||||
|
asyncSend(dest, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
69
contracts/mocks/RBACMock.sol
Normal file
69
contracts/mocks/RBACMock.sol
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
import "../ownership/rbac/RBACWithAdmin.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract RBACMock is RBACWithAdmin {
|
||||||
|
|
||||||
|
string constant ROLE_ADVISOR = "advisor";
|
||||||
|
|
||||||
|
modifier onlyAdminOrAdvisor()
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
hasRole(msg.sender, ROLE_ADMIN) ||
|
||||||
|
hasRole(msg.sender, ROLE_ADVISOR)
|
||||||
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RBACMock(address[] _advisors)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
addRole(msg.sender, ROLE_ADVISOR);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < _advisors.length; i++) {
|
||||||
|
addRole(_advisors[i], ROLE_ADVISOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onlyAdminsCanDoThis()
|
||||||
|
onlyAdmin
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
function onlyAdvisorsCanDoThis()
|
||||||
|
onlyRole(ROLE_ADVISOR)
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
function eitherAdminOrAdvisorCanDoThis()
|
||||||
|
onlyAdminOrAdvisor
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
function nobodyCanDoThis()
|
||||||
|
onlyRole("unknown")
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// admins can remove advisor's role
|
||||||
|
function removeAdvisor(address _addr)
|
||||||
|
onlyAdmin
|
||||||
|
public
|
||||||
|
{
|
||||||
|
// revert if the user isn't an advisor
|
||||||
|
// (perhaps you want to soft-fail here instead?)
|
||||||
|
checkRole(_addr, ROLE_ADVISOR);
|
||||||
|
|
||||||
|
// remove the advisor's role
|
||||||
|
removeRole(_addr, ROLE_ADVISOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
108
contracts/ownership/rbac/RBAC.sol
Normal file
108
contracts/ownership/rbac/RBAC.sol
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
import "./Roles.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title RBAC (Role-Based Access Control)
|
||||||
|
* @author Matt Condon (@Shrugs)
|
||||||
|
* @dev Stores and provides setters and getters for roles and addresses.
|
||||||
|
* @dev Supports unlimited numbers of roles and addresses.
|
||||||
|
* @dev See //contracts/mocks/RBACMock.sol for an example of usage.
|
||||||
|
* This RBAC method uses strings to key roles. It may be beneficial
|
||||||
|
* for you to write your own implementation of this interface using Enums or similar.
|
||||||
|
* It's also recommended that you define constants in the contract, like ROLE_ADMIN below,
|
||||||
|
* to avoid typos.
|
||||||
|
*/
|
||||||
|
contract RBAC {
|
||||||
|
using Roles for Roles.Role;
|
||||||
|
|
||||||
|
mapping (string => Roles.Role) private roles;
|
||||||
|
|
||||||
|
event RoleAdded(address addr, string roleName);
|
||||||
|
event RoleRemoved(address addr, string roleName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev reverts if addr does not have role
|
||||||
|
* @param addr address
|
||||||
|
* @param roleName the name of the role
|
||||||
|
* // reverts
|
||||||
|
*/
|
||||||
|
function checkRole(address addr, string roleName)
|
||||||
|
view
|
||||||
|
public
|
||||||
|
{
|
||||||
|
roles[roleName].check(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev determine if addr has role
|
||||||
|
* @param addr address
|
||||||
|
* @param roleName the name of the role
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function hasRole(address addr, string roleName)
|
||||||
|
view
|
||||||
|
public
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return roles[roleName].has(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev add a role to an address
|
||||||
|
* @param addr address
|
||||||
|
* @param roleName the name of the role
|
||||||
|
*/
|
||||||
|
function addRole(address addr, string roleName)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
roles[roleName].add(addr);
|
||||||
|
emit RoleAdded(addr, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev remove a role from an address
|
||||||
|
* @param addr address
|
||||||
|
* @param roleName the name of the role
|
||||||
|
*/
|
||||||
|
function removeRole(address addr, string roleName)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
roles[roleName].remove(addr);
|
||||||
|
emit RoleRemoved(addr, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev modifier to scope access to a single role (uses msg.sender as addr)
|
||||||
|
* @param roleName the name of the role
|
||||||
|
* // reverts
|
||||||
|
*/
|
||||||
|
modifier onlyRole(string roleName)
|
||||||
|
{
|
||||||
|
checkRole(msg.sender, roleName);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev modifier to scope access to a set of roles (uses msg.sender as addr)
|
||||||
|
* @param roleNames the names of the roles to scope access to
|
||||||
|
* // reverts
|
||||||
|
*
|
||||||
|
* @TODO - when solidity supports dynamic arrays as arguments to modifiers, provide this
|
||||||
|
* see: https://github.com/ethereum/solidity/issues/2467
|
||||||
|
*/
|
||||||
|
// modifier onlyRoles(string[] roleNames) {
|
||||||
|
// bool hasAnyRole = false;
|
||||||
|
// for (uint8 i = 0; i < roleNames.length; i++) {
|
||||||
|
// if (hasRole(msg.sender, roleNames[i])) {
|
||||||
|
// hasAnyRole = true;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// require(hasAnyRole);
|
||||||
|
|
||||||
|
// _;
|
||||||
|
// }
|
||||||
|
}
|
||||||
60
contracts/ownership/rbac/RBACWithAdmin.sol
Normal file
60
contracts/ownership/rbac/RBACWithAdmin.sol
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
import "./RBAC.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title RBACWithAdmin
|
||||||
|
* @author Matt Condon (@Shrugs)
|
||||||
|
* @dev It's recommended that you define constants in the contract,
|
||||||
|
* @dev like ROLE_ADMIN below, to avoid typos.
|
||||||
|
*/
|
||||||
|
contract RBACWithAdmin is RBAC {
|
||||||
|
/**
|
||||||
|
* A constant role name for indicating admins.
|
||||||
|
*/
|
||||||
|
string public constant ROLE_ADMIN = "admin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev modifier to scope access to admins
|
||||||
|
* // reverts
|
||||||
|
*/
|
||||||
|
modifier onlyAdmin()
|
||||||
|
{
|
||||||
|
checkRole(msg.sender, ROLE_ADMIN);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev constructor. Sets msg.sender as admin by default
|
||||||
|
*/
|
||||||
|
function RBACWithAdmin()
|
||||||
|
public
|
||||||
|
{
|
||||||
|
addRole(msg.sender, ROLE_ADMIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev add a role to an address
|
||||||
|
* @param addr address
|
||||||
|
* @param roleName the name of the role
|
||||||
|
*/
|
||||||
|
function adminAddRole(address addr, string roleName)
|
||||||
|
onlyAdmin
|
||||||
|
public
|
||||||
|
{
|
||||||
|
addRole(addr, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev remove a role from an address
|
||||||
|
* @param addr address
|
||||||
|
* @param roleName the name of the role
|
||||||
|
*/
|
||||||
|
function adminRemoveRole(address addr, string roleName)
|
||||||
|
onlyAdmin
|
||||||
|
public
|
||||||
|
{
|
||||||
|
removeRole(addr, roleName);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
contracts/ownership/rbac/Roles.sol
Normal file
55
contracts/ownership/rbac/Roles.sol
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title Roles
|
||||||
|
* @author Francisco Giordano (@frangio)
|
||||||
|
* @dev Library for managing addresses assigned to a Role.
|
||||||
|
* See RBAC.sol for example usage.
|
||||||
|
*/
|
||||||
|
library Roles {
|
||||||
|
struct Role {
|
||||||
|
mapping (address => bool) bearer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev give an address access to this role
|
||||||
|
*/
|
||||||
|
function add(Role storage role, address addr)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
role.bearer[addr] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev remove an address' access to this role
|
||||||
|
*/
|
||||||
|
function remove(Role storage role, address addr)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
role.bearer[addr] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev check if an address has this role
|
||||||
|
* // reverts
|
||||||
|
*/
|
||||||
|
function check(Role storage role, address addr)
|
||||||
|
view
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
require(has(role, addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev check if an address has this role
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function has(Role storage role, address addr)
|
||||||
|
view
|
||||||
|
internal
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return role.bearer[addr];
|
||||||
|
}
|
||||||
|
}
|
||||||
43
contracts/payment/PullPayment.sol
Normal file
43
contracts/payment/PullPayment.sol
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
|
||||||
|
import "../math/SafeMath.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title PullPayment
|
||||||
|
* @dev Base contract supporting async send for pull payments. Inherit from this
|
||||||
|
* contract and use asyncSend instead of send or transfer.
|
||||||
|
*/
|
||||||
|
contract PullPayment {
|
||||||
|
using SafeMath for uint256;
|
||||||
|
|
||||||
|
mapping(address => uint256) public payments;
|
||||||
|
uint256 public totalPayments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Withdraw accumulated balance, called by payee.
|
||||||
|
*/
|
||||||
|
function withdrawPayments() public {
|
||||||
|
address payee = msg.sender;
|
||||||
|
uint256 payment = payments[payee];
|
||||||
|
|
||||||
|
require(payment != 0);
|
||||||
|
require(address(this).balance >= payment);
|
||||||
|
|
||||||
|
totalPayments = totalPayments.sub(payment);
|
||||||
|
payments[payee] = 0;
|
||||||
|
|
||||||
|
payee.transfer(payment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Called by the payer to store the sent amount as credit to be pulled.
|
||||||
|
* @param dest The destination address of the funds.
|
||||||
|
* @param amount The amount to transfer.
|
||||||
|
*/
|
||||||
|
function asyncSend(address dest, uint256 amount) internal {
|
||||||
|
payments[dest] = payments[dest].add(amount);
|
||||||
|
totalPayments = totalPayments.add(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
77
test/library/ECRecovery.test.js
Normal file
77
test/library/ECRecovery.test.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
hashMessage,
|
||||||
|
signMessage,
|
||||||
|
} from '../helpers/sign';
|
||||||
|
const ECRecoveryMock = artifacts.require('ECRecoveryMock');
|
||||||
|
|
||||||
|
require('chai')
|
||||||
|
.use(require('chai-as-promised'))
|
||||||
|
.should();
|
||||||
|
|
||||||
|
contract('ECRecovery', function (accounts) {
|
||||||
|
let ecrecovery;
|
||||||
|
const TEST_MESSAGE = 'OpenZeppelin';
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
ecrecovery = await ECRecoveryMock.new();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recover v0', async function () {
|
||||||
|
// Signature generated outside ganache with method web3.eth.sign(signer, message)
|
||||||
|
let signer = '0x2cc1166f6212628a0deef2b33befb2187d35b86c';
|
||||||
|
let message = web3.sha3(TEST_MESSAGE);
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
let signature = '0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be89200';
|
||||||
|
const addrRecovered = await ecrecovery.recover(message, signature);
|
||||||
|
addrRecovered.should.eq(signer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recover v1', async function () {
|
||||||
|
// Signature generated outside ganache with method web3.eth.sign(signer, message)
|
||||||
|
let signer = '0x1e318623ab09fe6de3c9b8672098464aeda9100e';
|
||||||
|
let message = web3.sha3(TEST_MESSAGE);
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
let signature = '0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e001';
|
||||||
|
const addrRecovered = await ecrecovery.recover(message, signature);
|
||||||
|
addrRecovered.should.eq(signer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recover using web3.eth.sign()', async function () {
|
||||||
|
// Create the signature using account[0]
|
||||||
|
const signature = signMessage(accounts[0], TEST_MESSAGE);
|
||||||
|
|
||||||
|
// Recover the signer address from the generated message and signature.
|
||||||
|
const addrRecovered = await ecrecovery.recover(
|
||||||
|
hashMessage(TEST_MESSAGE),
|
||||||
|
signature
|
||||||
|
);
|
||||||
|
addrRecovered.should.eq(accounts[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recover using web3.eth.sign() should return wrong signer', async function () {
|
||||||
|
// Create the signature using account[0]
|
||||||
|
const signature = signMessage(accounts[0], TEST_MESSAGE);
|
||||||
|
|
||||||
|
// Recover the signer address from the generated message and wrong signature.
|
||||||
|
const addrRecovered = await ecrecovery.recover(hashMessage('Test'), signature);
|
||||||
|
assert.notEqual(accounts[0], addrRecovered);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recover should fail when a wrong hash is sent', async function () {
|
||||||
|
// Create the signature using account[0]
|
||||||
|
let signature = signMessage(accounts[0], TEST_MESSAGE);
|
||||||
|
|
||||||
|
// Recover the signer address from the generated message and wrong signature.
|
||||||
|
const addrRecovered = await ecrecovery.recover(hashMessage(TEST_MESSAGE).substring(2), signature);
|
||||||
|
addrRecovered.should.eq('0x0000000000000000000000000000000000000000');
|
||||||
|
});
|
||||||
|
|
||||||
|
context('toEthSignedMessage', () => {
|
||||||
|
it('should prefix hashes correctly', async function () {
|
||||||
|
const hashedMessage = web3.sha3(TEST_MESSAGE);
|
||||||
|
const ethMessage = await ecrecovery.toEthSignedMessageHash(hashedMessage);
|
||||||
|
ethMessage.should.eq(hashMessage(TEST_MESSAGE));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
43
test/library/Math.test.js
Normal file
43
test/library/Math.test.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
var MathMock = artifacts.require('MathMock');
|
||||||
|
|
||||||
|
contract('Math', function (accounts) {
|
||||||
|
let math;
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
math = await MathMock.new();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns max64 correctly', async function () {
|
||||||
|
let a = 5678;
|
||||||
|
let b = 1234;
|
||||||
|
await math.max64(a, b);
|
||||||
|
let result = await math.result64();
|
||||||
|
assert.equal(result, a);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns min64 correctly', async function () {
|
||||||
|
let a = 5678;
|
||||||
|
let b = 1234;
|
||||||
|
await math.min64(a, b);
|
||||||
|
let result = await math.result64();
|
||||||
|
|
||||||
|
assert.equal(result, b);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns max256 correctly', async function () {
|
||||||
|
let a = 5678;
|
||||||
|
let b = 1234;
|
||||||
|
await math.max256(a, b);
|
||||||
|
let result = await math.result256();
|
||||||
|
assert.equal(result, a);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns min256 correctly', async function () {
|
||||||
|
let a = 5678;
|
||||||
|
let b = 1234;
|
||||||
|
await math.min256(a, b);
|
||||||
|
let result = await math.result256();
|
||||||
|
|
||||||
|
assert.equal(result, b);
|
||||||
|
});
|
||||||
|
});
|
||||||
61
test/library/MerkleProof.test.js
Normal file
61
test/library/MerkleProof.test.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
import MerkleTree from '../helpers/merkleTree.js';
|
||||||
|
import { sha3, bufferToHex } from 'ethereumjs-util';
|
||||||
|
|
||||||
|
var MerkleProofWrapper = artifacts.require('MerkleProofWrapper');
|
||||||
|
|
||||||
|
contract('MerkleProof', function (accounts) {
|
||||||
|
let merkleProof;
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
merkleProof = await MerkleProofWrapper.new();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('verifyProof', function () {
|
||||||
|
it('should return true for a valid Merkle proof', async function () {
|
||||||
|
const elements = ['a', 'b', 'c', 'd'];
|
||||||
|
const merkleTree = new MerkleTree(elements);
|
||||||
|
|
||||||
|
const root = merkleTree.getHexRoot();
|
||||||
|
|
||||||
|
const proof = merkleTree.getHexProof(elements[0]);
|
||||||
|
|
||||||
|
const leaf = bufferToHex(sha3(elements[0]));
|
||||||
|
|
||||||
|
const result = await merkleProof.verifyProof(proof, root, leaf);
|
||||||
|
assert.isOk(result, 'verifyProof did not return true for a valid proof');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for an invalid Merkle proof', async function () {
|
||||||
|
const correctElements = ['a', 'b', 'c'];
|
||||||
|
const correctMerkleTree = new MerkleTree(correctElements);
|
||||||
|
|
||||||
|
const correctRoot = correctMerkleTree.getHexRoot();
|
||||||
|
|
||||||
|
const correctLeaf = bufferToHex(sha3(correctElements[0]));
|
||||||
|
|
||||||
|
const badElements = ['d', 'e', 'f'];
|
||||||
|
const badMerkleTree = new MerkleTree(badElements);
|
||||||
|
|
||||||
|
const badProof = badMerkleTree.getHexProof(badElements[0]);
|
||||||
|
|
||||||
|
const result = await merkleProof.verifyProof(badProof, correctRoot, correctLeaf);
|
||||||
|
assert.isNotOk(result, 'verifyProof did not return false for an invalid proof');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for a Merkle proof of invalid length', async function () {
|
||||||
|
const elements = ['a', 'b', 'c'];
|
||||||
|
const merkleTree = new MerkleTree(elements);
|
||||||
|
|
||||||
|
const root = merkleTree.getHexRoot();
|
||||||
|
|
||||||
|
const proof = merkleTree.getHexProof(elements[0]);
|
||||||
|
const badProof = proof.slice(0, proof.length - 5);
|
||||||
|
|
||||||
|
const leaf = bufferToHex(sha3(elements[0]));
|
||||||
|
|
||||||
|
const result = await merkleProof.verifyProof(badProof, root, leaf);
|
||||||
|
assert.isNotOk(result, 'verifyProof did not return false for proof of invalid length');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
98
test/ownership/rbac/RBAC.test.js
Normal file
98
test/ownership/rbac/RBAC.test.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import expectThrow from '../../helpers/expectThrow';
|
||||||
|
import expectEvent from '../../helpers/expectEvent';
|
||||||
|
|
||||||
|
const RBACMock = artifacts.require('RBACMock');
|
||||||
|
|
||||||
|
require('chai')
|
||||||
|
.use(require('chai-as-promised'))
|
||||||
|
.should();
|
||||||
|
|
||||||
|
const ROLE_ADVISOR = 'advisor';
|
||||||
|
|
||||||
|
contract('RBAC', function (accounts) {
|
||||||
|
let mock;
|
||||||
|
|
||||||
|
const [
|
||||||
|
admin,
|
||||||
|
anyone,
|
||||||
|
futureAdvisor,
|
||||||
|
...advisors
|
||||||
|
] = accounts;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
mock = await RBACMock.new(advisors, { from: admin });
|
||||||
|
});
|
||||||
|
|
||||||
|
context('in normal conditions', () => {
|
||||||
|
it('allows admin to call #onlyAdminsCanDoThis', async () => {
|
||||||
|
await mock.onlyAdminsCanDoThis({ from: admin })
|
||||||
|
.should.be.fulfilled;
|
||||||
|
});
|
||||||
|
it('allows admin to call #onlyAdvisorsCanDoThis', async () => {
|
||||||
|
await mock.onlyAdvisorsCanDoThis({ from: admin })
|
||||||
|
.should.be.fulfilled;
|
||||||
|
});
|
||||||
|
it('allows advisors to call #onlyAdvisorsCanDoThis', async () => {
|
||||||
|
await mock.onlyAdvisorsCanDoThis({ from: advisors[0] })
|
||||||
|
.should.be.fulfilled;
|
||||||
|
});
|
||||||
|
it('allows admin to call #eitherAdminOrAdvisorCanDoThis', async () => {
|
||||||
|
await mock.eitherAdminOrAdvisorCanDoThis({ from: admin })
|
||||||
|
.should.be.fulfilled;
|
||||||
|
});
|
||||||
|
it('allows advisors to call #eitherAdminOrAdvisorCanDoThis', async () => {
|
||||||
|
await mock.eitherAdminOrAdvisorCanDoThis({ from: advisors[0] })
|
||||||
|
.should.be.fulfilled;
|
||||||
|
});
|
||||||
|
it('does not allow admins to call #nobodyCanDoThis', async () => {
|
||||||
|
await expectThrow(
|
||||||
|
mock.nobodyCanDoThis({ from: admin })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('does not allow advisors to call #nobodyCanDoThis', async () => {
|
||||||
|
await expectThrow(
|
||||||
|
mock.nobodyCanDoThis({ from: advisors[0] })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('does not allow anyone to call #nobodyCanDoThis', async () => {
|
||||||
|
await expectThrow(
|
||||||
|
mock.nobodyCanDoThis({ from: anyone })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('allows an admin to remove an advisor\'s role', async () => {
|
||||||
|
await mock.removeAdvisor(advisors[0], { from: admin })
|
||||||
|
.should.be.fulfilled;
|
||||||
|
});
|
||||||
|
it('allows admins to #adminRemoveRole', async () => {
|
||||||
|
await mock.adminRemoveRole(advisors[3], ROLE_ADVISOR, { from: admin })
|
||||||
|
.should.be.fulfilled;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('announces a RoleAdded event on addRole', async () => {
|
||||||
|
await expectEvent.inTransaction(
|
||||||
|
mock.adminAddRole(futureAdvisor, ROLE_ADVISOR, { from: admin }),
|
||||||
|
'RoleAdded'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('announces a RoleRemoved event on removeRole', async () => {
|
||||||
|
await expectEvent.inTransaction(
|
||||||
|
mock.adminRemoveRole(futureAdvisor, ROLE_ADVISOR, { from: admin }),
|
||||||
|
'RoleRemoved'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('in adversarial conditions', () => {
|
||||||
|
it('does not allow an advisor to remove another advisor', async () => {
|
||||||
|
await expectThrow(
|
||||||
|
mock.removeAdvisor(advisors[1], { from: advisors[0] })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('does not allow "anyone" to remove an advisor', async () => {
|
||||||
|
await expectThrow(
|
||||||
|
mock.removeAdvisor(advisors[0], { from: anyone })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
71
test/payment/PullPayment.test.js
Normal file
71
test/payment/PullPayment.test.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
var PullPaymentMock = artifacts.require('PullPaymentMock');
|
||||||
|
|
||||||
|
contract('PullPayment', function (accounts) {
|
||||||
|
let ppce;
|
||||||
|
let amount = 17 * 1e18;
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
ppce = await PullPaymentMock.new({ value: amount });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can\'t call asyncSend externally', async function () {
|
||||||
|
assert.isUndefined(ppce.asyncSend);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can record an async payment correctly', async function () {
|
||||||
|
let AMOUNT = 100;
|
||||||
|
await ppce.callSend(accounts[0], AMOUNT);
|
||||||
|
let paymentsToAccount0 = await ppce.payments(accounts[0]);
|
||||||
|
let totalPayments = await ppce.totalPayments();
|
||||||
|
|
||||||
|
assert.equal(totalPayments, AMOUNT);
|
||||||
|
assert.equal(paymentsToAccount0, AMOUNT);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can add multiple balances on one account', async function () {
|
||||||
|
await ppce.callSend(accounts[0], 200);
|
||||||
|
await ppce.callSend(accounts[0], 300);
|
||||||
|
let paymentsToAccount0 = await ppce.payments(accounts[0]);
|
||||||
|
let totalPayments = await ppce.totalPayments();
|
||||||
|
|
||||||
|
assert.equal(totalPayments, 500);
|
||||||
|
assert.equal(paymentsToAccount0, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can add balances on multiple accounts', async function () {
|
||||||
|
await ppce.callSend(accounts[0], 200);
|
||||||
|
await ppce.callSend(accounts[1], 300);
|
||||||
|
|
||||||
|
let paymentsToAccount0 = await ppce.payments(accounts[0]);
|
||||||
|
assert.equal(paymentsToAccount0, 200);
|
||||||
|
|
||||||
|
let paymentsToAccount1 = await ppce.payments(accounts[1]);
|
||||||
|
assert.equal(paymentsToAccount1, 300);
|
||||||
|
|
||||||
|
let totalPayments = await ppce.totalPayments();
|
||||||
|
assert.equal(totalPayments, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can withdraw payment', async function () {
|
||||||
|
let payee = accounts[1];
|
||||||
|
let initialBalance = web3.eth.getBalance(payee);
|
||||||
|
|
||||||
|
await ppce.callSend(payee, amount);
|
||||||
|
|
||||||
|
let payment1 = await ppce.payments(payee);
|
||||||
|
assert.equal(payment1, amount);
|
||||||
|
|
||||||
|
let totalPayments = await ppce.totalPayments();
|
||||||
|
assert.equal(totalPayments, amount);
|
||||||
|
|
||||||
|
await ppce.withdrawPayments({ from: payee });
|
||||||
|
let payment2 = await ppce.payments(payee);
|
||||||
|
assert.equal(payment2, 0);
|
||||||
|
|
||||||
|
totalPayments = await ppce.totalPayments();
|
||||||
|
assert.equal(totalPayments, 0);
|
||||||
|
|
||||||
|
let balance = web3.eth.getBalance(payee);
|
||||||
|
assert(Math.abs(balance - initialBalance - amount) < 1e16);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user