Skip to content

Commit

Permalink
Merge branch 'main' into feature/plume-nest
Browse files Browse the repository at this point in the history
  • Loading branch information
jaketimothy authored Dec 30, 2024
2 parents 1fa1a29 + c2b47fb commit c420541
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 67 deletions.
35 changes: 6 additions & 29 deletions script/DeployAllSandbox.s.sol → script/DeployAll.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,10 @@ import {UpgradeableBeacon} from "openzeppelin-contracts/contracts/proxy/beacon/U
import {BeaconProxy} from "openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol";
import {ERC1967Proxy} from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract DeployAllSandbox is Script {
contract DeployAll is Script {
struct DeployConfig {
address deployer;
address treasury;
address operator;
address distributor;
address relayer;
address paymentToken;
}

struct Deployments {
Expand All @@ -44,27 +40,18 @@ contract DeployAllSandbox is Script {
DividendDistribution dividendDistributor;
}

uint64 constant perOrderFee = 1e8;
uint24 constant percentageFeeRate = 5_000;

function run() external {
// load env variables
uint256 deployerPrivateKey = vm.envUint("DEPLOY_KEY");

DeployConfig memory cfg = DeployConfig({
deployer: vm.addr(deployerPrivateKey),
treasury: vm.envAddress("TREASURY"),
operator: vm.envAddress("OPERATOR"),
distributor: vm.envAddress("DISTRIBUTOR"),
relayer: vm.envAddress("RELAYER"),
paymentToken: vm.envAddress("GNUSD")
});
DeployConfig memory cfg =
DeployConfig({deployer: vm.addr(deployerPrivateKey), treasury: vm.envAddress("TREASURY")});

Deployments memory deployments;

console.log("deployer: %s", cfg.deployer);

bytes32 salt = keccak256(abi.encodePacked("0.4.1pre1"));
bytes32 salt = keccak256(abi.encodePacked("0.4.3"));

// send txs as deployer
vm.startBroadcast(deployerPrivateKey);
Expand Down Expand Up @@ -137,27 +124,17 @@ contract DeployAllSandbox is Script {

// fulfillment router
deployments.fulfillmentRouter = new FulfillmentRouter(cfg.deployer);
console.log("fulfillment router: %s", address(deployments.fulfillmentRouter));

// latest price helper
deployments.latestPriceHelper = new LatestPriceHelper{salt: salt}();

// config operator
deployments.orderProcessor.setOperator(address(deployments.fulfillmentRouter), true);
deployments.fulfillmentRouter.grantRole(deployments.fulfillmentRouter.OPERATOR_ROLE(), cfg.operator);

// config payment token
deployments.orderProcessor.setPaymentToken(
cfg.paymentToken, bytes4(0), perOrderFee, percentageFeeRate, perOrderFee, percentageFeeRate
);
console.log("latest price helper: %s", address(deployments.latestPriceHelper));

/// ------------------ dividend distributor ------------------

deployments.dividendDistributor = new DividendDistribution{salt: salt}(cfg.deployer);
console.log("dividend distributor: %s", address(deployments.dividendDistributor));

// add distributor
deployments.dividendDistributor.grantRole(deployments.dividendDistributor.DISTRIBUTOR_ROLE(), cfg.distributor);

vm.stopBroadcast();
}
}
3 changes: 3 additions & 0 deletions script/deployall-cmd-plume.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

forge script script/DeployAll.s.sol:DeployAll --rpc-url $RPC_URL -vvv --broadcast --slow --skip-simulation --verifier blockscout --verifier-url https://phoenix-explorer.plumenetwork.xyz/api\? --verify
3 changes: 3 additions & 0 deletions script/deployall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

op run --env-file="./.env.prod-plume" -- ./script/deployall-cmd-plume.sh
6 changes: 0 additions & 6 deletions script/deployallsandbox.sh

This file was deleted.

4 changes: 2 additions & 2 deletions script/verify.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#!/bin/sh

cp .env.prod-kinto .env
cp .env.prod-plume .env
source .env

# args
# forge verify-contract --chain-id 161221135 --verifier blockscout --watch --constructor-args $(cast abi-encode "constructor(address,bytes)" "0x638c2Fa8B02E8F294e8Af9d7F2248Ec1E085aa79" "0x000000000000000000000000702347e2b1be68444c1451922275b66aabdac5280000000000000000000000000fe4f28b0213201f333e9bf29fca76965a8c5fc80000000000000000000000003934aeee752235aee8139dbec4493639534eff2d000000000000000000000000aa5474bbb3aec03b81d1e280c821dbef60a7aabe") 0x94902a03f7E27c6f512B3E1E8cc7b1e1d2CCeE63 lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy
# forge verify-contract --chain-id 161221135 --verifier blockscout --watch --constructor-args $(cast abi-encode "constructor(address)" "0x702347E2B1be68444C1451922275b66AABDaC528") 0x0F96bf4a333ab9f46B7bA9B873B99F6022798Aa5 src/dividend/DividendDistribution.sol:DividendDistribution
# no args
forge verify-contract --chain-id 7887 --verifier blockscout --watch 0x3F3D37A3cA88070e125b3CbfAe07f77425F08c47 src/orders/OrderProcessor.sol:OrderProcessor
forge verify-contract --chain-id 98865 --verifier blockscout --verifier-url https://phoenix-explorer.plumenetwork.xyz/api\? --watch 0x84fB5Af5Cf1ee395fa249c67a02E2f15C7111444 src/TransferRestrictor.sol:TransferRestrictor
37 changes: 22 additions & 15 deletions src/ERC20Rebasing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
pragma solidity ^0.8.23;

import {ERC20} from "solady/src/tokens/ERC20.sol";
import {mulDiv, mulDiv18} from "prb-math/Common.sol";
import {NumberUtils} from "./common/NumberUtils.sol";
import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol";

/// @notice Rebasing ERC20 token as an in-place upgrade to solady erc20
/// @author Dinari (https://github.com/dinaricrypto/sbt-contracts/blob/main/src/dShare.sol)
Expand All @@ -23,11 +23,11 @@ abstract contract ERC20Rebasing is ERC20 {
function balancePerShare() public view virtual returns (uint128);

function sharesToBalance(uint256 shares) public view returns (uint256) {
return mulDiv18(shares, balancePerShare()); // floor
return FixedPointMathLib.fullMulDiv(shares, balancePerShare(), _INITIAL_BALANCE_PER_SHARE); // floor
}

function balanceToShares(uint256 balance) public view returns (uint256) {
return mulDiv(balance, _INITIAL_BALANCE_PER_SHARE, balancePerShare()); // floor
return FixedPointMathLib.fullMulDiv(balance, _INITIAL_BALANCE_PER_SHARE, balancePerShare()); // floor
}

/// ------------------ ERC20 ------------------
Expand All @@ -36,12 +36,17 @@ abstract contract ERC20Rebasing is ERC20 {
return sharesToBalance(super.totalSupply());
}

/// @notice Returns the maximum supply of the token in balance.
/// @dev Useful for sanity checks before minting since the total supply of shares can overflow.
function maxSupply() public view virtual returns (uint256) {
// Reduced maxSupply of shares to prevent overflow in balanceToShares and other functions
uint128 balancePerShare_ = balancePerShare();
if (balancePerShare_ < _INITIAL_BALANCE_PER_SHARE) {
return mulDiv18(type(uint256).max, balancePerShare_);
// maxSupply = type(uint256).max * balancePerShare_ / _INITIAL_BALANCE_PER_SHARE
return FixedPointMathLib.fullMulDiv(type(uint256).max, balancePerShare_, _INITIAL_BALANCE_PER_SHARE);
} else if (balancePerShare_ > _INITIAL_BALANCE_PER_SHARE) {
return mulDiv(type(uint256).max, _INITIAL_BALANCE_PER_SHARE, balancePerShare_);
// maxSupply = type(uint256).max * _INITIAL_BALANCE_PER_SHARE / balancePerShare_
return FixedPointMathLib.fullMulDiv(type(uint256).max, _INITIAL_BALANCE_PER_SHARE, balancePerShare_);
}
return type(uint256).max;
}
Expand Down Expand Up @@ -101,20 +106,21 @@ abstract contract ERC20Rebasing is ERC20 {
function _mint(address to, uint256 amount) internal virtual override {
_beforeTokenTransfer(address(0), to, amount);
uint256 totalSharesBefore = super.totalSupply();
uint256 totalSupplyBefore = sharesToBalance(totalSharesBefore);
uint256 totalSupplyAfter = 0;
unchecked {
totalSupplyAfter = totalSupplyBefore + amount;
if (totalSupplyAfter < totalSupplyBefore) revert TotalSupplyOverflow();
}
if (NumberUtils.mulDivCheckOverflow(totalSupplyAfter, _INITIAL_BALANCE_PER_SHARE, balancePerShare())) {
revert TotalSupplyOverflow();
}
// Floor the shares to mint
uint256 shares = balanceToShares(amount);
// Check the total supply limit for shares
uint256 totalSharesAfter = 0;
unchecked {
totalSharesAfter = totalSharesBefore + shares;
}
// Check overflow
if (totalSharesAfter < totalSharesBefore) revert TotalSupplyOverflow();
// Check total supply limit, can also revert with FullMulDivFailed in fullMulDivUp
// Round up for total supply limit check
if (
FixedPointMathLib.fullMulDivUp(totalSharesAfter, balancePerShare(), _INITIAL_BALANCE_PER_SHARE)
> maxSupply()
) revert TotalSupplyOverflow();
/// @solidity memory-safe-assembly
assembly {
// Store the updated total supply.
Expand All @@ -135,7 +141,8 @@ abstract contract ERC20Rebasing is ERC20 {
// Convert to shares
function _burn(address from, uint256 amount) internal virtual override {
_beforeTokenTransfer(from, address(0), amount);
uint256 shares = balanceToShares(amount);
// Round up the shares to burn in favor of the contract
uint256 shares = FixedPointMathLib.fullMulDivUp(amount, _INITIAL_BALANCE_PER_SHARE, balancePerShare());
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
Expand Down
66 changes: 51 additions & 15 deletions test/dShare.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {ERC1967Proxy} from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC19
import {IERC20Errors} from "openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol";
import {PRBMath_MulDiv18_Overflow, PRBMath_MulDiv_Overflow} from "prb-math/Common.sol";
import {NumberUtils} from "../src/common/NumberUtils.sol";
import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol";

contract DShareTest is Test {
event NameSet(string name);
Expand All @@ -25,6 +26,16 @@ contract DShareTest is Test {
address user = address(2);
address admin = address(3);

function checkMintOverFlow(uint256 toMint, uint128 balancePerShare) private returns (bool) {
return NumberUtils.mulDivCheckOverflow(toMint, 1 ether, balancePerShare)
|| NumberUtils.mulDivCheckOverflow(toMint, balancePerShare, 1 ether)
|| (
balancePerShare > 1 ether
&& FixedPointMathLib.fullMulDiv(toMint, balancePerShare, 1 ether)
> FixedPointMathLib.fullMulDiv(type(uint256).max, 1 ether, balancePerShare)
);
}

function setUp() public {
vm.prank(admin);
restrictor = new TransferRestrictor(address(this));
Expand Down Expand Up @@ -79,7 +90,7 @@ contract DShareTest is Test {
assertEq(address(token.transferRestrictor()), account);
}

function testMint() public {
function testMintFixed() public {
token.grantRole(token.MINTER_ROLE(), address(this));
token.mint(user, 1e18);
assertEq(token.totalSupply(), 1e18);
Expand All @@ -94,7 +105,7 @@ contract DShareTest is Test {
token.mint(user, 1e18);
}

function testBurn() public {
function testBurnFixed() public {
token.grantRole(token.MINTER_ROLE(), address(this));
token.mint(user, 1e18);
token.grantRole(token.BURNER_ROLE(), user);
Expand Down Expand Up @@ -191,15 +202,15 @@ contract DShareTest is Test {

function testMint(uint256 amount, uint128 balancePerShare) public {
vm.assume(balancePerShare > 0);
vm.assume(!NumberUtils.mulDivCheckOverflow(amount, 1 ether, balancePerShare));
vm.assume(!checkMintOverFlow(amount, balancePerShare));

token.setBalancePerShare(balancePerShare);

token.grantRole(token.MINTER_ROLE(), address(this));
token.mint(user, amount);
uint256 balance = _nearestBalanceAmount(amount);
assertEq(token.totalSupply(), balance);
assertEq(token.balanceOf(user), balance);
assertLe(token.totalSupply(), balance);
assertLe(token.balanceOf(user), balance);
}

function testBurnUnauthorizedReverts(uint256 amount) public {
Expand All @@ -215,41 +226,43 @@ contract DShareTest is Test {

function testBurn(uint256 amount, uint128 balancePerShare) public {
vm.assume(balancePerShare > 0);
vm.assume(!NumberUtils.mulDivCheckOverflow(amount, 1 ether, balancePerShare));
vm.assume(!checkMintOverFlow(amount, balancePerShare));

token.setBalancePerShare(balancePerShare);

token.grantRole(token.MINTER_ROLE(), address(this));
token.mint(user, amount);
token.grantRole(token.BURNER_ROLE(), user);

uint256 userBalance = token.balanceOf(user);
vm.prank(user);
token.burn(amount);
token.burn(userBalance);
assertEq(token.totalSupply(), 0);
assertEq(token.balanceOf(user), 0);
}

function testBurnFrom(uint256 amount, uint128 balancePerShare) public {
vm.assume(balancePerShare > 0);
vm.assume(!NumberUtils.mulDivCheckOverflow(amount, 1 ether, balancePerShare));
vm.assume(!checkMintOverFlow(amount, balancePerShare));

token.setBalancePerShare(balancePerShare);

token.grantRole(token.MINTER_ROLE(), address(this));
token.mint(user, amount);
token.grantRole(token.BURNER_ROLE(), address(this));

uint256 userBalance = token.balanceOf(user);
vm.prank(user);
token.approve(address(this), amount);
token.approve(address(this), userBalance);

token.burnFrom(user, amount);
token.burnFrom(user, userBalance);
assertEq(token.totalSupply(), 0);
assertEq(token.balanceOf(user), 0);
}

function testTransfer(uint256 amount, uint128 balancePerShare) public {
vm.assume(balancePerShare > 0);
vm.assume(!NumberUtils.mulDivCheckOverflow(amount, 1 ether, balancePerShare));
vm.assume(!checkMintOverFlow(amount, balancePerShare));

token.setBalancePerShare(balancePerShare);

Expand All @@ -258,11 +271,13 @@ contract DShareTest is Test {
token.grantRole(token.MINTER_ROLE(), address(this));
token.mint(address(this), amount);

assertTrue(token.transfer(user, amount));
assertEq(token.totalSupply(), balance);
uint256 senderBalance = token.balanceOf(address(this));
assertTrue(token.transfer(user, senderBalance));
assertLe(token.totalSupply(), balance);

assertEq(token.balanceOf(address(this)), 0);
assertEq(token.balanceOf(user), balance);
// Can collect dust
// assertEq(token.balanceOf(address(this)), 0);
assertLe(token.balanceOf(user), balance);
}

function testTransferRestrictedTo(uint256 amount) public {
Expand Down Expand Up @@ -307,4 +322,25 @@ contract DShareTest is Test {
assertEq(token.totalSupply(), balance);
assertEq(token.balanceOf(user), balance);
}

function testMaxSupply(uint128 balancePerShare_) public {
vm.assume(balancePerShare_ > 0); // From testSetBalancePerShareZeroReverts
// Skip cases where multiplication would overflow
vm.assume(!NumberUtils.mulDivCheckOverflow(type(uint256).max, balancePerShare_, 1 ether));

// Set the balance per share
token.setBalancePerShare(balancePerShare_);

// Get actual max supply
uint256 maxSupply = token.maxSupply();

// Compare with expected value based on balancePerShare
if (balancePerShare_ == 1 ether) {
assertEq(maxSupply, type(uint256).max);
} else if (balancePerShare_ < 1 ether) {
assertEq(maxSupply, FixedPointMathLib.fullMulDiv(type(uint256).max, balancePerShare_, 1e18));
} else {
assertEq(maxSupply, FixedPointMathLib.fullMulDiv(type(uint256).max, 1 ether, balancePerShare_));
}
}
}

0 comments on commit c420541

Please sign in to comment.