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
4 changes: 4 additions & 0 deletions ops/deploy_swarm_contracts_zksync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
# - COUNTRY_MULTIPLIER: (optional) Country multiplier for bond calculation (0 = use default)
# - BOND_QUOTA: (optional) Max bond amount sponsorable per period in wei
# - BOND_PERIOD: (optional) Quota renewal period in seconds
# - FLEET_OPERATOR_BOND_ALLOWANCE: (optional) Initial bond allowance for fleet operator in wei (defaults to BOND_QUOTA)
#
# =============================================================================

Expand Down Expand Up @@ -190,6 +191,7 @@ preflight_checks() {
export PAYMASTER_WITHDRAWER="${PAYMASTER_WITHDRAWER:-$L2_ADMIN}"
export BOND_QUOTA="${BOND_QUOTA:-100000000000000000000000}" # 100000 NODL default
export BOND_PERIOD="${BOND_PERIOD:-86400}" # 1 day default
export FLEET_OPERATOR_BOND_ALLOWANCE="${FLEET_OPERATOR_BOND_ALLOWANCE:-$BOND_QUOTA}"

log_success "Pre-flight checks passed"
}
Expand Down Expand Up @@ -347,6 +349,7 @@ deploy_contracts() {
log_info " FLEET_OPERATOR: $FLEET_OPERATOR"
log_info " BOND_QUOTA: $BOND_QUOTA"
log_info " BOND_PERIOD: $BOND_PERIOD"
log_info " FLEET_OPERATOR_BOND_ALLOWANCE: $FLEET_OPERATOR_BOND_ALLOWANCE"
log_info " RPC: $RPC_URL"
return 0
fi
Expand Down Expand Up @@ -616,6 +619,7 @@ print_summary() {
echo " Base Bond: $BASE_BOND wei"
echo " Bond Quota: $BOND_QUOTA wei"
echo " Bond Period: $BOND_PERIOD seconds"
echo " Operator Allowance: $FLEET_OPERATOR_BOND_ALLOWANCE wei"
echo ""
echo "=============================================="
}
Expand Down
6 changes: 6 additions & 0 deletions script/DeploySwarmUpgradeableZkSync.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {BondTreasuryPaymaster} from "../src/paymasters/BondTreasuryPaymaster.sol
* - BOND_QUOTA: (optional) Max bond amount sponsorable per period in wei (defaults to 100k NODL)
* - BOND_PERIOD: (optional) Quota renewal period in seconds (defaults to 1 day)
* - FLEET_OPERATOR: Address of the Nodle swarm operator (initial whitelisted user)
* - FLEET_OPERATOR_BOND_ALLOWANCE: (optional) Initial bond allowance for fleet operator in wei (defaults to BOND_QUOTA)
*/
contract DeploySwarmUpgradeableZkSync is Script {
// Deployment artifacts
Expand All @@ -50,6 +51,7 @@ contract DeploySwarmUpgradeableZkSync is Script {
uint256 bondQuota = vm.envOr("BOND_QUOTA", uint256(100_000 ether)); // 100k NODL default
uint256 bondPeriod = vm.envOr("BOND_PERIOD", uint256(1 days));
address fleetOperator = vm.envAddress("FLEET_OPERATOR");
uint256 fleetOperatorBondAllowance = vm.envOr("FLEET_OPERATOR_BOND_ALLOWANCE", bondQuota);

console.log("=== Deploying Upgradeable Swarm Contracts on ZkSync ===");
console.log("Bond Token:", bondToken);
Expand All @@ -59,6 +61,7 @@ contract DeploySwarmUpgradeableZkSync is Script {
console.log("Bond Quota:", bondQuota);
console.log("Bond Period:", bondPeriod);
console.log("Fleet Operator:", fleetOperator);
console.log("Fleet Operator Bond Allowance:", fleetOperatorBondAllowance);
console.log("");

vm.startBroadcast(deployerPrivateKey);
Expand Down Expand Up @@ -105,13 +108,16 @@ contract DeploySwarmUpgradeableZkSync is Script {
whitelistedContracts[2] = swarmRegistryProxy;
address[] memory whitelistedUsers = new address[](1);
whitelistedUsers[0] = fleetOperator;
uint256[] memory initialBondAllowances = new uint256[](1);
initialBondAllowances[0] = fleetOperatorBondAllowance;
bondTreasuryPaymaster = address(
new BondTreasuryPaymaster(
owner,
fleetOperator,
withdrawer,
whitelistedContracts,
whitelistedUsers,
initialBondAllowances,
bondToken,
bondQuota,
bondPeriod
Expand Down
4 changes: 4 additions & 0 deletions src/paymasters/BondTreasuryPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,20 @@ contract BondTreasuryPaymaster is WhitelistPaymaster, QuotaControl {
error CallerNotWhitelistedContract();
error InsufficientBondBalance();
error UserBondAllowanceExceeded();
error ArrayLengthMismatch();

constructor(
address admin,
address whitelistAdmin,
address withdrawer,
address[] memory initialWhitelistedContracts,
address[] memory initialWhitelistedUsers,
uint256[] memory initialBondAllowances,
address bondToken_,
uint256 initialQuota,
uint256 initialPeriod
) WhitelistPaymaster(admin, withdrawer) QuotaControl(initialQuota, initialPeriod, admin) {
if (initialWhitelistedUsers.length != initialBondAllowances.length) revert ArrayLengthMismatch();
if (whitelistAdmin != admin) {
_grantRole(WHITELIST_ADMIN_ROLE, whitelistAdmin);
}
Expand All @@ -61,6 +64,7 @@ contract BondTreasuryPaymaster is WhitelistPaymaster, QuotaControl {
uint256 m = initialWhitelistedUsers.length;
for (uint256 j = 0; j < m; ++j) {
isWhitelistedUser[initialWhitelistedUsers[j]] = true;
userBondAllowance[initialWhitelistedUsers[j]] = initialBondAllowances[j];
}
if (m > 0) {
emit WhitelistedUsersAdded(initialWhitelistedUsers);
Expand Down
122 changes: 116 additions & 6 deletions test/paymasters/BondTreasuryPaymaster.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ contract MockBondTreasuryPaymaster is BondTreasuryPaymaster {
address withdrawer,
address[] memory initialWhitelistedContracts,
address[] memory initialWhitelistedUsers,
uint256[] memory initialBondAllowances,
address bondToken_,
uint256 initialQuota,
uint256 initialPeriod
Expand All @@ -56,6 +57,7 @@ contract MockBondTreasuryPaymaster is BondTreasuryPaymaster {
withdrawer,
initialWhitelistedContracts,
initialWhitelistedUsers,
initialBondAllowances,
bondToken_,
initialQuota,
initialPeriod
Expand Down Expand Up @@ -111,12 +113,22 @@ contract BondTreasuryPaymasterTest is Test {
return new address[](0);
}

function _emptyAmounts() internal pure returns (uint256[] memory) {
return new uint256[](0);
}

function _singleAddress(address a) internal pure returns (address[] memory) {
address[] memory arr = new address[](1);
arr[0] = a;
return arr;
}

function _singleAmount(uint256 v) internal pure returns (uint256[] memory) {
uint256[] memory arr = new uint256[](1);
arr[0] = v;
return arr;
}

function setUp() public {
bondToken = new MockERC20SCP();

Expand All @@ -131,12 +143,17 @@ contract BondTreasuryPaymasterTest is Test {
initialUsers[0] = alice;
initialUsers[1] = admin;

uint256[] memory initialAllowances = new uint256[](2);
initialAllowances[0] = 10_000 ether;
initialAllowances[1] = 10_000 ether;

paymaster = new MockBondTreasuryPaymaster(
admin,
admin,
withdrawer,
_initialContractWhitelist(address(fleet)),
initialUsers,
initialAllowances,
address(bondToken),
QUOTA,
PERIOD
Expand All @@ -145,10 +162,6 @@ contract BondTreasuryPaymasterTest is Test {
bondToken.mint(address(paymaster), 10_000 ether);
whitelistTargets = new address[](1);
whitelistTargets[0] = alice;

// Give alice a generous bond allowance for existing tests
vm.prank(admin);
paymaster.setUserBondAllowance(alice, 10_000 ether);
}

// ══════════════════════════════════════════════
Expand All @@ -169,6 +182,7 @@ contract BondTreasuryPaymasterTest is Test {
withdrawer,
_initialContractWhitelist(address(fleet)),
_emptyAddresses(),
_emptyAmounts(),
address(bondToken),
QUOTA,
PERIOD
Expand Down Expand Up @@ -197,13 +211,19 @@ contract BondTreasuryPaymasterTest is Test {
assertTrue(paymaster.isWhitelistedUser(admin));
}

function test_initialBondAllowancesSetInConstructor() public view {
assertEq(paymaster.userBondAllowance(alice), 10_000 ether);
assertEq(paymaster.userBondAllowance(admin), 10_000 ether);
}

function test_constructorWithEmptyWhitelistedUsers() public {
MockBondTreasuryPaymaster pm = new MockBondTreasuryPaymaster(
admin,
admin,
withdrawer,
_initialContractWhitelist(address(fleet)),
_emptyAddresses(),
_emptyAmounts(),
address(bondToken),
QUOTA,
PERIOD
Expand All @@ -219,26 +239,39 @@ contract BondTreasuryPaymasterTest is Test {
users[1] = bob;
users[2] = charlie;

uint256[] memory allowances = new uint256[](3);
allowances[0] = 500 ether;
allowances[1] = 300 ether;
allowances[2] = 100 ether;

MockBondTreasuryPaymaster pm = new MockBondTreasuryPaymaster(
admin,
admin,
withdrawer,
_initialContractWhitelist(address(fleet)),
users,
allowances,
address(bondToken),
QUOTA,
PERIOD
);
assertTrue(pm.isWhitelistedUser(alice));
assertTrue(pm.isWhitelistedUser(bob));
assertTrue(pm.isWhitelistedUser(charlie));
assertEq(pm.userBondAllowance(alice), 500 ether);
assertEq(pm.userBondAllowance(bob), 300 ether);
assertEq(pm.userBondAllowance(charlie), 100 ether);
}

function test_constructorEmitsWhitelistedUsersAdded() public {
address[] memory users = new address[](2);
users[0] = alice;
users[1] = bob;

uint256[] memory allowances = new uint256[](2);
allowances[0] = 100 ether;
allowances[1] = 200 ether;

vm.expectEmit();
emit WhitelistPaymaster.WhitelistedUsersAdded(users);
new MockBondTreasuryPaymaster(
Expand All @@ -247,6 +280,7 @@ contract BondTreasuryPaymasterTest is Test {
withdrawer,
_initialContractWhitelist(address(fleet)),
users,
allowances,
address(bondToken),
QUOTA,
PERIOD
Expand All @@ -261,6 +295,7 @@ contract BondTreasuryPaymasterTest is Test {
withdrawer,
_initialContractWhitelist(address(fleet)),
_emptyAddresses(),
_emptyAmounts(),
address(bondToken),
QUOTA,
PERIOD
Expand All @@ -272,6 +307,80 @@ contract BondTreasuryPaymasterTest is Test {
}
}

function test_RevertIf_constructorArrayLengthMismatch_moreThanUsers() public {
uint256[] memory tooMany = new uint256[](2);
tooMany[0] = 100 ether;
tooMany[1] = 200 ether;

vm.expectRevert(BondTreasuryPaymaster.ArrayLengthMismatch.selector);
new MockBondTreasuryPaymaster(
admin,
admin,
withdrawer,
_initialContractWhitelist(address(fleet)),
_singleAddress(alice),
tooMany,
address(bondToken),
QUOTA,
PERIOD
);
}

function test_RevertIf_constructorArrayLengthMismatch_fewerThanUsers() public {
address[] memory users = new address[](2);
users[0] = alice;
users[1] = bob;

vm.expectRevert(BondTreasuryPaymaster.ArrayLengthMismatch.selector);
new MockBondTreasuryPaymaster(
admin,
admin,
withdrawer,
_initialContractWhitelist(address(fleet)),
users,
_singleAmount(100 ether),
address(bondToken),
QUOTA,
PERIOD
);
}

function test_constructorZeroAllowance_whitelistedButNoAllowance() public {
MockBondTreasuryPaymaster pm = new MockBondTreasuryPaymaster(
admin,
admin,
withdrawer,
_initialContractWhitelist(address(fleet)),
_singleAddress(alice),
_singleAmount(0),
address(bondToken),
QUOTA,
PERIOD
);
assertTrue(pm.isWhitelistedUser(alice));
assertEq(pm.userBondAllowance(alice), 0);
}

function test_constructorAllowance_usableImmediately() public {
MockBondTreasuryPaymaster pm = new MockBondTreasuryPaymaster(
admin,
admin,
withdrawer,
_initialContractWhitelist(address(fleet)),
_singleAddress(alice),
_singleAmount(BASE_BOND),
address(bondToken),
QUOTA,
PERIOD
);
bondToken.mint(address(pm), 10_000 ether);

vm.prank(alice);
fleet.claimUuidSponsored(UUID_1, address(0), address(pm));
assertEq(fleet.uuidOwner(UUID_1), alice);
assertEq(pm.userBondAllowance(alice), 0);
}

// ══════════════════════════════════════════════
// Whitelist Management
// ══════════════════════════════════════════════
Expand Down Expand Up @@ -553,14 +662,13 @@ contract BondTreasuryPaymasterTest is Test {
withdrawer,
_initialContractWhitelist(address(fleet)),
_singleAddress(alice),
_singleAmount(10_000 ether),
address(bondToken),
BASE_BOND / 2,
PERIOD
);

bondToken.mint(address(tightPaymaster), 10_000 ether);
vm.prank(admin);
tightPaymaster.setUserBondAllowance(alice, 10_000 ether);

vm.prank(alice);
vm.expectRevert(QuotaControl.QuotaExceeded.selector);
Expand Down Expand Up @@ -638,6 +746,7 @@ contract BondTreasuryPaymasterTest is Test {
withdrawer,
_initialContractWhitelist(address(fleet)),
_emptyAddresses(),
_emptyAmounts(),
address(bondToken),
QUOTA,
0
Expand All @@ -652,6 +761,7 @@ contract BondTreasuryPaymasterTest is Test {
withdrawer,
_initialContractWhitelist(address(fleet)),
_emptyAddresses(),
_emptyAmounts(),
address(bondToken),
QUOTA,
31 days
Expand Down
Loading