Context
After adopting a waffle example I'm experiencing some difficulties in reading out the balances of the contracts that are made using a unit test in Waffle.
Test file
import {expect, use} from 'chai';
import {Contract, utils, Wallet} from 'ethers';
import {deployContract, deployMockContract, MockProvider, solidity} from 'ethereum-waffle';
import IERC20 from '../build/IERC20.json';
import AmIRichAlready from '../build/AmIRichAlready.json';
import SolveContract from '../build/SolveContract.json';
import RandomNumberConsumer from '../build/RandomNumberConsumer.json';
use(solidity);
describe('Am I Rich Already', () => {
// Declare contracts
let mockERC20: Contract;
let askRootContract: Contract;
let solveRootContract: Contract;
let vrfContract: Contract;
// Declare wallets
let mockWallet: Wallet;
let askRootWallet: Wallet;
let solveRootWallet: Wallet;
let vrfWallet: Wallet;
beforeEach(async () => {
// generate random wallets or random origin
//const [mockWallet, askRootWallet, solveRootWallet, vrfWallet] = Wallet.createRandom();
//const original = Wallet.createRandom();
// specify wallet balances
const provider = new MockProvider(
{
ganacheOptions: {
// The private key is used to generate the four respective wallet addresses.
accounts: [
{balance: '16862680000000000001', secretKey: '0x706618637b8ca922f6290ce1ecd4c31247e9ab75cf0530a0ac95c0332173d7c1'},
{balance: '16862680000000000002', secretKey: '0x706618637b8ca922f6290ce1ecd4c31247e9ab75cf0530a0ac95c0332173d7c2'},
{balance: '16862680000000000003', secretKey: '0x706618637b8ca922f6290ce1ecd4c31247e9ab75cf0530a0ac95c0332173d7c3'},
{balance: '16862680000000000004', secretKey: '0x706618637b8ca922f6290ce1ecd4c31247e9ab75cf0530a0ac95c0332173d7c4'}
]
}
}
);
[mockWallet, askRootWallet, solveRootWallet, vrfWallet] = provider.getWallets();
mockERC20 = await deployMockContract(mockWallet, IERC20.abi);
askRootContract = await deployContract(askRootWallet, AmIRichAlready, [mockERC20.address]);
solveRootContract = await deployContract(solveRootWallet, SolveContract, [mockERC20.address]);
vrfContract = await deployContract(vrfWallet, RandomNumberConsumer);
});
// custom test in AskRoot contract
it('checks askRootContract address is returned correctly', async () => {
expect(await askRootContract.getAddressThis()).to.be.equal('0x82A666453d8aa239eEBE4578E83cD0988D62c83F');
});
// custom test in AskRoot contract
it('checks askRootWallet address balance is returned correctly', async () => {
expect(await askRootContract.getAddressThisBalance()).to.be.equal(9001);
});
});
Example contract
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
// Example contract of a TestContract.
contract SolveContract {
bool forTestingPurposes; // Boolean to run test on this contract
TemplateTestContract testContract; // Create variable for the testContract which needs to be solved.
address payable owner; // Create variable for the owner which solves the test contract.
// Constructor to initialise the contract variables.
constructor(address testAddress) public payable {
testContract = TemplateTestContract(testAddress); // Initialise the testContract variable.
owner = msg.sender; // Initialise the owner of the contract to be the creator of the contract.
}
// Function to solve the testContract.
function solve() public payable returns(uint256){
testContract.differentFunctionName(owner);
return owner.balance;
}
// Example of the main function which solves the testContract.
// Calculates the squre root function.
function main(uint x) pure public returns(uint y) {
uint z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
// Getter function for the Ownership.
function getOwner() public view returns (address) {
return owner;
}
// Getter function for the address(this).
function getAddressThis() public view returns (address) {
return address(this);
}
// Getter function for the balance of the contract.
function getBalance() public view returns (uint) {
//return address(this).balance;
//testAddress.balance;
return owner.balance;
}
// Getter function for the forTestingPurposes boolean.
function getForTestingPurposes() public view returns (bool){
return forTestingPurposes;
}
}
// TemplateTestContract so the SolveContract knows the structure of the testContract.
abstract contract TemplateTestContract {
function differentFunctionName(address payable hunter) public virtual;
}
Test Output
1) checks askRootWallet address balance is returned correctly
AssertionError: Expected "0" to be equal 9001
at Context.it (test/AmIRichAlready.test.ts:53:76)
at process._tickCallback (internal/process/next_tick.js:68:7)
Question
How can I (set) and read out the balance of the wallet address of a particular contract in a Waffle test?
Your test script is calling function getAddressThisBalance(), but this function is not defined in the contract.
Different JSON-RPC wrappers act differently when performing calls (read-only, without transaction) to non-existing contract functions. Some return undefined, some throw an exception, and it seems that Waffle returns a value that can be typecasted to 0.
Solution:
Unify the contract function getBalance() and the JS snippet calling getAddressThisBalance(). E.g. change the JS call to getBalance().
Uncomment the line return address(this).balance; in your getBalance() contract function. This expression returns the current balance of the contract.
Related
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "#openzeppelin/contracts/token/ERC1155/ERC1155.sol";
abstract contract ERC1155OpenSeaGassLess is ERC1155 {
address public _openSeaProxyRegistryAddress;
constructor(address openSeaProxyRegistryAddress_) {
_openSeaProxyRegistryAddress = openSeaProxyRegistryAddress_;
}
function _setOpenSeaProxyRegistryAddress(address openSeaProxyRegistryAddress_) internal virtual {
_openSeaProxyRegistryAddress = openSeaProxyRegistryAddress_;
}
function isApprovedForAll(address _owner, address operator) public view virtual override returns (bool) {
if (_openSeaProxyRegistryAddress != address(0)) {
**OpenSeaProxyRegistry proxyRegistry = OpenSeaProxyRegistry(_openSeaProxyRegistryAddress);
if (address(proxyRegistry.proxies(_owner)) == operator)** {
return true;
}
}
return super.isApprovedForAll(_owner, operator);
}
}
contract OwnableDelegateProxy {}
contract OpenSeaProxyRegistry {
mapping(address => OwnableDelegateProxy) public proxies;
}
in the above code can someone explain me a few things
1.
OpenSeaProxyRegistry proxyRegistry = OpenSeaProxyRegistry(_openSeaProxyRegistryAddress);
if (address(proxyRegistry.proxies(_owner)) == operator)
what this part is trying to do...
2.
contract OwnableDelegateProxy {}
what does this contract do?
3.
contract OpenSeaProxyRegistry {mapping(address => OwnableDelegateProxy) public proxies;}
what does this contract do?
will be highly thankful if someone explains the full contract flow
I have multiple cloud functions for my contracts and I am trying to create some unit tests using sinon. However, I have issues when trying to simulate some of the cloud of the functions. I want just to stub them and inside the methods I have called some properties of the smart contracts and want it to return a specified value. In order to understand what am I doing, here is a code snippet of the contract (partial):
contract RoundOracle is Ownable, AccessControl, Pausable {
uint8 public round; // Starts from 1. Updated after each round has finished.
uint8 public constant START = 0;
uint8 public constant END = 1;
event LogSetRoundBounds(uint8 round, uint256 startRound, uint256 endRound);
constructor() {
round = 1;
}
}
Then I want to stub this property round and assert that its value is equal to 2, it gives me the aforementioned error. Here is the code:
describe("NFT Rounds Test", () => {
context("NFT Rounds positive testing", () => {
before(function () {
initializeENV();
});
after(function () {
sandbox.restore();
});
it.only("Test the web3 calls", async() => {
console.log("hit b4 web3");
var web = Web3.getInstance();
console.log("hit after web3")
var newSandbox = sinon.createSandbox();
newSandbox.stub(web.roundOracle, "round").value(2);
expect(web.roundOracle.round).to.equal(2, "not equal to 2");
console.log("finally passed this stubbing ---");
});
});
});
I understand that it is because the property is non-writable, but is it possible somehow to test such scenarios even if they are not writable or configurable?
I figured out how can I make a workaround. First, I removed the objects (cleared them) and then defined new properties and when mocking then, just return the value that is desired.
// remove the desired objects (properties/functions)
removeRef(name, object) {
for (let i = 0; i < this[name].length; i++) {
delete object[this[name][i]];
}
for (let i = 0; i < this[name].length; i++) {
object[this[name][i]] = {};
}
}
// setting the function callback and the properties that I want to be true
mockFunction(o, k, cb) {
Object.defineProperty(o, k, {
enumerable: true,
value: cb,
writable: true,
configurable: true,
});
}
Afterwards, I am just calling the removeRef in the before() hook and then in the test functions calling it like this:
prerequisites.mockFunction(<object-instance>, "<method/property-name>", () => "hash");
the object-instance = the class where the properties and the functions are declared.
I'm using Solidity/Truffle. I have two contracts called Category and Post. Post is inherited from Category (Post is Category ).
Contract Category has an array called categories. I have a function in contract Category called isCategoryExists.
When I try to call this function from contract Category, everything is ok. But when I want to call this function from contract Post, I receive false because categories.length is 0.
Category.sol:
ragma solidity >=0.4.22 <0.9.0;
import "./ICategory.sol";
/// #title Create, edit and manage categories
contract Category is ICategory {
// State variables
uint256 currentIndex;
CategoryStruct[] private categories;
mapping(address => CategoryStruct[]) categoriesByUser;
// Modifiers
modifier onlyValidInput(CategoryInputStruct memory _input) {
bytes memory errCode;
bytes memory title = bytes(_input.title);
if (title.length == 0) {
errCode = bytes("invalidTitle");
}
if (errCode.length > 0) {
revert(string(errCode));
}
_;
}
modifier onlyValidIndex(uint256 index) {
if (isCategoryExists(index)) {
_;
} else {
revert("Invalid index.");
}
}
// Constructor
constructor() {
currentIndex = categories.length;
}
// Functions
/// #notice Check if category exists
function isCategoryExists(uint256 index) public view returns (bool) {
if (index >= categories.length) {
return false;
}
return categories[index].isExist;
}
/// #notice This function creates a category.
/// #dev Before this function, the entered data is validated with onlyValidInput modifier.
function createCategory(
CategoryInputStruct memory _input,
LocationStruct memory _location
) external onlyValidInput(_input) returns (bool) {
CategoryStruct memory newCategory = CategoryStruct({
id: currentIndex,
user: msg.sender,
title: _input.title,
isExist: true
});
categories.push(newCategory);
categoriesByUser[msg.sender].push(newCategory);
emit CategoryCreated(currentIndex);
currentIndex++;
return true;
}
}
Post.sol:
pragma solidity >=0.4.22 <0.9.0;
import "../category/Category.sol";
/// #title Create, edit and manage posts
contract Post is Category {
// State variables
uint256 currentPostIndex;
struct PostStruct {
uint256 id;
address user;
string title;
string body;
uint256 categoryId;
}
struct PostInputStruct {
string title;
string body;
uint256 categoryId;
}
PostStruct[] private posts;
mapping(address => uint256[]) postIndexesByUser; // example: 0x01234 => [3, 5, 24, 112, 448]
// Modifiers
modifier onlyValidPostInput(PostInputStruct memory _input) {
bytes memory errCode;
bytes memory title = bytes(_input.title);
bytes memory body = bytes(_input.body);
uint256 categoryId = uint256(_input.categoryId);
if (title.length == 0) {
errCode = bytes("invalidTitle");
} else if (body.length == 0) {
errCode = bytes("invalidBody");
}
if (errCode.length > 0) {
revert(string(errCode));
}
_;
}
// Constructor
constructor() {
currentPostIndex = posts.length;
}
// Functions
/// #notice This function creates a post.
/// #dev Before this function, the entered data is validated with onlyValidPostInput modifier.
function createPost(PostInputStruct memory _input)
external
onlyValidPostInput(_input)
returns (bool)
{
bool isExist = isCategoryExists(_input.categoryId);
PostStruct memory newPost = PostStruct({
id: currentPostIndex,
user: msg.sender,
title: _input.title,
body: _input.body,
categoryId: _input.categoryId
});
posts.push(newPost);
postIndexesByUser[msg.sender].push(currentPostIndex);
currentPostIndex++;
return true;
}
}
I make this call from the JS test environment in Truffle.
What I guess:
I think everything resets every time I run the tests. I'm not sure yet. This is a bit strange and has wasted a few days of my time.
Category.test.js:
const Chance = require("chance");
const Category = artifacts.require("Category");
const chance = new Chance();
contract("Category", (accounts) => {
// Setup 1 account.
const accountOne = accounts[0];
const accountTwo = accounts[1];
let categoryInstance;
before(async () => {
categoryInstance = await Category.deployed();
});
it("should create a category", async () => {
const title = chance.sentence();
// Create category
const categoryCreated = await categoryInstance.createCategory([title], {
from: accountOne,
});
// Get a category by array index
const category = await categoryInstance.getCategoryByIndex(0);
assert.equal(category.id, 0, "There is no data for this index");
assert.equal(category.title, title, "title is not equal");
});
it("Should return true because a category exists", async () => {
const isExists = await categoryInstance.isCategoryExists(0, {
from: accountOne,
});
assert.equal(isExists, true, "Category exists but result is false.");
});
});
Post.test.js
const Chance = require("chance");
const Category = artifacts.require("Category");
const Post = artifacts.require("post");
const chance = new Chance();
contract("Post", (accounts) => {
// Setup 1 account.
const accountOne = accounts[0];
const accountTwo = accounts[1];
let categoryInstance;
let postInstance;
before(async () => {
// Create a sample category
categoryInstance = await Category.deployed();
const title = chance.sentence();
// Create category
const categoryCreated = await categoryInstance.createCategory(
[title, purpose, area],
[polygon],
{
from: accountOne,
}
);
postInstance = await Post.deployed();
});
it("should create a post", async () => {
// Generate sample data
const title = chance.sentence();
const body = chance.paragraph();
const thumbnail = chance.url();
const categoryId = 0;
// Create post
const postCreated = await postInstance.createPost(
[title, body, thumbnail, categoryId],
{
from: accountOne,
}
);
// Get a post by array index
const post = await postInstance.getPostByIndex(0);
assert.equal(post.id, 0, "There is no data for this index");
assert.equal(post.title, title, "title is not equal");
assert.equal(post.categoryId, categoryId, "categoryId is not equal");
});
});
The problem is that you have misunderstood the concept of inheritance. The fact that Post inherits from Category does not mean that instances of Post are sharing the state with instances of Category. What it means is that Post is also a Category, so an object/instance of Post has that same state (variables) and behavior (functions/methods) of a Category contained within itself. In your case, this actually means an object of Post has its own array categories and that one is being checked when you call createPost, and it will of course be empty as you have never added a category using that object. The only way that array can be non-empty is if you call createCategory from postInstance, not from categoryInstance.
P.S.
Just to quickly clarify what I meant by "nothing should be persisted". The test suite should be created in a way that each test is independent, no test should ever depend on the execution of some other test. One should be able to run any test from the suite on its own and have it passing. I initially thought this was the problem, as you did not share the entire code.
I am trying to transfer ERC1155 tokens to another account. I have deployed the contract and have put down my contract code below.
The problem I am facing is I get the below error when I call getMyToken function from my frontend web3js code.
{code: -32603, message: 'execution reverted: ERC1155: caller is not owner nor approved', data: {…}}
Not sure what is happening. I am calling the function as below from frontend. The isApproved function is returning true.
const selectedAccountIsApproved = await monkeysContract.methods.isApproved(selectedAccount).call();
console.log('IS APPROVED RESULT: ', selectedAccountIsApproved);
if(selectedAccountIsApproved) {
const result = await monkeysContract.methods.mintMyMonkey(selectedAccount, 0).call();
console.log('Transfer Result: ', result);
} else {
console.log('Account not approved by owner for transfers');
}
NOTE:
I have called the setApprovalForAll method and approved the receiver account.
I am able to call isApprovedForAll and verify that the account is approved.
I am able to transfer tokens to the receiver through Etherscan contract interface.
Contract Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "#openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "#openzeppelin/contracts/access/Ownable.sol";
contract Monkeys is ERC1155, Ownable {
uint256 public constant CC1 = 0;
uint256 public constant CC2 = 1;
mapping (uint256 => string) private _uris;
constructor() public ERC1155("https://game.example/api/item/{id}.json") {
_mint(msg.sender, CC1, 1000, "");
_mint(msg.sender, CC2, 1000, "");
}
function uri(uint256 tokenId) override public view returns (string memory) {
return(_uris[tokenId]);
}
function setTokenUri(uint256 id, string memory url) public onlyOwner {
require(bytes(_uris[id]).length == 0, "Cannot set uri twice");
_uris[id] = url;
}
function transfer(address from, address to, uint256 id, uint256 amount, bytes memory data) public {
safeTransferFrom(from, to, id, amount, data);
}
function isApproved(address user) public view virtual returns (bool) {
return isApprovedForAll(owner(), user);
}
function getMyToken(address to, uint256 id) public {
transfer(owner(), to, id, 1, 'test');
}
}
I am trying to follow the Chainlink VRF tutorial found here: https://docs.chain.link/docs/intermediates-tutorial/ with hardhat and am running into this issue when calling the rollDice function:
Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={"reason":"cannot estimate gas; transaction may fail or may require manual gas limit","code":"UNPREDICTABLE_GAS_LIMIT","method":"estimateGas","transaction":{"from":"0x014Da1D627E6ceB555975F09D26B048644382Ac6","maxPriorityFeePerGas":{"type":"BigNumber","hex":"0x9502f900"},"maxFeePerGas":{"type":"BigNumber","hex":"0x9502f90e"},"to":"0x5887946875A01D1BB79d6Fb357BceeA5A0096D2e","data":"0xdd02d9e5000000000000000000000000014da1d627e6ceb555975f09d26b048644382ac6","type":2,"accessList":null}}, tx={"data":"0xdd02d9e5000000000000000000000000014da1d627e6ceb555975f09d26b048644382ac6","to":{},"from":"0x014Da1D627E6ceB555975F09D26B048644382Ac6","type":2,"maxFeePerGas":{"type":"BigNumber","hex":"0x9502f90e"},"maxPriorityFeePerGas":{"type":"BigNumber","hex":"0x9502f900"},"nonce":{},"gasLimit":{},"chainId":{}}, code=UNPREDICTABLE_GAS_LIMIT, version=abstract-signer/5.5.0)
at Logger.makeError (/Users/matt/Desktop/hardhat/randomDay/node_modules/#ethersproject/logger/src.ts/index.ts:225:28)
at Logger.throwError (/Users/matt/Desktop/hardhat/randomDay/node_modules/#ethersproject/logger/src.ts/index.ts:237:20)
at /Users/matt/Desktop/hardhat/randomDay/node_modules/#ethersproject/abstract-signer/src.ts/index.ts:301:31
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Promise.all (index 7)
I am able to deploy to the Kovan testnet, I was able to verify the contract, and I have sent the contract LINK tokens, but am still running into the issue. The contract can be viewed here: https://kovan.etherscan.io/address/0x7b72d80670512c87605ab8ac7e6113fda9c57de4#code
I am using version 0.8 of the Chainlink Contracts.
RandomDay.sol
pragma solidity ^0.8.9;
import "#chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
contract RandomDay is VRFConsumerBase {
uint256 private constant ROLL_IN_PROGRESS = 42;
bytes32 private s_keyHash;
uint256 private s_fee;
mapping(bytes32 => address) private s_rollers;
mapping(address => uint256) private s_results;
event DiceRolled(bytes32 indexed requestId, address indexed roller);
event DiceLanded(bytes32 indexed requestId, uint256 indexed result);
constructor(address vrfCoordinator, address link, bytes32 keyHash, uint256 fee) VRFConsumerBase(vrfCoordinator, link) {
s_keyHash = keyHash;
s_fee = fee;
}
function rollDice (address roller) public returns (bytes32 requestId) {
require(LINK.balanceOf(address(this)) >= s_fee, "Not enough LINK to pay fee");
require(s_results[roller] == 0, "Already rolled");
requestId = requestRandomness(s_keyHash, s_fee);
s_rollers[requestId] = roller;
s_results[roller] = ROLL_IN_PROGRESS;
emit DiceRolled(requestId, roller);
return requestId;
}
function fulfillRandomness (bytes32 requestId, uint256 randomness) internal override {
uint256 dayOfWeek = (randomness % 7) + 1;
s_results[s_rollers[requestId]] = dayOfWeek;
emit DiceLanded(requestId, dayOfWeek);
}
function weekday (address player) public view returns (string memory) {
require(s_results[player] != 0, "Dice not rolled");
require(s_results[player] != ROLL_IN_PROGRESS, "Roll in progress");
return getWeekdayName(s_results[player]);
}
function getWeekdayName (uint256 id) private pure returns (string memory) {
string[7] memory weekdays = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
return weekdays[id - 1];
}
}
hardhat.config.js
/**
* #type import('hardhat/config').HardhatUserConfig
*/
require("#nomiclabs/hardhat-waffle")
require("#nomiclabs/hardhat-etherscan")
const ALCHEMY_API_KEY = "*************************";
const ROPSTEN_PRIVATE_KEY = "*********************";
module.exports = {
solidity: "0.8.9",
networks: {
kovan: {
url: `https://eth-kovan.alchemyapi.io/v2/${ALCHEMY_API_KEY}`,
accounts: [`0x${ROPSTEN_PRIVATE_KEY}`],
gas: 2700000000,
maxFeePerGas: 30000000000,
}
},
etherscan: {
// Your API key for Etherscan
// Obtain one at https://etherscan.io/
apiKey: "****************************"
}
};
deploy.js
const { ethers } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
console.log("Account balance:", (await deployer.getBalance()).toString());
const Token = await ethers.getContractFactory("RandomDay");
const token = await Token.deploy("0xa36085F69e2889c224210F603D836748e7dC0088", "0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9", "0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4", "100000000000000000");
console.log("Token address:", token.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
quickRun.js
var ethers = require('ethers');
var provider = ethers.providers.getDefaultProvider('kovan');
var address = '0x7b72d80670512c87605aB8aC7E6113Fda9c57de4';
var abi = [{"inputs":[{"internalType":"address","name":"vrfCoordinator","type":"address"},{"internalType":"address","name":"link","type":"address"},{"internalType":"bytes32","name":"keyHash","type":"bytes32"},{"internalType":"uint256","name":"fee","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"requestId","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"result","type":"uint256"}],"name":"DiceLanded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"requestId","type":"bytes32"},{"indexed":true,"internalType":"address","name":"roller","type":"address"}],"name":"DiceRolled","type":"event"},{"inputs":[{"internalType":"bytes32","name":"requestId","type":"bytes32"},{"internalType":"uint256","name":"randomness","type":"uint256"}],"name":"rawFulfillRandomness","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"roller","type":"address"}],"name":"rollDice","outputs":[{"internalType":"bytes32","name":"requestId","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"weekday","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}];
var privateKey = '*******************************';
var wallet = new ethers.Wallet(privateKey, provider);
var contract = new ethers.Contract(address, abi, wallet);
var sendPromise = contract.rollDice('0x014Da1D627E6ceB555975F09D26B048644382Ac6');
sendPromise.then(function(transaction){
console.log(transaction);
});
I believe the addresses are off:
Constructor Arguments of your contract:
-----Decoded View---------------
Arg [0] : vrfCoordinator (address): 0xa36085f69e2889c224210f603d836748e7dc0088
Arg [1] : link (address): 0xdd3782915140c8f3b190b5d67eac6dc5760c46e9
Arg [2] : keyHash (bytes32): 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4
Arg [3] : fee (uint256): 100000000000000000
Via Chainlinks Docs:
LINK 0xa36085F69e2889c224210F603D836748e7dC0088
VRF Coordinator 0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9
Key Hash 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4
Fee 0.1 LINK
Switched Link and VRF are probably the culprit.
9 out of 10 is because you passed something wrong in the constructor or something in the contructor is not initialized propertly. this kind of things aren't detected by compiler.