thousandetherhomepage / ketherhomepage

Own a piece of blockchain history!
https://thousandetherhomepage.com
MIT License
144 stars 38 forks source link

ALERT: Transaction Error. Exception thrown in contract code. Metamask while interacting with contract #54

Closed jesterxd closed 3 years ago

jesterxd commented 3 years ago

When I try to interact with the contract via Metamask browser extension, I get the warning "ALERT: Transaction Error. Exception thrown in contract code." when I attempt to buy. The code works, and the site is fine, it's just that when I attempt to transact, I get the error, and the transaction doesn't go through. It looks like this (not my image, but that is how the error looks). I have opened an issue in the MetaMask repo, asked questions on the community forum, ethereum.stackexchange.com, and reddit r/ethdev, but have received no answers. I also reinstalled and updated MetaMask on Chrome, but it didn't help.

I tried debugging with Remix, but it only highlighted that certain parts of the code were deprecated, nothing else. I was also able to successfully deploy the contract to Rinkeby via Remix (for the 2nd time, I first deployed via command line). My command line deployment steps were not the ones in the readme. I just installed truffle via npm , edited truffle.js, and ran truffle deploy --network rinkeby. New truffle.js:

var HDWalletProvider = require("truffle-hdwallet-provider");
var mnemonic = "";

module.exports = {
  networks: {
//     test: {
  //    provider: TestRPC.provider(), // in-memory TestRPC provider
//      network_id: "*" // Match any network id
//    },
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*" // Match any network id
    },
      rinkeby: {
      provider: function() { 
       return new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/");
      },
      network_id: 4,
//      gas: 4500000,
      gasPrice: 21000000000,
    },
    live: { // to be edited before production
      host: "127.0.0.1",
      port: 8545,
      network_id: "1", // Only mainnet
      gasPrice: "10000000000", // 10 gwei
      gas: "5000000", // 0.02 eth at 4 gwei price
    },  
  },
  compilers: {
    solc: {
      version: "0.4.15",

    },
  },
};

.sol files are the same as on GitHub. App.vue:

import Web3 from 'web3'
import contractJSON from 'json-loader!../build/contracts/KetherHomepage.json'
const deployConfig = {
  "rinkeby": {
    contractAddr: 'e',
    web3Fallback: 'https://rinkeby.infura.io/v3/',
    etherscanLink: 'https://rinkeby.etherscan.io/address/',
    prerendered: {
      image: '',
      data: '',
      loadRemoteImages: true,
      loadFromWeb3: true,
    },
  },
  "main": {
    contractAddr: '',
    web3Fallback: '',
    etherscanLink: '',
    prerendered: {
      image: '',
      data: '',
      loadRemoteImages: true,
      loadFromWeb3: true,
    },
  }
}
const web3Networks = [
  undefined, 'main', undefined, undefined, 'rinkeby',
];
const defaultNetwork = 'main';
import Dropdown from './Dropdown.vue'
import Homepage from './Homepage.vue'
function waitForWeb3(options, cb) {
  const web3Fallback = options.web3Fallback || "http://localhost:8545/";
  function getWeb3() {
    window.Web3 = Web3;
    let web3 = window.ethereum;
    if (typeof web3 !== 'undefined') {
      // we're using a wallet browser
      window.ethereum.enable()
      web3 = new Web3(window.ethereum)
    } else {
      // we're using a fallback
      //web3 = new Web3(Web3.givenProvider || web3Fallback);
      web3 = new Web3(new Web3.providers.HttpProvider(web3Fallback));
    }
    return web3;
    /*
    try {
      if (web3.currentProvider.connected) return web3;
    } catch (_) {
      return null;
    }
    */
  }
  function startWaiting() {
    const interval = setInterval(function() {
      let r = getWeb3()
      if (r) {
        clearInterval(interval)
        cb(r);
      }
    }, 1000);
  }
  if (window.web3Loading === true) {
    // Can't do on window load too late.
    startWaiting();
    return;
  }
  window.addEventListener('load', function() {
    window.web3Loading = true;
    startWaiting();
  });
}
export default {
  name: 'app',
  data() {
    return {
      'availableNetworks': deployConfig,
      'activeNetwork': null,
      'networkConfig': {},
      'selecting': false,
      'web3': null,
      'contract': null,
      'ready': false,
      'isReadOnly': false,
      'showNSFW': false,
      'prerendered': null,
    }
  },
  methods: {
    setNetwork(network) {
      if (this.activeNetwork === network) return;
      this.activeNetwork = network;
      this.ready = false;
      waitForWeb3(deployConfig[network || defaultNetwork], function(web3) {
        // VueJS tries to inspect/walk/observe objects unless they're frozen. This breaks web3.
        this.web3 = Object.freeze(web3);
        this.web3.eth.net.getNetworkType(function(error, networkVersion) {
          if (error) throw error;
          if (this.activeNetwork === undefined) {
            this.activeNetwork = networkVersion;
          }
          const providerHost = this.web3.currentProvider.host
          this.isReadOnly = providerHost && providerHost.indexOf('infura.io') !== -1;
          if (this.activeNetwork === undefined) {
            this.isReadOnly = false;
            return;
          }
          // Load contract data
          const options = deployConfig[this.activeNetwork];
          this.networkConfig = options;
          const contractAt = new this.web3.eth.Contract(contractJSON.abi, options.contractAddr);
          contractAt._network = this.activeNetwork;
          this.contract = Object.freeze(contractAt);
          this.ready = true;
          this.prerendered = options.prerendered;
          if (web3.currentProvider.isMetaMask) {
            // Poll for network changes, because MetaMask no longer reloads
            const app = this;
            const interval = setInterval(function() {
              web3.eth.net.getNetworkType(function(error, newNetworkVersion) {
                if (error || newNetworkVersion !== networkVersion) {
                  clearInterval(interval)
                  app.setNetwork();
                }
              }.bind(this));
            }, 2000);
          }
        }.bind(this))
      }.bind(this));
    },
  },
  created() {
    this.setNetwork();
  },
  components: {
    'Homepage': Homepage,
    'Dropdown': Dropdown,
  }
}

Buy.vue:

const ethPerPixel = 1000 / 1000000;
export default {
  props: ["web3", "contract", "isReadOnly"],
  data() {
    ga('send', {
      hitType: 'event',
      eventCategory: this.contract._network,
      eventAction: 'buy-open',
    });
    return {
      error: null,
      success: null,
      available: false,
    }
  },
  computed: {
    isAvailable: function() {
      return this.checkAvailable(this.$parent.left, this.$parent.top, this.$parent.width, this.$parent.height, this.$store.state.ads);
    }
  },
  methods: {
    price(height, width) {
      // Round up to the nearest 0.01
      // TODO: BigNumber?
      return Math.ceil(height * width * ethPerPixel * 100) / 100;
    },
    checkAccounts() {
      this.web3.eth.getAccounts(function(err, res) {
        for (const acct of res) {
          this.$store.commit('addAccount', acct)
        }
      }.bind(this));
    },
    checkAvailable(x, y, width, height) {
      const x1 = Math.floor(x/10);
      const y1 = Math.floor(y/10);
      const x2 = x1 + Math.floor(width/10) - 1;
      const y2 = y1 + Math.floor(height/10) - 1;
      return !this.$store.getters.isColliding(x1, y1, x2, y2);
    },
    buy() {
      const ad = {x: this.$parent.left, y: this.$parent.top, width: this.$parent.width, height: this.$parent.height}
      if (!this.checkAvailable(ad.x, ad.y, ad.width, ad.height)) {
        this.error = `Slot is not available: ${ad}`
        return;
      }
      const weiPrice = this.web3.utils.toWei(this.price(ad.width, ad.height).toString(), "ether");
      const x = Math.floor(ad.x/10), y = Math.floor(ad.y/10), width = Math.floor(ad.width/10), height = Math.floor(ad.height/10);
      const account = this.$store.state.activeAccount;
      ga('send', {
        hitType: 'event',
        eventCategory: this.contract._network,
        eventAction: 'buy-submit',
        eventValue: weiPrice,
        eventLabel: ad.width + "x" + ad.height,
      });
      this.contract.methods.buy(x, y, width, height).send({ value: weiPrice, from: account }, function(err, res) {
        if (err) {
          ga('send', {
            hitType: 'event',
            eventCategory: this.contract._network,
            eventAction: 'buy-error',
            eventLabel: JSON.stringify(err),
          });
          if ((err.stack || err.message || "").indexOf('User denied transaction signature.') !== -1)  {
            // Aborted, revert to original state.
            return;
          }
          this.error = err.toString();
          return;
        }
        this.success = 'Transaction sent successfully.'
        this.$emit("buy", {x, y, width, height})
        ga('send', {
          hitType: 'event',
          eventCategory: this.contract._network,
          eventAction: 'buy-success',
          eventValue: weiPrice,
          eventLabel: ad.width + "x" + ad.height,
        });
        // TODO: Transition to Publish route?
      }.bind(this));
    }
  },
}

What could be causing this? Thanks.

shazow commented 3 years ago

@jesterxd Heya, we don't offer support for your own deployments of this app as a rule.

But what you described is what happens when a contract reverts -- that is, it reaches some state that fails a check.

It really depends on how you're "interacting" with it. Are you trying to buy an ad slot that is already purchased, for example?

You also don't need to use the vue dapp to interact with the contract, you can also use etherscan for example. Or write your own code.

One final suggestion: I rewrote the original contract using more modern solidity (since the deployed version is quite old). It has better error messages and might help you if you're trying to build something on top of it. It's in this branch, but it will be merged eventually: https://github.com/thousandetherhomepage/ketherhomepage/blob/nft-wrap/contracts/KetherHomepageV2.sol

Good luck!