diff --git a/contracts/sfc/ConstantsManager.sol b/contracts/sfc/ConstantsManager.sol index 0e23abc..8c35e2a 100644 --- a/contracts/sfc/ConstantsManager.sol +++ b/contracts/sfc/ConstantsManager.sol @@ -37,6 +37,10 @@ contract ConstantsManager is Ownable { // Zero to disable validators deactivation by this metric. uint64 public minAverageUptime; + // The address of the recipient that receives issued tokens + // as a counterparty to the burnt FTM tokens + address public issuedTokensRecipient; + /** * @dev Given value is too small */ @@ -177,4 +181,8 @@ contract ConstantsManager is Ownable { } minAverageUptime = v; } + + function updateIssuedTokensRecipient(address v) external virtual onlyOwner { + issuedTokensRecipient = v; + } } diff --git a/contracts/sfc/NodeDriverAuth.sol b/contracts/sfc/NodeDriverAuth.sol index eba4f24..94383cd 100644 --- a/contracts/sfc/NodeDriverAuth.sol +++ b/contracts/sfc/NodeDriverAuth.sol @@ -83,9 +83,6 @@ contract NodeDriverAuth is OwnableUpgradeable, UUPSUpgradeable { /// Mint native token. To be used by SFC for minting validators rewards. function incBalance(address acc, uint256 diff) external onlySFC { - if (acc != address(sfc)) { - revert RecipientNotSFC(); - } driver.setBalance(acc, address(acc).balance + diff); } diff --git a/contracts/sfc/SFC.sol b/contracts/sfc/SFC.sol index 3adbc73..ef9b782 100644 --- a/contracts/sfc/SFC.sol +++ b/contracts/sfc/SFC.sol @@ -459,6 +459,15 @@ contract SFC is OwnableUpgradeable, UUPSUpgradeable, Version { _burnFTM(amount); } + /// Issue tokens to the issued tokens recipient as a counterparty to the burnt FTM tokens. + function issueTokens(uint256 amount) external onlyOwner { + if (c.issuedTokensRecipient() == address(0)) { + revert ZeroAddress(); + } + node.incBalance(c.issuedTokensRecipient(), amount); + totalSupply += amount; + } + /// Update treasury address. function updateTreasuryAddress(address v) external onlyOwner { treasuryAddress = v; diff --git a/test/SFC.ts b/test/SFC.ts index 7c72c77..b3ea8d6 100644 --- a/test/SFC.ts +++ b/test/SFC.ts @@ -131,6 +131,30 @@ describe('SFC', () => { }); }); + describe('Issue tokens', () => { + it('Should revert when not owner', async function () { + await expect(this.sfc.connect(this.user).issueTokens(ethers.parseEther('100'))).to.be.revertedWithCustomError( + this.sfc, + 'OwnableUnauthorizedAccount', + ); + }); + + it('Should revert when recipient is not set', async function () { + await expect(this.sfc.connect(this.owner).issueTokens(ethers.parseEther('100'))).to.be.revertedWithCustomError( + this.sfc, + 'ZeroAddress', + ); + }); + + it('Should succeed and issue tokens', async function () { + await this.constants.updateIssuedTokensRecipient(this.user); + const supply = await this.sfc.totalSupply(); + const amount = ethers.parseEther('100'); + await this.sfc.connect(this.owner).issueTokens(amount); + expect(await this.sfc.totalSupply()).to.equal(supply + amount); + }); + }); + describe('Create validator', () => { const validatorsFixture = async () => { const validatorPubKey =