Added friends to message tributes to skip fees and added test unit
This commit is contained in:
parent
5272171a17
commit
0db5b6af5d
|
@ -26,6 +26,9 @@ contract MessageTribute is Controlled {
|
||||||
mapping(address => mapping(address => Fee)) feeCatalog;
|
mapping(address => mapping(address => Fee)) feeCatalog;
|
||||||
mapping(address => uint256) balances;
|
mapping(address => uint256) balances;
|
||||||
|
|
||||||
|
mapping(bytes32 => uint256) friendIndex;
|
||||||
|
address[] friends;
|
||||||
|
|
||||||
struct Audience {
|
struct Audience {
|
||||||
uint256 blockNum;
|
uint256 blockNum;
|
||||||
Fee fee;
|
Fee fee;
|
||||||
|
@ -33,12 +36,39 @@ contract MessageTribute is Controlled {
|
||||||
|
|
||||||
mapping(address => mapping(address => Audience)) audienceRequested;
|
mapping(address => mapping(address => Audience)) audienceRequested;
|
||||||
|
|
||||||
|
|
||||||
function MessageTribute(address _SNT) public {
|
function MessageTribute(address _SNT) public {
|
||||||
SNT = MiniMeToken(_SNT);
|
SNT = MiniMeToken(_SNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addFriends(address[] _friends) public {
|
||||||
|
uint256 len = _friends.length;
|
||||||
|
for (uint256 i = 0; i < len; i++) {
|
||||||
|
bytes32 frHash = keccak256(_friends[i], msg.sender);
|
||||||
|
if (friendIndex[frHash] == 0)
|
||||||
|
friendIndex[frHash] = friends.push(_friends[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFriends(address[] _friends) public {
|
||||||
|
uint256 len = _friends.length;
|
||||||
|
for (uint256 i = 0; i < len; i++) {
|
||||||
|
bytes32 frHash = keccak256(_friends[i], msg.sender);
|
||||||
|
require(friendIndex[frHash] > 0);
|
||||||
|
uint index = friendIndex[frHash] - 1;
|
||||||
|
delete friendIndex[frHash];
|
||||||
|
address replacer = friends[friends.length - 1];
|
||||||
|
friends[index] = replacer;
|
||||||
|
friendIndex[keccak256(replacer, msg.sender)] = index;
|
||||||
|
friends.length--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFriend(address _friend) public view returns(bool) {
|
||||||
|
return friendIndex[keccak256(_friend, msg.sender)] > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function setRequiredTribute(address _to, uint _amount, bool _isTribute, bool _isPermanent) public {
|
function setRequiredTribute(address _to, uint _amount, bool _isTribute, bool _isPermanent) public {
|
||||||
|
require(friendIndex[keccak256(msg.sender, _to)] == 0);
|
||||||
feeCatalog[msg.sender][_to] = Fee(_amount, _isTribute, _isPermanent);
|
feeCatalog[msg.sender][_to] = Fee(_amount, _isTribute, _isPermanent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,19 +80,10 @@ contract MessageTribute is Controlled {
|
||||||
tribute = f.tribute;
|
tribute = f.tribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFee(address _from) internal
|
|
||||||
returns (Fee)
|
|
||||||
{
|
|
||||||
Fee memory generalFee = feeCatalog[_from][address(0)];
|
|
||||||
Fee memory specificFee = feeCatalog[_from][msg.sender];
|
|
||||||
return specificFee.amount > 0 ? specificFee : generalFee;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function deposit(uint256 _value) public {
|
function deposit(uint256 _value) public {
|
||||||
require(_value > 0);
|
require(_value > 0);
|
||||||
balances[msg.sender] += _value;
|
balances[msg.sender] += _value;
|
||||||
require(SNT.transferFrom(msg.sender, this, _value));
|
require(SNT.transferFrom(msg.sender, address(this), _value));
|
||||||
}
|
}
|
||||||
|
|
||||||
function balance() public view returns (uint256) {
|
function balance() public view returns (uint256) {
|
||||||
|
@ -72,7 +93,8 @@ contract MessageTribute is Controlled {
|
||||||
function withdraw(uint256 _value) public {
|
function withdraw(uint256 _value) public {
|
||||||
require(balances[msg.sender] > 0);
|
require(balances[msg.sender] > 0);
|
||||||
require(_value <= balances[msg.sender]);
|
require(_value <= balances[msg.sender]);
|
||||||
require(SNT.transferFrom(msg.sender, this, _value));
|
balances[msg.sender] -= _value;
|
||||||
|
require(SNT.transfer(msg.sender, _value));
|
||||||
}
|
}
|
||||||
|
|
||||||
event AudienceRequested(address from, address to);
|
event AudienceRequested(address from, address to);
|
||||||
|
@ -91,6 +113,11 @@ contract MessageTribute is Controlled {
|
||||||
balances[msg.sender] -= f.amount;
|
balances[msg.sender] -= f.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasPendingAudience(address _from)
|
||||||
|
public view returns (bool) {
|
||||||
|
return audienceRequested[_from][msg.sender].blockNum > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function cancelAudienceRequest(address _from) public {
|
function cancelAudienceRequest(address _from) public {
|
||||||
if (audienceRequested[_from][msg.sender].blockNum > 0) {
|
if (audienceRequested[_from][msg.sender].blockNum > 0) {
|
||||||
AudienceCancelled(_from, msg.sender);
|
AudienceCancelled(_from, msg.sender);
|
||||||
|
@ -99,7 +126,7 @@ contract MessageTribute is Controlled {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function grantAudience(address _to, bool _approve) {
|
function grantAudience(address _to, bool _approve) public {
|
||||||
|
|
||||||
Audience memory aud = audienceRequested[msg.sender][_to];
|
Audience memory aud = audienceRequested[msg.sender][_to];
|
||||||
|
|
||||||
|
@ -132,6 +159,18 @@ contract MessageTribute is Controlled {
|
||||||
return getFee(_to).amount <= balances[msg.sender];
|
return getFee(_to).amount <= balances[msg.sender];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFee(address _from) internal view
|
||||||
|
returns (Fee)
|
||||||
|
{
|
||||||
|
Fee memory generalFee = feeCatalog[_from][address(0)];
|
||||||
|
Fee memory specificFee = feeCatalog[_from][msg.sender];
|
||||||
|
|
||||||
|
if (friendIndex[keccak256(msg.sender, _from)] > 0)
|
||||||
|
return Fee(0, false, false);
|
||||||
|
|
||||||
|
return specificFee.amount > 0 ? specificFee : generalFee;
|
||||||
|
}
|
||||||
|
|
||||||
function clearFee(address _from, address _to) private {
|
function clearFee(address _from, address _to) private {
|
||||||
if (!feeCatalog[_from][_to].permanent) {
|
if (!feeCatalog[_from][_to].permanent) {
|
||||||
feeCatalog[_from][_to].amount = 0;
|
feeCatalog[_from][_to].amount = 0;
|
||||||
|
@ -140,4 +179,6 @@ contract MessageTribute is Controlled {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,312 @@
|
||||||
|
const TestUtils = require("../utils/testUtils.js");
|
||||||
|
|
||||||
|
describe('MessageTribute', function() {
|
||||||
|
|
||||||
|
let accounts;
|
||||||
|
let SNT;
|
||||||
|
|
||||||
|
this.timeout(0);
|
||||||
|
|
||||||
|
before( function(done) {
|
||||||
|
this.timeout(0);
|
||||||
|
|
||||||
|
EmbarkSpec.deployAll({
|
||||||
|
"MiniMeTokenFactory": {},
|
||||||
|
"MiniMeToken": {
|
||||||
|
"args": [
|
||||||
|
"$MiniMeTokenFactory",
|
||||||
|
"0x0",
|
||||||
|
"0x0",
|
||||||
|
"Status Test Token",
|
||||||
|
18,
|
||||||
|
"STT",
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"MessageTribute": {
|
||||||
|
"args": ["$MiniMeToken"]
|
||||||
|
}
|
||||||
|
}, (_accounts) => {
|
||||||
|
accounts = _accounts;
|
||||||
|
|
||||||
|
SNT = MiniMeToken;
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
SNT.methods.generateTokens(accounts[0], 5000).send(),
|
||||||
|
SNT.methods.generateTokens(accounts[1], 5000).send(),
|
||||||
|
SNT.methods.generateTokens(accounts[2], 5000).send(),
|
||||||
|
SNT.methods.generateTokens(accounts[3], 5000).send(),
|
||||||
|
SNT.methods.generateTokens(accounts[4], 5000).send(),
|
||||||
|
SNT.methods.generateTokens(accounts[5], 5000).send(),
|
||||||
|
SNT.methods.generateTokens(accounts[6], 5000).send(),
|
||||||
|
SNT.methods.generateTokens(accounts[7], 5000).send(),
|
||||||
|
SNT.methods.generateTokens(accounts[8], 5000).send(),
|
||||||
|
SNT.methods.generateTokens(accounts[9], 5000).send()
|
||||||
|
])
|
||||||
|
.then(() => {
|
||||||
|
console.log(" - Added balances");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Adding friends", async () => {
|
||||||
|
await MessageTribute.methods.addFriends([accounts[1], accounts[2]]).send({from: accounts[0]});
|
||||||
|
await MessageTribute.methods.addFriends([accounts[3]]).send({from: accounts[1]});
|
||||||
|
await MessageTribute.methods.addFriends([accounts[4]]).send({from: accounts[1]});
|
||||||
|
await MessageTribute.methods.addFriends([accounts[5]]).send({from: accounts[1]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.isFriend(accounts[1]).call({from: accounts[0]}),
|
||||||
|
true,
|
||||||
|
accounts[1] + " must be a friend of " + accounts[0]);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.isFriend(accounts[3]).call({from: accounts[1]}),
|
||||||
|
true,
|
||||||
|
accounts[3] + " must be a friend of " + accounts[1]);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.isFriend(accounts[4]).call({from: accounts[0]}),
|
||||||
|
false,
|
||||||
|
accounts[4] + " must not be a friend of " + accounts[0]);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.isFriend(accounts[2]).call({from: accounts[1]}),
|
||||||
|
false,
|
||||||
|
accounts[2] + " must not be a friend of " + accounts[1]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Removing friends", async () => {
|
||||||
|
await MessageTribute.methods.removeFriends([accounts[3]]).send({from: accounts[1]});
|
||||||
|
|
||||||
|
await MessageTribute.methods.removeFriends([accounts[4], accounts[5]]).send({from: accounts[1]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.isFriend(accounts[3]).call({from: accounts[0]}),
|
||||||
|
false,
|
||||||
|
accounts[3] + " must not be a friend of " + accounts[0]);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.isFriend(accounts[4]).call({from: accounts[0]}),
|
||||||
|
false,
|
||||||
|
accounts[4] + " must not be a friend of " + accounts[0]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let tx = await MessageTribute.methods.removeFriends([accounts[5]]).send({from: accounts[1]});
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should be able to deposit", async() => {
|
||||||
|
let amount = 2000;
|
||||||
|
|
||||||
|
await SNT.methods.approve(MessageTribute.address, amount).send({from: accounts[1]});
|
||||||
|
|
||||||
|
let initialBalance = await SNT.methods.balanceOf(accounts[1]).call();
|
||||||
|
|
||||||
|
await MessageTribute.methods.deposit(amount).send({from: accounts[1]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.balance().call({from: accounts[1]}),
|
||||||
|
amount,
|
||||||
|
"Deposited balance must be " + amount);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await SNT.methods.balanceOf(accounts[1]).call(),
|
||||||
|
web3.utils.toBN(initialBalance).sub(web3.utils.toBN(amount)).toString(),
|
||||||
|
accounts[1] + " SNT balance is incorrect");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await SNT.methods.balanceOf(MessageTribute.address).call(),
|
||||||
|
amount,
|
||||||
|
"Contract SNT balance is incorrect");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should be able to withdraw", async() => {
|
||||||
|
let amount = 2000;
|
||||||
|
|
||||||
|
let initialBalance = await SNT.methods.balanceOf(accounts[1]).call();
|
||||||
|
|
||||||
|
await MessageTribute.methods.withdraw(amount).send({from: accounts[1]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await SNT.methods.balanceOf(accounts[1]).call(),
|
||||||
|
web3.utils.toBN(initialBalance).add(web3.utils.toBN(amount)).toString(),
|
||||||
|
"SNT Balance must be " + initialBalance + amount);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await SNT.methods.balanceOf(MessageTribute.address).call(),
|
||||||
|
0,
|
||||||
|
"Contract SNT balance is incorrect");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.balance().call({from: accounts[1]}),
|
||||||
|
0,
|
||||||
|
"Deposited balance must be 0");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Requesting audience without requiring deposit", async () => {
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.balance().call({from: accounts[9]}),
|
||||||
|
0,
|
||||||
|
"Deposited balance must be 0");
|
||||||
|
|
||||||
|
let tx = await MessageTribute.methods.requestAudience(accounts[0]).send({from: accounts[9]});
|
||||||
|
|
||||||
|
assert.notEqual(tx.events.AudienceRequested, undefined, "AudienceRequested wasn't triggered");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Requesting audience requiring deposit and no funds deposited", async () => {
|
||||||
|
await MessageTribute.methods.setRequiredTribute(accounts[9], 100, false, true).send({from: accounts[0]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.balance().call({from: accounts[9]}),
|
||||||
|
0,
|
||||||
|
"Deposited balance must be 0");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.hasEnoughFundsToTalk(accounts[0]).call({from: accounts[9]}),
|
||||||
|
false,
|
||||||
|
"Must return false");
|
||||||
|
|
||||||
|
try {
|
||||||
|
let tx = await MessageTribute.methods.requestAudience(accounts[0]).send({from: accounts[9]});
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Requesting an audience as a friend", async() => {
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.isFriend(accounts[2]).call({from: accounts[0]}),
|
||||||
|
true,
|
||||||
|
"Should be friends");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.balance().call({from: accounts[2]}),
|
||||||
|
0,
|
||||||
|
"Deposited balance must be 0");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.hasEnoughFundsToTalk(accounts[0]).call({from: accounts[2]}),
|
||||||
|
true,
|
||||||
|
"Must return true");
|
||||||
|
|
||||||
|
let tx = await MessageTribute.methods.requestAudience(accounts[0]).send({from: accounts[2]});
|
||||||
|
|
||||||
|
assert.notEqual(tx.events.AudienceRequested, undefined, "AudienceRequested wasn't triggered");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Request audience requiring deposit, not having funds at the beginning, deposit funds and request audience", async () => {
|
||||||
|
await MessageTribute.methods.setRequiredTribute(accounts[8], 200, false, true).send({from: accounts[0]});
|
||||||
|
|
||||||
|
await SNT.methods.approve(MessageTribute.address, 100).send({from: accounts[8]});
|
||||||
|
await MessageTribute.methods.deposit(100).send({from: accounts[8]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.balance().call({from: accounts[8]}),
|
||||||
|
100,
|
||||||
|
"Deposited balance must be 100");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.hasEnoughFundsToTalk(accounts[0]).call({from: accounts[8]}),
|
||||||
|
false,
|
||||||
|
"Must return false");
|
||||||
|
|
||||||
|
try {
|
||||||
|
let tx = await MessageTribute.methods.requestAudience(accounts[0]).send({from: accounts[8]});
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await SNT.methods.approve(MessageTribute.address, 100).send({from: accounts[8]});
|
||||||
|
await MessageTribute.methods.deposit(100).send({from: accounts[8]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.balance().call({from: accounts[8]}),
|
||||||
|
200,
|
||||||
|
"Deposited balance must be 200");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.hasEnoughFundsToTalk(accounts[0]).call({from: accounts[8]}),
|
||||||
|
true,
|
||||||
|
"Must return true");
|
||||||
|
|
||||||
|
let tx = await MessageTribute.methods.requestAudience(accounts[0]).send({from: accounts[8]});
|
||||||
|
|
||||||
|
assert.notEqual(tx.events.AudienceRequested, undefined, "AudienceRequested wasn't triggered");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Requesting tribute from specific account", async() => {
|
||||||
|
await MessageTribute.methods.setRequiredTribute(accounts[7], 100, true, true).send({from: accounts[0]});
|
||||||
|
|
||||||
|
await SNT.methods.approve(MessageTribute.address, 200).send({from: accounts[7]});
|
||||||
|
await MessageTribute.methods.deposit(200).send({from: accounts[7]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.balance().call({from: accounts[7]}),
|
||||||
|
200,
|
||||||
|
"Deposited balance must be 200");
|
||||||
|
|
||||||
|
let tx = await MessageTribute.methods.requestAudience(accounts[0]).send({from: accounts[7]});
|
||||||
|
|
||||||
|
assert.notEqual(tx.events.AudienceRequested, undefined, "AudienceRequested wasn't triggered");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.balance().call({from: accounts[7]}),
|
||||||
|
100,
|
||||||
|
"Deposited balance must be 100");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Missing tests", async () => {
|
||||||
|
// Cancelling Audience - account8
|
||||||
|
|
||||||
|
// Granting Audience - account7
|
||||||
|
|
||||||
|
// Deniying Audience
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Requiring 200 tribute from account6 non permanent
|
||||||
|
await MessageTribute.methods.setRequiredTribute(accounts[4], 200, true, false).send({from: accounts[0]});
|
||||||
|
|
||||||
|
// Requiring 100 deposit from everyone
|
||||||
|
await MessageTribute.methods.setRequiredTribute("0x0", 100, false, true).send({from: accounts[0]});
|
||||||
|
|
||||||
|
// Requiring 100 tribute from everyone
|
||||||
|
await MessageTribute.methods.setRequiredTribute("0x0", 100, true, true).send({from: accounts[1]});
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Requesting a non permanent tribute from specific account", async() => {
|
||||||
|
await MessageTribute.methods.setRequiredTribute(accounts[6], 100, true, false).send({from: accounts[0]});
|
||||||
|
|
||||||
|
await SNT.methods.approve(MessageTribute.address, 200).send({from: accounts[6]});
|
||||||
|
await MessageTribute.methods.deposit(200).send({from: accounts[6]});
|
||||||
|
|
||||||
|
let tx = await MessageTribute.methods.requestAudience(accounts[0]).send({from: accounts[6]});
|
||||||
|
|
||||||
|
assert.notEqual(tx.events.AudienceRequested, undefined, "AudienceRequested wasn't triggered");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await MessageTribute.methods.balance().call({from: accounts[6]}),
|
||||||
|
100,
|
||||||
|
"Deposited balance must be 100");
|
||||||
|
|
||||||
|
// TODO grant audience
|
||||||
|
// TODO request another audience
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue