Updated Hacknet Node API documentation for the BN-9 changes. netscript functions now properly fail for Hacknet Servers

This commit is contained in:
danielyxie 2019-03-30 19:53:57 -07:00
parent b6b6d8e9fa
commit 804e4c23e3
5 changed files with 117 additions and 118 deletions

@ -0,0 +1,17 @@
getCacheUpgradeCost() Netscript Function
========================================
.. warning:: This page contains spoilers for the game
.. js:function:: getCacheUpgradeCost(i, n)
:param number i: Index/Identifier of Hacknet Node. :ref:`See here for details <netscript_hacknetnodeapi_referencingahacknetnode>`
:param number n: Number of times to upgrade cache. Must be positive. Rounded to nearest integer
.. note:: This function is only applicable for Hacknet Servers (the upgraded version of
a Hacknet Node).
Returns the cost of upgrading the cache level of the specified Hacknet Server by *n*.
If an invalid value for *n* is provided, then this function returns 0. If the
specified Hacknet Server is already at the max cache level, then Infinity is returned.

@ -1,6 +1,8 @@
getNodeStats() Netscript Function getNodeStats() Netscript Function
================================= =================================
.. warning:: This page contains spoilers for the game
.. js:function:: getNodeStats(i) .. js:function:: getNodeStats(i)
:param number i: Index/Identifier of Hacknet Node. :ref:`See here for details <netscript_hacknetnodeapi_referencingahacknetnode>` :param number i: Index/Identifier of Hacknet Node. :ref:`See here for details <netscript_hacknetnodeapi_referencingahacknetnode>`
@ -12,7 +14,12 @@ getNodeStats() Netscript Function
level: Node's level, level: Node's level,
ram: Node's RAM, ram: Node's RAM,
cores: Node's number of cores, cores: Node's number of cores,
production: Node's money earned per second, cache: Cache level. Only applicable for Hacknet Servers
production: Node's production per second
timeOnline: Number of seconds since Node has been purchased, timeOnline: Number of seconds since Node has been purchased,
totalProduction: Total number of money Node has produced totalProduction: Total amount that the Node has produced
} }
.. note:: Note that for Hacknet Nodes, production refers to the amount of money the node generates.
For Hacknet Servers (the upgraded version of Hacknet Nodes), production refers to the amount
of hashes the node generates.

@ -0,0 +1,19 @@
upgradeCache() Netscript Function
=================================
.. warning:: This page contains spoilers for the game
.. js:function:: upgradeCache(i, n)
:param number i: Index/Identifier of Hacknet Node. :ref:`See here for details <netscript_hacknetnodeapi_referencingahacknetnode>`
:param number n: Number of cache levels to purchase. Must be positive. Rounded to nearest integer
.. note:: This function is only applicable for Hacknet Servers (the upgraded version of
a Hacknet Node).
Tries to upgrade the specified Hacknet Server's cache *n* times.
Returns true if it successfully upgrades the Server's cache *n* times, or if
it purchases some positive amount and the Server reaches its max cache level.
Returns false otherwise.

