Title: _Wand__aka__DSProxy_2_0_
Category: Magicians

Author: nikolai
Date: 2021-10-06T12:12:32.108Z

github.com




GitHub - nmushegian/wand: DSProxy 2.0
DSProxy 2.0






This is a grab bag of proxy contract R&D. I’m publishing this to help bump priority for SET_INDESTRICTUBLE.
Code sketch and README at time of publishing are copied below:

interface WandAuth {
  function canCast(address witch, address spell, bytes4 sigil)
    external returns (bool);
}

contract Wand {
  address public root;
  address public auth;
  address public code;
  address public lock;

  function cast(address spell, bytes calldata data)
    payable public
      returns (bool bit, bytes memory ret)
  {
    require(lock == ZERO, 'ERR_LOCK');
    lock = msg.sender;

    bytes4 sigil; assembly{ sigil := calldataload(data.offset) }

    if (msg.sender != root) {
      require(auth != ZERO, 'ERR_ROOT');
      require(WandAuth(auth).canCast(msg.sender, spell, sigil), 'ERR_AUTH');
    }

    code = spell;
    address root_ = root;
    address auth_ = auth;

    (bit, ret) = spell.delegatecall(data);

    if (msg.sender != root) {
      require(root == root_, 'ERR_SET_ROOT');
      require(auth == auth_, 'ERR_SET_AUTH');
    }
    code = ZERO;
    lock = ZERO;

    // // This doesn't work, but ideally there's some way to detect selfdestruct
    // uint256 size; assembly { size := codesize(); }
    // require(size > 0, 'ERR_BOOM');

    assembly{ log4(caller(), spell, sigil, bit, 0, 0) }
  }

  function give(address dest) public {
    require(lock == ZERO, 'ERR_LOCK');
    require(msg.sender == root, 'ERR_GIVE');
    root = dest;
    assembly{ log3(caller(), 'give', dest, 0, 0) }
  }

  function bind(address what) public {
    require(lock == ZERO, 'ERR_LOCK');
    require(msg.sender == root, 'ERR_BIND');
    auth = what;
    assembly{ log3(caller(), 'bind', what, 0, 0) }
  }

  constructor() {
    root = msg.sender;
    assembly{ log3(0, 'give', caller(), 0, 0) }
  }

  address constant internal ZERO = address(0);
}


A Wand is a variation of the Proxy pattern with some differences and extra features
canCast authorization

The authorization pattern for access controlled calls (non-root/owner callers) has
changed from DSProxy. Instead of using DSAuth to access control the proxy object itself,
root (owner) retains sole access to true “root” functions (transfer and update authority),
while cast applies the canCast access control table to the spell being cast. Concretely:

canCall(address caller, address object, bytes4 sig) -> (bool)
which was used like
function exec(address target, bytes calldata data) returns (bytes ret) {
  assert authority.canCall(msg.sender, address(this), msg.sig)`
  ...
}

becomes
`canCast(address caller, address spell, bytes4 sig)`

which is called like
function cast(address spell, bytes calldata data) returns (bool ok, bytes ret) {
  assert auth.canCast(msg.sender, spell, data[0:4]);
  ...
}

protected root and auth

caller-saved owner (root) and permission table (auth) makes spells
somewhat safer to use.

address root_ = root;
address auth_ = auth;

// this is in a fresh context, the local execution stack is not visible
(bit, ret) = spell.delegatecall(data);

require(auth == auth_, 'ERR_SUDO');
require(root == root_, 'ERR_SUDO')

(The remaining loss of root control danger lies in SELFDESTRUCT,
which is easier to statically detect
(mitigated in future by
EIP4571 SET_INDESTRUCTIBLE,
or any way to detect that code has been scheduled for selfdestruct)
reentry lock and caller reference

reentry lock saves the caller in storage and exposes it via function,
for access from both the spell being run external contracts. It is zero’d
for gas savings and consistency between spells

lock = msg.sender;
...

lock = ZERO;

spell (library) code reference

the code being delegatecalled (the spell that was cast) is also saved
in storage so that the spell knows the actual contract object which has
the code being run (libraries do not have a “library object” reference, but
they could! These would be like singleton stateful libraries). It is also zero’d
after each cast (and/but is “caller save” for spells calling spells)

code = spell;
...

code = ZERO;

Author: PaulRBerg
Date: 2021-10-08T16:31:43.277Z

Lots of good ideas here! Thanks for sharing.



 nikolai:

the code being delegatecalled (the spell that was cast) is also saved
in storage so that the spell knows the actual contract object which has
the code being run


When would this be useful? You mentioned the concept of “singleton stateful libraries” but I’ve never seen that implemented in Ethereum.

Author: poma
Date: 2021-10-12T15:58:14.024Z

Aren’t checks for LOCK variable redundant?

delegatecall can override it during execution anyway
Any changes to root and auth will be caught by guards after the delegatecall

Also auth and root are read 3 times before they are cached, can slightly save on gas there. I know it’s super minor but reading storage and then caching it immediately catches the attention.

Author: nikolai
Date: 2021-10-15T10:57:05.628Z

You are correct, see this issue and discussion: Reentry is impossible to prevent when delegate calling · Issue #3 · nmushegian/wand · GitHub