ethers/lib/utils#sha256 TypeScript Examples

The following examples show how to use ethers/lib/utils#sha256. 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: hashToField.ts    From hubble-contracts with MIT License 5 votes vote down vote up
export function expandMsg(
    domain: Uint8Array,
    msg: Uint8Array,
    outLen: number
): Uint8Array {
    if (domain.length > 32) {
        throw new Error("bad domain size");
    }

    const out: Uint8Array = new Uint8Array(outLen);

    const len0 = 64 + msg.length + 2 + 1 + domain.length + 1;
    const in0: Uint8Array = new Uint8Array(len0);
    // zero pad
    let off = 64;
    // msg
    in0.set(msg, off);
    off += msg.length;
    // l_i_b_str
    in0.set([(outLen >> 8) & 0xff, outLen & 0xff], off);
    off += 2;
    // I2OSP(0, 1)
    in0.set([0], off);
    off += 1;
    // DST_prime
    in0.set(domain, off);
    off += domain.length;
    in0.set([domain.length], off);

    const b0 = sha256(in0);

    const len1 = 32 + 1 + domain.length + 1;
    const in1: Uint8Array = new Uint8Array(len1);
    // b0
    in1.set(arrayify(b0), 0);
    off = 32;
    // I2OSP(1, 1)
    in1.set([1], off);
    off += 1;
    // DST_prime
    in1.set(domain, off);
    off += domain.length;
    in1.set([domain.length], off);

    const b1 = sha256(in1);

    // b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime);
    const ell = Math.floor((outLen + 32 - 1) / 32);
    let bi = b1;

    for (let i = 1; i < ell; i++) {
        const ini: Uint8Array = new Uint8Array(32 + 1 + domain.length + 1);
        const nb0 = zeroPad(arrayify(b0), 32);
        const nbi = zeroPad(arrayify(bi), 32);
        const tmp = new Uint8Array(32);
        for (let i = 0; i < 32; i++) {
            tmp[i] = nb0[i] ^ nbi[i];
        }

        ini.set(tmp, 0);
        let off = 32;
        ini.set([1 + i], off);
        off += 1;
        ini.set(domain, off);
        off += domain.length;
        ini.set([domain.length], off);

        out.set(arrayify(bi), 32 * (i - 1));
        bi = sha256(ini);
    }

    out.set(arrayify(bi), 32 * (ell - 1));
    return out;
}
Example #2
Source File: setup-para-tests.ts    From moonbeam with GNU General Public License v3.0 4 votes vote down vote up
export function describeParachain(
  title: string,
  options: ParaTestOptions,
  cb: (context: InternalParaTestContext) => void
) {
  describe(title, function () {
    // Set timeout to 5000 for all tests.
    this.timeout(300000);

    // The context is initialized empty to allow passing a reference
    // and to be filled once the node information is retrieved
    let context: InternalParaTestContext = {} as InternalParaTestContext;

    // Making sure the Moonbeam node has started
    before("Starting Moonbeam Test Node", async function () {
      this.timeout(300000);
      try {
        const init = !DEBUG_MODE
          ? await startParachainNodes(options)
          : {
              paraPorts: [
                {
                  parachainId: 1000,
                  ports: [
                    {
                      p2pPort: 19931,
                      wsPort: 19933,
                      rpcPort: 19932,
                    },
                  ],
                },
              ],
              relayPorts: [],
            };
        // Context is given prior to this assignement, so doing
        // context = init.context will fail because it replace the variable;

        context._polkadotApiParachains = [];
        context._polkadotApiRelaychains = [];
        context._web3Providers = [];

        context.createWeb3 = async (protocol: "ws" | "http" = "http") => {
          const provider =
            protocol == "ws"
              ? await provideWeb3Api(init.paraPorts[0].ports[0].wsPort, "ws")
              : await provideWeb3Api(init.paraPorts[0].ports[0].rpcPort, "http");
          context._web3Providers.push((provider as any)._provider);
          return provider;
        };
        context.createEthers = async () => provideEthersApi(init.paraPorts[0].ports[0].rpcPort);
        context.createPolkadotApiParachain = async (parachainNumber: number) => {
          const promise = providePolkadotApi(init.paraPorts[parachainNumber].ports[0].wsPort);
          context._polkadotApiParachains.push({
            parachainId: init.paraPorts[parachainNumber].parachainId,
            apis: [await promise],
          });
          return promise;
        };
        context.createPolkadotApiParachains = async () => {
          const apiPromises = await Promise.all(
            init.paraPorts.map(async (parachain: ParachainPorts) => {
              return {
                parachainId: parachain.parachainId,
                apis: await Promise.all(
                  parachain.ports.map(async (ports: NodePorts) => {
                    return providePolkadotApi(ports.wsPort);
                  })
                ),
              };
            })
          );
          // We keep track of the polkadotApis to close them at the end of the test
          context._polkadotApiParachains = apiPromises;
          await Promise.all(
            apiPromises.map(async (promises) =>
              Promise.all(promises.apis.map((promise) => promise.isReady))
            )
          );
          // Necessary hack to allow polkadotApi to finish its internal metadata loading
          // apiPromise.isReady unfortunately doesn't wait for those properly
          await new Promise((resolve) => {
            setTimeout(resolve, 100);
          });

          return apiPromises[0].apis[0];
        };
        context.createPolkadotApiRelaychains = async () => {
          const apiPromises = await Promise.all(
            init.relayPorts.map(async (ports: NodePorts) => {
              return await providePolkadotApi(ports.wsPort, true);
            })
          );
          // We keep track of the polkadotApis to close them at the end of the test
          context._polkadotApiRelaychains = apiPromises;
          await Promise.all(apiPromises.map((promise) => promise.isReady));
          // Necessary hack to allow polkadotApi to finish its internal metadata loading
          // apiPromise.isReady unfortunately doesn't wait for those properly
          await new Promise((resolve) => {
            setTimeout(resolve, 100);
          });

          return apiPromises[0];
        };

        let pendingPromises = [];
        const subBlocks = async (api) => {
          return api.rpc.chain.subscribeNewHeads(async (header) => {
            context.blockNumber = header.number.toNumber();
            if (context.blockNumber == 0) {
              console.log(
                `Start listening for new blocks. Production will start in ${chalk.red(`1 minute`)}`
              );
            }

            let i = pendingPromises.length;
            while (i--) {
              const pendingPromise = pendingPromises[i];
              if (pendingPromise.blockNumber <= context.blockNumber) {
                pendingPromises.splice(i, 1);
                pendingPromise.resolve(context.blockNumber);
              }
            }
          });
        };

        context.polkadotApiParaone = await context.createPolkadotApiParachains();
        subBlocks(context.polkadotApiParaone);

        context.waitBlocks = async (count: number) => {
          return new Promise<number>((resolve) => {
            pendingPromises.push({
              blockNumber: context.blockNumber + count,
              resolve,
            });
          });
        };

        context.upgradeRuntime = async (
          from: KeyringPair,
          runtimeName: "moonbase" | "moonriver" | "moonbeam",
          runtimeVersion: string,
          waitMigration: boolean = true
        ) => {
          return new Promise<number>(async (resolve, reject) => {
            try {
              const code = fs
                .readFileSync(await getRuntimeWasm(runtimeName, runtimeVersion))
                .toString();

              const existingCode = await context.polkadotApiParaone.rpc.state.getStorage(":code");
              if (existingCode.toString() == code) {
                reject(
                  `Runtime upgrade with same code: ${existingCode.toString().slice(0, 20)} vs ${code
                    .toString()
                    .slice(0, 20)}`
                );
              }

              let nonce = (
                await context.polkadotApiParaone.rpc.system.accountNextIndex(from.address)
              ).toNumber();

              process.stdout.write(
                `Sending sudo.setCode (${sha256(Buffer.from(code))} [~${Math.floor(
                  code.length / 1024
                )} kb])...`
              );
              const unsubSetCode = await context.polkadotApiParaone.tx.sudo
                .sudoUncheckedWeight(
                  await context.polkadotApiParaone.tx.system.setCodeWithoutChecks(code),
                  1
                )
                .signAndSend(from, { nonce: nonce++ }, async (result) => {
                  if (result.isInBlock) {
                    unsubSetCode();
                    // ==== This is not supported anymore :/ ===
                    // if (runtimeVersion == "local") {
                    //   // This is a trick. We set the lastRuntimeUpgrade version to a number lower
                    //   // at the block right before it gets applied, otherwise it gets reverted to
                    //   // the original version (not sure why).
                    //   // This is require when developping and the runtime version hasn't been
                    //   // increased. As using the same runtime version prevents the migration
                    //   // to happen
                    //   await context.waitBlocks(2);

                    //   const lastRuntimeUpgrade =
                    //     (await context.polkadotApiParaone.query.system.lastRuntimeUpgrade())
                    //     as any;
                    //   process.stdout.write(
                    //     `Overriding on-chain current runtime ${lastRuntimeUpgrade
                    //       .unwrap()
                    //       .specVersion.toNumber()} to ${
                    //       lastRuntimeUpgrade.unwrap().specVersion.toNumber() - 1
                    //     }`
                    //   );
                    //   context.polkadotApiParaone.tx.sudo
                    //     .sudo(
                    //       await context.polkadotApiParaone.tx.system.setStorage([
                    //         [
                    //           context.polkadotApiParaone.query.system.lastRuntimeUpgrade.key(),
                    //           `0x${Buffer.from(
                    //             context.polkadotApiParaone.registry
                    //               .createType(
                    //                 "Compact<u32>",
                    //                 lastRuntimeUpgrade.unwrap().specVersion.toNumber() - 2
                    //               )
                    //               .toU8a()
                    //           ).toString("hex")}${lastRuntimeUpgrade.toHex().slice(6)}`,
                    //         ],
                    //       ])
                    //     )
                    //     .signAndSend(from, { nonce: nonce++ });
                    //   process.stdout.write(`✅\n`);
                    // }
                  }
                });
              process.stdout.write(`✅\n`);

              process.stdout.write(`Waiting to apply new runtime (${chalk.red(`~4min`)})...`);
              let isInitialVersion = true;
              const unsub = await context.polkadotApiParaone.rpc.state.subscribeRuntimeVersion(
                async (version) => {
                  if (!isInitialVersion) {
                    const blockNumber = context.blockNumber;
                    console.log(
                      `✅ [${version.implName}-${version.specVersion} ${existingCode
                        .toString()
                        .slice(0, 6)}...] [#${blockNumber}]`
                    );
                    unsub();
                    const newCode = await context.polkadotApiParaone.rpc.state.getStorage(":code");
                    if (newCode.toString() != code) {
                      reject(
                        `Unexpected new code: ${newCode.toString().slice(0, 20)} vs ${code
                          .toString()
                          .slice(0, 20)}`
                      );
                    }
                    if (waitMigration) {
                      // Wait for next block to have the new runtime applied
                      await context.waitBlocks(1);
                    }
                    resolve(blockNumber);
                  }
                  isInitialVersion = false;
                }
              );
            } catch (e) {
              console.error(`Failed to setCode`);
              reject(e);
            }
          });
        };
        context.web3 = await context.createWeb3();
        context.ethers = await context.createEthers();
        debug(
          `Setup ready [${/:([0-9]+)$/.exec((context.web3.currentProvider as any).host)[1]}] for ${
            this.currentTest.title
          }`
        );
      } catch (e) {
        console.error(`Failed to start nodes !!!`);
        console.error(e);
        process.exit(1);
      }
    });

    after(async function () {
      await Promise.all(context._web3Providers.map((p) => p.disconnect()));
      await Promise.all(
        context._polkadotApiParachains.map(
          async (ps) => await Promise.all(ps.apis.map((p) => p.disconnect()))
        )
      );
      await Promise.all(context._polkadotApiRelaychains.map((p) => p.disconnect()));

      if (!DEBUG_MODE) {
        await stopParachainNodes();
        await new Promise((resolve) => {
          // TODO: Replace Sleep by actually checking the process has ended
          setTimeout(resolve, 1000);
        });
      }
    });

    cb(context);
  });
}
Example #3
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 #4
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
    }
  }
}