diff --git a/bridge-web/src/App.tsx b/bridge-web/src/App.tsx
index 210393f..02a7485 100644
--- a/bridge-web/src/App.tsx
+++ b/bridge-web/src/App.tsx
@@ -86,14 +86,14 @@ function App() {
const { data: fees } = useContractRead({
abi: tokenManagerAbi,
functionName: "getFees",
- address: fromChainConfig.tokenManagerAddress,
- enabled: !!fromChainConfig.tokenManagerAddress,
+ address: token.tokenManagerAddress,
+ enabled: !!token.tokenManagerAddress,
});
const { data: paused } = useContractRead({
abi: tokenManagerAbi,
functionName: "paused",
- address: fromChainConfig.tokenManagerAddress,
- enabled: !!fromChainConfig.tokenManagerAddress,
+ address: token.tokenManagerAddress,
+ enabled: !!token.tokenManagerAddress,
});
const { data: balance } = useContractRead({
abi: erc20ABI,
@@ -108,9 +108,9 @@ function App() {
abi: erc20ABI,
functionName: "allowance",
address: token.address,
- args: [account!, fromChainConfig.tokenManagerAddress],
+ args: [account!, token.tokenManagerAddress],
enabled:
- !!account && !!token.address && !!fromChainConfig.tokenManagerAddress,
+ !!account && !!token.address && !!token.tokenManagerAddress,
watch: true,
});
@@ -124,7 +124,7 @@ function App() {
: false;
const { config: transferConfig } = usePrepareContractWrite({
- address: fromChainConfig.tokenManagerAddress,
+ address: token.tokenManagerAddress,
abi: tokenManagerAbi,
args: recipientEth && [
token.address,
@@ -154,7 +154,7 @@ function App() {
} = useContractWrite({
mode: "prepared",
request: {
- address: fromChainConfig.tokenManagerAddress,
+ address: token.tokenManagerAddress,
chain: fromChainConfig.wagmiChain,
account: account!,
abi: tokenManagerAbi,
@@ -176,7 +176,7 @@ function App() {
address: token.address,
abi: erc20ABI,
args: [
- fromChainConfig.tokenManagerAddress,
+ token.tokenManagerAddress,
amount ? parseUnits(amount, decimals ?? 0) : 0n,
],
functionName: "approve",
@@ -264,7 +264,7 @@ function App() {
toast.update(id, {
render: (
- Bridge txn complete, funds arrived to {toChainConfig.name}{" "}
+ Bridge txn complete, funds arrived on {toChainConfig.name}{" "}
chain. View on{" "}
> =
zq: {
chain: "zq",
name: "Zilliqa",
- tokenManagerAddress: "0x6D61eFb60C17979816E4cE12CD5D29054E755948",
chainGatewayAddress: "0xbA44BC29371E19117DA666B729A1c6e1b35DDb40",
- tokenManagerType: TokenManagerType.LockAndRelease,
wagmiChain: zilliqa,
tokens: [
{
@@ -38,6 +37,8 @@ export const chainConfigs: Partial> =
blockExplorer:
"https://otterscan.zilliqa.com/address/0x63B991C17010C21250a0eA58C6697F696a48cdf3",
logo: hrse_token,
+ tokenManagerAddress: "0x6D61eFb60C17979816E4cE12CD5D29054E755948",
+ tokenManagerType: TokenManagerType.LockAndRelease,
},
{
name: "FPS",
@@ -45,6 +46,8 @@ export const chainConfigs: Partial> =
blockExplorer:
"https://otterscan.zilliqa.com/address/0x241c677D9969419800402521ae87C411897A029f",
logo: fps_token,
+ tokenManagerAddress: "0x6D61eFb60C17979816E4cE12CD5D29054E755948",
+ tokenManagerType: TokenManagerType.LockAndRelease,
},
],
chainId: 32769,
@@ -59,9 +62,7 @@ export const chainConfigs: Partial> =
bsc,
`${import.meta.env.VITE_BSC_MAINNET_API}/${import.meta.env.VITE_BSC_MAINNET_KEY}`,
),
- tokenManagerAddress: "0xF391A1Ee7b3ccad9a9451D2B7460Ac646F899f23",
chainGatewayAddress: "0x3967f1a272Ed007e6B6471b942d655C802b42009",
- tokenManagerType: TokenManagerType.MintAndBurn,
tokens: [
{
name: "HRSE",
@@ -69,6 +70,8 @@ export const chainConfigs: Partial> =
blockExplorer:
"https://bscscan.com/address/0x3BE0E5EDC58bd55AAa381Fa642688ADC289c05a3",
logo: hrse_token,
+ tokenManagerAddress: "0xF391A1Ee7b3ccad9a9451D2B7460Ac646F899f23",
+ tokenManagerType: TokenManagerType.MintAndBurn,
},
{
name: "FPS",
@@ -76,6 +79,8 @@ export const chainConfigs: Partial> =
blockExplorer:
"https://bscscan.com/address/0x351dA1E7500aBA1d168b9435DCE73415718d212F",
logo: fps_token,
+ tokenManagerAddress: "0xF391A1Ee7b3ccad9a9451D2B7460Ac646F899f23",
+ tokenManagerType: TokenManagerType.MintAndBurn,
},
],
chainId: 56,
@@ -88,8 +93,6 @@ export const chainConfigs: Partial> =
"zq-testnet": {
chain: "zq-testnet",
name: "Zilliqa Testnet",
- tokenManagerAddress: "0x1509988c41f02014aA59d455c6a0D67b5b50f129",
- tokenManagerType: TokenManagerType.LockAndRelease,
chainGatewayAddress: "0x7370e69565BB2313C4dA12F9062C282513919230",
wagmiChain: zilliqaTestnet,
tokens: [
@@ -99,6 +102,8 @@ export const chainConfigs: Partial> =
blockExplorer:
"https://otterscan.testnet.zilliqa.com/address/0x8618d39a8276D931603c6Bc7306af6A53aD2F1F3",
logo: fps_token,
+ tokenManagerAddress: "0xF391A1Ee7b3ccad9a9451D2B7460Ac646F899f23",
+ tokenManagerType: TokenManagerType.LockAndRelease,
},
{
name: "TSLM Z",
@@ -106,6 +111,8 @@ export const chainConfigs: Partial> =
blockExplorer:
"https://otterscan.testnet.zilliqa.com/address/0xE90Dd366D627aCc5feBEC126211191901A69f8a0",
logo: test_hrse_token,
+ tokenManagerAddress: "0xF391A1Ee7b3ccad9a9451D2B7460Ac646F899f23",
+ tokenManagerType: TokenManagerType.LockAndRelease,
},
],
chainId: 33101,
@@ -120,8 +127,6 @@ export const chainConfigs: Partial> =
bscTestnet,
`${import.meta.env.VITE_BSC_TESTNET_API}/${import.meta.env.VITE_BSC_TESTNET_KEY}`,
),
- tokenManagerAddress: "0xA6D73210AF20a59832F264fbD991D2abf28401d0",
- tokenManagerType: TokenManagerType.MintAndBurn,
chainGatewayAddress: "0xa9A14C90e53EdCD89dFd201A3bF94D867f8098fE",
tokens: [
{
@@ -130,6 +135,8 @@ export const chainConfigs: Partial> =
blockExplorer:
"https://testnet.bscscan.com/address/0x5190e8b4Bbe8C3a732BAdB600b57fD42ACbB9F4B",
logo: fps_token,
+ tokenManagerAddress: "0xA6D73210AF20a59832F264fbD991D2abf28401d0",
+ tokenManagerType: TokenManagerType.MintAndBurn,
},
{
name: "TSLM B",
@@ -137,6 +144,17 @@ export const chainConfigs: Partial> =
blockExplorer:
"https://testnet.bscscan.com/address/0x7Cc585de659E8938Aa7d5709BeaF34bD108bdC03",
logo: test_hrse_token,
+ tokenManagerAddress: "0xA6D73210AF20a59832F264fbD991D2abf28401d0",
+ tokenManagerType: TokenManagerType.MintAndBurn,
+ },
+ {
+ name: "zbBSCERC20",
+ address: "0x59A23d0957B63BC6c5682F211eE731513EECBB98",
+ blockExplorer:
+ "https://testnet.bscscan.com/address/0x59A23d0957B63BC6c5682F211eE731513EECBB98",
+ logo: fps_token,
+ tokenManagerAddress: "0x36b8A9cd6Bf9bfA5984093005cf81CAfB1Bf06F7",
+ tokenManagerType: TokenManagerType.ZilBridge
},
],
chainId: 97,
@@ -150,9 +168,7 @@ export type ChainConfig = {
name: string;
chain: Chains;
wagmiChain: Chain;
- tokenManagerAddress: `0x${string}`;
chainGatewayAddress: `0x${string}`;
- tokenManagerType: TokenManagerType;
tokens: TokenConfig[];
chainId: number;
isZilliqa: boolean;
@@ -165,4 +181,6 @@ export type TokenConfig = {
address: `0x${string}`;
blockExplorer: string;
logo?: string;
+ tokenManagerAddress: `0x${string}`;
+ tokenManagerType: TokenManagerType;
};
diff --git a/docs/zilbridge.md b/docs/zilbridge.md
index a431802..f96378e 100644
--- a/docs/zilbridge.md
+++ b/docs/zilbridge.md
@@ -122,6 +122,12 @@ export TOKEN_MANAGER=(whatever address the deployNativeTokenManagerV3 script abo
npx hardhat run scripts/deploy.ts
```
+And now we ship an ERC20 proxy for our ZRC2 and switcheo tokens.
+
+```
+
+```
+
And now we can set up routing for the tokens we just deployed. This is "just" calls, so
```
diff --git a/scilla-contracts/scripts/deploy.ts b/scilla-contracts/scripts/deploy.ts
index 5a25c80..1288d47 100644
--- a/scilla-contracts/scripts/deploy.ts
+++ b/scilla-contracts/scripts/deploy.ts
@@ -15,14 +15,14 @@ async function main() {
}
let tokenContract = await hre.deployScillaContract("SwitcheoTokenZRC2", "Bridged-XTST", "XTST",
account.address, 18, 0, zqTestnetTokenManager);
- console.log(`TokenContract: ${tokenContract.address}`);
+ console.log(`zq_bridged_erc20 = ${tokenContract.address}`);
let nativeBridgedContract = await hre.deployScillaContract("SwitcheoTokenZRC2", "Bridged-BNB", "eBNB",
account.address, 18, 0, zqTestnetTokenManager);
- console.log(`NativeBridgedContract: ${nativeBridgedContract.address}`);
+ console.log(`zq_bridged_bnb = ${nativeBridgedContract.address}`);
let local = await hre.deployScillaContract("FungibleToken", account.address, "zq_native Test", "ZTST", 18, 1_000_000);
- console.log(`Locally generated fungible token: ${local.address}`);
+ console.log(`zq_zrc2 = ${local.address}`);
}
// We recommend this pattern to be able to use async/await everywhere
diff --git a/smart-contracts/script/testnet_config.s.sol b/smart-contracts/script/testnet_config.s.sol
index b77e10e..0c1d8fc 100644
--- a/smart-contracts/script/testnet_config.s.sol
+++ b/smart-contracts/script/testnet_config.s.sol
@@ -20,8 +20,14 @@ abstract contract TestnetConfig {
address public constant zq_chainGateway = 0x7370e69565BB2313C4dA12F9062C282513919230;
address public constant zq_lockAndReleaseOrNativeTokenManager = 0xBe90AB2cd65E207F097bEF733F8D239A59698b8A;
+ // Scilla contracts.
address public constant zq_bridged_erc20 = address(0x00839901f1e39De75301667C6bBbF7fB556Ea2510E);
address public constant zq_bridged_bnb = address(0x0006852e68A3c24917cfA4C2dbDaE4B308C69aDA5e);
address public constant zq_zrc2 = address(0x00155F0f76b660290F2F00Bb5674b80eDC208bF2e6);
+ // ERC20 fascias for Scilla contracts
+ address public constant zq_bridged_erc20_evm = address(0x0);
+ address public constant zq_bridged_bnb_evm = address(0x0);
+ address public constant zq_zrc2_evm = address(0x0);
+
}
diff --git a/smart-contracts/script/zq-testnet/deployZRC2ERC20.s.sol b/smart-contracts/script/zq-testnet/deployZRC2ERC20.s.sol
new file mode 100644
index 0000000..51ba774
--- /dev/null
+++ b/smart-contracts/script/zq-testnet/deployZRC2ERC20.s.sol
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+pragma solidity ^0.8.20;
+
+import {Script} from "forge-std/Script.sol";
+import {LockAndReleaseTokenManagerUpgradeable} from "contracts/periphery/LockAndReleaseTokenManagerUpgradeable.sol";
+import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
+import { ZRC2ProxyForZRC2 } from "test/zilbridge/zrc2erc20/ZRC2ProxyForZRC2.sol";
+import { TestnetConfig } from "script/testnet_config.s.sol";
+import "forge-std/console.sol";
+
+/*** @title Deploy an ERC20 proxy for our ZRC2, so we can set routing with it.
+ */
+contract Deployment is Script, TestnetConfig {
+ function run() external {
+ uint256 validatorPrivateKey = vm.envUint("PRIVATE_KEY_TESTNET");
+ address validator = vm.addr(validatorPrivateKey);
+ console.log("Owner is %s", validator);
+ vm.startBroadcast(validatorPrivateKey);
+ {
+ ZRC2ProxyForZRC2 proxy = new ZRC2ProxyForZRC2(zq_bridged_erc20);
+ console.log("zq_bridged_erc20_evm = %s", address(proxy));
+ }
+ {
+ ZRC2ProxyForZRC2 proxy = new ZRC2ProxyForZRC2(zq_bridged_bnb);
+ console.log("zq_bridged_bnb_evm = %s", address(proxy));
+ }
+ {
+ ZRC2ProxyForZRC2 proxy = new ZRC2ProxyForZRC2(zq_zrc2);
+ console.log("zq_zrc2_evm = %s", address(proxy));
+ }
+ }
+}
diff --git a/smart-contracts/script/zq-testnet/registerZilBridgeTokens.s.sol b/smart-contracts/script/zq-testnet/registerZilBridgeTokens.s.sol
deleted file mode 100644
index eb95a8b..0000000
--- a/smart-contracts/script/zq-testnet/registerZilBridgeTokens.s.sol
+++ /dev/null
@@ -1,12 +0,0 @@
-// SPDX-License-Identifier: MIT OR Apache-2.0
-pragma solidity ^0.8.20;
-
-import {Script} from "forge-std/Script.sol";
-import {LockAndReleaseTokenManagerUpgradeable} from "contracts/periphery/LockAndReleaseTokenManagerUpgradeable.sol";
-import {ITokenManagerStructs} from "contracts/periphery/TokenManagerUpgradeable.sol";
-import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
-import "forge-std/console.sol";
-
-/*** @notice this script wires up the token manager for the zilbridge tests on zq_testnet. The corresponding code on bsc is
- * in
- */
diff --git a/smart-contracts/test/zilbridge/zrc2erc20/ScillaConnector.sol b/smart-contracts/test/zilbridge/zrc2erc20/ScillaConnector.sol
new file mode 100644
index 0000000..63f86ca
--- /dev/null
+++ b/smart-contracts/test/zilbridge/zrc2erc20/ScillaConnector.sol
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity ^0.8.20;
+
+library ScillaConnector {
+ uint private constant CALL_SCILLA_WITH_THE_SAME_SENDER = 1;
+ uint private constant SCILLA_CALL_PRECOMPILE_ADDRESS = 0x5a494c53;
+ uint private constant SCILLA_STATE_READ_PRECOMPILE_ADDRESS = 0x5a494c92;
+
+ /**
+ * @dev Calls a ZRC2 contract function with two arguments
+ * @param target The address of the ZRC2 contract
+ * @param tran_name The name of the function to call
+ * @param arg1 The first argument to the function
+ * @param arg2 The second argument to the function
+ */
+ function call(
+ address target,
+ string memory tran_name,
+ address arg1,
+ uint128 arg2
+ ) internal {
+ bytes memory encodedArgs = abi.encode(
+ target,
+ tran_name,
+ CALL_SCILLA_WITH_THE_SAME_SENDER,
+ arg1,
+ arg2
+ );
+ uint256 argsLength = encodedArgs.length;
+
+ assembly {
+ let alwaysSuccessForThisPrecompile := call(
+ 21000,
+ SCILLA_CALL_PRECOMPILE_ADDRESS,
+ 0,
+ add(encodedArgs, 0x20),
+ argsLength,
+ 0x20,
+ 0
+ )
+ }
+ }
+
+ /**
+ * @dev Calls a ZRC2 contract function with three arguments
+ * @param target The address of the ZRC2 contract
+ * @param tran_name The name of the function to call on the ZRC2 contract
+ * @param arg1 The first argument to the function
+ * @param arg2 The second argument to the function
+ * @param arg3 The third argument to the function
+ */
+ function call(
+ address target,
+ string memory tran_name,
+ address arg1,
+ address arg2,
+ uint128 arg3
+ ) internal {
+ bytes memory encodedArgs = abi.encode(
+ target,
+ tran_name,
+ CALL_SCILLA_WITH_THE_SAME_SENDER,
+ arg1,
+ arg2,
+ arg3
+ );
+ uint256 argsLength = encodedArgs.length;
+
+ assembly {
+ let alwaysSuccessForThisPrecompile := call(
+ 21000,
+ SCILLA_CALL_PRECOMPILE_ADDRESS,
+ 0,
+ add(encodedArgs, 0x20),
+ argsLength,
+ 0x20,
+ 0
+ )
+ }
+ }
+
+ /**
+ * @dev Reads a 128 bit integer from a ZRC2 contract
+ * @param target The address of the ZRC2 contract
+ * @param variable_name The name of the variable to read from the ZRC2 contract
+ * @return The value of the variable
+ */
+ function readUint128(
+ address target,
+ string memory variable_name
+ ) internal view returns (uint128) {
+ bytes memory encodedArgs = abi.encode(target, variable_name);
+ uint256 argsLength = encodedArgs.length;
+ bytes memory output = new bytes(36);
+
+ assembly {
+ let alwaysSuccessForThisPrecompile := staticcall(
+ 21000,
+ SCILLA_STATE_READ_PRECOMPILE_ADDRESS,
+ add(encodedArgs, 0x20),
+ argsLength,
+ add(output, 0x20),
+ 32
+ )
+ }
+
+ return abi.decode(output, (uint128));
+ }
+
+ /**
+ * @dev Reads a 32 bit integer from a ZRC2 contract
+ * @param target The address of the ZRC2 contract
+ * @param variable_name The name of the variable to read from the ZRC2 contract
+ * @return The value of the variable
+ */
+ function readUint32(
+ address target,
+ string memory variable_name
+ ) internal view returns (uint32) {
+ bytes memory encodedArgs = abi.encode(target, variable_name);
+ uint256 argsLength = encodedArgs.length;
+ bytes memory output = new bytes(36);
+
+ assembly {
+ let alwaysSuccessForThisPrecompile := staticcall(
+ 21000,
+ SCILLA_STATE_READ_PRECOMPILE_ADDRESS,
+ add(encodedArgs, 0x20),
+ argsLength,
+ add(output, 0x20),
+ 32
+ )
+ }
+
+ return abi.decode(output, (uint32));
+ }
+
+ /**
+ * @dev Reads a string from a ZRC2 contract
+ * @param target The address of the ZRC2 contract
+ * @param variable_name The name of the variable to read from the ZRC2 contract
+ * @return retVal The value of the variable
+ */
+ function readString(
+ address target,
+ string memory variable_name
+ ) internal view returns (string memory retVal) {
+ bytes memory encodedArgs = abi.encode(target, variable_name);
+ uint256 argsLength = encodedArgs.length;
+ bool success;
+ bytes memory output = new bytes(128);
+ uint256 output_len = output.length - 4;
+ assembly {
+ success := staticcall(
+ 21000,
+ SCILLA_STATE_READ_PRECOMPILE_ADDRESS,
+ add(encodedArgs, 0x20),
+ argsLength,
+ add(output, 0x20),
+ output_len
+ )
+ }
+ require(success);
+
+ (retVal) = abi.decode(output, (string));
+ return retVal;
+ }
+
+ /**
+ * @dev Reads a 128 bit integer from a map in a ZRC2 contract
+ * @param variable_name The name of the map in the ZRC2 contract
+ * @param addressMapKey The key to the map
+ * @return The value associated with the key in the map
+ */
+ function readMapUint128(
+ address target,
+ string memory variable_name,
+ address addressMapKey
+ ) internal view returns (uint128) {
+ bytes memory encodedArgs = abi.encode(
+ target,
+ variable_name,
+ addressMapKey
+ );
+ uint256 argsLength = encodedArgs.length;
+ bytes memory output = new bytes(36);
+
+ assembly {
+ let alwaysSuccessForThisPrecompile := staticcall(
+ 21000,
+ SCILLA_STATE_READ_PRECOMPILE_ADDRESS,
+ add(encodedArgs, 0x20),
+ argsLength,
+ add(output, 0x20),
+ 32
+ )
+ }
+
+ return abi.decode(output, (uint128));
+ }
+
+ /**
+ * @dev Reads a 128 bit integer from a nested map in a ZRC2 contract
+ * @param target The address of the ZRC2 contract
+ * @param variable_name The name of the map in the ZRC2 contract
+ * @param firstMapKey The first key to the map
+ * @param secondMapKey The second key to the map
+ * @return The value associated with the keys in the map
+ */
+ function readNestedMapUint128(
+ address target,
+ string memory variable_name,
+ address firstMapKey,
+ address secondMapKey
+ ) internal view returns (uint128) {
+ bytes memory encodedArgs = abi.encode(
+ target,
+ variable_name,
+ firstMapKey,
+ secondMapKey
+ );
+ uint256 argsLength = encodedArgs.length;
+ bytes memory output = new bytes(36);
+
+ assembly {
+ let alwaysSuccessForThisPrecompile := staticcall(
+ 21000,
+ SCILLA_STATE_READ_PRECOMPILE_ADDRESS,
+ add(encodedArgs, 0x20),
+ argsLength,
+ add(output, 0x20),
+ 32
+ )
+ }
+
+ return abi.decode(output, (uint128));
+ }
+}
diff --git a/smart-contracts/test/zilbridge/zrc2erc20/ZRC2ProxyForZRC2.sol b/smart-contracts/test/zilbridge/zrc2erc20/ZRC2ProxyForZRC2.sol
new file mode 100644
index 0000000..4332aeb
--- /dev/null
+++ b/smart-contracts/test/zilbridge/zrc2erc20/ZRC2ProxyForZRC2.sol
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity ^0.8.20;
+
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
+import {ScillaConnector} from "./ScillaConnector.sol";
+
+contract ZRC2ProxyForZRC2 is IERC20 {
+ using ScillaConnector for address;
+ using SafeCast for uint256;
+
+ address public zrc2_proxy;
+
+ // Additional variables useful for wallets
+ uint8 public decimals;
+ string public symbol;
+ string public name;
+
+ /**
+ * @notice Constructs a new ZRC2Proxy contract
+ * @param zrc2_address The address of the underlying ZRC2 contract
+ */
+ constructor(address zrc2_address) {
+ zrc2_proxy = zrc2_address;
+
+ symbol = zrc2_proxy.readString("symbol");
+ decimals = uint256(zrc2_proxy.readUint32("decimals")).toUint8();
+ name = zrc2_proxy.readString("name");
+ }
+
+ /**
+ * @notice Get the total supply of tokens
+ * @return The total supply of tokens
+ */
+ function totalSupply() external view returns (uint256) {
+ return zrc2_proxy.readUint128("total_supply");
+ }
+
+ /**
+ * @notice Get the token balance for a specific account
+ * @param tokenOwner The address of the account
+ * @return The balance of the account
+ */
+ function balanceOf(address tokenOwner) external view returns (uint256) {
+ return zrc2_proxy.readMapUint128("balances", tokenOwner);
+ }
+
+ /**
+ * @notice Transfer tokens to a specified address
+ * @param to The address to transfer to
+ * @param tokens The amount of tokens to transfer
+ * @return true if transfer was successful
+ */
+ function transfer(address to, uint256 tokens) external returns (bool) {
+ zrc2_proxy.call("Transfer", to, tokens.toUint128());
+ return true;
+ }
+
+ /**
+ * @notice Transfer tokens from one address to another
+ * @param from The address to transfer from
+ * @param to The address to transfer to
+ * @param tokens The amount of tokens to transfer
+ * @return true if transfer was successful
+ */
+ function transferFrom(
+ address from,
+ address to,
+ uint256 tokens
+ ) external returns (bool) {
+ zrc2_proxy.call("TransferFrom", from, to, tokens.toUint128());
+ return true;
+ }
+
+ /**
+ * @notice Check the amount of tokens that an owner has allowed a spender to use
+ * @param tokenOwner The address of the token owner
+ * @param spender The address of the spender
+ * @return The amount of tokens remaining for the spender
+ */
+ function allowance(
+ address tokenOwner,
+ address spender
+ ) external view returns (uint256) {
+ return
+ zrc2_proxy.readNestedMapUint128("allowances", tokenOwner, spender);
+ }
+
+ /**
+ * @notice Approve a spender to spend a certain amount of tokens
+ * @param spender The address of the spender
+ * @param new_allowance The new allowance for the spender
+ * @return true if approval was successful
+ */
+ function approve(
+ address spender,
+ uint256 new_allowance
+ ) external returns (bool) {
+ uint256 current_allowance = this.allowance(msg.sender, spender);
+
+ if (current_allowance >= new_allowance) {
+ zrc2_proxy.call(
+ "DecreaseAllowance",
+ spender,
+ (current_allowance - new_allowance).toUint128()
+ );
+ } else {
+ zrc2_proxy.call(
+ "IncreaseAllowance",
+ spender,
+ (new_allowance - current_allowance).toUint128()
+ );
+ }
+ return true;
+ }
+}