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 usingDSAuth
to access control the proxy object itself,
root
(owner) retains sole access to true “root” functions (transfer and update authority),
whilecast
applies thecanCast
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;