Skip to content

Commit f4377b1

Browse files
committed
adjust copy fee logic and add some function into router
1 parent 7ecb9cd commit f4377b1

File tree

9 files changed

+136
-59
lines changed

9 files changed

+136
-59
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ deploy:
1717
deploy-router:
1818
STORAGE_ADDRESS=$(STORAGE_ADDRESS) forge script ./script/DeployRouter.s.sol --rpc-url $(RPC_URL) --broadcast --private-key $(PRIVATE_KEY)
1919

20+
upgrade-router:
21+
PROXY_ADDRESS=$(PROXY_ADDRESS) STORAGE_ADDRESS=$(STORAGE_ADDRESS) forge script ./script/UpgradeRouter.s.sol --rpc-url $(RPC_URL) --broadcast --private-key $(PRIVATE_KEY)
22+
2023
upgrade:
2124
PROXY_ADDRESS=$(PROXY_ADDRESS) forge script ./script/Upgrade$(UPGRADE_TO).s.sol --rpc-url $(RPC_URL) --broadcast --private-key $(PRIVATE_KEY)
2225

script/UpgradeRouter.s.sol

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.26;
3+
4+
import {Script, console} from "forge-std/Script.sol";
5+
import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol";
6+
import {BlueprintV3} from "../src/BlueprintV3.sol";
7+
8+
contract DeployScript is Script {
9+
function setUp() public {}
10+
11+
function run() public {
12+
vm.startBroadcast();
13+
14+
address proxyAddr = vm.envAddress("PROXY_ADDRESS");
15+
Options memory opts;
16+
opts.referenceContract = "BlueprintV2.sol";
17+
Upgrades.upgradeProxy(
18+
proxyAddr, "BlueprintV3.sol:BlueprintV3", abi.encodeCall(BlueprintV3.initialize, ()), opts
19+
);
20+
BlueprintV3 proxy = BlueprintV3(proxyAddr);
21+
console.log("New Version:", proxy.VERSION());
22+
23+
vm.stopBroadcast();
24+
}
25+
}

