Skip to content

Instantly share code, notes, and snippets.

@stefek99
Created June 6, 2018 14:34
Show Gist options
  • Save stefek99/e7acf11b40df03473425125f00e7b108 to your computer and use it in GitHub Desktop.
Save stefek99/e7acf11b40df03473425125f00e7b108 to your computer and use it in GitHub Desktop.
Auction contract - getting better at Solidity
/* eslint-disable no-undef */ // Avoid the linter considering truffle elements as undef.
const Auction = artifacts.require('Auction.sol')
const { expectThrow, increaseTime } = require('./helpers')
contract('Auction', function (accounts) {
let owner = accounts[0]
let bidderA = accounts[1]
let bidderB = accounts[2]
let bidderC = accounts[3]
let duration = 3600;
let auction;
let timestampEnd
beforeEach(async function() {
timestampEnd = web3.eth.getBlock('latest').timestamp + duration; // 1 hour from now
auction = await Auction.new(1e18, "item", timestampEnd, {from: owner});
});
it('Should be able to set up the constructor auction', async function() {
assert.equal(await auction.owner(), owner, 'The owner is not set correctly')
assert.equal(await auction.description(), "item", 'The description is not set correctly')
assert.equal(await auction.timestampEnd(), timestampEnd, 'The endtime is not set correctly')
})
it('Should be able to send a bid above the initial price', async function() {
await auction.sendTransaction({ value: 1e18, from: bidderA });
assert.equal(await auction.price(), 1e18, "Price not set up correctly");
assert.equal(await auction.winner(), bidderA, "Winner not set up correctly");
})
it('Should not be able to send a bid below the initial price', async function() {
await expectThrow(auction.sendTransaction({ value: 0.5e18, from: bidderA }));
})
it('Should not be able to send a bid after the end of auction', async function() {
increaseTime(duration + 1);
await expectThrow(auction.sendTransaction({ value: 1e18, from: bidderA }));
})
it('Should be able to outbid', async function() {
await auction.sendTransaction({ value: 1e18, from: bidderA });
await auction.sendTransaction({ value: 1.25e18, from: bidderB });
assert.equal(await auction.price(), 1.25e18, "Price not set up correctly");
assert.equal(await auction.winner(), bidderB, "Winner not set up correctly");
})
it('Should not be able to outbid if bid too low', async function() {
await auction.sendTransaction({ value: 1e18, from: bidderA });
await expectThrow(auction.sendTransaction({ value: 1.2e18, from: bidderB }))
assert.equal(await auction.price(), 1e18, "Price not set up correctly");
assert.equal(await auction.winner(), bidderA, "Winner not set up correctly");
});
it('Should be able to outbid - partially (we already have some ETH', async function() {
await auction.sendTransaction({ value: 1e18, from: bidderA });
await auction.sendTransaction({ value: 1.25e18, from: bidderB });
await auction.sendTransaction({ value: 0.5625e18, from: bidderA });
assert.equal(await auction.price(), 1.5625e18, "Price not set up correctly");
assert.equal(await auction.winner(), bidderA, "Winner not set up correctly");
});
it('Winner should be able to set delivery instructions', async function() {
await auction.sendTransaction({ value: 1e18, from: bidderA });
increaseTime(3700);
await expectThrow(auction.setInstructions("whitehouse", {from: bidderB }));
await auction.setInstructions("whitehouse", {from: bidderA });
assert.equal(await auction.instructions(), "whitehouse", "Delivery instructions not set up correctly");
});
});
pragma solidity ^0.4.23;
contract Auction {
string public description;
string public instructions; // will be used for delivery address or email
uint public price;
bool public initialPrice = true; // at first asking price is OK, then +25% required
uint public timestampEnd;
address public owner;
address public winner;
mapping(address => uint) public bids;
// TODO - expand time factor
// a) 24 hours or less before the end
// b) 4 hours the expansion
uint public increaseTimeIfBidBeforeEnd = 24 * 60 * 60; // Naming things: https://www.instagram.com/p/BSa_O5zjh8X/
uint public increaseTimeBy = 24 * 60 * 60;
event Bid(address indexed winner, uint indexed price, uint indexed timestamp);
modifier onlyOwner { require(owner == msg.sender, "only owner"); _; }
modifier onlyWinner { require(winner == msg.sender, "only winner"); _; }
modifier ended { require(timestampEnd > now, "not ended yet"); _; }
function setDescription(string _description) public onlyOwner() {
description = _description;
}
function setInstructions(string _instructions) public ended() onlyWinner() {
instructions = _instructions;
}
constructor(uint _price, string _description, uint _timestampEnd) public {
require(_timestampEnd > now, "end of the auction must be in the future");
owner = msg.sender;
price = _price;
description = _description;
timestampEnd = _timestampEnd;
}
function() public payable {
require(now < timestampEnd, "auction has ended");
if (bids[msg.sender] > 0) { // First we add the bid to an existing bid
bids[msg.sender] += msg.value;
} else {
bids[msg.sender] = msg.value;
}
if (initialPrice) {
require(bids[msg.sender] >= price, "big too low, minimum is the initial price");
} else {
require(bids[msg.sender] >= (price * 5 / 4), "big too low, minimum 25% increment");
}
if (now > timestampEnd - increaseTimeIfBidBeforeEnd) {
timestampEnd = now + increaseTimeBy;
}
initialPrice = false;
price = bids[msg.sender];
winner = msg.sender;
emit Bid(winner, price, now);
}
function withdraw() public {
// TODO WIP
}
function withdrawBeneficiary() public ended() onlyOwner() {
// TODO WIP
}
}
module.exports = {
increaseTime: addSeconds => {
web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [addSeconds], id: 0})
},
mineBlock: () => {
web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine"})
},
expectThrow: async promise => {
try {
await promise
} catch (error) {
const invalidJump = error.message.search('invalid JUMP') >= 0
const invalidOpcode = error.message.search('invalid opcode') >= 0
const outOfGas = error.message.search('out of gas') >= 0
// TODO: Test if the error is a revert.
return
}
assert.fail('Expected throw not received')
},
waitForMined: tx => {
return new Promise((resolve, reject) => {
let setIntervalId = setInterval(() => web3.eth.getTransactionReceipt(tx, (err, receipt) => {
if (err) reject(err.message)
if (receipt) {
clearInterval(setIntervalId)
resolve(receipt)
}
}), 1000)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment