Skip to content

Commit

Permalink
feat: example payload
Browse files Browse the repository at this point in the history
  • Loading branch information
fxp3 committed Mar 22, 2024
1 parent fe6f11f commit dcb378c
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 96 deletions.
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ set positional-arguments
forge script SafeScript --sig "sendBatch(string)" $2 --ffi -vvv && \
echo "-> Sent!"

@safe-add batchfile:
@safe-file batchfile:
echo "-> Sending $1.." && \
bun utils/ffi.ts proposeBatch $1 && \
echo "-> Sent!"
Expand Down
41 changes: 41 additions & 0 deletions src/contracts/scripts/SafeExample.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ArbScript} from "scripts/utils/ArbScript.s.sol";
import {Help, Log} from "kresko-lib/utils/Libs.s.sol";
import {SwapRouteSetter} from "scdp/STypes.sol";
import {scdp} from "scdp/SState.sol";
import {ArbDeployAddr} from "kresko-lib/info/ArbDeployAddr.sol";
import {deployPayload} from "scripts/locals/Payloads.sol";
import {IExtendedDiamondCutFacet} from "diamond/interfaces/IDiamondCutFacet.sol";

// solhint-disable no-empty-blocks, reason-string, state-visibility

contract ExamplePayload0002 is ArbDeployAddr {
function executePayload() public {
require(USDC.allowance(safe, kreskoAddr) == 0, "allowance not 0");
require(!scdp().isRoute[krETHAddr][kissAddr], "route is not disabled");
scdp().isRoute[krETHAddr][kissAddr] = true;
}
}

