Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions scripts/aragon/agtTreasuryWithdrawalVote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { BigNumberish, Signer, providers, utils } from "ethers";

export interface CallScriptAction {
to: string;
data: string;
}

export interface AgtTreasuryDaoConfig {
tokenManager: string;
voting: string;
finance: string;
vault: string;
dai: string;
agt: string;
}

export interface BuildAgtTreasuryWithdrawalVoteParams {
recipient: string;
amount: BigNumberish;
metadata: string;
reference: string;
token?: string;
}

export interface BuiltAgtTreasuryWithdrawalVote {
paymentCalldata: string;
executionScript: string;
newVoteCalldata: string;
forwardScript: string;
tx: providers.TransactionRequest;
}

export const CALLS_SCRIPT_SPEC_ID = "0x00000001";

export const AGT_TREASURY_DAO: AgtTreasuryDaoConfig = {
tokenManager: utils.getAddress("0x8eFea71B63DB02C00229794d90ec4ba8ecD4Ea81"),
voting: utils.getAddress("0x93CF86a83bAA323407857C0A25e768869E12C721"),
finance: utils.getAddress("0x4f5762334100f6a80d35a616ef9df71b0452cf44"),
vault: utils.getAddress("0xFFE6280ae4E864D9aF836B562359FD828EcE8020"),
dai: utils.getAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"),
agt: utils.getAddress("0xb677136aC25e5e8763Eaf7f80D54de7D86313546"),
};

const financeInterface = new utils.Interface([
"function newImmediatePayment(address,address,uint256,string)",
]);
const votingInterface = new utils.Interface([
"function newVote(bytes,string,bool,bool)",
]);
const tokenManagerInterface = new utils.Interface([
"function forward(bytes evmScript)",
]);

export function encodeCallScript(actions: CallScriptAction[]): string {
const encodedActions = actions.map((action) => {
const target = utils.getAddress(action.to).slice(2).toLowerCase();
const dataLength = utils
.hexZeroPad(utils.hexlify(utils.hexDataLength(action.data)), 4)
.slice(2);

return `${target}${dataLength}${action.data.slice(2)}`;
});

return `${CALLS_SCRIPT_SPEC_ID}${encodedActions.join("")}`;
}

export function decodeCallScript(script: string): CallScriptAction[] {
if (!script.startsWith(CALLS_SCRIPT_SPEC_ID)) {
throw new Error("Unsupported EVMScript spec");
}

const actions: CallScriptAction[] = [];
let offset = CALLS_SCRIPT_SPEC_ID.length;

while (offset < script.length) {
const to = utils.getAddress(`0x${script.slice(offset, offset + 40)}`);
offset += 40;

const length = parseInt(script.slice(offset, offset + 8), 16);
offset += 8;

const data = `0x${script.slice(offset, offset + length * 2)}`;
offset += length * 2;

actions.push({ to, data });
}

return actions;
}

export function buildAgtTreasuryWithdrawalVote(
params: BuildAgtTreasuryWithdrawalVoteParams
): BuiltAgtTreasuryWithdrawalVote {
const token = utils.getAddress(params.token ?? AGT_TREASURY_DAO.dai);
const recipient = utils.getAddress(params.recipient);

const paymentCalldata = financeInterface.encodeFunctionData(
"newImmediatePayment(address,address,uint256,string)",
[token, recipient, params.amount, params.reference]
);

const executionScript = encodeCallScript([
{ to: AGT_TREASURY_DAO.finance, data: paymentCalldata },
]);

const newVoteCalldata = votingInterface.encodeFunctionData(
"newVote(bytes,string,bool,bool)",
[executionScript, params.metadata, false, true]
);

const forwardScript = encodeCallScript([
{ to: AGT_TREASURY_DAO.voting, data: newVoteCalldata },
]);

return {
paymentCalldata,
executionScript,
newVoteCalldata,
forwardScript,
tx: {
to: AGT_TREASURY_DAO.tokenManager,
data: tokenManagerInterface.encodeFunctionData("forward", [forwardScript]),
value: 0,
},
};
}

