diff --git a/app/js/actions/bucket.ts b/app/js/actions/bucket.ts index b91ab23..16a6b67 100644 --- a/app/js/actions/bucket.ts +++ b/app/js/actions/bucket.ts @@ -1,60 +1,60 @@ import { RootState } from '../reducers'; -import GiftBucket from '../../../embarkArtifacts/contracts/GiftBucket'; +import ERC20Bucket from '../../../embarkArtifacts/contracts/ERC20Bucket'; import IERC20Detailed from '../../../embarkArtifacts/contracts/IERC20Detailed'; import { config } from "../config"; import { Contract } from 'web3-eth-contract'; import { Dispatch } from 'redux'; -export const ERROR_GIFT_NOT_FOUND = "ERROR_GIFT_NOT_FOUND"; -export interface ErrGiftNotFound { - type: typeof ERROR_GIFT_NOT_FOUND +export const ERROR_REDEEMABLE_NOT_FOUND = "ERROR_REDEEMABLE_NOT_FOUND"; +export interface ErrRedeemableNotFound { + type: typeof ERROR_REDEEMABLE_NOT_FOUND } -export const ERROR_LOADING_GIFT = "ERROR_LOADING_GIFT"; -export interface ErrLoadingGift { - type: typeof ERROR_LOADING_GIFT +export const ERROR_LOADING_REDEEMABLE = "ERROR_LOADING_REDEEMABLE"; +export interface ErrLoadingRedeemable { + type: typeof ERROR_LOADING_REDEEMABLE message: string } export type BucketErrors = - ErrGiftNotFound | - ErrLoadingGift; + ErrRedeemableNotFound | + ErrLoadingRedeemable; -const errGiftNotFound = (): ErrGiftNotFound => ({ - type: ERROR_GIFT_NOT_FOUND, +const errRedeemableNotFound = (): ErrRedeemableNotFound => ({ + type: ERROR_REDEEMABLE_NOT_FOUND, }); -const errLoadingGift = (message: string): ErrLoadingGift => ({ - type: ERROR_LOADING_GIFT, +const errLoadingRedeemable = (message: string): ErrLoadingRedeemable => ({ + type: ERROR_LOADING_REDEEMABLE, message, }); -export const BUCKET_GIFT_LOADING = "BUCKET_GIFT_LOADING"; -export interface BucketGiftLoadingAction { - type: typeof BUCKET_GIFT_LOADING +export const BUCKET_REDEEMABLE_LOADING = "BUCKET_REDEEMABLE_LOADING"; +export interface BucketRedeemableLoadingAction { + type: typeof BUCKET_REDEEMABLE_LOADING address: string recipient: string } -export const BUCKET_GIFT_LOADING_ERROR = "BUCKET_GIFT_LOADING_ERROR"; -export interface BucketGiftLoadingErrorAction { - type: typeof BUCKET_GIFT_LOADING_ERROR - error: ErrLoadingGift +export const BUCKET_REDEEMABLE_LOADING_ERROR = "BUCKET_REDEEMABLE_LOADING_ERROR"; +export interface BucketRedeemableLoadingErrorAction { + type: typeof BUCKET_REDEEMABLE_LOADING_ERROR + error: ErrLoadingRedeemable } -export const BUCKET_GIFT_LOADED = "BUCKET_GIFT_LOADED"; -export interface BucketGiftLoadedAction { - type: typeof BUCKET_GIFT_LOADED +export const BUCKET_REDEEMABLE_LOADED = "BUCKET_REDEEMABLE_LOADED"; +export interface BucketRedeemableLoadedAction { + type: typeof BUCKET_REDEEMABLE_LOADED expirationTime: number recipient: string amount: string codeHash: string } -export const BUCKET_GIFT_NOT_FOUND = "BUCKET_GIFT_NOT_FOUND"; -export interface BucketGiftNotFoundAction { - type: typeof BUCKET_GIFT_NOT_FOUND - error: ErrGiftNotFound +export const BUCKET_REDEEMABLE_NOT_FOUND = "BUCKET_REDEEMABLE_NOT_FOUND"; +export interface BucketRedeemableNotFoundAction { + type: typeof BUCKET_REDEEMABLE_NOT_FOUND + error: ErrRedeemableNotFound } export const BUCKET_TOKEN_LOADING = "BUCKET_TOKEN_LOADING"; @@ -71,35 +71,35 @@ export interface BucketTokenLoadedAction { } export type BucketActions = - BucketGiftLoadingAction | - BucketGiftLoadingErrorAction | - BucketGiftLoadedAction | - BucketGiftNotFoundAction | + BucketRedeemableLoadingAction | + BucketRedeemableLoadingErrorAction | + BucketRedeemableLoadedAction | + BucketRedeemableNotFoundAction | BucketTokenLoadingAction | BucketTokenLoadedAction; -export const loadingGift = (address: string, recipient: string): BucketGiftLoadingAction => ({ - type: BUCKET_GIFT_LOADING, +export const loadingRedeemable = (address: string, recipient: string): BucketRedeemableLoadingAction => ({ + type: BUCKET_REDEEMABLE_LOADING, address, recipient, }); -export const giftLoaded = (expirationTime: number, recipient: string, amount: string, codeHash: string): BucketGiftLoadedAction => ({ - type: BUCKET_GIFT_LOADED, +export const redeemableLoaded = (expirationTime: number, recipient: string, amount: string, codeHash: string): BucketRedeemableLoadedAction => ({ + type: BUCKET_REDEEMABLE_LOADED, expirationTime, recipient, amount, codeHash, }); -export const giftNotFound = (): BucketGiftNotFoundAction => ({ - type: BUCKET_GIFT_NOT_FOUND, - error: errGiftNotFound(), +export const redeemableNotFound = (): BucketRedeemableNotFoundAction => ({ + type: BUCKET_REDEEMABLE_NOT_FOUND, + error: errRedeemableNotFound(), }); -export const errorLoadingGift = (errorMessage: string): BucketGiftLoadingErrorAction => ({ - type: BUCKET_GIFT_LOADING_ERROR, - error: errLoadingGift(errorMessage), +export const errorLoadingRedeemable = (errorMessage: string): BucketRedeemableLoadingErrorAction => ({ + type: BUCKET_REDEEMABLE_LOADING_ERROR, + error: errLoadingRedeemable(errorMessage), }); export const loadingToken = (address: string): BucketTokenLoadingAction => ({ @@ -114,7 +114,7 @@ export const tokenLoaded = (symbol: string, decimals: number): BucketTokenLoaded }); export const newBucketContract = (address: string) => { - const bucketAbi = GiftBucket.options.jsonInterface; + const bucketAbi = ERC20Bucket.options.jsonInterface; const bucket = new config.web3!.eth.Contract(bucketAbi, address); return bucket; } @@ -125,22 +125,22 @@ const newERC20Contract = (address: string) => { return erc20; } -export const loadGift = (bucketAddress: string, recipientAddress: string) => { +export const loadRedeemable = (bucketAddress: string, recipientAddress: string) => { return async (dispatch: Dispatch, getState: () => RootState) => { - dispatch(loadingGift(bucketAddress, recipientAddress)); + dispatch(loadingRedeemable(bucketAddress, recipientAddress)); const bucket = newBucketContract(bucketAddress); const expirationTime = await bucket.methods.expirationTime().call(); - bucket.methods.gifts(recipientAddress).call().then((result: any) => { + bucket.methods.redeemables(recipientAddress).call().then((result: any) => { const { recipient, amount, code } = result; if (amount === "0") { - dispatch(giftNotFound()) + dispatch(redeemableNotFound()) return; } - dispatch(giftLoaded(expirationTime, recipient, amount, code)); + dispatch(redeemableLoaded(expirationTime, recipient, amount, code)); dispatch(loadToken(bucket)) }).catch(err => { - dispatch(errorLoadingGift(err)) + dispatch(errorLoadingRedeemable(err)) console.error("err: ", err) }) }; diff --git a/app/js/actions/redeem.ts b/app/js/actions/redeem.ts index 72e094e..0feadc4 100644 --- a/app/js/actions/redeem.ts +++ b/app/js/actions/redeem.ts @@ -1,5 +1,5 @@ import { RootState } from '../reducers'; -import GiftBucket from '../../../embarkArtifacts/contracts/GiftBucket'; +import ERC20Bucket from '../../../embarkArtifacts/contracts/ERC20Bucket'; import IERC20Detailed from '../../../embarkArtifacts/contracts/IERC20Detailed'; import { config } from "../config"; import { Dispatch } from 'redux'; @@ -154,7 +154,7 @@ async function signRedeem(web3Type: Web3Type, contractAddress: string, signer: s ]; const domainData = { - name: "KeycardGift", + name: "KeycardERC20Bucket", version: "1", chainId: chainId, verifyingContract: contractAddress diff --git a/app/js/components/App.tsx b/app/js/components/App.tsx index c3b595f..6b499c9 100644 --- a/app/js/components/App.tsx +++ b/app/js/components/App.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import GiftBucketFactory from '../../../embarkArtifacts/contracts/GiftBucketFactory'; +import ERC20BucketFactory from '../../../embarkArtifacts/contracts/ERC20BucketFactory'; import { shallowEqual, useSelector, @@ -40,7 +40,7 @@ export default function(ownProps: any) { return <> Network ID: {props.networkID}
- Factory: {GiftBucketFactory.address}
+ Factory: {ERC20BucketFactory.address}
Web3 Type: {web3Type(props.type)}
diff --git a/app/js/components/Redeem.tsx b/app/js/components/Redeem.tsx index 4e2f946..269a324 100644 --- a/app/js/components/Redeem.tsx +++ b/app/js/components/Redeem.tsx @@ -8,10 +8,10 @@ import { } from 'react-redux'; import { redeemPath } from '../config'; import { - loadGift, + loadRedeemable, BucketErrors, - ERROR_LOADING_GIFT, - ERROR_GIFT_NOT_FOUND, + ERROR_LOADING_REDEEMABLE, + ERROR_REDEEMABLE_NOT_FOUND, } from '../actions/bucket'; import { toBaseUnit } from "../utils"; import { @@ -25,11 +25,11 @@ const REDEEM_CODE = "hello world"; const buckerErrorMessage = (error: BucketErrors): string => { switch (error.type) { - case ERROR_LOADING_GIFT: - return "couldn't load gift"; + case ERROR_LOADING_REDEEMABLE: + return "couldn't load redeemable"; - case ERROR_GIFT_NOT_FOUND: - return "gift not found or already redeemed"; + case ERROR_REDEEMABLE_NOT_FOUND: + return "redeemable not found or already redeemed"; default: return "something went wrong"; @@ -79,7 +79,7 @@ export default function(ownProps: any) { }, shallowEqual); useEffect(() => { - dispatch(loadGift(bucketAddress, recipientAddress)); + dispatch(loadRedeemable(bucketAddress, recipientAddress)); }, [bucketAddress, recipientAddress]); if (props.error) { diff --git a/app/js/reducers/bucket.ts b/app/js/reducers/bucket.ts index 05c3138..cd5a214 100644 --- a/app/js/reducers/bucket.ts +++ b/app/js/reducers/bucket.ts @@ -1,9 +1,9 @@ import { BucketActions, BucketErrors, - BUCKET_GIFT_LOADING, - BUCKET_GIFT_NOT_FOUND, - BUCKET_GIFT_LOADED, + BUCKET_REDEEMABLE_LOADING, + BUCKET_REDEEMABLE_NOT_FOUND, + BUCKET_REDEEMABLE_LOADED, BUCKET_TOKEN_LOADING, BUCKET_TOKEN_LOADED, } from "../actions/bucket"; @@ -36,7 +36,7 @@ const initialState: BucketState = { export const bucketReducer = (state: BucketState = initialState, action: BucketActions): BucketState => { switch (action.type) { - case BUCKET_GIFT_LOADING: { + case BUCKET_REDEEMABLE_LOADING: { return { ...initialState, loading: true, @@ -45,7 +45,7 @@ export const bucketReducer = (state: BucketState = initialState, action: BucketA } } - case BUCKET_GIFT_NOT_FOUND: { + case BUCKET_REDEEMABLE_NOT_FOUND: { return { ...state, loading: false, @@ -53,7 +53,7 @@ export const bucketReducer = (state: BucketState = initialState, action: BucketA } } - case BUCKET_GIFT_LOADED: { + case BUCKET_REDEEMABLE_LOADED: { return { ...state, loading: false, diff --git a/app/js/reducers/redeem.ts b/app/js/reducers/redeem.ts index 8106e38..8c12986 100644 --- a/app/js/reducers/redeem.ts +++ b/app/js/reducers/redeem.ts @@ -6,8 +6,8 @@ import { REDEEM_DONE, } from "../actions/redeem"; import { - BucketGiftLoadingAction, - BUCKET_GIFT_LOADING + BucketRedeemableLoadingAction, + BUCKET_REDEEMABLE_LOADING } from "../actions/bucket"; export interface RedeemState { @@ -24,9 +24,9 @@ const initialState: RedeemState = { receiver: undefined, } -export const redeemReducer = (state: RedeemState = initialState, action: RedeemActions | BucketGiftLoadingAction): RedeemState => { +export const redeemReducer = (state: RedeemState = initialState, action: RedeemActions | BucketRedeemableLoadingAction): RedeemState => { switch (action.type) { - case BUCKET_GIFT_LOADING: { + case BUCKET_REDEEMABLE_LOADING: { return initialState; } diff --git a/config/contracts.js b/config/contracts.js index b036281..67be30b 100644 --- a/config/contracts.js +++ b/config/contracts.js @@ -33,7 +33,7 @@ module.exports = { TestToken: { args: ["TEST", 18], }, - GiftBucketFactory: { + ERC20BucketFactory: { params: [], }, } diff --git a/contracts/Bucket.sol b/contracts/Bucket.sol index affbf67..e4124f3 100644 --- a/contracts/Bucket.sol +++ b/contracts/Bucket.sol @@ -13,7 +13,7 @@ abstract contract Bucket { bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); bytes32 DOMAIN_SEPARATOR; - struct Gift { + struct Redeemable { address recipient; bytes32 code; uint256 data; @@ -26,7 +26,7 @@ abstract contract Bucket { bytes32 code; } - mapping(address => Gift) public gifts; + mapping(address => Redeemable) public redeemables; modifier onlyOwner() { require(msg.sender == owner, "owner required"); @@ -67,16 +67,16 @@ abstract contract Bucket { address recipient = recoverSigner(DOMAIN_SEPARATOR, _redeem, _sig); - Gift storage gift = gifts[recipient]; - require(gift.recipient == recipient, "not found"); + Redeemable storage redeemable = redeemables[recipient]; + require(redeemable.recipient == recipient, "not found"); - validateCode(_redeem, gift.code); + validateCode(_redeem, redeemable.code); - uint256 data = gift.data; + uint256 data = redeemable.data; - gift.recipient = address(0); - gift.code = 0; - gift.data = 0; + redeemable.recipient = address(0); + redeemable.code = 0; + redeemable.data = 0; transferRedeemable(data, _redeem); } @@ -109,7 +109,7 @@ abstract contract Bucket { require(_redeem.blockNumber >= (block.number - _maxTxDelayInBlocks), "transaction too old"); require(_redeem.blockHash == blockhash(_redeem.blockNumber), "invalid block hash"); - require(block.timestamp < _expirationTime, "expired gift"); + require(block.timestamp < _expirationTime, "expired redeemable"); require(block.timestamp > _startTime, "reedeming not yet started"); } diff --git a/contracts/GiftBucket.sol b/contracts/ERC20Bucket.sol similarity index 68% rename from contracts/GiftBucket.sol rename to contracts/ERC20Bucket.sol index aeda192..160765c 100644 --- a/contracts/GiftBucket.sol +++ b/contracts/ERC20Bucket.sol @@ -4,13 +4,13 @@ pragma experimental ABIEncoderV2; import "./Bucket.sol"; import "./erc20/IERC20.sol"; -contract GiftBucket is Bucket { +contract ERC20Bucket is Bucket { uint256 public redeemableSupply; constructor( address _tokenAddress, uint256 _startTime, - uint256 _expirationTime) Bucket("KeycardGift", _tokenAddress, _startTime, _expirationTime) public {} + uint256 _expirationTime) Bucket("KeycardERC20Bucket", _tokenAddress, _startTime, _expirationTime) public {} function totalSupply() public view returns(uint256) { return IERC20(tokenAddress).balanceOf(address(this)); @@ -23,30 +23,30 @@ contract GiftBucket is Bucket { return _totalSupply - redeemableSupply; } - function createGift(address recipient, uint256 amount, bytes32 code) external onlyOwner { + function createRedeemable(address recipient, uint256 amount, bytes32 code) external onlyOwner { require(amount > 0, "invalid amount"); uint256 _availableSupply = this.availableSupply(); require(_availableSupply >= amount, "low supply"); - Gift storage gift = gifts[recipient]; - require(gift.recipient == address(0), "recipient already used"); + Redeemable storage redeemable = redeemables[recipient]; + require(redeemable.recipient == address(0), "recipient already used"); - gift.recipient = recipient; - gift.code = code; - gift.data = amount; + redeemable.recipient = recipient; + redeemable.code = code; + redeemable.data = amount; require(redeemableSupply + amount > redeemableSupply, "addition overflow"); redeemableSupply += amount; } - function transferRedeemable(uint256 data, Redeem memory redeem) override internal { + function transferRedeemable(uint256 data, Redeem memory redeem) internal override { require(redeemableSupply >= data, "not enough redeemable supply"); redeemableSupply -= data; IERC20(tokenAddress).transfer(redeem.receiver, data); } - function transferRedeemablesToOwner() override internal { + function transferRedeemablesToOwner() internal override { bool success = IERC20(tokenAddress).transfer(owner, this.totalSupply()); assert(success); } diff --git a/contracts/ERC20BucketFactory.sol b/contracts/ERC20BucketFactory.sol new file mode 100644 index 0000000..12fc5d5 --- /dev/null +++ b/contracts/ERC20BucketFactory.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.6.1; + +import "./ERC20Bucket.sol"; +import "./Proxy.sol"; + +contract ERC20BucketFactory { + ERC20Bucket public ERC20BucketImplementation; + + event BucketCreated(address indexed provider, address indexed bucket); + + constructor() public { + ERC20BucketImplementation = new ERC20Bucket(address(0), 0, block.timestamp + 1); + } + + function create(address _tokenAddress, uint256 _startTime, uint256 _expirationTime) public returns (address) { + address p = address(new Proxy(abi.encodeWithSelector(0x4e9464ed, "KeycardERC20Bucket", _tokenAddress, _startTime, _expirationTime, msg.sender), address(ERC20BucketImplementation))); + emit BucketCreated(msg.sender, p); + return p; + } +} diff --git a/contracts/GiftBucketFactory.sol b/contracts/GiftBucketFactory.sol deleted file mode 100644 index 77513ee..0000000 --- a/contracts/GiftBucketFactory.sol +++ /dev/null @@ -1,20 +0,0 @@ -pragma solidity ^0.6.1; - -import "./GiftBucket.sol"; -import "./Proxy.sol"; - -contract GiftBucketFactory { - GiftBucket public GiftBucketImplementation; - - event BucketCreated(address indexed gifter, address indexed bucket); - - constructor() public { - GiftBucketImplementation = new GiftBucket(address(0), 0, block.timestamp + 1); - } - - function create(address _tokenAddress, uint256 _startTime, uint256 _expirationTime) public returns (address) { - address p = address(new Proxy(abi.encodeWithSelector(0x4e9464ed, "KeycardGift", _tokenAddress, _startTime, _expirationTime, msg.sender), address(GiftBucketImplementation))); - emit BucketCreated(msg.sender, p); - return p; - } -} diff --git a/contracts/NFTBucket.sol b/contracts/NFTBucket.sol index 8c61c1d..67c1556 100644 --- a/contracts/NFTBucket.sol +++ b/contracts/NFTBucket.sol @@ -12,13 +12,13 @@ contract NFTBucket is Bucket, IERC165, IERC721Receiver { constructor( address _tokenAddress, uint256 _startTime, - uint256 _expirationTime) Bucket("KeycardNFTGift", _tokenAddress, _startTime, _expirationTime) public {} + uint256 _expirationTime) Bucket("KeycardNFTBucket", _tokenAddress, _startTime, _expirationTime) public {} - function transferRedeemable(uint256 data, Redeem memory redeem) override internal { + function transferRedeemable(uint256 data, Redeem memory redeem) internal override { IERC721(tokenAddress).safeTransferFrom(address(this), redeem.receiver, data); } - function transferRedeemablesToOwner() override internal { + function transferRedeemablesToOwner() internal override { IERC721(tokenAddress).setApprovalForAll(owner, true); assert(IERC721(tokenAddress).isApprovedForAll(address(this), owner)); } @@ -29,7 +29,7 @@ contract NFTBucket is Bucket, IERC165, IERC721Receiver { function onERC721Received(address _operator, address _from, uint256 _tokenID, bytes calldata _data) external override(IERC721Receiver) returns(bytes4) { require(msg.sender == tokenAddress, "only the NFT contract can call this"); - require((_operator == owner) || (_from == owner), "only the owner can create gifts"); + require((_operator == owner) || (_from == owner), "only the owner can create redeemables"); require(_data.length == 52, "invalid data field"); bytes memory d = _data; @@ -44,12 +44,12 @@ contract NFTBucket is Bucket, IERC165, IERC721Receiver { address recipient = address(uint160(uint256(tmp))); - Gift storage gift = gifts[recipient]; - require(gift.recipient == address(0), "recipient already used"); + Redeemable storage redeemable = redeemables[recipient]; + require(redeemable.recipient == address(0), "recipient already used"); - gift.recipient = recipient; - gift.code = code; - gift.data = _tokenID; + redeemable.recipient = recipient; + redeemable.code = code; + redeemable.data = _tokenID; return _ERC721_RECEIVED; } diff --git a/contracts/NFTBucketFactory.sol b/contracts/NFTBucketFactory.sol index 7d979f4..2e141df 100644 --- a/contracts/NFTBucketFactory.sol +++ b/contracts/NFTBucketFactory.sol @@ -6,14 +6,14 @@ import "./Proxy.sol"; contract NFTBucketFactory { NFTBucket public NFTBucketImplementation; - event BucketCreated(address indexed gifter, address indexed bucket); + event BucketCreated(address indexed provider, address indexed bucket); constructor() public { NFTBucketImplementation = new NFTBucket(address(0), 0, block.timestamp + 1); } function create(address _tokenAddress, uint256 _startTime, uint256 _expirationTime) public returns (address) { - address p = address(new Proxy(abi.encodeWithSelector(0x4e9464ed, "KeycardNFTGift", _tokenAddress, _startTime, _expirationTime, msg.sender), address(NFTBucketImplementation))); + address p = address(new Proxy(abi.encodeWithSelector(0x4e9464ed, "KeycardNFTBucket", _tokenAddress, _startTime, _expirationTime, msg.sender), address(NFTBucketImplementation))); emit BucketCreated(msg.sender, p); return p; } diff --git a/scripts/create-gift.js b/scripts/create-redeemable.js similarity index 94% rename from scripts/create-gift.js rename to scripts/create-redeemable.js index 025121c..5fb6c65 100644 --- a/scripts/create-gift.js +++ b/scripts/create-redeemable.js @@ -8,7 +8,7 @@ const argv = parseArgs(process.argv.slice(2), {boolean: ["nft", "deploy-factory" const web3 = new Web3(argv["endpoint"]); -const classPrefix = argv["nft"] ? "NFT" : "Gift"; +const classPrefix = argv["nft"] ? "NFT" : "ERC20"; const BucketConfig = loadEmbarkArtifact(`./embarkArtifacts/contracts/${classPrefix}Bucket.js`); const BucketFactoryConfig = loadEmbarkArtifact(`./embarkArtifacts/contracts/${classPrefix}BucketFactory.js`); @@ -76,9 +76,9 @@ async function deployBucket(sender, factory, token, startInDays, validityInDays) } } -async function createGift(sender, bucket, keycard) { +async function createRedeemable(sender, bucket, keycard) { Bucket.options.address = bucket; - let methodCall = Bucket.methods.createGift(keycard.keycard, keycard.amount, keycard.code); + let methodCall = Bucket.methods.createRedeemable(keycard.keycard, keycard.amount, keycard.code); try { let receipt = await sendMethod(methodCall, sender, Bucket.options.address); @@ -185,7 +185,7 @@ async function run() { let file = fs.readFileSync(argv["file"], 'utf8'); keycards = file.split("\n").map(processLine); for (let keycard of keycards) { - await argv["nft"] ? createGift(sender, bucket, keycard) : transferNFT(sender, argv["token"], bucket, keycard); + await argv["nft"] ? createRedeemable(sender, bucket, keycard) : transferNFT(sender, argv["token"], bucket, keycard); } } else if (!hasDoneSomething) { console.error("the --file option must be specified"); diff --git a/test/contract_spec.js b/test/contract_spec.js index 8aeb1af..416f196 100644 --- a/test/contract_spec.js +++ b/test/contract_spec.js @@ -1,7 +1,7 @@ const EmbarkJS = artifacts.require('EmbarkJS'); const TestToken = artifacts.require('TestToken'); -const _GiftBucket = artifacts.require('GiftBucket'); -const GiftBucketFactory = artifacts.require('GiftBucketFactory'); +const _ERC20Bucket = artifacts.require('ERC20Bucket'); +const ERC20BucketFactory = artifacts.require('ERC20BucketFactory'); const TOTAL_SUPPLY = 10000; const GIFT_AMOUNT = 10; @@ -22,10 +22,10 @@ config({ "TestToken": { args: ["TEST", 18], }, - "GiftBucket": { + "ERC20Bucket": { args: ["$TestToken", START_TIME, EXPIRATION_TIME], }, - "GiftBucketFactory": { + "ERC20BucketFactory": { args: [], }, } @@ -61,7 +61,7 @@ async function signRedeem(contractAddress, signer, message) { ]; const domainData = { - name: "KeycardGift", + name: "KeycardERC20Bucket", version: "1", chainId: chainId, verifyingContract: contractAddress @@ -115,14 +115,14 @@ if (assert.match === undefined) { } } -contract("GiftBucket", function () { - let GiftBucket; +contract("ERC20Bucket", function () { + let ERC20Bucket; sendMethod = (web3.currentProvider.sendAsync) ? web3.currentProvider.sendAsync.bind(web3.currentProvider) : web3.currentProvider.send.bind(web3.currentProvider); it("deploy factory", async () => { // only to test gas - const deploy = GiftBucketFactory.deploy({ + const deploy = ERC20BucketFactory.deploy({ arguments: [] }); @@ -132,7 +132,7 @@ contract("GiftBucket", function () { it("deploy bucket", async () => { // only to test gas - const deploy = _GiftBucket.deploy({ + const deploy = _ERC20Bucket.deploy({ arguments: [TestToken._address, START_TIME, EXPIRATION_TIME] }); @@ -141,7 +141,7 @@ contract("GiftBucket", function () { }); it("deploy bucket via factory", async () => { - const create = GiftBucketFactory.methods.create(TestToken._address, START_TIME, EXPIRATION_TIME); + const create = ERC20BucketFactory.methods.create(TestToken._address, START_TIME, EXPIRATION_TIME); const gas = await create.estimateGas(); const receipt = await create.send({ from: shop, @@ -149,8 +149,8 @@ contract("GiftBucket", function () { }); const bucketAddress = receipt.events.BucketCreated.returnValues.bucket; - const jsonInterface = _GiftBucket.options.jsonInterface; - GiftBucket = new EmbarkJS.Blockchain.Contract({ + const jsonInterface = _ERC20Bucket.options.jsonInterface; + ERC20Bucket = new EmbarkJS.Blockchain.Contract({ abi: jsonInterface, address: bucketAddress, }); @@ -172,99 +172,99 @@ contract("GiftBucket", function () { }); it("add supply", async function() { - let bucketBalance = await TestToken.methods.balanceOf(GiftBucket._address).call(); + let bucketBalance = await TestToken.methods.balanceOf(ERC20Bucket._address).call(); assert.equal(parseInt(bucketBalance), 0, `bucket balance before is ${bucketBalance} instead of 0`); let shopBalance = await TestToken.methods.balanceOf(shop).call(); assert.equal(parseInt(shopBalance), TOTAL_SUPPLY, `shop balance before is ${shopBalance} instead of ${TOTAL_SUPPLY}`); - const transfer = TestToken.methods.transfer(GiftBucket._address, TOTAL_SUPPLY); + const transfer = TestToken.methods.transfer(ERC20Bucket._address, TOTAL_SUPPLY); const transferGas = await transfer.estimateGas(); await transfer.send({ from: shop, gas: transferGas, }); - bucketBalance = await TestToken.methods.balanceOf(GiftBucket._address).call(); + bucketBalance = await TestToken.methods.balanceOf(ERC20Bucket._address).call(); assert.equal(parseInt(bucketBalance), TOTAL_SUPPLY, `bucket balance after is ${bucketBalance} instead of ${TOTAL_SUPPLY}`); shopBalance = await TestToken.methods.balanceOf(shop).call(); assert.equal(parseInt(shopBalance), 0, `shop balance after is ${shopBalance} instead of 0`); - let totalSupply = await GiftBucket.methods.totalSupply().call(); + let totalSupply = await ERC20Bucket.methods.totalSupply().call(); assert.equal(parseInt(totalSupply), TOTAL_SUPPLY, `total contract supply is ${totalSupply} instead of ${TOTAL_SUPPLY}`); - let availableSupply = await GiftBucket.methods.availableSupply().call(); + let availableSupply = await ERC20Bucket.methods.availableSupply().call(); assert.equal(parseInt(availableSupply), TOTAL_SUPPLY, `available contract supply is ${availableSupply} instead of ${TOTAL_SUPPLY}`); }); - async function testCreateGift(keycard, amount) { - let initialSupply = await GiftBucket.methods.totalSupply().call(); - let initialAvailableSupply = await GiftBucket.methods.availableSupply().call(); + async function testCreateRedeemable(keycard, amount) { + let initialSupply = await ERC20Bucket.methods.totalSupply().call(); + let initialAvailableSupply = await ERC20Bucket.methods.availableSupply().call(); const redeemCodeHash = web3.utils.sha3(REDEEM_CODE); - const createGift = GiftBucket.methods.createGift(keycard, amount, redeemCodeHash); - const createGiftGas = await createGift.estimateGas(); - await createGift.send({ + const createRedeemable = ERC20Bucket.methods.createRedeemable(keycard, amount, redeemCodeHash); + const createRedeemableGas = await createRedeemable.estimateGas(); + await createRedeemable.send({ from: shop, - gas: createGiftGas, + gas: createRedeemableGas, }); - let totalSupply = await GiftBucket.methods.totalSupply().call(); + let totalSupply = await ERC20Bucket.methods.totalSupply().call(); assert.equal(parseInt(totalSupply), parseInt(initialSupply), `totalSupply is ${totalSupply} instead of ${initialSupply}`); - let availableSupply = await GiftBucket.methods.availableSupply().call(); + let availableSupply = await ERC20Bucket.methods.availableSupply().call(); assert.equal(parseInt(availableSupply), parseInt(initialAvailableSupply) - amount); } - it("createGift should fail if amount is zero", async function() { + it("createRedeemable should fail if amount is zero", async function() { try { - await testCreateGift(keycard_1, 0); - assert.fail("createGift should have failed"); + await testCreateRedeemable(keycard_1, 0); + assert.fail("createRedeemable should have failed"); } catch(e) { assert.match(e.message, /invalid amount/); } }); - it("createGift fails if amount > totalSupply", async function() { + it("createRedeemable fails if amount > totalSupply", async function() { try { - await testCreateGift(keycard_1, TOTAL_SUPPLY + 1); - assert.fail("createGift should have failed"); + await testCreateRedeemable(keycard_1, TOTAL_SUPPLY + 1); + assert.fail("createRedeemable should have failed"); } catch(e) { assert.match(e.message, /low supply/); } }); - it("createGift", async function() { - await testCreateGift(keycard_1, GIFT_AMOUNT); + it("createRedeemable", async function() { + await testCreateRedeemable(keycard_1, GIFT_AMOUNT); }); - it("createGift should fail if keycard has already been used", async function() { + it("createRedeemable should fail if keycard has already been used", async function() { try { - await testCreateGift(keycard_1, 1); - assert.fail("createGift should have failed"); + await testCreateRedeemable(keycard_1, 1); + assert.fail("createRedeemable should have failed"); } catch(e) { assert.match(e.message, /recipient already used/); } }); - it("createGift amount > availableSupply", async function() { + it("createRedeemable amount > availableSupply", async function() { try { - await testCreateGift(keycard_2, TOTAL_SUPPLY - GIFT_AMOUNT + 1); - assert.fail("createGift should have failed"); + await testCreateRedeemable(keycard_2, TOTAL_SUPPLY - GIFT_AMOUNT + 1); + assert.fail("createRedeemable should have failed"); } catch(e) { assert.match(e.message, /low supply/); } }); async function testRedeem(receiver, recipient, signer, relayer, redeemCode, blockNumber, blockHash) { - let initialBucketBalance = await TestToken.methods.balanceOf(GiftBucket._address).call(); + let initialBucketBalance = await TestToken.methods.balanceOf(ERC20Bucket._address).call(); let initialUserBalance = await TestToken.methods.balanceOf(user).call(); - let initialRedeemableSupply = await GiftBucket.methods.redeemableSupply().call(); + let initialRedeemableSupply = await ERC20Bucket.methods.redeemableSupply().call(); - let gift = await GiftBucket.methods.gifts(recipient).call(); - const amount = parseInt(gift.data); + let redeemable = await ERC20Bucket.methods.redeemables(recipient).call(); + const amount = parseInt(redeemable.data); const message = { blockNumber: blockNumber, @@ -273,8 +273,8 @@ contract("GiftBucket", function () { code: redeemCode, }; - const sig = await signRedeem(GiftBucket._address, signer, message); - const redeem = GiftBucket.methods.redeem(message, sig); + const sig = await signRedeem(ERC20Bucket._address, signer, message); + const redeem = ERC20Bucket.methods.redeem(message, sig); const redeemGas = await redeem.estimateGas(); await redeem.send({ from: relayer, @@ -283,7 +283,7 @@ contract("GiftBucket", function () { let expectedBucketBalance = parseInt(initialBucketBalance) - amount; - let bucketBalance = await TestToken.methods.balanceOf(GiftBucket._address).call(); + let bucketBalance = await TestToken.methods.balanceOf(ERC20Bucket._address).call(); assert.equal(parseInt(bucketBalance), expectedBucketBalance, `bucketBalance after redeem should be ${expectedBucketBalance} instead of ${bucketBalance}`); let expectedUserBalance = parseInt(initialUserBalance + amount); @@ -291,7 +291,7 @@ contract("GiftBucket", function () { 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(); + let redeemableSupply = await ERC20Bucket.methods.redeemableSupply().call(); assert.equal(parseInt(redeemableSupply), expectedRedeemableSupply, `redeemableSupply after redeem should be ${expectedRedeemableSupply} instead of ${redeemableSupply}`); } @@ -382,9 +382,9 @@ contract("GiftBucket", function () { async function testKill() { let initialShopBalance = parseInt(await TestToken.methods.balanceOf(shop).call()); - let initialBucketBalance = parseInt(await TestToken.methods.balanceOf(GiftBucket._address).call()); + let initialBucketBalance = parseInt(await TestToken.methods.balanceOf(ERC20Bucket._address).call()); - await GiftBucket.methods.kill().send({ + await ERC20Bucket.methods.kill().send({ from: shop, }); @@ -392,7 +392,7 @@ contract("GiftBucket", function () { let shopBalance = await TestToken.methods.balanceOf(shop).call(); assert.equal(parseInt(shopBalance), expectedShopBalance, `shop balance after kill is ${shopBalance} instead of ${expectedShopBalance}`); - let bucketBalance = await TestToken.methods.balanceOf(GiftBucket._address).call(); + let bucketBalance = await TestToken.methods.balanceOf(ERC20Bucket._address).call(); assert.equal(parseInt(bucketBalance), 0, `bucketBalance after kill is ${bucketBalance} instead of 0`); } diff --git a/test/nft_contract_spec.js b/test/nft_contract_spec.js index ffd92c9..e1502f8 100644 --- a/test/nft_contract_spec.js +++ b/test/nft_contract_spec.js @@ -62,7 +62,7 @@ async function signRedeem(contractAddress, signer, message) { ]; const domainData = { - name: "KeycardNFTGift", + name: "KeycardNFTBucket", version: "1", chainId: chainId, verifyingContract: contractAddress @@ -157,39 +157,39 @@ contract("NFTBucket", function () { }); }); - function createGiftData(recipient) { + function createRedeemableData(recipient) { const redeemCodeHash = web3.utils.sha3(REDEEM_CODE); return recipient + redeemCodeHash.replace("0x", ""); } - async function checkGift(recipient, tokenID) { - let gift = await NFTBucket.methods.gifts(recipient).call(); - assert.equal(gift.recipient, recipient, "gift not found"); - assert.equal(parseInt(gift.data), tokenID, "token ID does not match"); + async function checkRedeemable(recipient, tokenID) { + let redeemable = await NFTBucket.methods.redeemables(recipient).call(); + assert.equal(redeemable.recipient, recipient, "redeemable not found"); + assert.equal(parseInt(redeemable.data), tokenID, "token ID does not match"); let tokenOwner = await TestNFT.methods.ownerOf(tokenID).call(); assert.equal(tokenOwner, NFTBucket._address, "token owner is wrong"); } - it("mint directly to gift", async function () { - await TestNFT.methods.mint(NFTBucket._address, 42, createGiftData(keycard_1)).send({ + it("mint directly to redeemable", async function () { + await TestNFT.methods.mint(NFTBucket._address, 42, createRedeemableData(keycard_1)).send({ from: shop, }); - await checkGift(keycard_1, 42); + await checkRedeemable(keycard_1, 42); }); it("transfer token from shop", async function() { await TestNFT.methods.mint(shop, 0xcafe).send({from: shop,}); - await TestNFT.methods.safeTransferFrom(shop, NFTBucket._address, 0xcafe, createGiftData(keycard_2)).send({from: shop}); + await TestNFT.methods.safeTransferFrom(shop, NFTBucket._address, 0xcafe, createRedeemableData(keycard_2)).send({from: shop}); - await checkGift(keycard_2, 0xcafe); + await checkRedeemable(keycard_2, 0xcafe); }); - it("cannot create two gifts for the same recipient", async function() { + it("cannot create two redeemables for the same recipient", async function() { await TestNFT.methods.mint(shop, 43).send({from: shop}); try { - await TestNFT.methods.safeTransferFrom(shop, NFTBucket._address, 43, createGiftData(keycard_2)).send({from: shop}); + await TestNFT.methods.safeTransferFrom(shop, NFTBucket._address, 43, createRedeemableData(keycard_2)).send({from: shop}); assert.fail("transfer should have failed"); } catch(e) { assert.match(e.message, /already used/); @@ -197,9 +197,9 @@ contract("NFTBucket", function () { }); - it("cannot create two gifts for the same token", async function() { + it("cannot create two redeemables for the same token", async function() { try { - await NFTBucket.methods.onERC721Received(shop, shop, 0xcafe, createGiftData(keycard_3)).send({from: shop}); + await NFTBucket.methods.onERC721Received(shop, shop, 0xcafe, createRedeemableData(keycard_3)).send({from: shop}); assert.fail("transfer should have failed"); } catch(e) { assert.match(e.message, /only the NFT/); @@ -208,8 +208,8 @@ contract("NFTBucket", function () { }); async function testRedeem(receiver, recipient, signer, relayer, redeemCode, blockNumber, blockHash) { - let gift = await NFTBucket.methods.gifts(recipient).call(); - const tokenID = gift.data; + let redeemable = await NFTBucket.methods.redeemables(recipient).call(); + const tokenID = redeemable.data; const message = { blockNumber: blockNumber,