diff --git a/Anchor.toml b/Anchor.toml index 7cfe463..0c4745e 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -1,4 +1,5 @@ [toolchain] +anchor_version = "0.30.1" [features] resolution = true diff --git a/common/addresses.ts b/common/addresses.ts index 05f68b5..6cdfbbb 100644 --- a/common/addresses.ts +++ b/common/addresses.ts @@ -19,6 +19,10 @@ export interface DataFeed { export interface TokenAddresses { acRole?: PublicKey; + acGlobalOverride?: { + ac: PublicKey; + acRole: PublicKey; + }; mToken?: PublicKey; tokenAuthority?: { seed: string; @@ -99,6 +103,28 @@ export const addresses: Record = { account: new PublicKey('DNJMfdgrrVHKp1nFY5Qoqq14erqzdJoMve5THgKpCkrb'), }, }, + [MProduct.PSV]: { + acRole: new PublicKey('77YMLUMHD5Pdq2qvYCjTNL5oP1Z5hKPK4yuak2EaLJya'), + acGlobalOverride: { + ac: new PublicKey('6kGJVtfqxi2Jv5Ejb7W8UwWd9yuhckA69u9zjpRnVQiW'), + acRole: new PublicKey('9LWZYKZdNN6cBFf8Tu2NLUujgqZ2HZQ8ZsYJVUvnuHHr'), + }, + mToken: new PublicKey('H4hLpHyvjMiDytckLgAhRyTzHAoSYg2eQ9RGTUissayx'), + tokenAuthority: { + account: new PublicKey('AEk5FmQYH6uqxsiQRX6yUkyugLd9QVheSsP4eKFhZYyK'), + seed: 'psv-token-authority', + }, + mTokenDataFeed: new PublicKey('K5CPdTisCUonoqzjJH2NBiHRY7gU7oPxSS5wymmU78z'), + mTokenUnderlyingFeed: new PublicKey('3JQuSWEyd8CwcniXWfSWGgzrUQfcsTsgtGHe92pVaTHi'), + minter: { + commonVault: new PublicKey('GgmNCBisHT3SQ6aVyabPXyJE6ss8v4s23JFnBXJBzasz'), + account: new PublicKey('6FqbTK8xSiQPA5BLyzTkR2hjXPNN1jrut8qU333o8hea'), + }, + redeemer: { + commonVault: new PublicKey('93Qpf7sfihJr5a6HdZ5N53jqe7ieevxuEmLqdAVBRrK8'), + account: new PublicKey('DKp86fdtsZMbegJNcxH3ea9eGhahXDWsxaSXCe79MYXZ'), + }, + }, }, feeds: { [PaymentToken.USDC]: { @@ -107,6 +133,12 @@ export const addresses: Record = { dataFeed: new PublicKey('EY9TeqHx3QbKfSbZW7vZPNeg6Y8nwprsa9rm6okGCKpn'), underlyingFeed: new PublicKey('Dpw1EAVrSB1ibxiDQyTAW6Zip3J4Btk2x4SgApQCeFbX'), }, + [PaymentToken.wSOL]: { + token: new PublicKey('So11111111111111111111111111111111111111112'), + tokenProgram: TOKEN_PROGRAM_ID, + dataFeed: new PublicKey('3XCjjrbWFkiUmUs1i3MKk9GbXSAGQw7vZAhJb6XH3xCH'), + underlyingFeed: new PublicKey('H1kJWEqotQcdg2fiMNby1Fhtp44EZnCLFbuvwk7fmTBy'), + }, }, }, }; diff --git a/common/provider.ts b/common/provider.ts index 541f621..b1dd143 100644 --- a/common/provider.ts +++ b/common/provider.ts @@ -26,6 +26,28 @@ export interface CustomSignerModule { string | { sent: boolean; txId?: string; signedTransaction?: string; signature?: string } >; getSolanaWalletAddressForAction: (action: string, mtoken?: string, chainId?: string) => string; + createSolanaAddressBookContract: ({ + address, + contractName, + mToken, + chain, + contractTag, + }: { + address: string; + contractName: string; + mToken: string; + chain?: string; + contractTag?: string; + }) => Promise< + | { + sent: boolean; + txId?: undefined; + } + | { + sent: boolean; + txId: string; + } + >; } function createProvider(network: string, wallet: Wallet): AnchorProvider { diff --git a/common/scriptRunner.ts b/common/scriptRunner.ts index e9fa1ec..0cc4361 100644 --- a/common/scriptRunner.ts +++ b/common/scriptRunner.ts @@ -26,9 +26,30 @@ async function loadCustomSignerModule(): Promise { return { signSolanaTransaction: module.signSolanaTransaction, getSolanaWalletAddressForAction: module.getSolanaWalletAddressForAction, + createSolanaAddressBookContract: module.createSolanaAddressBookContract, }; } +export const createCustomSignerProvider = async ( + network: string, + action?: string, + mtoken?: string, +) => { + const isLocalnet = network.toLowerCase() === 'localnet'; + const useFordefi = !isLocalnet && !!action; + + const customSignerModule = useFordefi ? await loadCustomSignerModule() : undefined; + initCustomSigner(customSignerModule, network); + + const { provider, payer } = await createNetworkProvider( + network, + customSignerModule, + action, + mtoken, + ); + + return { provider, payer, customSignerModule }; +}; /** * Run a script with the appropriate wallet: * - localnet: always uses WALLET_PATH keypair @@ -41,21 +62,32 @@ export async function executeNetworkScript( mtoken?: string, ): Promise { try { - const isLocalnet = network.toLowerCase() === 'localnet'; - const useFordefi = !isLocalnet && !!action; - - const customSignerModule = useFordefi ? await loadCustomSignerModule() : undefined; - initCustomSigner(customSignerModule, network); - - const { provider, payer } = await createNetworkProvider( - network, - customSignerModule, - action, - mtoken, - ); - + const { provider, payer } = await createCustomSignerProvider(network, action, mtoken); await scriptFn(provider, payer, network); } catch (error) { handleError(error); } } + +export const createSolanaAddressBookContract = async ({ + network, + address, + contractName, + mToken, + contractTag, +}: { + network: string; + address: string; + contractName: string; + mToken: string; + contractTag?: string; +}) => { + const { customSignerModule } = await createCustomSignerProvider(network, 'deployer'); + return await customSignerModule.createSolanaAddressBookContract({ + address, + contractName, + mToken, + chain: network, + contractTag, + }); +}; diff --git a/common/tokenTypes.ts b/common/tokenTypes.ts index 618fd05..5b8a2a8 100644 --- a/common/tokenTypes.ts +++ b/common/tokenTypes.ts @@ -1,11 +1,13 @@ export enum MProduct { MTBILL = 'mTBILL', MFONE = 'mFONE', + PSV = 'pSV', } export enum PaymentToken { USDC = 'USDC', USDT = 'USDT', + wSOL = 'wSOL', } export function isMProduct(value: string): value is MProduct { diff --git a/package.json b/package.json index 0f71af3..ab05790 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "deploy:global-ac-role": "tsx scripts/tasks/deploy/network/deploy-global-ac-role.ts", "deploy:global-ac": "tsx scripts/tasks/deploy/network/deploy-global-ac.ts", "deploy:token-ac-role": "tsx scripts/tasks/deploy/deploy-token-ac-role.ts", + "deploy:token-ac:global-override": "tsx scripts/tasks/deploy/deploy-global-ac-override.ts", + "deploy:token-ac-role:global-override": "tsx scripts/tasks/deploy/deploy-global-ac-role-override.ts", "deploy:token-mint": "tsx scripts/tasks/deploy/deploy-token-mint.ts", "deploy:token-authority": "tsx scripts/tasks/deploy/deploy-token-authority.ts", "deploy:token-datafeed": "tsx scripts/tasks/deploy/deploy-token-datafeed.ts", @@ -31,7 +33,9 @@ "transfer:authority": "tsx scripts/tasks/manage/transfer-authority.ts", "update:data-feed": "tsx scripts/tasks/manage/update-data-feed.ts", "update:manual-feed-price": "tsx scripts/tasks/manage/update-manual-feed-price.ts", - "delegate": "tsx scripts/tasks/manage/delegate.ts" + "delegate": "tsx scripts/tasks/manage/delegate.ts", + "pause:functions": "tsx scripts/tasks/manage/pause-functions.ts", + "add:to-addressbook": "tsx scripts/tasks/manage/add-to-addressbook.ts" }, "dependencies": { "@coral-xyz/anchor": "0.30.1", diff --git a/scripts/README.md b/scripts/README.md index 5ec0ae9..7abaa92 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -20,6 +20,11 @@ Run once per network before deploying any tokens: ### Token Deployment +If you need separated greenlist for a token, first deploy ac global overrides: + +1. `yarn deploy:token-ac-role:global-override --mtoken --network ` +2. `yarn deploy:token-ac:global-override --mtoken --network ` + Run in order for each token: 1. `yarn deploy:token-ac-role --mtoken --network ` diff --git a/scripts/configs/roles-types.ts b/scripts/configs/roles-types.ts index 996d78e..cc8aaeb 100644 --- a/scripts/configs/roles-types.ts +++ b/scripts/configs/roles-types.ts @@ -44,11 +44,11 @@ export const ROLE_GROUPS = { SOLANA_ROLES.M_MINTER, // Manual mint tokens SOLANA_ROLES.M_BURNER, // Manual burn tokens SOLANA_ROLES.M_FREEZER, // Freeze/thaw accounts - SOLANA_ROLES.VAULT_PAUSER, // Pause vault operations ], VAULTS_MANAGER: [ SOLANA_ROLES.VAULT_ADMIN, // Manage both minter and redeemer vaults + SOLANA_ROLES.VAULT_PAUSER, // Pause vault operations ], ORACLE_MANAGER: [ diff --git a/scripts/configs/tokens/index.ts b/scripts/configs/tokens/index.ts index 6b9053a..be26aa4 100644 --- a/scripts/configs/tokens/index.ts +++ b/scripts/configs/tokens/index.ts @@ -3,8 +3,10 @@ import { TokenConfigWithNetworks } from '@/scripts/configs/types'; import { mFONEConfig } from './mFONE'; import { mTBILLConfig } from './mTBILL'; +import { pSVConfig } from './pSV'; export const tokenConfigs: Partial> = { [MProduct.MTBILL]: mTBILLConfig, [MProduct.MFONE]: mFONEConfig, + [MProduct.PSV]: pSVConfig, }; diff --git a/scripts/configs/tokens/pSV.ts b/scripts/configs/tokens/pSV.ts new file mode 100644 index 0000000..d6346e4 --- /dev/null +++ b/scripts/configs/tokens/pSV.ts @@ -0,0 +1,80 @@ +import { PaymentToken } from '@/common/tokenTypes'; +import { TokenConfigWithNetworks } from '@/scripts/configs/types'; +import { UNLIMITED } from '@/scripts/constants/pricing'; +import { VaultActionIds } from '@/test/constants/vaults.constants'; + +export const pSVConfig: TokenConfigWithNetworks = { + // Shared configuration (same across all networks) + metadata: { + name: 'Private Strategy Vault', + symbol: 'pSV', + decimals: 9, + uri: 'https://raw.githubusercontent.com/midas-apps/midas-assets/refs/heads/main/solana/psv-metadata', + }, + tokenAuthority: { + seed: 'psv-token-authority', + }, + // Network-specific configurations + networks: { + mainnet: { + dataFeed: { + // Oracle tolerance: 1.04% + mode: 'manual', + minPrice: '0.99', + maxPrice: '1.03', + initialPrice: '1', + maxStaleness: 2592000, + }, + minter: { + instantFee: '0', + instantDailyLimit: UNLIMITED, + variationTolerance: '0.2', // 20 bps + minAmount: '0', + firstMintMinMTokens: '0', + greenListEnforced: true, + feeReceiver: 'FCp91ChRdqcwZBp6N5R3ZrxbCMi7mQvvLntFZ27sBdkW', + tokensReceiver: 'HPdZXFUCcAmbnRh7sYHGWZFvhVhr5eC68vKuNcpo7So7', + paymentTokens: [ + { + symbol: PaymentToken.wSOL, + fee: '0', + allowance: '10000000', + stable: true, + isFiat: false, + }, + ], + }, + redeemer: { + instantFee: '0', // 100 bps (1%) + instantDailyLimit: UNLIMITED, + variationTolerance: '0.2', // 20 bps + minAmount: '0', + minFiatRedeemAmount: '10', + fiatFlatFee: '30', + greenListEnforced: true, + feeReceiver: '2rpnUduUCHRB9gCr3zmDQFgr5KSReLiN56QjNJ82D7vv', + tokensReceiver: 'HPdZXFUCcAmbnRh7sYHGWZFvhVhr5eC68vKuNcpo7So7', + requestRedeemer: '3DAz5Sdwaofm2FtM3bFSGdwihgmmwn4YhPNWLcXQ5BRc', + paymentTokens: [ + { + symbol: PaymentToken.wSOL, + fee: '0', + allowance: '10000000', + stable: true, + isFiat: false, + }, + ], + }, + grantRoles: { + tokenManagerAddress: '4qeuVmTwFRo1ZF7eVQXZNDNVFT33eav6KNP9xjSKQmZB', + vaultsManagerAddress: 'QRLkMrM5jfEmS6kmBBEgfDo97VariSWiuoCn1WkmBpj', + oracleManagerAddress: '28pe6ahpsFAo5DConqyw46Z1qAKtMNcqSKQ6iwwBYBcD', + metadataAuthority: '77F5WP7E9PE3cRbUXGZ8W8S2zvSGvb2WS7QuVGYpavug', + }, + pauseFunctions: { + redeemer: [VaultActionIds.REDEEM_REQUEST_FIAT], + minter: [VaultActionIds.MINT_REQUEST], + }, + }, + }, +}; diff --git a/scripts/configs/tokens/payment-tokens.ts b/scripts/configs/tokens/payment-tokens.ts index 146f535..fba1688 100644 --- a/scripts/configs/tokens/payment-tokens.ts +++ b/scripts/configs/tokens/payment-tokens.ts @@ -45,4 +45,23 @@ export const paymentTokenConfigs: Partial { diff --git a/scripts/deploy/feeds/manual.ts b/scripts/deploy/feeds/manual.ts index e5bada6..66e1232 100644 --- a/scripts/deploy/feeds/manual.ts +++ b/scripts/deploy/feeds/manual.ts @@ -1,10 +1,15 @@ +import { Wallet } from '@coral-xyz/anchor'; import { Keypair, PublicKey, Transaction } from '@solana/web3.js'; +import { createCustomSignerProvider } from '@/common/scriptRunner'; import { sendAndWaitForCustomSolanaTxSign } from '@/common/solanaTxHelper'; -import { PRICE_DECIMALS } from '@/scripts/constants/pricing'; import { AC_ROLES } from '@/test/constants/ac.constants'; import { DATA_FEED_AC_ROLES } from '@/test/constants/data-feed.constants'; -import { getAccountAcRoleStatePda, acRoleToBuffer } from '@/test/helpers/ac.helpers'; +import { + getAccountAcRoleStatePda, + acRoleToBuffer, + fetchAccountAcRoleState, +} from '@/test/helpers/ac.helpers'; import { toBN } from '@/test/helpers/common.helpers'; import { getManualFeedStatePda } from '@/test/helpers/data-feed.helpers'; @@ -23,6 +28,8 @@ export interface DeployManualFeedParams { maxPrice: bigint; maxStaleness: number; initialPrice?: bigint; + isPaymentToken?: boolean; + existingDataFeed?: PublicKey; } /** @@ -34,7 +41,7 @@ export async function deployManualFeed( common: CommonParams, params: DeployManualFeedParams, ): Promise { - const { provider, payer } = common; + let { provider, payer } = common; // If underlyingFeed is missing, create manual feed if (!params.underlyingFeed) { @@ -42,7 +49,9 @@ export async function deployManualFeed( const baseFeedKeypair = Keypair.generate(); // Calculate the manual feed PDA that will be created later // This is safe because PDAs are deterministic - const manualFeedPda = getManualFeedStatePda(baseFeedKeypair.publicKey); + const manualFeedPda = getManualFeedStatePda( + params.existingDataFeed ?? baseFeedKeypair.publicKey, + ); const tempConfig: DeployDataFeedConfig = { acRole: params.acRole, @@ -54,7 +63,21 @@ export async function deployManualFeed( feed: baseFeedKeypair, }; - const feedPublicKey = await deployDataFeed(common, tempConfig); + const feedPublicKey = params.existingDataFeed + ? params.existingDataFeed + : await deployDataFeed(common, tempConfig); + + let action = 'deployer'; + let waitForTx = true; + + // if payment token is true - then acRole is global and should be executed from a different wallet + if (params.isPaymentToken) { + const res = await createCustomSignerProvider(common.network, 'update-feed-ptoken'); + provider = res.provider; + payer = res.payer as Wallet; + action = 'update-feed-ptoken'; + waitForTx = false; + } // Grant FEED_ADMIN role if not already granted const acProgram = getAcProgram(provider); @@ -64,51 +87,62 @@ export async function deployManualFeed( DATA_FEED_AC_ROLES.FEED_ADMIN, ); - // Attempt to grant FEED_ADMIN role (required for manual feed operations) - const grantRoleTx = new Transaction().add( - await acProgram.methods - .grantRole(acRoleToBuffer(DATA_FEED_AC_ROLES.FEED_ADMIN)) - .accountsPartial({ - account: payer.publicKey, - acRole: params.acRole, - authority: payer.publicKey, - authorityAcAdminRole: getAccountAcRoleStatePda( - params.acRole, - payer.publicKey, - AC_ROLES.ADMIN, - ), - accountAcRole: authorityAcRolePda, - }) - .instruction(), - ); - - try { - const roleResult = await sendAndWaitForCustomSolanaTxSign(provider, grantRoleTx, [], { - action: 'deployer', - comment: 'Grant FEED_ADMIN role for manual feed', - waitForTx: true, - }); - if (roleResult.signature) { - console.log(`Transaction signature: ${roleResult.signature}`); - } - console.log('✓ FEED_ADMIN role granted'); - } catch (error) { - // Check if error is "role already exists" - if so, continue - const errorMsg = error?.toString() || ''; - if (errorMsg.includes('already') || errorMsg.includes('AlreadyGranted')) { - console.log('ℹ FEED_ADMIN role already exists, continuing'); - } else { - throw error; + const feeAdminRole = await fetchAccountAcRoleState(acProgram, authorityAcRolePda, true); + + if (!feeAdminRole) { + // Attempt to grant FEED_ADMIN role (required for manual feed operations) + const grantRoleTx = new Transaction().add( + await acProgram.methods + .grantRole(acRoleToBuffer(DATA_FEED_AC_ROLES.FEED_ADMIN)) + .accountsPartial({ + account: payer.publicKey, + acRole: params.acRole, + authority: payer.publicKey, + authorityAcAdminRole: getAccountAcRoleStatePda( + params.acRole, + payer.publicKey, + AC_ROLES.ADMIN, + ), + accountAcRole: authorityAcRolePda, + }) + .instruction(), + ); + + try { + const roleResult = await sendAndWaitForCustomSolanaTxSign(provider, grantRoleTx, [], { + action, + comment: 'Grant FEED_ADMIN role for manual feed', + waitForTx, + pollingIntervalMs: waitForTx ? 1000 : undefined, + timeoutDurationMs: waitForTx ? 120 * 1000 : undefined, + }); + if (roleResult.signature) { + console.log(`Transaction signature: ${roleResult.signature}`); + } + console.log('✓ FEED_ADMIN role granted'); + } catch (error) { + // Check if error is "role already exists" - if so, continue + const errorMsg = error?.toString() || ''; + if (errorMsg.includes('already') || errorMsg.includes('AlreadyGranted')) { + console.log('ℹ FEED_ADMIN role already exists, continuing'); + } else { + throw error; + } } } - // Step 2: Deploy manual feed associated with base feed const dataFeedProgram = getDataFeedProgram(provider); + if (params.existingDataFeed) { + console.log(`✓ Using existing data feed: ${params.existingDataFeed.toString()}`); + } + const initialPrice = params.initialPrice ?? params.minPrice; + console.log(`Initial price: ${initialPrice}`); + const manualFeedTx = new Transaction().add( await dataFeedProgram.methods - .newManualFeed(toBN(initialPrice), PRICE_DECIMALS) + .newManualFeed(toBN(initialPrice), 8) .accountsPartial({ authority: payer.publicKey, manualFeed: manualFeedPda, @@ -120,15 +154,17 @@ export async function deployManualFeed( ); const manualResult = await sendAndWaitForCustomSolanaTxSign(provider, manualFeedTx, [], { - action: 'deployer', + action, comment: 'Deploy Manual Feed', - waitForTx: true, - pollingIntervalMs: 1000, - timeoutDurationMs: 120 * 1000, + waitForTx, + pollingIntervalMs: waitForTx ? 1000 : undefined, + timeoutDurationMs: waitForTx ? 120 * 1000 : undefined, }); if (manualResult.signature) { console.log(`Transaction signature: ${manualResult.signature}`); + } else { + console.log(`Transaction created | TX ID: ${manualResult.txId}`); } return feedPublicKey; diff --git a/scripts/tasks/deploy/deploy-global-ac-override.ts b/scripts/tasks/deploy/deploy-global-ac-override.ts new file mode 100644 index 0000000..6bdb506 --- /dev/null +++ b/scripts/tasks/deploy/deploy-global-ac-override.ts @@ -0,0 +1,53 @@ +import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; + +import { createUserError } from '@/common/errorHandler'; +import { executeNetworkScript } from '@/common/scriptRunner'; + +import { deployAc, DeployAcConfig } from '../../deploy/ac'; +import { getTokenAddresses } from '../../utils/addressQueries'; +import { registerAddress } from '../../utils/addressRegistry'; +import { saveAddressesToFile } from '../../utils/addressStorage'; +import { getMtoken, getNetwork } from '../../utils/argumentParser'; + +async function main(provider: AnchorProvider, payer: Wallet, network: string) { + const mtoken = getMtoken(); + + console.log(`Deploying Global AC Override for: ${mtoken} on ${network}`); + + const addresses = getTokenAddresses(network, mtoken); + + if (!addresses?.acGlobalOverride?.acRole) { + throw createUserError(`Token AC Role Global Override not found for ${mtoken} on ${network}`, [ + `Run: yarn deploy:token-ac-role:global-override --mtoken ${mtoken} --network ${network}`, + ]); + } + + if (addresses?.acGlobalOverride?.ac) { + console.log('✓ AC Global Override already deployed'); + console.log(`AC Global Override: ${addresses.acGlobalOverride.ac.toString()}`); + console.log(`AC Role Global Override: ${addresses.acGlobalOverride.acRole.toString()}`); + return; + } + + const common = { provider, payer, network }; + + const acConfig: DeployAcConfig = { + acRole: addresses?.acGlobalOverride?.acRole, + }; + const ac = await deployAc(common, acConfig); + + registerAddress(network, mtoken, 'acGlobalOverride', { + ac, + acRole: addresses?.acGlobalOverride?.acRole, + }); + + await saveAddressesToFile(); + + console.log('\n✅ AC Global Override deployment submitted'); + console.log(`AC Global Override: ${ac.toString()}`); + console.log(`AC Role Global Override: ${addresses?.acGlobalOverride?.acRole.toString()}`); + return; +} + +const network = getNetwork(); +executeNetworkScript(network, main, 'deployer'); diff --git a/scripts/tasks/deploy/deploy-global-ac-role-override.ts b/scripts/tasks/deploy/deploy-global-ac-role-override.ts new file mode 100644 index 0000000..b267a4b --- /dev/null +++ b/scripts/tasks/deploy/deploy-global-ac-role-override.ts @@ -0,0 +1,45 @@ +import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; + +import { executeNetworkScript } from '@/common/scriptRunner'; + +import { deployAcRole, DeployAcRoleConfig } from '../../deploy/ac'; +import { getTokenAddresses } from '../../utils/addressQueries'; +import { registerAddress } from '../../utils/addressRegistry'; +import { saveAddressesToFile } from '../../utils/addressStorage'; +import { getMtoken, getNetwork } from '../../utils/argumentParser'; + +async function main(provider: AnchorProvider, payer: Wallet, network: string) { + const mtoken = getMtoken(); + + console.log(`Deploying Global AC Role Override for: ${mtoken} on ${network}`); + + const addresses = getTokenAddresses(network, mtoken); + + if (addresses?.acGlobalOverride?.acRole) { + console.log('✓ AC Role Global Override already deployed'); + console.log(`AC Role Global Override: ${addresses.acGlobalOverride.acRole.toString()}`); + return; + } + + const common = { provider, payer, network }; + + const acRoleGlobalConfig: DeployAcRoleConfig = {}; + const acRoleGlobal = await deployAcRole(common, acRoleGlobalConfig); + + registerAddress(network, mtoken, 'acGlobalOverride', { + acRole: acRoleGlobal, + ac: addresses?.acGlobalOverride?.ac, + }); + + await saveAddressesToFile(); + + console.log('\n✅ AC Role deployment submitted'); + console.log(`AC Role Global Override: ${acRoleGlobal.toString()}`); + console.log( + `\nNext step: yarn deploy:token-ac:global-override --mtoken ${mtoken} --network ${network}`, + ); + return; +} + +const network = getNetwork(); +executeNetworkScript(network, main, 'deployer'); diff --git a/scripts/tasks/deploy/deploy-minter-vault.ts b/scripts/tasks/deploy/deploy-minter-vault.ts index 35d44df..4a00be6 100644 --- a/scripts/tasks/deploy/deploy-minter-vault.ts +++ b/scripts/tasks/deploy/deploy-minter-vault.ts @@ -25,7 +25,22 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { const config = loadTokenConfig(mtoken, network); - const globalAc = getAcAddress(network); + const tokenAddrs = getTokenAddresses(network, mtoken); + + if (!tokenAddrs) { + throw createUserError(`Token addresses not found for ${mtoken} on ${network}`, [ + `Run: yarn deploy:token-ac-role --mtoken ${mtoken} --network ${network}`, + ]); + } + + if (tokenAddrs.acGlobalOverride && !tokenAddrs.acGlobalOverride.ac) { + throw createUserError(`AC Global Override config is not complete for ${mtoken} on ${network}`, [ + `Run: yarn deploy:token-ac:global-override --mtoken ${mtoken} --network ${network}`, + ]); + } + + const globalAc = tokenAddrs.acGlobalOverride?.ac ?? getAcAddress(network); + if (!globalAc) { throw createUserError(`AC not found for network ${network}`, [ `Run: yarn deploy:global-ac-role --network ${network} && yarn deploy:global-ac --network ${network}`, @@ -39,7 +54,8 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { ]); } - const acRoleGlobal = getAcRoleGlobalAddress(network); + const acRoleGlobal = tokenAddrs.acGlobalOverride?.acRole ?? getAcRoleGlobalAddress(network); + if (acRoleGlobal && tokenAcRole.equals(acRoleGlobal)) { throw createUserError(`Token AC Role cannot match global AC Role`, [ `Token AC Role: ${tokenAcRole.toString()}`, @@ -47,13 +63,6 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { ]); } - const tokenAddrs = getTokenAddresses(network, mtoken); - if (!tokenAddrs) { - throw createUserError(`Token addresses not found for ${mtoken} on ${network}`, [ - `Run: yarn deploy:token-ac-role --mtoken ${mtoken} --network ${network}`, - ]); - } - if (!tokenAddrs.mToken) { throw createUserError(`mToken not found for ${mtoken} on ${network}`, [ `Run: yarn deploy:token-mint --mtoken ${mtoken} --network ${network}`, diff --git a/scripts/tasks/deploy/deploy-payment-token-feed.ts b/scripts/tasks/deploy/deploy-payment-token-feed.ts index 24ad867..e1305db 100644 --- a/scripts/tasks/deploy/deploy-payment-token-feed.ts +++ b/scripts/tasks/deploy/deploy-payment-token-feed.ts @@ -33,11 +33,9 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { } // Check if data feed already exists - if (existingAddresses?.dataFeed) { + if (existingAddresses?.dataFeed && existingAddresses.underlyingFeed) { console.log(`✓ Data feed already exists: ${existingAddresses.dataFeed.toString()}`); - if (existingAddresses.underlyingFeed) { - console.log(`✓ Underlying feed: ${existingAddresses.underlyingFeed.toString()}`); - } + console.log(`✓ Underlying feed: ${existingAddresses.underlyingFeed.toString()}`); console.log('\nℹ️ Payment token feed already deployed. Skipping...'); return; } @@ -50,6 +48,8 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { network, acRole: acRoleGlobal, dataFeedConfig: config.dataFeed, + existingDataFeed: existingAddresses?.dataFeed, + isPaymentToken: true, }); console.log(`✅ Data feed deployed: ${dataFeed.toString()}`); diff --git a/scripts/tasks/deploy/deploy-redeemer-vault.ts b/scripts/tasks/deploy/deploy-redeemer-vault.ts index ed0176d..5768785 100644 --- a/scripts/tasks/deploy/deploy-redeemer-vault.ts +++ b/scripts/tasks/deploy/deploy-redeemer-vault.ts @@ -25,7 +25,20 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { const config = loadTokenConfig(mtoken, network); - const globalAc = getAcAddress(network); + const tokenAddrs = getTokenAddresses(network, mtoken); + if (!tokenAddrs) { + throw createUserError(`Token addresses not found for ${mtoken} on ${network}`, [ + `Run: yarn deploy:token-ac-role --mtoken ${mtoken} --network ${network}`, + ]); + } + + if (tokenAddrs.acGlobalOverride && !tokenAddrs.acGlobalOverride.ac) { + throw createUserError(`AC Global Override config is not complete for ${mtoken} on ${network}`, [ + `Run: yarn deploy:token-ac:global-override --mtoken ${mtoken} --network ${network}`, + ]); + } + + const globalAc = tokenAddrs.acGlobalOverride?.ac ?? getAcAddress(network); if (!globalAc) { throw createUserError(`AC not found for network ${network}`, [ `Run: yarn deploy:global-ac-role --network ${network} && yarn deploy:global-ac --network ${network}`, @@ -39,7 +52,7 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { ]); } - const acRoleGlobal = getAcRoleGlobalAddress(network); + const acRoleGlobal = tokenAddrs.acGlobalOverride?.acRole ?? getAcRoleGlobalAddress(network); if (acRoleGlobal && tokenAcRole.equals(acRoleGlobal)) { throw createUserError(`Token AC Role cannot match global AC Role`, [ `Token AC Role: ${tokenAcRole.toString()}`, @@ -47,13 +60,6 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { ]); } - const tokenAddrs = getTokenAddresses(network, mtoken); - if (!tokenAddrs) { - throw createUserError(`Token addresses not found for ${mtoken} on ${network}`, [ - `Run: yarn deploy:token-ac-role --mtoken ${mtoken} --network ${network}`, - ]); - } - if (!tokenAddrs.mToken) { throw createUserError(`mToken not found for ${mtoken} on ${network}`, [ `Run: yarn deploy:token-mint --mtoken ${mtoken} --network ${network}`, diff --git a/scripts/tasks/manage/add-to-addressbook.ts b/scripts/tasks/manage/add-to-addressbook.ts new file mode 100644 index 0000000..b5b330d --- /dev/null +++ b/scripts/tasks/manage/add-to-addressbook.ts @@ -0,0 +1,59 @@ +import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; + +import { createSolanaAddressBookContract, executeNetworkScript } from '@/common/scriptRunner'; +import { getTokenAddresses } from '@/scripts/utils/addressQueries'; +import { getMtoken, getNetwork } from '@/scripts/utils/argumentParser'; + +async function main(provider: AnchorProvider, payer: Wallet, network: string) { + const mtoken = getMtoken(); + const tokenAddresses = getTokenAddresses(network, mtoken); + if (!tokenAddresses) { + throw new Error(`Token addresses not found for ${mtoken} on ${network}`); + } + + for (const [key, value] of Object.entries(tokenAddresses)) { + if (!value) { + continue; + } + + let address = typeof value === 'string' ? value : undefined; + let contractName = ''; + let contractTag: string | undefined; + + if (key === 'redeemer') { + contractName = 'Redemption Vault'; + address = tokenAddresses.redeemer.account.toBase58(); + } else if (key.startsWith('minter')) { + contractName = 'Minter Vault'; + address = tokenAddresses.minter.account.toBase58(); + } else if (key === 'mTokenUnderlyingFeed') { + contractName = 'Oracle'; + address = tokenAddresses.mTokenUnderlyingFeed.toBase58(); + } else if (key === 'mToken') { + contractName = mtoken; + address = tokenAddresses.mToken.toBase58(); + } else if (key === 'mTokenDataFeed') { + contractName = 'Oracle'; + contractTag = 'datafeed'; + address = tokenAddresses.mTokenDataFeed.toBase58(); + } + + if (!contractName || !address) { + continue; + } + console.log('Adding to address book', contractName, address, contractTag); + + const result = await createSolanaAddressBookContract({ + network, + mToken: mtoken, + address, + contractName, + contractTag, + }); + + console.log('Successfully added to address book', result); + } +} +const network = getNetwork(); + +executeNetworkScript(network, main, 'deployer'); diff --git a/scripts/tasks/manage/pause-functions.ts b/scripts/tasks/manage/pause-functions.ts new file mode 100644 index 0000000..b201f01 --- /dev/null +++ b/scripts/tasks/manage/pause-functions.ts @@ -0,0 +1,109 @@ +import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; +import { Transaction } from '@solana/web3.js'; + +import { executeNetworkScript } from '@/common/scriptRunner'; +import { sendAndWaitForCustomSolanaTxSign } from '@/common/solanaTxHelper'; +import { loadTokenConfig } from '@/scripts/configs/loadTokenConfig'; +import { SOLANA_ROLES } from '@/scripts/configs/roles-types'; +import { getVaultsProgram } from '@/scripts/deploy/vaults'; +import { getTokenAddresses } from '@/scripts/utils/addressQueries'; +import { getMtoken, getNetwork } from '@/scripts/utils/argumentParser'; +import { getAccountAcRoleStatePda } from '@/test/helpers/ac.helpers'; +import { fetchPauseInxState, getPauseInxStatePda } from '@/test/helpers/vaults.helpers'; + +const main = async (provider: AnchorProvider, payer: Wallet, network: string) => { + const mtoken = getMtoken(); + + // Load configuration (with cross-reference validation for payment tokens) + console.log('Loading configuration...'); + const config = loadTokenConfig(mtoken, network); + console.log('✓ Configuration loaded'); + + // Get token addresses + const tokenAddrs = getTokenAddresses(network, mtoken); + if (!tokenAddrs) { + throw new Error(`Token addresses not found for ${mtoken} on ${network}`); + } + + const vaultsProgram = getVaultsProgram(provider); + + const functionsToPause = [ + ...(config.pauseFunctions?.minter || []).map((fn) => ({ + vault: tokenAddrs.minter.commonVault, + fnId: fn, + })), + ...(config.pauseFunctions?.redeemer || []).map((fn) => ({ + vault: tokenAddrs.redeemer.commonVault, + fnId: fn, + })), + ]; + + if (functionsToPause.length === 0) { + throw new Error('No functions to pause'); + } + + console.log( + `Pausing functions: ${functionsToPause.map((fn) => `${fn.vault.toBase58()}:${fn.fnId}`).join(', ')}`, + ); + + const tx = new Transaction(); + + for (const functionToPause of functionsToPause) { + const pauseInxStatePda = getPauseInxStatePda(functionToPause.vault, functionToPause.fnId); + const pauseInxState = await fetchPauseInxState(vaultsProgram, pauseInxStatePda, true); + + if (pauseInxState?.paused) { + console.log(`Function ${functionToPause} is already paused, skipping...`); + continue; + } + + if (!pauseInxState) { + console.log(`Function ${functionToPause} is not initialized, initializing...`); + tx.add( + await vaultsProgram.methods + .newPauseInx(functionToPause.fnId) + .accountsPartial({ + vaultCommon: functionToPause.vault, + authority: payer.publicKey, + authorityAcRole: getAccountAcRoleStatePda( + tokenAddrs.acRole, + payer.publicKey, + SOLANA_ROLES.VAULT_PAUSER, + ), + pauseInxState: pauseInxStatePda, + }) + .instruction(), + ); + } + + tx.add( + await vaultsProgram.methods + .updatePauseInx(functionToPause.fnId, true) + .accountsPartial({ + vaultCommon: functionToPause.vault, + authority: payer.publicKey, + authorityAcRole: getAccountAcRoleStatePda( + tokenAddrs.acRole, + payer.publicKey, + SOLANA_ROLES.VAULT_PAUSER, + ), + pauseInxState: pauseInxStatePda, + }) + .instruction(), + ); + } + + const txResult = await sendAndWaitForCustomSolanaTxSign(provider, tx, [], { + action: 'update-vault', + comment: `Pause functions for ${mtoken}`, + mToken: mtoken, + waitForTx: false, + }); + + // Multi-sig - transaction pending approval + console.log(` ⏳ Pending approval | TX ID: ${txResult.txId}`); + return `pending:${txResult.txId}`; +}; + +const network = getNetwork(); +executeNetworkScript(network, main, 'update-vault'); diff --git a/scripts/tasks/manage/token-ac/grant-admin-role.ts b/scripts/tasks/manage/token-ac/grant-admin-role.ts index f8e04d2..00d4250 100644 --- a/scripts/tasks/manage/token-ac/grant-admin-role.ts +++ b/scripts/tasks/manage/token-ac/grant-admin-role.ts @@ -4,6 +4,7 @@ import { PublicKey, Transaction } from '@solana/web3.js'; import { createUserError } from '@/common/errorHandler'; import { executeNetworkScript } from '@/common/scriptRunner'; import { sendAndWaitForCustomSolanaTxSign } from '@/common/solanaTxHelper'; +import { MProduct } from '@/common/tokenTypes'; import { AC_ROLES } from '@/test/constants/ac.constants'; import { acRoleToBuffer, @@ -16,28 +17,18 @@ import { getAcProgram } from '../../../deploy/ac'; import { getTokenAddresses } from '../../../utils/addressQueries'; import { getMtoken, getNetwork } from '../../../utils/argumentParser'; -async function main(provider: AnchorProvider, payer: Wallet, network: string) { - const mtoken = getMtoken(); - - console.log(`\n━━━ Step 1/3: Grant ADMIN Role ━━━`); - console.log(`Token: ${mtoken} | Network: ${network}\n`); - - const networkRolesConfig = networkRolesConfigs[network]; - if (!networkRolesConfig) { - throw createUserError(`Network roles config not found: ${network}`); - } - - const accessControlAdminAddress = new PublicKey(networkRolesConfig.accessControlAdminAddress); - - const tokenAddrs = getTokenAddresses(network, mtoken); - if (!tokenAddrs?.acRole) { - throw createUserError(`AC Role not found for ${mtoken} on ${network}`, [ - `Run: yarn deploy:token-ac-role --mtoken ${mtoken} --network ${network}`, - ]); - } +const grantAdminRole = async ( + provider: AnchorProvider, + payer: Wallet, + network: string, + mtoken: MProduct, + acRole?: PublicKey, +) => { + const accessControlAdminAddress = new PublicKey( + networkRolesConfigs[network].accessControlAdminAddress, + ); const acProgram = getAcProgram(provider); - const acRole = tokenAddrs.acRole; console.log(`Deployer: ${payer.publicKey.toString()}`); console.log(`AC Admin: ${accessControlAdminAddress.toString()}\n`); @@ -82,13 +73,40 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { const result = await sendAndWaitForCustomSolanaTxSign(provider, tx, [], { action: 'deployer', comment: `Grant ADMIN for ${mtoken}`, - mToken: mtoken, waitForTx: true, }); const txInfo = result.signature || result.txId; console.log(`✓ ADMIN granted | TX: ${txInfo}`); console.log(`\n→ Next: yarn token-ac:revoke-deployer --mtoken ${mtoken} --network ${network}\n`); +}; + +async function main(provider: AnchorProvider, payer: Wallet, network: string) { + const mtoken = getMtoken(); + + console.log(`\n━━━ Step 1/3: Grant ADMIN Role ━━━`); + console.log(`Token: ${mtoken} | Network: ${network}\n`); + + const networkRolesConfig = networkRolesConfigs[network]; + if (!networkRolesConfig) { + throw createUserError(`Network roles config not found: ${network}`); + } + + const tokenAddrs = getTokenAddresses(network, mtoken); + if (!tokenAddrs?.acRole) { + throw createUserError(`AC Role not found for ${mtoken} on ${network}`, [ + `Run: yarn deploy:token-ac-role --mtoken ${mtoken} --network ${network}`, + ]); + } + + await grantAdminRole(provider, payer, network, mtoken, tokenAddrs.acRole); + + if (tokenAddrs.acGlobalOverride?.acRole) { + console.log( + `Granting ADMIN role from global AC role override: ${tokenAddrs.acGlobalOverride.acRole.toString()}`, + ); + await grantAdminRole(provider, payer, network, mtoken, tokenAddrs.acGlobalOverride.acRole); + } } const network = getNetwork(); diff --git a/scripts/tasks/manage/token-ac/grant-operational-roles.ts b/scripts/tasks/manage/token-ac/grant-operational-roles.ts index 3f06ddd..9e71a0d 100644 --- a/scripts/tasks/manage/token-ac/grant-operational-roles.ts +++ b/scripts/tasks/manage/token-ac/grant-operational-roles.ts @@ -4,6 +4,7 @@ import { PublicKey, Transaction } from '@solana/web3.js'; import { createUserError } from '@/common/errorHandler'; import { executeNetworkScript } from '@/common/scriptRunner'; import { sendAndWaitForCustomSolanaTxSign } from '@/common/solanaTxHelper'; +import { networkRolesConfigs } from '@/scripts/configs/network-roles'; import { AC_ROLES } from '@/test/constants/ac.constants'; import { acRoleToBuffer, @@ -69,7 +70,8 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { console.log(` Vaults Manager: ${vaultsManagerAddress.toString()}`); console.log(` Oracle Manager: ${oracleManagerAddress.toString()}\n`); - const roleGrants: { account: PublicKey; role: string; category: string }[] = []; + const roleGrants: { account: PublicKey; role: string; category: string; acRole?: PublicKey }[] = + []; for (const role of ROLE_GROUPS.TOKEN_MANAGER) { roleGrants.push({ account: tokenManagerAddress, role, category: 'Token' }); @@ -81,6 +83,21 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { roleGrants.push({ account: oracleManagerAddress, role, category: 'Oracle' }); } + roleGrants.push({ + account: new PublicKey(networkRolesConfigs[network].accessControlAdminAddress), + role: AC_ROLES.UPDATE_ACCOUNT_AC, + category: 'Access Control Admin', + }); + + if (tokenAddrs.acGlobalOverride?.acRole) { + roleGrants.push({ + account: new PublicKey(networkRolesConfigs[network].accessControlAdminAddress), + acRole: tokenAddrs.acGlobalOverride.acRole, + role: AC_ROLES.UPDATE_ACCOUNT_AC, + category: 'Access Control Admin (Global AC Override)', + }); + } + const toGrant: typeof roleGrants = []; const alreadyGranted: typeof roleGrants = []; @@ -109,15 +126,31 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { const tx = new Transaction(); for (const grant of toGrant) { + const rolePda = getAccountAcRoleStatePda(grant.acRole ?? acRole, grant.account, grant.role); + const role = await fetchAccountAcRoleState(acProgram, rolePda, true); + + if (role) { + console.log(`✓ ${grant.role.replace('_role', '')} already granted`); + continue; + } + tx.add( await acProgram.methods .grantRole(acRoleToBuffer(grant.role)) .accountsPartial({ account: grant.account, - acRole: acRole, + acRole: grant.acRole ?? acRole, authority: payer.publicKey, - authorityAcAdminRole: getAccountAcRoleStatePda(acRole, payer.publicKey, AC_ROLES.ADMIN), - accountAcRole: getAccountAcRoleStatePda(acRole, grant.account, grant.role), + authorityAcAdminRole: getAccountAcRoleStatePda( + grant.acRole ?? acRole, + payer.publicKey, + AC_ROLES.ADMIN, + ), + accountAcRole: getAccountAcRoleStatePda( + grant.acRole ?? acRole, + grant.account, + grant.role, + ), }) .instruction(), ); @@ -126,7 +159,6 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { const result = await sendAndWaitForCustomSolanaTxSign(provider, tx, [], { action: 'update-ac', comment: `Grant operational ${mtoken} roles`, - mToken: mtoken, waitForTx: false, }); diff --git a/scripts/tasks/manage/token-ac/revoke-deployer-roles.ts b/scripts/tasks/manage/token-ac/revoke-deployer-roles.ts index 225603f..06f2934 100644 --- a/scripts/tasks/manage/token-ac/revoke-deployer-roles.ts +++ b/scripts/tasks/manage/token-ac/revoke-deployer-roles.ts @@ -4,6 +4,7 @@ import { PublicKey, Transaction } from '@solana/web3.js'; import { createUserError } from '@/common/errorHandler'; import { executeNetworkScript } from '@/common/scriptRunner'; import { sendAndWaitForCustomSolanaTxSign } from '@/common/solanaTxHelper'; +import { MProduct } from '@/common/tokenTypes'; import { AC_ROLES } from '@/test/constants/ac.constants'; import { acRoleToBuffer, @@ -17,28 +18,18 @@ import { getAcProgram } from '../../../deploy/ac'; import { getTokenAddresses } from '../../../utils/addressQueries'; import { getMtoken, getNetwork } from '../../../utils/argumentParser'; -async function main(provider: AnchorProvider, payer: Wallet, network: string) { - const mtoken = getMtoken(); - - console.log(`\n━━━ Step 2/3: Revoke Deployer Roles ━━━`); - console.log(`Token: ${mtoken} | Network: ${network}\n`); - - const networkRolesConfig = networkRolesConfigs[network]; - if (!networkRolesConfig) { - throw createUserError(`Network roles config not found: ${network}`); - } - - const accessControlAdminAddress = new PublicKey(networkRolesConfig.accessControlAdminAddress); - - const tokenAddrs = getTokenAddresses(network, mtoken); - if (!tokenAddrs?.acRole) { - throw createUserError(`AC Role not found for ${mtoken} on ${network}`, [ - `Run: yarn deploy:token-ac-role --mtoken ${mtoken} --network ${network}`, - ]); - } +const revokeDeployerRoles = async ( + provider: AnchorProvider, + payer: Wallet, + network: string, + mtoken: MProduct, + acRole: PublicKey, +) => { + const accessControlAdminAddress = new PublicKey( + networkRolesConfigs[network].accessControlAdminAddress, + ); const acProgram = getAcProgram(provider); - const acRole = tokenAddrs.acRole; console.log(`Deployer: ${payer.publicKey.toString()}`); console.log(`AC Admin: ${accessControlAdminAddress.toString()}\n`); @@ -126,7 +117,6 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { const result = await sendAndWaitForCustomSolanaTxSign(provider, tx, [], { action: 'deployer', comment: `Revoke deployer roles for ${mtoken}`, - mToken: mtoken, waitForTx: false, }); @@ -141,6 +131,32 @@ async function main(provider: AnchorProvider, payer: Wallet, network: string) { console.log( `\n→ Next: yarn token-ac:grant-operational --mtoken ${mtoken} --network ${network}\n`, ); +}; + +async function main(provider: AnchorProvider, payer: Wallet, network: string) { + const mtoken = getMtoken(); + console.log(`\n━━━ Step 2/3: Revoke Deployer Roles ━━━`); + console.log(`Token: ${mtoken} | Network: ${network}\n`); + + const networkRolesConfig = networkRolesConfigs[network]; + if (!networkRolesConfig) { + throw createUserError(`Network roles config not found: ${network}`); + } + const tokenAddrs = getTokenAddresses(network, mtoken); + if (!tokenAddrs?.acRole) { + throw createUserError(`AC Role not found for ${mtoken} on ${network}`, [ + `Run: yarn deploy:token-ac-role --mtoken ${mtoken} --network ${network}`, + ]); + } + const acRole = tokenAddrs.acRole; + await revokeDeployerRoles(provider, payer, network, mtoken, acRole); + + if (tokenAddrs.acGlobalOverride?.acRole) { + console.log( + `Revoking deployer roles from global AC role override: ${tokenAddrs.acGlobalOverride.acRole.toString()}`, + ); + await revokeDeployerRoles(provider, payer, network, mtoken, tokenAddrs.acGlobalOverride.acRole); + } } const network = getNetwork(); diff --git a/scripts/tasks/manage/update-data-feed.ts b/scripts/tasks/manage/update-data-feed.ts index ebf4af6..0e4d227 100644 --- a/scripts/tasks/manage/update-data-feed.ts +++ b/scripts/tasks/manage/update-data-feed.ts @@ -44,7 +44,6 @@ async function main(provider: AnchorProvider, payer: Wallet) { manual: DataFeedMode.manual, switchboard: DataFeedMode.switchboard, pyth: DataFeedMode.pyth, - chainlink: DataFeedMode.chainlink, }; const newModeEnum = newMode ? modeMap[newMode] : null; diff --git a/scripts/tasks/manage/update-manual-feed-price.ts b/scripts/tasks/manage/update-manual-feed-price.ts index 4cb48d5..5e7f5db 100644 --- a/scripts/tasks/manage/update-manual-feed-price.ts +++ b/scripts/tasks/manage/update-manual-feed-price.ts @@ -60,8 +60,8 @@ async function main(provider: AnchorProvider, payer: Wallet) { throw createUserError('Invalid decimals value', ['Decimals must be between 0 and 18']); } - // Convert price to base-9 format if provided - const priceBase9 = price !== null ? toBN(BigInt(Math.round(price * 1e9))) : null; + // Convert price to base-8 format if provided + const priceBase9 = price !== null ? toBN(BigInt(Math.round(price * 1e8))) : null; // Get manual feed PDA const manualFeedPda = getManualFeedStatePda(tokenAddrs.mTokenDataFeed); @@ -94,7 +94,7 @@ async function main(provider: AnchorProvider, payer: Wallet) { ); const txResult = await sendAndWaitForCustomSolanaTxSign(provider, tx, [], { - action: 'update-feed-ptoken', + action: 'update-feed-mtoken', comment: `Update manual feed price for ${mtoken}`, mToken: mtoken, waitForTx: false, @@ -113,4 +113,5 @@ async function main(provider: AnchorProvider, payer: Wallet) { } const network = getNetwork(); -executeNetworkScript(network, main, 'update-feed-ptoken'); +const mtoken = getMtoken(); +executeNetworkScript(network, main, 'update-feed-mtoken', mtoken); diff --git a/scripts/utils/addressRegistry.ts b/scripts/utils/addressRegistry.ts index 321180c..1ca8891 100644 --- a/scripts/utils/addressRegistry.ts +++ b/scripts/utils/addressRegistry.ts @@ -135,6 +135,7 @@ function validateComponentValue( function mapComponentToStateName(component: K): ComponentName { const mapping: Partial> = { acRole: 'acRole', + acGlobalOverride: 'acGlobalOverride', mToken: 'mToken', mTokenDataFeed: 'dataFeed', tokenAuthority: 'tokenAuthority', diff --git a/scripts/utils/addressStorage.ts b/scripts/utils/addressStorage.ts index 7f5089e..04ddfbd 100644 --- a/scripts/utils/addressStorage.ts +++ b/scripts/utils/addressStorage.ts @@ -30,6 +30,16 @@ function formatTokenAuthority( ]); } +function formatAcGlobalOverride( + acGlobalOverride: { ac?: PublicKey; acRole: PublicKey } | undefined, +): string { + if (!acGlobalOverride) return ''; + return formatObject([ + `ac: ${acGlobalOverride.ac ? formatPublicKey(acGlobalOverride.ac) : 'undefined'}`, + `acRole: ${formatPublicKey(acGlobalOverride.acRole)}`, + ]); +} + function formatVault(vault: { commonVault: PublicKey; account: PublicKey } | undefined): string { if (!vault) return ''; return formatObject([ @@ -57,6 +67,8 @@ function formatDataFeed(feed: DataFeed | undefined): string { function generateTokenAddressesCode(tokenAddrs: TokenAddresses, indent = ' '): string { const parts: string[] = []; if (tokenAddrs.acRole) parts.push(`acRole: ${formatPublicKey(tokenAddrs.acRole)}`); + if (tokenAddrs.acGlobalOverride) + parts.push(`acGlobalOverride: ${formatAcGlobalOverride(tokenAddrs.acGlobalOverride)}`); if (tokenAddrs.mToken) parts.push(`mToken: ${formatPublicKey(tokenAddrs.mToken)}`); if (tokenAddrs.tokenAuthority) { parts.push(`tokenAuthority: ${formatTokenAuthority(tokenAddrs.tokenAuthority)}`); @@ -171,6 +183,10 @@ export interface DataFeed { export interface TokenAddresses { acRole?: PublicKey; + acGlobalOverride?: { + ac: PublicKey; + acRole: PublicKey; + }; mToken?: PublicKey; tokenAuthority?: { seed: string; diff --git a/scripts/utils/deploymentState.ts b/scripts/utils/deploymentState.ts index 709ff40..edb9978 100644 --- a/scripts/utils/deploymentState.ts +++ b/scripts/utils/deploymentState.ts @@ -22,6 +22,7 @@ export interface DeploymentState { export const VALID_COMPONENTS = [ 'acRole', + 'acGlobalOverride', 'mToken', 'tokenAuthority', 'dataFeed', diff --git a/scripts/utils/feedDeployment.ts b/scripts/utils/feedDeployment.ts index 17b8e77..38e7e85 100644 --- a/scripts/utils/feedDeployment.ts +++ b/scripts/utils/feedDeployment.ts @@ -17,6 +17,9 @@ export interface DeployFeedParams { network: string; acRole: PublicKey; dataFeedConfig: DataFeedConfig; + action?: string; + isPaymentToken?: boolean; + existingDataFeed?: PublicKey; } export interface DeployFeedResult { @@ -30,6 +33,9 @@ export async function deployFeedFromConfig({ network, acRole, dataFeedConfig, + action, + existingDataFeed, + isPaymentToken, }: DeployFeedParams): Promise { const mode = dataFeedConfig.mode; const underlyingFeed = dataFeedConfig.underlyingFeed @@ -38,12 +44,14 @@ export async function deployFeedFromConfig({ const feedConfig = { acRole, + isPaymentToken: !!isPaymentToken, underlyingFeed, + existingDataFeed, minPrice: BigInt(Math.floor(parseFloat(dataFeedConfig.minPrice) * PRICE_MULTIPLIER)), maxPrice: BigInt(Math.floor(parseFloat(dataFeedConfig.maxPrice) * PRICE_MULTIPLIER)), maxStaleness: dataFeedConfig.maxStaleness, initialPrice: dataFeedConfig.initialPrice - ? BigInt(Math.floor(parseFloat(dataFeedConfig.initialPrice) * PRICE_MULTIPLIER)) + ? BigInt(Math.floor(parseFloat(dataFeedConfig.initialPrice) * 10 ** 8)) : undefined, }; @@ -113,7 +121,10 @@ export async function deployFeedFromConfig({ // } case 'manual': { - const dataFeed = await deployManualFeed({ provider, payer, network }, feedConfig); + const dataFeed = await deployManualFeed( + { provider, payer, network, action }, + { ...feedConfig, isPaymentToken, existingDataFeed }, + ); // For manual feeds, the underlying feed is a PDA derived from the data feed const dataFeedProgram = getDataFeedProgram(provider); const [manualFeedPda] = PublicKey.findProgramAddressSync(