Difference between revisions of "Prevent child classes from redefining methods"

From RunWiki
Jump to: navigation, search
Line 11: Line 11:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
and changing class properties before deploy
+
and changing class properties before deploy. This could be a new type of Runcraft contracts, that we may call "clone contracts" since all children are a guaranteed clone of the parent (except for properties).
 
 
Here is the full code:
 
  
 +
# full code for for preventing overrides
  
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">

Revision as of 06:49, 30 January 2026

Here is a solution that we found to prevent child classes (using extends) to override some specific methods from your parent class.

Note that this has limitations though: it does NOT prevent someone to create a new function and implement any behavior they want. So while they may not be able to change how send() works, they can still create send2() and do anything they want in it.

We might be able to use this to prevent also adding any other function, essentially only allowing


class MyToken extends MyParentTokenClass {}

and changing class properties before deploy. This could be a new type of Runcraft contracts, that we may call "clone contracts" since all children are a guaranteed clone of the parent (except for properties).

  1. full code for for preventing overrides
class MyParentTokenClass extends Token {

    init(amount, owner) {
        const extended = this.constructor != MyParentTokenClass
        if (!extended) throw new Error('This contract must be extended');

        super.init(amount, owner);

        // --- prevent redefining send() in child classes

        // basic version (prevent overriding only one method like "send")
        // const proto = Object.getPrototypeOf(this);
        // if (proto.send !== MyParentTokenClass.prototype.send) {
        //     throw new Error(`Method 'send' cannot be overridden by class ${this.constructor.name}`);
        // }

        // advanced version, for ex to prevent overriding all methods
        function getDeepPropertyNames(obj) {
            const props = new Set(); // set to keep unique names
            let current = obj;
            while (current && current !== Object.prototype) {
                // Get all string properties (including non-enumerable ones)
                const ownProps = Object.getOwnPropertyNames(current);
                ownProps.forEach(prop => props.add(prop));
                // Move one level up the chain
                current = Object.getPrototypeOf(current);
            }
            return Array.from(props);
        }
        const childProto = Object.getPrototypeOf(this);
        const parentProto = Object.getPrototypeOf(childProto);
        const childMethods = Object.getOwnPropertyNames(parentProto);
        // console.log("childMethods:", childMethods);
        const parentMethods = getDeepPropertyNames(MyParentTokenClass.prototype);
        // console.log("parentMethods:", parentMethods)
        let overriddenMethods = childMethods.filter(method =>
            parentMethods.includes(method) && method !== 'constructor'
            // && method !== 'init' // unsure if we want to allow overriding init() or not, might depend on case
        );
        console.log("overriddenMethods:", overriddenMethods);
        if (overriddenMethods.includes('send')) {
            throw new Error(
                `Error: Subclass "${this.constructor.name}" is not allowed to override the critical methods: [${overriddenMethods}]. ` +
                `These methods must remain as implemented in the base class.`
            );
        }
        // console.log(`-- No forbidden overrides detected in "${this.constructor.name}". --\n`);
    }
}