Difference between revisions of "Super.init() caller"
(Created page with "<div><br></div><div><div><div>Redefining init() with a call to super.init() inside causes issues:</div><div> - "minting = false, sending = true" (should be...") |
|||
| (9 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
| − | + | When you extend Token, redefining init() with a call to super.init() inside causes issues: | |
| + | |||
| + | - "minting = false, sending = true" (should be the opposite) | ||
| + | |||
| + | - 'caller' variable which should be the class TokenA, but becomes instead: | ||
| + | |||
| + | <syntaxhighlight lang="javascript">Object <Object <Object <Object <Complex prototype>>>> { | ||
| + | location: 'error://Undeployed', | ||
| + | origin: 'error://Undeployed', | ||
| + | nonce: 0, | ||
| + | owner: 'mo61dRQheTcBMf1d7UEv91ybaDqXUyKUwd', | ||
| + | satoshis: undefined | ||
| + | }</syntaxhighlight> | ||
| + | |||
| + | |||
| + | Note that despite these issues, it works nonetheless, since it detects the situation as 'sending' instead of 'minting', which manages to still pass the check and mint the token. | ||
| + | |||
| + | |||
| + | |||
| + | Full Code you can test, just comment the init() entirely in TokenA to see the difference in logs while running it: | ||
| + | |||
| + | |||
| + | <syntaxhighlight lang="javascript"> | ||
| + | const Run = require('../../libs/run.0.6.44.node.min.js'); | ||
| + | const log = console.log | ||
| + | |||
| + | let run = new Run({ network: 'mock', trust: 'state' }) | ||
| + | |||
| + | // pure copy of the Token20 with only some added logging | ||
| + | class Token20Copy extends Jig { | ||
| + | init(amount, owner) { | ||
| + | this._checkAmount(amount) | ||
| + | |||
| + | // The base Token class cannot be created on its own | ||
| + | const extended = this.constructor !== Token20Copy | ||
| + | if (!extended) throw new Error('Token must be extended') | ||
| + | |||
| + | // Make sure we are calling from ourself | ||
| + | const minting = caller === this.constructor | ||
| + | const sending = caller && caller.constructor === this.constructor | ||
| + | console.log("minting =", minting, ", sending =", sending, ", \n caller =", caller) | ||
| + | if (!minting && !sending) throw new Error('Must create token using mint()') | ||
| + | |||
| + | this.sender = sending ? caller.owner : null | ||
| + | this.amount = amount | ||
| + | if (owner) this.owner = owner | ||
| + | } | ||
| + | |||
| + | static mint(amount, owner) { | ||
| + | this.supply += amount | ||
| + | return new this(amount, owner) | ||
| + | } | ||
| + | |||
| + | send(to, amount = this.amount) { | ||
| + | this._checkAmount(amount) | ||
| + | |||
| + | if (this.amount === amount) { | ||
| + | this.destroy() | ||
| + | } else if (this.amount > amount) { | ||
| + | this.amount -= amount | ||
| + | } else { | ||
| + | throw new Error('Not enough funds') | ||
| + | } | ||
| + | |||
| + | return new this.constructor(amount, to) | ||
| + | } | ||
| + | |||
| + | combine(...tokens) { | ||
| + | // If no tokens to combine, nothing to do | ||
| + | if (!tokens.length) return this | ||
| + | |||
| + | // Each token to combine must all be of this type | ||
| + | const all = tokens.concat(this) | ||
| + | if (all.some(token => token.constructor !== this.constructor)) { | ||
| + | throw new Error('Cannot combine different token classes') | ||
| + | } | ||
| + | |||
| + | // Check for duplicate tokens in the array | ||
| + | const countOf = token => all.reduce((count, next) => next === token ? count + 1 : count, 0) | ||
| + | if (all.some(token => countOf(token) > 1)) throw new Error('Cannot combine duplicate tokens') | ||
| + | |||
| + | // Destroy each token, absorbing it into this one | ||
| + | tokens.forEach(token => { | ||
| + | this.amount += token.amount | ||
| + | token.destroy() | ||
| + | }) | ||
| + | |||
| + | // There is no sender for combined tokens | ||
| + | this.sender = null | ||
| + | |||
| + | // Make sure our new amount is within safe range | ||
| + | this._checkAmount(this.amount) | ||
| + | |||
| + | return this | ||
| + | } | ||
| + | |||
| + | destroy() { | ||
| + | super.destroy() | ||
| + | |||
| + | this.amount = 0 | ||
| + | this.sender = null | ||
| + | } | ||
| + | |||
| + | _checkAmount(amount) { | ||
| + | if (typeof amount !== 'number') throw new Error('amount is not a number') | ||
| + | if (!Number.isInteger(amount)) throw new Error('amount must be an integer') | ||
| + | if (amount <= 0) throw new Error('amount must be positive') | ||
| + | if (amount > Number.MAX_SAFE_INTEGER) throw new Error('amount too large') | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| + | class TokenA extends Token20Copy { | ||
| + | /** Redefining init() with a call to super.init() inside causes issues: | ||
| + | * - "minting = false, sending = true" (should be the opposite) | ||
| + | * - 'caller' variable which should be the class TokenA, | ||
| + | * but becomes instead: Object <Object <Object <Object <Complex prototype>>>> { | ||
| + | * location: 'error://Undeployed', | ||
| + | * origin: 'error://Undeployed', | ||
| + | * nonce: 0, | ||
| + | * owner: 'mo61dRQheTcBMf1d7UEv91ybaDqXUyKUwd', | ||
| + | * satoshis: undefined | ||
| + | * } | ||
| + | * | ||
| + | * Note that despite these issues, it works nontheless, since it detects as 'sending' instead of 'minting', | ||
| + | * which manages to still pass the check and mint the token. | ||
| + | **/ | ||
| + | init(amount, owner) { | ||
| + | super.init(amount, owner) | ||
| + | } | ||
| + | } | ||
| + | TokenA.decimals = 2 | ||
| + | TokenA.ticker = 'TokenA' | ||
| + | TokenA.currency = 'TokenA' | ||
| + | |||
| + | |||
| + | async function displayTokensOnAddress() { | ||
| + | await run.inventory.sync() | ||
| + | const tokens = run.inventory.jigs | ||
| + | log("---tokens on address:", tokens.length) | ||
| + | for (const token of tokens) { | ||
| + | log("token:", token.constructor.ticker, token.amount / (10 ** token.constructor.decimals), token.constructor.origin) | ||
| + | } | ||
| + | log("---end---\n") | ||
| + | } | ||
| + | |||
| + | |||
| + | async function _main() { | ||
| + | const owner = run.owner.address | ||
| + | await displayTokensOnAddress() | ||
| + | |||
| + | TokenA = run.deploy(TokenA) | ||
| + | await run.sync() | ||
| + | TokenA = await run.load(TokenA.origin) | ||
| + | log("TokenA.owner =", TokenA.owner) | ||
| + | |||
| + | log("minting TokenA") | ||
| + | let tokenACoins = TokenA.mint(10 * 10 ** TokenA.decimals, owner) | ||
| + | await run.sync() | ||
| + | |||
| + | await displayTokensOnAddress() | ||
| + | } | ||
| + | |||
| + | _main() | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | [[Category:Issues]] | ||
Latest revision as of 08:13, 1 February 2026
When you extend Token, redefining init() with a call to super.init() inside causes issues:
- "minting = false, sending = true" (should be the opposite)
- 'caller' variable which should be the class TokenA, but becomes instead:
Object <Object <Object <Object <Complex prototype>>>> {
location: 'error://Undeployed',
origin: 'error://Undeployed',
nonce: 0,
owner: 'mo61dRQheTcBMf1d7UEv91ybaDqXUyKUwd',
satoshis: undefined
}
Note that despite these issues, it works nonetheless, since it detects the situation as 'sending' instead of 'minting', which manages to still pass the check and mint the token.
Full Code you can test, just comment the init() entirely in TokenA to see the difference in logs while running it:
const Run = require('../../libs/run.0.6.44.node.min.js');
const log = console.log
let run = new Run({ network: 'mock', trust: 'state' })
// pure copy of the Token20 with only some added logging
class Token20Copy extends Jig {
init(amount, owner) {
this._checkAmount(amount)
// The base Token class cannot be created on its own
const extended = this.constructor !== Token20Copy
if (!extended) throw new Error('Token must be extended')
// Make sure we are calling from ourself
const minting = caller === this.constructor
const sending = caller && caller.constructor === this.constructor
console.log("minting =", minting, ", sending =", sending, ", \n caller =", caller)
if (!minting && !sending) throw new Error('Must create token using mint()')
this.sender = sending ? caller.owner : null
this.amount = amount
if (owner) this.owner = owner
}
static mint(amount, owner) {
this.supply += amount
return new this(amount, owner)
}
send(to, amount = this.amount) {
this._checkAmount(amount)
if (this.amount === amount) {
this.destroy()
} else if (this.amount > amount) {
this.amount -= amount
} else {
throw new Error('Not enough funds')
}
return new this.constructor(amount, to)
}
combine(...tokens) {
// If no tokens to combine, nothing to do
if (!tokens.length) return this
// Each token to combine must all be of this type
const all = tokens.concat(this)
if (all.some(token => token.constructor !== this.constructor)) {
throw new Error('Cannot combine different token classes')
}
// Check for duplicate tokens in the array
const countOf = token => all.reduce((count, next) => next === token ? count + 1 : count, 0)
if (all.some(token => countOf(token) > 1)) throw new Error('Cannot combine duplicate tokens')
// Destroy each token, absorbing it into this one
tokens.forEach(token => {
this.amount += token.amount
token.destroy()
})
// There is no sender for combined tokens
this.sender = null
// Make sure our new amount is within safe range
this._checkAmount(this.amount)
return this
}
destroy() {
super.destroy()
this.amount = 0
this.sender = null
}
_checkAmount(amount) {
if (typeof amount !== 'number') throw new Error('amount is not a number')
if (!Number.isInteger(amount)) throw new Error('amount must be an integer')
if (amount <= 0) throw new Error('amount must be positive')
if (amount > Number.MAX_SAFE_INTEGER) throw new Error('amount too large')
}
}
class TokenA extends Token20Copy {
/** Redefining init() with a call to super.init() inside causes issues:
* - "minting = false, sending = true" (should be the opposite)
* - 'caller' variable which should be the class TokenA,
* but becomes instead: Object <Object <Object <Object <Complex prototype>>>> {
* location: 'error://Undeployed',
* origin: 'error://Undeployed',
* nonce: 0,
* owner: 'mo61dRQheTcBMf1d7UEv91ybaDqXUyKUwd',
* satoshis: undefined
* }
*
* Note that despite these issues, it works nontheless, since it detects as 'sending' instead of 'minting',
* which manages to still pass the check and mint the token.
**/
init(amount, owner) {
super.init(amount, owner)
}
}
TokenA.decimals = 2
TokenA.ticker = 'TokenA'
TokenA.currency = 'TokenA'
async function displayTokensOnAddress() {
await run.inventory.sync()
const tokens = run.inventory.jigs
log("---tokens on address:", tokens.length)
for (const token of tokens) {
log("token:", token.constructor.ticker, token.amount / (10 ** token.constructor.decimals), token.constructor.origin)
}
log("---end---\n")
}
async function _main() {
const owner = run.owner.address
await displayTokensOnAddress()
TokenA = run.deploy(TokenA)
await run.sync()
TokenA = await run.load(TokenA.origin)
log("TokenA.owner =", TokenA.owner)
log("minting TokenA")
let tokenACoins = TokenA.mint(10 * 10 ** TokenA.decimals, owner)
await run.sync()
await displayTokensOnAddress()
}
_main()