src/Agent.sol

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ contract Agent is ERC2771Context, Payment {
1212
string public VERSION;
1313

1414
event SetCopyAgentFee(bytes32 indexed agentRequestID, address userAddress, address tokenAddress, uint256 fee);
15-
event CopyAgentRequest(bytes32 indexed copyID, bytes32 indexed originalAgentRequestID, address userAddress);
15+
event CopyAgentRequest(
16+
bytes32 indexed copyID, bytes32 indexed originalAgentRequestID, address userAddress, uint256 totalFee
17+
);
1618

1719
event UserTopUp(
1820
address indexed walletAddress, address feeCollectionWalletAddress, address tokenAddress, uint256 amount
@@ -35,8 +37,8 @@ contract Agent is ERC2771Context, Payment {
3537
}
3638

3739
function setCopyAgentFee(bytes32 agentRequestID, address tokenAddress, uint256 fee) external {
38-
require(fee > 0, "Fee must be greater than 0");
3940
require(Blueprint(blueprintStorageProxy).paymentAddressEnableMp(tokenAddress), "Invalid token address");
41+
4042
address sender = _msgSender();
4143
require(Blueprint(blueprintStorageProxy).getDeploymentOwner(agentRequestID) == sender, "Not owner");
4244

@@ -49,7 +51,6 @@ contract Agent is ERC2771Context, Payment {
4951
{
5052
address owner = Blueprint(blueprintStorageProxy).getDeploymentOwner(agentRequestID);
5153
require(owner != address(0), "Invalid agentRequestID");
52-
require(fee > 0, "Fee must be greater than 0");
5354
require(Blueprint(blueprintStorageProxy).paymentAddressEnableMp(tokenAddress), "Invalid token address");
5455

5556
uint256 nonce = Blueprint(blueprintStorageProxy).getUserNonce(owner);
@@ -103,17 +104,12 @@ contract Agent is ERC2771Context, Payment {
103104
address owner = Blueprint(blueprintStorageProxy).getDeploymentOwner(originalAgentRequestID);
104105
require(owner != address(0), "Invalid original agent request ID");
105106

106-
uint256 fee = Blueprint(blueprintStorageProxy).copyAgentFeeMp(originalAgentRequestID, tokenAddress);
107-
require(fee > 0, "Copy Agent is not set by the owner");
108-
107+
uint256 platformFee = Blueprint(blueprintStorageProxy).platformFee(tokenAddress);
109108
// check platformFee is set or not
110-
require(Blueprint(blueprintStorageProxy).platformCopyAgentFee() > 0, "Platform fee is not set");
111-
109+
require(platformFee > 0, "Platform fee is not set");
112110
// calculate platform and creator fees
113-
uint256 platformFee =
114-
(fee * Blueprint(blueprintStorageProxy).platformCopyAgentFee()) / Blueprint(blueprintStorageProxy).factor();
115-
116-
uint256 creatorFee = fee - platformFee;
111+
uint256 totalFee = getCreateCopyAgentFee(originalAgentRequestID, tokenAddress);
112+
uint256 creatorFee = totalFee - platformFee;
117113

118114
address feeWallet = Blueprint(blueprintStorageProxy).feeCollectionWalletAddress();
119115
address fromAddr = getPaymentFromAddress();
@@ -125,9 +121,23 @@ contract Agent is ERC2771Context, Payment {
125121
payWithERC20(tokenAddress, platformFee, fromAddr, feeWallet);
126122

127123
// pay the creator fee to the owner of the original agent request
128-
payWithERC20(tokenAddress, creatorFee, fromAddr, owner);
124+
if (creatorFee > 0) {
125+
// if creator fee is greater than 0, we pay the creator fee to the owner
126+
payWithERC20(tokenAddress, creatorFee, fromAddr, owner);
127+
}
129128

130-
emit CopyAgentRequest(copyID, originalAgentRequestID, userAddress);
129+
emit CopyAgentRequest(copyID, originalAgentRequestID, userAddress, totalFee);
130+
}
131+
132+
function getCreateCopyAgentFee(bytes32 originalAgentRequestID, address tokenAddress)
133+
public
134+
view
135+
returns (uint256)
136+
{
137+
require(Blueprint(blueprintStorageProxy).paymentAddressEnableMp(tokenAddress), "Invalid token address");
138+
uint256 fee = Blueprint(blueprintStorageProxy).copyAgentFeeMp(originalAgentRequestID, tokenAddress);
139+
uint256 platformFee = Blueprint(blueprintStorageProxy).platformFee(tokenAddress);
140+
return (fee * platformFee) / Blueprint(blueprintStorageProxy).factor() + platformFee;
131141
}
132142

133143
function createAgentWithToken(

src/Blueprint.sol

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ contract Blueprint is Initializable, OwnableUpgradeable, BlueprintCore {
1414
event SetWorkerAdmin(address workerAdmin);
1515
event UpdateWorker(address workerAddress, bool isTrusted);
1616
event CreditReward(address indexed userAddress, uint256 amount);
17-
event SetGlobalPlatformFee(uint256 fee, uint256 factor);
17+
event SetGlobalPlatformFee(uint256 baseFee, address paymentAddress);
1818
event SetAdminContract(address agentContract);
19+
event RemoveAdminContract(address agentContract);
1920

2021
modifier isAdmin() {
2122
// slither-disable-next-line timestamp
@@ -136,13 +137,17 @@ contract Blueprint is Initializable, OwnableUpgradeable, BlueprintCore {
136137
emit SetAdminContract(_adminContract);
137138
}
138139

139-
// 6 %。= 6 / 100 means fee =6 and baseFactor = 100
140-
function setGlobalPlatformFee(uint256 fee, uint256 baseFactor) public isAdmin {
140+
function removeAdminContract(address _adminContract) public onlyOwner {
141+
require(_adminContract != address(0), "Admin contract address is invalid");
142+
delete adminContracts[_adminContract];
143+
emit RemoveAdminContract(_adminContract);
144+
}
145+
146+
// nation token fee is a global fee that applies to all agents
147+
function setGlobalPlatformFee(uint256 baseFee, address paymentAddress) public isAdmin {
141148
// base factor should be greater or equal than 10
142-
require(baseFactor >= 10, "Base factor should be greater than or equal to 10");
143-
require(fee <= baseFactor, "Fee cannot be greater than factor");
144-
factor = baseFactor;
145-
platformCopyAgentFee = fee;
146-
emit SetGlobalPlatformFee(fee, baseFactor);
149+
require(paymentAddressEnableMp[paymentAddress], "Payment Address is not added");
150+
platformFee[paymentAddress] = baseFee;
151+
emit SetGlobalPlatformFee(baseFee, paymentAddress);
147152
}
148153
}

src/BlueprintCore.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ contract BlueprintCore is Initializable, EIP712, Payment {
113113

114114
// agent copy fee mapping
115115
mapping(address => bool) public adminContracts;
116-
uint256 public platformCopyAgentFee;
116+
mapping(address => uint256) public platformFee;
117117
mapping(bytes32 => mapping(address => uint256)) public copyAgentFeeMp;
118118

119119
event CreateProjectID(bytes32 indexed projectID, address walletAddress);
@@ -207,7 +207,6 @@ contract BlueprintCore is Initializable, EIP712, Payment {
207207
// (if you ever add state, initialize it here)
208208
// default factor
209209
factor = 1000; // 100% factor
210-
platformCopyAgentFee = 100; // 1% platform fee for copy agent
211210
}
212211

213212
// slither-disable-end naming-convention

src/RouterV1.sol

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ contract RouterV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Payment
5757
external
5858
payable
5959
{
60-
uint256 fee = Blueprint(blueprint).copyAgentFeeMp(originalAgentRequestID, tokenAddress);
60+
uint256 totalFee = Agent(agent).getCreateCopyAgentFee(originalAgentRequestID, tokenAddress);
6161
// transfer funds to agent
6262
if (tokenAddress != address(0)) {
6363
// transfer ERC20 tokens to agent
64-
payWithERC20(tokenAddress, fee, msg.sender, agent);
64+
payWithERC20(tokenAddress, totalFee, msg.sender, agent);
6565
}
6666

6767
bytes memory data = abi.encodeWithSelector(
@@ -80,7 +80,7 @@ contract RouterV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Payment
8080
address tokenAddress,
8181
bytes memory signature
8282
) external payable {
83-
uint256 fee = Blueprint(blueprint).copyAgentFeeMp(originalAgentRequestID, tokenAddress);
83+
uint256 totalFee = Agent(agent).getCreateCopyAgentFee(originalAgentRequestID, tokenAddress);
8484

8585
// get digest
8686
bytes32 digest =
@@ -91,7 +91,7 @@ contract RouterV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Payment
9191
// transfer funds to agent
9292
if (tokenAddress != address(0)) {
9393
// transfer ERC20 tokens to agent
94-
payWithERC20(tokenAddress, fee, signer, agent);
94+
payWithERC20(tokenAddress, totalFee, signer, agent);
9595
}
9696

9797
Agent(agent).createCopyAgentRequestWithSig{value: msg.value}(
@@ -161,6 +161,15 @@ contract RouterV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Payment
161161
);
162162
}
163163

164+
// add getCreateCopyAgentFee from agent contract into Router
165+
function getCreateCopyAgentFee(bytes32 originalAgentRequestID, address tokenAddress)
166+
external
167+
view
168+
returns (uint256)
169+
{
170+
return Blueprint(blueprint).copyAgentFeeMp(originalAgentRequestID, tokenAddress);
171+
}
172+
164173
// --- Payment/TopUp ---
165174
function userTopUp(address tokenAddress, uint256 amount) external payable {
166175
// transfer funds to agent

test/Agent.t.sol

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ contract AgentTest is Test {
9696
agent.createAgentWithToken(projectId, base64Proposal, workerAddress, serverURL, address(mockToken));
9797

9898
// set copy agent fee to 1000
99-
uint256 copyAgentFee = 1000;
99+
uint256 copyAgentFee = 10; // 10 percent
100+
uint256 baseFee = 1000000; // 100000 token
100101

101102
// Expect revert because agentContract is not set in Blueprint
102103
// vm.expectRevert("Only Agent contract allow");
@@ -111,9 +112,12 @@ contract AgentTest is Test {
111112
);
112113

113114
// creator not set copy fee
114-
vm.expectRevert("Copy Agent is not set by the owner");
115+
vm.expectRevert("Platform fee is not set");
115116
agent.createCopyAgentRequest(copyID, requestId, address(mockToken));
116117

118+
// set platform fee
119+
blueprint.setGlobalPlatformFee(baseFee, address(mockToken)); // 100000 token
120+
117121
// set copy agent fee
118122
vm.expectEmit(true, true, true, true);
119123
emit Agent.SetCopyAgentFee(requestId, address(this), address(mockToken), copyAgentFee);
@@ -124,30 +128,34 @@ contract AgentTest is Test {
124128
agent.createCopyAgentRequest(copyID, requestId, address(mockToken));
125129

126130
// transfer some mock tokens to the sender
127-
mockToken.mint(address(this), copyAgentFee);
131+
uint256 totalFee = agent.getCreateCopyAgentFee(requestId, address(mockToken));
132+
mockToken.mint(address(this), totalFee);
128133

129134
// revert: not grant allowance to Agent contract
130135
vm.expectRevert("ERC20: transfer amount exceeds allowance");
131136
agent.createCopyAgentRequest(copyID, requestId, address(mockToken));
132137

133138
// grant allowance to blueprint address
134-
mockToken.approve(address(agent), copyAgentFee);
139+
mockToken.approve(address(agent), totalFee);
135140

136141
// owner cannot create copy agent request
137142
vm.expectRevert("Cannot transfer to self address");
138143
agent.createCopyAgentRequest(copyID, requestId, address(mockToken));
139144

140145
// transfer some mock tokens to relayer
141146
address relayer = address(0xBEEF);
142-
mockToken.mint(relayer, copyAgentFee);
147+
mockToken.mint(relayer, totalFee);
143148
vm.prank(relayer);
144149

145150
// grant allowance to blueprint address
146-
mockToken.approve(address(agent), copyAgentFee);
151+
mockToken.approve(address(agent), totalFee);
152+
153+
uint256 platformFee = blueprint.platformFee(address(mockToken));
154+
uint256 creatorFee = (copyAgentFee * platformFee) / blueprint.factor();
147155

148156
// Expect the CopyAgentRequest event (from Agent, which emits the same event)
149157
vm.expectEmit(true, false, false, false);
150-
emit Agent.CopyAgentRequest(copyID, requestId, address(this));
158+
emit Agent.CopyAgentRequest(copyID, requestId, address(this), platformFee + creatorFee);
151159

152160
// creator balance before creating copy agent request
153161
uint256 creatorBalanceBefore = mockToken.balanceOf(address(this));
@@ -157,8 +165,6 @@ contract AgentTest is Test {
157165
agent.createCopyAgentRequest(copyID, requestId, address(mockToken));
158166

159167
// check fee collect wallet balance and creator balance
160-
uint256 platformFee = (copyAgentFee * blueprint.platformCopyAgentFee()) / blueprint.factor();
161-
uint256 creatorFee = copyAgentFee - platformFee;
162168
uint256 feeCollectionWalletBalance = mockToken.balanceOf(blueprint.feeCollectionWalletAddress());
163169
// creator balance after creating copy agent request
164170
uint256 creatorBalanceAfter = mockToken.balanceOf(address(this));
@@ -192,7 +198,7 @@ contract AgentTest is Test {
192198
projectId, base64Proposal, workerAddress, serverURL, address(mockToken), createAgentSig
193199
);
194200
// Set copy agent fee using gasless signature
195-
uint256 copyAgentFee = 1000;
201+
uint256 copyAgentFee = 100; // 100 / 1000(factor) percent
196202
uint256 nonce = blueprint.getUserNonce(owner);
197203
bytes32 digest = blueprint.getSetCopyAgentFeeDigest(requestId, address(mockToken), copyAgentFee, nonce);
198204
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, digest);
@@ -215,7 +221,13 @@ contract AgentTest is Test {
215221
string memory serverURL = "app.crestal.network";
216222

217223
// Mint and approve tokens for owner (not relayer)
218-
uint256 copyAgentFee = 1000;
224+
uint256 copyAgentFee = 100; // 100 / 1000 (factor) percent
225+
226+
uint256 baseFee = 1000000; // 100000 token
227+
uint256 totalFee = baseFee + (copyAgentFee * baseFee) / blueprint.factor();
228+
// Set the global platform fee
229+
blueprint.setGlobalPlatformFee(baseFee, address(mockToken)); // 100000 token
230+
219231
// 0 cost, no need to set allowance and mint tokens
220232
// Create agent with token, owner is address(this)
221233
bytes32 requestId =
@@ -234,16 +246,16 @@ contract AgentTest is Test {
234246
bytes memory signature = abi.encodePacked(r, s, v);
235247

236248
// Mint and approve tokens for owner (not relayer)
237-
mockToken.mint(user, copyAgentFee);
249+
mockToken.mint(user, totalFee);
238250
vm.prank(user);
239-
mockToken.approve(address(agent), copyAgentFee);
251+
mockToken.approve(address(agent), totalFee);
240252

241253
// Record balances before
242254
uint256 ownerBalanceBefore = mockToken.balanceOf(user);
243255

244256
// Expect event
245-
vm.expectEmit(true, true, true, true);
246-
emit Agent.CopyAgentRequest(copyID, requestId, user);
257+
vm.expectEmit(true, true, true, false);
258+
emit Agent.CopyAgentRequest(copyID, requestId, user, 0);
247259

248260
// Relayer submits the gasless request
249261
vm.prank(relayer);
@@ -252,6 +264,6 @@ contract AgentTest is Test {
252264
// Check relayer balance is unchanged (should be 0)
253265
assertEq(mockToken.balanceOf(relayer), 0, "Relayer balance should be 0 after gasless copy agent request");
254266
// Check owner's balance is reduced by the fee
255-
assertEq(mockToken.balanceOf(user), ownerBalanceBefore - copyAgentFee, "Owner should pay the copy agent fee");
267+
assertEq(mockToken.balanceOf(user), ownerBalanceBefore - totalFee, "Owner should pay the copy agent fee");
256268
}
257269
}

test/BlueprintV7.t.sol

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,17 @@ contract BlueprintTest is Test {
4040
}
4141

4242
function test_setGlobalPlatformFee() public {
43+
// Add the payment address
44+
blueprint.addPaymentAddress(address(mockToken));
4345
// Set the global platform fee to 5%
44-
uint256 newFee = 5; // 5% in basis points
45-
blueprint.setGlobalPlatformFee(newFee, 100);
46+
uint256 baseFee = 100000; // 100000 nation token
47+
blueprint.setGlobalPlatformFee(baseFee, address(mockToken));
4648
// Retrieve the global platform fee
47-
uint256 fee = blueprint.platformCopyAgentFee();
49+
uint256 fee = blueprint.platformFee(address(mockToken));
4850
// Assert that the fee is set correctly
49-
assertEq(fee, newFee);
51+
assertEq(fee, baseFee);
5052
// factor check
51-
assertEq(100, blueprint.factor());
53+
assertEq(1000, blueprint.factor());
5254
}
5355

5456
function test_updateWorkerDeploymentConfig() public {

0 commit comments

Comments
 (0)