Skip to content

Commit

Permalink
Merge branch 'main' into feature/prd-4316
Browse files Browse the repository at this point in the history
  • Loading branch information
makarychev committed Nov 20, 2024
2 parents 98d5084 + dd6244e commit e8d2d67
Show file tree
Hide file tree
Showing 57 changed files with 1,284 additions and 575 deletions.
830 changes: 427 additions & 403 deletions Cargo.lock

Large diffs are not rendered by default.

24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,24 +358,25 @@ Typically any legal entity third-party Transfer Agent will need access to both t
| Function | Contract Admin | Reserve Admin | Transfer Admin | Wallets Admin |
| -------------------------- | -------------- | ------------- | -------------- | ------------- |
| upgradeTransferRules() | **yes** | no | no | no |
| snapshot() | **yes** | no | no | no |
| newDistributor() | **yes** | no | **yes** | no |
| mint() | no | **yes** | no | no |
| burn() | no | **yes** | no | no |
| forceTransferBetween() | no | **yes** | no | no |
| pause() or unpause (ie pause(false)) | **yes** | no | **yes** | no |
| pause() or unpause (ie pause(false)) | **yes** | no | **yes** | no |
| setMaxTotalSuplly() | no | **yes** | no | no |
| setAllowGroupTransfer() | no | no | **yes** | no |
| setAllowTransferRule() | no | no | **yes** | no |
| setHolderMax() | no | no | **yes** | no |
| setHolderGroupMax() | no | no | **yes** | no |
| fundDividend() | no | no | **yes** | no |
| initilizeTransferRule() | no | no | **yes** | **yes** |
| initilizeTransferRule() | no | no | **yes** | no |
| freezeWallet() | no | no | **yes** | **yes** |
| thawWallet() | no | no | **yes** | **yes** |
| setTransferGroup() | no | no | **yes** | **yes** |
| createHolderFromAddress() | no | no | **yes** | **yes** |
| appendHolderAddress() | no | no | **yes** | **yes** |
| addHolderWithAddresses() | no | no | **yes** | **yes** |
| removeHolder() | no | no | **yes** | **yes** |
| revokeHolder() | no | no | **yes** | **yes** |
| revokeHolderGroup() | no | no | **yes** | **yes** |
| revokeSecurityAssociatedAccount() | no | no | **yes** | **yes** |
| createReleaseSchedule() | **yes** | **yes** | **yes** | **yes** |
| mintReleaseSchedule() | no | **yes** | no | no |

Expand Down Expand Up @@ -432,6 +433,16 @@ sequenceDiagram
5. The Reserve Admin then transfers tokens to the Wallets Admin address.
6. The Wallets Admin then transfers tokens to Investors or other stakeholders who are entitled to tokens.

## Revoke Holder
To redeem reserved SOL used for rent-exempt space allocation, you can utilize the `revoke*` methods. It is crucial to revoke accounts in the following sequence:

1. *Revoke Security-Associated Accounts*: Revoke all security-associated accounts for the specified holder.
2. *Revoke Holder Group Accounts*: Revoke all holder group accounts for the specified holder and associated groups.
3. *Revoke the Holder*: Finally, revoke the holder account.

A holder can only be revoked if it is not linked to any group or security-associated account. This condition is met when both `current_wallets_count` and `current_holder_group_count` are zero.


# Setup For Separate Issuer Private Key Management Roles