@ -31,9 +31,11 @@ In :ref:`netscriptjs`::
upgradeLevel() <hacknetnodeapi/upgradeLevel> upgradeLevel() <hacknetnodeapi/upgradeLevel>
upgradeRam() <hacknetnodeapi/upgradeRam> upgradeRam() <hacknetnodeapi/upgradeRam>
upgradeCore() <hacknetnodeapi/upgradeCore> upgradeCore() <hacknetnodeapi/upgradeCore>
upgradeCache() <hacknetnodeapi/upgradeCache>
getLevelUpgradeCost() <hacknetnodeapi/getLevelUpgradeCost> getLevelUpgradeCost() <hacknetnodeapi/getLevelUpgradeCost>
getRamUpgradeCost() <hacknetnodeapi/getRamUpgradeCost> getRamUpgradeCost() <hacknetnodeapi/getRamUpgradeCost>
getCoreUpgradeCost() <hacknetnodeapi/getCoreUpgradeCost> getCoreUpgradeCost() <hacknetnodeapi/getCoreUpgradeCost>
getCacheUpgradeCost() <hacknetnodeapi/getCacheUpgradeCost>
.. _netscript_hacknetnodeapi_referencingahacknetnode: .. _netscript_hacknetnodeapi_referencingahacknetnode:
@ -68,23 +70,25 @@ The following is an example of one way a script can be used to automate the
purchasing and upgrading of Hacknet Nodes. purchasing and upgrading of Hacknet Nodes.
This script attempts to purchase Hacknet Nodes until the player has a total of 8. Then This script attempts to purchase Hacknet Nodes until the player has a total of 8. Then
it gradually upgrades those Node's to a minimum of level 140, 64 GB RAM, and 8 cores:: it gradually upgrades those Node's to a minimum of level 140, 64 GB RAM, and 8 cores
.. code:: javascript
function myMoney() { function myMoney() {
return getServerMoneyAvailable("home");() <hacknetnodeapi/ return getServerMoneyAvailable("home");> return getServerMoneyAvailable("home");
} }
}() <hacknetnodeapi/>
disableLog("getServerMoneyAvailable"); disableLog("getServerMoneyAvailable");
disableLog("sleep"); disableLog("sleep");
cnt = 8; var cnt = 8;
while(hacknet.numNodes() < cnt) { while(hacknet.numNodes() < cnt) {
res = hacknet.purchaseNode(); res = hacknet.purchaseNode();
print("Purchased hacknet Node with index " + res); print("Purchased hacknet Node with index " + res);
}; };
for (i = 0; i < cnt; i++) { for (var i = 0; i < cnt; i++) {
while (hacknet.getNodeStats(i).level <= 80) { while (hacknet.getNodeStats(i).level <= 80) {
var cost = hacknet.getLevelUpgradeCost(i, 10); var cost = hacknet.getLevelUpgradeCost(i, 10);
while (myMoney() < cost) { while (myMoney() < cost) {
@ -95,9 +99,9 @@ it gradually upgrades those Node's to a minimum of level 140, 64 GB RAM, and 8 c
}; };
}; };
print("All nodes upgrade to level 80"); print("All nodes upgraded to level 80");
for (i = 0; i < cnt; i++) { for (var i = 0; i < cnt; i++) {
while (hacknet.getNodeStats(i).ram < 16) { while (hacknet.getNodeStats(i).ram < 16) {
var cost = hacknet.getRamUpgradeCost(i, 2); var cost = hacknet.getRamUpgradeCost(i, 2);
while (myMoney() < cost) { while (myMoney() < cost) {
@ -108,43 +112,4 @@ it gradually upgrades those Node's to a minimum of level 140, 64 GB RAM, and 8 c
}; };
}; };
print("All nodes upgrade to 16GB RAM"); print("All nodes upgraded to 16GB RAM");
for (i = 0; i < cnt; i++) {
while (hacknet.getNodeStats(i).level <= 140) {
var cost = hacknet.getLevelUpgradeCost(i, 5);
while (myMoney() < cost) {
print("Need $" + cost + " . Have $" + myMoney());
sleep(3000);
}
res = hacknet.upgradeLevel(i, 5);
};
};
print("All nodes upgrade to level 140");
for (i = 0; i < cnt; i++) {
while (hacknet.getNodeStats(i).ram < 64) {
var cost = hacknet.getRamUpgradeCost(i, 2);
while (myMoney() < cost) {
print("Need $" + cost + " . Have $" + myMoney());
sleep(3000);
}
res = hacknet.upgradeRam(i, 2);
};
};
print("All nodes upgrade to 64GB RAM (MAX)");
for (i = 0; i < cnt; i++) {
while (hacknetnodes.getNodeStatsi(i).cores < 8) {
var cost = hacknet.getCoreUpgradeCost(7);
while (myMoney() < cost) {
print("Need $" + cost + " . Have $" + myMoney());
sleep(3000);
}
res = hacknet.upgradeCore(i, 7);
};
};
print("All nodes upgrade to 8 cores");

@ -37,6 +37,7 @@ import { getCostOfNextHacknetNode,
getCostOfNextHacknetServer, getCostOfNextHacknetServer,
hasHacknetServers, hasHacknetServers,
purchaseHacknet } from "./Hacknet/HacknetHelpers"; purchaseHacknet } from "./Hacknet/HacknetHelpers";
import { HacknetServer } from "./Hacknet/HacknetServer";
import {Locations} from "./Locations"; import {Locations} from "./Locations";
import { Message } from "./Message/Message"; import { Message } from "./Message/Message";
import { Messages } from "./Message/MessageHelpers"; import { Messages } from "./Message/MessageHelpers";
@ -234,16 +235,34 @@ function NetscriptFunctions(workerScript) {
* Gets the Server for a specific hostname/ip, throwing an error * Gets the Server for a specific hostname/ip, throwing an error
* if the server doesn't exist. * if the server doesn't exist.
* @param {string} Hostname or IP of the server * @param {string} Hostname or IP of the server
* @param {string} callingFnName - Name of calling function. For logging purposes
* @returns {Server} The specified Server * @returns {Server} The specified Server
*/ */
const safeGetServer = function(ip, callingFnName="") { const safeGetServer = function(ip, callingFnName="") {
var server = getServer(ip); var server = getServer(ip);
if (server == null) { if (server == null) {
workerScript.log(`ERROR: Invalid IP or hostname passed into ${callingFnName}()`);
throw makeRuntimeRejectMsg(workerScript, `Invalid IP or hostname passed into ${callingFnName}() function`); throw makeRuntimeRejectMsg(workerScript, `Invalid IP or hostname passed into ${callingFnName}() function`);
} }
return server; return server;
} }
/**
* Used to fail a function if the function's target is a Hacknet Server.
* This is used for functions that should run on normal Servers, but not Hacknet Servers
* @param {Server} server - Target server
* @param {string} callingFn - Name of calling function. For logging purposes
* @returns {boolean} True if the server is a Hacknet Server, false otherwise
*/
const failOnHacknetServer = function(server, callingFn="") {
if (server instanceof HacknetServer) {
workerScript.log(`ERROR: ${callingFn}() failed because it does not work on Hacknet Servers`);
return true;
} else {
return false;
}
}
// Utility function to get Hacknet Node object // Utility function to get Hacknet Node object
const getHacknetNode = function(i) { const getHacknetNode = function(i) {
if (isNaN(i)) { if (isNaN(i)) {
@ -1341,25 +1360,22 @@ function NetscriptFunctions(workerScript) {
let copy = Object.assign({}, BitNodeMultipliers); let copy = Object.assign({}, BitNodeMultipliers);
return copy; return copy;
}, },
getServerMoneyAvailable : function(ip){ getServerMoneyAvailable : function(ip) {
if (workerScript.checkingRam) { if (workerScript.checkingRam) {
return updateStaticRam("getServerMoneyAvailable", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerMoneyAvailable", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerMoneyAvailable", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerMoneyAvailable", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerMoneyAvailable");
if (server == null) { if (failOnHacknetServer(server, "getServerMoneyAvailable")) { return 0; }
workerScript.scriptRef.log("getServerMoneyAvailable() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getServerMoneyAvailable() failed. Invalid IP or hostname passed in: " + ip);
}
if (server.hostname == "home") { if (server.hostname == "home") {
//Return player's money // Return player's money
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMoneyAvailable == null) { if (workerScript.shouldLog("getServerMoneyAvailable")) {
workerScript.scriptRef.log("getServerMoneyAvailable('home') returned player's money: $" + formatNumber(Player.money.toNumber(), 2)); workerScript.log("getServerMoneyAvailable('home') returned player's money: $" + formatNumber(Player.money.toNumber(), 2));
} }
return Player.money.toNumber(); return Player.money.toNumber();
} }
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMoneyAvailable == null) { if (workerScript.shouldLog("getServerMoneyAvailable")) {
workerScript.scriptRef.log("getServerMoneyAvailable() returned " + formatNumber(server.moneyAvailable, 2) + " for " + server.hostname); workerScript.log("getServerMoneyAvailable() returned " + formatNumber(server.moneyAvailable, 2) + " for " + server.hostname);
} }
return server.moneyAvailable; return server.moneyAvailable;
}, },
@ -1368,13 +1384,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerSecurityLevel", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerSecurityLevel", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerSecurityLevel");
if (server == null) { if (failOnHacknetServer(server, "getServerSecurityLevel")) { return 1; }
workerScript.scriptRef.log("getServerSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerSecurityLevel")) {
throw makeRuntimeRejectMsg(workerScript, "getServerSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerSecurityLevel() returned " + formatNumber(server.hackDifficulty, 3) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerSecurityLevel == null) {
workerScript.scriptRef.log("getServerSecurityLevel() returned " + formatNumber(server.hackDifficulty, 3) + " for " + server.hostname);
} }
return server.hackDifficulty; return server.hackDifficulty;
}, },
@ -1383,13 +1396,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerBaseSecurityLevel", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerBaseSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerBaseSecurityLevel", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerBaseSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerBaseSecurityLevel");
if (server == null) { if (failOnHacknetServer(server, "getServerBaseSecurityLevel")) { return 1; }
workerScript.scriptRef.log("getServerBaseSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerBaseSecurityLevel")) {
throw makeRuntimeRejectMsg(workerScript, "getServerBaseSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerBaseSecurityLevel() returned " + formatNumber(server.baseDifficulty, 3) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerBaseSecurityLevel == null) {
workerScript.scriptRef.log("getServerBaseSecurityLevel() returned " + formatNumber(server.baseDifficulty, 3) + " for " + server.hostname);
} }
return server.baseDifficulty; return server.baseDifficulty;
}, },
@ -1398,13 +1408,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerMinSecurityLevel", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerMinSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerMinSecurityLevel", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerMinSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerMinSecurityLevel");
if (server == null) { if (failOnHacknetServer(server, "getServerMinSecurityLevel")) { return 1; }
workerScript.scriptRef.log("getServerMinSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerMinSecurityLevel")) {
throw makeRuntimeRejectMsg(workerScript, "getServerMinSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerMinSecurityLevel() returned " + formatNumber(server.minDifficulty, 3) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMinSecurityLevel == null) {
workerScript.scriptRef.log("getServerMinSecurityLevel() returned " + formatNumber(server.minDifficulty, 3) + " for " + server.hostname);
} }
return server.minDifficulty; return server.minDifficulty;
}, },
@ -1413,13 +1420,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerRequiredHackingLevel", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerRequiredHackingLevel", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerRequiredHackingLevel", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerRequiredHackingLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerRequiredHackingLevel");
if (server == null) { if (failOnHacknetServer(server, "getServerRequiredHackingLevel")) { return 1; }
workerScript.scriptRef.log("getServerRequiredHackingLevel() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerRequiredHackingLevel")) {
throw makeRuntimeRejectMsg(workerScript, "getServerRequiredHackingLevel() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerRequiredHackingLevel returned " + formatNumber(server.requiredHackingSkill, 0) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerRequiredHackingLevel == null) {
workerScript.scriptRef.log("getServerRequiredHackingLevel returned " + formatNumber(server.requiredHackingSkill, 0) + " for " + server.hostname);
} }
return server.requiredHackingSkill; return server.requiredHackingSkill;
}, },
@ -1428,13 +1432,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerMaxMoney", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerMaxMoney", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerMaxMoney", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerMaxMoney", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerMaxMoney");
if (server == null) { if (failOnHacknetServer(server, "getServerMaxMoney")) { return 0; }
workerScript.scriptRef.log("getServerMaxMoney() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerMaxMoney")) {
throw makeRuntimeRejectMsg(workerScript, "getServerMaxMoney() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerMaxMoney() returned " + formatNumber(server.moneyMax, 0) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMaxMoney == null) {
workerScript.scriptRef.log("getServerMaxMoney() returned " + formatNumber(server.moneyMax, 0) + " for " + server.hostname);
} }
return server.moneyMax; return server.moneyMax;
}, },
@ -1443,13 +1444,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerGrowth", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerGrowth", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerGrowth", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerGrowth", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerGrowth");
if (server == null) { if (failOnHacknetServer(server, "getServerGrowth")) { return 1; }
workerScript.scriptRef.log("getServerGrowth() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerGrowth")) {
throw makeRuntimeRejectMsg(workerScript, "getServerGrowth() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerGrowth() returned " + formatNumber(server.serverGrowth, 0) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerGrowth == null) {
workerScript.scriptRef.log("getServerGrowth() returned " + formatNumber(server.serverGrowth, 0) + " for " + server.hostname);
} }
return server.serverGrowth; return server.serverGrowth;
}, },
@ -1458,13 +1456,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerNumPortsRequired", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerNumPortsRequired", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerNumPortsRequired", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerNumPortsRequired", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerNumPortsRequired");
if (server == null) { if (failOnHacknetServer(server, "getServerNumPortsRequired")) { return 5; }
workerScript.scriptRef.log("getServerNumPortsRequired() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerNumPortsRequired")) {
throw makeRuntimeRejectMsg(workerScript, "getServerNumPortsRequired() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerNumPortsRequired() returned " + formatNumber(server.numOpenPortsRequired, 0) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerNumPortsRequired == null) {
workerScript.scriptRef.log("getServerNumPortsRequired() returned " + formatNumber(server.numOpenPortsRequired, 0) + " for " + server.hostname);
} }
return server.numOpenPortsRequired; return server.numOpenPortsRequired;
}, },
@ -1473,13 +1468,9 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerRam", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerRam", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerRam", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerRam", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerRam");
if (server == null) { if (workerScript.shouldLog("getServerRam")) {
workerScript.scriptRef.log("getServerRam() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerRam() returned [" + formatNumber(server.maxRam, 2) + "GB, " + formatNumber(server.ramUsed, 2) + "GB]");
throw makeRuntimeRejectMsg(workerScript, "getServerRam() failed. Invalid IP or hostname passed in: " + ip);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerRam == null) {
workerScript.scriptRef.log("getServerRam() returned [" + formatNumber(server.maxRam, 2) + "GB, " + formatNumber(server.ramUsed, 2) + "GB]");
} }
return [server.maxRam, server.ramUsed]; return [server.maxRam, server.ramUsed];
}, },
@ -4771,7 +4762,7 @@ function NetscriptFunctions(workerScript) {
answer = String(answer); answer = String(answer);
} }
const serv = safeGetServer(ip, "codingcontract.attempt()"); const serv = safeGetServer(ip, "codingcontract.attempt");
if (contract.isSolution(answer)) { if (contract.isSolution(answer)) {
const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty()); const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty());
workerScript.log(`Successfully completed Coding Contract ${fn}. Reward: ${reward}`); workerScript.log(`Successfully completed Coding Contract ${fn}. Reward: ${reward}`);