Bläddra i källkod

Merge pull request #2163 from pyth-network/add-fees-to-lazer-evm-contract

feat(lazer/contracts/evm): Add fees for verification
Darun Seethammagari 11 månader sedan
förälder
incheckning
ba3b04f76b

+ 12 - 1
lazer/contracts/evm/README.md

@@ -32,8 +32,10 @@ $ forge snapshot
 
 ### Anvil
 
+Anvil does not come with CreateX by default. It can be deployed or an RPC which has the contract can be forked. The below command forks an RPC with a functional CreateX contract.
+
 ```shell
-$ anvil
+$ anvil --fork-url "https://eth-sepolia.public.blastapi.io"
 ```
 
 ### Deploy
@@ -42,6 +44,15 @@ $ anvil
 $ forge script script/PythLazerDeploy.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast
 ```
 
+### Upgrade
+
+The UUPSUpgradeable feature adds functions to the cocntract which support upgrading through the use of an UUPS/ERC1967Proxy. A function can be defined to migrate state if needed. Be careful of changing storage slots when upgrading. See [Documentation](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable) for more details.
+In addition, the private key is necessary or contracts will be deployed to different addresses than expected.
+
+```shell
+$ forge script script/PythLazerDeploy.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast --sig "migrate()"
+```
+
 ### Cast
 
 ```shell

+ 18 - 0
lazer/contracts/evm/script/PythLazerDeploy.s.sol

@@ -6,6 +6,7 @@ import {PythLazer} from "../src/PythLazer.sol";
 import {ICreateX} from "createx/ICreateX.sol";
 import {CreateX} from "createx/CreateX.sol";
 import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
+import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
 
 // This script deploys the PythLazer proxy and implementation contract using
 // CreateX's contract factory to a deterministic address. Having deterministic
@@ -168,8 +169,25 @@ contract PythLazerDeployScript is Script {
         return addr;
     }
 
+    function getProxyAddress(bytes11 seed) public view returns (address addr) {
+        (, bytes32 guardedSalt) = generateSalt(seed);
+        address proxyAddr = createX.computeCreate3Address({salt: guardedSalt});
+        return proxyAddr;
+    }
+
     function run() public {
         address impl = deployImplementation("lazer:impl");
         deployProxy("lazer:proxy", impl);
     }
+
+    function migrate() public {
+        // Deploys new version and updates proxy to use new address
+        address proxyAddress = getProxyAddress("lazer:proxy");
+        address newImpl = deployImplementation("lazer:impl");
+        bytes memory migrateCall = abi.encodeWithSignature("migrate()");
+        vm.startBroadcast();
+        UUPSUpgradeable proxy = UUPSUpgradeable(proxyAddress);
+        proxy.upgradeToAndCall(newImpl, migrateCall);
+        vm.stopBroadcast();
+    }
 }

+ 16 - 3
lazer/contracts/evm/src/PythLazer.sol

@@ -5,7 +5,8 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
 import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
 
 contract PythLazer is OwnableUpgradeable, UUPSUpgradeable {
-    TrustedSignerInfo[2] public trustedSigners;
+    TrustedSignerInfo[100] internal trustedSigners;
+    uint256 public verification_fee;
 
     struct TrustedSignerInfo {
         address pubkey;
@@ -15,6 +16,12 @@ contract PythLazer is OwnableUpgradeable, UUPSUpgradeable {
     function initialize(address _topAuthority) public initializer {
         __Ownable_init(_topAuthority);
         __UUPSUpgradeable_init();
+
+        verification_fee = 1 wei;
+    }
+
+    function migrate() public onlyOwner {
+        verification_fee = 1 wei;
     }
 
     function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -62,7 +69,13 @@ contract PythLazer is OwnableUpgradeable, UUPSUpgradeable {
 
     function verifyUpdate(
         bytes calldata update
-    ) external view returns (bytes calldata payload, address signer) {
+    ) external payable returns (bytes calldata payload, address signer) {
+        // Require fee and refund excess
+        require(msg.value >= verification_fee, "Insufficient fee provided");
+        if (msg.value > verification_fee) {
+            payable(msg.sender).transfer(msg.value - verification_fee);
+        }
+
         if (update.length < 71) {
             revert("input too short");
         }
@@ -93,6 +106,6 @@ contract PythLazer is OwnableUpgradeable, UUPSUpgradeable {
     }
 
     function version() public pure returns (string memory) {
-        return "0.1.0";
+        return "0.1.1";
     }
 }

+ 44 - 1
lazer/contracts/evm/test/PythLazer.t.sol

@@ -12,7 +12,7 @@ contract PythLazerTest is Test {
         pythLazer.initialize(address(1));
     }
 
-    function test_update() public {
+    function test_update_add_signer() public {
         assert(!pythLazer.isValidSigner(address(2)));
         vm.prank(address(1));
         pythLazer.updateTrustedSigner(address(2), block.timestamp + 1000);
@@ -20,4 +20,47 @@ contract PythLazerTest is Test {
         skip(2000);
         assert(!pythLazer.isValidSigner(address(2)));
     }
+
+    function test_update_remove_signer() public {
+        assert(!pythLazer.isValidSigner(address(2)));
+        vm.prank(address(1));
+        pythLazer.updateTrustedSigner(address(2), block.timestamp + 1000);
+        assert(pythLazer.isValidSigner(address(2)));
+
+        vm.prank(address(1));
+        pythLazer.updateTrustedSigner(address(2), 0);
+        assert(!pythLazer.isValidSigner(address(2)));
+    }
+
+    function test_verify() public {
+        // Prepare dummy update and signer
+        address trustedSigner = 0xEfEf56cD66896f6799A90A4e4d512C330c094e44;
+        vm.prank(address(1));
+        pythLazer.updateTrustedSigner(trustedSigner, 3000000000000000);
+        bytes
+            memory update = hex"2a22999a577d3cc0202197939d736bc0dcf71b9dde7b9470e4d16fa8e2120c0787a1c0d744d0c39cc372af4d1ecf2d09e84160ca905f3f597d20e2eec144a446a0459ad600001c93c7d3750006240af373971c01010000000201000000000005f5e100";
+
+        uint256 fee = pythLazer.verification_fee();
+
+        address alice = makeAddr("alice");
+        vm.deal(alice, 1 ether);
+        address bob = makeAddr("bob");
+        vm.deal(bob, 1 ether);
+
+        // Alice provides appropriate fee
+        vm.prank(alice);
+        pythLazer.verifyUpdate{value: fee}(update);
+        assertEq(alice.balance, 1 ether - fee);
+
+        // Alice overpays and is refunded
+        vm.prank(alice);
+        pythLazer.verifyUpdate{value: 0.5 ether}(update);
+        assertEq(alice.balance, 1 ether - fee - fee);
+
+        // Bob does not attach a fee
+        vm.prank(bob);
+        vm.expectRevert("Insufficient fee provided");
+        pythLazer.verifyUpdate(update);
+        assertEq(bob.balance, 1 ether);
+    }
 }