Merge pull request #22 from status-im/gift-to-erc20-redeemable

remove gift terminology
This commit is contained in:
Bitgamma 2020-04-28 13:16:54 +03:00 committed by GitHub
commit a7e86d8652
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 194 additions and 194 deletions

View File

@ -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<any>(loadToken(bucket))
}).catch(err => {
dispatch(errorLoadingGift(err))
dispatch(errorLoadingRedeemable(err))
console.error("err: ", err)
})
};

View File

@ -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

View File

@ -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} <br />
Factory: {GiftBucketFactory.address} <br />
Factory: {ERC20BucketFactory.address} <br />
Web3 Type: {web3Type(props.type)}
<hr />
<div>

View File

@ -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) {

View File

@ -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,

View File

@ -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;
}

View File

@ -33,7 +33,7 @@ module.exports = {
TestToken: {
args: ["TEST", 18],
},
GiftBucketFactory: {
ERC20BucketFactory: {
params: [],
},
}

View File

@ -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");
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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");

View File

@ -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`);
}

View File

@ -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,