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

From RunWiki
Jump to: navigation, search
(Created page with "<syntaxhighlight lang="javascript"> class MyParentTokenClass extends Token { init(amount, owner) { const extended = this.constructor != MyParentTokenClass...")
 
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
 +
Here is a solution that we found to prevent child classes (using extends) to override some specific methods from your parent class.
 +
 +
Beware 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 method, essentially only allowing:
 +
 +
<syntaxhighlight lang="javascript">
 +
class MyToken extends MyParentTokenClass {}
 +
 +
</syntaxhighlight>
 +
 +
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).
 +
 +
Also, properties could be used to turn on/off specific features in the contract.
 +
 +
== full code for preventing overrides ==
 +
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
  
Line 12: Line 31:
  
 
         // basic version (prevent overriding only one method like "send")
 
         // basic version (prevent overriding only one method like "send")
 +
        // this will allow deploy but break on instance creation (as expected)
 
         // const proto = Object.getPrototypeOf(this);
 
         // const proto = Object.getPrototypeOf(this);
 
         // if (proto.send !== MyParentTokenClass.prototype.send) {
 
         // if (proto.send !== MyParentTokenClass.prototype.send) {
Line 17: Line 37:
 
         // }
 
         // }
  
         // advanced version, for ex to prevent overriding all methods
+
         // advanced version -> we check the entire prototype chain to avoid any bypass
 
         function getDeepPropertyNames(obj) {
 
         function getDeepPropertyNames(obj) {
 
             const props = new Set(); // set to keep unique names
 
             const props = new Set(); // set to keep unique names

Latest revision as of 18:13, 7 February 2026

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

Beware 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 method, 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).

Also, properties could be used to turn on/off specific features in the contract.

full code 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")
        // this will allow deploy but break on instance creation (as expected)
        // 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 -> we check the entire prototype chain to avoid any bypass
        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`);
    }
}