contract SafeExample is ArbScript {
using Log for *;
using Help for *;

modifier setUp() {
ArbScript.initialize();
fetchPythAndUpdate();
_;
}

function payload0002() public setUp broadcasted(safe) {
USDC.approve(kreskoAddr, 1);
USDC.approve(kreskoAddr, 0);
kresko.setSingleSwapRouteSCDP(SwapRouteSetter({assetIn: krETHAddr, assetOut: kissAddr, enabled: false}));
IExtendedDiamondCutFacet(kreskoAddr).executeInitializer(
deployPayload(type(ExamplePayload0002).creationCode, "", 2),
abi.encodeWithSelector(ExamplePayload0002.executePayload.selector)
);
}
}
10 changes: 9 additions & 1 deletion src/contracts/scripts/deploy/libs/Deployed.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ library Deployed {

struct Cache {
string deployId;
address factory;
mapping(string => address) cache;
mapping(string => Asset) assets;
}
Expand All @@ -20,7 +21,14 @@ library Deployed {
}

function factory() internal returns (IDeploymentFactory) {
return IDeploymentFactory(addr("Factory"));
if (state().factory == address(0)) {
return IDeploymentFactory(addr("Factory"));
}
return IDeploymentFactory(state().factory);
}

function factory(address _factory) internal {
state().factory = _factory;
}

function addr(string memory name, uint256 chainId) internal returns (address) {
Expand Down
4 changes: 2 additions & 2 deletions src/contracts/scripts/deploy/libs/LibDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,9 @@ library LibDeploy {
state().disableLog = true;
}

function factory() internal returns (IDeploymentFactory) {
function factory() internal returns (IDeploymentFactory factory_) {
if (address(state().factory) == address(0)) {
state().factory = IDeploymentFactory(Deployed.addr("Factory"));
state().factory = Deployed.factory();
}
return state().factory;
}
Expand Down
5 changes: 5 additions & 0 deletions src/contracts/scripts/utils/ArbScript.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {Asset, Enums, Oracle, RawPrice} from "common/Types.sol";
import {ArbDeployAddr} from "kresko-lib/info/ArbDeployAddr.sol";
import {IAggregatorV3} from "kresko-lib/vendor/IAggregatorV3.sol";
import {Anvil} from "./Anvil.s.sol";
import {Deployed} from "scripts/deploy/libs/Deployed.s.sol";

// solhint-disable state-visibility, max-states-count, var-name-mixedcase, no-global-import, const-name-snakecase, no-empty-blocks, no-console

Expand Down Expand Up @@ -54,15 +55,18 @@ contract ArbScript is Anvil, Scripted, ArbDeployAddr {
function initialize(string memory mnemonic) public {
useMnemonic(mnemonic);
vm.createSelectFork("arbitrum");
Deployed.factory(factoryAddr);
}

function initialize() public {
useMnemonic("MNEMONIC_DEPLOY");
vm.createSelectFork("arbitrum");
Deployed.factory(factoryAddr);
}

function initFork(address sender) internal returns (uint256 forkId) {
forkId = vm.createSelectFork("localhost");
Deployed.factory(factoryAddr);
Anvil.syncTime(0);
vm.makePersistent(address(pythEP));
broadcastWith(sender);
Expand All @@ -86,6 +90,7 @@ contract ArbScript is Anvil, Scripted, ArbDeployAddr {

function initLocal(uint256 blockNr, bool sync) public {
useMnemonic("MNEMONIC_DEPLOY");
Deployed.factory(factoryAddr);
vm.createSelectFork("arbitrum", blockNr);
if (sync) syncTimeLocal();
}
Expand Down
101 changes: 20 additions & 81 deletions utils/ffi-safe.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
import { writeFileSync } from 'node:fs'
import TrezorConnect from '@trezor/connect'
import { glob } from 'glob'
import {
Address,
Hex,
checksumAddress,
decodeAbiParameters,
encodeAbiParameters,
parseAbiParameters,
toFunctionSelector,
zeroAddress,
} from 'viem'
import type { BroadcastJSON, SafeInfoResponse } from './ffi-shared'
import { deploysBroadcasts, signaturesPath } from './ffi-shared'

const SAFE_API = 'https://safe-transaction-arbitrum.safe.global/api/v1/safes/'
const SAFE_API_V1 = 'https://safe-transaction-arbitrum.safe.global/api/v1/'

const txPayloadOutput = parseAbiParameters([
'Payloads result',
'struct Payload { address to; uint256 value; bytes data; }',
'struct PayloadExtra { string name; address contractAddr; string transactionType; string func; string funcSig; string[] args; address[] creations; uint256 gas; }',
'struct Payloads { Payload[] payloads; PayloadExtra[] extras; uint256 txCount; uint256 creationCount; uint256 totalGas; uint256 safeNonce; string safeVersion; uint256 timestamp; uint256 chainId; }',
])

const signPayloadInput = parseAbiParameters([
'Batch batch',
'struct Batch { address to; uint256 value; bytes data; uint8 operation; uint256 safeTxGas; uint256 baseGas; uint256 gasPrice; address gasToken; address refundReceiver; uint256 nonce; bytes32 txHash; bytes signature; }',
])

const signatureOutput = parseAbiParameters(['string,bytes,address'])
const proposeOutput = parseAbiParameters(['string,string'])
import {
SAFE_API,
SAFE_API_V1,
deploysBroadcasts,
getArg,
proposeOutput,
signPayloadInput,
signatureOutput,
signaturesPath,
txPayloadOutput,
} from './ffi-shared'
import { signData, signHash } from './ffi-signers'

const SAFE_ADDRESS = '0x266489Bde85ff0dfe1ebF9f0a7e6Fed3a973cEc3'
const CHAIN_ID = 42161

const DERIVATION_PATH = process.env.MNEMONIC_PATH || "m/44'/60'/0'/0/0"
export const types = {
EIP712Domain: [
{ name: 'verifyingContract', type: 'address' },
Expand All @@ -51,20 +44,12 @@ export const types = {
{ name: 'nonce', type: 'uint256' },
],
}
const get = async (op: (trezor: typeof TrezorConnect) => Promise<[signature: Hex, address: Address]>) => {
await TrezorConnect.init({
manifest: {
email: '[email protected]',
appUrl: 'https://kresko.fi',
},
})
return op(TrezorConnect)
}

const typedData = (safe: Address, message: any) => ({
types,
domain: {
verifyingContract: safe,
chainId: 42161,
chainId: CHAIN_ID,
},
primaryType: 'SafeTx' as const,
message: message,
Expand Down Expand Up @@ -158,57 +143,11 @@ export async function deleteBatch(txHash?: Hex) {
}

export async function safeSign(txHash?: Hex): Promise<[Hex, Address]> {
const [signature, signer] = await signHash(getArg(txHash))
const [signature, signer] = await signHash(txHash)
const v1 = parseInt(signature.slice(-2), 16) + 4
return [`${signature.slice(0, -2)}${v1.toString(16)}` as Hex, signer]
}

export function signHash(hash?: string) {
return get(async trezor => {
const result = await trezor.ethereumSignMessage({
message: getArg(hash),
hex: true,
path: DERIVATION_PATH,
})
if (!result.success) throw new Error(result.payload.error)
return [`0x${result.payload.signature}` as Hex, result.payload.address as Address]
})
}

export function signMessage(message?: string) {
return get(async trezor => {
const result = await trezor.ethereumSignMessage({
message: getArg(message),
hex: false,
path: DERIVATION_PATH,
})
if (!result.success) throw new Error(result.payload.error)
return [`0x${result.payload.signature}` as Hex, result.payload.address as Address]
})
}

export async function signData(data?: any) {
return get(async trezor => {
let arg = getArg(data)
if (typeof arg === 'string') {
arg = JSON.parse(arg)
}
const result = await trezor.ethereumSignTypedData({
path: DERIVATION_PATH,
data: arg,
metamask_v4_compat: true,
})
if (!result.success) throw new Error(result.payload.error)
return [`0x${result.payload.signature}` as Hex, result.payload.address as Address]
})
}

const getArg = <T>(arg?: T) => {
if (!arg) arg = process.argv[3] as T
if (!arg) throw new Error('No argument provided')
return arg
}

async function parseBroadcast(name: string, chainId: number, safeAddr: Address) {
const files = glob.sync(`${deploysBroadcasts}/broadcast/**/${chainId}/dry-run/${name}-latest.json`)
if (files.length !== 1) throw new Error(`Expected 1 file, got ${files.length}`)
Expand Down Expand Up @@ -275,8 +214,8 @@ const deleteData = (txHash: Hex) => ({
domain: {
name: 'Safe Transaction Service',
version: '1.0',
chainId: 42161,
verifyingContract: '0x266489Bde85ff0dfe1ebF9f0a7e6Fed3a973cEc3',
chainId: CHAIN_ID,
verifyingContract: SAFE_ADDRESS,
},
message: {
safeTxHash: txHash,
Expand Down
34 changes: 33 additions & 1 deletion utils/ffi-shared.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'path'
import type { Address, Hex } from 'viem'
import { type Address, type Hex, parseAbiParameters } from 'viem'

export type SignResult = [signature: Hex, address: Address]
type TxType = 'CALL' | 'CREATE' | 'CREATE2'
type AdditionalContract = {
transactionType: TxType
Expand Down Expand Up @@ -87,6 +88,12 @@ export const deploysBroadcasts = `${root}/out/foundry`

export const signaturesPath = `${process.cwd()}/temp/sign/`

export const getArg = <T>(arg?: T) => {
if (!arg) arg = process.argv[3] as T
if (!arg) throw new Error('No argument provided')
return arg
}

export function success(str: string | any[]) {
if (!str?.length) process.exit(0)
if (Array.isArray(str)) {
Expand All @@ -113,3 +120,28 @@ export function error(str: string) {
process.exitCode = 1
setTimeout(() => process.exit(1), 1)
}

export enum Signer {
Trezor,
Frame,
Cast,
}
export type Method = 'personal_sign' | 'eth_sign' | 'eth_signTypedData_v4'

export const SAFE_API = 'https://safe-transaction-arbitrum.safe.global/api/v1/safes/'
export const SAFE_API_V1 = 'https://safe-transaction-arbitrum.safe.global/api/v1/'

export const txPayloadOutput = parseAbiParameters([
'Payloads result',
'struct Payload { address to; uint256 value; bytes data; }',
'struct PayloadExtra { string name; address contractAddr; string transactionType; string func; string funcSig; string[] args; address[] creations; uint256 gas; }',
'struct Payloads { Payload[] payloads; PayloadExtra[] extras; uint256 txCount; uint256 creationCount; uint256 totalGas; uint256 safeNonce; string safeVersion; uint256 timestamp; uint256 chainId; }',
])

export const signPayloadInput = parseAbiParameters([
'Batch batch',
'struct Batch { address to; uint256 value; bytes data; uint8 operation; uint256 safeTxGas; uint256 baseGas; uint256 gasPrice; address gasToken; address refundReceiver; uint256 nonce; bytes32 txHash; bytes signature; }',
])

export const signatureOutput = parseAbiParameters(['string,bytes,address'])
export const proposeOutput = parseAbiParameters(['string,string'])
Loading

0 comments on commit dcb378c

Please sign in to comment.