tact-lang / tact

Tact compiler main repository
https://tact-lang.org
MIT License
281 stars 56 forks source link

Custom Modifier Functions #513

Closed ZigBalthazar closed 8 hours ago

ZigBalthazar commented 6 days ago

sometimes some smart contract functions need to check some conditions modify some states out of function scope or add some check or logic to function without changing the function body(decorator pattern). for example: if we needed to check the owner's permission for this receive:

    receive(msg: Mint) {
        require(self.mintable, "Can't Mint Anymore");
        self.mint(msg.receiver, msg.amount, self.owner); // (to, amount, response_destination)
    }

we have to load the context and then check the sender's address to be equal to the owner's address by using require :

    receive(msg: Mint) {
        let ctx: Context = context();
        require(ctx.sender == self.owner, "Not Owner");
        require(self.mintable, "Can't Mint Anymore");
        self.mint(msg.receiver, msg.amount, self.owner); // (to, amount, response_destination)
    }

if we can define some modifier(or decorator) function to add the owner checking condition to this receive we get these points:

  1. DRY: the load context and owner checking do not repeat over and over and will be reusable
  2. the functions will be isolated and Singelton to do just one logic
  3. this feature lets developers to write more flexible codes

in Tact I think(not sure) some reserved words like virtual or get are like modifiers(decorator) because they change the function behavior(override: let function to be overridden and, get: let function to be public). I suggest having some modifiers to allow developers to write the logic. for example(regarding to prev example):

    modifier fun onlyOwner() {
        let ctx: Context = context();
        require(ctx.sender == self.owner, "Not Owner");
        // maybe use _; like solidity(just for example :)) or return boolean
    }

now write the mint receive again:

    receive(msg: Mint) onlyOwner() {
        require(self.mintable, "Can't Mint Anymore");
        self.mint(msg.receiver, msg.amount, self.owner); // (to, amount, response_destination)
    }

I can reference you to the Solidity Modifier or the Nestjs decorators

anton-trunov commented 6 days ago

If you import @stdlib/ownable and use the Ownable trait, you get something like this:

import "@stdlib/ownable";

contract Foo with Ownable {
  ...
  init(owner: Address) { self.owner = owner }

  receive(msg: Mint) {
    self.requireOwner();  // -- very close to a decorator, but does not complicate the language further
    require(self.mintable, "Can't Mint Anymore");
    self.mint(msg.receiver, msg.amount, self.owner); // (to, amount, response_destination)
  }
}
anton-trunov commented 8 hours ago

Closing this for now, feel free to re-open if there are more considerations