Skip to content

Commit b6c4839

Browse files
authored
Allow truffle projects to contain vyper contracts (#571)
1 parent c88eed2 commit b6c4839

File tree

4 files changed

+124
-6
lines changed

4 files changed

+124
-6
lines changed

plugins/resources/plugin.utils.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ function checkContext(config, tempContractsDir, tempArtifactsDir){
131131
// =============================
132132

133133
function assembleFiles(config, skipFiles=[]){
134-
const targetsPath = path.join(config.contractsDir, '**', '*.sol');
134+
const targetsPath = path.join(config.contractsDir, '**', '*.{sol,vy}');
135135
const targets = shell.ls(targetsPath).map(path.normalize);
136136

137137
skipFiles = assembleSkipped(config, targets, skipFiles);
@@ -145,7 +145,7 @@ function assembleTargets(config, targets=[], skipFiles=[]){
145145
const cd = config.contractsDir;
146146

147147
for (let target of targets){
148-
if (skipFiles.includes(target)){
148+
if (skipFiles.includes(target) || path.extname(target) === '.vy'){
149149

150150
skipped.push({
151151
canonicalPath: target,
@@ -177,7 +177,9 @@ function assembleSkipped(config, targets, skipFiles=[]){
177177
skipFiles = skipFiles.map(contract => path.join(config.contractsDir, contract));
178178

179179
// Enumerate files in skipped folders
180-
const skipFolders = skipFiles.filter(item => path.extname(item) !== '.sol')
180+
const skipFolders = skipFiles.filter(item => {
181+
return path.extname(item) !== '.sol' || path.extname(item) !== '.vy'
182+
});
181183

182184
for (let folder of skipFolders){
183185
for (let target of targets ) {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Open Auction
2+
3+
# Auction params
4+
# Beneficiary receives money from the highest bidder
5+
beneficiary: public(address)
6+
auctionStart: public(uint256)
7+
auctionEnd: public(uint256)
8+
9+
# Current state of auction
10+
highestBidder: public(address)
11+
highestBid: public(uint256)
12+
13+
# Set to true at the end, disallows any change
14+
ended: public(bool)
15+
16+
# Keep track of refunded bids so we can follow the withdraw pattern
17+
pendingReturns: public(HashMap[address, uint256])
18+
19+
# Create a simple auction with `_bidding_time`
20+
# seconds bidding time on behalf of the
21+
# beneficiary address `_beneficiary`.
22+
@external
23+
def __init__(_beneficiary: address, _bidding_time: uint256):
24+
self.beneficiary = _beneficiary
25+
self.auctionStart = block.timestamp
26+
self.auctionEnd = self.auctionStart + _bidding_time
27+
28+
# Bid on the auction with the value sent
29+
# together with this transaction.
30+
# The value will only be refunded if the
31+
# auction is not won.
32+
@external
33+
@payable
34+
def bid():
35+
# Check if bidding period is over.
36+
assert block.timestamp < self.auctionEnd
37+
# Check if bid is high enough
38+
assert msg.value > self.highestBid
39+
# Track the refund for the previous high bidder
40+
self.pendingReturns[self.highestBidder] += self.highestBid
41+
# Track new high bid
42+
self.highestBidder = msg.sender
43+
self.highestBid = msg.value
44+
45+
# Withdraw a previously refunded bid. The withdraw pattern is
46+
# used here to avoid a security issue. If refunds were directly
47+
# sent as part of bid(), a malicious bidding contract could block
48+
# those refunds and thus block new higher bids from coming in.
49+
@external
50+
def withdraw():
51+
pending_amount: uint256 = self.pendingReturns[msg.sender]
52+
self.pendingReturns[msg.sender] = 0
53+
send(msg.sender, pending_amount)
54+
55+
# End the auction and send the highest bid
56+
# to the beneficiary.
57+
@external
58+
def endAuction():
59+
# It is a good guideline to structure functions that interact
60+
# with other contracts (i.e. they call functions or send Ether)
61+
# into three phases:
62+
# 1. checking conditions
63+
# 2. performing actions (potentially changing conditions)
64+
# 3. interacting with other contracts
65+
# If these phases are mixed up, the other contract could call
66+
# back into the current contract and modify the state or cause
67+
# effects (Ether payout) to be performed multiple times.
68+
# If functions called internally include interaction with external
69+
# contracts, they also have to be considered interaction with
70+
# external contracts.
71+
72+
# 1. Conditions
73+
# Check if auction endtime has been reached
74+
assert block.timestamp >= self.auctionEnd
75+
# Check if this function has already been called
76+
assert not self.ended
77+
78+
# 2. Effects
79+
self.ended = True
80+
81+
# 3. Interaction
82+
send(self.beneficiary, self.highestBid)

test/units/truffle/standard.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,4 +467,34 @@ describe('Truffle Plugin: standard use cases', function() {
467467

468468
verify.lineCoverage(expected);
469469
})
470+
471+
it('compiles when a project includes vyper contracts', async function() {
472+
const skipMigration = true;
473+
474+
truffleConfig.logger = mock.testLogger;
475+
solcoverConfig.istanbulReporter = ['json-summary', 'text']
476+
477+
mock.installDouble(
478+
['Simple', 'auction.vy'],
479+
'simple.js',
480+
solcoverConfig,
481+
skipMigration
482+
);
483+
484+
485+
await plugin(truffleConfig);
486+
487+
assert(
488+
mock.loggerOutput.val.includes('Compiling ./.coverage_contracts/auction.vy')
489+
);
490+
491+
const expected = [
492+
{
493+
file: mock.pathToContract(truffleConfig, 'Simple.sol'),
494+
pct: 100
495+
}
496+
];
497+
498+
verify.lineCoverage(expected);
499+
});
470500
})

test/util/integration.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ function install(
303303
/**
304304
* Installs mock truffle/buidler project with two contracts (for inheritance, libraries, etc)
305305
*/
306-
function installDouble(contracts, test, config) {
306+
function installDouble(contracts, test, config, skipMigration) {
307307
const configjs = getSolcoverJS(config);
308308
const migration = deployDouble(contracts);
309309

@@ -313,11 +313,15 @@ function installDouble(contracts, test, config) {
313313

314314
// Contracts
315315
contracts.forEach(item => {
316-
shell.cp(`${sourcesPath}${item}.sol`, `${temp}/contracts/${item}.sol`)
316+
(item.includes('.'))
317+
? shell.cp(`${sourcesPath}${item}`, `${temp}/contracts/${item}`)
318+
: shell.cp(`${sourcesPath}${item}.sol`, `${temp}/contracts/${item}.sol`);
317319
});
318320

319321
// Migration
320-
fs.writeFileSync(migrationPath, migration)
322+
if (!skipMigration){
323+
fs.writeFileSync(migrationPath, migration)
324+
}
321325

322326
// Test
323327
shell.cp(`${testPath}${test}`, `${temp}/test/${test}`);

0 commit comments

Comments
 (0)