Skip to content
This repository was archived by the owner on Jan 14, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions server/dapp/dapp.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ function* receiveProject(userManager, projectManager, projectName) {
return result;
}

// receive project
function* rejectProject(userManager, projectManager, projectName, buyerName) {
rest.verbose('dapp: rejectProject', projectName);
// get the accepted bid
const bid = yield projectManager.getAcceptedBid(projectName);
// get the buyer who created the project
const buyer = yield userManager.getUser(buyerName);
// Reject the project: change state to REJECTED and send money back to the buyer
const result = yield projectManager.rejectProject(projectName, bid.address, buyer.account);
return result;
}

// handle project event
function* handleEvent(userManager, projectManager, args) {
const name = args.name;
Expand All @@ -167,6 +179,9 @@ function* handleEvent(userManager, projectManager, args) {
case ProjectEvent.RECEIVE:
return yield receiveProject(userManager, projectManager, args.projectName);

case ProjectEvent.REJECT:
return yield rejectProject(userManager, projectManager, args.projectName, args.username);

case ProjectEvent.ACCEPT:
return yield acceptBid(userManager, projectManager, args.username, args.password, args.bidId, args.projectName);

Expand Down
19 changes: 17 additions & 2 deletions server/lib/bid/contracts/Bid.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,26 @@ contract Bid is ErrorCodes, BidState {
if (this.balance < amount) {
return ErrorCodes.INSUFFICIENT_BALANCE;
}
uint fee = 10000000 wei; // supplier absorbs the fee
uint fee = 0 wei; // supplier absorbs the fee
uint amountWei = amount * 1 ether;

// transfer will throw
supplierAddress.send(amountWei-fee);
return ErrorCodes.SUCCESS;
}
}

//this function rejects the order in transit and sends funds back to the buyer
//note, having anybody able to call this function is bad security practice, but is done for simplicity in this sample app
function reject(address buyerAddress) returns (ErrorCodes) {
// confirm balance, to return error
if (this.balance < amount) {
return ErrorCodes.INSUFFICIENT_BALANCE;
}

uint amountWei = amount * 1 ether;

// transfer will throw
buyerAddress.send(amountWei);
return ErrorCodes.SUCCESS;
}
}
1 change: 1 addition & 0 deletions server/lib/bid/test/bid.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('Bid tests', function() {
assert.equal(bid.name, name, 'name');
assert.equal(bid.supplier, supplier, 'supplier');
assert.equal(bid.amount, amount, 'amount');
assert.equal(bid.buyer, admin.address, 'buyer');
});

it('Search Contract', function* () {
Expand Down
3 changes: 2 additions & 1 deletion server/lib/project/contracts/ProjectEvent.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ contract ProjectEvent {
NULL,
ACCEPT,
DELIVER,
RECEIVE
RECEIVE,
REJECT
}
}
14 changes: 14 additions & 0 deletions server/lib/project/contracts/ProjectManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ contract ProjectManager is ErrorCodes, Util, ProjectState, ProjectEvent, BidStat
return bid.settle(supplierAddress);
}

function rejectProject(string name, address bidAddress, address buyerAddress) returns (ErrorCodes) {
// validity
if (!exists(name)) return (ErrorCodes.NOT_FOUND);
// set project state
address projectAddress = getProject(name);
var (errorCode, state) = handleEvent(projectAddress, ProjectEvent.REJECT);
if (errorCode != ErrorCodes.SUCCESS) return errorCode;
// reject
Bid bid = Bid(bidAddress);
return bid.reject(buyerAddress);
}

/**
* handleEvent - transition project to a new state based on incoming event
*/
Expand Down Expand Up @@ -133,6 +145,8 @@ contract ProjectManager is ErrorCodes, Util, ProjectState, ProjectEvent, BidStat
if (state == ProjectState.INTRANSIT) {
if (projectEvent == ProjectEvent.RECEIVE)
return (ErrorCodes.SUCCESS, ProjectState.RECEIVED);
if (projectEvent == ProjectEvent.REJECT)
return (ErrorCodes.SUCCESS, ProjectState.REJECTED);
}
return (ErrorCodes.ERROR, state);
}
Expand Down
3 changes: 2 additions & 1 deletion server/lib/project/contracts/ProjectState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ contract ProjectState {
OPEN,
PRODUCTION,
INTRANSIT,
RECEIVED
RECEIVED,
REJECTED
}
}
19 changes: 19 additions & 0 deletions server/lib/project/projectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ function setContract(admin, contract) {
contract.settleProject = function* (projectName, supplierAddress, bidAddress) {
return yield settleProject(admin, contract, projectName, supplierAddress, bidAddress);
}
contract.rejectProject = function* (projectName, bidAddress, buyerAddress) {
return yield rejectProject(admin, contract, projectName, bidAddress, buyerAddress);
}
contract.getAcceptedBid = getAcceptedBid;

return contract;
Expand Down Expand Up @@ -205,6 +208,22 @@ function* settleProject(admin, contract, projectName, supplierAddress, bidAddres
}
}

