Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
],
"packageManager": "yarn@4.6.0",
"resolutions": {
"hardhat": "2.12.3"
"hardhat": "2.28.3"
}
}
131 changes: 131 additions & 0 deletions packages/bridge-contracts/contracts/oft/GoodDollarMinterBurner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8;

import {ISuperGoodDollar} from "./interfaces/ISuperGoodDollar.sol";
import {DAOUpgradeableContract, INameService} from "@gooddollar/goodprotocol/contracts/utils/DAOUpgradeableContract.sol";

interface IIdentity {
function isWhitelisted(address) external view returns (bool);
}

/**
* @title GoodDollarMinterBurner
* @dev DAO-upgradeable contract that handles minting and burning of GoodDollar tokens for OFT
*
* This contract is used by the GoodDollarOFTAdapter to mint and burn tokens during
* cross-chain transfers via LayerZero. It is upgradeable and controlled by the DAO.
*
* Key functionalities:
* - Mint tokens when receiving cross-chain transfers
* - Burn tokens when sending cross-chain transfers
* - Manage operators (like OFT adapter) that can mint/burn
* - Pause functionality for emergency situations
* - Upgradeable via DAO governance
*/
contract GoodDollarMinterBurner is DAOUpgradeableContract {
ISuperGoodDollar public token;
mapping(address => bool) public operators;

bool public paused;

event OperatorSet(address indexed operator, bool status);
event Paused(address indexed account);
event Unpaused(address indexed account);
event TokensMinted(address indexed to, uint256 amount, address indexed operator);
event TokensBurned(address indexed from, uint256 amount, address indexed operator);

modifier onlyOperators() {
require(operators[msg.sender] || msg.sender == avatar, "Not authorized");
require(!paused, "Contract is paused");
_;
}

/**
* @dev Initialize the MinterBurner contract
* @param _nameService The NameService contract for DAO integration
*/
function initialize(
INameService _nameService
) public initializer {
setDAO(_nameService);
token = ISuperGoodDollar(address(nativeToken()));
}

/**
* @dev Set or remove an operator that can mint/burn tokens
* @param _operator The address of the operator (e.g., OFT adapter)
* @param _status True to enable, false to disable
*
* Only the DAO avatar can call this function.
*/
function setOperator(address _operator, bool _status) external {
_onlyAvatar();
operators[_operator] = _status;
emit OperatorSet(_operator, _status);
}


/**
* @dev Burn tokens from an address
* @param _from The address to burn tokens from
* @param _amount The amount of tokens to burn
* @return success True if the burn was successful
*
* Only authorized operators (like OFT adapter) or the DAO avatar can call this.
* Note: Limits are NOT enforced on the sending side (burning), matching MessagePassingBridge behavior.
* Limits are only enforced on the receiving side (minting) when tokens are received.
*/
function burn(address _from, uint256 _amount) external onlyOperators returns (bool) {
token.burnFrom(_from, _amount);

emit TokensBurned(_from, _amount, msg.sender);
return true;
}

/**
* @dev Mint tokens to an address
* @param _to The address to mint tokens to
* @param _amount The amount of tokens to mint
* @return success True if the mint was successful
*
* Only authorized operators (like OFT adapter) or the DAO avatar can call this.
*/
function mint(address _to, uint256 _amount) external onlyOperators returns (bool) {
bool success = token.mint(_to, _amount);
if (success) {
emit TokensMinted(_to, _amount, msg.sender);
}
return success;
}

/**
* @dev Pause all mint and burn operations
*
* Only the DAO avatar can call this. Useful for emergency situations.
*/
function pause() external {
_onlyAvatar();
require(!paused, "Already paused");
paused = true;
emit Paused(msg.sender);
}

/**
* @dev Unpause mint and burn operations
*
* Only the DAO avatar can call this.
*/
function unpause() external {
_onlyAvatar();
require(paused, "Not paused");
paused = false;
emit Unpaused(msg.sender);
}

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
118 changes: 118 additions & 0 deletions packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { OFTCoreUpgradeable } from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTCoreUpgradeable.sol";
import { IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/interfaces/IMintableBurnable.sol";

/**
* @title GoodDollarOFTAdapter
* @notice Upgradeable OFT adapter that uses mint/burn mechanisms for cross-chain transfers
* @dev Inherits from OFTCoreUpgradeable (which already includes OwnableUpgradeable) and implements mint/burn logic similar to MintBurnOFTAdapter
*/
contract GoodDollarOFTAdapter is OFTCoreUpgradeable {
/// @dev The underlying ERC20 token
IERC20 internal innerToken;

/// @dev The contract responsible for minting and burning tokens
IMintableBurnable public minterBurner;

/**
* @dev Constructor for the upgradeable contract
* @param _token The address of the underlying ERC20 token (used to get decimals)
* @param _lzEndpoint The LayerZero endpoint address
* @dev The constructor is called when deploying the implementation contract
* @dev The token address is only used here to get decimals for the parent constructor
*/
constructor(address _token, address _lzEndpoint)
OFTCoreUpgradeable(IERC20Metadata(_token).decimals(), _lzEndpoint)
{
// Disable initialization in the constructor to prevent initialization of the implementation
_disableInitializers();
}

/**
* @notice Initializes the GoodDollarOFTAdapter contract
* @param _token The address of the underlying ERC20 token
* @param _minterBurner The contract responsible for minting and burning tokens
* @param _owner The contract owner
* @dev The LayerZero endpoint is set in the constructor and cannot be changed per proxy
*/
function initialize(
address _token,
IMintableBurnable _minterBurner,
address _owner
) public initializer {
// Initialize parent contracts
// __OFTCore_init already initializes OwnableUpgradeable through OAppCoreUpgradeable
__OFTCore_init(_owner);

// Set state variables
innerToken = IERC20(_token);
minterBurner = _minterBurner;
}

/**
* @notice Retrieves the address of the underlying ERC20 token
* @return The address of the adapted ERC20 token
*/
function token() public view returns (address) {
return address(innerToken);
}

/**
* @notice Indicates whether the OFT contract requires approval of the underlying token to send
* @return requiresApproval False because this adapter uses mint and burn privileges
*/
function approvalRequired() external pure returns (bool) {
return false;
}

/**
* @notice Burns tokens from the sender's balance to prepare for sending
* @param _from The address to debit the tokens from
* @param _amountLD The amount of tokens to send in local decimals
* @param _minAmountLD The minimum amount to send in local decimals
* @param _dstEid The destination chain ID
* @return amountSentLD The amount sent in local decimals
* @return amountReceivedLD The amount received in local decimals on the remote
*/
function _debit(
address _from,
uint256 _amountLD,
uint256 _minAmountLD,
uint32 _dstEid
) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {
(amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);
// Burns tokens from the caller
minterBurner.burn(_from, amountSentLD);
}

/**
* @notice Mints tokens to the specified address upon receiving them
* @param _to The address to credit the tokens to
* @param _amountLD The amount of tokens to credit in local decimals
* @param _srcEid The source chain ID
* @return amountReceivedLD The amount of tokens actually received in local decimals
*/
function _credit(
address _to,
uint256 _amountLD,
uint32 _srcEid
) internal virtual override returns (uint256 amountReceivedLD) {
if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0)

// Mint tokens to recipient
bool success = minterBurner.mint(_to, _amountLD);
require(success, "GoodDollarOFTAdapter: Mint failed");

return _amountLD;
}

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8;

/**
* @title ISuperGoodDollar
* @notice Interface for GoodDollar token functions used in this project
* @dev This is a minimal interface containing only the functions actually used
*/
interface ISuperGoodDollar {
function isMinter(address _minter) external view returns (bool);

function mint(address to, uint256 amount) external returns (bool);

function burnFrom(address account, uint256 amount) external;

function addMinter(address _minter) external;
}
30 changes: 23 additions & 7 deletions packages/bridge-contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import 'hardhat-contract-sizer';
import '@openzeppelin/hardhat-upgrades';
import '@nomicfoundation/hardhat-chai-matchers';
import '@nomicfoundation/hardhat-verify';
import '@nomiclabs/hardhat-waffle';
import 'hardhat-deploy';
import { HttpNetworkAccountsConfig } from 'hardhat/types';
import { configDotenv } from 'dotenv';
import * as envEnc from '@chainlink/env-enc';

import '@layerzerolabs/toolbox-hardhat'
import { EndpointId } from '@layerzerolabs/lz-definitions'

configDotenv();
envEnc.config();

Expand All @@ -35,13 +38,26 @@ if (pkey) {
*/
const config: HardhatUserConfig = {
solidity: {
version: '0.8.10',
settings: {
optimizer: {
enabled: true,
runs: 0,
compilers: [
{
version: '0.8.22',
settings: {
optimizer: {
enabled: true,
runs: 0,
},
},
},
},
{
version: '0.8.10',
settings: {
optimizer: {
enabled: true,
runs: 0,
},
},
},
],
},
networks: {
develop: {
Expand Down
Loading