2020-02-21 19:01:15 +00:00
const EmbarkJS = artifacts . require ( 'EmbarkJS' ) ;
2020-02-19 09:33:38 +00:00
const TestToken = artifacts . require ( 'TestToken' ) ;
2020-02-21 19:01:15 +00:00
const _GiftBucket = artifacts . require ( 'GiftBucket' ) ;
const GiftBucketFactory = artifacts . require ( 'GiftBucketFactory' ) ;
2020-02-18 19:23:31 +00:00
2020-02-19 11:28:05 +00:00
const TOTAL _SUPPLY = 10000 ;
const GIFT _AMOUNT = 10 ;
const REDEEM _CODE = web3 . utils . sha3 ( "hello world" ) ;
const NOW = Math . round ( new Date ( ) . getTime ( ) / 1000 ) ;
const EXPIRATION _TIME = NOW + 60 * 60 * 24 ; // in 24 hours
2020-03-25 08:41:59 +00:00
const NEW _EXPIRATION _TIME = EXPIRATION _TIME + 60 ;
2020-02-19 11:28:05 +00:00
2020-02-18 19:23:31 +00:00
let shop ,
2020-02-21 22:57:44 +00:00
user ,
relayer ,
keycard _1 ,
keycard _2 ;
2020-02-18 19:23:31 +00:00
config ( {
contracts : {
deploy : {
"TestToken" : {
2020-03-30 13:43:39 +00:00
args : [ "TEST" , 18 ] ,
2020-02-19 11:28:05 +00:00
} ,
2020-02-21 11:41:03 +00:00
"GiftBucket" : {
2020-02-19 11:28:05 +00:00
args : [ "$TestToken" , EXPIRATION _TIME ] ,
2020-02-21 19:01:15 +00:00
} ,
"GiftBucketFactory" : {
args : [ ] ,
} ,
2020-02-18 19:23:31 +00:00
}
2020-02-19 13:35:11 +00:00
} ,
2020-02-18 19:23:31 +00:00
} , ( _err , _accounts ) => {
2020-02-21 22:57:44 +00:00
shop = _accounts [ 0 ] ;
user = _accounts [ 1 ] ;
relayer = _accounts [ 2 ] ;
keycard _1 = _accounts [ 3 ] ;
keycard _2 = _accounts [ 4 ] ;
2020-02-18 19:23:31 +00:00
} ) ;
let sendMethod ;
async function signRedeem ( contractAddress , signer , message ) {
const result = await web3 . eth . net . getId ( ) ;
2020-02-19 13:35:11 +00:00
let chainId = parseInt ( result ) ;
2020-04-01 16:27:06 +00:00
//FIXME: in tests, getChainID in the contract returns 1 so we hardcode it here to 1.
2020-02-19 13:35:11 +00:00
chainId = 1 ;
2020-02-18 19:23:31 +00:00
2020-04-01 16:27:06 +00:00
const domain = [
2020-02-18 19:23:31 +00:00
{ name : "name" , type : "string" } ,
{ name : "version" , type : "string" } ,
{ name : "chainId" , type : "uint256" } ,
{ name : "verifyingContract" , type : "address" }
] ;
2020-04-01 16:27:06 +00:00
const redeem = [
2020-04-02 11:28:41 +00:00
{ name : "blockNumber" , type : "uint256" } ,
{ name : "blockHash" , type : "bytes32" } ,
2020-02-18 19:23:31 +00:00
{ name : "receiver" , type : "address" } ,
{ name : "code" , type : "bytes32" } ,
] ;
2020-04-01 16:27:06 +00:00
const domainData = {
2020-02-18 19:23:31 +00:00
name : "KeycardGift" ,
version : "1" ,
chainId : chainId ,
verifyingContract : contractAddress
} ;
2020-04-01 16:27:06 +00:00
const data = {
2020-02-18 19:23:31 +00:00
types : {
EIP712Domain : domain ,
Redeem : redeem ,
} ,
primaryType : "Redeem" ,
domain : domainData ,
message : message
} ;
return new Promise ( ( resolve , reject ) => {
sendMethod ( {
jsonrpc : '2.0' ,
id : Date . now ( ) . toString ( ) . substring ( 9 ) ,
method : "eth_signTypedData" ,
params : [ signer , data ] ,
2020-02-19 13:35:11 +00:00
from : signer
2020-02-18 19:23:31 +00:00
} , ( error , res ) => {
if ( error ) {
return reject ( error ) ;
}
resolve ( res . result ) ;
} ) ;
} ) ;
}
function mineAt ( timestamp ) {
return new Promise ( ( resolve , reject ) => {
sendMethod ( {
jsonrpc : '2.0' ,
method : "evm_mine" ,
params : [ timestamp ] ,
id : Date . now ( ) . toString ( ) . substring ( 9 )
} , ( error , res ) => {
if ( error ) {
return reject ( error ) ;
}
resolve ( res . result ) ;
} ) ;
} ) ;
}
2020-02-21 15:09:42 +00:00
if ( assert . match === undefined ) {
assert . match = ( message , pattern ) => {
assert ( pattern . test ( message ) , ` ${ message } doesn't match ${ pattern } ` ) ;
}
}
2020-02-21 11:41:03 +00:00
contract ( "GiftBucket" , function ( ) {
2020-02-21 19:01:15 +00:00
let GiftBucket ;
2020-02-18 19:23:31 +00:00
sendMethod = ( web3 . currentProvider . sendAsync ) ? web3 . currentProvider . sendAsync . bind ( web3 . currentProvider ) : web3 . currentProvider . send . bind ( web3 . currentProvider ) ;
2020-03-25 08:41:59 +00:00
it ( "deploy factory" , async ( ) => {
// only to test gas
const deploy = GiftBucketFactory . deploy ( {
arguments : [ ]
} ) ;
const gas = await deploy . estimateGas ( ) ;
await deploy . send ( { gas } )
} ) ;
2020-02-21 19:01:15 +00:00
it ( "deploy bucket" , async ( ) => {
// only to test gas
2020-03-25 08:41:59 +00:00
const deploy = _GiftBucket . deploy ( {
arguments : [ TestToken . _address , EXPIRATION _TIME ]
} ) ;
const gas = await deploy . estimateGas ( ) ;
await deploy . send ( { gas } )
2020-02-21 19:01:15 +00:00
} ) ;
it ( "deploy bucket via factory" , async ( ) => {
const create = GiftBucketFactory . methods . create ( TestToken . _address , EXPIRATION _TIME ) ;
const gas = await create . estimateGas ( ) ;
const receipt = await create . send ( {
from : shop ,
gas : gas ,
} ) ;
2020-04-08 11:00:16 +00:00
const bucketAddress = receipt . events . BucketCreated . returnValues . bucket ;
2020-02-21 19:01:15 +00:00
const jsonInterface = _GiftBucket . options . jsonInterface ;
GiftBucket = new EmbarkJS . Blockchain . Contract ( {
abi : jsonInterface ,
address : bucketAddress ,
} ) ;
} ) ;
2020-02-19 11:28:05 +00:00
it ( "shop buys 100 tokens" , async function ( ) {
let supply = await TestToken . methods . totalSupply ( ) . call ( ) ;
assert . equal ( parseInt ( supply ) , 0 ) ;
2020-02-18 19:23:31 +00:00
2020-02-19 11:28:05 +00:00
await TestToken . methods . mint ( TOTAL _SUPPLY ) . send ( {
from : shop ,
} ) ;
2020-02-18 19:23:31 +00:00
2020-02-19 11:28:05 +00:00
supply = await TestToken . methods . totalSupply ( ) . call ( ) ;
assert . equal ( parseInt ( supply ) , TOTAL _SUPPLY ) ;
2020-02-18 19:23:31 +00:00
2020-02-19 11:28:05 +00:00
let shopBalance = await TestToken . methods . balanceOf ( shop ) . call ( ) ;
assert . equal ( parseInt ( shopBalance ) , TOTAL _SUPPLY ) ;
} ) ;
2020-02-18 19:23:31 +00:00
2020-02-21 15:09:42 +00:00
it ( "add supply" , async function ( ) {
let bucketBalance = await TestToken . methods . balanceOf ( GiftBucket . _address ) . call ( ) ;
assert . equal ( parseInt ( bucketBalance ) , 0 , ` bucket balance before is ${ bucketBalance } instead of 0 ` ) ;
2020-02-19 09:33:38 +00:00
2020-02-19 11:28:05 +00:00
let shopBalance = await TestToken . methods . balanceOf ( shop ) . call ( ) ;
assert . equal ( parseInt ( shopBalance ) , TOTAL _SUPPLY , ` shop balance before is ${ shopBalance } instead of ${ TOTAL _SUPPLY } ` ) ;
2020-02-19 09:33:38 +00:00
2020-02-21 15:09:42 +00:00
const transfer = TestToken . methods . transfer ( GiftBucket . _address , TOTAL _SUPPLY ) ;
const transferGas = await transfer . estimateGas ( ) ;
await transfer . send ( {
2020-02-19 11:28:05 +00:00
from : shop ,
2020-02-21 15:09:42 +00:00
gas : transferGas ,
2020-02-19 11:28:05 +00:00
} ) ;
2020-02-19 09:33:38 +00:00
2020-02-21 15:09:42 +00:00
bucketBalance = await TestToken . methods . balanceOf ( GiftBucket . _address ) . call ( ) ;
assert . equal ( parseInt ( bucketBalance ) , TOTAL _SUPPLY , ` bucket balance after is ${ bucketBalance } instead of ${ TOTAL _SUPPLY } ` ) ;
2020-02-19 09:33:38 +00:00
2020-02-19 11:28:05 +00:00
shopBalance = await TestToken . methods . balanceOf ( shop ) . call ( ) ;
assert . equal ( parseInt ( shopBalance ) , 0 , ` shop balance after is ${ shopBalance } instead of 0 ` ) ;
2020-02-19 09:33:38 +00:00
2020-02-21 11:41:03 +00:00
let totalSupply = await GiftBucket . methods . totalSupply ( ) . call ( ) ;
2020-02-19 11:28:05 +00:00
assert . equal ( parseInt ( totalSupply ) , TOTAL _SUPPLY , ` total contract supply is ${ totalSupply } instead of ${ TOTAL _SUPPLY } ` ) ;
2020-02-21 11:41:03 +00:00
let availableSupply = await GiftBucket . methods . availableSupply ( ) . call ( ) ;
2020-02-19 11:28:05 +00:00
assert . equal ( parseInt ( availableSupply ) , TOTAL _SUPPLY , ` available contract supply is ${ availableSupply } instead of ${ TOTAL _SUPPLY } ` ) ;
2020-02-18 19:23:31 +00:00
} ) ;
2020-02-19 11:28:05 +00:00
async function testCreateGift ( keycard , amount ) {
2020-02-21 15:09:42 +00:00
let initialSupply = await GiftBucket . methods . totalSupply ( ) . call ( ) ;
let initialAvailableSupply = await GiftBucket . methods . availableSupply ( ) . call ( ) ;
2020-02-19 11:28:05 +00:00
const redeemCodeHash = web3 . utils . sha3 ( REDEEM _CODE ) ;
2020-02-21 11:41:03 +00:00
const createGift = GiftBucket . methods . createGift ( keycard , amount , redeemCodeHash ) ;
2020-02-19 11:28:05 +00:00
const createGiftGas = await createGift . estimateGas ( ) ;
await createGift . send ( {
from : shop ,
gas : createGiftGas ,
} ) ;
2020-02-18 19:23:31 +00:00
2020-02-21 11:41:03 +00:00
let totalSupply = await GiftBucket . methods . totalSupply ( ) . call ( ) ;
2020-02-21 15:09:42 +00:00
assert . equal ( parseInt ( totalSupply ) , parseInt ( initialSupply ) , ` totalSupply is ${ totalSupply } instead of ${ initialSupply } ` ) ;
2020-02-18 19:23:31 +00:00
2020-02-21 11:41:03 +00:00
let availableSupply = await GiftBucket . methods . availableSupply ( ) . call ( ) ;
2020-02-21 15:09:42 +00:00
assert . equal ( parseInt ( availableSupply ) , parseInt ( initialAvailableSupply ) - amount ) ;
2020-02-19 11:28:05 +00:00
}
2020-02-18 19:23:31 +00:00
2020-02-21 15:09:42 +00:00
it ( "createGift should fail if amount is zero" , async function ( ) {
2020-02-19 11:28:05 +00:00
try {
await testCreateGift ( keycard _1 , 0 ) ;
assert . fail ( "createGift should have failed" ) ;
} catch ( e ) {
assert . match ( e . message , /invalid amount/ ) ;
}
} ) ;
2020-02-18 19:23:31 +00:00
2020-02-19 11:28:05 +00:00
it ( "createGift fails if amount > totalSupply" , async function ( ) {
try {
await testCreateGift ( keycard _1 , TOTAL _SUPPLY + 1 ) ;
assert . fail ( "createGift should have failed" ) ;
} catch ( e ) {
assert . match ( e . message , /low supply/ ) ;
}
} ) ;
2020-02-18 19:23:31 +00:00
2020-02-19 11:28:05 +00:00
it ( "createGift" , async function ( ) {
await testCreateGift ( keycard _1 , GIFT _AMOUNT ) ;
} ) ;
2020-02-18 19:23:31 +00:00
2020-02-19 11:28:05 +00:00
it ( "createGift should fail if keycard has already been used" , async function ( ) {
try {
await testCreateGift ( keycard _1 , 1 ) ;
assert . fail ( "createGift should have failed" ) ;
} catch ( e ) {
2020-02-21 19:01:15 +00:00
assert . match ( e . message , /recipient already used/ ) ;
2020-02-19 11:28:05 +00:00
}
} ) ;
2020-02-18 19:23:31 +00:00
2020-02-19 11:28:05 +00:00
it ( "createGift amount > availableSupply" , async function ( ) {
try {
await testCreateGift ( keycard _2 , TOTAL _SUPPLY - GIFT _AMOUNT + 1 ) ;
assert . fail ( "createGift should have failed" ) ;
} catch ( e ) {
assert . match ( e . message , /low supply/ ) ;
}
} ) ;
2020-02-18 19:23:31 +00:00
2020-04-02 11:28:41 +00:00
async function testRedeem ( receiver , recipient , signer , relayer , redeemCode , blockNumber , blockHash ) {
2020-02-21 15:09:42 +00:00
let initialBucketBalance = await TestToken . methods . balanceOf ( GiftBucket . _address ) . call ( ) ;
let initialUserBalance = await TestToken . methods . balanceOf ( user ) . call ( ) ;
let initialRedeemableSupply = await GiftBucket . methods . redeemableSupply ( ) . call ( ) ;
2020-02-18 19:23:31 +00:00
2020-02-21 19:01:15 +00:00
let gift = await GiftBucket . methods . gifts ( recipient ) . call ( ) ;
2020-02-21 15:09:42 +00:00
const amount = parseInt ( gift . amount ) ;
2020-02-18 19:23:31 +00:00
2020-02-19 11:28:05 +00:00
const message = {
2020-04-02 11:28:41 +00:00
blockNumber : blockNumber ,
blockHash : blockHash ,
2020-02-21 15:09:42 +00:00
receiver : receiver ,
2020-02-19 11:28:05 +00:00
code : redeemCode ,
} ;
2020-02-18 19:23:31 +00:00
2020-02-21 15:09:42 +00:00
const sig = await signRedeem ( GiftBucket . _address , signer , message ) ;
2020-02-21 11:41:03 +00:00
const redeem = GiftBucket . methods . redeem ( message , sig ) ;
2020-02-19 11:28:05 +00:00
const redeemGas = await redeem . estimateGas ( ) ;
await redeem . send ( {
2020-02-21 22:57:44 +00:00
from : relayer ,
2020-02-19 11:28:05 +00:00
gas : redeemGas ,
} ) ;
2020-02-21 15:09:42 +00:00
let expectedBucketBalance = parseInt ( initialBucketBalance ) - amount ;
let bucketBalance = await TestToken . methods . balanceOf ( GiftBucket . _address ) . call ( ) ;
assert . equal ( parseInt ( bucketBalance ) , expectedBucketBalance , ` bucketBalance after redeem should be ${ expectedBucketBalance } instead of ${ bucketBalance } ` ) ;
let expectedUserBalance = parseInt ( initialUserBalance + amount ) ;
2020-02-19 11:28:05 +00:00
userBalance = await TestToken . methods . balanceOf ( user ) . call ( ) ;
2020-02-21 15:09:42 +00:00
assert . equal ( parseInt ( userBalance ) , expectedUserBalance , ` user ` , ` userBalance after redeem should be ${ expectedUserBalance } instead of ${ userBalance } ` ) ;
let expectedRedeemableSupply = initialRedeemableSupply - amount ;
let redeemableSupply = await GiftBucket . methods . redeemableSupply ( ) . call ( ) ;
assert . equal ( parseInt ( redeemableSupply ) , expectedRedeemableSupply , ` redeemableSupply after redeem should be ${ expectedRedeemableSupply } instead of ${ redeemableSupply } ` ) ;
2020-02-19 11:28:05 +00:00
}
it ( "cannot redeem after expiration date" , async function ( ) {
2020-04-02 11:28:41 +00:00
const block = await web3 . eth . getBlock ( "latest" ) ;
2020-02-19 11:28:05 +00:00
await mineAt ( EXPIRATION _TIME ) ;
2020-04-02 11:28:41 +00:00
2020-02-19 11:28:05 +00:00
try {
2020-04-02 11:28:41 +00:00
await testRedeem ( user , keycard _1 , keycard _1 , relayer , REDEEM _CODE , block . number , block . hash ) ;
2020-02-19 11:28:05 +00:00
assert . fail ( "redeem should have failed" ) ;
} catch ( e ) {
assert . match ( e . message , /expired/ ) ;
}
} ) ;
it ( "cannot redeem with invalid code" , async function ( ) {
2020-04-02 11:28:41 +00:00
const block = await web3 . eth . getBlock ( "latest" ) ;
2020-02-19 11:28:05 +00:00
await mineAt ( NOW ) ;
try {
2020-04-02 11:28:41 +00:00
await testRedeem ( user , keycard _1 , keycard _1 , relayer , web3 . utils . sha3 ( "bad-code" ) , block . number , block . hash ) ;
2020-02-19 11:28:05 +00:00
assert . fail ( "redeem should have failed" ) ;
} catch ( e ) {
assert . match ( e . message , /invalid code/ ) ;
}
} ) ;
2020-03-25 08:23:22 +00:00
it ( "cannot redeem with invalid recipient" , async function ( ) {
2020-04-02 11:28:41 +00:00
const block = await web3 . eth . getBlock ( "latest" ) ;
2020-02-21 15:09:42 +00:00
await mineAt ( NOW ) ;
try {
2020-04-02 11:28:41 +00:00
await testRedeem ( user , keycard _1 , keycard _2 , relayer , REDEEM _CODE , block . number , block . hash ) ;
2020-02-21 15:09:42 +00:00
assert . fail ( "redeem should have failed" ) ;
} catch ( e ) {
2020-03-25 08:23:22 +00:00
assert . match ( e . message , /not found/ ) ;
2020-02-21 15:09:42 +00:00
}
} ) ;
2020-04-02 11:28:41 +00:00
it ( "cannot redeem with a block in the future" , async function ( ) {
const block = await web3 . eth . getBlock ( "latest" ) ;
await mineAt ( NOW ) ;
try {
await testRedeem ( user , keycard _1 , keycard _1 , relayer , REDEEM _CODE , ( block . number + 2 ) , "0x0000000000000000000000000000000000000000000000000000000000000000" ) ;
} catch ( e ) {
assert . match ( e . message , /future/ ) ;
}
} ) ;
it ( "cannot redeem with an old block" , async function ( ) {
const currentBlock = await web3 . eth . getBlock ( "latest" ) ;
const block = await web3 . eth . getBlock ( currentBlock . number - 10 ) ;
await mineAt ( NOW ) ;
try {
await testRedeem ( user , keycard _1 , keycard _1 , relayer , REDEEM _CODE , block . number , block . hash ) ;
} catch ( e ) {
assert . match ( e . message , /too old/ ) ;
}
} ) ;
it ( "cannot redeem with an invalid hash" , async function ( ) {
const block = await web3 . eth . getBlock ( "latest" ) ;
await mineAt ( NOW ) ;
try {
await testRedeem ( user , keycard _1 , keycard _1 , relayer , REDEEM _CODE , block . number , "0x0000000000000000000000000000000000000000000000000000000000000000" ) ;
} catch ( e ) {
assert . match ( e . message , /invalid block hash/ ) ;
}
} ) ;
2020-02-19 11:28:05 +00:00
it ( "can redeem before expiration date" , async function ( ) {
2020-04-02 11:28:41 +00:00
const block = await web3 . eth . getBlock ( "latest" ) ;
2020-02-19 11:28:05 +00:00
await mineAt ( NOW ) ;
2020-04-02 11:28:41 +00:00
await testRedeem ( user , keycard _1 , keycard _1 , relayer , REDEEM _CODE , block . number , block . hash ) ;
2020-02-19 11:28:05 +00:00
} ) ;
async function testKill ( ) {
2020-02-21 15:09:42 +00:00
let initialShopBalance = parseInt ( await TestToken . methods . balanceOf ( shop ) . call ( ) ) ;
let initialBucketBalance = parseInt ( await TestToken . methods . balanceOf ( GiftBucket . _address ) . call ( ) ) ;
2020-02-19 11:28:05 +00:00
2020-02-21 11:41:03 +00:00
await GiftBucket . methods . kill ( ) . send ( {
2020-02-19 11:28:05 +00:00
from : shop ,
} ) ;
2020-02-21 15:09:42 +00:00
let expectedShopBalance = initialShopBalance + initialBucketBalance ;
let shopBalance = await TestToken . methods . balanceOf ( shop ) . call ( ) ;
assert . equal ( parseInt ( shopBalance ) , expectedShopBalance , ` shop balance after kill is ${ shopBalance } instead of ${ expectedShopBalance } ` ) ;
2020-02-19 11:28:05 +00:00
2020-02-21 15:09:42 +00:00
let bucketBalance = await TestToken . methods . balanceOf ( GiftBucket . _address ) . call ( ) ;
assert . equal ( parseInt ( bucketBalance ) , 0 , ` bucketBalance after kill is ${ bucketBalance } instead of 0 ` ) ;
2020-02-19 11:28:05 +00:00
}
it ( "shop cannot kill contract before expirationTime" , async function ( ) {
await mineAt ( NOW ) ;
try {
await testKill ( ) ;
assert . fail ( "redeem should have failed" ) ;
} catch ( e ) {
assert . match ( e . message , /not expired yet/ ) ;
}
} ) ;
it ( "shop can kill contract after expirationTime" , async function ( ) {
await mineAt ( EXPIRATION _TIME ) ;
await testKill ( ) ;
} ) ;
2020-02-18 19:23:31 +00:00
} ) ;