Skip to content

Commit

Permalink
Upgradeable ERC-20 Tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
kacperzuk-neti committed Aug 18, 2023
1 parent 8de18f0 commit d00a2b1
Show file tree
Hide file tree
Showing 15 changed files with 337 additions and 170 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test-solidity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
working-directory: ./eth-bridge/contracts/
run: |
forge --version
forge build --sizes
forge build --build-info --sizes
id: build_solidity

- name: Run Forge tests
Expand All @@ -73,4 +73,4 @@ jobs:
with:
target: ./eth-bridge/contracts/
slither-config: ./eth-bridge/contracts/slither.config.json
ignore-compilation: true
ignore-compile: true
42 changes: 26 additions & 16 deletions eth-bridge/contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,34 +163,44 @@ Get contract addresses (replace path):
> jq '.transactions | map(select(.transactionType == "CREATE")) | map(.contractName, .contractAddress)' broadcast/Deploy.s.sol/31337/run-latest.json
[
"Bridge",
"0x5FbDB2315678afecb367f032d93F642f64180aa3",
"0x700b6A60ce7EaaEA56F065753d8dcB9653dbAD35",
"WrappedToken",
"0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"0xA15BB66138824a1c7167f5E85b957d04Dd34E468",
"ERC1967Proxy",
"0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
"WrappedToken",
"0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
"0xb19b36b1456E65E3A6D514D3F715f204BD59f431",
"ERC1967Proxy",
"0x8ce361602B935680E8DeC218b820ff5056BeB7af",
"ERC1967Proxy",
"0x5FC8d32690cc91D4c39d9d3abcBD16989F875707"
"0x0C8E79F3534B00D9a3D4a856B665Bf4eBC22f2ba",
"ERC1967Proxy",
"0xeD1DB453C3156Ff3155a97AD217b3087D5Dc5f6E"
]
```
Note that `Bridge` is underlying implementation and you should interact with Proxies.
Note that `Bridge` and `WrappedToken` are underlying implementation and you can't use it directly - you must interact with Proxies.

Take the WrappedToken addresses and check which is which:
Take addresses of proxies and check which are tokens:
```
> cast call 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 'name()' | cast --to-ascii
Liberland Dollars
> cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 'name()' | cast --to-ascii
> cast call 0xeD1DB453C3156Ff3155a97AD217b3087D5Dc5f6E 'name()' | cast --to-ascii
Error:
(code: 3, message: execution reverted, data: Some(String("0x")))
> cast call 0x0C8E79F3534B00D9a3D4a856B665Bf4eBC22f2ba 'name()' | cast --to-ascii
Liberland Merits
> cast call 0x8ce361602B935680E8DeC218b820ff5056BeB7af 'name()' | cast --to-ascii
Error:
(code: 3, message: execution reverted, data: Some(String("0x")))
> cast call 0xb19b36b1456E65E3A6D514D3F715f204BD59f431 'name()' | cast --to-ascii
Liberland Dollars
```

Take the Proxy addresses and check which tokens they use:
Take the proxy addresses that failed - these are bridge proxies - and check which tokens they use:
```
> cast call 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707 'token()'
0x000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f0512 # LLD Token address
> cast call 0x8ce361602B935680E8DeC218b820ff5056BeB7af 'token()'
0x000000000000000000000000b19b36b1456e65e3a6d514d3f715f204bd59f431 # LLD Token address
> cast call 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 'token()'
0x000000000000000000000000Dc64a140Aa3E981100a9becA4E685f962f0cF6C9 # LLM Token address
> cast call 0xeD1DB453C3156Ff3155a97AD217b3087D5Dc5f6E 'token()'
0x0000000000000000000000000c8e79f3534b00d9a3d4a856b665bf4ebc22f2ba # LLM Token address
```

License: MIT
2 changes: 1 addition & 1 deletion eth-bridge/contracts/lib/openzeppelin-contracts
26 changes: 22 additions & 4 deletions eth-bridge/contracts/script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@ contract Deploy is Script {
uint32 votesRequired = 5;

Bridge bridgeImpl = new Bridge();
WrappedToken tokenImpl = new WrappedToken();

WrappedToken lld = new WrappedToken("Liberland Dollars", "LLD");
WrappedToken lld = WrappedToken(
address(
new ERC1967Proxy(
address(tokenImpl),
abi.encodeCall(WrappedToken.initialize, ("Liberland Dollars", "LLD"))
)
)
);
ERC1967Proxy lldBridge = new ERC1967Proxy(
address(bridgeImpl),
abi.encodeCall(
Expand All @@ -37,9 +45,17 @@ contract Deploy is Script {
)
)
);
lld.transferOwnership(address(lldBridge));
lld.grantRole(lld.MINTER_ROLE(), address(lldBridge));
lld.grantRole(lld.PAUSER_ROLE(), address(lldBridge));

WrappedToken llm = new WrappedToken("Liberland Merits", "LLM");
WrappedToken llm = WrappedToken(
address(
new ERC1967Proxy(
address(tokenImpl),
abi.encodeCall(WrappedToken.initialize, ("Liberland Merits", "LLM"))
)
)
);
ERC1967Proxy llmBridge = new ERC1967Proxy(
address(bridgeImpl),
abi.encodeCall(
Expand All @@ -56,7 +72,9 @@ contract Deploy is Script {
)
)
);
llm.transferOwnership(address(llmBridge));
llm.grantRole(llm.MINTER_ROLE(), address(llmBridge));
llm.grantRole(llm.PAUSER_ROLE(), address(llmBridge));

vm.stopBroadcast();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ pragma solidity ^0.8.18;
import "forge-std/Script.sol";
import "../src/Bridge.sol";

contract Upgrade is Script {
contract UpgradeBridges is Script {
function run() external {
vm.startBroadcast();
Bridge lldProxy = Bridge(vm.envAddress("LLDProxy"));
Bridge llmProxy = Bridge(vm.envAddress("LLMProxy"));
Bridge lldProxy = Bridge(vm.envAddress("LLDBridgeProxy"));
Bridge llmProxy = Bridge(vm.envAddress("LLMBridgeProxy"));

Bridge newBridgeImpl = new Bridge();

Expand Down
20 changes: 20 additions & 0 deletions eth-bridge/contracts/script/UpgradeTokens.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.18;

import "forge-std/Script.sol";
import "../src/Bridge.sol";

contract UpgradeTokens is Script {
function run() external {
vm.startBroadcast();
Bridge lldBridge = Bridge(vm.envAddress("LLDBridgeProxy"));
Bridge llmBridge = Bridge(vm.envAddress("LLMBridgeProxy"));
WrappedToken lldProxy = lldBridge.token();
WrappedToken llmProxy = llmBridge.token();

WrappedToken newTokenImpl = new WrappedToken();

lldProxy.upgradeTo(address(newTokenImpl));
llmProxy.upgradeTo(address(newTokenImpl));
}
}
71 changes: 2 additions & 69 deletions eth-bridge/contracts/src/Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,8 @@ import {UUPSUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/prox
import {AccessControlUpgradeable} from
"openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol";
import {WrappedToken} from "./WrappedToken.sol";

/// Struct for representing Substrate -> ETH transfer
struct IncomingReceiptStruct {
uint64 substrateBlockNumber;
address ethRecipient;
uint256 amount;
uint256 approvedOn;
uint256 processedOn;
}

/// Rate limit parameters. Limit how fast can tokens be minted.This is
/// implemented as The Leaky Bucket as a Meter algorithm -
/// https://en.wikipedia.org/wiki/Leaky_bucket.
/// `counterLimit` is the max counter (a.k.a. max burst, max single withdrawal)
/// `decayRate` is after reaching max, how much can be minted per block.
struct RateLimitParameters {
uint256 counterLimit;
uint256 decayRate;
}

/// Struct for keeping track of counters required to enforce rate limits
struct RateLimitCounter {
uint256 counter;
uint256 lastUpdate;
}
import {IncomingReceiptStruct, RateLimitParameters, RateLimitCounter} from "./BridgeTypes.sol";
import {BridgeEvents} from "./BridgeEvents.sol";

/// Bridge is deactivated - see `bridgeActive()` and `setActive(bool)`
error BridgeInactive();
Expand Down Expand Up @@ -59,37 +36,6 @@ error AlreadyVoted();
/// Transferred amount is less than configured minimum
error TooSmallAmount();

/// @title Interface with events emitted by the bridge
interface BridgeEvents {
/// Emitted after burn, notifies relays that transfer is happening
/// @param from Account that burned its tokens
/// @param substrateRecipient Who should get tokens on substrate
/// @param amount Amount of token burned
event OutgoingReceipt(address indexed from, bytes32 indexed substrateRecipient, uint256 amount);

/// Bridge get activated or deactivated
/// @param newBridgeState New bridge state
event StateChanged(bool newBridgeState);

/// An IncomingReceipt was approved for transfer. `mint(bytes32)`
/// can now be called for this receipt after `mintDelay` blocks pass.
/// @param receiptId Receipt that got approved
event Approved(bytes32 indexed receiptId);

/// Vote was cast to approve IncomingReceipt
/// @param receiptId subject Receipt
/// @param relay Relay that cast the vote
event Vote(bytes32 indexed receiptId, address indexed relay, uint64 substrateBlockNumber);

/// IncomingReceipt was completely processed - tokens were minted
/// @param receiptId subject Receipt
event Processed(bytes32 indexed receiptId);

/// Bridge was emergency stopped by watcher - misbehavior by relay was
/// detected
event EmergencyStop();
}

/// @title Substrate <-> ETH bridge for Liberland
/// @dev Must be used with ERC1967Proxy
contract Bridge is Initializable, AccessControlUpgradeable, UUPSUpgradeable, BridgeEvents {
Expand Down Expand Up @@ -426,19 +372,6 @@ contract Bridge is Initializable, AccessControlUpgradeable, UUPSUpgradeable, Bri
minTransfer = minTransfer_;
}

/// Transfer ownership of underlying token contract.
/// Will stop the bridge and set the token address to 0, effectively
/// bricking the bridge.
/// @param newOwner new owner
/// @dev Only addresses with SUPER_ADMIN_ROLE can call this
/// @dev Interacts with `token` contract
function transferTokenOwnership(address newOwner) public onlyRole(SUPER_ADMIN_ROLE) {
WrappedToken token_ = token;
token = WrappedToken(address(0));
_setActive(false);
token_.transferOwnership(newOwner);
}

/// Check if given receiptId was already voted on by given voter
/// @param receiptId ReceiptId to check
/// @param voter Voter to check
Expand Down
33 changes: 33 additions & 0 deletions eth-bridge/contracts/src/BridgeEvents.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

/// @title Interface with events emitted by the bridge
interface BridgeEvents {
/// Emitted after burn, notifies relays that transfer is happening
/// @param from Account that burned its tokens
/// @param substrateRecipient Who should get tokens on substrate
/// @param amount Amount of token burned
event OutgoingReceipt(address indexed from, bytes32 indexed substrateRecipient, uint256 amount);

/// Bridge get activated or deactivated
/// @param newBridgeState New bridge state
event StateChanged(bool newBridgeState);

/// An IncomingReceipt was approved for transfer. `mint(bytes32)`
/// can now be called for this receipt after `mintDelay` blocks pass.
/// @param receiptId Receipt that got approved
event Approved(bytes32 indexed receiptId);

/// Vote was cast to approve IncomingReceipt
/// @param receiptId subject Receipt
/// @param relay Relay that cast the vote
event Vote(bytes32 indexed receiptId, address indexed relay, uint64 substrateBlockNumber);

/// IncomingReceipt was completely processed - tokens were minted
/// @param receiptId subject Receipt
event Processed(bytes32 indexed receiptId);

/// Bridge was emergency stopped by watcher - misbehavior by relay was
/// detected
event EmergencyStop();
}
27 changes: 27 additions & 0 deletions eth-bridge/contracts/src/BridgeTypes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

/// Struct for representing Substrate -> ETH transfer
struct IncomingReceiptStruct {
uint64 substrateBlockNumber;
address ethRecipient;
uint256 amount;
uint256 approvedOn;
uint256 processedOn;
}

/// Rate limit parameters. Limit how fast can tokens be minted.This is
/// implemented as The Leaky Bucket as a Meter algorithm -
/// https://en.wikipedia.org/wiki/Leaky_bucket.
/// `counterLimit` is the max counter (a.k.a. max burst, max single withdrawal)
/// `decayRate` is after reaching max, how much can be minted per block.
struct RateLimitParameters {
uint256 counterLimit;
uint256 decayRate;
}

/// Struct for keeping track of counters required to enforce rate limits
struct RateLimitCounter {
uint256 counter;
uint256 lastUpdate;
}
Loading

0 comments on commit d00a2b1

Please sign in to comment.