Skip to content

Instantly share code, notes, and snippets.

@izqui
Last active September 20, 2019 13:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save izqui/54875e896838be894a8a4a9cab52f86e to your computer and use it in GitHub Desktop.
Save izqui/54875e896838be894a8a4a9cab52f86e to your computer and use it in GitHub Desktop.
pragma experimental ABIEncoderV2;
// Some fake interfaces so it compiles
interface IACL {
function initialize(address permissionsCreator) external;
function createPermission(address _entity, address _app, bytes32 _role, address _manager) external;
function grantPermission(address _entity, address _app, bytes32 _role) external;
function hasPermission(address who, address where, bytes32 what, bytes calldata how) external view returns (bool);
}
interface ERCProxy {}
interface ERC20 {
function transfer(address to, uint256 amount) external returns (bool);
}
interface Agent {
function initialize() external;
}
interface IKernel {
function acl() external view returns (IACL);
function hasPermission(address who, address where, bytes32 what, bytes calldata how) external view returns (bool);
function newAppInstance(bytes32 _appId, address _appBase, bytes calldata _initializePayload, bool _setDefault) external returns (ERCProxy appProxy);
function setApp(bytes32 namespace, bytes32 appId, address app) external;
function getApp(bytes32 namespace, bytes32 appId) external view returns (address);
}
contract AragonApp {
modifier auth(string memory role) {_;}
modifier onlyInit {_;}
function kernel() public view returns (IKernel);
}
// App that would be installed in DAOs that want to use one transaction/vote installs
contract AppCenter is AragonApp {
struct RegisteredInstall {
InstallerBase installer; // Perhaps allow an array of installers as well (in case the installer code is too big)
uint256 nextInstall;
bytes32[] callHashes;
}
RegisteredInstall[] public registeredInstalls;
event Installed(address indexed installer, bytes data);
event Registered(uint256 installId, address indexed installer, bytes[] calls);
function executeInstaller(InstallerBase installer, bytes calldata data) auth("EXECUTE_INSTALLER") external {
_executeInstaller(installer, data);
}
// Allows multi-transaction
function registerInstall(InstallerBase installer, bytes[] calldata calls) auth("REGISTER_INSTALLS") external returns (uint256 installId) {
bytes32[] memory callHashes = new bytes32[](calls.length);
for (uint256 i = 0; i < calls.length; i++) {
callHashes[i] = keccak256(abi.encodePacked(calls[i]));
}
installId = registeredInstalls.push(RegisteredInstall(installer, 0, callHashes)) - 1;
emit Registered(installId, address(installer), calls);
}
// Unprotected function
function executeRegisteredInstall(uint256 installId, uint256 callId, bytes calldata data) external {
require(registeredInstalls.length > installId);
RegisteredInstall storage registeredInstall = registeredInstalls[installId];
require(registeredInstall.nextInstall == callId);
require(registeredInstall.callHashes[callId] == keccak256(abi.encodePacked(data)));
registeredInstall.nextInstall += 1;
_executeInstaller(registeredInstall.installer, data);
}
function _executeInstaller(InstallerBase installer, bytes memory data) internal {
(bool ok,) = address(installer).delegatecall(data);
require(ok);
emit Installed(address(installer), data);
}
}
// Example installer for installing Agent into an org
contract InstallerBase is AragonApp {
function getBaseFromAPM(bytes32 appId) internal returns (address) {
return address(0); // TODO
}
}
contract AgentInstaller is InstallerBase {
bytes32 internal constant AGENT_APP_ID = 0x00;
bytes32 internal constant AGENT_EXEC_ROLE = 0x00;
ERC20 public constant ANT = ERC20(0);
uint256 public constant DEV_FEE = 10 ether; // 10 ANT
address public constant AGENT_DEV = address(0);
function install(address[] calldata allowedExecutors, address permissionManager) external onlyInit {
IKernel dao = kernel();
IACL acl = dao.acl();
Agent agent = Agent(address(dao.newAppInstance(AGENT_APP_ID, getBaseFromAPM(AGENT_APP_ID), new bytes(0), false)));
agent.initialize();
for (uint256 i = 0; i < allowedExecutors.length; i++) {
if (i == 0) {
acl.createPermission(allowedExecutors[i], address(agent), AGENT_EXEC_ROLE, permissionManager);
} else {
// Even if the installer is not the manager, AppCenter is 'permissionManagerRoot' in the ACL (to be implemented) so it can grant the permission regardless
acl.grantPermission(allowedExecutors[i], address(agent), AGENT_EXEC_ROLE);
}
}
// Developers that create installers for easy installs can charge a fee
// Funds would be sent to the AppCenter app to pay for installs or a transfer from Finance can be done
// in the same vote before triggering the install
require(ANT.transfer(AGENT_DEV, DEV_FEE));
}
}
@facuspagnuolo
Copy link

I loved the idea! Looking forward to see more installers :)
Some comments/ideas:

  • I think we can avoid telling the callId from outside in executeRegisteredInstall, right? See here
  • Would be nice to have a function that executes several registered calls, could be inputted by the callee
  • We could also have specific uninstallers :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment