일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 컨트렉트 동일한 함수이름 호출
- ethers websocket
- multicall
- 오블완
- vue기초
- 스마트컨트렉트테스트
- 러스트 기초 학습
- git rebase
- 체인의정석
- ethers typescript
- Vue
- 머신러닝기초
- ambiguous function description
- 티스토리챌린지
- Vue.js
- 러스트 기초
- rust 기초
- 러스트기초
- 계정추상화
- erc4337
- chainlink 설명
- erc4337 contract
- 스마트컨트렉트 함수이름 중복 호출
- 스마트컨트렉트 예약어 함수이름 중복
- 컨트렉트 배포 자동화
- ethers type
- SBT표준
- 스마트 컨트렉트 함수이름 중복
- ethers
- ethers v6
- Today
- Total
체인의정석
폴리곤 브릿지 컨트렉트에서 ERC20 및 이더리움을 다루는 코드 분석 본문
참고 : 폴리곤 pos portal 소스코드 중 RootChainManager.sol
폴리곤의 pos portal에서는
delegate Call을 썼다. 주석을 보면 프록시 배포를 사용했기 때문에 사용했다고 하는 Call이 사용되고 있다.
하지만 프록시 배포를 사용하는 상황이 아니라면 이더리움을 직접 전송할 때는 transfer를 사용하는 것이 가장 좋다.
먼저 위는 일반 erc20 토큰을 예치할 때의 과정 아래는 이더리움과 같은 암호화폐를 예치할 때의 과정인데 결국 _deplositFor를 부르는 것은 동일하다. 여기서 넣는 ETHER ADDRSS의 경우 컨트렉트 상단에 명시되어 있는데
address public constant ETHER_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
이런식으로 되어 있다. 이건 주소 값을 임의로 정해 놓은 모양으로 보인다.
그리고 이렇게 임의로 정해놓은 주소값을 rootToken 자리에 넣어준다.
원래 rootToken은 erc20 토큰 주소를 받아서 해당 주소를 tokenToType 이라는 구조체에서 주소를 넣으면 type이 나오고 그 type을 키 값으로 해서 CA값이 키 값으로 나오게 되면 해당 컨트렉트로 call을 통해 이더리움을 보내게 된다.
먼저 공개 되어 있는 함수의 경우 depositEtherFor를 사용한 후 external override payable을 사용하여서 외부에서 접근한 후 payable을 명시하여서 호출하고 있다.
/**
* @notice Move ether from root to child chain, accepts ether transfer
* Keep in mind this ether cannot be used to pay gas on child chain
* Use Matic tokens deposited using plasma mechanism for that
* @param user address of account that should receive WETH on child chain
*/
function depositEtherFor(address user) external override payable {
_depositEtherFor(user);
}
/**
* @notice Move tokens from root to child chain
* @dev This mechanism supports arbitrary tokens as long as its predicate has been registered and the token is mapped
* @param user address of account that should receive this deposit on child chain
* @param rootToken address of token that is being deposited
* @param depositData bytes data that is sent to predicate and child token contracts to handle deposit
*/
function depositFor(
address user,
address rootToken,
bytes calldata depositData
) external override {
require(
rootToken != ETHER_ADDRESS,
"RootChainManager: INVALID_ROOT_TOKEN"
);
_depositFor(user, rootToken, depositData);
}
function _depositEtherFor(address user) private {
bytes memory depositData = abi.encode(msg.value);
_depositFor(user, ETHER_ADDRESS, depositData);
// payable(typeToPredicate[tokenToType[ETHER_ADDRESS]]).transfer(msg.value);
// transfer doesn't work as expected when receiving contract is proxified so using call
(bool success, /* bytes memory data */) = typeToPredicate[tokenToType[ETHER_ADDRESS]].call{value: msg.value}("");
if (!success) {
revert("RootChainManager: ETHER_TRANSFER_FAILED");
}
}
그래서 결국
1. 프록시 배포를 사용한 후 call 사용
2. 프록시 배포를 사용안 할 시에는 tranfer 사용
이렇게 하면 되는것 같다.
아무튼 모든 종류의 토큰(이더리움도 마찬가지)은 _depositFor을 통해서 TypeToPredicate를 통해 미리 등록해둔 타입별 컨트렉트 주소로 보내버린다.
function _depositFor(
address user,
address rootToken,
bytes memory depositData
) private {
bytes32 tokenType = tokenToType[rootToken];
require(
rootToChildToken[rootToken] != address(0x0) &&
tokenType != 0,
"RootChainManager: TOKEN_NOT_MAPPED"
);
address predicateAddress = typeToPredicate[tokenType];
require(
predicateAddress != address(0),
"RootChainManager: INVALID_TOKEN_TYPE"
);
require(
user != address(0),
"RootChainManager: INVALID_USER"
);
ITokenPredicate(predicateAddress).lockTokens(
_msgSender(),
user,
rootToken,
depositData
);
bytes memory syncData = abi.encode(user, rootToken, depositData);
_stateSender.syncState(
childChainManagerAddress,
abi.encode(DEPOSIT, syncData)
);
}
여기서 보내지는 토큰의 종류로는
이와 같이 ERC20, 721, 1155 그리고 네이티브 토큰(가스 토큰) 등이 있다.
이러한 컨트렉트들은 직접 토큰을 보관하는 금고 역할을 하는 컨트렉트기 때문에 safeTrasferFrom을 사용해야 하며,
한번 ERC20 과 Ether를 살펴보도록 하겠다.
ERC20 Predicate
contract ERC20Predicate is ITokenPredicate, AccessControlMixin, Initializable {
using RLPReader for bytes;
using RLPReader for RLPReader.RLPItem;
using SafeERC20 for IERC20;
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
bytes32 public constant TOKEN_TYPE = keccak256("ERC20");
bytes32 public constant TRANSFER_EVENT_SIG = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
event LockedERC20(
address indexed depositor,
address indexed depositReceiver,
address indexed rootToken,
uint256 amount
);
constructor() public {}
function initialize(address _owner) external initializer {
_setupContractId("ERC20Predicate");
_setupRole(DEFAULT_ADMIN_ROLE, _owner);
_setupRole(MANAGER_ROLE, _owner);
}
/**
* @notice Lock ERC20 tokens for deposit, callable only by manager
* @param depositor Address who wants to deposit tokens
* @param depositReceiver Address (address) who wants to receive tokens on child chain
* @param rootToken Token which gets deposited
* @param depositData ABI encoded amount
*/
function lockTokens(
address depositor,
address depositReceiver,
address rootToken,
bytes calldata depositData
)
external
override
only(MANAGER_ROLE)
{
uint256 amount = abi.decode(depositData, (uint256));
emit LockedERC20(depositor, depositReceiver, rootToken, amount);
IERC20(rootToken).safeTransferFrom(depositor, address(this), amount);
}
/**
* @notice Validates log signature, from and to address
* then sends the correct amount to withdrawer
* callable only by manager
* @param rootToken Token which gets withdrawn
* @param log Valid ERC20 burn log from child chain
*/
function exitTokens(
address,
address rootToken,
bytes memory log
)
public
override
only(MANAGER_ROLE)
{
RLPReader.RLPItem[] memory logRLPList = log.toRlpItem().toList();
RLPReader.RLPItem[] memory logTopicRLPList = logRLPList[1].toList(); // topics
require(
bytes32(logTopicRLPList[0].toUint()) == TRANSFER_EVENT_SIG, // topic0 is event sig
"ERC20Predicate: INVALID_SIGNATURE"
);
address withdrawer = address(logTopicRLPList[1].toUint()); // topic1 is from address
require(
address(logTopicRLPList[2].toUint()) == address(0), // topic2 is to address
"ERC20Predicate: INVALID_RECEIVER"
);
IERC20(rootToken).safeTransfer(
withdrawer,
logRLPList[2].toUint() // log data field
);
}
}
일단 Lock을 보니 encode를 해둔 데이터를 decode 하여 숫자만 받아와서 해당 수량 만큼 예치해 둔 것을 볼 수 있었고 exitToken 에서는 withdrawer 와 받는 양 만큼을 받아와서 보내주는 것을 볼 수 있다. 이때 ERC20 burn log를 가져와서 타당한지 확인하는데 이렇게 로그를 가져와서 검사하는 로직은 자체적인 브릿지에서의 검증 로직에서 불필요할 경우 생략해도 되므로 패스하였다.
Ether predicate
// File: contracts/root/TokenPredicates/EtherPredicate.sol
pragma solidity 0.6.6;
contract EtherPredicate is ITokenPredicate, AccessControlMixin, Initializable {
using RLPReader for bytes;
using RLPReader for RLPReader.RLPItem;
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
bytes32 public constant TOKEN_TYPE = keccak256("Ether");
bytes32 public constant TRANSFER_EVENT_SIG = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
event LockedEther(
address indexed depositor,
address indexed depositReceiver,
uint256 amount
);
event ExitedEther(
address indexed exitor,
uint256 amount
);
constructor() public {}
function initialize(address _owner) external initializer {
_setupContractId("EtherPredicate");
_setupRole(DEFAULT_ADMIN_ROLE, _owner);
_setupRole(MANAGER_ROLE, _owner);
}
/**
* @notice Receive Ether to lock for deposit, callable only by manager
*/
receive() external payable only(MANAGER_ROLE) {}
/**
* @notice handle ether lock, callable only by manager
* @param depositor Address who wants to deposit tokens
* @param depositReceiver Address (address) who wants to receive tokens on child chain
* @param depositData ABI encoded amount
*/
function lockTokens(
address depositor,
address depositReceiver,
address,
bytes calldata depositData
)
external
override
only(MANAGER_ROLE)
{
uint256 amount = abi.decode(depositData, (uint256));
emit LockedEther(depositor, depositReceiver, amount);
}
/**
* @notice Validates log signature, from and to address
* then sends the correct amount to withdrawer
* callable only by manager
* @param log Valid ERC20 burn log from child chain
*/
function exitTokens(
address,
address,
bytes memory log
)
public
override
only(MANAGER_ROLE)
{
RLPReader.RLPItem[] memory logRLPList = log.toRlpItem().toList();
RLPReader.RLPItem[] memory logTopicRLPList = logRLPList[1].toList(); // topics
require(
bytes32(logTopicRLPList[0].toUint()) == TRANSFER_EVENT_SIG, // topic0 is event sig
"EtherPredicate: INVALID_SIGNATURE"
);
address withdrawer = address(logTopicRLPList[1].toUint()); // topic1 is from address
require(
address(logTopicRLPList[2].toUint()) == address(0), // topic2 is to address
"EtherPredicate: INVALID_RECEIVER"
);
emit ExitedEther(withdrawer, logRLPList[2].toUint());
(bool success, /* bytes memory data */) = withdrawer.call{value: logRLPList[2].toUint()}("");
if (!success) {
revert("EtherPredicate: ETHER_TRANSFER_FAILED");
}
}
}
Ether의 경우 다른 것은 크게 없었지만 , exited Ether 에서 withdrawer가 call을 해서 이더리움을 보내는 부분이 달랐다.
'블록체인 > NFT & BRIDGE' 카테고리의 다른 글
SBT와 관련된 표준 ERC 정리) 소각 권한을 상황에 맞게 분리시켜주는 ERC-5484 (0) | 2023.09.14 |
---|---|
SBT와 관련된 표준 ERC 정리) 최소한의 표준인 ERC-5192 & 만료기한과 권한을 추가시켜서 관리하는 ERC-6147 설명 (0) | 2023.09.14 |
ethers에서 원하는 주소로 eip712 서명하기 (테스트 코드가 아닌 이미 존재하는 지갑 주소로 eip712 형태의 서명하는 방법) (0) | 2022.07.29 |
EIP712 사용하여 오더북 만들기, EIP712 사용법 프론트엔드에서 스마트컨트렉트까지 (0) | 2022.07.20 |
Solidity에서 enum 타입이 다뤄지는 법과 EIP712 형태의 서명에서 enum 다루기 (0) | 2022.07.01 |