Skip to content

Commit

Permalink
rename turnstone to compass (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
webelf101 committed Sep 3, 2023
1 parent ec0c6cc commit 6776efb
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 65 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -9,7 +9,7 @@ Usage example:
- You send 25 DAI to the Compass-EVM contract, specifying which address on the Paloma chain should recieve the synthetic DAI.
- Validators on the Paloma blockchain see that this has happened and mint 25 synthetic DAI for the address you specified on the Paloma chain.
- You send the 25 synthetic DAI to Jim on the Paloma chain.
- Jim sends the synthetic DAI to Turnstone module on the Paloma blockchain, specifying which Ethereum address should receive it.
- Jim sends the synthetic DAI to Compass module on the Paloma blockchain, specifying which Ethereum address should receive it.
- The Paloma validators burn the synthetic DAI on the Paloma blockchain and unlock 25 DAI for Jim on Ethereum

## Security model
Expand Down
28 changes: 14 additions & 14 deletions contracts/Turnstone.vy → contracts/Compass.vy
@@ -1,6 +1,6 @@
# @version 0.3.7
"""
@title Turnstone-EVM
@title Compass-EVM
@author Volume.Finance
"""

Expand All @@ -9,7 +9,7 @@ MAX_PAYLOAD: constant(uint256) = 20480
MAX_BATCH: constant(uint256) = 64

POWER_THRESHOLD: constant(uint256) = 2_863_311_530 # 2/3 of 2^32, Validator powers will be normalized to sum to 2 ^ 32 in every valset update.
TURNSTONE_ID: immutable(bytes32)
COMPASS_ID: immutable(bytes32)

interface ERC20:
def balanceOf(_owner: address) -> uint256: view
Expand Down Expand Up @@ -66,11 +66,11 @@ last_checkpoint: public(bytes32)
last_valset_id: public(uint256)
message_id_used: public(HashMap[uint256, bool])

# turnstone_id: unique identifier for turnstone instance
# compass_id: unique identifier for compass instance
# valset: initial validator set
@external
def __init__(turnstone_id: bytes32, valset: Valset):
TURNSTONE_ID = turnstone_id
def __init__(compass_id: bytes32, valset: Valset):
COMPASS_ID = compass_id
cumulative_power: uint256 = 0
i: uint256 = 0
# check cumulative power is enough
Expand All @@ -80,15 +80,15 @@ def __init__(turnstone_id: bytes32, valset: Valset):
break
i += 1
assert cumulative_power >= POWER_THRESHOLD, "Insufficient Power"
new_checkpoint: bytes32 = keccak256(_abi_encode(valset.validators, valset.powers, valset.valset_id, turnstone_id, method_id=method_id("checkpoint(address[],uint256[],uint256,bytes32)")))
new_checkpoint: bytes32 = keccak256(_abi_encode(valset.validators, valset.powers, valset.valset_id, compass_id, method_id=method_id("checkpoint(address[],uint256[],uint256,bytes32)")))
self.last_checkpoint = new_checkpoint
self.last_valset_id = valset.valset_id
log ValsetUpdated(new_checkpoint, valset.valset_id)

@external
@pure
def turnstone_id() -> bytes32:
return TURNSTONE_ID
def compass_id() -> bytes32:
return COMPASS_ID

# utility function to verify EIP712 signature
@internal
Expand Down Expand Up @@ -116,13 +116,13 @@ def check_validator_signatures(consensus: Consensus, hash: bytes32):
# A checkpoint is a hash of all relevant information about the valset. This is stored by the contract,
# instead of storing the information directly. This saves on storage and gas.
# The format of the checkpoint is:
# keccak256 hash of abi_encoded checkpoint(validators[], powers[], valset_id, turnstone_id)
# keccak256 hash of abi_encoded checkpoint(validators[], powers[], valset_id, compass_id)
# The validator powers must be decreasing or equal. This is important for checking the signatures on the
# next valset, since it allows the caller to stop verifying signatures once a quorum of signatures have been verified.
@internal
@view
def make_checkpoint(valset: Valset) -> bytes32:
return keccak256(_abi_encode(valset.validators, valset.powers, valset.valset_id, TURNSTONE_ID, method_id=method_id("checkpoint(address[],uint256[],uint256,bytes32)")))
return keccak256(_abi_encode(valset.validators, valset.powers, valset.valset_id, COMPASS_ID, method_id=method_id("checkpoint(address[],uint256[],uint256,bytes32)")))

# This updates the valset by checking that the validators in the current valset have signed off on the
# new valset. The signatures supplied are the signatures of the current valset over the checkpoint hash
Expand Down Expand Up @@ -163,8 +163,8 @@ def submit_logic_call(consensus: Consensus, args: LogicCallArgs, message_id: uin
self.message_id_used[message_id] = True
# check if the supplied current validator set matches the saved checkpoint
assert self.last_checkpoint == self.make_checkpoint(consensus.valset), "Incorrect Checkpoint"
# signing data is keccak256 hash of abi_encoded logic_call(args, message_id, turnstone_id, deadline)
args_hash: bytes32 = keccak256(_abi_encode(args, message_id, TURNSTONE_ID, deadline, method_id=method_id("logic_call((address,bytes),uint256,bytes32,uint256)")))
# signing data is keccak256 hash of abi_encoded logic_call(args, message_id, compass_id, deadline)
args_hash: bytes32 = keccak256(_abi_encode(args, message_id, COMPASS_ID, deadline, method_id=method_id("logic_call((address,bytes),uint256,bytes32,uint256)")))
# check if enough validators signed args_hash
self.check_validator_signatures(consensus, args_hash)
# make call to logic contract
Expand Down Expand Up @@ -212,8 +212,8 @@ def submit_batch(consensus: Consensus, token: address, args: TokenSendArgs, mess
self.message_id_used[message_id] = True
# check if the supplied current validator set matches the saved checkpoint
assert self.last_checkpoint == self.make_checkpoint(consensus.valset), "Incorrect Checkpoint"
# signing data is keccak256 hash of abi_encoded logic_call(args, message_id, turnstone_id, deadline)
args_hash: bytes32 = keccak256(_abi_encode(token, args, message_id, TURNSTONE_ID, deadline, method_id=method_id("batch_call(address,(address[],uint256[]),uint256,bytes32,uint256)")))
# signing data is keccak256 hash of abi_encoded logic_call(args, message_id, compass_id, deadline)
args_hash: bytes32 = keccak256(_abi_encode(token, args, message_id, COMPASS_ID, deadline, method_id=method_id("batch_call(address,(address[],uint256[]),uint256,bytes32,uint256)")))
# check if enough validators signed args_hash
self.check_validator_signatures(consensus, args_hash)
# make call to logic contract
Expand Down
6 changes: 3 additions & 3 deletions scripts/deploy.py
@@ -1,10 +1,10 @@
from brownie import accounts, Turnstone
from brownie import accounts, Compass
from eth_abi import encode_abi

def main():
acct = accounts.load("deployer_account")
validators = [] # should update
powers = [] # should update
valset_id = 0 # should update
turnstone_id = b"ETH_01" # should update
Turnstone.deploy(encode_abi(["bytes32"], [turnstone_id]), [validators, powers, valset_id],{"from": acct})
compass_id = b"ETH_01" # should update
Compass.deploy(encode_abi(["bytes32"], [compass_id]), [validators, powers, valset_id],{"from": acct})
6 changes: 3 additions & 3 deletions tests/conftest.py
@@ -1,14 +1,14 @@
#!/usr/bin/python3

import pytest
from brownie import accounts, Turnstone, TestERC20, web3
from brownie import accounts, Compass, TestERC20, web3
from eth_abi import encode_abi
from eth_account import Account
from eth_account.messages import encode_defunct

@pytest.fixture
def TurnstoneContract(validators, powers):
return Turnstone.deploy(bstring2bytes32(b"ETH_01"), [validators, powers, 0], {"from": accounts[0]})
def CompassContract(validators, powers):
return Compass.deploy(bstring2bytes32(b"ETH_01"), [validators, powers, 0], {"from": accounts[0]})

@pytest.fixture
def TestERC20Contract():
Expand Down
4 changes: 2 additions & 2 deletions tests/test_deploy.py
Expand Up @@ -4,5 +4,5 @@

from conftest import *

def test_deploy(TurnstoneContract):
assert TurnstoneContract.turnstone_id().decode("utf8") == "ETH_01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
def test_deploy(CompassContract):
assert CompassContract.compass_id().decode("utf8") == "ETH_01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
62 changes: 31 additions & 31 deletions tests/test_submit_logic_call.py
Expand Up @@ -5,27 +5,27 @@
from conftest import *
from brownie.network.state import Chain

def test_submit_logic_call_success(TurnstoneContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(TurnstoneContract, 10 ** 18, {"from": accounts[0]})
def test_submit_logic_call_success(CompassContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(CompassContract, 10 ** 18, {"from": accounts[0]})
transfer_amount = 5 * 10 ** 17
func_sig = function_signature("transfer(address,uint256)")
enc_abi = encode_abi(["address", "uint256"], [accounts[1].address, transfer_amount])
payload = func_sig + enc_abi
message_id = 1000
valset_id = 0
func_sig = function_signature("logic_call((address,bytes),uint256,bytes32,uint256)")
turnstone_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, turnstone_id, 2 ** 256 - 1])
compass_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, compass_id, 2 ** 256 - 1])
hash = web3.keccak(func_sig + enc_abi)
sigs = sign_hash(validators, hash)
TurnstoneContract.submit_logic_call(
CompassContract.submit_logic_call(
[[[validators[0].address, validators[1].address, validators[2].address, validators[3].address], powers, valset_id], sigs], [TestERC20Contract, payload], message_id, 2 ** 256 - 1,
{"from": accounts[0]}
)
assert TestERC20Contract.balanceOf(accounts[1]) == transfer_amount

def test_submit_logic_call_timeout_revert(TurnstoneContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(TurnstoneContract, 10 ** 18, {"from": accounts[0]})
def test_submit_logic_call_timeout_revert(CompassContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(CompassContract, 10 ** 18, {"from": accounts[0]})
transfer_amount = 5 * 10 ** 17
func_sig = function_signature("transfer(address,uint256)")
enc_abi = encode_abi(["address", "uint256"], [accounts[1].address, transfer_amount])
Expand All @@ -35,96 +35,96 @@ def test_submit_logic_call_timeout_revert(TurnstoneContract, TestERC20Contract,
chain = Chain()
timestamp = chain.time()
func_sig = function_signature("logic_call((address,bytes),uint256,bytes32,uint256)")
turnstone_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, turnstone_id, timestamp - 1])
compass_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, compass_id, timestamp - 1])
hash = web3.keccak(func_sig + enc_abi)
sigs = sign_hash(validators, hash)
with brownie.reverts("Timeout"):
TurnstoneContract.submit_logic_call(
CompassContract.submit_logic_call(
[[[validators[0].address, validators[1].address, validators[2].address, validators[3].address], powers, valset_id], sigs], [TestERC20Contract, payload], message_id, timestamp - 1,
{"from": accounts[0]}
)

def test_submit_logic_call_used_message_id_revert(TurnstoneContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(TurnstoneContract, 10 ** 18, {"from": accounts[0]})
def test_submit_logic_call_used_message_id_revert(CompassContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(CompassContract, 10 ** 18, {"from": accounts[0]})
transfer_amount = 5 * 10 ** 17
func_sig = function_signature("transfer(address,uint256)")
enc_abi = encode_abi(["address", "uint256"], [accounts[1].address, transfer_amount])
payload = func_sig + enc_abi
message_id = 1000
valset_id = 0
func_sig = function_signature("logic_call((address,bytes),uint256,bytes32,uint256)")
turnstone_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, turnstone_id, 2 ** 256 - 1])
compass_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, compass_id, 2 ** 256 - 1])
hash = web3.keccak(func_sig + enc_abi)
sigs = sign_hash(validators, hash)
TurnstoneContract.submit_logic_call(
CompassContract.submit_logic_call(
[[[validators[0].address, validators[1].address, validators[2].address, validators[3].address], powers, valset_id], sigs], [TestERC20Contract, payload], message_id, 2 ** 256 - 1,
{"from": accounts[0]}
)
with brownie.reverts("Used Message_ID"):
TurnstoneContract.submit_logic_call(
CompassContract.submit_logic_call(
[[[validators[0].address, validators[1].address, validators[2].address, validators[3].address], powers, valset_id], sigs], [TestERC20Contract, payload], message_id, 2 ** 256 - 1,
{"from": accounts[0]}
)

def test_submit_logic_call_incorrect_checkpoint_revert(TurnstoneContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(TurnstoneContract, 10 ** 18, {"from": accounts[0]})
def test_submit_logic_call_incorrect_checkpoint_revert(CompassContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(CompassContract, 10 ** 18, {"from": accounts[0]})
transfer_amount = 5 * 10 ** 17
func_sig = function_signature("transfer(address,uint256)")
enc_abi = encode_abi(["address", "uint256"], [accounts[1].address, transfer_amount])
payload = func_sig + enc_abi
message_id = 1000
valset_id = 0
func_sig = function_signature("logic_call((address,bytes),uint256,bytes32,uint256)")
turnstone_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, turnstone_id, 2 ** 256 - 1])
compass_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, compass_id, 2 ** 256 - 1])
hash = web3.keccak(func_sig + enc_abi)
sigs = sign_hash(validators, hash)
powers[0] -= 1
with brownie.reverts("Incorrect Checkpoint"):
TurnstoneContract.submit_logic_call(
CompassContract.submit_logic_call(
[[[validators[0].address, validators[1].address, validators[2].address, validators[3].address], powers, valset_id], sigs], [TestERC20Contract, payload], message_id, 2 ** 256 - 1,
{"from": accounts[0]}
)

def test_submit_logic_call_invalid_signature(TurnstoneContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(TurnstoneContract, 10 ** 18, {"from": accounts[0]})
def test_submit_logic_call_invalid_signature(CompassContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(CompassContract, 10 ** 18, {"from": accounts[0]})
transfer_amount = 5 * 10 ** 17
func_sig = function_signature("transfer(address,uint256)")
enc_abi = encode_abi(["address", "uint256"], [accounts[1].address, transfer_amount])
payload = func_sig + enc_abi
message_id = 1000
valset_id = 0
func_sig = function_signature("logic_call((address,bytes),uint256,bytes32,uint256)")
turnstone_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, turnstone_id, 2 ** 256 - 1])
compass_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, compass_id, 2 ** 256 - 1])
hash = web3.keccak(func_sig + enc_abi)
sigs = sign_hash(validators, hash)
sigs[0][0] = 1
with brownie.reverts("Invalid Signature"):
TurnstoneContract.submit_logic_call(
CompassContract.submit_logic_call(
[[[validators[0].address, validators[1].address, validators[2].address, validators[3].address], powers, valset_id], sigs], [TestERC20Contract, payload], message_id, 2 ** 256 - 1,
{"from": accounts[0]}
)

def test_submit_logic_call_insufficient_power(TurnstoneContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(TurnstoneContract, 10 ** 18, {"from": accounts[0]})
def test_submit_logic_call_insufficient_power(CompassContract, TestERC20Contract, validators, powers, accounts):
TestERC20Contract.transfer(CompassContract, 10 ** 18, {"from": accounts[0]})
transfer_amount = 5 * 10 ** 17
func_sig = function_signature("transfer(address,uint256)")
enc_abi = encode_abi(["address", "uint256"], [accounts[1].address, transfer_amount])
payload = func_sig + enc_abi
message_id = 1000
valset_id = 0
func_sig = function_signature("logic_call((address,bytes),uint256,bytes32,uint256)")
turnstone_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, turnstone_id, 2 ** 256 - 1])
compass_id = bstring2bytes32(b"ETH_01")
enc_abi = encode_abi(["(address,bytes)", "uint256", "bytes32", "uint256"], [[TestERC20Contract.address, payload], message_id, compass_id, 2 ** 256 - 1])
hash = web3.keccak(func_sig + enc_abi)
sigs = sign_hash(validators, hash)
sigs[0][0] = 0
sigs[1][0] = 0
with brownie.reverts("Insufficient Power"):
TurnstoneContract.submit_logic_call(
CompassContract.submit_logic_call(
[[[validators[0].address, validators[1].address, validators[2].address, validators[3].address], powers, valset_id], sigs], [TestERC20Contract, payload], message_id, 2 ** 256 - 1,
{"from": accounts[0]}
)

0 comments on commit 6776efb

Please sign in to comment.