ethers#Bytes TypeScript Examples

The following examples show how to use ethers#Bytes. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts    From zora-v1-subgraph with MIT License 6 votes vote down vote up
export async function randomHashBytes(): Promise<Bytes> {
  return ethers.utils.randomBytes(32)
}
Example #2
Source File: Media.test.ts    From core with GNU General Public License v3.0 5 votes vote down vote up
contentHashBytes: Bytes
Example #3
Source File: Media.test.ts    From core with GNU General Public License v3.0 5 votes vote down vote up
otherContentHashBytes: Bytes
Example #4
Source File: Media.test.ts    From core with GNU General Public License v3.0 5 votes vote down vote up
zeroContentHashBytes: Bytes
Example #5
Source File: Media.test.ts    From core with GNU General Public License v3.0 5 votes vote down vote up
metadataHashBytes: Bytes
Example #6
Source File: Media.test.ts    From core with GNU General Public License v3.0 4 votes vote down vote up
describe('Media', () => {
  let [
    deployerWallet,
    bidderWallet,
    creatorWallet,
    ownerWallet,
    prevOwnerWallet,
    otherWallet,
    nonBidderWallet,
  ] = generatedWallets(provider);

  let defaultBidShares = {
    prevOwner: Decimal.new(10),
    owner: Decimal.new(80),
    creator: Decimal.new(10),
  };

  let defaultTokenId = 1;
  let defaultAsk = {
    amount: 100,
    currency: '0x41A322b28D0fF354040e2CbC676F0320d8c8850d',
    sellOnShare: Decimal.new(0),
  };
  const defaultBid = (
    currency: string,
    bidder: string,
    recipient?: string
  ) => ({
    amount: 100,
    currency,
    bidder,
    recipient: recipient || bidder,
    sellOnShare: Decimal.new(10),
  });

  let auctionAddress: string;
  let tokenAddress: string;

  async function tokenAs(wallet: Wallet) {
    return MediaFactory.connect(tokenAddress, wallet);
  }
  async function deploy() {
    const auction = await (
      await new MarketFactory(deployerWallet).deploy()
    ).deployed();
    auctionAddress = auction.address;
    const token = await (
      await new MediaFactory(deployerWallet).deploy(auction.address)
    ).deployed();
    tokenAddress = token.address;

    await auction.configure(tokenAddress);
  }

  async function mint(
    token: Media,
    metadataURI: string,
    tokenURI: string,
    contentHash: Bytes,
    metadataHash: Bytes,
    shares: BidShares
  ) {
    const data: MediaData = {
      tokenURI,
      metadataURI,
      contentHash,
      metadataHash,
    };
    return token.mint(data, shares);
  }

  async function mintWithSig(
    token: Media,
    creator: string,
    tokenURI: string,
    metadataURI: string,
    contentHash: Bytes,
    metadataHash: Bytes,
    shares: BidShares,
    sig: EIP712Sig
  ) {
    const data: MediaData = {
      tokenURI,
      metadataURI,
      contentHash,
      metadataHash,
    };

    return token.mintWithSig(creator, data, shares, sig);
  }

  async function setAsk(token: Media, tokenId: number, ask: Ask) {
    return token.setAsk(tokenId, ask);
  }

  async function removeAsk(token: Media, tokenId: number) {
    return token.removeAsk(tokenId);
  }

  async function setBid(token: Media, bid: Bid, tokenId: number) {
    return token.setBid(tokenId, bid);
  }

  async function removeBid(token: Media, tokenId: number) {
    return token.removeBid(tokenId);
  }

  async function acceptBid(token: Media, tokenId: number, bid: Bid) {
    return token.acceptBid(tokenId, bid);
  }

  // Trade a token a few times and create some open bids
  async function setupAuction(currencyAddr: string, tokenId = 0) {
    const asCreator = await tokenAs(creatorWallet);
    const asPrevOwner = await tokenAs(prevOwnerWallet);
    const asOwner = await tokenAs(ownerWallet);
    const asBidder = await tokenAs(bidderWallet);
    const asOther = await tokenAs(otherWallet);

    await mintCurrency(currencyAddr, creatorWallet.address, 10000);
    await mintCurrency(currencyAddr, prevOwnerWallet.address, 10000);
    await mintCurrency(currencyAddr, ownerWallet.address, 10000);
    await mintCurrency(currencyAddr, bidderWallet.address, 10000);
    await mintCurrency(currencyAddr, otherWallet.address, 10000);
    await approveCurrency(currencyAddr, auctionAddress, creatorWallet);
    await approveCurrency(currencyAddr, auctionAddress, prevOwnerWallet);
    await approveCurrency(currencyAddr, auctionAddress, ownerWallet);
    await approveCurrency(currencyAddr, auctionAddress, bidderWallet);
    await approveCurrency(currencyAddr, auctionAddress, otherWallet);

    await mint(
      asCreator,
      metadataURI,
      tokenURI,
      contentHashBytes,
      metadataHashBytes,
      defaultBidShares
    );

    await setBid(
      asPrevOwner,
      defaultBid(currencyAddr, prevOwnerWallet.address),
      tokenId
    );
    await acceptBid(asCreator, tokenId, {
      ...defaultBid(currencyAddr, prevOwnerWallet.address),
    });
    await setBid(
      asOwner,
      defaultBid(currencyAddr, ownerWallet.address),
      tokenId
    );
    await acceptBid(
      asPrevOwner,
      tokenId,
      defaultBid(currencyAddr, ownerWallet.address)
    );
    await setBid(
      asBidder,
      defaultBid(currencyAddr, bidderWallet.address),
      tokenId
    );
    await setBid(
      asOther,
      defaultBid(currencyAddr, otherWallet.address),
      tokenId
    );
  }

  beforeEach(async () => {
    await blockchain.resetAsync();

    metadataHex = ethers.utils.formatBytes32String('{}');
    metadataHash = await sha256(metadataHex);
    metadataHashBytes = ethers.utils.arrayify(metadataHash);

    contentHex = ethers.utils.formatBytes32String('invert');
    contentHash = await sha256(contentHex);
    contentHashBytes = ethers.utils.arrayify(contentHash);

    otherContentHex = ethers.utils.formatBytes32String('otherthing');
    otherContentHash = await sha256(otherContentHex);
    otherContentHashBytes = ethers.utils.arrayify(otherContentHash);

    zeroContentHashBytes = ethers.utils.arrayify(ethers.constants.HashZero);
  });

  describe('#constructor', () => {
    it('should be able to deploy', async () => {
      await expect(deploy()).eventually.fulfilled;
    });
  });

  describe('#mint', () => {
    beforeEach(async () => {
      await deploy();
    });

    it('should mint a token', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(
        mint(
          token,
          metadataURI,
          tokenURI,
          contentHashBytes,
          metadataHashBytes,
          {
            prevOwner: Decimal.new(10),
            creator: Decimal.new(90),
            owner: Decimal.new(0),
          }
        )
      ).fulfilled;

      const t = await token.tokenByIndex(0);
      const ownerT = await token.tokenOfOwnerByIndex(creatorWallet.address, 0);
      const ownerOf = await token.ownerOf(0);
      const creator = await token.tokenCreators(0);
      const prevOwner = await token.previousTokenOwners(0);
      const tokenContentHash = await token.tokenContentHashes(0);
      const metadataContentHash = await token.tokenMetadataHashes(0);
      const savedTokenURI = await token.tokenURI(0);
      const savedMetadataURI = await token.tokenMetadataURI(0);

      expect(toNumWei(t)).eq(toNumWei(ownerT));
      expect(ownerOf).eq(creatorWallet.address);
      expect(creator).eq(creatorWallet.address);
      expect(prevOwner).eq(creatorWallet.address);
      expect(tokenContentHash).eq(contentHash);
      expect(metadataContentHash).eq(metadataHash);
      expect(savedTokenURI).eq(tokenURI);
      expect(savedMetadataURI).eq(metadataURI);
    });

    it('should revert if an empty content hash is specified', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(
        mint(
          token,
          metadataURI,
          tokenURI,
          zeroContentHashBytes,
          metadataHashBytes,
          {
            prevOwner: Decimal.new(10),
            creator: Decimal.new(90),
            owner: Decimal.new(0),
          }
        )
      ).rejectedWith('Media: content hash must be non-zero');
    });

    it('should revert if the content hash already exists for a created token', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(
        mint(
          token,
          metadataURI,
          tokenURI,
          contentHashBytes,
          metadataHashBytes,
          {
            prevOwner: Decimal.new(10),
            creator: Decimal.new(90),
            owner: Decimal.new(0),
          }
        )
      ).fulfilled;

      await expect(
        mint(
          token,
          metadataURI,
          tokenURI,
          contentHashBytes,
          metadataHashBytes,
          {
            prevOwner: Decimal.new(10),
            creator: Decimal.new(90),
            owner: Decimal.new(0),
          }
        )
      ).rejectedWith(
        'Media: a token has already been created with this content hash'
      );
    });

    it('should revert if the metadataHash is empty', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(
        mint(
          token,
          metadataURI,
          tokenURI,
          contentHashBytes,
          zeroContentHashBytes,
          {
            prevOwner: Decimal.new(10),
            creator: Decimal.new(90),
            owner: Decimal.new(0),
          }
        )
      ).rejectedWith('Media: metadata hash must be non-zero');
    });

    it('should revert if the tokenURI is empty', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(
        mint(token, metadataURI, '', zeroContentHashBytes, metadataHashBytes, {
          prevOwner: Decimal.new(10),
          creator: Decimal.new(90),
          owner: Decimal.new(0),
        })
      ).rejectedWith('Media: specified uri must be non-empty');
    });

    it('should revert if the metadataURI is empty', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(
        mint(token, '', tokenURI, zeroContentHashBytes, metadataHashBytes, {
          prevOwner: Decimal.new(10),
          creator: Decimal.new(90),
          owner: Decimal.new(0),
        })
      ).rejectedWith('Media: specified uri must be non-empty');
    });

    it('should not be able to mint a token with bid shares summing to less than 100', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(
        mint(
          token,
          metadataURI,
          tokenURI,
          contentHashBytes,
          metadataHashBytes,
          {
            prevOwner: Decimal.new(15),
            owner: Decimal.new(15),
            creator: Decimal.new(15),
          }
        )
      ).rejectedWith('Market: Invalid bid shares, must sum to 100');
    });

    it('should not be able to mint a token with bid shares summing to greater than 100', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(
        mint(token, metadataURI, '222', contentHashBytes, metadataHashBytes, {
          prevOwner: Decimal.new(99),
          owner: Decimal.new(1),
          creator: Decimal.new(1),
        })
      ).rejectedWith('Market: Invalid bid shares, must sum to 100');
    });
  });

  describe('#mintWithSig', () => {
    beforeEach(async () => {
      await deploy();
    });

    it('should mint a token for a given creator with a valid signature', async () => {
      const token = await tokenAs(otherWallet);
      const market = await MarketFactory.connect(auctionAddress, otherWallet);
      const sig = await signMintWithSig(
        creatorWallet,
        token.address,
        creatorWallet.address,
        contentHash,
        metadataHash,
        Decimal.new(5).value.toString(),
        1
      );

      const beforeNonce = await token.mintWithSigNonces(creatorWallet.address);
      await expect(
        mintWithSig(
          token,
          creatorWallet.address,
          tokenURI,
          metadataURI,
          contentHashBytes,
          metadataHashBytes,
          {
            prevOwner: Decimal.new(0),
            owner: Decimal.new(95),
            creator: Decimal.new(5),
          },
          sig
        )
      ).fulfilled;

      const recovered = await token.tokenCreators(0);
      const recoveredTokenURI = await token.tokenURI(0);
      const recoveredMetadataURI = await token.tokenMetadataURI(0);
      const recoveredContentHash = await token.tokenContentHashes(0);
      const recoveredMetadataHash = await token.tokenMetadataHashes(0);
      const recoveredCreatorBidShare = formatUnits(
        (await market.bidSharesForToken(0)).creator.value,
        'ether'
      );
      const afterNonce = await token.mintWithSigNonces(creatorWallet.address);

      expect(recovered).to.eq(creatorWallet.address);
      expect(recoveredTokenURI).to.eq(tokenURI);
      expect(recoveredMetadataURI).to.eq(metadataURI);
      expect(recoveredContentHash).to.eq(contentHash);
      expect(recoveredMetadataHash).to.eq(metadataHash);
      expect(recoveredCreatorBidShare).to.eq('5.0');
      expect(toNumWei(afterNonce)).to.eq(toNumWei(beforeNonce) + 1);
    });

    it('should not mint a token for a different creator', async () => {
      const token = await tokenAs(otherWallet);
      const sig = await signMintWithSig(
        bidderWallet,
        token.address,
        creatorWallet.address,
        tokenURI,
        metadataURI,
        Decimal.new(5).value.toString(),
        1
      );

      await expect(
        mintWithSig(
          token,
          creatorWallet.address,
          tokenURI,
          metadataURI,
          contentHashBytes,
          metadataHashBytes,
          {
            prevOwner: Decimal.new(0),
            owner: Decimal.new(95),
            creator: Decimal.new(5),
          },
          sig
        )
      ).rejectedWith('Media: Signature invalid');
    });

    it('should not mint a token for a different contentHash', async () => {
      const badContent = 'bad bad bad';
      const badContentHex = formatBytes32String(badContent);
      const badContentHash = sha256(badContentHex);
      const badContentHashBytes = arrayify(badContentHash);

      const token = await tokenAs(otherWallet);
      const sig = await signMintWithSig(
        creatorWallet,
        token.address,
        creatorWallet.address,
        contentHash,
        metadataHash,
        Decimal.new(5).value.toString(),
        1
      );

      await expect(
        mintWithSig(
          token,
          creatorWallet.address,
          tokenURI,
          metadataURI,
          badContentHashBytes,
          metadataHashBytes,
          {
            prevOwner: Decimal.new(0),
            owner: Decimal.new(95),
            creator: Decimal.new(5),
          },
          sig
        )
      ).rejectedWith('Media: Signature invalid');
    });
    it('should not mint a token for a different metadataHash', async () => {
      const badMetadata = '{"some": "bad", "data": ":)"}';
      const badMetadataHex = formatBytes32String(badMetadata);
      const badMetadataHash = sha256(badMetadataHex);
      const badMetadataHashBytes = arrayify(badMetadataHash);
      const token = await tokenAs(otherWallet);
      const sig = await signMintWithSig(
        creatorWallet,
        token.address,
        creatorWallet.address,
        contentHash,
        metadataHash,
        Decimal.new(5).value.toString(),
        1
      );

      await expect(
        mintWithSig(
          token,
          creatorWallet.address,
          tokenURI,
          metadataURI,
          contentHashBytes,
          badMetadataHashBytes,
          {
            prevOwner: Decimal.new(0),
            owner: Decimal.new(95),
            creator: Decimal.new(5),
          },
          sig
        )
      ).rejectedWith('Media: Signature invalid');
    });
    it('should not mint a token for a different creator bid share', async () => {
      const token = await tokenAs(otherWallet);
      const sig = await signMintWithSig(
        creatorWallet,
        token.address,
        creatorWallet.address,
        tokenURI,
        metadataURI,
        Decimal.new(5).value.toString(),
        1
      );

      await expect(
        mintWithSig(
          token,
          creatorWallet.address,
          tokenURI,
          metadataURI,
          contentHashBytes,
          metadataHashBytes,
          {
            prevOwner: Decimal.new(0),
            owner: Decimal.new(100),
            creator: Decimal.new(0),
          },
          sig
        )
      ).rejectedWith('Media: Signature invalid');
    });
    it('should not mint a token with an invalid deadline', async () => {
      const token = await tokenAs(otherWallet);
      const sig = await signMintWithSig(
        creatorWallet,
        token.address,
        creatorWallet.address,
        tokenURI,
        metadataURI,
        Decimal.new(5).value.toString(),
        1
      );

      await expect(
        mintWithSig(
          token,
          creatorWallet.address,
          tokenURI,
          metadataURI,
          contentHashBytes,
          metadataHashBytes,
          {
            prevOwner: Decimal.new(0),
            owner: Decimal.new(95),
            creator: Decimal.new(5),
          },
          { ...sig, deadline: '1' }
        )
      ).rejectedWith('Media: mintWithSig expired');
    });
  });

  describe('#setAsk', () => {
    let currencyAddr: string;
    beforeEach(async () => {
      await deploy();
      currencyAddr = await deployCurrency();
      await setupAuction(currencyAddr);
    });

    it('should set the ask', async () => {
      const token = await tokenAs(ownerWallet);
      await expect(setAsk(token, 0, defaultAsk)).fulfilled;
    });

    it('should reject if the ask is 0', async () => {
      const token = await tokenAs(ownerWallet);
      await expect(setAsk(token, 0, { ...defaultAsk, amount: 0 })).rejectedWith(
        'Market: Ask invalid for share splitting'
      );
    });

    it('should reject if the ask amount is invalid and cannot be split', async () => {
      const token = await tokenAs(ownerWallet);
      await expect(
        setAsk(token, 0, { ...defaultAsk, amount: 101 })
      ).rejectedWith('Market: Ask invalid for share splitting');
    });
  });

  describe('#removeAsk', () => {
    it('should remove the ask', async () => {
      const token = await tokenAs(ownerWallet);
      const market = await MarketFactory.connect(
        auctionAddress,
        deployerWallet
      );
      await setAsk(token, 0, defaultAsk);

      await expect(removeAsk(token, 0)).fulfilled;
      const ask = await market.currentAskForToken(0);
      expect(toNumWei(ask.amount)).eq(0);
      expect(ask.currency).eq(AddressZero);
    });

    it('should emit an Ask Removed event', async () => {
      const token = await tokenAs(ownerWallet);
      const auction = await MarketFactory.connect(
        auctionAddress,
        deployerWallet
      );
      await setAsk(token, 0, defaultAsk);
      const block = await provider.getBlockNumber();
      const tx = await removeAsk(token, 0);

      const events = await auction.queryFilter(
        auction.filters.AskRemoved(0, null),
        block
      );
      expect(events.length).eq(1);
      const logDescription = auction.interface.parseLog(events[0]);
      expect(toNumWei(logDescription.args.tokenId)).to.eq(0);
      expect(toNumWei(logDescription.args.ask.amount)).to.eq(defaultAsk.amount);
      expect(logDescription.args.ask.currency).to.eq(defaultAsk.currency);
    });

    it('should not be callable by anyone that is not owner or approved', async () => {
      const token = await tokenAs(ownerWallet);
      const asOther = await tokenAs(otherWallet);
      await setAsk(token, 0, defaultAsk);

      expect(removeAsk(asOther, 0)).rejectedWith(
        'Media: Only approved or owner'
      );
    });
  });

  describe('#setBid', () => {
    let currencyAddr: string;
    beforeEach(async () => {
      await deploy();
      await mint(
        await tokenAs(creatorWallet),
        metadataURI,
        '1111',
        otherContentHashBytes,
        metadataHashBytes,
        defaultBidShares
      );
      currencyAddr = await deployCurrency();
    });

    it('should revert if the token bidder does not have a high enough allowance for their bidding currency', async () => {
      const token = await tokenAs(bidderWallet);
      await expect(
        token.setBid(0, defaultBid(currencyAddr, bidderWallet.address))
      ).rejectedWith('SafeERC20: ERC20 operation did not succeed');
    });

    it('should revert if the token bidder does not have a high enough balance for their bidding currency', async () => {
      const token = await tokenAs(bidderWallet);
      await approveCurrency(currencyAddr, auctionAddress, bidderWallet);
      await expect(
        token.setBid(0, defaultBid(currencyAddr, bidderWallet.address))
      ).rejectedWith('SafeERC20: ERC20 operation did not succeed');
    });

    it('should set a bid', async () => {
      const token = await tokenAs(bidderWallet);
      await approveCurrency(currencyAddr, auctionAddress, bidderWallet);
      await mintCurrency(currencyAddr, bidderWallet.address, 100000);
      await expect(
        token.setBid(0, defaultBid(currencyAddr, bidderWallet.address))
      ).fulfilled;
      const balance = await getBalance(currencyAddr, bidderWallet.address);
      expect(toNumWei(balance)).eq(100000 - 100);
    });

    it('should automatically transfer the token if the ask is set', async () => {
      const token = await tokenAs(bidderWallet);
      const asOwner = await tokenAs(ownerWallet);
      await setupAuction(currencyAddr, 1);
      await setAsk(asOwner, 1, { ...defaultAsk, currency: currencyAddr });

      await expect(
        token.setBid(1, defaultBid(currencyAddr, bidderWallet.address))
      ).fulfilled;

      await expect(token.ownerOf(1)).eventually.eq(bidderWallet.address);
    });

    it('should refund a bid if one already exists for the bidder', async () => {
      const token = await tokenAs(bidderWallet);
      await setupAuction(currencyAddr, 1);

      const beforeBalance = toNumWei(
        await getBalance(currencyAddr, bidderWallet.address)
      );
      await setBid(
        token,
        {
          currency: currencyAddr,
          amount: 200,
          bidder: bidderWallet.address,
          recipient: otherWallet.address,
          sellOnShare: Decimal.new(10),
        },
        1
      );
      const afterBalance = toNumWei(
        await getBalance(currencyAddr, bidderWallet.address)
      );

      expect(afterBalance).eq(beforeBalance - 100);
    });
  });

  describe('#removeBid', () => {
    let currencyAddr: string;
    beforeEach(async () => {
      await deploy();
      currencyAddr = await deployCurrency();
      await setupAuction(currencyAddr);
    });

    it('should revert if the bidder has not placed a bid', async () => {
      const token = await tokenAs(nonBidderWallet);

      await expect(removeBid(token, 0)).rejectedWith(
        'Market: cannot remove bid amount of 0'
      );
    });

    it('should revert if the tokenId has not yet ben created', async () => {
      const token = await tokenAs(bidderWallet);

      await expect(removeBid(token, 100)).rejectedWith(
        'Media: token with that id does not exist'
      );
    });

    it('should remove a bid and refund the bidder', async () => {
      const token = await tokenAs(bidderWallet);
      const beforeBalance = toNumWei(
        await getBalance(currencyAddr, bidderWallet.address)
      );
      await expect(removeBid(token, 0)).fulfilled;
      const afterBalance = toNumWei(
        await getBalance(currencyAddr, bidderWallet.address)
      );

      expect(afterBalance).eq(beforeBalance + 100);
    });

    it('should not be able to remove a bid twice', async () => {
      const token = await tokenAs(bidderWallet);
      await removeBid(token, 0);

      await expect(removeBid(token, 0)).rejectedWith(
        'Market: cannot remove bid amount of 0'
      );
    });

    it('should remove a bid, even if the token is burned', async () => {
      const asOwner = await tokenAs(ownerWallet);
      const asBidder = await tokenAs(bidderWallet);
      const asCreator = await tokenAs(creatorWallet);

      await asOwner.transferFrom(ownerWallet.address, creatorWallet.address, 0);
      await asCreator.burn(0);
      const beforeBalance = toNumWei(
        await getBalance(currencyAddr, bidderWallet.address)
      );
      await expect(asBidder.removeBid(0)).fulfilled;
      const afterBalance = toNumWei(
        await getBalance(currencyAddr, bidderWallet.address)
      );
      expect(afterBalance).eq(beforeBalance + 100);
    });
  });

  describe('#acceptBid', () => {
    let currencyAddr: string;
    beforeEach(async () => {
      await deploy();
      currencyAddr = await deployCurrency();
      await setupAuction(currencyAddr);
    });

    it('should accept a bid', async () => {
      const token = await tokenAs(ownerWallet);
      const auction = await MarketFactory.connect(auctionAddress, bidderWallet);
      const asBidder = await tokenAs(bidderWallet);
      const bid = {
        ...defaultBid(currencyAddr, bidderWallet.address, otherWallet.address),
        sellOnShare: Decimal.new(15),
      };
      await setBid(asBidder, bid, 0);

      const beforeOwnerBalance = toNumWei(
        await getBalance(currencyAddr, ownerWallet.address)
      );
      const beforePrevOwnerBalance = toNumWei(
        await getBalance(currencyAddr, prevOwnerWallet.address)
      );
      const beforeCreatorBalance = toNumWei(
        await getBalance(currencyAddr, creatorWallet.address)
      );
      await expect(token.acceptBid(0, bid)).fulfilled;
      const newOwner = await token.ownerOf(0);
      const afterOwnerBalance = toNumWei(
        await getBalance(currencyAddr, ownerWallet.address)
      );
      const afterPrevOwnerBalance = toNumWei(
        await getBalance(currencyAddr, prevOwnerWallet.address)
      );
      const afterCreatorBalance = toNumWei(
        await getBalance(currencyAddr, creatorWallet.address)
      );
      const bidShares = await auction.bidSharesForToken(0);

      expect(afterOwnerBalance).eq(beforeOwnerBalance + 80);
      expect(afterPrevOwnerBalance).eq(beforePrevOwnerBalance + 10);
      expect(afterCreatorBalance).eq(beforeCreatorBalance + 10);
      expect(newOwner).eq(otherWallet.address);
      expect(toNumWei(bidShares.owner.value)).eq(75 * 10 ** 18);
      expect(toNumWei(bidShares.prevOwner.value)).eq(15 * 10 ** 18);
      expect(toNumWei(bidShares.creator.value)).eq(10 * 10 ** 18);
    });

    it('should emit a bid finalized event if the bid is accepted', async () => {
      const asBidder = await tokenAs(bidderWallet);
      const token = await tokenAs(ownerWallet);
      const auction = await MarketFactory.connect(auctionAddress, bidderWallet);
      const bid = defaultBid(currencyAddr, bidderWallet.address);
      const block = await provider.getBlockNumber();
      await setBid(asBidder, bid, 0);
      await token.acceptBid(0, bid);
      const events = await auction.queryFilter(
        auction.filters.BidFinalized(null, null),
        block
      );
      expect(events.length).eq(1);
      const logDescription = auction.interface.parseLog(events[0]);
      expect(toNumWei(logDescription.args.tokenId)).to.eq(0);
      expect(toNumWei(logDescription.args.bid.amount)).to.eq(bid.amount);
      expect(logDescription.args.bid.currency).to.eq(bid.currency);
      expect(toNumWei(logDescription.args.bid.sellOnShare.value)).to.eq(
        toNumWei(bid.sellOnShare.value)
      );
      expect(logDescription.args.bid.bidder).to.eq(bid.bidder);
    });

    it('should emit a bid shares updated event if the bid is accepted', async () => {
      const asBidder = await tokenAs(bidderWallet);
      const token = await tokenAs(ownerWallet);
      const auction = await MarketFactory.connect(auctionAddress, bidderWallet);
      const bid = defaultBid(currencyAddr, bidderWallet.address);
      const block = await provider.getBlockNumber();
      await setBid(asBidder, bid, 0);
      await token.acceptBid(0, bid);
      const events = await auction.queryFilter(
        auction.filters.BidShareUpdated(null, null),
        block
      );
      expect(events.length).eq(1);
      const logDescription = auction.interface.parseLog(events[0]);
      expect(toNumWei(logDescription.args.tokenId)).to.eq(0);
      expect(toNumWei(logDescription.args.bidShares.prevOwner.value)).to.eq(
        10000000000000000000
      );
      expect(toNumWei(logDescription.args.bidShares.owner.value)).to.eq(
        80000000000000000000
      );
      expect(toNumWei(logDescription.args.bidShares.creator.value)).to.eq(
        10000000000000000000
      );
    });

    it('should revert if not called by the owner', async () => {
      const token = await tokenAs(otherWallet);

      await expect(
        token.acceptBid(0, { ...defaultBid(currencyAddr, otherWallet.address) })
      ).rejectedWith('Media: Only approved or owner');
    });

    it('should revert if a non-existent bid is accepted', async () => {
      const token = await tokenAs(ownerWallet);
      await expect(
        token.acceptBid(0, { ...defaultBid(currencyAddr, AddressZero) })
      ).rejectedWith('Market: cannot accept bid of 0');
    });

    it('should revert if an invalid bid is accepted', async () => {
      const token = await tokenAs(ownerWallet);
      const asBidder = await tokenAs(bidderWallet);
      const bid = {
        ...defaultBid(currencyAddr, bidderWallet.address),
        amount: 99,
      };
      await setBid(asBidder, bid, 0);

      await expect(token.acceptBid(0, bid)).rejectedWith(
        'Market: Bid invalid for share splitting'
      );
    });

    // TODO: test the front running logic
  });

  describe('#transfer', () => {
    let currencyAddr: string;
    beforeEach(async () => {
      await deploy();
      currencyAddr = await deployCurrency();
      await setupAuction(currencyAddr);
    });

    it('should remove the ask after a transfer', async () => {
      const token = await tokenAs(ownerWallet);
      const auction = MarketFactory.connect(auctionAddress, deployerWallet);
      await setAsk(token, 0, defaultAsk);

      await expect(
        token.transferFrom(ownerWallet.address, otherWallet.address, 0)
      ).fulfilled;
      const ask = await auction.currentAskForToken(0);
      await expect(toNumWei(ask.amount)).eq(0);
      await expect(ask.currency).eq(AddressZero);
    });
  });

  describe('#burn', () => {
    beforeEach(async () => {
      await deploy();
      const token = await tokenAs(creatorWallet);
      await mint(
        token,
        metadataURI,
        tokenURI,
        contentHashBytes,
        metadataHashBytes,
        {
          prevOwner: Decimal.new(10),
          creator: Decimal.new(90),
          owner: Decimal.new(0),
        }
      );
    });

    it('should revert when the caller is the owner, but not creator', async () => {
      const creatorToken = await tokenAs(creatorWallet);
      await creatorToken.transferFrom(
        creatorWallet.address,
        ownerWallet.address,
        0
      );
      const token = await tokenAs(ownerWallet);
      await expect(token.burn(0)).rejectedWith(
        'Media: owner is not creator of media'
      );
    });

    it('should revert when the caller is approved, but the owner is not the creator', async () => {
      const creatorToken = await tokenAs(creatorWallet);
      await creatorToken.transferFrom(
        creatorWallet.address,
        ownerWallet.address,
        0
      );
      const token = await tokenAs(ownerWallet);
      await token.approve(otherWallet.address, 0);

      const otherToken = await tokenAs(otherWallet);
      await expect(otherToken.burn(0)).rejectedWith(
        'Media: owner is not creator of media'
      );
    });

    it('should revert when the caller is not the owner or a creator', async () => {
      const token = await tokenAs(otherWallet);

      await expect(token.burn(0)).rejectedWith('Media: Only approved or owner');
    });

    it('should revert if the token id does not exist', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(token.burn(100)).rejectedWith('Media: nonexistent token');
    });

    it('should clear approvals, set remove owner, but maintain tokenURI and contentHash when the owner is creator and caller', async () => {
      const token = await tokenAs(creatorWallet);
      await expect(token.approve(otherWallet.address, 0)).fulfilled;

      await expect(token.burn(0)).fulfilled;

      await expect(token.ownerOf(0)).rejectedWith(
        'ERC721: owner query for nonexistent token'
      );

      const totalSupply = await token.totalSupply();
      expect(toNumWei(totalSupply)).eq(0);

      await expect(token.getApproved(0)).rejectedWith(
        'ERC721: approved query for nonexistent token'
      );

      const tokenURI = await token.tokenURI(0);
      expect(tokenURI).eq('www.example.com');

      const contentHash = await token.tokenContentHashes(0);
      expect(contentHash).eq(contentHash);

      const previousOwner = await token.previousTokenOwners(0);
      expect(previousOwner).eq(AddressZero);
    });

    it('should clear approvals, set remove owner, but maintain tokenURI and contentHash when the owner is creator and caller is approved', async () => {
      const token = await tokenAs(creatorWallet);
      await expect(token.approve(otherWallet.address, 0)).fulfilled;

      const otherToken = await tokenAs(otherWallet);

      await expect(otherToken.burn(0)).fulfilled;

      await expect(token.ownerOf(0)).rejectedWith(
        'ERC721: owner query for nonexistent token'
      );

      const totalSupply = await token.totalSupply();
      expect(toNumWei(totalSupply)).eq(0);

      await expect(token.getApproved(0)).rejectedWith(
        'ERC721: approved query for nonexistent token'
      );

      const tokenURI = await token.tokenURI(0);
      expect(tokenURI).eq('www.example.com');

      const contentHash = await token.tokenContentHashes(0);
      expect(contentHash).eq(contentHash);

      const previousOwner = await token.previousTokenOwners(0);
      expect(previousOwner).eq(AddressZero);
    });
  });

  describe('#updateTokenURI', async () => {
    let currencyAddr: string;

    beforeEach(async () => {
      await deploy();
      currencyAddr = await deployCurrency();
      await setupAuction(currencyAddr);
    });

    it('should revert if the token does not exist', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(token.updateTokenURI(1, 'blah blah')).rejectedWith(
        'ERC721: operator query for nonexistent token'
      );
    });

    it('should revert if the caller is not the owner of the token and does not have approval', async () => {
      const token = await tokenAs(otherWallet);

      await expect(token.updateTokenURI(0, 'blah blah')).rejectedWith(
        'Media: Only approved or owner'
      );
    });

    it('should revert if the uri is empty string', async () => {
      const token = await tokenAs(ownerWallet);
      await expect(token.updateTokenURI(0, '')).rejectedWith(
        'Media: specified uri must be non-empty'
      );
    });

    it('should revert if the token has been burned', async () => {
      const token = await tokenAs(creatorWallet);

      await mint(
        token,
        metadataURI,
        tokenURI,
        otherContentHashBytes,
        metadataHashBytes,
        {
          prevOwner: Decimal.new(10),
          creator: Decimal.new(90),
          owner: Decimal.new(0),
        }
      );

      await expect(token.burn(1)).fulfilled;

      await expect(token.updateTokenURI(1, 'blah')).rejectedWith(
        'ERC721: operator query for nonexistent token'
      );
    });

    it('should set the tokenURI to the URI passed if the msg.sender is the owner', async () => {
      const token = await tokenAs(ownerWallet);
      await expect(token.updateTokenURI(0, 'blah blah')).fulfilled;

      const tokenURI = await token.tokenURI(0);
      expect(tokenURI).eq('blah blah');
    });

    it('should set the tokenURI to the URI passed if the msg.sender is approved', async () => {
      const token = await tokenAs(ownerWallet);
      await token.approve(otherWallet.address, 0);

      const otherToken = await tokenAs(otherWallet);
      await expect(otherToken.updateTokenURI(0, 'blah blah')).fulfilled;

      const tokenURI = await token.tokenURI(0);
      expect(tokenURI).eq('blah blah');
    });
  });

  describe('#updateMetadataURI', async () => {
    let currencyAddr: string;

    beforeEach(async () => {
      await deploy();
      currencyAddr = await deployCurrency();
      await setupAuction(currencyAddr);
    });

    it('should revert if the token does not exist', async () => {
      const token = await tokenAs(creatorWallet);

      await expect(token.updateTokenMetadataURI(1, 'blah blah')).rejectedWith(
        'ERC721: operator query for nonexistent token'
      );
    });

    it('should revert if the caller is not the owner of the token or approved', async () => {
      const token = await tokenAs(otherWallet);

      await expect(token.updateTokenMetadataURI(0, 'blah blah')).rejectedWith(
        'Media: Only approved or owner'
      );
    });

    it('should revert if the uri is empty string', async () => {
      const token = await tokenAs(ownerWallet);
      await expect(token.updateTokenMetadataURI(0, '')).rejectedWith(
        'Media: specified uri must be non-empty'
      );
    });

    it('should revert if the token has been burned', async () => {
      const token = await tokenAs(creatorWallet);

      await mint(
        token,
        metadataURI,
        tokenURI,
        otherContentHashBytes,
        metadataHashBytes,
        {
          prevOwner: Decimal.new(10),
          creator: Decimal.new(90),
          owner: Decimal.new(0),
        }
      );

      await expect(token.burn(1)).fulfilled;

      await expect(token.updateTokenMetadataURI(1, 'blah')).rejectedWith(
        'ERC721: operator query for nonexistent token'
      );
    });

    it('should set the tokenMetadataURI to the URI passed if msg.sender is the owner', async () => {
      const token = await tokenAs(ownerWallet);
      await expect(token.updateTokenMetadataURI(0, 'blah blah')).fulfilled;

      const tokenURI = await token.tokenMetadataURI(0);
      expect(tokenURI).eq('blah blah');
    });

    it('should set the tokenMetadataURI to the URI passed if the msg.sender is approved', async () => {
      const token = await tokenAs(ownerWallet);
      await token.approve(otherWallet.address, 0);

      const otherToken = await tokenAs(otherWallet);
      await expect(otherToken.updateTokenMetadataURI(0, 'blah blah')).fulfilled;

      const tokenURI = await token.tokenMetadataURI(0);
      expect(tokenURI).eq('blah blah');
    });
  });

  describe('#permit', () => {
    let currency: string;

    beforeEach(async () => {
      await deploy();
      currency = await deployCurrency();
      await setupAuction(currency);
    });

    it('should allow a wallet to set themselves to approved with a valid signature', async () => {
      const token = await tokenAs(otherWallet);
      const sig = await signPermit(
        ownerWallet,
        otherWallet.address,
        token.address,
        0,
        // NOTE: We set the chain ID to 1 because of an error with ganache-core: https://github.com/trufflesuite/ganache-core/issues/515
        1
      );
      await expect(token.permit(otherWallet.address, 0, sig)).fulfilled;
      await expect(token.getApproved(0)).eventually.eq(otherWallet.address);
    });

    it('should not allow a wallet to set themselves to approved with an invalid signature', async () => {
      const token = await tokenAs(otherWallet);
      const sig = await signPermit(
        ownerWallet,
        bidderWallet.address,
        token.address,
        0,
        1
      );
      await expect(token.permit(otherWallet.address, 0, sig)).rejectedWith(
        'Media: Signature invalid'
      );
      await expect(token.getApproved(0)).eventually.eq(AddressZero);
    });
  });

  describe('#supportsInterface', async () => {
    beforeEach(async () => {
      await deploy();
    });

    it('should return true to supporting new metadata interface', async () => {
      const token = await tokenAs(otherWallet);
      const interfaceId = ethers.utils.arrayify('0x4e222e66');
      const supportsId = await token.supportsInterface(interfaceId);
      expect(supportsId).eq(true);
    });

    it('should return false to supporting the old metadata interface', async () => {
      const token = await tokenAs(otherWallet);
      const interfaceId = ethers.utils.arrayify('0x5b5e139f');
      const supportsId = await token.supportsInterface(interfaceId);
      expect(supportsId).eq(false);
    });
  });

  describe('#revokeApproval', async () => {
    let currency: string;

    beforeEach(async () => {
      await deploy();
      currency = await deployCurrency();
      await setupAuction(currency);
    });

    it('should revert if the caller is the owner', async () => {
      const token = await tokenAs(ownerWallet);
      await expect(token.revokeApproval(0)).rejectedWith(
        'Media: caller not approved address'
      );
    });

    it('should revert if the caller is the creator', async () => {
      const token = await tokenAs(creatorWallet);
      await expect(token.revokeApproval(0)).rejectedWith(
        'Media: caller not approved address'
      );
    });

    it('should revert if the caller is neither owner, creator, or approver', async () => {
      const token = await tokenAs(otherWallet);
      await expect(token.revokeApproval(0)).rejectedWith(
        'Media: caller not approved address'
      );
    });

    it('should revoke the approval for token id if caller is approved address', async () => {
      const token = await tokenAs(ownerWallet);
      await token.approve(otherWallet.address, 0);
      const otherToken = await tokenAs(otherWallet);
      await expect(otherToken.revokeApproval(0)).fulfilled;
      const approved = await token.getApproved(0);
      expect(approved).eq(ethers.constants.AddressZero);
    });
  });
});
Example #7
Source File: media.ts    From zora-v1-subgraph with MIT License 4 votes vote down vote up
async function start() {
  const args = require('minimist')(process.argv.slice(2))

  if (!args.chainId) {
    throw new Error('--chainId chain ID is required')
  }

  if (!args.funcName) {
    throw new Error('--funcName is required')
  }

  const path = `${process.cwd()}/.env${
    args.chainId === 1 ? '.prod' : args.chainId === 4 ? '.dev' : '.local'
  }`

  const sharedAddressPath = `${process.cwd()}/config/${args.chainId}.json`
  // @ts-ignore
  const addressBook = JSON.parse(await fs.readFile(sharedAddressPath))

  if (addressBook.mediaAddress == null) {
    throw new Error('media address not specified in addressbook')
  }

  await require('dotenv').config({ path })
  const provider = new JsonRpcProvider(process.env.RPC_ENDPOINT)

  let [wallet1, wallet2, wallet3, wallet4, wallet5] = generatedWallets(provider)

  let contentHex: string
  let contentHash: string
  let contentHashBytes: Bytes

  let metadataHex: string
  let metadataHash: string
  let metadataHashBytes: Bytes

  // switch statement for function with args
  switch (args.funcName) {
    case 'mintFromIPFS': {
      // need an ipfs link
      if (!args.ipfsPath) {
        throw new Error('--ipfsPath required')
      }

      const ipfsPath = args.ipfsPath

      if (!args.mimeType) {
        throw new Error('--mimeType required')
      }

      const mimeType = args.mimeType

      const fleekApiKey = process.env.FLEEK_API_KEY
      const fleekApiSecret = process.env.FLEEK_API_SECRET

      const result = await fleekStorage.getFileFromHash({
        hash: ipfsPath,
        getFileFromHashOptions: ['buffer'],
      })

      const contentsha256 = crypto.createHash('sha256')
      contentsha256.update(Buffer.from(result))
      const contentHash = contentsha256.digest()

      const randomName = randomWords({ min: 2, max: 5, join: ' ' })
      const randomDescription = randomWords({ exactly: 10, join: ' ' })

      // create metadata json, upload to ipfs
      const metadata = {
        version: 'zora-20210101',
        name: randomName,
        description: randomDescription,
        mimeType: mimeType,
      }

      const minified = generateMetadata(metadata.version, metadata)

      // hash the metadata
      let metadataSha256 = crypto.createHash('sha256')
      metadataSha256.update(Buffer.from(minified))
      let metadataHash = metadataSha256.digest()

      const balance = await MediaFactory.connect(
        addressBook.mediaAddress,
        wallet1
      ).balanceOf(wallet1.address)

      const metadataCID = await fleekStorage.upload({
        apiKey: fleekApiKey,
        apiSecret: fleekApiSecret,
        key: wallet1.address.concat('-').concat(
          balance
            .toString()
            .concat('-')
            .concat('metadata')
        ),
        data: minified,
      })

      let mediaData = {
        tokenURI: 'https://ipfs.io/ipfs/'.concat(ipfsPath),
        metadataURI: 'https://ipfs.io/ipfs/'.concat(metadataCID.hash),
        contentHash: Uint8Array.from(contentHash),
        metadataHash: Uint8Array.from(metadataHash),
      }

      await mint(addressBook.mediaAddress, wallet1, mediaData)
      break
      // mint
    }
    case 'mintFromFile': {
      // need an ipfs link
      if (!args.filePath) {
        throw new Error('--filePath required')
      }

      const filePath = args.filePath

      if (!args.mimeType) {
        throw new Error('--mimeType required')
      }

      if (!args.name) {
        throw new Error('--name required')
      }

      if (!args.description) {
        throw new Error('--description required')
      }

      const mimeType = args.mimeType

      const fleekApiKey = process.env.FLEEK_API_KEY
      const fleekApiSecret = process.env.FLEEK_API_SECRET

      const contentHash = await sha256FromFile(filePath, 16)
      console.log(contentHash)

      const buf = await fs.readFile(filePath)
      const balance = await MediaFactory.connect(
        addressBook.mediaAddress,
        wallet1
      ).balanceOf(wallet1.address)

      const contentCID = await fleekStorage.upload({
        apiKey: fleekApiKey,
        apiSecret: fleekApiSecret,
        key: wallet1.address.concat('-').concat(balance.toString().concat('-')),
        data: buf,
      })

      const metadata = {
        name: args.name,
        description: args.description,
        mimeType: mimeType,
        version: 'zora-20210101',
      }

      const metadataJson = JSON.stringify(metadata)

      // hash the metadata
      const minified = generateMetadata(metadata.version, metadata)

      // hash the metadata
      let metadataSha256 = crypto.createHash('sha256')
      metadataSha256.update(Buffer.from(minified))
      let metadataHash = metadataSha256.digest()

      const metadataCID = await fleekStorage.upload({
        apiKey: fleekApiKey,
        apiSecret: fleekApiSecret,
        key: wallet1.address.concat('-').concat(
          balance
            .toString()
            .concat('-')
            .concat('metadata')
        ),
        data: minified,
      })

      let mediaData = {
        tokenURI: 'https://ipfs.io/ipfs/'.concat(contentCID.hash),
        metadataURI: 'https://ipfs.io/ipfs/'.concat(metadataCID.hash),
        contentHash: Uint8Array.from(Buffer.from(contentHash, 'hex')),
        metadataHash: Uint8Array.from(metadataHash),
      }

      await mint(addressBook.mediaAddress, wallet1, mediaData)
      break
    }
    case 'mint': {
      const supply = (await totalSupply(addressBook.mediaAddress, wallet1)).toNumber() + 1

      const metadata = `metadatas:${supply}`
      console.log('Metadata:', metadata)
      metadataHex = ethers.utils.formatBytes32String(metadata)
      metadataHash = await sha256(metadataHex)
      metadataHashBytes = ethers.utils.arrayify(metadataHash)

      const content = `inverts:${supply}`
      console.log('Content:', content)
      contentHex = ethers.utils.formatBytes32String(content)
      contentHash = await sha256(contentHex)

      console.log('ContentHash: ', contentHash)
      contentHashBytes = ethers.utils.arrayify(contentHash)

      let mediaData = {
        tokenURI: 'who cares',
        metadataURI: "i don't",
        contentHash: contentHashBytes,
        metadataHash: metadataHashBytes,
      }

      await mint(addressBook.mediaAddress, wallet1, mediaData)
      break
    }
    case 'burn': {
      if (!args.tokenId) {
        throw new Error('--tokenId is required')
      }

      const tokenId = BigNumber.from(args.tokenId)
      console.log(tokenId)

      await burn(addressBook.mediaAddress, wallet1, tokenId)
      break
    }
    case 'updateTokenURI': {
      if (!args.tokenId) {
        throw new Error('--tokenId is required')
      }
      const tokenId = BigNumber.from(args.tokenId)

      if (!args.uri) {
        throw new Error('--uri is required')
      }

      const tokenURI = args.uri.toString()
      console.log(addressBook.mediaAddress)

      await updateTokenURI(addressBook.mediaAddress, wallet1, tokenId, tokenURI)
      break
    }
    case 'updateTokenMetadataURI': {
      if (!args.tokenId) {
        throw new Error('--tokenId is required')
      }
      const tokenId = BigNumber.from(args.tokenId)

      if (!args.uri) {
        throw new Error('--uri is required')
      }

      const tokenMetadataURI = args.uri.toString()

      await updateTokenMetadataURI(
        addressBook.mediaAddress,
        wallet1,
        tokenId,
        tokenMetadataURI
      )
      break
    }
    case 'approve': {
      if (!args.tokenId) {
        throw new Error('--tokenId is required')
      }

      const tokenId = BigNumber.from(args.tokenId)

      if (!args.to) {
        throw new Error('--to is required')
      }

      const toAddress = args.to.toString()
      console.log(toAddress)
      await approve(addressBook.mediaAddress, wallet1, tokenId, toAddress)
      break
    }
    case 'approveForAll': {
      if (!args.operator) {
        throw new Error('--operator is required')
      }
      const operator = args.operator

      if (!args.approved) {
        throw new Error('--approved is required')
      }

      let approved: boolean
      if (args.approved.toString() == '0' || args.approved.toString() == 'false') {
        approved = false
      } else {
        approved = true
      }

      await approveForAll(addressBook.mediaAddress, wallet1, operator, approved)
      break
    }
    case 'transfer': {
      if (!args.tokenId) {
        throw new Error('--tokenId is required')
      }

      const tokenId = BigNumber.from(args.tokenId)

      if (!args.to) {
        throw new Error('--to is required')
      }

      const to = args.to

      let txHash = await transfer(addressBook.mediaAddress, wallet1, tokenId, to)
      let receipt = await provider.getTransactionReceipt(txHash)
      receipt.logs.forEach(log => {
        console.log(log)
      })

      break
    }
    case 'setAsk': {
      if (!args.tokenId) {
        throw new Error('--tokenId is required')
      }

      const tokenId = BigNumber.from(args.tokenId)

      let defaultAsk = {
        currency: 'eF77ce798401dAc8120F77dc2DebD5455eDdACf9', // DAI
        amount: Decimal.new(10).value,
        sellOnShare: Decimal.new(10),
      }

      await setAsk(addressBook.mediaAddress, wallet1, tokenId, defaultAsk)
      break
    }
    case 'removeAsk': {
      if (!args.tokenId) {
        throw new Error('--tokenId is required')
      }
      const tokenId = BigNumber.from(args.tokenId)

      await removeAsk(addressBook.mediaAddress, wallet1, tokenId)
      break
    }
    case `setBid`: {
      if (!args.tokenId) {
        throw new Error('--tokenId is required')
      }

      const tokenId = BigNumber.from(args.tokenId)

      let defaultBid = {
        currency: 'D1aE64401d65E9B0d1bF7E08Fbf75bb2F26eF70a',
        amount: 9,
        sellOnShare: Decimal.new(9),
        recipient: wallet1.address,
        bidder: wallet1.address,
      }

      await setBid(addressBook.mediaAddress, wallet2, tokenId, defaultBid)
      break
    }
    case `removeBid`: {
      if (!args.tokenId) {
        throw new Error('--tokenId is required')
      }

      const tokenId = BigNumber.from(args.tokenId)
      await removeBid(addressBook.mediaAddress, wallet2, tokenId)
      break
    }
  }
}
Example #8
Source File: media.test.ts    From zora-v1-subgraph with MIT License 4 votes vote down vote up
describe('Media', async () => {
  let mediaAddress: string
  let marketAddress: string
  let currencyAddress: string

  let provider = new JsonRpcProvider()
  let blockchain = new Blockchain(provider)
  let [creatorWallet, otherWallet, anotherWallet] = generatedWallets(provider)

  let defaultAsk = (currencyAddress: string) => ({
    currency: currencyAddress, // DAI
    amount: Decimal.new(10).value,
  })

  const defaultBid = (
    currency: string,
    bidder: string,
    recipient: string,
    amountValue?: number,
    sellOnShareValue?: number
  ) => ({
    currency: currency,
    amount: Decimal.new(amountValue || 9).value,
    sellOnShare: Decimal.new(sellOnShareValue || 9),
    bidder: bidder,
    recipient: recipient,
  })

  async function mint(wallet: Wallet, contentHash: Bytes, metadataHash: Bytes) {
    let defaultBidShares = {
      prevOwner: Decimal.new(10),
      owner: Decimal.new(80),
      creator: Decimal.new(10),
    }

    const media = await MediaFactory.connect(mediaAddress, wallet)

    const mediaData = {
      tokenURI: 'example.com',
      metadataURI: 'metadata.com',
      contentHash: contentHash,
      metadataHash: metadataHash,
    }

    await media.mint(mediaData, defaultBidShares)
    await delay(5000)
  }

  async function setAsk(
    wallet: Wallet,
    tokenId: BigNumberish,
    ask: SolidityAsk
  ): Promise<ContractTransaction> {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    const tx = await media.setAsk(tokenId, ask)
    await delay(5000)
    return tx
  }

  async function removeAsk(
    wallet: Wallet,
    tokenId: BigNumberish
  ): Promise<ContractTransaction> {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    const tx = await media.removeAsk(tokenId)
    await delay(5000)
    return tx
  }

  async function setBid(
    wallet: Wallet,
    tokenId: BigNumberish,
    bid: SolidityBid
  ): Promise<ContractTransaction> {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    const tx = await media.setBid(tokenId, bid)
    await delay(5000)
    return tx
  }

  async function removeBid(
    wallet: Wallet,
    tokenId: BigNumberish
  ): Promise<ContractTransaction> {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    const tx = await media.removeBid(tokenId)
    await delay(5000)
    return tx
  }

  async function acceptBid(
    wallet: Wallet,
    tokenId: BigNumberish,
    bid: SolidityBid
  ): Promise<ContractTransaction> {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    const tx = await media.acceptBid(tokenId, bid)
    await delay(5000)
    return tx
  }

  async function deploy(wallet: Wallet) {
    const market = await (await new MarketFactory(wallet).deploy()).deployed()
    marketAddress = market.address

    const media = await (await new MediaFactory(wallet).deploy(market.address)).deployed()
    mediaAddress = media.address

    await market.configure(mediaAddress)

    const currency = await (
      await new BaseErc20Factory(wallet).deploy('BRECK', 'BRECK', BigNumber.from(18))
    ).deployed()
    currencyAddress = currency.address

    for (const toWallet of generatedWallets(provider)) {
      await mintCurrency(
        wallet,
        currencyAddress,
        toWallet.address,
        BigNumber.from('10000000000000000000000')
      )
      await approveCurrency(toWallet, currencyAddress, marketAddress)
      await delay(1000)
    }

    await delay(5000)
  }

  async function transfer(wallet: Wallet, tokenId: BigNumberish, to: string) {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    const tx = await media.transferFrom(wallet.address, to, tokenId)
    let receipt = await provider.getTransactionReceipt(tx.hash)
    await delay(5000)
  }

  async function burn(wallet: Wallet, tokenId: BigNumber) {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    await media.burn(tokenId)
    await delay(5000)
  }

  async function approve(wallet: Wallet, tokenId: BigNumber, to: string) {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    await media.approve(to, tokenId)
    await delay(5000)
  }

  async function updateTokenURI(wallet: Wallet, tokenId: BigNumber, uri: string) {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    await media.updateTokenURI(tokenId, uri)
    await delay(5000)
  }

  async function updateTokenMetadataURI(wallet: Wallet, tokenId: BigNumber, uri: string) {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    await media.updateTokenMetadataURI(tokenId, uri)
    await delay(5000)
  }

  async function setApprovalForAll(wallet: Wallet, operator: string, approved: boolean) {
    const media = await MediaFactory.connect(mediaAddress, wallet)
    await media.setApprovalForAll(operator, approved)
    await delay(5000)
  }

  let contentHash: Bytes
  let metadataHash: Bytes
  let currencyDecimals: number
  let currencyName: string
  let currencySymbol: string

  beforeEach(async () => {
    // reset blockchain and deploy
    console.log('Resetting Blockchain')
    await blockchain.resetAsync()
    await blockchain.saveSnapshotAsync()
    console.log('Successfully Reset Blockchain')

    await deploy(creatorWallet)
    console.log('Market Deployed at: ', marketAddress)
    console.log('Media Deployed at: ', mediaAddress)

    // restart graph-node
    console.log('Resetting Graph-Node')
    await system(
      `cd ${pathToGraphNode.concat('/docker')} && docker-compose down && rm -rf ./data`
    )
    await system(`cd ${pathToGraphNode.concat('/docker')} && docker-compose up -d`)
    console.log('Successfully Reset Graph-Node')

    console.log('Waiting for Graph to startup before deploying subgraph')
    await axios.get('http://127.0.0.1:8000/')

    console.log('Creating Subgraph')
    await system(`yarn create-local`)
    console.log('Successfully Created Subgraph')

    await delay(1000)

    console.log('Deploying Subgraph')
    await system(`yarn deploy-local`)
    console.log('Successfully Deployed Subgraph')

    let currencyFactory = BaseErc20Factory.connect(currencyAddress, creatorWallet)
    currencyName = await currencyFactory.name()
    currencySymbol = await currencyFactory.symbol()
    currencyDecimals = await currencyFactory.decimals()
  })

  describe('#mint', async () => {
    it('it should correctly save the minted media and users', async () => {
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()

      await mint(creatorWallet, contentHash, metadataHash)

      let mediaResponse: MediaQueryResponse = await request(gqlURL, mediaByIdQuery('0'))
      let media = mediaResponse.media

      // TODO: Verify BidShares

      expect(media.id).toBe('0')
      expect(media.metadataHash).toBe(ethers.utils.hexlify(metadataHash))
      expect(media.contentHash).toBe(ethers.utils.hexlify(contentHash))
      expect(media.creator.id).toBe(creatorWallet.address.toLowerCase())
      expect(media.owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(media.prevOwner.id).toBe(creatorWallet.address.toLowerCase())

      let zeroUserResponse: UserQueryResponse = await request(
        gqlURL,
        userByIdQuery(ethers.constants.AddressZero)
      )
      let zeroUser = zeroUserResponse.user
      expect(zeroUser.id).toBe(ethers.constants.AddressZero)

      let userResponse: UserQueryResponse = await request(
        gqlURL,
        userByIdQuery(creatorWallet.address.toLowerCase())
      )
      let user = userResponse.user

      expect(user.id).toBe(creatorWallet.address.toLowerCase())
      expect(user.collection.length).toBe(1)
      expect(user.collection[0].id).toBe('0')
      expect(user.creations.length).toBe(1)
      expect(user.creations[0].id).toBe('0')

      let otherContentHash = await randomHashBytes()
      let otherMetadataHash = await randomHashBytes()

      let transfersResponse: TransfersQueryResponse = await request(
        gqlURL,
        transfersByMediaIdQuery('0')
      )
      expect(transfersResponse.transfers.length).toBe(1)
      expect(transfersResponse.transfers[0].from.id).toBe(ethers.constants.AddressZero)
      expect(transfersResponse.transfers[0].to.id).toBe(
        creatorWallet.address.toLowerCase()
      )
      // Mint again with the same address
      await mint(creatorWallet, otherContentHash, otherMetadataHash)

      let mediaResponse1: MediaQueryResponse = await request(gqlURL, mediaByIdQuery('1'))
      let media1 = mediaResponse1.media
      expect(media1.id).toBe('1')

      expect(media1.creator.id).toBe(creatorWallet.address.toLowerCase())
      expect(media1.owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(media1.prevOwner.id).toBe(creatorWallet.address.toLowerCase())

      let userResponse2: UserQueryResponse = await request(
        gqlURL,
        userByIdQuery(creatorWallet.address.toLowerCase())
      )
      let user2 = userResponse2.user
      expect(user2.id).toBe(creatorWallet.address.toLowerCase())
      expect(user2.collection.length).toBe(2)
      expect(user2.creations.length).toBe(2)

      transfersResponse = await request(gqlURL, transfersByMediaIdQuery('1'))
      expect(transfersResponse.transfers.length).toBe(1)
      expect(transfersResponse.transfers[0].from.id).toBe(ethers.constants.AddressZero)
      expect(transfersResponse.transfers[0].to.id).toBe(
        creatorWallet.address.toLowerCase()
      )

      // Mint with a new address
      let otherContentHash2 = await randomHashBytes()
      let otherMetadataHash2 = await randomHashBytes()

      await mint(otherWallet, otherContentHash2, otherMetadataHash2)

      let mediaResponse2: MediaQueryResponse = await request(gqlURL, mediaByIdQuery('2'))
      let media2 = mediaResponse2.media
      expect(media2.id).toBe('2')

      expect(media2.creator.id).toBe(otherWallet.address.toLowerCase())
      expect(media2.owner.id).toBe(otherWallet.address.toLowerCase())
      expect(media2.prevOwner.id).toBe(otherWallet.address.toLowerCase())

      let userResponse3: UserQueryResponse = await request(
        gqlURL,
        userByIdQuery(otherWallet.address.toLowerCase())
      )
      let user3 = userResponse3.user
      expect(user3.id).toBe(otherWallet.address.toLowerCase())
      expect(user3.collection.length).toBe(1)
      expect(user3.creations.length).toBe(1)

      // transfersResponse = await request(gqlURL, transfersByMediaIdQuery("2"));
      // expect(transfersResponse.transfers.length).toBe(1);
      // expect(transfersResponse.transfers[0].from.id).toBe(zeroAddress);
      // expect(transfersResponse.transfers[0].to.id).toBe(otherWallet.address.toLowerCase());
    })
  })

  describe('transfer', async () => {
    it('it correctly saves state when transfer event is emitted', async () => {
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()
      // mint (transfer from 0x000 to address)
      await mint(creatorWallet, contentHash, metadataHash)

      // verify 0 address user exists
      let zeroUserResponse: UserQueryResponse = await request(
        gqlURL,
        userByIdQuery(ethers.constants.AddressZero)
      )
      let zeroUser = zeroUserResponse.user
      expect(zeroUser.id).toBe(ethers.constants.AddressZero)

      // verify creator user exists
      let creatorUserResponse: UserQueryResponse = await request(
        gqlURL,
        userByIdQuery(creatorWallet.address.toLowerCase())
      )
      let creatorUser = creatorUserResponse.user
      expect(creatorUser.id).toBe(creatorWallet.address.toLowerCase())

      await transfer(creatorWallet, BigNumber.from(0), otherWallet.address)

      let transfersResponse: TransfersQueryResponse = await request(
        gqlURL,
        transfersByFromIdQuery(creatorWallet.address.toLowerCase())
      )
      expect(transfersResponse.transfers.length).toBe(1)
      expect(transfersResponse.transfers[0].from.id).toBe(
        creatorWallet.address.toLowerCase()
      )
      expect(transfersResponse.transfers[0].to.id).toBe(otherWallet.address.toLowerCase())
      expect(transfersResponse.transfers[0].media.id).toBe('0')

      // verify other address exists with correct data
      let otherUserResponse: UserQueryResponse = await request(
        gqlURL,
        userByIdQuery(otherWallet.address.toLowerCase())
      )
      let otherUser = otherUserResponse.user
      expect(otherUser.id).toBe(otherWallet.address.toLowerCase())
      expect(otherUser.collection.length).toBe(1)
      expect(otherUser.collection[0].id).toBe('0')
      expect(otherUser.creations.length).toBe(0)

      let mediaResponse: MediaQueryResponse = await request(gqlURL, mediaByIdQuery('0'))
      let media = mediaResponse.media

      expect(media.id).toBe('0')
      expect(media.creator.id).toBe(creatorWallet.address.toLowerCase())
      expect(media.prevOwner.id).toBe(creatorWallet.address.toLowerCase())
      expect(media.owner.id).toBe(otherWallet.address.toLowerCase())
      expect(media.approved).toBeNull()

      // TODO: verify approve gets reset to 0
      await approve(otherWallet, BigNumber.from(0), creatorWallet.address)
      mediaResponse = await request(gqlURL, mediaByIdQuery('0'))
      media = mediaResponse.media
      expect(media.approved.id).toBe(creatorWallet.address.toLowerCase())

      await transfer(otherWallet, BigNumber.from(0), anotherWallet.address)

      // verify anotherUser exists with correct data
      let anotherUserResponse: UserQueryResponse = await request(
        gqlURL,
        userByIdQuery(anotherWallet.address.toLowerCase())
      )
      let anotherUser = anotherUserResponse.user
      expect(anotherUser.id).toBe(anotherWallet.address.toLowerCase())
      expect(anotherUser.collection.length).toBe(1)
      expect(anotherUser.collection[0].id).toBe('0')
      expect(anotherUser.creations.length).toBe(0)

      mediaResponse = await request(gqlURL, mediaByIdQuery('0'))
      media = mediaResponse.media

      expect(media.id).toBe('0')
      expect(media.creator.id).toBe(creatorWallet.address.toLowerCase())
      expect(media.prevOwner.id).toBe(creatorWallet.address.toLowerCase())
      expect(media.owner.id).toBe(anotherWallet.address.toLowerCase())
      expect(media.approved).toBeNull()

      transfersResponse = await request(
        gqlURL,
        transfersByFromIdQuery(otherWallet.address.toLowerCase())
      )
      expect(transfersResponse.transfers.length).toBe(1)
      expect(transfersResponse.transfers[0].from.id).toBe(
        otherWallet.address.toLowerCase()
      )
      expect(transfersResponse.transfers[0].to.id).toBe(
        anotherWallet.address.toLowerCase()
      )
      expect(transfersResponse.transfers[0].media.id).toBe('0')

      // burn (transfer from address to 0x0000)
      await transfer(anotherWallet, BigNumber.from(0), creatorWallet.address)

      let anotherTransfersResponse: TransfersQueryResponse = await request(
        gqlURL,
        transfersByFromIdQuery(anotherWallet.address.toLowerCase())
      )
      expect(anotherTransfersResponse.transfers.length).toBe(1)
      expect(anotherTransfersResponse.transfers[0].from.id).toBe(
        anotherWallet.address.toLowerCase()
      )
      expect(anotherTransfersResponse.transfers[0].to.id).toBe(
        creatorWallet.address.toLowerCase()
      )
      expect(anotherTransfersResponse.transfers[0].media.id).toBe('0')

      await burn(creatorWallet, BigNumber.from(0))

      mediaResponse = await request(gqlURL, mediaByIdQuery('0'))
      media = mediaResponse.media

      expect(media.id).toBe('0')
      expect(media.creator.id).toBe(creatorWallet.address.toLowerCase())
      expect(media.prevOwner.id).toBe(ethers.constants.AddressZero)
      expect(media.owner.id).toBe(ethers.constants.AddressZero)
      expect(media.burnedAtTimestamp).not.toBeNull()
      expect(media.burnedAtBlockNumber).not.toBeNull()

      let burnTransfersResponse: TransfersQueryResponse = await request(
        gqlURL,
        transfersByToIdQuery(ethers.constants.AddressZero)
      )
      expect(burnTransfersResponse.transfers.length).toBe(1)
      expect(burnTransfersResponse.transfers[0].from.id).toBe(
        creatorWallet.address.toLowerCase()
      )
      expect(burnTransfersResponse.transfers[0].to.id).toBe(ethers.constants.AddressZero)
      expect(burnTransfersResponse.transfers[0].media.id).toBe('0')
    })
  })

  describe('#updateTokenURI', async () => {
    it('should correctly update state when token uri is updated', async () => {
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()
      // mint (transfer from 0x000 to address)
      await mint(creatorWallet, contentHash, metadataHash)
      let mediaResponse: MediaQueryResponse = await request(gqlURL, mediaByIdQuery('0'))
      let media = mediaResponse.media

      expect(media.id).toBe('0')
      expect(media.contentURI).toBe('example.com')

      await updateTokenURI(creatorWallet, BigNumber.from(0), 'content blah blah')

      mediaResponse = await request(gqlURL, mediaByIdQuery('0'))
      media = mediaResponse.media

      expect(media.id).toBe('0')
      expect(media.contentURI).toBe('content blah blah')

      let uriUpdateResponse: URIUpdatesQueryResponse = await request(
        gqlURL,
        uriUpdatesByMediaIdQuery('0')
      )
      let uriUpdates = uriUpdateResponse.uriupdates

      expect(uriUpdates.length).toBe(1)
      expect(uriUpdates[0].from).toBe('example.com')
      expect(uriUpdates[0].to).toBe('content blah blah')
      expect(uriUpdates[0].type).toBe('Content')
      expect(uriUpdates[0].owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(uriUpdates[0].updater.id).toBe(creatorWallet.address.toLowerCase())

      // approve then update
      await approve(creatorWallet, BigNumber.from(0), otherWallet.address)
      await updateTokenURI(otherWallet, BigNumber.from(0), 'other blah blah')

      let otherUriUpdateResponse: URIUpdatesQueryResponse = await request(
        gqlURL,
        uriUpdatesByUpdaterIdQuery(otherWallet.address.toLowerCase())
      )
      let otherUriUpdates = otherUriUpdateResponse.uriupdates

      expect(otherUriUpdates.length).toBe(1)
      expect(otherUriUpdates[0].from).toBe('content blah blah')
      expect(otherUriUpdates[0].to).toBe('other blah blah')
      expect(otherUriUpdates[0].type).toBe('Content')
      expect(otherUriUpdates[0].owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(otherUriUpdates[0].updater.id).toBe(otherWallet.address.toLowerCase())
    })
  })
  describe('#updateTokenMetadataURI', async () => {
    it('should correctly update state when metadata uri is updated', async () => {
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()
      await mint(creatorWallet, contentHash, metadataHash)
      let mediaResponse: MediaQueryResponse = await request(gqlURL, mediaByIdQuery('0'))
      let media = mediaResponse.media

      expect(media.id).toBe('0')
      expect(media.metadataURI).toBe('metadata.com')

      await updateTokenMetadataURI(creatorWallet, BigNumber.from(0), 'blah blah')

      mediaResponse = await request(gqlURL, mediaByIdQuery('0'))
      media = mediaResponse.media

      expect(media.id).toBe('0')
      expect(media.metadataURI).toBe('blah blah')

      let uriUpdateResponse: URIUpdatesQueryResponse = await request(
        gqlURL,
        uriUpdatesByMediaIdQuery('0')
      )
      let uriUpdates = uriUpdateResponse.uriupdates

      expect(uriUpdates.length).toBe(1)
      expect(uriUpdates[0].from).toBe('metadata.com')
      expect(uriUpdates[0].to).toBe('blah blah')
      expect(uriUpdates[0].type).toBe('Metadata')
      expect(uriUpdates[0].owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(uriUpdates[0].updater.id).toBe(creatorWallet.address.toLowerCase())

      // approve then update
      await approve(creatorWallet, BigNumber.from(0), otherWallet.address)
      await updateTokenMetadataURI(otherWallet, BigNumber.from(0), 'other blah blah')

      let otherUriUpdateResponse: URIUpdatesQueryResponse = await request(
        gqlURL,
        uriUpdatesByUpdaterIdQuery(otherWallet.address.toLowerCase())
      )
      let otherUriUpdates = otherUriUpdateResponse.uriupdates

      expect(otherUriUpdates.length).toBe(1)
      expect(otherUriUpdates[0].from).toBe('blah blah')
      expect(otherUriUpdates[0].to).toBe('other blah blah')
      expect(otherUriUpdates[0].type).toBe('Metadata')
      expect(otherUriUpdates[0].owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(otherUriUpdates[0].updater.id).toBe(otherWallet.address.toLowerCase())
    })
  })

  describe('#approve', async () => {
    it('it should correctly save the approval', async () => {
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()

      await mint(creatorWallet, contentHash, metadataHash)
      let mediaResponse = await request(gqlURL, mediaByIdQuery('0'))
      let media = mediaResponse.media

      expect(media.id).toBe('0')
      expect(media.approved).toBeNull()

      await approve(creatorWallet, BigNumber.from(0), otherWallet.address)
      mediaResponse = await request(gqlURL, mediaByIdQuery('0'))
      media = mediaResponse.media

      expect(media.id).toBe('0')
      expect(media.approved.id).toBe(otherWallet.address.toLowerCase())
    })
  })

  describe('#setApprovalForAll', async () => {
    it('should correctly save the approval for all', async () => {
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()

      await mint(creatorWallet, contentHash, metadataHash)

      let otherContentHash = await randomHashBytes()
      let otherMetadataHash = await randomHashBytes()

      await mint(otherWallet, otherContentHash, otherMetadataHash)

      await setApprovalForAll(creatorWallet, anotherWallet.address, true)

      // approval for new address
      let creatorUserResponse: UserQueryResponse = await request(
        gqlURL,
        userByIdQuery(creatorWallet.address.toLowerCase())
      )
      let creatorUser = creatorUserResponse.user
      expect(creatorUser.id).toBe(creatorWallet.address.toLowerCase())
      expect(creatorUser.authorizedUsers.length).toBe(1)
      expect(creatorUser.authorizedUsers[0].id).toBe(anotherWallet.address.toLowerCase())

      // approval for all for existing address
      await setApprovalForAll(creatorWallet, otherWallet.address, true)
      creatorUserResponse = await request(
        gqlURL,
        userByIdQuery(creatorWallet.address.toLowerCase())
      )
      creatorUser = creatorUserResponse.user
      expect(creatorUser.id).toBe(creatorWallet.address.toLowerCase())
      expect(creatorUser.authorizedUsers.length).toBe(2)
      expect(creatorUser.authorizedUsers[1].id).toBe(anotherWallet.address.toLowerCase())

      // approval for all revoked for existing address
      await setApprovalForAll(creatorWallet, otherWallet.address, false)
      creatorUserResponse = await request(
        gqlURL,
        userByIdQuery(creatorWallet.address.toLowerCase())
      )
      creatorUser = creatorUserResponse.user
      expect(creatorUser.id).toBe(creatorWallet.address.toLowerCase())
      expect(creatorUser.authorizedUsers.length).toBe(1)
      expect(creatorUser.authorizedUsers[0].id).toBe(anotherWallet.address.toLowerCase())

      // approval for all revoked for non existant address -- this might break stuff
    })
  })

  describe('#setAsk', async () => {
    // setAsk can only be emitted during a call to #setAsk.
    it('should save the proper state', async () => {
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()

      await mint(creatorWallet, contentHash, metadataHash)

      let onChainAsk = defaultAsk(currencyAddress)

      const tx = await setAsk(creatorWallet, 0, onChainAsk)
      const txReceipt = await provider.getTransactionReceipt(tx.hash)
      const block = await provider.getBlock(txReceipt.blockHash)

      let askId = '0'.concat('-').concat(creatorWallet.address.toLowerCase())

      // it creates an ask
      let askResponse: AskQueryResponse = await request(gqlURL, askByIdQuery(askId))
      let ask = askResponse.ask
      expect(ask.id).toBe(askId)
      expect(ask.owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(ask.currency.id).toBe(onChainAsk.currency.toLowerCase())
      expect(ask.amount).toBe(toNumWei(onChainAsk.amount).toString())
      expect(ask.createdAtTimestamp).toBe(block.timestamp.toString())
      expect(ask.createdAtBlockNumber).toBe(block.number.toString())

      // it creates a currency
      let currencyResponse: CurrencyQueryResponse = await request(
        gqlURL,
        currencyByIdQuery(currencyAddress.toLowerCase())
      )
      let currency = currencyResponse.currency

      expect(currency.id).toBe(currencyAddress.toLowerCase())
      expect(currency.liquidity).toBe('0')
      expect(currency.name).toBe(currencyName)
      expect(currency.decimals).toBe(currencyDecimals)
      expect(currency.symbol).toBe(currencySymbol)

      // create a duplicate ask
      const dupTx = await setAsk(creatorWallet, 0, onChainAsk)
      const dupTxReceipt = await provider.getTransactionReceipt(dupTx.hash)
      const dupBlock = await provider.getBlock(dupTxReceipt.blockHash)

      askResponse = await request(gqlURL, askByIdQuery(askId))
      ask = askResponse.ask
      expect(ask.id).toBe(askId)
      expect(ask.owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(ask.currency.id).toBe(onChainAsk.currency.toLowerCase())
      expect(ask.amount).toBe(toNumWei(onChainAsk.amount).toString())
      expect(ask.createdAtTimestamp).toBe(dupBlock.timestamp.toString())
      expect(ask.createdAtBlockNumber).toBe(dupBlock.number.toString())

      let inactiveAsksResponse: InactiveAsksQueryResponse = await request(
        gqlURL,
        inactiveAsksByMediaIdQuery('0')
      )
      let inactiveAsks = inactiveAsksResponse.inactiveAsks

      expect(inactiveAsks.length).toBe(1)
      expect(inactiveAsks[0].media.id).toBe('0')
      expect(inactiveAsks[0].amount).toBe(toNumWei(onChainAsk.amount).toString())
      expect(inactiveAsks[0].currency.id).toBe(onChainAsk.currency.toLowerCase())
      expect(inactiveAsks[0].owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(inactiveAsks[0].createdAtTimestamp).toBe(block.timestamp.toString())
      expect(inactiveAsks[0].createdAtBlockNumber).toBe(block.number.toString())
      expect(inactiveAsks[0].inactivatedAtTimestamp).toBe(dupBlock.timestamp.toString())
      expect(inactiveAsks[0].inactivatedAtBlockNumber).toBe(dupBlock.number.toString())
    })
  })

  describe('#removeAsk', async () => {
    // it can be called in removeAsk
    // it can be called during a transfer
    it('properly saves state', async () => {
      // mint + setAsk -> removeAsk removes ask and creates inActiveAsk
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()

      let onChainAsk = defaultAsk(currencyAddress)

      await mint(creatorWallet, contentHash, metadataHash)
      const setAskTx = await setAsk(creatorWallet, 0, onChainAsk)
      const setAskTxReceipt = await provider.getTransactionReceipt(setAskTx.hash)
      const setAskBlock = await provider.getBlock(setAskTxReceipt.blockHash)

      let askId = '0'.concat('-').concat(creatorWallet.address.toLowerCase())

      let askResponse: AskQueryResponse = await request(gqlURL, askByIdQuery(askId))
      expect(askResponse.ask).not.toBeNull()

      const removeAskTx = await removeAsk(creatorWallet, 0)
      const removeAskTxReceipt = await provider.getTransactionReceipt(removeAskTx.hash)
      const removeAskBlock = await provider.getBlock(removeAskTxReceipt.blockHash)

      askResponse = await request(gqlURL, askByIdQuery(askId))
      expect(askResponse.ask).toBeNull()

      let inactiveAsksResponse: InactiveAsksQueryResponse = await request(
        gqlURL,
        inactiveAsksByMediaIdQuery('0')
      )
      let inactiveAsks = inactiveAsksResponse.inactiveAsks

      expect(inactiveAsks.length).toBe(1)
      expect(inactiveAsks[0].media.id).toBe('0')
      expect(inactiveAsks[0].amount).toBe(toNumWei(onChainAsk.amount).toString())
      expect(inactiveAsks[0].currency.id).toBe(onChainAsk.currency.toLowerCase())
      expect(inactiveAsks[0].owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(inactiveAsks[0].createdAtTimestamp).toBe(setAskBlock.timestamp.toString())
      expect(inactiveAsks[0].createdAtBlockNumber).toBe(setAskBlock.number.toString())
      expect(inactiveAsks[0].inactivatedAtTimestamp).toBe(
        removeAskBlock.timestamp.toString()
      )
      expect(inactiveAsks[0].inactivatedAtBlockNumber).toBe(
        removeAskBlock.number.toString()
      )

      //setAsk with new user -> transfer removes ask and creates inActiveAsk
      await setAsk(creatorWallet, 0, onChainAsk)

      askResponse = await request(gqlURL, askByIdQuery(askId))
      expect(askResponse.ask).not.toBeNull()

      await transfer(creatorWallet, 0, otherWallet.address)

      askResponse = await request(gqlURL, askByIdQuery(askId))
      expect(askResponse.ask).toBeNull()

      inactiveAsksResponse = await request(gqlURL, inactiveAsksByMediaIdQuery('0'))
      inactiveAsks = inactiveAsksResponse.inactiveAsks
      expect(inactiveAsks.length).toBe(2)
    })
  })

  describe('#setBid', async () => {
    // it creates a bid
    it('properly saves state', async () => {
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()

      let onChainBid = defaultBid(
        currencyAddress,
        otherWallet.address,
        otherWallet.address,
        8,
        8
      )
      let onChainAsk = defaultAsk(currencyAddress)

      await mint(creatorWallet, contentHash, metadataHash)
      await setAsk(creatorWallet, 0, onChainAsk)

      const setBidTx = await setBid(otherWallet, 0, onChainBid)
      const setBidTxReceipt = await provider.getTransactionReceipt(setBidTx.hash)
      const setBidBlock = await provider.getBlock(setBidTxReceipt.blockHash)

      let currencyResponse: CurrencyQueryResponse = await request(
        gqlURL,
        currencyByIdQuery(currencyAddress.toLowerCase())
      )
      let currency = currencyResponse.currency

      expect(currency.id).toBe(currencyAddress.toLowerCase())
      expect(currency.liquidity).toBe(toNumWei(onChainBid.amount).toString())
      expect(currency.name).toBe(currencyName)
      expect(currency.decimals).toBe(currencyDecimals)
      expect(currency.symbol).toBe(currencySymbol)

      let bidId = '0'.concat('-').concat(otherWallet.address.toLowerCase())

      let bidResponse: BidQueryResponse = await request(gqlURL, bidByIdQuery(bidId))
      let bid = bidResponse.bid

      expect(bid).not.toBeNull()
      expect(bid.id).toBe(bidId)
      expect(bid.amount).toBe(toNumWei(onChainBid.amount).toString())
      expect(bid.currency.id).toBe(onChainBid.currency.toLowerCase())
      expect(bid.sellOnShare).toBe(toNumWei(onChainBid.sellOnShare.value).toString())
      expect(bid.createdAtBlockNumber).not.toBeNull()
      expect(bid.createdAtTimestamp).not.toBeNull()

      // when set bid is called again from same address with higher bid
      let higherOnChainBid = defaultBid(
        currencyAddress,
        otherWallet.address,
        otherWallet.address,
        9,
        9
      )

      const replaceBidTx = await setBid(otherWallet, 0, higherOnChainBid)
      const replaceBidTxReceipt = await provider.getTransactionReceipt(replaceBidTx.hash)
      const replaceBidBlock = await provider.getBlock(replaceBidTxReceipt.blockHash)

      bidResponse = await request(gqlURL, bidByIdQuery(bidId))
      bid = bidResponse.bid

      expect(bid).not.toBeNull()
      expect(bid.id).toBe(bidId)
      expect(bid.amount).toBe(toNumWei(higherOnChainBid.amount).toString())
      expect(bid.currency.id).toBe(higherOnChainBid.currency.toLowerCase())
      expect(bid.sellOnShare).toBe(
        toNumWei(higherOnChainBid.sellOnShare.value).toString()
      )
      expect(bid.createdAtBlockNumber).not.toBeNull()
      expect(bid.createdAtTimestamp).not.toBeNull()

      let inactiveBidsResponse: InactiveBidsQueryResponse = await request(
        gqlURL,
        inactiveBidsByMediaIdQuery('0')
      )
      let inactiveBids = inactiveBidsResponse.inactiveBids

      expect(inactiveBids.length).toBe(1)
      expect(inactiveBids[0].media.id).toBe('0')
      expect(inactiveBids[0].amount).toBe(toNumWei(onChainBid.amount).toString())
      expect(inactiveBids[0].currency.id).toBe(onChainBid.currency.toLowerCase())
      expect(inactiveBids[0].sellOnShare).toBe(
        toNumWei(onChainBid.sellOnShare.value).toString()
      )
      expect(inactiveBids[0].bidder.id).toBe(onChainBid.bidder.toLowerCase())
      expect(inactiveBids[0].recipient.id).toBe(onChainBid.recipient.toLowerCase())
      expect(inactiveBids[0].createdAtTimestamp).toBe(setBidBlock.timestamp.toString())
      expect(inactiveBids[0].createdAtBlockNumber).toBe(setBidBlock.number.toString())
      expect(inactiveBids[0].inactivatedAtTimestamp).toBe(
        replaceBidBlock.timestamp.toString()
      )
      expect(inactiveBids[0].inactivatedAtBlockNumber).toBe(
        replaceBidBlock.number.toString()
      )

      //when the bid is accepted it properly updates
      //the media, the bid, and creates an inactive bid
      let acceptedOnChainBid = defaultBid(
        currencyAddress,
        otherWallet.address,
        otherWallet.address,
        12,
        12
      )
      await setBid(otherWallet, 0, acceptedOnChainBid)
      // make sure bid no longer exists
      bidResponse = await request(gqlURL, bidByIdQuery(bidId))
      expect(bidResponse.bid).toBeNull()

      let mediaResponse: MediaQueryResponse = await request(gqlURL, mediaByIdQuery('0'))
      let media = mediaResponse.media

      expect(media).not.toBeNull()
      expect(media.id).toBe('0')
      expect(media.prevOwner.id).toBe(creatorWallet.address.toLowerCase())
      expect(media.owner.id).toBe(otherWallet.address.toLowerCase())
      expect(media.currentBids.length).toBe(0)
      expect(media.inactiveBids.length).toBe(3)
      expect(media.currentAsk).toBeNull()
      expect(media.approved).toBeNull()

      // it creates a transfer
      let transfersResponse: TransfersQueryResponse = await request(
        gqlURL,
        transfersByFromIdQuery(creatorWallet.address.toLowerCase())
      )
      expect(transfersResponse.transfers.length).toBe(1)
      expect(transfersResponse.transfers[0].from.id).toBe(
        creatorWallet.address.toLowerCase()
      )
      expect(transfersResponse.transfers[0].to.id).toBe(otherWallet.address.toLowerCase())
      expect(transfersResponse.transfers[0].media.id).toBe('0')

      // if a transfer happens, the prevOwner is not updated until after a bid is accepted.
      await transfer(otherWallet, 0, anotherWallet.address)

      // load media and ensure the prevOwner is still creatorWallet
      mediaResponse = await request(gqlURL, mediaByIdQuery('0'))
      media = mediaResponse.media
      expect(media.owner.id).toBe(anotherWallet.address.toLowerCase())
      expect(media.prevOwner.id).toBe(creatorWallet.address.toLowerCase())

      // set the ask
      await setAsk(anotherWallet, 0, onChainAsk)

      // set the bid with creator Wallet
      let anotherAcceptedOnChainBid = defaultBid(
        currencyAddress,
        creatorWallet.address,
        creatorWallet.address,
        12,
        12
      )
      await setBid(creatorWallet, 0, anotherAcceptedOnChainBid)

      // check if the prevOwner is not otherWallet
      mediaResponse = await request(gqlURL, mediaByIdQuery('0'))
      media = mediaResponse.media
      expect(media.owner.id).toBe(creatorWallet.address.toLowerCase())
      expect(media.prevOwner.id).toBe(anotherWallet.address.toLowerCase())

      transfersResponse = await request(
        gqlURL,
        transfersByFromIdQuery(anotherWallet.address.toLowerCase())
      )
      expect(transfersResponse.transfers.length).toBe(1)
      expect(transfersResponse.transfers[0].from.id).toBe(
        anotherWallet.address.toLowerCase()
      )
      expect(transfersResponse.transfers[0].to.id).toBe(
        creatorWallet.address.toLowerCase()
      )
      expect(transfersResponse.transfers[0].media.id).toBe('0')
    })
  })

  describe('#removeBid', async () => {
    it('should save state properly', async () => {
      // it removes a bid and creates an InactiveBid
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()

      let onChainBid = defaultBid(
        currencyAddress,
        otherWallet.address,
        otherWallet.address,
        8,
        8
      )

      await mint(creatorWallet, contentHash, metadataHash)
      const setTx = await setBid(otherWallet, 0, onChainBid)
      const setTxReceipt = await provider.getTransactionReceipt(setTx.hash)
      const setBlock = await provider.getBlock(setTxReceipt.blockHash)

      let currencyResponse: CurrencyQueryResponse = await request(
        gqlURL,
        currencyByIdQuery(currencyAddress.toLowerCase())
      )
      let currency = currencyResponse.currency
      expect(currency.id).toBe(currencyAddress.toLowerCase())
      expect(currency.liquidity).toBe(toNumWei(onChainBid.amount).toString())

      let bidId = '0'.concat('-').concat(otherWallet.address.toLowerCase())
      let bidResponse: BidQueryResponse = await request(gqlURL, bidByIdQuery(bidId))
      expect(bidResponse.bid).not.toBeNull()

      const removeTx = await removeBid(otherWallet, 0)
      const removeTxReceipt = await provider.getTransactionReceipt(removeTx.hash)
      const removeBlock = await provider.getBlock(removeTxReceipt.blockHash)

      bidResponse = await request(gqlURL, bidByIdQuery(bidId))
      expect(bidResponse.bid).toBeNull()

      let newCurrencyResponse: CurrencyQueryResponse = await request(
        gqlURL,
        currencyByIdQuery(currencyAddress.toLowerCase())
      )
      let newCurrency = newCurrencyResponse.currency
      expect(newCurrency.id).toBe(currencyAddress.toLowerCase())

      let expectedLiquidity = BigNumber.from(currency.liquidity.toString()).sub(
        onChainBid.amount
      )
      expect(BigNumber.from(newCurrency.liquidity.toString())).toMatchObject(
        expectedLiquidity
      )

      let inactiveBidsResponse: InactiveBidsQueryResponse = await request(
        gqlURL,
        inactiveBidsByMediaIdQuery('0')
      )
      let inactiveBids = inactiveBidsResponse.inactiveBids

      expect(inactiveBids.length).toBe(1)
      expect(inactiveBids[0].media.id).toBe('0')
      expect(inactiveBids[0].amount).toBe(toNumWei(onChainBid.amount).toString())
      expect(inactiveBids[0].currency.id).toBe(onChainBid.currency.toLowerCase())
      expect(inactiveBids[0].sellOnShare).toBe(
        toNumWei(onChainBid.sellOnShare.value).toString()
      )
      expect(inactiveBids[0].bidder.id).toBe(onChainBid.bidder.toLowerCase())
      expect(inactiveBids[0].recipient.id).toBe(onChainBid.recipient.toLowerCase())
      expect(inactiveBids[0].createdAtBlockNumber).toBe(
        setTxReceipt.blockNumber.toString()
      )
      expect(inactiveBids[0].createdAtTimestamp).toBe(setBlock.timestamp.toString())
      expect(inactiveBids[0].inactivatedAtTimestamp).toBe(
        removeBlock.timestamp.toString()
      )
      expect(inactiveBids[0].inactivatedAtBlockNumber).toBe(
        removeTxReceipt.blockNumber.toString()
      )
    })
  })

  describe('#acceptBid', async () => {
    it('should save the state properly', async () => {
      // it removes a bid and creates an InactiveBid
      contentHash = await randomHashBytes()
      metadataHash = await randomHashBytes()

      let onChainBid = defaultBid(
        currencyAddress,
        otherWallet.address,
        otherWallet.address,
        8,
        8
      )

      await mint(creatorWallet, contentHash, metadataHash)
      const setBidTx = await setBid(otherWallet, 0, onChainBid)
      const setBidTxReceipt = await provider.getTransactionReceipt(setBidTx.hash)
      const setBidBlock = await provider.getBlock(setBidTxReceipt.blockHash)

      let bidId = '0'.concat('-').concat(otherWallet.address.toLowerCase())
      let bidResponse: BidQueryResponse = await request(gqlURL, bidByIdQuery(bidId))
      expect(bidResponse.bid).not.toBeNull()

      let currencyResponse: CurrencyQueryResponse = await request(
        gqlURL,
        currencyByIdQuery(currencyAddress.toLowerCase())
      )
      let currency = currencyResponse.currency
      expect(currency.id).toBe(currencyAddress.toLowerCase())
      expect(currency.liquidity).toBe(toNumWei(onChainBid.amount).toString())

      const acceptBidTx = await acceptBid(creatorWallet, 0, onChainBid)
      const acceptBidTxReceipt = await provider.getTransactionReceipt(acceptBidTx.hash)
      const acceptBidBlock = await provider.getBlock(acceptBidTxReceipt.blockHash)

      bidResponse = await request(gqlURL, bidByIdQuery(bidId))
      expect(bidResponse.bid).toBeNull()

      let inactiveBidsResponse: InactiveBidsQueryResponse = await request(
        gqlURL,
        inactiveBidsByMediaIdQuery('0')
      )
      let inactiveBids = inactiveBidsResponse.inactiveBids

      expect(inactiveBids.length).toBe(1)
      expect(inactiveBids[0].media.id).toBe('0')
      expect(inactiveBids[0].amount).toBe(toNumWei(onChainBid.amount).toString())
      expect(inactiveBids[0].currency.id).toBe(onChainBid.currency.toLowerCase())
      expect(inactiveBids[0].sellOnShare).toBe(
        toNumWei(onChainBid.sellOnShare.value).toString()
      )
      expect(inactiveBids[0].bidder.id).toBe(onChainBid.bidder.toLowerCase())
      expect(inactiveBids[0].recipient.id).toBe(onChainBid.recipient.toLowerCase())
      expect(inactiveBids[0].createdAtBlockNumber).toBe(
        setBidTxReceipt.blockNumber.toString()
      )
      expect(inactiveBids[0].createdAtTimestamp).toBe(setBidBlock.timestamp.toString())
      expect(inactiveBids[0].inactivatedAtTimestamp).toBe(
        acceptBidBlock.timestamp.toString()
      )
      expect(inactiveBids[0].inactivatedAtBlockNumber).toBe(
        acceptBidTxReceipt.blockNumber.toString()
      )

      let mediaResponse: MediaQueryResponse = await request(gqlURL, mediaByIdQuery('0'))
      let media = mediaResponse.media

      expect(media).not.toBeNull()
      expect(media.id).toBe('0')
      expect(media.prevOwner.id).toBe(creatorWallet.address.toLowerCase())
      expect(media.owner.id).toBe(otherWallet.address.toLowerCase())
      expect(media.currentBids.length).toBe(0)
      expect(media.inactiveBids.length).toBe(1)
      expect(media.currentAsk).toBeNull()
      expect(media.approved).toBeNull()

      let newCurrencyResponse: CurrencyQueryResponse = await request(
        gqlURL,
        currencyByIdQuery(currencyAddress.toLowerCase())
      )
      let newCurrency = newCurrencyResponse.currency
      expect(newCurrency.id).toBe(currencyAddress.toLowerCase())

      let expectedLiquidity = BigNumber.from(currency.liquidity.toString()).sub(
        onChainBid.amount
      )
      expect(BigNumber.from(newCurrency.liquidity.toString())).toMatchObject(
        expectedLiquidity
      )
    })
  })
})