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
84 changes: 84 additions & 0 deletions contracts/BackedAutoFeeTokenImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation, IBackedA
uint256 public newMultiplierActivationTime;
MultiplierUpdate[] public multiplierUpdates;

// Multi-minter with per-address mint cap
mapping(address => uint256) public minterAllowance;

// Multi-burner with per-address burn cap
mapping(address => uint256) public burnerAllowance;

// Events
event MinterAllowanceChanged(address indexed minter, uint256 allowance);
event BurnerAllowanceChanged(address indexed burner, uint256 allowance);

function multiplierNonce() external view returns (uint256) {
if(block.timestamp >= newMultiplierActivationTime) {
return newMultiplierNonce;
Expand Down Expand Up @@ -412,6 +422,80 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation, IBackedA
periodLength = newPeriodLength;
}

/**
* @dev Function to set the minting allowance for an address. Allowed only for owner.
* Set to 0 to revoke minting rights.
*
* Emits a { MinterAllowanceChanged } event
*
* @param minterAddress The address to set the allowance for
* @param allowance The maximum amount of tokens this address can mint
*/
function setMinterAllowance(
address minterAddress,
uint256 allowance
) external onlyOwner {
minterAllowance[minterAddress] = allowance;
emit MinterAllowanceChanged(minterAddress, allowance);
}

/**
* @dev Function to mint tokens. Allowed for the primary minter (unlimited)
* or any address with sufficient minterAllowance (capped).
*
* @param account The address that will receive the minted tokens
* @param amount The amount of tokens to mint
*/
function mint(address account, uint256 amount) override external {
address sender = _msgSender();
if (sender == minter) {
// Primary minter: unlimited, backward-compatible
_mint(account, amount);
} else {
require(minterAllowance[sender] >= amount, "BackedToken: Minter allowance exceeded");
minterAllowance[sender] -= amount;
_mint(account, amount);
}
}

/**
* @dev Function to set the burning allowance for an address. Allowed only for owner.
* Set to 0 to revoke burning rights.
*
* Emits a { BurnerAllowanceChanged } event
*
* @param burnerAddress The address to set the allowance for
* @param allowance The maximum amount of tokens this address can burn
*/
function setBurnerAllowance(
address burnerAddress,
uint256 allowance
) external onlyOwner {
burnerAllowance[burnerAddress] = allowance;
emit BurnerAllowanceChanged(burnerAddress, allowance);
}

/**
* @dev Function to burn tokens. Allowed for the primary burner (unlimited)
* or any address with sufficient burnerAllowance (capped).
* The burned tokens must be from the caller (msg.sender), or from the contract itself.
*
* @param account The account from which the tokens will be burned
* @param amount The amount of tokens to be burned
*/
function burn(address account, uint256 amount) override external {
address sender = _msgSender();
require(account == sender || account == address(this), "BackedToken: Cannot burn account");
if (sender == burner) {
// Primary burner: unlimited, backward-compatible
_burn(account, amount);
} else {
require(burnerAllowance[sender] >= amount, "BackedToken: Burner allowance exceeded");
burnerAllowance[sender] -= amount;
_burn(account, amount);
}
}

/**
* @dev Function to change the contract multiplier, only if oldMultiplier did not change in the meantime. Allowed only for multiplierUpdater
*
Expand Down
115 changes: 110 additions & 5 deletions contracts/WrappedBackedTokenImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ import "@openzeppelin/contracts-upgradeable-new/utils/math/MathUpgradeable.sol";
import "./SanctionsList.sol";
import "./interfaces/IBackedAutoFeeToken.sol";

interface ITokenMintBurnable {
function mint(address account, uint256 amount) external;
function burn(address account, uint256 amount) external;
}

/**
* @dev
*
Expand Down Expand Up @@ -71,6 +76,18 @@ contract WrappedBackedTokenImplementation is OwnableUpgradeable, ERC4626Upgradea
// Roles:
address public pauser;

// Bridge rate-limiting
struct BridgeConfig {
uint256 mintLimit; // max shares mintable per window
uint256 burnLimit; // max shares burnable per window
uint256 windowLength; // time window in seconds
uint256 currentMintWindowStart; // start of current mint window
uint256 mintedInWindow; // shares minted in current window
uint256 currentBurnWindowStart; // start of current burn window
uint256 burnedInWindow; // shares burned in current window
}
mapping(address => BridgeConfig) public bridges;

// EIP-712 Delegate Functionality:
bool public delegateMode;
mapping(address => bool) public delegateWhitelist;
Expand All @@ -86,6 +103,7 @@ contract WrappedBackedTokenImplementation is OwnableUpgradeable, ERC4626Upgradea

// Events:
event NewPauser(address indexed newPauser);
event BridgeConfigChanged(address indexed bridge, uint256 mintLimit, uint256 burnLimit, uint256 windowLength);
event NewSanctionsList(address indexed newSanctionsList);
event PauseModeChange(bool pauseMode);
event NewTerms(string newTerms);
Expand Down Expand Up @@ -162,6 +180,29 @@ contract WrappedBackedTokenImplementation is OwnableUpgradeable, ERC4626Upgradea
emit NewPauser(newPauser);
}

/**
* @dev Function to configure a bridge address with rate-limited minting.
* Set mintLimit to 0 to revoke bridge rights. Allowed only for owner.
*
* Emits a { BridgeConfigChanged } event
*
* @param bridgeAddress The bridge address to configure
* @param mintLimit Max shares mintable per window (0 to revoke)
* @param windowLength Time window in seconds
*/
function setBridge(address bridgeAddress, uint256 mintLimit, uint256 burnLimit, uint256 windowLength) external onlyOwner {
bridges[bridgeAddress] = BridgeConfig({
mintLimit: mintLimit,
burnLimit: burnLimit,
windowLength: windowLength,
currentMintWindowStart: block.timestamp,
mintedInWindow: 0,
currentBurnWindowStart: block.timestamp,
burnedInWindow: 0
});
emit BridgeConfigChanged(bridgeAddress, mintLimit, burnLimit, windowLength);
}

/**
* @dev Function to change the contract Senctions List. Allowed only for owner
*
Expand Down Expand Up @@ -271,11 +312,56 @@ contract WrappedBackedTokenImplementation is OwnableUpgradeable, ERC4626Upgradea
*/
function _deposit(address caller, address receiver, uint256 assetsRequested, uint256 shares) internal virtual override {
IBackedAutoFeeToken assetToken = IBackedAutoFeeToken(asset());
assetToken.transferSharesFrom(caller, address(this), shares);
_mint(receiver, shares);
BridgeConfig storage bridgeCfg = bridges[caller];

if (bridgeCfg.mintLimit > 0) {
// Bridge: mint underlying tokens to this contract instead of pulling from caller

// Reset mint window if expired
if (block.timestamp >= bridgeCfg.currentMintWindowStart + bridgeCfg.windowLength) {
bridgeCfg.currentMintWindowStart = block.timestamp;
bridgeCfg.mintedInWindow = 0;
}
require(bridgeCfg.mintedInWindow + shares <= bridgeCfg.mintLimit, "WrappedBackedToken: Bridge mint limit exceeded");

uint256 sharesBefore = assetToken.sharesOf(address(this));
uint256 underlyingAmount = convertToAssets(shares);
ITokenMintBurnable(asset()).mint(address(this), underlyingAmount);
uint256 actualShares = assetToken.sharesOf(address(this)) - sharesBefore;

bridgeCfg.mintedInWindow += actualShares;

_mint(receiver, actualShares);
uint256 assets = convertToAssets(actualShares);
emit Deposit(caller, receiver, assets, actualShares);
} else {
// Normal: pull underlying shares from caller
assetToken.transferSharesFrom(caller, address(this), shares);
_mint(receiver, shares);
uint256 assets = convertToAssets(shares);
emit Deposit(caller, receiver, assets, shares);
}
}

uint256 assets = convertToAssets(shares);
emit Deposit(caller, receiver, assets, shares);
/**
* @dev Burns wrapped tokens by redeeming the underlying shares.
*
* @param shares The amount of wrapped token shares to burn
*/
function burn(address owner, uint256 shares) external virtual returns (bool) {
redeem(shares, owner, owner);
return true;
}

/**
* @dev Mints wrapped tokens by depositing the underlying shares.
*
* @param owner The address that will receive the minted wrapped tokens
* @param shares The amount of wrapped token shares to mint
*/
function mint(address owner, uint256 shares) external virtual returns (bool) {
mint(shares, owner);
return true;
}

/**
Expand All @@ -295,7 +381,26 @@ contract WrappedBackedTokenImplementation is OwnableUpgradeable, ERC4626Upgradea

_burn(owner, shares);
IBackedAutoFeeToken assetToken = IBackedAutoFeeToken(asset());
assetToken.transferShares(receiver, shares);

BridgeConfig storage bridgeCfg = bridges[caller];
if (bridgeCfg.burnLimit > 0) {
// Bridge: burn underlying tokens instead of transferring shares

// Reset burn window if expired
if (block.timestamp >= bridgeCfg.currentBurnWindowStart + bridgeCfg.windowLength) {
bridgeCfg.currentBurnWindowStart = block.timestamp;
bridgeCfg.burnedInWindow = 0;
}
require(bridgeCfg.burnedInWindow + shares <= bridgeCfg.burnLimit, "WrappedBackedToken: Bridge burn limit exceeded");

bridgeCfg.burnedInWindow += shares;

uint256 underlyingAmount = convertToAssets(shares);
ITokenMintBurnable(asset()).burn(address(this), underlyingAmount);
} else {
// Normal: transfer underlying shares to receiver
assetToken.transferShares(receiver, shares);
}

uint256 assets = convertToAssets(shares);
emit Withdraw(caller, receiver, owner, assets, shares);
Expand Down
Loading
Loading