function* rejectProject(admin, contract, projectName, bidAddress, buyerAddress) {
rest.verbose('rejectProject', {projectName, bidAddress, buyerAddress});
const method = 'rejectProject';
const args = {
name: projectName,
bidAddress: bidAddress,
buyerAddress: buyerAddress,
};

const result = yield rest.callMethod(admin, contract, method, args);
const errorCode = parseInt(result[0]);
if (errorCode != ErrorCodes.SUCCESS) {
throw new Error(errorCode);
}
}

function* getBid(bidId) {
rest.verbose('getBid', bidId);
return (yield rest.waitQuery(`Bid?id=eq.${bidId}`,1))[0];
Expand Down
89 changes: 85 additions & 4 deletions server/lib/project/test/projectManager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ describe('ProjectManager Life Cycle tests', function() {
assert.equal(filtered.length, 1, 'one and only one');
});

it.skip('Accept a Bid (send funds into accepted bid), rejects the others, receive project, settle (send bid funds to supplier)', function* () {
it('Accept a Bid (send funds into accepted bid), rejects the others, receive project, settle (send bid funds to supplier)', function* () {
const uid = util.uid();
const projectArgs = createProjectArgs(uid);
const password = '1234';
Expand All @@ -470,7 +470,7 @@ describe('ProjectManager Life Cycle tests', function() {
const buyer = yield userManagerContract.createUser(buyerArgs);
buyer.password = password; // IRL this will be a prompt to the buyer
// create suppliers
const suppliers = yield createSuppliers(3, password, uid);
const suppliers = yield createSuppliers(1, password, uid);

// create project
const project = yield contract.createProject(projectArgs);
Expand Down Expand Up @@ -513,13 +513,83 @@ describe('ProjectManager Life Cycle tests', function() {
if (supplier.username == acceptedBid.supplier) {
// the winning supplier should have the bid amount minus the tx fee
const delta = supplier.balance.minus(FAUCET_AWARD);
const fee = new BigNumber(10000000);
delta.should.be.bignumber.eq(amountWei.minus(fee));
delta.should.be.bignumber.eq(amountWei);
} else {
// everyone else should have the otiginal value
supplier.balance.should.be.bignumber.eq(FAUCET_AWARD);
}
}
yield rest.getState(acceptedBid);
});

it.only('Accept a Bid (send funds into accepted bid), rejects the others, receive project, reject (send bid funds back to supplier)', function* () {
const uid = util.uid();
const projectArgs = createProjectArgs(uid);
const password = '1234';
const amount = 23;
const amountWei = new BigNumber(amount).times(constants.ETHER);
const FAUCET_AWARD = new BigNumber(1000).times(constants.ETHER) ;
const GAS_LIMIT = new BigNumber(100000000); // default in bockapps-rest

// create buyer and suppliers
const buyerArgs = createUserArgs(projectArgs.buyer, password, UserRole.BUYER);
const buyer = yield userManagerContract.createUser(buyerArgs);
buyer.password = password; // IRL this will be a prompt to the buyer
// create suppliers
const suppliers = yield createSuppliers(1, password, uid);

// create project
const project = yield contract.createProject(projectArgs);
// create bids
const createdBids = yield createMultipleBids(projectArgs.name, suppliers, amount);
{ // test
const bids = yield projectManagerJs.getBidsByName(projectArgs.name);
assert.equal(createdBids.length, bids.length, 'should find all the created bids');
}
// get the buyers balance before accepting a bid
buyer.initialBalance = yield userManagerContract.getBalance(buyer.username);
buyer.initialBalance.should.be.bignumber.eq(FAUCET_AWARD);
// accept one bid (the first)
const acceptedBid = createdBids[0];
yield contract.acceptBid(buyer, acceptedBid.id, projectArgs.name);
// get the buyers balance after accepting a bid
buyer.balance = yield userManagerContract.getBalance(buyer.username);
const delta = buyer.initialBalance.minus(buyer.balance);
delta.should.be.bignumber.gte(amountWei); // amount + fee
delta.should.be.bignumber.lte(amountWei.plus(GAS_LIMIT)); // amount + max fee (gas-limit)
// get the bids
const bids = yield projectManagerJs.getBidsByName(projectArgs.name);
// check that the expected bid is ACCEPTED and all others are REJECTED
bids.map(bid => {
if (bid.id === acceptedBid.id) {
assert.equal(parseInt(bid.state), BidState.ACCEPTED, 'bid should be ACCEPTED');
} else {
assert.equal(parseInt(bid.state), BidState.REJECTED, 'bid should be REJECTED');
};
});
// deliver the project
const projectState = yield contract.handleEvent(projectArgs.name, ProjectEvent.DELIVER);
assert.equal(projectState, ProjectState.INTRANSIT, 'delivered project should be INTRANSIT ');
// receive the project
yield rejectProject(projectArgs.name, buyer.username);

// get the suppliers balances
for (let supplier of suppliers) {
supplier.balance = yield userManagerContract.getBalance(supplier.username);
if (supplier.username == acceptedBid.supplier) {
// the winning supplier should NOT have the bid amount
const delta = supplier.balance.minus(FAUCET_AWARD);
delta.should.be.bignumber.eq(0);
} else {
// everyone else should have the original value
supplier.balance.should.be.bignumber.eq(FAUCET_AWARD);
}
}

//we're just saying up to 1 ether is used for gas fees for testing purposes. The actual amount of gas used will be much lower.
const expectedValueFloor = buyer.initialBalance.minus(constants.ETHER);
buyer.balance.should.be.bignumber.lte(buyer.initialBalance); //accounting for gas fees we should be less than our initial balance
buyer.balance.should.be.bignumber.gte(expectedValueFloor); //we should be larger though than if we had still had 23 eth locked in escrow
});

function* createSuppliers(count, password, uid) {
Expand All @@ -544,6 +614,17 @@ describe('ProjectManager Life Cycle tests', function() {
yield contract.settleProject(projectName, supplier.account, bid.address);
}

// throws: ErrorCodes
function* rejectProject(projectName, buyerName) {
rest.verbose('rejectProject', projectName);
// get the accepted bid
const bid = yield projectManagerJs.getAcceptedBid(projectName);
// get the supplier for the accepted bid
const buyer = yield userManagerContract.getUser(buyerName);
// Settle the project: change state to REJECTED and send the funds back to the buyer
yield contract.rejectProject(projectName, bid.address, buyer.address);
}

});

// function createUser(address account, string username, bytes32 pwHash, UserRole role) returns (ErrorCodes) {
Expand Down
7 changes: 6 additions & 1 deletion ui/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@ export const STATES = {
state: 'RECEIVED',
icon: 'mood'
},
5: {
state: 'REJECTED',
icon: 'mood_bad'
},
OPEN: 1,
PRODUCTION: 2,
INTRANSIT: 3,
RECEIVED: 4,
REJECTED: 5,
}

export const BID_STATES = {
Expand All @@ -38,4 +43,4 @@ export const BID_STATES = {
REJECTED: 3,
}

export const PROJECT_EVENTS = ['NULL', 'Accepted', 'Shipped', 'Received']
export const PROJECT_EVENTS = ['NULL', 'Accepted', 'Shipped', 'Received', 'Rejected']
12 changes: 11 additions & 1 deletion ui/src/scenes/Projects/components/Project/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Project extends Component {
if (this.isBuyer) {

if (parseInt(project.state, 10) === STATES.INTRANSIT) {
//the 3 is a receive event and the 4 is a REJECT event (see ProjectEvent.sol)
actions.push(
<Button
icon
Expand All @@ -67,7 +68,16 @@ class Project extends Component {
key="mood"
>
mood
</Button>
</Button>,
<Button
icon
primary
onClick={(e) => this.handleProjectEventClick(e, project.name, 4)}
tooltipLabel="Mark as Rejected"
key="mood_bad"
>
mood_bad
</Button>,
);
}
}
Expand Down