By default the reserve tokens cannot be transferred to. To allow transfers the Transfer Admin or Wallets Admin must configure transfer rules using both `updateTransferRestrictionGroup(transferGroup)` to configure the individual account rules and `initialzeTransferRule/updateTransferRule(account, groupFrom, groupTo)` to configure transfers between accounts in a group. A group represents a category like US accredited investors (Reg D) or foreign investors (Reg S).
Expand Down Expand Up @@ -976,6 +987,7 @@ await program.methods
mint: dividendsMintPubkey,
authorityWalletRole,
accessControl: accessControlPubkey,
securityMint: securityMintPubkey,
payer: signer.publicKey,
systemProgram: SystemProgram.programId,
})
Expand Down
10 changes: 10 additions & 0 deletions app/src/idls/access_control.json
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,16 @@
"code": 6007,
"name": "NewMaxTotalSupplyMustExceedCurrentTotalSupply",
"msg": "New max total supply must exceed current total supply"
},
{
"code": 6008,
"name": "CannotFreezeLockupEscrowAccount",
"msg": "Cannot freeze lockup escrow account"
},
{
"code": 6009,
"name": "ValueUnchanged",
"msg": "The provided value is already set. No changes were made"
}
],
"types": [
Expand Down
26 changes: 25 additions & 1 deletion app/src/idls/dividends.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,26 @@
"name": "access_control",
"docs": [
"Access Control for Security Token."
]
],
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
99
]
},
{
"kind": "account",
"path": "security_mint"
}
]
}
},
{
"name": "security_mint",
"writable": true
},
{
"name": "payer",
Expand Down Expand Up @@ -518,6 +537,11 @@
"code": 6010,
"name": "InvalidIPFSHashSize",
"msg": "Invalid IPFS hash size"
},
{
"code": 6011,
"name": "ValueUnchanged",
"msg": "The provided value is already set. No changes were made"
}
],
"types": [
Expand Down
23 changes: 22 additions & 1 deletion app/src/idls/transfer_restrictions.json
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@
}
},
{
"name": "holder"
"name": "holder",
"writable": true
},
{
"name": "authority_wallet_role"
Expand Down Expand Up @@ -1040,6 +1041,7 @@
"accounts": [
{
"name": "holder",
"writable": true,
"pda": {
"seeds": [
{
Expand Down Expand Up @@ -2102,6 +2104,21 @@
"code": 6020,
"name": "ZeroGroupHolderGroupMaxCannotBeNonZero",
"msg": "Zero group holder group max cannot be non-zero"
},
{
"code": 6021,
"name": "NonPositiveHolderGroupCount",
"msg": "Non-positive holder group count"
},
{
"code": 6022,
"name": "CurrentHolderGroupCountMustBeZero",
"msg": "Current holder group count must be zero"
},
{
"code": 6023,
"name": "ValueUnchanged",
"msg": "The provided value is already set. No changes were made"
}
],
"types": [
Expand Down Expand Up @@ -2244,6 +2261,10 @@
"name": "current_wallets_count",
"type": "u64"
},
{
"name": "current_holder_group_count",
"type": "u64"
},
{
"name": "id",
"type": "u64"
Expand Down
10 changes: 10 additions & 0 deletions app/src/types/access_control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,16 @@ export type AccessControl = {
code: 6007;
name: "newMaxTotalSupplyMustExceedCurrentTotalSupply";
msg: "New max total supply must exceed current total supply";
},
{
code: 6008;
name: "cannotFreezeLockupEscrowAccount";
msg: "Cannot freeze lockup escrow account";
},
{
code: 6009;
name: "valueUnchanged";
msg: "The provided value is already set. No changes were made";
}
];
types: [
Expand Down
21 changes: 21 additions & 0 deletions app/src/types/dividends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,22 @@ export type Dividends = {
{
name: "accessControl";
docs: ["Access Control for Security Token."];
pda: {
seeds: [
{
kind: "const";
value: [97, 99];
},
{
kind: "account";
path: "securityMint";
}
];
};
},
{
name: "securityMint";
writable: true;
},
{
name: "payer";
Expand Down Expand Up @@ -360,6 +376,11 @@ export type Dividends = {
code: 6010;
name: "invalidIpfsHashSize";
msg: "Invalid IPFS hash size";
},
{
code: 6011;
name: "valueUnchanged";
msg: "The provided value is already set. No changes were made";
}
];
types: [
Expand Down
21 changes: 21 additions & 0 deletions app/src/types/transfer_restrictions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ export type TransferRestrictions = {
},
{
name: "holder";
writable: true;
},
{
name: "authorityWalletRole";
Expand Down Expand Up @@ -848,6 +849,7 @@ export type TransferRestrictions = {
accounts: [
{
name: "holder";
writable: true;
pda: {
seeds: [
{
Expand Down Expand Up @@ -1706,6 +1708,21 @@ export type TransferRestrictions = {
code: 6020;
name: "zeroGroupHolderGroupMaxCannotBeNonZero";
msg: "Zero group holder group max cannot be non-zero";
},
{
code: 6021;
name: "nonPositiveHolderGroupCount";
msg: "Non-positive holder group count";
},
{
code: 6022;
name: "currentHolderGroupCountMustBeZero";
msg: "Current holder group count must be zero";
},
{
code: 6023;
name: "valueUnchanged";
msg: "The provided value is already set. No changes were made";
}
];
types: [
Expand Down Expand Up @@ -1848,6 +1865,10 @@ export type TransferRestrictions = {
name: "currentWalletsCount";
type: "u64";
},
{
name: "currentHolderGroupCount";
type: "u64";
},
{
name: "id";
type: "u64";
Expand Down
1 change: 1 addition & 0 deletions deploy/dividends/new-dividends-distribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const deployerKeypairPath = `deploy/${options.cluster}/keys/deployer.json`;
deployerKeypair.publicKey
)[0],
accessControl: accessControlHelper.accessControlPubkey,
securityMint: config.securityMint,
payer: deployerKeypair.publicKey,
systemProgram: SystemProgram.programId,
})
Expand Down
4 changes: 2 additions & 2 deletions programs/access-control/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
[dependencies]
anchor-lang = { version = "0.30.1" }
anchor-spl = { version = "0.30.1" }
spl-transfer-hook-interface = "0.6.3"
spl-tlv-account-resolution = "0.6.3"
spl-transfer-hook-interface = "0.6.5"
spl-tlv-account-resolution = "0.6.5"
num_enum = "0.7.2"
tokenlock-accounts = { path = "../../libraries/tokenlock-accounts" }
4 changes: 4 additions & 0 deletions programs/access-control/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ pub enum AccessControlError {
CantForceTransferBetweenLockup,
#[msg("New max total supply must exceed current total supply")]
NewMaxTotalSupplyMustExceedCurrentTotalSupply,
#[msg("Cannot freeze lockup escrow account")]
CannotFreezeLockupEscrowAccount,
#[msg("The provided value is already set. No changes were made")]
ValueUnchanged,
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ pub fn set_lockup_escrow_account(ctx: Context<SetLockupEscrowAccount>) -> Result
{
return Err(AccessControlError::Unauthorized.into());
}
if ctx.accounts.access_control_account.lockup_escrow_account
== Some(ctx.accounts.escrow_account.key())
{
return Err(AccessControlError::ValueUnchanged.into());
}

let discriminator = TokenLockData::discriminator();
let tokenlock_account = &ctx.accounts.tokenlock_account;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub fn update_wallet_role(ctx: Context<UpdateWalletRole>, role: u8) -> Result<()
if role > Roles::All as u8 {
return Err(AccessControlError::InvalidRole.into());
}
if role == ctx.accounts.wallet_role.role {
return Err(AccessControlError::ValueUnchanged.into());
}

let wallet_role = &mut ctx.accounts.wallet_role;
wallet_role.role = role;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ pub fn freeze_wallet(ctx: Context<FreezeWallet>) -> Result<()> {
if !ctx
.accounts
.authority_wallet_role
.has_any_role(crate::Roles::TransferAdmin as u8 | crate ::Roles::WalletsAdmin as u8)
.has_any_role(crate::Roles::TransferAdmin as u8 | crate::Roles::WalletsAdmin as u8)
{
return Err(AccessControlError::Unauthorized.into());
}
if ctx.accounts.access_control.lockup_escrow_account == Some(ctx.accounts.target_account.key())
{
return Err(AccessControlError::CannotFreezeLockupEscrowAccount.into());
}

let mint = ctx.accounts.security_mint.to_account_info();
let accounts = FreezeAccount {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub fn thaw_wallet(ctx: Context<ThawWallet>) -> Result<()> {
if !ctx
.accounts
.authority_wallet_role
.has_any_role(crate::Roles::TransferAdmin as u8 | crate ::Roles::WalletsAdmin as u8)
.has_any_role(crate::Roles::TransferAdmin as u8 | crate::Roles::WalletsAdmin as u8)
{
return Err(AccessControlError::Unauthorized.into());
}
Expand Down
2 changes: 2 additions & 0 deletions programs/dividends/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ pub enum DividendsErrorCode {
DistributorNotReadyToClaim,
#[msg("Invalid IPFS hash size")]
InvalidIPFSHashSize,
#[msg("The provided value is already set. No changes were made")]
ValueUnchanged,
}
26 changes: 21 additions & 5 deletions programs/dividends/src/instructions/new_distributor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use access_control::{program::AccessControl as AccessControlProgram, AccessControl, WalletRole};
use anchor_lang::prelude::*;
use anchor_spl::token_interface::Mint;
use access_control::{
program::AccessControl as AccessControlProgram, AccessControl, WalletRole, ACCESS_CONTROL_SEED,
};
use anchor_lang::{prelude::*, solana_program::program_option::COption};
use anchor_spl::{token_2022::ID as TOKEN_2022_PROGRAM_ID, token_interface::Mint};

use crate::{errors::DividendsErrorCode, MerkleDistributor, MAX_IPFS_HASH_LEN};

Expand Down Expand Up @@ -36,16 +38,30 @@ pub struct NewDistributor<'info> {
/// Authority wallet role to create the distributor.
#[account(
constraint = authority_wallet_role.owner == payer.key(),
constraint = authority_wallet_role.has_role(access_control::Roles::ContractAdmin) @ DividendsErrorCode::Unauthorized,
constraint = authority_wallet_role.has_any_role(access_control::Roles::ContractAdmin as u8 | access_control::Roles::TransferAdmin as u8) @ DividendsErrorCode::Unauthorized,
constraint = authority_wallet_role.access_control == access_control.key(),
owner = AccessControlProgram::id(),
)]
pub authority_wallet_role: Account<'info, WalletRole>,

/// Access Control for Security Token.
#[account(owner = AccessControlProgram::id())]
#[account(
constraint = security_mint.key() == access_control.mint,
seeds = [
ACCESS_CONTROL_SEED,
security_mint.key().as_ref(),
],
bump,
seeds::program = AccessControlProgram::id(),
)]
pub access_control: Account<'info, AccessControl>,

#[account(mut,
mint::token_program = TOKEN_2022_PROGRAM_ID,
constraint = security_mint.mint_authority == COption::Some(access_control.key()),
)]
pub security_mint: Box<InterfaceAccount<'info, Mint>>,

/// Payer to create the distributor.
#[account(mut)]
pub payer: Signer<'info>,
Expand Down
Loading

0 comments on commit e8d2d67

Please sign in to comment.