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