Skip to content

Commit 1df0476

Browse files
authored
Move teleport functional to AnlogTokenV2 (#14)
* move teleportation functionality to V2 * upd README * s/V1/V2/g; in V2 contracts
1 parent ebfa4c6 commit 1df0476

File tree

7 files changed

+500
-241
lines changed

7 files changed

+500
-241
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Load environment variables and run the deployment script:
7878

7979
``` sh
8080
source .env.anvil
81-
forge script script/00_Deploy.s.sol --rpc-url $ANVIL_RPC_URL --broadcast -i 1
81+
forge script script/00_Deploy.V1.s.sol --rpc-url $ANVIL_RPC_URL --broadcast -i 1
8282
```
8383

8484
It will ask you to enter the private key. As we're using Anvil's default `account (0)` as the deployer (specified in the [`.env.anvil`](./.env.anvil)), use its (**!well-known!**) key here (can be found in Anvil logs).
@@ -106,7 +106,7 @@ Deploy:
106106

107107
``` sh
108108
source .env.sepolia
109-
forge script script/00_Deploy.s.sol --rpc-url $ANVIL_RPC_URL --broadcast -i 1
109+
forge script script/00_Deploy.V1.s.sol --rpc-url $ANVIL_RPC_URL --broadcast -i 1
110110
```
111111

112112
Make sure to provide the private key of the `DEPLOYER` account upon script's interactive prompt.
@@ -120,13 +120,13 @@ Make sure to provide the private key of the `DEPLOYER` account upon script's int
120120
Once steps described above taken and succeed, deploy to Sepolia with:
121121

122122
``` sh
123-
forge script script/00_Deploy.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast -i 1
123+
forge script script/00_Deploy.V1.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast -i 1
124124
```
125125

126126
> [!NOTE]
127127
> You can also use hardware wallet for signing the transaction. E.g. for using with _Ledger_ run:
128128
> ```sh
129-
> forge script script/00_Deploy.s.sol --rpc-url $ANVIL_RPC_URL --broadcast -l
129+
> forge script script/00_Deploy.V1.s.sol --rpc-url $ANVIL_RPC_URL --broadcast -l
130130
> ```
131131
132132
##### Verify

script/00_Deploy.V1.s.sol

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import {Script, console} from "forge-std/Script.sol";
5+
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
6+
import {AnlogTokenV1} from "../src/AnlogTokenV1.sol";
7+
8+
contract AnlogTokenScript is Script {
9+
AnlogTokenV1 public token;
10+
11+
function setUp() public {}
12+
13+
function run() public {
14+
address deployer = vm.envAddress("DEPLOYER");
15+
16+
address minter = vm.envAddress("MINTER");
17+
address upgrader = vm.envAddress("UPGRADER");
18+
address pauser = vm.envAddress("PAUSER");
19+
address unpauser = vm.envAddress("UNPAUSER");
20+
21+
vm.startBroadcast(deployer);
22+
23+
address proxyAddress = Upgrades.deployUUPSProxy(
24+
"AnlogTokenV1.sol", abi.encodeCall(AnlogTokenV1.initialize, (minter, upgrader, pauser, unpauser))
25+
);
26+
27+
vm.stopBroadcast();
28+
29+
console.log("[dry-run] Deployed AnlogTokenV1.sol implentation to proxy address: ", proxyAddress);
30+
console.log(" ROLES:");
31+
console.log(" MINTER: ", minter);
32+
console.log(" UPGRADER: ", upgrader);
33+
console.log(" PAUSER: ", pauser);
34+
console.log(" UNPAUSER: ", unpauser);
35+
}
36+
}

src/AnlogTokenV1.sol

Lines changed: 4 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ import {ERC20PausableUpgradeable} from
1010
"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
1111
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
1212
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
13-
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
14-
import {IGmpReceiver} from "@analog-gmp/interfaces/IGmpReceiver.sol";
15-
import {IGateway} from "@analog-gmp/interfaces/IGateway.sol";
1613

1714
/// @notice V1: Roles Model implementation of upgradable ERC20 token.
1815
/// This to be used as the initial implementation of UUPS proxy.
@@ -24,99 +21,15 @@ contract AnlogTokenV1 is
2421
ERC20BurnableUpgradeable,
2522
ERC20PausableUpgradeable,
2623
AccessControlUpgradeable,
27-
UUPSUpgradeable,
28-
IGmpReceiver
24+
UUPSUpgradeable
2925
{
3026
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
3127
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
3228
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
3329
bytes32 public constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE");
3430

35-
/**
36-
* @dev Length of `OutboundTeleportCommand` struct encoded in bytes.
37-
* ```
38-
* uint256 messageLength = abi.encode(OutboundTeleportCommand({from: address(0), to: bytes32(0), amount: 0})).length;
39-
* ```
40-
*/
41-
uint256 public constant TELEPORT_COMMAND_ENCODED_LEN = 96;
42-
43-
/**
44-
* @dev Minimun gas limit necessary to execute the `onGmpReceived` method defined in this contract.
45-
*/
46-
uint256 public constant INBOUND_TRANSFER_GAS_LIMIT = 100_000;
47-
48-
/**
49-
* @dev Address of Analog Gateway deployed in the local network, work as "broker" to exchange messages,
50-
* between this contract and the Timechain.
51-
*
52-
* References:
53-
* - Protocol Overview: https://docs.analog.one/documentation/developers/analog-gmp
54-
* - Gateway source-code: https://github.com/Analog-Labs/analog-gmp
55-
*/
56-
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
57-
IGateway public immutable GATEWAY;
58-
59-
/**
60-
* @dev Timechain's Route ID, this is the unique identifier of Timechain's network.
61-
*/
62-
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
63-
uint16 public immutable TIMECHAIN_ROUTE_ID;
64-
65-
/**
66-
* @dev Minimal quantity of tokens allowed per teleport.
67-
*
68-
* IMPORTANT: This value MUST be equal or greater than the timechain's existential deposit.
69-
* see: https://github.com/paritytech/polkadot-sdk/blob/polkadot-v1.17.1/substrate/frame/balances/README.md?plain=1#L24-L29
70-
*/
71-
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
72-
uint256 public immutable MINIMAL_TELEPORT_VALUE;
73-
74-
/**
75-
* @dev Emitted when `amount` tokens are teleported from `source` account in the local network to `recipient` in Timechain.
76-
*/
77-
event OutboundTransfer(bytes32 indexed id, address indexed source, bytes32 indexed recipient, uint256 amount);
78-
79-
/**
80-
* @dev @dev Emitted when `amount` tokens are teleported from `source` in Timechain to `recipient` in the local network.
81-
*/
82-
event InboundTransfer(bytes32 indexed id, bytes32 indexed source, address indexed recipient, uint256 amount);
83-
84-
/**
85-
* @dev One or more preconditions of `onGmpReceived` method failed.
86-
*/
87-
error Unauthorized();
88-
89-
/**
90-
* @dev Command encoded in the `data` field on the `onGmpReceived` method, representing a teleport from Timechain to the local network.
91-
* @param from Timechain's account teleporting the tokens.
92-
* @param to Local account receing the tokens.
93-
* @param amount The amount of tokens teleported.
94-
*/
95-
struct InboundTeleportCommand {
96-
bytes32 from;
97-
address to;
98-
uint256 amount;
99-
}
100-
101-
/**
102-
* @dev Command that that teleports tokens from the local network to the Timechain.
103-
* @param from Account in the local network teleporting the tokens.
104-
* @param to Account in Timechain receing the tokens.
105-
* @param amount The amount of tokens to teleport.
106-
*/
107-
struct OutboundTeleportCommand {
108-
address from;
109-
bytes32 to;
110-
uint256 amount;
111-
}
112-
11331
/// @custom:oz-upgrades-unsafe-allow constructor
114-
constructor(address gateway, uint16 timechainId, uint256 minimalTeleport) {
115-
require(gateway.code.length > 0, "Gateway address is not a contract");
116-
require(IGateway(gateway).networkId() != timechainId, "local network and Timechain must be different networks");
117-
GATEWAY = IGateway(gateway);
118-
TIMECHAIN_ROUTE_ID = timechainId;
119-
MINIMAL_TELEPORT_VALUE = minimalTeleport;
32+
constructor() {
12033
_disableInitializers();
12134
}
12235

@@ -150,142 +63,14 @@ contract AnlogTokenV1 is
15063
_mint(to, amount);
15164
}
15265

153-
/**
154-
* @dev The following functions are overrides required by Solidity.
155-
*/
15666
function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {}
15767

68+
// The following functions are overrides required by Solidity.
69+
15870
function _update(address from, address to, uint256 value)
15971
internal
16072
override(ERC20Upgradeable, ERC20PausableUpgradeable)
16173
{
16274
super._update(from, to, value);
16375
}
164-
165-
/**
166-
* @dev Workaround for EVM compatibility, in some chains like `Astar` where `address(this).balance` can
167-
* be less than `msg.value` if this contract has no previous existential deposit.
168-
* Reference:
169-
* - https://github.com/polkadot-evm/frontier/blob/polkadot-v1.11.0/ts-tests/tests/test-balance.ts#L41
170-
*/
171-
function _msgValue() private view returns (uint256) {
172-
return Math.min(msg.value, address(this).balance);
173-
}
174-
175-
/**
176-
* @dev Teleport a `value` amount of tokens from the caller's account in the local chain to `to`
177-
* account in the Timechain.
178-
*
179-
* Returns the GMP message identifier.
180-
*
181-
* Requirements:
182-
* - `to` cannot be the zero address.
183-
* - `value` must be equal or greater than `MINIMAL_TELEPORT_VALUE`.
184-
* - the caller must have a balance of at least `value`.
185-
*
186-
* Emits a {OutboundTransfer} event.
187-
*/
188-
function teleport(bytes32 to, uint256 value) external payable returns (bytes32 messageID) {
189-
return _teleportFrom(_msgSender(), to, value);
190-
}
191-
192-
/**
193-
* @dev Teleports a `value` amount of tokens from `from` account in the local chain to `to` account
194-
* in the Timechain using the allowance mechanism. `value` is then deducted from the caller's
195-
* allowance.
196-
*
197-
* Returns the GMP message identifier.
198-
*
199-
* NOTE: Does not update the allowance if the current allowance
200-
* is the maximum `uint256`.
201-
*
202-
* Requirements:
203-
* - `from` and `to` cannot be the zero address.
204-
* - `from` must have a balance of at least `value`.
205-
* - `value` must be equal or greater than `MINIMAL_TELEPORT_VALUE`.
206-
* - the caller must have allowance for ``from``'s tokens of at least
207-
* `value`.
208-
*
209-
* Emits a {OutboundTransfer} event.
210-
*/
211-
function teleportFrom(address from, bytes32 to, uint256 value) external payable returns (bytes32 messageID) {
212-
address spender = _msgSender();
213-
_spendAllowance(from, spender, value);
214-
return _teleportFrom(from, to, value);
215-
}
216-
217-
/**
218-
* @dev Teleports a `value` amount of tokens from `from` account in the local chain to `to` account
219-
* in the Timechain.
220-
*
221-
* Requirements:
222-
* - `from` and `to` cannot be the zero address.
223-
* - `from` must have a balance of at least `value`.
224-
* - `value` must be equal or greater than `MINIMAL_TELEPORT_VALUE`.
225-
*
226-
* Emits a {OutboundTransfer} event.
227-
*/
228-
function _teleportFrom(address from, bytes32 to, uint256 value) private returns (bytes32 messageID) {
229-
if (from == address(0)) {
230-
revert ERC20InvalidSender(address(0));
231-
}
232-
if (to == bytes32(bytes20(address(0)))) {
233-
revert ERC20InvalidReceiver(address(0));
234-
}
235-
require(value >= MINIMAL_TELEPORT_VALUE, "value below minimum required");
236-
_burn(from, value);
237-
bytes memory message = abi.encode(OutboundTeleportCommand({from: from, to: to, amount: value}));
238-
messageID = GATEWAY.submitMessage{value: _msgValue()}(
239-
address(0), TIMECHAIN_ROUTE_ID, INBOUND_TRANSFER_GAS_LIMIT, message
240-
);
241-
emit OutboundTransfer(messageID, from, to, value);
242-
}
243-
244-
/**
245-
* @dev Estimate the teleport cost in native tokens, the returned is the amount of ether to send to `teleport` method.
246-
*/
247-
function estimateTeleportCost() public view returns (uint256) {
248-
return GATEWAY.estimateMessageCost(TIMECHAIN_ROUTE_ID, TELEPORT_COMMAND_ENCODED_LEN, INBOUND_TRANSFER_GAS_LIMIT);
249-
}
250-
251-
/**
252-
* @dev Handles the receipt of a single GMP message.
253-
* The contract must verify the msg.sender, it must be the Gateway Contract address.
254-
*
255-
* @param id The global unique identifier of the message.
256-
* @param network The unique identifier of the source chain who send the message
257-
* @param payload The message payload with no specified format
258-
* @return 32 byte result which will be stored together with GMP message
259-
*
260-
* * Requirements:
261-
* - the caller must be the `GATEWAY` contract.
262-
* - `network` must be the `TIMECHAIN_ROUTE_ID`.
263-
* - `source` must be the `REMOTE_ADDRESS`.
264-
* - `payload` must be the struct `InboundTeleportCommand` encoded.
265-
*
266-
* Emits a {InboundTransfer} event.
267-
*/
268-
function onGmpReceived(bytes32 id, uint128 network, bytes32, bytes calldata payload)
269-
external
270-
payable
271-
returns (bytes32)
272-
{
273-
// Check preconditions
274-
require(msg.sender == address(GATEWAY), Unauthorized());
275-
require(network == TIMECHAIN_ROUTE_ID, Unauthorized());
276-
277-
// Decode the command
278-
InboundTeleportCommand memory command = abi.decode(payload, (InboundTeleportCommand));
279-
280-
// Mint the tokens to the recipient account
281-
if (command.to != address(0) && command.amount > 0) {
282-
_mint(command.to, command.amount);
283-
}
284-
emit InboundTransfer(id, command.from, command.to, command.amount);
285-
286-
// Returns the current total supply as result, the result is included in the `GmpExecuted` event
287-
// emitted by the gateway. It allows the Timechain to verify if the amount of tokens locked matches
288-
// the total supply of this contract.
289-
return bytes32(totalSupply());
290-
}
29176
}

0 commit comments

Comments
 (0)