export async function createAgtTreasuryWithdrawalVote(
signer: Signer,
params: BuildAgtTreasuryWithdrawalVoteParams
) {
const built = buildAgtTreasuryWithdrawalVote(params);
return signer.sendTransaction(built.tx);
}
136 changes: 136 additions & 0 deletions scripts/aragon/createTreasuryWithdrawalVote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { ethers } from "ethers";
import * as dotenv from "dotenv";
import {
AGT_TREASURY_DAO,
buildAgtTreasuryWithdrawalVote,
createAgtTreasuryWithdrawalVote,
} from "./agtTreasuryWithdrawalVote";

dotenv.config();

interface CliArgs {
[key: string]: string | boolean | undefined;
}

function parseArgs(argv: string[]): CliArgs {
const parsed: CliArgs = {};

for (let i = 0; i < argv.length; i++) {
const current = argv[i];
if (!current.startsWith("--")) continue;

const key = current.slice(2);
const next = argv[i + 1];

if (!next || next.startsWith("--")) {
parsed[key] = true;
continue;
}

parsed[key] = next;
i += 1;
}

return parsed;
}

function requireStringArg(args: CliArgs, key: string): string {
const value = args[key];
if (!value || typeof value !== "string") {
throw new Error(`Missing required argument --${key}`);
}
return value;
}

function getPrivateKey(args: CliArgs): string {
const fromArgs = args["private-key"];
const raw =
(typeof fromArgs === "string" ? fromArgs : undefined) ||
process.env.PRIVATE_KEY ||
process.env.SECRET;

if (!raw) {
throw new Error("Missing private key. Pass --private-key or set PRIVATE_KEY/SECRET.");
}

return raw.startsWith("0x") ? raw : `0x${raw}`;
}

function getAmount(args: CliArgs) {
const amountWei = args["amount-wei"];
if (typeof amountWei === "string") {
return ethers.BigNumber.from(amountWei);
}

const amount = requireStringArg(args, "amount");
return ethers.utils.parseUnits(amount, 18);
}

async function main() {
const args = parseArgs(process.argv.slice(2));
const recipient = requireStringArg(args, "recipient");
const amount = getAmount(args);
const token =
typeof args.token === "string" ? args.token : AGT_TREASURY_DAO.dai;
const metadata =
typeof args.metadata === "string"
? args.metadata
: `Withdraw ${ethers.utils.formatUnits(amount, 18)} DAI from Treasury 2`;
const reference =
typeof args.reference === "string" ? args.reference : metadata;

const built = buildAgtTreasuryWithdrawalVote({
recipient,
amount,
metadata,
reference,
token,
});

console.log("Treasury withdrawal vote payload");
console.log(JSON.stringify({
tokenManager: AGT_TREASURY_DAO.tokenManager,
voting: AGT_TREASURY_DAO.voting,
finance: AGT_TREASURY_DAO.finance,
token,
recipient,
amountWei: amount.toString(),
metadata,
reference,
tx: built.tx,
executionScript: built.executionScript,
forwardScript: built.forwardScript,
}, null, 2));

if (!args.broadcast) {
console.log("\nDry run only. Re-run with --broadcast to submit the vote.");
return;
}

const rpcUrl =
(typeof args["rpc-url"] === "string" ? args["rpc-url"] : undefined) ||
process.env.MAINNET_URL;
if (!rpcUrl) {
throw new Error("Missing rpc url. Pass --rpc-url or set MAINNET_URL.");
}

const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
const signer = new ethers.Wallet(getPrivateKey(args), provider);

const tx = await createAgtTreasuryWithdrawalVote(signer, {
recipient,
amount,
metadata,
reference,
token,
});

console.log(`\nSubmitted transaction: ${tx.hash}`);
const receipt = await tx.wait();
console.log(`Mined in block ${receipt.blockNumber}`);
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
Loading
Loading